2024 D^3CTF 部分题解
千年老二,真系顶佢个肺。
Pwn
PwnShell
1 |
|
write_flag_where
1 | import sys |
D3BabyEscape
没啥好说就是说
1 |
|
d3note
泄露 libc 直接打 free got 表
1 | from pwn import * |
d3ctf{a5479d2c2c552504ef0e8571804e4d9bccfda81e}
Web
d3pythonhttp
第一步首先要绕过 jwt。因为它 jwt 的 key 是根据 kid
读取的,并且当 kid
不存在的时候会返回空,所以可以利用这一点来构造一个 jwt 来通过认证。
tecl 走私干掉 BackdoorPasswordOnlyForAdmin,再 pickle 反序列化,flag 真难找! 还得是 frank
1 | import http.client |
stackoverflow
根据题目名字,稍微瞎试一下不难发现确实有溢出,会覆盖掉第 0 条到 [[short - 3]]
(含)的命令。不过要构造地址的话还得泄漏 pie 才行,这个通过覆盖掉 write
的输出长度让它多爆点米就行了。
接下来,就可以构造 read
来读入更长的程序段来执行了。但是 call_interface
和 {{}}
不能用,并且 call_interface
是在 vm 内执行的。 所以一个合理的想法是,令 call_interface
执行一个函数,这个函数返回一个 {{}}
样子的字符串,然后就能绕过限制,在 eval
里任意执行代码了。 至于绕过 call_interface
,我们可以利用现成的 call_interface
,不覆盖掉它就行了。
然后因为题目不出网,所以可以把输出扔到 /app/static
里,然后通过静态文件访问带出。
1 | import requests |
MoonBox
下载到别人上传的 tar 包之后发现逻辑,然后上车
Exp
/.sandbox-module/bin/install-local-agent.sh 还有 start-remote-agent.sh
1 |
|
/sandbox/bin/sandbox.sh 空文件
打包,注意 z 参数
1 | tar czf 1.tar sandbox .sandbox-module |
然后打包上传到“更新附件”那里
然后去 /api/record/run
运行,参数 "hostIp":"moonbox-server","sftpPort":"22","passWord":"123456","userName":"root"
原因是
dockerbuild 时候看到的:"root:123456" | chpasswd
配置文件里看到 server 为 moonbox-server
这样以后可以弹到 shell
Crypto
myRSA
这篇 paper:Generalized Implicit Factorization Problem
对着源码改改,sage 自带的 gb 又慢而且解不出来,把多项式抽出来然后跑 fast gb
1 | import random |
Github 上给的 example 中分母就是一个 solve,猜测这里的分母和 solve 也有关系,用测试数据 gcd 一轮发现分母(同乘后的首项)就是 w,用 N2 除去得到 z
代入两个式子后 Coppersmith 或者 gcd 两种方法都能求出 x, y
1 | x,y=(2150041731351815713171104523921920493220624053206985451744233895108303740469684723305396314365408654901185731316940674743393624005747389336974965252847296612520628261079495101318288878763133399451251, 226424529213344999668721893182041940135510332166374912207004824461410074124256560817809951006519763202279310112144759780123971358841835703176196881303793274922977845173880043211792854018584878197697797884263497201497403074557926911586759787949296373546575946325913789515250) |
分解了 N1,N2 最后解密即可
d3matrix2
根据迹的性质有
1 | from Crypto.Util.number import * |
求出 L 之后 reverse 一下就是 rangelist,拿去解密就行
sym_signin
爆
1 |
|
1 | import random |
d3ctf{W0w_51ide_Att4ck_M4dE_Ez!}
strange_image_plus+
tap_list 可以重复,所以可以重复输入偶数个 0,使得后续 LFSR 出的东西都是 0
1 | from pwn import * |
1 | from PIL import Image |
enctwice
AGCD + AES Padding oracle (根据 OFB 模式的性质)
1 | ct1 + long_to_bytes(tag + bytes_to_long(ct2) * self.X) + iv + nonce |
中间这部分抠出来:
- Tag: limit, 250bit
- ct2: 256bit
- X: 300bit
一共可以获得 7 组这样的,然后根据 AGCD 的原理进行求解,这个时候 ct2 tag X 都知道了。
1 | from pwn import process, remote |
Reverse
RandomVM
用伪随机数来决定虚拟机流程的虚拟机。首先打开 ida 的 trace function 可以 dump 出整个大概的执行流程,提取出函数的偏移,然后用 idapython 脚本可以提取大概的真正有用的汇编指令
1 | from idaapi import * |
可以发现计算的指令只有 shl,xor。但是我们还能看到三个 syscall。其中有一个是反调试的,如果调试状态下走到那里会多走一个 rand 流程导致后面所有的流程全部向前偏移了一位。最开始就是在这里卡了好久。找到那个 syscall 然后把返回的 0xff 改成 0 过掉那个 rand 就行。只需要过一次就够了
随后就是在每个 xor 和 shl 下断点
1 | def rotate_left(num, shift): |
打印出 log 发现就 43 行,还原即可
1 | def rotate_left(num, shift): |
modern_legacy
谜语人提示,没这谜语的 1960s,当普通 vm 逆还能更快出,唉,纯纯苦力活。
调试一下可以知道 0x1400082A0 是 I/O,跟进去 I/O 函数可以发现会对输入输出做一个映射表处理:
1 | [32, 65, 66, 67, 68, 69, 70, 71, 72, 73, 39, 74, 75, 76, 77, 78, 79, 80, 81, 82, 176, 34, 83, 84, 85, 86, 87, 88, 89, 90, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 46, 44, 40, 41, 43, 45, 42, 47, 61, 36, 60, 62, 64, 59, 58] |
同时从输出函数的参数可以发现 vm 初始化后半段就是数据段初始化,中间有一部分就是密文。根据密文特征 + 调试大概可以发现输入应该为 35 字节,分为 7 份,每份 5 字节进行处理。
调试给输入下硬断,跟了一下就嫌烦了,直接在算术运算那些指令下条件断点跟数据流:
1 | # 0x14000707E |
输入 0123456789ABCDEFGHIJKLMNOPQRSTUVWXY 得到 trace:
1 | 0x2324252627 << 4 = 0x3242526270 |
就是你 1960 年发明了 1994 年的算法?
1 | void decrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { |
D3CTF(TECH-EV0LVE,EMBR@C3-PR0GR3SS)
forest
字符串逻辑比较简单,选对端序 XOR 就可以
核心是 sub_401A00 异常处理内先 SMC 然后篡改控制流,走了 137 次 cli 触发 priv_inst,就是 8*17 次,检查了每个 bit。
爆破到 d3ctf{0ut??????????????} 不给爆了 乐
18496 = 64 * 17 * 17, 每 64byte 一组,迷宫一共 17*17 大,win 的条件是触发 c000005,需要跑出去迷宫
SEH 里边根据当前位置的两个常量计算返回地址,num 1,2 就是下图 data 里的两个常量,所以走迷宫,从 MSB 开始根据每一位是 0 还是 1 决定用上边两个还是下边两个计算下一次跳转的位置
想走出去最后一次应该计算出一个比较大的值,那从 0x406030+18496 每 64byte 切片,然后从 FF 开始反向搜索,上图每两个 cli 对应一个 flag bit 上边是 0 下边是 1,每两个 cli 里常量上边是1 的下边是17 的
1 | import copy |
可以跑到 ut_of_th3Fores
,有了 d3ctf{0ut_of_th3Fores??},大概是起点错了。差两位拿 bat 爆一下好了:
1 | f=open("r\\1.bat", "w") |
ezjunk
反调试:
在 sub_401550 那里的 IsDebuggerPresent,是针对 tea 的 key 的
tea 部分:
sub_401917,很明显的 tea,跑一下就能跑出 sum 和 x
fakeflag 和 flag 在 tea 上无区别
crc 校验:
exp:
1 | #include <stdio.h> |
Misc
Baldur's Gate 3 Complete Spell List
把名字稍微搜索一下,不难发现这些 spell 都对应着一个 level(0~9)。而且 json 中的 spells,数组的每个 item 都由三个或两个 spell 组成,所以猜想每个部份都能翻译成一个数字。于是从 bg3.wiki 查出 spells 对应的 level 就能得到这么一串数字(当然肯定难免有错):
1 | 236 249 249 245 248 75 63 63 239 244 228 241 228 248 249 244 249 236 233 242 228 254 52 231 244 242 63 81 228 91 212 64 231 96 96 71 95 255 74 247 95 243 84 252 231 67 212 245 229 217 231 251 219 66 96 252 98 216 236 68 96 91 236 242 231 65 248 252 221 242 254 236 226 255 69 233 229 242 231 78 |
因为 0 没有出现,所以怀疑 9 进制数。减 1 之后按 9 进制转,得到:
1 | https://koalastothemax%com/?aHR0cMM6Ly9rLnBvc3RpbWcuY2MvOVh4MHhmc1svZmxh_y5ebmc= |
然后把后面的 base64 稍微修一下就得到了一个神秘网址:https://i.postimg.cc/9Xx0xfsk/flag.png,就是 flag 了。
O!!!SPF!!!!!! Enhanced
附件里有一个 OpenVPN 配置,但是缺少了 TLS key 部份。然后题目说缺失的部份在前往 2a13:b487:11aa::d3:c7f:2f
的路径上。所以就 mtr 一下,得到:
按顺序拼起来就是 TLS key,而且长度刚好是整数。有了完整配置之后,就能通过 OpenVPN 连上实例。
剩下的部份就是陪交互程序稍微交互那么一会,然后给它一个 Y
就会吐出 flag。
22:14 发现貌似好像非预期了,更新一下:就是说,通过 OpenVPN 连上对面网络之后,随便 ping 一下,发现 .1 和 .2 好像都有东西。然后发现其中一个的 :18080 端口开着,可以连上 flagserver 服务。然后不难发现,hash 是否正确并不影响程序的正常运行,于是我们就随便给他一些内容,陪它演一下(hash 全错也没有关系),最后它会问你要不要 flag,你和它说 Y
就行。
IOV
D3_car Flag1
发现一个包 com.d3car.factory,把 apk 弄了下来 /system/priv-app/D3Factory/D3Factory.apk
在 activity 里有 flag
2024 D^3CTF 部分题解