2024 XCTF 联赛 SCTF 题解
大家都冇咁搏命啦,唞吓啦!祝各位师傅国庆节快乐~
Pwn
kno_puts
非预期,写 /sbin/poweroff 即可
factory
1 | from pwn import * |
GoCompiler
栈上构造一些栈指针,格式化字符串一把梭!
1 | package main |
1 | from pwn import * |
vmCode
看到 VM 就是逆逆逆逆逆逆逆逆逆逆逆逆,然后就是调试调调调调调调调调,最后就是 ORW
1 | from pwn import * |
c_or_go
1 | from pwn import * |
kno_puts revenge
程序没有上锁,用 userfaultfd 卡在 copy from user,然后 kfree 后就会出现 uaf,然后修改 tty 栈迁移到 pt_regs 上即可
前面的随机数绕过可以赌随机数的第一位为,然后爆破绕过
Kernel base 地址泄露参考:https://qanux.github.io/2024/04/17/notes/
1 | // musl-gcc exp.c --static -masm=intel -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp |
Misc
Fixit
写个 html 显示一下 css 的渲染:
1 | <html> |
把 css 的宽改成 3,渲染出来一个:
谷歌识图一下,是 ATZEC🐎,换几个网站,这个比较好用:
https://products.fileformat.app/barcode/recognize/aztec#google_vignette
SCTF{W3lcomeToM1scW0rld}
easyMCU
纯纯固件逆向题,谁把它丢 Misc 里的,这也能放错方向的。。
由开发板照片可知型号是 SAK-TC275TP-64F200W DC,指令集架构是 TriCore。Ghidra 有对应于 TriCore 的反编译器。
由截图可知密文是 32 字节 63 D4 DD 72 B0 8C AE 31 8C 33 03 22 03 1C E4 D3 C3 E3 54 B2 1D EB EB 9D 45 B1 BE 86 CD E9 93 D8,既然题目丢 Misc 里了,根据刻板印象,给个截图有可能是因为右边的参数暗藏玄机,结果没有,出题人还是善良的。
固件文件是 s19 (SRec) 格式,可以在 GitHub 上找到项目把它转为 ELF,然后丢进 Ghidra。
多处按 D 键标记为指令,让 Ghidra 反编译出大部分代码,粗略浏览。在 0x80000690 处的函数有依次处理 32 个字节的逻辑,比较可疑。
1 | undefined4 FUN_80000690(void) |
其中深入看 FUN_800001f2 发现是 AES,四个参数分别是
0x60000004 是输入的 flag
0x800039a3 是 16 字节的 key
- 从 s19 文件中找这个地址得到 2E 35 7D 6A ED 44 F3 4D AD B9 11 34 13 EA 32 4E
0x6000007c 存放输出值
0x20 是 flag 长度
在 AES 加密前没有对 32 字节的 flag 后面再补齐 16 字节。
之后对 AES 密文又进行了循环左移 3、前一个异或后一个、异或 0xFF。
解密:
1 | def encrypt(in_flag): |
速来探索 SCTF 星球隐藏的秘密!
尝试到不是 Ready 的情况,获得前半段
Ans: HAHAHAy04
TerraWorld
解出 6 个压缩包,解出 zenith!但是没什么用
1 | with open('2024SCTF.wld', 'rb') as f: |
然后随便试一下?(出题人忘记改 Hex 成 UTF8 了)
musicMaster
MKV 封装文件,有两个图像流,两个音频流
MKV 的各流是交错存储的,所以不能直接按一般二进制文件分离,可以使用 MKVToolNix 中的 mkvextract
1 | .\mkvextract.exe tracks --raw daytime_final.mkv 0:v0.raw 1:a1.ogg 2:v2.gif 3:a3.ogg |
两个图像流分别是 默认播放的视频 和 GIF 五彩斑斓的黑
两个音频流分别是 默认播放的音乐 和 SSTV
SSTV 音频又分左右声道,分别传输一张图像:
异或一下(再取反)得到:
Aztec 解码得 d6f3a8568d5f9c03915494e6b584e216
GIF 共有 15 帧
识图一下知道是 cimbar https://github.com/sz3/libcimbar
15 帧一并丢进去,解码出需要密码的 7z 文件
解压密码是上面 SSTV 的结果,得到名为 daytime 的 182KB 的文件
1 | $ file ./daytime |
用 Fasttracker II 打开,发现元数据 table64 和 Hexrotor,两列十六进制数据
14340d1411272d141a03112e12353d190c07151f0d053d0c0c170d
340c3639291b23251f1317251f131301241d163033173435350d1305231f104040
注意到十六进制数据都在 0 - 64 之间,且最后有两个 0x40
猜测代表 Base64 字母表的下标
1 | import base64 |
(还挺好听的)
Crypto
Signin
二元 copper 即可
1 | from Crypto.Util.number import * |
不完全阻塞干扰
现根据给出的 pem 密钥进行还原,得到泄露的 p、q 高位,然后拿去打二元 copper
1 | from Crypto.Util.number import * |
Whisper
根据 pem 文件得到下面参数
1 | n1=0x1B5D4FE0AA6782E275D4CE12A6D57562EFBBE7DB6F5277255B891729BFA2A18D3EDB49843D7989A37B9516BE2DF8CA939058E65F64B5FB2071BEA4F5F8D1392895B32BF0377D99F4F79979125E5DB01CDB5080A1C2D665C9AC31B5823025499C9513277BAE5E7A846CD271C4396E2BA219020E58A9055CB18A28D36A00BF717B |
Dual rsa,e 是相同的,好像卡界了
可能有点用:https://elliptic-shiho.github.io/ctf-writeups/#!ctf/2017/0CTF%20Finals/cr1000-AuthenticationSecrecy/README.md
真的就是这么简单,把上面的代码改成 python3 的然后直接就能把 d 跑出来
1 | from sage.all import * |
LinearARTS
用已知条件求出 A 然后打 lwe 即可
1 | AD=PM.solve_left(AA) |
Web
SycServer2.0
Sql 注入,万能密码登录
1 | from Crypto.PublicKey import RSA |
/ExP0rtApi?v=static&f=..././..././..././..././..././..././..././etc/passwd 双写读文件
原型链污染 + 环境变量劫持
1 | { |
ezRender
file open 后没有 close ulimit 理论上 2048 次次会触发 too many files
time 可以爆破
异常会跳过,只剩下 time
无回显 SSTI 直接 fenjing 改造打内存马
ezjump
https://nvd.nist.gov/vuln/detail/CVE-2024-34351
改 Host Origin 即可
类似的题目
https://siunam321.github.io/ctf/UIUCTF-2024/Web/Log-Action/
1 | #!/usr/bin/env python3 |
Redis 5 可以主从复制 RCE
1 | #!/usr/bin/env python3 |
ez_tex
RCE
1 | \documentclass{article} |
1 | import requests |
提权拿 root flag
Reverse
ez_cython
pyinstxtractor 提取并用 pycdc 反编译 ez_cython.pyc,
可知输入在 cy.cp38-win_amd64.pyd 的 sub14514 函数中被验证:
1 | # Source Generated with Decompyle++ |
注入 cy 类,获取运算过程:
1 | import cy |
1 | new_a[0] = ((a[0] + ((((a[31] >> 3) ^ (a[1] << 3)) + ((a[1] >> 4) ^ (a[31] << 2))) ^ ((a[1] ^ 2654435769) + (a[31] ^ 49)))) & 4294967295) |
可知是一个类似 TEA 的加密过程,直接逆运算:
1 | var1 = [2654435769,1013904242,3668340011,2027808484,387276957] |
SGAME
搜字符串有 lua5.4.7 字样,说明嵌入了个 lua,自己编译一个然后 diff 一下,基本都能还原,就可以正式开逆了
逐 cout 动调到出现 game 字符串
注意到 SYC 相关字符串,并会在特定条件下被变换成 SYC{9876543210000}
均尝试进后续解密
发现在 SYC{123456789}(即没有调试)下可以正确解密 luac 并传递给 lua 引擎执行。
用脚趾头想都知道肯定魔改了,一般就是改文件结构、字节码啥的。文件结构改了头前 4 字节,字节码改了两个地方,一个是 opcode 顺序,一个是把 opcode 和第一个参数的位置给颠倒了。
文件结构直接把前 4 字节改回去就完事了。opcode 顺序看 luaV_excute
函数,把操作弄成函数,然后 diff 顺序就出来了,用 unluac 自带的 --opmap
选项替换下就行。最后一个魔改点直接 clone unluac 的源码,然后在 code 加载后解析前把 opcode 和第一个参数的位置给换回去就行。
最后成功反编译(改了下变量名啥的):
1 | local strPadding = function(str) |
简单魔改 xtea:
1 |
|
BBox
输入被 strange.encode 编码后,在 libbbandroid.so 的 checkFlag 中被加密并验证,
首先分析 checkFlag,其中用到了伪随机,种子固定:
而 strange.encode 被混淆了,很难逆向编码过程:
用 r0tracer 得到 ALPHABET 和加密后的结果,推测为换表进行 base64,测试可知结果异或了原文长度:
1 | char[] ALPHABET => n,o,p,q,r,s,t,D,E,F,G,H,I,J,K,L,h,i,j,k,l,U,V,Q,R,S,T,/,W,X,Y,Z,a,b,A,B,C,c,d,e,f,g,m,u,v,6,7,8,9,+,w,x,y,z,0,1,2,3,4,5,M,N,O,P => ["n","o","p","q","r","s","t","D","E","F","G","H","I","J","K","L","h","i","j","k","l","U","V","Q","R","S","T","/","W","X","Y","Z","a","b","A","B","C","c","d","e","f","g","m","u","v","6","7","8","9","+","w","x","y","z","0","1","2","3","4","5","M","N","O","P"] |
直接逆运算得到 flag:
1 | import base64 |
ezgo
搜 flag 字样可以在 go 的一个初始化函数找到 flag 的解析方式,是命令行参数-flag input
,但正常输入依旧 panic。。。在这个初始函数里还发现有 6 个函数指针赋值,后面发现是开了 6 个 goroutine,功能大致如下:
检查
/proc/self/status
的TracerPid
,如果被调试就 panic,没有就调用一次 rc4 解密密文,并通知加密 goroutine。调用
ptrace
,如果不被调试就调用一次 rc4 解密密文,并通知加密 goroutine。检查是否有进程名
linux_server
和linux_server64
,没有就调用一次 rc4 解密密文,并通知加密 goroutine。查看
/proc/net/tcp
,如果没有端口 A8D5 的使用情况(IDA 默认调试端口) 就调用一次 rc4 解密密文,并通知加密 goroutine。调用
getppid
,检查/proc/[ppid]/comm
,如果有问题就 panic。应该是正常输入依旧 panic 的原因,出题人这条件写烂了。。程序运行时间超过 5ns(?)就把密文异或 0x66,这个正常情况下会触发。
主函数也起了个 goroutine,并接受上面几个 goroutine 的信号,得到信号后会调用函数 0x4B0DA0 对对应的输入块做 AES-ECB 加密。注意每次调用 rc4 和 aes 都会对 key 做变化。
理解流程后解 flag 就简单了:
1 | from Crypto.Cipher import AES, ARC4 |
uds
Tea key:
[0x0123,0x4567,0x89AB,0xCDEF]
思路:TEA 加密 [0x44332211, 0x88776655] 之后就是 rc4 的 key,找到密文就可以解密了(注意大小端)
1 |
|
1 | a = [0x01, 0x13, 0x02, 0x96, 0x88, 0x00, 0x12, 0xB0, 0x14, 0xA6, 0x91, 0xFE, 0xB9, 0xD7, 0x41, 0xAF, 0x82, 0xCC, 0x4E, 0xE9, 0x47, 0x47, 0x28, 0x4F, 0xD1, 0x42, 0x10, 0x52, 0x01, 0x58, 0x90, 0xD0, 0x03, 0x00, 0x90, 0xD0, 0x03, 0x02, 0x18, 0x01,] |
四个一组
前三个是最后一个的三个参数
重点看第一组
0810004C 这个函数
byte_20000000 这个是第二个参数
回到这里
这里的第二个参数是 byte_200000A8
好巧不巧,我自己编译写了一下
这个 14 a6 的位置刚好距离 byte_20000000+0xA8
SCTF{W0L000043MB541337}
2024 XCTF 联赛 SCTF 题解