简单

locke-treasure

逆向狂喜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void __fastcall decrypt_flag_to_buf(
const unsigned __int8 *enc,
int enc_len,
const char *key,
char *out_buf,
int out_buf_len)
{
int key_len; // [rsp+24h] [rbp+4h]
int i; // [rsp+44h] [rbp+24h]

j___CheckForDebuggerJustMyCode(&_68090DB3_calc_hide_c);
key_len = j_strlen_0(key);
if ( out_buf_len >= enc_len + 1 )
{
for ( i = 0; i < enc_len; ++i )
out_buf[i] = key[i % key_len] ^ enc[i];
out_buf[enc_len] = 0;
}
else if ( out_buf_len > 0 )
{
*out_buf = 0;
}
}

这是关键函数,key和enc均给了

(这道题好像只能静态分析,密码是abc123,但是却让输入纯数字)

exp:

1
2
3
4
5
6
enc=[0x7, 0xe, 0x2, 0x56, 0x49, 0x44, 0x8, 0xc, 0x3c, 0x59, 0x53, 0x40, 0x9, 0x3d, 0x1, 0x48, 0x42, 0x52, 0x12, 0x11, 0x1e]
flag=''
key="abc123"
for i in range(len(enc)):
flag+=chr(ord(key[i%6])^enc[i])
print(flag)

智能门锁里的秘钥信息

打开二进制程序

看到敏感函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char *get_encryption_key()
{
_BYTE c4t_[11]; // [rsp+4h] [rbp-1Ch] BYREF
char _15_cut3_[17]; // [rsp+Fh] [rbp-11h] BYREF

*(_QWORD *)&_15_cut3_[9] = __readfsqword(0x28u);
strcpy(&c4t_[5], "d0g3_");
strcpy(c4t_, "c4t_");
strcpy(_15_cut3_, "15_cut3!");
strcpy(final_key_0, &c4t_[5]);
strcat(final_key_0, c4t_);
strcat(final_key_0, _15_cut3_);
byte_4030 = 0;
return final_key_0;
}

提取出密钥d0g3_c4t_15_cut3!一般取前16字节

脚本如下:

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
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import os

def decrypt_file(input_file, output_file):
raw_key_string = "d0g3_c4t_15_cut3!"
key = raw_key_string.encode('utf-8')[:16]
iv = b'\x00' * 16
try:
with open(input_file, 'rb') as f:
ciphertext = f.read()
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
with open(output_file, 'wb') as f:
f.write(decrypted_data)

print(f"[*] 解密成功!解密后的文件已保存为: {output_file}")
except Exception as e:
print(f"[-] 发生错误: {e}")

if __name__ == '__main__':
ENCRYPTED_FILE = 'log_encrypted.bin'
DECRYPTED_FILE = 'decrypted_data.txt'

if os.path.exists(ENCRYPTED_FILE):
decrypt_file(ENCRYPTED_FILE, DECRYPTED_FILE)
else:
print(f"[-] 找不到加密文件: {ENCRYPTED_FILE},请检查路径。")

提取flag

在010都异或0x9A,紧跟着payload后面出来flag

image-20260418132814760

WiFi抓包

搜索flag字符找到以下数据包:
image-20260418131246486

经过URL解码是flag{asfhwoefiajsfhowea}

咖啡店的虚假WiFi登录页面

需要找到:

  1. 受害者MAC地址的前6位十六进制字符(去掉冒号)
  2. 钓鱼页面域名(不含http://)
  3. 攻击者窃取到的密码
  4. 攻击时间戳密码数据包的最后4位数字(UNIX时间戳)

第一个根据前三个数据包就能看出来了,第一个数据包是路由器周期性对外广播自己的 WiFi 信息,第二个是Authentication 认证帧,设备向路由器发送身份检验

image-20260418111748053

因此我的设备MAC前六位十六进制字符就是112233

第二个需要找钓鱼页面域名:
image-20260418120129296

攻击者窃取的密码在下图找到了:
image-20260418120114781

时间戳就是上图的数据包的时间戳,在左侧分组详情查看即可

最终答案:flag{112233_starbucks-wifi-auth.com_MySecurePass123!_8000}

EasyRouter

某校园物联网实验室部署了一批入门级教学路由器(型号:EasyRouter-100),用于学生实践嵌入式设备配置与安全测试。为方便教师验证学生的固件分析能力,实验室技术人员通过 Flashrom 工具 读取了路由器 NOR Flash 芯片(型号 W25Q16JV,2MB 容量)的完整镜像,将通关验证 Flag 隐藏在固件文件系统中。
该路由器为简化教学,未启用复杂加密机制,仅对 Flag 做基础编码处理。现需你通过分析 Flash 镜像、提取文件系统,找到隐藏的 Flag,完成设备安全验证任务。

题目给了一个附件,binwalk提取固件得到flag.txt,内容如下:Q1RG{ZWFzeV9mbGFzaF8yMDI0}

BASE64解码即可,结果是flag{easy_flash_2024}

WiFi协议

题目只给了一个流量包,一个个找在这里发现了关键信息,疑似是flag

image-20260418113428549

结果是flag{2025_1123_wifi_polar}

透明的无线电

题目给了一个txt文本和一个C程序源码

C源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Device: Smart_Meter_V3

#include <lorawan.h>

// Device Address 0x26013300
uint8_t DEV_ADDR[4] = { 0x00, 0x33, 0x01, 0x26 };

// Network Session Key (NwkSKey) - Used for MIC check
uint8_t NWK_S_KEY[16] = { 0x00 }; // Redacted

// Application Session Key (AppSKey) - Used for Payload Encryption
// LEAKED!
uint8_t APP_S_KEY[16] = {
0x2B, 0x7E, 0x15, 0x16,0x28, 0xAE, 0xD2, 0xA6,0xAB, 0xF7, 0x15, 0x88,0x09, 0xCF, 0x4F, 0x3C
};

题目给的txt文件经过base解码和16进制,应该是协议报文:

1
40 00 33 01 26 00 05 00 01 d7 6b 07 06 5d 45 c9 bc d0 66 06 ca 11 40 4f 44 4b 9a 3b 93 31 aa bb cc dd

按 LoRaWAN 拆字段后:

  • 40:MHDR,表示这是一个 uplink 数据帧
  • 00 33 01 26:DevAddr,小端表示,正好是 0x26013300(在C源码表示地址)
  • 00:FCtrl
  • 05 00:FCnt = 5
  • 01:FPort = 1
  • 后面一段是 FRMPayload
  • 最后 aa bb cc dd 看起来是 MIC

刚开始题目附件上错了,flag计算的是错误的,但是到这里的思路是对的

接下来就是解密FRMPayload

LoRaWAN 的 FRMPayload 不是直接 AES-CBC 那种解法,而是AES-128 的流式异或解法。
核心是先构造一组 Ai 块,然后:

1
Si = aes128_encrypt(AppSKey, Ai)

最后:

1
明文 = 密文 XOR S
  • FRMPayload = d76b07065d45c6bbd30d0edf7f4d182c409e78c67e29d4d99fc960a60f27919a1a04eec98263

因为是 uplink,所以 Dir = 0

对应的 AiSi

1
2
3
4
5
6
7
8
A1 = 01000000000000330126050000000001
S1 = b10766612623a5ddb7396fb94e29211b

A2 = 01000000000000330126050000000002
S2 = 23ff49f64c11b0b8aafd04c73e15a8af

A3 = 01000000000000330126050000000003
S3 = 2232d6aab51edf927e8cd833a210c730

FRMPayloadS1|S2|S3 按长度逐字节异或,得到明文:

1
666c61677b66636664346166316439376361313032386461353464613132393538363863377d

转 ASCII 就是:

1
flag{fcfd4af1d97ca1028da54da1295868c7}

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
import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

appskey = bytes.fromhex("2B7E151628AED2A6ABF7158809CF4F3C")
pkt = base64.b64decode("QAAzASYABQAB12sHBl1FxrvTDQ7ff00YLECeeMZ+KdTZn8lgpg8nkZoaBO7JgmPerb7v")

devaddr = pkt[1:5]
fcnt = int.from_bytes(pkt[6:8], "little")
frmpayload = pkt[9:-4]
direction = 0 # uplink

cipher = Cipher(algorithms.AES(appskey), modes.ECB())
enc = cipher.encryptor()

keystream = b""
for i in range(1, (len(frmpayload) + 15) // 16 + 1):
Ai = (
b"\x01\x00\x00\x00\x00" +
bytes([direction]) +
devaddr +
fcnt.to_bytes(4, "little") +
b"\x00" +
bytes([i])
)
Si = enc.update(Ai)
print(f"A{i} = {Ai.hex()}")
print(f"S{i} = {Si.hex()}")
keystream += Si

plaintext = bytes(a ^ b for a, b in zip(frmpayload, keystream))
print(plaintext.decode())

中等

你会伪造吗

附件是一个gpx文件,看来是要篡改gpx文件伪造运动轨迹

基准对齐(Base Alignment):按原顺序包含 base.gpx 的 101 个原始点,未修改任何原始坐标。

点数要求:在原有点中插入了 2 个 CP 打卡点,并在末尾插入了 3 个占位点,总计正好 106 个点。

速度要求:时间戳从 09:00:00 开始,以严格的 6秒/点 均匀递增,直到 09:10:30 结束,完美满足最高速度要求。

这个真的不太了解,用gemini跑的

image-20260418180039799

智能盆栽固件分析

binwalk提取,找到隐藏的脚本:

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
#!/bin/bash
# PlantPal Secret Access Script
# This script is triggered by the diagnostic backdoor

echo ""
echo "╔══════════════════════════════════════════╗"
echo "║ SECRET GARDEN ACCESS ║"
echo "║ AUTHENTICATED ║"
echo "╚══════════════════════════════════════════╝"
echo ""
echo "[🔓] Backdoor authentication: OK"
echo "[🚀] Privilege escalation: OK"
echo "[📁] Accessing secure partition..."
echo ""
sleep 1

# Read the secret key
if [ -f /etc/plantpal_secret ]; then
SECRET_KEY=$(cat /etc/plantpal_secret)
echo "[✓] Secret key loaded: (hidden)"
else
SECRET_KEY="DEFAULT_KEY_BACKDOOR_123"
echo "[⚠] Using default secret key"
fi

# Generate flag
DEVICE_ID="PP-2024-"
MD5_HASH=$(echo -n "$SECRET_KEY" | md5sum | awk '{print $1}')
MD5_PREFIX=$(echo "$MD5_HASH" | cut -c1-8)
FLAG="flag{${DEVICE_ID}${MD5_PREFIX}}"

echo ""
echo "════════════════════════════════════════════"
echo " 🏴 FLAG: $FLAG"
echo "════════════════════════════════════════════"
echo ""
echo "[📝] Debug Information:"
echo " Device ID: $DEVICE_ID"
echo " Secret Key MD5: $MD5_HASH"
echo " MD5 Prefix (8 chars): $MD5_PREFIX"
echo ""
echo "[⚠️] Security Notice:"
echo " This backdoor will be removed in v1.1.0"
echo " Please secure your devices in production"
echo ""

以及一个秘密钥匙:MY_SECRET_BACKDOOR_KEY_1337

加密算法就是md5密钥取前八位再加上DEVICE-ID

我的flash

提示说厂商为了防止被直接扫描,采用了“多段分散 + 简单混淆”的方式存储敏感信息。

010打开,里面有大量的0XFF,那也就是说不是0XFF的很有可能是加密数据,甚至是密钥都是简单一字节,加密方式是异或

猜测有了,接下来就是开始答题

由于0XFF太多,让AI提取出不同位置的信息,如下:

第一段,0x2000 处按 0x4B 异或后:
FLAG{flashro

第二段,0x3F00 处按 0xFF 异或后:
_zlib_not}

第三段,0x9000 处是 zlib,解压后:
om_segmented_xor

答案:FLAG{flashroom_segmented_xor_zlib_not}

智能门锁协议分析

固件里面可以直接看到key:
image-20260418165501082

AES加密模式也给了。但是在这个固件之中,counter值是从1开始

下面是AI生成的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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# =========================
# 1. 题目里从固件提取出的 AES 密钥
# =========================
key = b"LOCK_KEY_2024_!!"

# =========================
# 2. 三条日志
# nonce 是 8 字节
# 密文是十六进制字符串
# =========================
records = [
{
"desc": "管理员开锁记录",
"nonce": "72c4e915062c70d3",
"ciphertext": "336b1e563c51bfa1b965307190596c76b406a86dd6ac38022450f4b248f65382d9c6a67a002f164aa6222d1b11ddd050462fe827139cf7365425a56c33eaab7e2504d2e5c567eff95687bed9f198dbbc"
},
{
"desc": "用户开锁记录",
"nonce": "a624068b03d3effb",
"ciphertext": "3f5f193d2aa20ece3bd76457245ede0851db9925684be457b4ef62563350ee9b6efee2a31f10992c5a64796fd611227591cee2f27362b6c6c8f33451576cf1d9a2385ac463b38ace677741ad5ad01268"
},
{
"desc": "失败尝试记录",
"nonce": "634e44a002b69715",
"ciphertext": "847d0c34d459eb05d062ff78901cf48fa2208459fe6ca4d6d4bc3452ff1e333bd8b66d209c84360634ac01df01aa981b919006eb87c1e95a2cb4206229f871b6a32ac26c2ecdc922fc3064119c3b1591"
}
]

# =========================
# 3. 去 PKCS7 填充
# 如果最后几个字节是合法填充,就去掉
# =========================
def remove_pkcs7_padding(data: bytes) -> bytes:
if len(data) == 0:
return data

pad_len = data[-1]

# pad_len 必须在 1~16 之间
if pad_len < 1 or pad_len > 16:
return data

# 最后 pad_len 个字节都要等于 pad_len
if data[-pad_len:] != bytes([pad_len]) * pad_len:
return data

return data[:-pad_len]

# =========================
# 4. AES-CTR 解密
# 这题的 IV = nonce(8字节) + counter(8字节)
# 注意:counter 初始值要从 1 开始
# =========================
def aes_ctr_decrypt(key: bytes, nonce_hex: str, ciphertext_hex: str) -> bytes:
nonce = bytes.fromhex(nonce_hex)
ciphertext = bytes.fromhex(ciphertext_hex)

# 计数器从 1 开始,占 8 字节,大端
counter_start = (1).to_bytes(8, "big")

# CTR 模式需要 16 字节初始块
iv = nonce + counter_start

cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()

return plaintext

# =========================
# 5. 解密全部日志
# =========================
admin_plaintext = None

for i, record in enumerate(records, 1):
plaintext = aes_ctr_decrypt(key, record["nonce"], record["ciphertext"])
plaintext = remove_pkcs7_padding(plaintext)

text = plaintext.decode("utf-8", errors="replace")

print(f"记录{i} - {record['desc']}")
print(text)
print("-" * 60)

if record["desc"] == "管理员开锁记录":
admin_plaintext = text

# =========================
# 6. 从管理员记录里提取 door 和 time
# 例如:
# DOOR:B208 CARD:ADMIN TIME:20240115_143022 ACTION:UNLOCK RESULT:SUCCESS
# =========================
if admin_plaintext:
parts = admin_plaintext.split()

door = None
time_str = None

for part in parts:
if part.startswith("DOOR:"):
door = part.split(":", 1)[1]
elif part.startswith("TIME:"):
time_str = part.split(":", 1)[1]

if door and time_str:
flag = f"flag{{{door}_{time_str}}}"
print("最终 flag =", flag)
else:
print("管理员记录解出来了,但没有成功提取 DOOR 或 TIME")
else:
print("没有找到管理员记录")

私钥解密

提取一下文件系统

找到一个提示文本,说密码在2025年中秋节(6)

一个文件夹内有10个脚本,应该有一个对应6,执行了以下命令:
openssl enc -des3 -nosalt -salt -in data.txt -out data.enc -k pass123

但是这个执行完报错,不是密码

密码经过尝试应该是123456才对

然后用RSA私钥解密

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pathlib import Path
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding

pem = Path("private_key_encrypted.pem").read_bytes()
enc = Path("flag.enc").read_bytes()

private_key = serialization.load_pem_private_key(
pem,
password=b"123456"
)

plaintext = private_key.decrypt(enc, padding.PKCS1v15())
print(plaintext.decode())

easy壳

壳逻辑在 FileUtils.splitPayLoadFromDex 里:读取 APK 的 classes.dex,整体 xor 0xff,末尾 4 字节是原始 dex 长度,然后从尾部切出真实 dex。

image-20260418195212845

找到真逻辑如下:

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
package com.example.iot2;

import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

/* loaded from: f:\Desktop\新建文件夹\yongye.dex */
public class MainActivity extends AppCompatActivity {
private final String CORRECT_PWD = "adsbu32cais";
private final String FLAG = "flag{sadbuy12368gdsabhu}";
private EditText et_pwd;

@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
this.et_pwd = (EditText) findViewById(R.id.et_password);
}

public void checkPassword(View view) {
if (this.et_pwd.getText().toString().trim().equals("adsbu32cais")) {
Toast.makeText(this, "flag{sadbuy12368gdsabhu}", 1).show();
} else {
Toast.makeText(this, "密码错误,请重试!", 0).show();
}
}
}

明文flag已经给出

固件锁匠

先说一个解法:010打开内部出现明文flag

image-20260418135703694

按照题目要求,我们应该找到这个代码的后门在哪里

image-20260418135938665

也就是说,只要传入指定参数,就可以绕过保护,命令如下所示

image-20260418140040447

LoRaWAN网络安全审计

这是一个py打包的exe

实际上解包反编译就出来了

image-20260418182956415

困难

有些题目看描述说是需要ESP32板子,就作罢了。只写了一些简单的

ApK47突突突

直接手撕so文件

通过JNI_ONload函数发现关键代码:

image-20260418182136854

这是密文image-20260418182115138

说白了反向逻辑就是 ((byte_36B9C[i + 1] - mod5 - n2)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
byte_36B9C = [
0x76, 0x65, 0x82, 0x76, 0x6B, 0x7D, 0x77, 0x66, 0x83,
0x77, 0x80, 0x74, 0x82, 0x76, 0x84, 0x73, 0x81, 0x75,
0x83, 0x24, 0x23, 0x21, 0x26, 0x23, 0x28, 0x73, 0x81,
0x22, 0x27
]
flag = "flag{a"
for i in range(28):
n2 = -1 if (i % 2 == 0) else 2
mod5 = (i + 1) % 5
char_val = ((byte_36B9C[i + 1] - mod5 - n2) & 0xFF) ^ 0x15
flag += chr(char_val)
flag += "}"
print(flag)

appreverse

检验逻辑应该在so层

image-20260418122125026

image-20260418122101570

厨子解决:
image-20260418122016443

最终答案:flag{wdiu23087fsebu4387fds}