“抗疫”CTF题解
题解
这次比赛和 TBMK 一起打的, 做题的几天搜了很多文章, 感觉学到了很多东西.
Misc
感觉 Misc 这种还是比较有意思的
贝斯的复仇
附件下载为防止附件过大, 请用 gen_problem.py 动态生成题目文件 flag.base
打开的时候是不会的, 因为之前没见过这种base.
拿 python 写了个统计字符数量的脚本, 得出全是可打印字符并且少了其中几种, 比如引号方括号之类的.
然后去网上搜, 找到有个 base85, 拿 python 试了一下, 果然可以解密.
那剩下的就简单了, 每解密一次看看输出, 然后一层一层解密, flag 就出来了.
1 | import base64 |
这个脚本看起来不是一层一层解密的. 因为比赛后自己整理并重写了一遍.
flag{th4t_1s_b4s3_3nc0des}
capture
附件下载先用 WireShark 打开, 然后, 呃, 不知道怎么下手. 还是先看看十六进制吧, 搜到一个 flag
字符串, 旁边还有个 PK 头.
这样目的就比较明确了, 翻了一下, 成功得到了—一个加密的压缩包.
然后在周围搜了一下, 有个qwe123!@#114514
, 但那不是密码, 就先放弃了.
几天后突然想一条一条翻, 然后快到末尾的地方有一大串奇怪的base64
解码一下, 恍然大悟:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#######################################
# 2021-05-25 19:35:31 #
#######################################
--------------------------------------------------
WindowTitle:图片
Time:2021-05-25 19:35:45
[Delete]
--------------------------------------------------
WindowTitle:新建压缩文件
Time:2021-05-25 19:35:55
[Lshift]SECRET[Return]
--------------------------------------------------
WindowTitle:输入密码
Time:2021-05-25 19:36:01
[Capital]S[Back]ASDFGHJKL;'[Tab]ASDFGHJKL;'[Return]
--------------------------------------------------
WindowTitle:新建压缩文件
Time:2021-05-25 19:36:09
[Return]
mssctf{Pc4p_1s_S0o0o0o0o0o0o0o0o0oEz}
Evilcode
附件下载是一堆不认识的十六进制文本, 但根据重复序列的特征和文本行数, 猜测是一张图片的 RGB. (之前打 NCTF 碰到过类似的题, 只不过那个直接给的是 bits).
写个脚本填入 RGB, 果然得到了一张图片. 用 QR research 扫一下, 把网址后面的 base16 解码一下就有了.
1 | from PIL import Image |
flag{D0NT_sc4n_QRc0d3_fr0m_Unkn0wns0ur4}
打不开的压缩包
附件下载压缩包有密码, 没有附加数据, 但里面除了 flag 以外还有个 hint.png, 而 png 图片的文件头我们是知道的, 而且文件的压缩算法是 ZipCrypto Store. 于是去网上找找看有没有什么破解的方法.
找到一篇博客, 链接
根据文中所述的方法, 尝试使用以下命令行:1
bkcrack -C attachment.zip -c hint.png -p head.png
这个大概跑了十几分钟, 然后出来了三段 key, 把它们填入下面的命令行.1
2bkcrack -C attachment.zip -c flag -k c257ccb7 ee535b48 af274d68 -d flag
bkcrack -C attachment.zip -c hint.png -k c257ccb7 ee535b48 af274d68 -d hint.png
成功拿到其中的两个文件.
然而, 真正难的才刚刚开始
打开其中的 flag, 发现是一堆十进制数字(查看文件)
然后…观察了这段文本的特点, 尝试了很多方法:
- 直接整数转字节串
- base 系列算法
- 由于文本很多以12字节为周期, 尝试了以12个字符为1片切片, 然后对它们进行各种数值操作. 至于文件大小不是12的倍数, 按照”规律”补了个4
- 把字符数组根据索引模12分为12个类, 再观察它们的特征
- 重新回到 hint.png, 分析文件是否存在隐写
- 重新检查 attachment.zip, 分析有无数据附加
然后, 全军覆没
后来根据 TBMK 发现的文章(传送门)
一看, 这不原题嘛, 然后根据这个知道了aa3d这个东西. 感叹到自己学的还是太少了qwq
按照这个做法, 是把文字排列为 97*47 的矩阵, 然后截图并用stegsolve偏移+异或
4559=97*47, 这个是真没想到
不管怎么样, 只能说解题的收获颇丰吧
MiniL{A@3d-1s_Ar7!!}
Re
主要打的是 Re, 做到第七个做不动了
Rust? Rua死它!!!
附件下载之前看过 rust 的一本参考书
结果, 宏什么的完全不知道, 只是看到类似于 false as u8
之类的不会一脸懵.
其实 rust 学好的话用起来应该还是很舒服的
然后就硬看, 终于看出这是个逐步分析的过程…
其中never
是原文(buf
), gonna
是一个类似字节寄存器的玩意(c
), give
是当前字节指针(p
)的索引. 诶~有brainfuck
那味了.
前缀 | 代号 | 意义 |
---|---|---|
Never gonna give you up | 0 | p++ |
Never gonna let you down | 1 | p-- |
Never gonna run around and desert you | 2 | (*p)++ |
Never gonna make you cry | 3 | (*p)-- |
Never gonna say goodbye | 4 | c=*p |
Never gonna tell a lie and hurt you | 5 | *p=c |
然后把那一大串歌词弄下来, 拿数字代替并包装成数组, 可以写出 python 脚本:
1 |
|
感觉这道题还是蛮有意思的
flag{A6C33EA2571A2AE26BFAE7BEA2CD8F54}
文件勒索病毒
附件下载, 密码9ss8
这个 exe 短小精悍, 应该是刻意处理过的, 它甚至没有 crt 的 main 函数, start 一来就是主逻辑. 用 DIE 扫描出程序是 vs2019 写的, 对于高版本 vs, 为了保持这种特性, 不太方便使用 CRT 函数(因为它们会链接到 msvcr14x, vcruntimex, 而且产生一堆 thunks), 所以程序只使用了 kernel32.dll 导出的 api 进行控制台操作.
大概分析下这个文件干了什么
程序先获取控制台输入输出句柄, 然后就可以对控制台进行读写, 可以把它们想象成 stdin 和 stdout. 实际上 crt 函数大多是对 kernel32 的封装.
输入密码后, 程序进入下图:
先设置当前目录到有加密文件的目录, 然后遍历所有文件, 把文件路径和密码字符串传给sub_401220
:
可以看到, 程序对于打开的文件, 每次读取8个字节并解密, sub_4011F0
就是解密函数.
注意这个CreateFileA
在这里不是创建文件而是打开文件. 这个我当时学 win32 也一脸懵B. 类似的还有OpenProcess
是打开进程(获取句柄)而不是创建进程; CloseWindow
是最小化窗口而不是关闭窗口.
这个就是解密的过程了, 那么可以想到根据 PNG 文件头去反推密码:
1 | png_header = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] |
把这个输入到所给程序就OK
flag{c405e6725f58ba79c4078b02ac93808b68a12499}
saber
附件下载常见的 Misc 套路, 给了一个图片, 其中 exe 放在 PNG 文件末尾, 用十六进制编辑器可以把文件 dump 出来
然后就简单了, 写个程序异或回来即可:
1 |
|
flag{Exca1i6ur!!!}
题目忘了
附件下载一看程序反编译结果, 不像是直接用 C 写的. 顺着start
往里面看, 可以看到这些字符串:
看到了 OCRA, 猜测是 ruby 写的.
在网上寻找 OCRA 怎么反编译, 无果. 猜测 exe 是释放脚本并运行(以前某 bat2exe 就是这么做的).
打开程序挂着, 使用 Everything 搜索 .rb
, 按修改时间排序, 果然在临时目录有很多*.rb
文件
打开目录, 容易在(root)/src/
找到chall.rb
文件, 然后就简单了.
一条捷径: 运行程序并输入CTRL+Z并回车, 可以看到源文件 chall.rb
.
两次 base64 解密即可
flag{1llyasviel_v0n_E1nz6ern}
题目忘了*2
附件下载题目给出了主程序和一个 .pyc
, 显然我们需要逆向这个 python3.10 的字节码.
在网上找了一圈, 好像没有直接反编译成 .py
的反编译器, 最多找到个反编译成类似汇编代码的方法:
1 | import dis |
得到的结果: 查看
然后就以上面的代码为参考, 一句一句逆出来, 遇到汇编不对的就把自己那一句话改一改.
《易得》
1 | class DataFrame(object): |
我敢保证行数都是一样的
然后就按照源码写出顺序相反的代码:
1 | class DataFrame(object): |
flag{R3s3@rch_by73c0de_m@yb3_a_gO0d_way_2_l3a7n_python}
baby_vm
附件下载猜测有一个虚拟机类, 分析结果见文件
四条指令分别为 0xF1
~ 0xF4
.
根据虚拟机的四种指令执行对应操作, 写个脚本逆过来就行:
1 |
|
flag{Wh4t_@_stack_vm_M@573r!}
Rx 的烦恼
附件下载呃这题没做出来就不贴了, 看了看题解感觉还是想多了, 之前看完流程感觉应该没有flag, 然后一直想着是不是要故意触发异常跳到一个特殊的位置解密magic
.
下面是一个分析的半成品:
文件下载pwn
抱着捞分的心态做了几个题
clang-format
请找出让 clang_format 13.0 崩溃的 cpp 代码, 把代码转为 base64 发送到链接:
http://150.158.88.195:58888/format/?your_base64
题目要求找出clang-format
13.0 的漏洞, 既然给的是官方文件, 那肯定会有对应的 bug report. 于是我们可以去网上找相关数据, 这里给出我找到的: 这个网站中的这个邮件
使用:1
std::vector<std::vector<uint8_t>> var_len_seq{ { 0x80 }, { 0xf5 }, { 0xc3, 0x7f }, { 0xc3, 0xc0 }, { 0xe1, 0x7f }, { 0xe1, 0xc0 }, { 0xe1, 0x81, 0x7f }, };
去访问:1
http://150.158.88.195:58888/format/?c3RkOjp2ZWN0b3I8c3RkOjp2ZWN0b3I8dWludDhfdD4+IHZhcl9sZW5fc2VxeyB7IDB4ODAgfSwgeyAweGY1IH0sIHsgMHhjMywgMHg3ZiB9LCB7IDB4YzMsIDB4YzAgfSwgeyAweGUxLCAweDdmIH0sIHsgMHhlMSwgMHhjMCB9LCB7IDB4ZTEsIDB4ODEsIDB4N2YgfSwgfTsg
成功拿到 flag.
flag{eXcel1Ent1Y_Cr4sH_cLanG-F0rm4t}
blind
就这种题, 我闭着眼睛也能打
nc sec.eqqie.cn 10012
你觉得呢?
一个经典的栈溢出题, 直接造栈溢出, 管他长度是多少, 反正一直重复就对了:
1 | from pwn import * |
flag{I_believe_you_can_be_a_good_fuzzer}
ezlogin
nc sec.eqqie.cn 10013
附件下载
整数溢出 + 栈溢出即可
1 | from pwn import * |
flag{try_t0_c0mb1n3_tw0_vulnerabilities_t0g3ther}
note
nc 139.155.15.113 10000
附件下载
new
分配的缓冲区大小为0x1C
. 原理8太懂, 只知道经过测试两个两个地址间的距离为0x20
. 这样的话我们可以通过一个note
覆盖下一个note
的虚函数表. 那么我们需要一块大小为4的内存指针, 且地址里存放了system
函数指针. 这样下一条note
调用虚函数时就可拿到shell.
在这里我们选择题目给的gift
, 即先把此地址溢出填入note
的虚函数表, 再在下一条note
修改时输入system
函数的地址.
1 | from pwn import * |
flag{cpp_is_easy}
抗疫日记
nc sec.arttnba3.cn 10001
附件下载
这题可以利用的是野指针, 也就是旧指针在释放时没有将指针置零, 导致新内存分配到同一地址时导致意外修改.
对于高版本libc, 分配小内存先会从tcache中申请, 释放后回收, 等再次分配相同大小的内存时, 拿到的地址为上次释放的内存地址.
利用这一点, 我们先创建一条日记, 长度为16, 此时有两块内存, 指针为p1
, p2
. p1
是对象的16字节内存, p2
是缓冲区内存
再创建一条, 长度为其他数, 这里选择了96. 申请的内存指针为p3
, p4
. 含义同上
注意这时p1
, p2
和 p3
大小均为16字节, 而p4
不是, 此时释放第一条日记, 再释放第二条日记. 现在tcache中链表为p2
->p1
->p3
.
再创建一条日记, 此时拿到的指针为p3
和p1
, 此时就可在p1
中随意写数据了. 向其中写入/bin/sh
和system
即可.
1 | from pwn import * |
flag{h3@p_1s_s0_s1mp13_7o_3xp10I7!}
嗯, 就写到这, 吃饭去了