Web

录播回放

简单的扫雷

  1. 进入后先尝试抓包,发现有一个 script.js

    image-20251122192932853

  2. 审计它的代码,我们会发现它有一个游戏结束的函数

    // 游戏结束
    function endGame(isWin) {
        gameState.gameOver = true;
        clearInterval(gameState.timerInterval);
        if (isWin) {
            // 增加对应难度的通关次数
            const currentDifficulty = getCurrentDifficulty();
            winCounts[currentDifficulty]++;
            // 检查是否满足获取flag的条件
            if (checkFlagCondition()) {
                // 创建 FormData 对象
                const formData = new FormData();
                formData.append("difficulty", "expert");
                fetch('flag.php', {
                    method: 'POST',
                    body: formData
                })
                    .then(response => response.json())
                    .then(data => {
                        if (data.success) {
                            alert(data.message + '\n' + data.flag);
                        } else {
                            alert(data.message);
                        }
                    })
                    .catch(error => {
                        console.error('Error:', error);
                        alert('恭喜你满足了获取flag的条件!\n请联系管理员获取flag。');
                    });
            } else {
                let winMessage = '恭喜你赢了!\n';
                winMessage += `当前通关次数:\n`;
                winMessage += `简单: ${winCounts.beginner}/3\n`;
                winMessage += `中级: ${winCounts.intermediate}/2\n`;
                winMessage += `困难: ${winCounts.expert}/1\n`;
    
                // 计算还需要多少次
                const neededExpert = Math.max(0, 1 - winCounts.expert);
                const neededIntermediate = Math.max(0, 2 - winCounts.intermediate);
                const neededBeginner = Math.max(0, 3 - winCounts.beginner);
    
                // 计算每种难度的完成百分比
                const percentExpert = (winCounts.expert / 1) * 100;
                const percentIntermediate = (winCounts.intermediate / 2) * 100;
                const percentBeginner = (winCounts.beginner / 3) * 100;
    
                if (neededExpert === 0 || neededIntermediate === 0 || neededBeginner === 0) {
                    // 已经满足条件之一
                    winMessage += "\n已满足获取flag条件!再来一局即可获取flag。";
                } else {
                    // 还未满足任何条件,显示进度最高的难度
                    const maxPercent = Math.max(percentExpert, percentIntermediate, percentBeginner);
                    if (maxPercent === percentExpert) {
                        winMessage += `\n困难难度进度: ${Math.round(percentExpert)}% (${winCounts.expert}/1)`;
                    } else if (maxPercent === percentIntermediate) {
                        winMessage += `\n中级难度进度: ${Math.round(percentIntermediate)}% (${winCounts.intermediate}/2)`;
                    } else {
                        winMessage += `\n简单难度进度: ${Math.round(percentBeginner)}% (${winCounts.beginner}/3)`;
                    }
                }
                alert(winMessage);
            }
    //....Code
    
  3. 尝试在终端运行它

    image-20251122193216282

    • 多尝试几次

    image-20251122193255066

    拿到 flag:aicx{m1n3sw33p3r_m4st3r_2025_[TEAM_HASH]}

    其中 [TEAM_HASH]会被自动替换为队伍 Token 和比赛信息所生成的哈希值

巧妙的绕过

  1. 进入后发现网址自动重定向到 http://[ip]:[port]/index.php?img=LFLVUMC2LBEXKY2HGVXA====中,首先 f12 查看源码

    image-20251122193906378

    对 img 变量值进行分析,发现他是先进行 Base64加密后又进行 Base32加密,通过解密后可得明文是 after.png

  2. 随后尝试将 img 值清空对比之前的变化

    image-20251122194013073

    我们可以初步确认 img 标签中的内容为网址中 img 变量解密后的文件名,那我们是不是可以尝试获取 index.php 内容

  3. 将明文为 index.php构造变量值 MFLTK222LBTXKY2HNB3Q====,并发送请求

    image-20251122194349947

    我们发现 img 标签中出现内容了,对其进行解密,其 index.php的部分内容为

    <?php
    //code
    header("Content-Type: text/html; charset=utf-8");
    
    if (!isset($_GET['img']))
        header('Refresh:0;url=./index.php?img=LFLVUMC2LBEXKY2HGVXA====');
    
    $file = base64_decode(base32_decode($_GET['img']));
    $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
    if (preg_match("/flag/i", $file)) {
        die("别妄想偷取flag");
    } else {
        $txt = base64_encode(file_get_contents($file));
        echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    }
    
    if (isset($_GET['a']) && isset($_GET['b'])) {
        $a = $_GET['a'];
        $b = $_GET['b'];
        if ($a != $b) {
            if (md5($a) === md5($b)) {
                echo "Very good, you're just one step away.";
                if (isset($_POST['cmd'])) {
                    $cmd = $_POST['cmd'];
                    system($cmd);
                }
            }
        }
    }
    ?>
    //code
    

    我们发现 php 有一个 get 请求,需要传入 a,b 变量,需要保证两者变量值不一样且其 md5 值相同,我们可以传入两个数组,因为 php 中数组不能对其 md5,只能返回 None,之后我们在 Post Data 中传入 cmd,系统就会执行我们的命令,我们尝试构造地址发送 POST 请求 http://[ip]:[port]/index.php?img=MFLTK222LBTXKY2HNB3Q====&a[]=1&b[]=2

    image-20251122202422272

    很显然我们拿到了根目录文件,再根据 index 中的描述,图中的 flag 是一个文件夹而不是文件,所以 cat 无法读取,我们再进行深入,即可拿到 flag

    image-20251122202618901

    flag 为 aicx{not_easy_bypass_success_[TEAM_HASH]}

    其中 [TEAM_HASH]会被自动替换为队伍 Token 和比赛信息所生成的哈希值

耄耋的图书馆

  1. 进入后查看网页

    image-20251122202903614

    由网页可知这题需要对其进行 xss 注入即可拿到 flag,但网页过滤了 <script>, <img>, <iframe>, onerror, onclick, onload, javascript:等一系列常见的 xss 注入

  2. 可以尝试用 <svg onload=alert(1)>的方式注入

    image-20251122203145367

flag 为 aicx{hajimi_success_beautiful_[TEAM_HASH]}

其中 [TEAM_HASH]会被自动替换为队伍 Token 和比赛信息所生成的哈希值

小彩蛋

  • 可以查看热门图书 XSS绕过技巧,并通过 http://[ip]:[port]/XSS绕过技巧即可获得以下密文
  • KBEE4MS2PFBHMYTNPB3FSV2RHFMVO6DMMNXFC32NKNVSW===
    
  • 通过Base32再Base64解密后获得明文 <svg onload=alert(1)>解出此题

基米好想你

  1. 进入后查看网页

    image-20251122204022641

    网页说检测到文件 flag.php,但是当进入 flag.php 后发现是空白网页,随即应该想到通过构造伪协议的方法去读取 flag.php 中的文件

  2. 尝试构造伪协议 http://[ip]:[port]/?file=php://filter/read=convert.base64-encode/resource=flag.php并访问

    详询可以参考PHP 文件包含 - 伪协议 - Lixiney's Blog

    image-20251122204641499

    通过 Base64 解码后获得源码

    <?php
    $aicx = "aicx{3x4mpl3_fl4g_TestTeamHash}";
    //我遗憾这故事又得结束在那下一页
    //可惜在看到你的那一瞬间,我的心脏停止了一秒
    //bWlzc3ko5q2k5Li65LqM5YiG5LmL5LiAKQ==
    ?>
    

flag 为 aicx{3x4mpl3_fl4g_[TEAM_HASH]}

其中 [TEAM_HASH]会被自动替换为队伍 Token 和比赛信息所生成的哈希值

大彩蛋

  1. 在一开始的页面中出现了 file=flag{N52TGNRVFZYGQ4A=}和 flag.php 的注释中 bWlzc3ko5q2k5Li65LqM5YiG5LmL5LiAKQ==,分别对其进行解密

    bWlzc3ko5q2k5Li65LqM5YiG5LmL5LiAKQ== --Base64---> missy(此为二分之一)

    N52TGNRVFZYGQ4A= --Base32---> ou365.php

  2. 拼接起来为 missyou365.php,去访问

    image-20251122205258464

    观察网页和网址,再结合首页一开始的歌词 我想你365天外加65秒,感觉越痛反而越珍惜在一起的每秒,正确的参数应该为 /missyou365.php?day=365&time=65

  3. 访问后出现

    image-20251122205459181

    查看源码

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>天真以为相处时间太长就无感</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                min-height: 100vh;
                margin: 0;
                background: linear-gradient(135deg, #f891eeff 0%, #5f08b6ff 100%);
                color: white;
                text-align: center;
            }
    
            .message {
                background: rgba(255, 255, 255, 0.1);
                padding: 30px;
                border-radius: 10px;
                backdrop-filter: blur(10px);
            }
    
            .btn {
                display: inline-block;
                padding: 10px 20px;
                margin-top: 20px;
                background: white;
                color: #667eea;
                text-decoration: none;
                border-radius: 5px;
            }
        </style>
    </head>
    <body>
        <div class="message">
            <h1>你能走到这一步很不容易</h1>
            <p>但是故事还没有结束,不要忽略每一个地方</p>
            <!--
            后来我才明白,ta在乎的是细节
            626.php
            我想起来了,那张照片
        -->
        </div>
    </body>
    </html>
    

    发现 626.php

  4. 访问 626.php会下载一个全是 RGB 的 txt 文件,很显然这是一张图片的每个像素点,用 随波逐流解出图片

    tmpbaelrxnp

  5. 访问 424.php,发现耄耋的加密聊天记录,发现有用户登录,尝试 SQL 注入 ' OR '1'='1

    image-20251122210029894

  6. 注入成功后,前往 65.php 发现入侵者证据文件

    image-20251122210220826

    image-20251122210320898

  7. 之后进入 end.php

    image-20251122210429602

  • 至此,隐藏关卡完成?了吗
  1. 对这个页面查询源代码,能看到其中一个 js 脚本中有一个注释 // dlc->only.php

    // 打字机效果显示文本
    document.addEventListener('DOMContentLoaded', function() {
        const elements = document.querySelectorAll('.story-content');
    
        elements.forEach((element, index) => {
            const paragraphs = element.querySelectorAll('p, .code-block');
    
            // 为每个段落添加延迟
            paragraphs.forEach((p, pIndex) => {
                const originalHTML = p.innerHTML;
                p.style.opacity = '0';
                p.innerHTML = '';
    
                setTimeout(() => {
                    p.style.opacity = '1';
                    p.innerHTML = originalHTML;
    
                    // dlc->only.php
                    if (p.classList.contains('code-block')) {
                        return;
                    }
    
                    // 为文本段落添加打字效果
                    const text = p.textContent;
                    p.textContent = '';
    
                    let i = 0;
                    const typeWriter = () => {
                        if (i < text.length) {
                            p.textContent += text.charAt(i);
                            i++;
                            setTimeout(typeWriter, 30);
                        }
                    };
    
                    typeWriter();
                }, (index * 1000) + (pIndex * 500));
            });
        });
    });
    
  2. 最后根据线索进入 only.php,会下载一个 5number.zip,密码用暴力破解后为 99998,里面是队长给的究极彩蛋

  • 至此所有彩蛋全部解释完毕

Misc

伪装的哈基米

附件:伪装的哈基米.zip

  1. 打开后发现是一个压缩包,且需要密码

    image-20251123170920053

  2. 尝试用 随波逐流破解

    image-20251123171030004

  3. 解压出图片后再将图片用 随波逐流破解,发现图片宽度和高度被修改过,可能存在隐写

    image-20251123171251233

    最后得出 flag 并重新编写包头 aicx{haji_mi_laomao}

黑客耄耋的“音频隐写”

  1. 打开解压后发现是一个非常好听的哈吉米音乐

    image-20251123171524887

  2. 尝试用 010Editor打开查看,发现隐写了 zip 压缩包,并且能看到里面的 flag

    image-20251123171705726

    最后得出 flag 并重新编写包头 aicx{haqile_504B0304}

耄耋的黑入

  1. 打开后发现是一个流量分析,用 Wireshark打开并尝试追踪流

    image-20251123172046082

  2. 在追踪到 流14时候发现携带了一些可疑包

    image-20251123172218916

  3. admin_pass很像栅栏的结构,先对其 url 解码后得 fWcylikoal_uglf}{_o@Ihr@_a_@并尝试用 随波逐流求解

    image-20251123172451951

    当在第七栏时得出 flag 并重新编写包头 aicx{I_Will_hack_for_you}

哈只能离开

  1. 解压后打开发现有一张图片和一个压缩包,压缩包带密码,尝试从图片入手,使用盲水印技术提取

    image-20251123172903255

  2. 输入密码 I'm leaving 成功解压出压缩文件,有一个 txt 文件

    I love UUUUUUUUUUUU
    

    和一道好听的哈吉米音乐,尝试用 010Editor打开,并检索 txt 文本中的 UUUUUUUUUUUU

    image-20251123173326258

  3. 发现底下有很多 RGB 编码文本,复制到 随波逐流尝试还原图片

    image-20251123173511098

  4. 还原出一个缺少定位符的二维码,用图片工具去还原,这里用 PPT为例子

    image-20251123173712955

  5. 最后保存图片,扫码得 flag 并重新编写包头 aicx{I_am_not_qualified}

    image-20251123173840218

Crypto

你一定很懂MD5

附件:md5.zip

拿到题目,先观察代码

import uuid
import hashlib
if __name__ == "__main__":
  flag = "aicx{xxxxxxxxxxx}"
  for char in flag:
    md5_hash = hashlib.md5(char.encode('utf-8')).hexdigest()
    print(f"{md5_hash}")
'''output:
0cc175b9c0f1b6a831c399e269772661
865c0c0b4ab0e063e5caa3387c1a8741
4a8a08f09d37b73795649038408b5f33
9dd4e461268c8034f5c8564e155c67a6
f95b70fdc3088560732a5ac135644506
8fa14cdd754f91cc6554c9e71929cce7
45c48cce2e2d7fbdea1afc51c7c6ad26
a87ff679a2f3e71d9181a67b7542122c
8277e0910d750195b448797616e091ad
eccbc87e4b5ce2fe28308fd9f2a7baf3
cfcd208495d565ef66e7dff9f98764da
c4ca4238a0b923820dcc509a6f75849b
1679091c5a880faf6fb5e6087eb1b2dc
336d5ebc5436534e61d16e63ddfca327
a87ff679a2f3e71d9181a67b7542122c
0cc175b9c0f1b6a831c399e269772661
8fa14cdd754f91cc6554c9e71929cce7
c4ca4238a0b923820dcc509a6f75849b
336d5ebc5436534e61d16e63ddfca327
a87ff679a2f3e71d9181a67b7542122c
cfcd208495d565ef66e7dff9f98764da
45c48cce2e2d7fbdea1afc51c7c6ad26
92eb5ffee6ae2fec3ad71c777531578f
336d5ebc5436534e61d16e63ddfca327
c9f0f895fb98ab9159f51fd0297e236d
e4da3b7fbbce2345d7772b0674a318d5
eccbc87e4b5ce2fe28308fd9f2a7baf3
8f14e45fceea167a5a36dedd4bea2543
336d5ebc5436534e61d16e63ddfca327
92eb5ffee6ae2fec3ad71c777531578f
c9f0f895fb98ab9159f51fd0297e236d
45c48cce2e2d7fbdea1afc51c7c6ad26
a87ff679a2f3e71d9181a67b7542122c
45c48cce2e2d7fbdea1afc51c7c6ad26
c81e728d9d4c2f636f067f89cc14862c
8fa14cdd754f91cc6554c9e71929cce7
45c48cce2e2d7fbdea1afc51c7c6ad26
cfcd208495d565ef66e7dff9f98764da
8277e0910d750195b448797616e091ad
e4da3b7fbbce2345d7772b0674a318d5
1679091c5a880faf6fb5e6087eb1b2dc
cbb184dd8e05c9709e5dcaedaa0495cf
'''
  • 可以发现,这段代码用了 Python UUID库,因此可以大胆猜测flag是以UUID的形式出现,再看包头 aicx{xxxx}以及for循环的逻辑,不难发现他的加密逻辑是挨个将flag的字符串依次加密成md5的形式
import uuid
import hashlib

jihe = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","-"]
jihe_md5 = []
flag_md5 = [
 '0cc175b9c0f1b6a831c399e269772661', -->a
 '865c0c0b4ab0e063e5caa3387c1a8741', -->i
 '4a8a08f09d37b73795649038408b5f33', -->c
 '9dd4e461268c8034f5c8564e155c67a6', -->x
 'f95b70fdc3088560732a5ac135644506', -->{
'8fa14cdd754f91cc6554c9e71929cce7',
'45c48cce2e2d7fbdea1afc51c7c6ad26',
'a87ff679a2f3e71d9181a67b7542122c',
'8277e0910d750195b448797616e091ad',
'eccbc87e4b5ce2fe28308fd9f2a7baf3',
'cfcd208495d565ef66e7dff9f98764da',
'c4ca4238a0b923820dcc509a6f75849b',
'1679091c5a880faf6fb5e6087eb1b2dc',
'336d5ebc5436534e61d16e63ddfca327',
'a87ff679a2f3e71d9181a67b7542122c',
'0cc175b9c0f1b6a831c399e269772661',
'8fa14cdd754f91cc6554c9e71929cce7',
'c4ca4238a0b923820dcc509a6f75849b',
'336d5ebc5436534e61d16e63ddfca327',
'a87ff679a2f3e71d9181a67b7542122c',
'cfcd208495d565ef66e7dff9f98764da',
'45c48cce2e2d7fbdea1afc51c7c6ad26',
'92eb5ffee6ae2fec3ad71c777531578f',
'336d5ebc5436534e61d16e63ddfca327',
'c9f0f895fb98ab9159f51fd0297e236d',
'e4da3b7fbbce2345d7772b0674a318d5',
'eccbc87e4b5ce2fe28308fd9f2a7baf3',
'8f14e45fceea167a5a36dedd4bea2543',
'336d5ebc5436534e61d16e63ddfca327',
'92eb5ffee6ae2fec3ad71c777531578f',
'c9f0f895fb98ab9159f51fd0297e236d',
'45c48cce2e2d7fbdea1afc51c7c6ad26',
'a87ff679a2f3e71d9181a67b7542122c',
'45c48cce2e2d7fbdea1afc51c7c6ad26',
'c81e728d9d4c2f636f067f89cc14862c',
'8fa14cdd754f91cc6554c9e71929cce7',
'45c48cce2e2d7fbdea1afc51c7c6ad26',
'cfcd208495d565ef66e7dff9f98764da',
'8277e0910d750195b448797616e091ad',
'e4da3b7fbbce2345d7772b0674a318d5',
'1679091c5a880faf6fb5e6087eb1b2dc',
 'cbb184dd8e05c9709e5dcaedaa0495cf' -->}
]
jieguo = ''

if __name__ == "__main__":
    for char in jihe:
        md5_hash = hashlib.md5(char.encode('utf-8')).hexdigest()
        jihe_md5.append(md5_hash)
    print("aicx{",end="")
    for i in flag_md5:
        for j_index in range(len(jihe_md5)):
            if jihe_md5[j_index] == i:
                print(jihe[j_index],end="")
print("}")

最后得出 flag aicx{f94d3016-4af1-409b-8537-b89492f90d56}

来自蝌蚪群的低语

附件:Scattered_Tadpoles_Key.zip

打开后观察内容

/ۘۙۖۧۢۗۨ۟ۖۚ۫ۡۧ۬ۢۤۘۚۖ۠۬۠ۙ۠ۡ۬ۜ۫ۘۗۚۜۜۢۨۘۗۚۤۚۧۜۜ۠ۧۙۡۧۘ۬ۧ۫ۗۚۤۡۚ۫ۖۘۜۧۨۧۧۖ۟ۙ۫۟۟ۗ۟ۡۧ۠ۛۙۚۢۘۜۛۜۗۧۘۧۧ۫ۙ۫ۜۜۘۗ

由题干得知这道题和蝌蚪有关,所以先用 随波逐流蝌蚪解密解密出内容:

YWljeHtDMG5mdXNlZF9UYWRwMDFlX1JlZ3IwdXBlZH0=

随后用 Base64 解密得:aicx{C0nfused_Tadp01e_Regr0uped}

社会主义核心价值观

附件:socialist_core_values.zip

观察内容

公正民主法治友善平等和谐公正法治文明和谐自由法治和谐和谐和谐和谐自由公正公正公正敬业公正公正平等友善敬业和谐和谐公正诚信文明平等诚信平等平等诚信平等法治平等法治平等公正和谐和谐自由公正和谐平等友善敬业法治平等和谐自由公正文明法治自由公正友善公正法治爱国公正诚信文明和谐富强法治公正和谐和谐法治文明和谐和谐和谐民主法治友善法治

由题干得知这道题和蝌蚪有关,所以先用 随波逐流社会主义核心价值观解密解密出内容:

a{6r4s34fif_3l__uuc4c_u4btlxl0v3r31}

题目又提示你 请根据提供的密文,结合社会主义核心价值观的特点,再运用栅栏密码技术进行解密。

我们知道社会主义核心价值观是以 4 个为一组,因此我们可以联想到栅栏解密是不是也是四个:

因此当栅栏当分为 4 栏时,解密结果为:aicx{f4l6_c0r3_v4lu3s_4r3_b34ut1ful}

简单的RSA

附件:simple_rsa.zip

观察内容:

import libnum
import gmpy2

p = libnum.generate_prime(1024)
q = libnum.generate_prime(1024)
N = p * q
e = 65537
phi_N = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi_N)

flag = "aicx{xxxxx}"

m = libnum.s2n(flag)
c = pow(m, e, N)

print("n=", N)
print("d=", d)
print("e=", e)
print("c=", c)

"""
n= 16575827738887961346438492123321401653601437738094946808015871706433413719845955728637169154847742511124638444895003756622524084227986025903309517393364417308214164142020627651655615817318164554159060393746961664730231172027362776066808921369616114141456159887169577539223566383813233117186669991059761419120246856163096080839938418354265116272958240667469372766345462025185986003423732767711676661748391706450193218644007150696230325310680339162985826724903117352677241802308334978466018036303778050041947108997075633326250354195725999752474757074588377021238672065871378215539609839836677597191127037366530996194921
d= 16425591358264810316641820558842513200021944413462626158551272658664650307581303735063913154756503073072728298927571127246236196377894875875286174040271378812691341713463320285967783969728452029246866032181957236257006011947037868167703309283412107674879646482940230314628317922150583614285882924598228265589043774044407688435158926789766696705585411940604896870964701617014925664459709493340453880421863810301526216003018057747595788192627750826013698398380213117441971512599378712254400438652699278522563304680329417957435411506899743217802519312477151930278130355054664424961835646691800439054413503359294784871681
e= 65537
c= 14014159519162236609164431026272910890908470886580000387926969438242229668934878342473126282635725506252805608888379137722160610193621443698701757725629567839106040861779495485667645087387266029349798272469889276721111177743879180653066329824334278182604241432974794932851371036038363969919684366870439706065639005991629961171752112226962028779834045997976473142525902240266797978474857645096458942824970018261837127568951146896803025963016085897853244359745282707521092070786872860223588919446200835383335807091117194081672389833134083300232423437063429371605762347138778982790746464397538654186583793880683094120742
"""

解密代码

import libnum

p = 140926796066565229407336542154389985070575897227020830186621519064945029175562511155639463681521170968913423233227197576124947612576005051689117059168910666187020672452750373137901603667952893561399268550869545321953296047654791350371208630086699802981799696652538937934282879351062844806370420835494822373943
q = 120968704307624070652838208622787832320412639243769150475242480968762909977242323120489958200542364554868096224988947036901147509849742057739150439375011560496956912508156200986366529742972306035077812177906219535525745452644206672223602651060299897483270815563801388815061882847384053180824092360316728317163

n= 16575827738887961346438492123321401653601437738094946808015871706433413719845955728637169154847742511124638444895003756622524084227986025903309517393364417308214164142020627651655615817318164554159060393746961664730231172027362776066808921369616114141456159887169577539223566383813233117186669991059761419120246856163096080839938418354265116272958240667469372766345462025185986003423732767711676661748391706450193218644007150696230325310680339162985826724903117352677241802308334978466018036303778050041947108997075633326250354195725999752474757074588377021238672065871378215539609839836677597191127037366530996194921
d= 16425591358264810316641820558842513200021944413462626158551272658664650307581303735063913154756503073072728298927571127246236196377894875875286174040271378812691341713463320285967783969728452029246866032181957236257006011947037868167703309283412107674879646482940230314628317922150583614285882924598228265589043774044407688435158926789766696705585411940604896870964701617014925664459709493340453880421863810301526216003018057747595788192627750826013698398380213117441971512599378712254400438652699278522563304680329417957435411506899743217802519312477151930278130355054664424961835646691800439054413503359294784871681
e= 65537
c= 14014159519162236609164431026272910890908470886580000387926969438242229668934878342473126282635725506252805608888379137722160610193621443698701757725629567839106040861779495485667645087387266029349798272469889276721111177743879180653066329824334278182604241432974794932851371036038363969919684366870439706065639005991629961171752112226962028779834045997976473142525902240266797978474857645096458942824970018261837127568951146896803025963016085897853244359745282707521092070786872860223588919446200835383335807091117194081672389833134083300232423437063429371605762347138778982790746464397538654186583793880683094120742

m = pow(c, d, n)
print(libnum.n2s(m))

当然用 轩禹CTF工具也能一把梭,最后结果为 aicx{2df25c86e0b8dfebb7f940d7025febba}

Reverse

Hello Reverse

附件:hello_reverse.zip

  1. 首先用DIE查看头文件

image-20251121185005002

image-20251121185119135

用 IDA 打开后反编译

__int64 __fastcall main()
{
  FILE *v0; // rax
  size_t v1; // rax
  char flag[48]; // [rsp+20h] [rbp-40h] BYREF
  char *secret; // [rsp+50h] [rbp-10h]
  int i; // [rsp+5Ch] [rbp-4h]

  _main();
  printf(&_format);
  printf(&byte_140009029);
  for ( i = 5; i > 0; --i )
  {
    printf(&byte_140009043, (unsigned int)i);
    v0 = __acrt_iob_func(1u);
    fflush(v0);
    sleep(1LL);
  }
  printf(asc_140009058);
  sleep(1LL);
  qmemcpy(flag, "aicx{Welcome_to_the_CTF_selection_competition}", 46);
  secret = flag;
  v1 = strlen(flag);
  memset(flag, 0, v1);
  return 0LL;
}

发现 flag aicx{Welcome_to_the_CTF_selection_competition}

保险箱里的秘密

附件:xor_vault_secret.zip

  1. 首先用DIE查看头文件

image-20251121185437966

发现这是一个二进制文件且被套上了 UPX 壳

  1. 用 UPX 解壳

    upx -d xor_vault_secret
    
  2. 解完后用 IDA 打开并反编译查看

    puts("==========================================");
      draw_keypad_ui();
      __isoc99_scanf("%s", s1);
      if ( !strcmp(s1, CORRECT_PASSWORD) )
      {
        puts(asc_2196);
        puts(asc_21B0);
        puts("==========================================");
        puts(asc_21F0);
        for ( i = 0; i <= 7; ++i )
          v6[i] = XOR_KEY_BYTES[i];
        v12 = strlen(s1);
        for ( j = 0LL; j < v12 - 1; j += 2LL )
        {
          v10 = s1[j] - 48;
          v9 = s1[j + 1] - 48;
          if ( v10 < 8 && v9 < 8 )
          {
            v8 = v6[v10];
            v6[v10] = v6[v9];
            v6[v9] = v8;
          }
        }
        puts(asc_2248);
        puts(&byte_2270);
        printf("  -> ");
        __isoc99_scanf("%s", s);
        v11 = strlen(s);
        double_encrypt((const unsigned __int8 *)s, v6, v4, v11);
        if ( v11 == 26 && !memcmp(v4, &DE_FLAG, 0x1AuLL) )
        {
          puts("\n==========================================");
          puts(&byte_22B8);
          puts(&byte_22E0);
          puts("==========================================");
        }
        else
        {
          puts(asc_2308);
        }
        return 0;
      }
      else
      {
        puts(asc_2218);
        return 0;
      }
    }
    
  3. 首先入口发现需要用户输入一个 s1 并与 CORRECT_PASSWORD 变量进行比较

其中 CORRECT_PASSWORD 为 20251121

image-20251121203959128

  1. 再往下看,s1 校验正确后,进入第一个 for 循环,将 XOR_KEY_BYTES 数组中的变量依次赋值到 v6 数组中

    image-20251121204326370

  2. 第二个 for 循环

v12 = strlen(s1);
for ( j = 0LL; j < v12 - 1; j += 2LL )
{
  v10 = s1[j] - 48;
  v9 = s1[j + 1] - 48;
  if ( v10 < 8 && v9 < 8 )
  {
    v8 = v6[v10];
    v6[v10] = v6[v9];
    v6[v9] = v8;
  }
}

不难理解 for 循环中把 s1 中的值【也就是 CORRECT_PASSWORD】作为索引,将 v6【也就是 XOR_KEY_BYTES】的值两两互换位置,交换后才是最终的 XOR_KEY_BYTES

  1. 接着往下看不难发现有一个加密函数 double_encrypt和变量 DE_FLAG

其中 DE_FLAG 为
image-20251121202911627

  1. 进入 double_encrypt阅读代码后发现这个加密函数中还经过了两个函数

根据 double_encrypt((const unsigned __int8 *)s, v6, v4, v11);传入了参数的对应关系为

a1 -> s -> 用户输入的值=DE_FLAG

a2 -> v6 ->交换后的XOR_KEY_BYTES

a3 -> v4 -> 输入加密后的密文

a4 -> v11 ->strlen(s)=用户输入的内容的长度

__int64 __fastcall double_encrypt(
        const unsigned __int8 *a1,
        const unsigned __int8 *a2,
        unsigned __int8 *a3,
        unsigned __int64 a4)
{
  xor_crypt(a1, a2, a3, a4);
  return caesar_shift(a3, a4, 5);
}
  1. 继续进入加密函数 xor_crypt
unsigned __int64 __fastcall xor_crypt(
        const unsigned __int8 *a1,
        const unsigned __int8 *a2,
        unsigned __int8 *a3,
        unsigned __int64 a4)
{
  unsigned __int64 result; // rax
  unsigned __int64 i; // [rsp+28h] [rbp-8h]

  for ( i = 0LL; ; ++i )
  {
    result = i;
    if ( i >= a4 )
      break;
    a3[i] = a2[i & 7] ^ a1[i];
  }
  return result;
}

由函数得知,明文经过该函数时会根据 交换后的XOR_KEY_BYTES依次进行 XOR 操作

  1. 进入 caesar_shift函数中查看
unsigned __int64 __fastcall caesar_shift(unsigned __int8 *a1, unsigned __int64 a2, int a3)
{
  unsigned __int64 result; // rax
  unsigned __int64 i; // [rsp+1Ch] [rbp-8h]

  for ( i = 0LL; ; ++i )
  {
    result = i;
    if ( i >= a2 )
      break;
    a1[i] = (int)((unsigned __int8)(((unsigned int)((a3 % 256 + 256) >> 31) >> 24) + a3 % 256)
                - ((unsigned int)((a3 % 256 + 256) >> 31) >> 24)
                + a1[i])
          % 256;
  }
  return result;
}

当 a3 传入了 5 时,密文向后位移了 5 个单位,这是典型的凯撒位移

  • 至此,所有的加密步骤解析完毕,根据以上推论,我们可以逆推解密代码

    #include "stdio.h"
    
    unsigned char XOR_KEY_BYTES[] =
    {
      0x11, 0x21, 0x12, 0x34, 0x56, 0x31, 0x58, 0x54, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    unsigned char v6[8];
    unsigned char a3[99];
    unsigned char s1[] = "20251121";
    unsigned char a1[] =
    {
      0x78, 0x5D, 0x47, 0x51, 0x32, 0x6E, 0x6D, 0x2B, 0x52, 0x47,
      0x5B, 0x0A, 0x27, 0x77, 0x35, 0x10, 0x84, 0x59, 0x47, 0x61,
      0x67, 0x84, 0x6E, 0x2C, 0x84, 0x51
    };
    int main() {
        for (int i = 0; i <= 7; ++i)
            v6[i] = XOR_KEY_BYTES[i];
    
        //加密XOR_KEY_BYTES
        for (int j = 0; j < 7; j += 2LL)
        {
            int v10 = s1[j] - 48;
            int v9 = s1[j + 1] - 48;
            if (v10 < 8 && v9 < 8)
            {
                int v8 = v6[v10];
                v6[v10] = v6[v9];
                v6[v9] = v8;
            }
        }
        //凯撒复位
        for (int i = 0; i < sizeof(a1) / sizeof(a1[0]); ++i)
        {
            a1[i] = (int)((unsigned __int8)(((unsigned int)((5 % 256 + 256) >> 31) >> 24) - 5 % 256)
                - ((unsigned int)((5 % 256 + 256) >> 31) >> 24)
                + a1[i])
                % 256;
        }
        //xor
        for (int i = 0; i < sizeof(a1) / sizeof(a1[0]); ++i)
        {
            int result = i;
            a3[i] = v6[i & 7] ^ a1[i];
        }
        printf("%s", a3);
    }
    
解得flag为

aicx{x0r_sw1tch_mech4n1sm}

终端激活码

附件:terminal_activation.zip

  1. 首先用 DIE 查看头文件

    image-20251121234338363

    发现这是一个二进制文件且被套上了 UPX 壳

  2. 用 UPX 解壳

    upx -d terminal_activation
    
  3. 解完后用 IDA 打开并反编译查看

    int __fastcall main(int argc, const char **argv, const char **envp)
    {
      char v4[400]; // [rsp+0h] [rbp-390h] BYREF
      char v5[112]; // [rsp+190h] [rbp-200h] BYREF
      int v6[100]; // [rsp+200h] [rbp-190h] BYREF
    
      memset(v6, 0, sizeof(v6));
      v6[0] = 61;
      v6[1] = 49;
      v6[2] = 109;
      v6[3] = 99;
      v6[4] = 118;
      v6[5] = 70;
      v6[6] = 67;
      v6[7] = 101;
      v6[8] = 118;
      v6[9] = 109;
      v6[10] = 109;
      v6[11] = 100;
      v6[12] = 105;
      v6[13] = 54;
      v6[14] = 86;
      v6[15] = 91;
      v6[16] = 101;
      v6[17] = 79;
      v6[18] = 71;
      v6[19] = 91;
      v6[20] = 120;
      v6[21] = 71;
      v6[22] = 49;
      v6[23] = 101;
      v6[24] = 113;
      v6[25] = 79;
      v6[26] = 70;
      v6[27] = 79;
      v6[28] = 104;
      v6[29] = 117;
      v6[30] = 71;
      v6[31] = 102;
      v6[32] = 105;
      v6[33] = 109;
      v6[34] = 86;
      v6[35] = 90;
      puts(&s);
      __isoc99_scanf("%99s", v5);
      encrypt_flag(v5, v4);
      if ( (unsigned int)compare_char_with_int_array(v4, v6) )
        puts(&byte_20A0);
      else
        puts(&byte_208F);
      return 0;
    }
    

    观察代码,我们会发现一个疑似密文的 v6 数组,和两个函数 encrypt_flag compare_char_with_int_array

    其中 compare_char_with_int_array函数名告诉你了是比较函数,那我们大胆猜测 v6 是正确密文,而 v5 是用户输入的明文,最后 v4 是用户输入后经过函数加密的密文

  4. 进入加密函数 encrypt_flag(v5, v4);

    void __fastcall encrypt_flag(char *a1, char *a2)
    {
      unsigned __int64 v2; // [rsp+18h] [rbp-28h] BYREF
      char v3; // [rsp+27h] [rbp-19h]
      char *src; // [rsp+28h] [rbp-18h]
      unsigned __int64 v5; // [rsp+30h] [rbp-10h]
      int j; // [rsp+38h] [rbp-8h]
      int i; // [rsp+3Ch] [rbp-4h]
    
      v5 = strlen(a1);
      src = (char *)_64ba((const unsigned __int8 *)a1, v5, &v2);
      for ( i = 0; i < v2; ++i )
      {
        if ( src[i] != 61 )
        {
          if ( (i & 1) != 0 )
            --src[i];
          else
            ++src[i];
        }
      }
      for ( j = 0; j < v2 >> 1; ++j )
      {
        v3 = src[j];
        src[j] = src[v2 - j - 1];
        src[v2 - j - 1] = v3;
      }
      strcpy(a2, src);
      free(src);
    }
    

    发现 src变量通过了一个叫 _64ba函数中拿到了值,而加密函数内的 v5 变量值为函数外用户输入的值的字符长度

  5. 进入 _64ba((const unsigned __int8 *)a1, v5, &v2);函数,观察部分代码

    v11 = (v12 << 8) + (v13 << 16) + v8;
        v14[v16] = _64ba_chars[(v11 >> 18) & 0x3F];
        v14[v16 + 1] = _64ba_chars[(v11 >> 12) & 0x3F];
        v14[v16 + 2] = _64ba_chars[(v11 >> 6) & 0x3F];
        v9 = v16 + 3;
        v16 += 4;
        v14[v9] = _64ba_chars[v11 & 0x3F];
    

    函数中频繁调用 _64ba_chars变量值, 我们查看其值便会发现

    image-20251122000112149

    已经可以断定 _64ba函数就是 Base64 加密函数

  6. 此时再回到 encrypt_flag(v5, v4);中,我们可以发现,src -> base64(用户明文),继续往下观察两个 for 循环

    for ( i = 0; i < v2; ++i )
      {
        if ( src[i] != '=' )
        {
          if ( (i & 1) != 0 )
            --src[i];
          else
            ++src[i];
        }
      }
      for ( j = 0; j < v2 >> 1; ++j )
      {
        v3 = src[j];
        src[j] = src[v2 - j - 1];
        src[v2 - j - 1] = v3;
      }
      strcpy(a2, src);
      free(src);
    }
    
    • 第一个for循环对每个字符进行简单的变换,即偶数位置字符加1,奇数位置字符减1,但是遇到 =字符时则不变换
    • 第二个for循环则是将结果倒序输出
  7. 至此,所有的加密步骤解析完毕,根据以上推论,我们可以逆推解密代码

    import base64
     解密过程
     先倒序回来
    deflag = [61, 49, 109, 99, 118, 70, 67, 101, 118, 109, 109, 100, 105, 54, 86, 91, 101, 79, 71, 91, 120, 71, 49, 101, 113, 79, 70, 79, 104, 117, 71, 102, 105, 109, 86, 90]
    decrypted = deflag[::-1]
     进行相反的字符变换
    for i in range(len(decrypted)):
        if chr(decrypted[i]) != '=':
            if i % 2 == 0:
                decrypted[i] = decrypted[i] - 1
            else:
                decrypted[i] = decrypted[i] + 1
        else:
             '='字符保持不变
            pass
     转换为字符
    decrypted_chars = [chr(code) for code in decrypted]
     连接成字符串
    encrypted_base64 = ''.join(decrypted_chars)
     Base64解码得到原始flag
    original_flag = base64.b64decode(encrypted_base64).decode('utf-8')
    print("解密后的Base64:", encrypted_base64)
    

    解得 flag 为 aicx{b4ckwards_encrypt10n}

失落的密钥

附件:lost_key.zip

首先用 DIE 查看头文件

image-20251122002402122

发现是 C 写的二进制文件

  1. 用 IDA 打开并反编译查看

    int __fastcall main(int argc, const char **argv, const char **envp)
    {
      char v4[8]; // [rsp+10h] [rbp-70h] BYREF
      __int64 v5; // [rsp+18h] [rbp-68h]
      __int64 v6; // [rsp+20h] [rbp-60h]
      __int64 v7; // [rsp+28h] [rbp-58h]
      __int64 v8; // [rsp+30h] [rbp-50h]
      __int64 v9; // [rsp+38h] [rbp-48h]
      __int64 v10; // [rsp+40h] [rbp-40h]
      __int64 v11; // [rsp+48h] [rbp-38h]
      __int64 v12; // [rsp+50h] [rbp-30h]
      __int64 v13; // [rsp+58h] [rbp-28h]
      __int64 v14; // [rsp+60h] [rbp-20h]
      __int64 v15; // [rsp+68h] [rbp-18h]
      int v16; // [rsp+70h] [rbp-10h]
      *(_QWORD *)v4 = 0LL;
      v5 = 0LL;
      v6 = 0LL;
      v7 = 0LL;
      v8 = 0LL;
      v9 = 0LL;
      v10 = 0LL;
      v11 = 0LL;
      v12 = 0LL;
      v13 = 0LL;
      v14 = 0LL;
      v15 = 0LL;
      v16 = 0;
      puts(&byte_2EF1);
      sleep(1u);
      printf(&format);
      __isoc99_scanf("%99s", v4);
      _getdata(v4);
      return 0;
    }
    

    main 函数中没有什么有价值的东西,用户输入一个变量 v4 然后进入 _getdata函数,所以重点看这个函数

  2. 进入 _getdata(v4);函数

    int __fastcall _getdata(char *a1)
    {
      _DWORD v2[128]; // [rsp+10h] [rbp-2A0h]
      _DWORD v3[34]; // [rsp+210h] [rbp-A0h]
      int v4; // [rsp+298h] [rbp-18h]
      int v5; // [rsp+29Ch] [rbp-14h]
      int k; // [rsp+2A0h] [rbp-10h]
      int v7; // [rsp+2A4h] [rbp-Ch]
      int j; // [rsp+2A8h] [rbp-8h]
      int i; // [rsp+2ACh] [rbp-4h]
    
      v3[0] = 11;
      v3[1] = 20;
      v3[2] = 1;
      v3[3] = 26;
      v3[4] = 29;
      v3[5] = 8;
      v3[6] = 28;
      v3[7] = 16;
      v3[8] = 30;
      v3[9] = 22;
      v3[10] = 24;
      v3[11] = 14;
      v3[12] = 5;
      v3[13] = 3;
      v3[14] = 9;
      v3[15] = 19;
      v3[16] = 27;
      v3[17] = 12;
      v3[18] = 23;
      v3[19] = 6;
      v3[20] = 21;
      v3[21] = 15;
      v3[22] = 0;
      v3[23] = 2;
      v3[24] = 18;
      v3[25] = 13;
      v3[26] = 25;
      v3[27] = 7;
      v3[28] = 17;
      v3[29] = 10;
      v3[30] = 4;
      v5 = strlen(a1);
      for ( i = 0; i < v5; ++i )
      {
        v4 = v3[i];
        *((_BYTE *)&v2[100] + i) = a1[v4];
      }
      for ( j = 0; j < v5; ++j )
      {
        *((_BYTE *)&v2[100] + j) ^= v3[v5 - 1 - j];
        v2[j] = *((char *)&v2[100] + j);
      }
      v7 = 1;
      for ( k = 0; k < v5; ++k )
      {
        if ( v2[k] != FINAL_FLAG_SCRAMBLED[k] )
        {
          v7 = 0;
          break;
        }
      }
      if ( !v7 )
        return puts(&byte_2EE8);
      puts(&byte_2EDF);
      sleep(1u);
      return _message();
    }
    
  • 首先知道a1作为传入进来的参数,也就是用户输入的明文,当a1进入第一个for循环时,开始遍历v3中的值,并将其值作为a1的索引重新排序,也就是算法中常见的索引排序的逻辑,没看懂也没关系,只要知道这是其中一个加密逻辑即可。
  • 其次排序完后的a1进入第二个for循环a1[j]对每个v3[v5 - 1 - j]进行XOR错位加密,也就是a1与每个倒序v3进行异或
  • 至此,所有的加密步骤解析完毕,根据以上推论,我们可以逆推解密代码,这里提供C和Python两种语言:

Python

mapping = [11, 20, 1, 26, 29, 8, 28, 16, 30, 22, 24, 14, 5, 3, 9, 19, 27, 12, 23, 6, 21, 15, 0, 2, 18, 13, 25, 7, 17, 10, 4]
int_de_flag = [105, 62, 120, 88, 96, 116, 33, 54, 125, 108, 102, 116, 97, 116, 68, 76, 98, 115, 54, 61, 123, 98, 127, 115, 104, 56, 110, 104, 111, 125, 112]
flag_jiemi = [''] * len(int_de_flag)

转字符串
for i in range(len(int_de_flag)):
    flag_jiemi[i] = chr(int_de_flag[i])

XOR解密
for i in range(len(int_de_flag)):
    flag_jiemi[i] = chr(ord(flag_jiemi[i]) ^ (mapping[len(int_de_flag) - 1 - i]))

恢复原始顺序
final_flag = [''] * len(flag_jiemi)
for new_index, original_index in enumerate(mapping):
    final_flag[original_index] = flag_jiemi[new_index]

print(''.join(final_flag))

C

#include <stdio.h>

unsigned char FINAL_FLAG_SCRAMBLED[] =
{
    0x69,0x3E,0x78,0x58,0x60,0x74,0x21,0x36,0x7D,0x6C,
    0x66,0x74,0x61,0x74,0x44,0x4C,0x62,0x73,0x36,0x3D,
    0x7B,0x62,0x7F,0x73,0x68,0x38,0x6E,0x68,0x6F,0x7D,
    0x70,0x00
};

unsigned char enflag[31];
int v3[31];

unsigned char userflag[32];

int main() {
    v3[0] = 11;
    v3[1] = 20;
    v3[2] = 1;
    v3[3] = 26;
    v3[4] = 29;
    v3[5] = 8;
    v3[6] = 28;
    v3[7] = 16;
    v3[8] = 30;
    v3[9] = 22;
    v3[10] = 24;
    v3[11] = 14;
    v3[12] = 5;
    v3[13] = 3;
    v3[14] = 9;
    v3[15] = 19;
    v3[16] = 27;
    v3[17] = 12;
    v3[18] = 23;
    v3[19] = 6;
    v3[20] = 21;
    v3[21] = 15;
    v3[22] = 0;
    v3[23] = 2;
    v3[24] = 18;
    v3[25] = 13;
    v3[26] = 25;
    v3[27] = 7;
    v3[28] = 17;
    v3[29] = 10;
    v3[30] = 4;
    int len = 31;
    for (int j = 0; j < len; j++) {
        enflag[j] = FINAL_FLAG_SCRAMBLED[j] ^ v3[(len - 1) - j];
    }

    int temp[31];
    for (int i = 0; i < len; i++) {
        temp[v3[i]] = i;
    }
    for (int i = 0; i < len; i++) {
        userflag[i] = enflag[temp[i]];
    }
    printf("%s\n", userflag);
}