diff --git a/content/generated/metas.json b/content/generated/metas.json index 5e5852b..e93356a 100644 --- a/content/generated/metas.json +++ b/content/generated/metas.json @@ -1,9 +1,10 @@ { - "tags": [["ctf", 3], ["open-keyprec", 1], ["技术", 1], ["日记", 2], ["梦", 1], ["心理", 1], ["影评", 1]], + "tags": [["ctf", 4], ["教程", 1], ["open-keyprec", 1], ["技术", 1], ["日记", 2], ["梦", 1], ["心理", 1], ["影评", 1]], "categories": [["置顶", 1], ["技术", 1], ["日记", 2], ["梦", 1], ["想想想", 1], ["影评", 1]], - "pins": [12], + "pins": [13], "posts": [ - {"id": 12, "title": "部落格索引", "subtitle": "按分类、标签检索", "category": "置顶", "pinned": 1, "file": "content/posts/Index.md", "tags": [], "date": "2024-08-06T06:06:08.343977", "url_name": "Index", "content": "按分类检索:\n\n\n\n按主题检索:\n\n"}, + {"id": 13, "title": "部落格索引", "subtitle": "按分类、标签检索", "category": "置顶", "pinned": 1, "file": "content/posts/Index.md", "tags": [], "date": "2024-10-25T01:35:18.686601", "url_name": "Index", "content": "按分类检索:\n\n\n\n按主题检索:\n\n"}, + {"id": 12, "title": "世界计划 缤纷舞台!feat. IL2Cpp", "title_image": "https://profile-content.hydev.org/content/posts/Assets/2024-10-24%20PJSK%20Reversing/crop.png", "subtitle": "从零开始解密 pjsk 的网络请求", "tags": ["ctf", "教程"], "file": "content/posts/2024-10-24 PJSK Reversing.md", "date": "2024-10-24T00:00:00", "url_name": "2024-10-24-PJSK-Reversing", "content": "玩了这么久プロセカ,有点想分析一下游戏的排行数据,做一些自动化,比如让自己一直停留在整两百名之类的。这种手游抓抓包模仿请求一定不会很难吧——装上 Reqable 连上模拟器抓抓包偷吃几块饼干之后发现,请求负载和返回内容都依然是乱码...\n\n
只偷吃到了饼干渣
\n\n看到乱码的第一反应是编码不对,或者过了某种基础混淆,总之先丢给 CyberChef 看看魔法糊糊器能不能把答案糊出来,发现不行。去群里问问之后 Menci 和我说是 AES 加密过的,还告诉我了加密密钥在 APIManager 里面,那就试着找找吧\n\n## 1 从拆开 APK 开始\n\n因为プロセカ的客户端能够解密,那么解密密钥一定就在客户端里。首先下载了プロセカ 4.0.0 的 XAPK 包。所谓的 XAPK 其实就是把多个 APK 压缩在一起啦,其中一个包含了所有架构共享的文件,另一个包里放了 arm64 平台专属的二进制(虽然这个游戏好像还没有编译过除了 arm64 以外的平台,不知道为什么还要这样分包)\n\n解压缩之后单独用 apktool 把两个安装包的文件拆出来看一看吧。目录结构看上去就是一个一般的 IL2Cpp Unity 项目,没有什么特别的。编译过的游戏二进制在 lib/arm64-v8a/libil2cpp.so 里面,然后运行它、让它能够和 C# 那边的程序对接需要的元信息在 assets/bin/Data/Managed/Metadata/global-metadata.dat 里面。\n\n
两个重要的文件
\n\n如果想要快速找到代码的话,只看 IL2Cpp 编译好的的二进制肯定不行。这个文件里一共有 44 万个没有名字的函数指针,IDA 把这些分析出来都花了快一个小时,从这里一个一个找肯定是不行的。所以我搜索了一下 IL2Cpp 反编译,找到了 Perfare/IL2CppDumper 这个工具,试了一下发现报错说 \"Metadata file supplied is not valid metadata file\"\n\n
正常的元文件的文件头
\n\n为什么呢?看了一下,一般的 global-metadata 的文件头是 0xAF1BB1FA,而我拿到的这个不是,也就说明它很可能被加了壳。\n\n## 2. 脱壳元文件\n\n怎样解密呢?首先试了同一个作者的另一个工具 Zygisk-IL2CppDumper,是一个据说可以自动解密元文件的 Magisk 模块,但是构建好装上之后打开游戏发现什么都没有发生,说会解包到 /data/data 里面但是也没有,还是试试别的方法吧。\n\n既然上次用 Cheat Engine 在电脑上从内存中脱壳了 maimai 的 Assembly-CSharp,我觉得应该也可以用同样的方式脱壳プロセカ的元文件,所以就下载了一份 Game Guardian 试了一下。打开游戏,挂载上 GG,内存搜索 \"H AF1BB1FA\" 之后马上就找到了!\n\n
搜索到了内存
\n\n把地址复制下来,去第四个 Tab 把地址粘贴进去之后点旁边的选择器选中这个区域的头到尾,导出成文件之后重命名成新的 global-metadata.dat 就可以了。之后重新试了一下用 IL2CppDumper 反编译就没有报错了!\n\n
反编译成功了!
\n\n虽然还是有 \"This file may be protected\" 的报错,原本还以为 libil2cpp.so 也被套了壳,结果发现并没有,只是识别到了 JNI_OnLoad 预警性地报了一个错而已,也就是说已经成功啦。看了一眼 Dumper 输出的 `dump.cs`,里面确实有 APIManager,里面也确实用到了 AES 加密,但是因为实现的部分被转成二进制了,这里只能看到定义。\n\n## 3. 找钥匙!\n\n接下来就是找钥匙的环节,但是怎么找呢?如果要找到钥匙的话,大概需要看一下 APIManager 的代码实现,可是 `dump.cs` 里面只有空壳没有实现,可是只看 libil2cpp.so 又没办法知道 APIManager 在哪... 怎么办呢?看了一下 Dumper 仓库里面写的文档,看起来像是能和 IDA 联动让实现和空壳结合在一起的样子,那就下载一个 IDA 试试吧。\n\n下载了 IDA 9.0,其实不太会用,我只懂得基础操作比如用 F5 切换反编译代码和汇编之类的,但是总之先打开二进制 libil2cpp.so 看看吧。刚打开的时候什么都没有,只有不到一百个函数,我还在怀疑是不是打开错文件了,但是等了十分钟又出现了很多函数,发现原来它只是还没有分析完... 可能 150MB 大的二进制对于 IDA 来说也太大了吧,完整分析完花了差不多一个小时,存的数据库有 2 GB 大 (╯’ – ‘)╯︵ ┻━┻\n\n
好大文件
\n\n分析完之后依然没有函数名,全部都是 sub_1234567 这样的数字编号。看着 Dumper 的文档试着用 File > Script file 跑了一下那个 ida_py3 的脚本,选中了 script.json 之后函数名确实出现了!不过这只是第一步。\n\n看了 `dump.cs` 里面的函数定义之后,我觉得 CreateCrypt 这个函数比较像是我们要找的,因为是一个静态函数,调用了之后会凭空造出来一个 FastAES 的实例,而 FastAES 需要传入两个钥匙才能创建实例,所以无论怎样这里都会用到钥匙吧?\n\n
APIManager 的函数定义
\n\n看看反汇编的二进制代码发现确实用到了密钥。虽然变量全都被打乱了,但是从 FastAES 的构造器参数可以看出来 v3 v4 是 AES128 的两个钥匙 (key, iv),然后规格是 AES128,密文模式 1 (CBC),补格模式 2 (PKCS7)。那么这两个变量是从哪里来的呢?从代码里也能看到,v2 是从 v1 + 184 的指针地址读过来的,v3 是 v2 的值,v4 是 v2 的下一个值也就是 v1 + 192,而 v1 是 APIManager_TypeInfo 的地址。\n\n\n\n从 C# 的逻辑上想的话,TypeInfo 应该是一个类附带的常量或者变量类型的地方,从 `dump.cs` 里面可以看到它定义的变量指针偏差从 0 加到 8 加到 0x20, 0x28... 可是这里面没有 +184 = 0xb8 这个位置,为什么呢?\n\n
APIManager 类的变量偏差最高到了 0x38
\n\n我在这里卡了超级久,然后看了一下构造器发现 +184 这个偏差原来是在调用构造器的时候动态写进去的,是代码里面没有定义的,也许是某种编译器优化吧。打开 `APIManager$$.cctor` 构造器首先发现的是它写入了指向 +184 的地方 (L23 行),写入的是 v3 的值。向前读可以看到 v3 就是 v1,是一个空的 16 字节数组,然后把 v2 的数据复制到了 v1,而 v2 是 `Field__` 开头的这一长串。\n\n
cctor 构造器的反编译代码
\n\n回到 `dump.cs` 里面查找这个字段,发现它是一个编译器生成的只读项,上面下面也有类似的东西,看这个 \"Static Array Init Type Size\" 的话这些应该就是代码里用到的数组常量被转移到的位置了。但是这一长串的十六进制似乎只是某种指针,数值似乎不在这里——因为如果是数值的话,数组的长度对不上——我们要找的 AES128 密钥是 128 位也就是 16 字节,但是这一长串每个都有 32 字节,和前面的 Array Size 也对不上。那么数值在哪呢?\n\n
十六进制 offset 在编辑器里五颜六色
\n\n看到后面五颜六色的 offset 那里写了注释 \"Metadata offset\",我就在想难道它会在 global-metadata 里面吗?用 hexed.it 打开了这个文件然后把 offset 复制到 Go to 栏里面转向,发现那里真的有一个 16 位的钥匙!接下来又按同样的方式找到了 16 位的 iv,整个 AES 加密就被破解啦(在这里打一下码,需要的话看完这篇肯定就能够自己找到了 quq)\n\n
找到了 16 位的钥匙
\n\n## 4. 可以解密了!\n\n接下来就是解密的过程了,把请求内容和返回内容都导出成文件,再随手用搓两行 python 脚本就可以解密啦:\n\n```python\nimport sys\nfrom Crypto.Cipher import AES\nfrom Crypto.Util.Padding import unpad\nfrom pathlib import Path\n\naes = AES.new(b\"g2fcC0ZczN9MTJ61\", AES.MODE_CBC, b\"msx3IV0i9XE5uYZ1\")\ndec = lambda x: unpad(aes.decrypt(x), AES.block_size, style='pkcs7')\n\n[f.with_suffix(\".dec\").write_bytes(dec(f.read_bytes())) \n for f in Path(sys.argv[1]).glob(\"*\")]\n```\n\n解密之后似乎是一个 pjsk 自己的二进制序列化格式,看起来还需要一些时间研究怎样把它转换成 json 呢,不过今天就到这里好啦,累累睡睡(x)\n\n
解密之后的明文
"}, {"id": 11, "title": "示波器破解移动光光猫", "title_image": "https://profile-content.hydev.org/content/posts/Assets/2024-07-30%20%E5%85%89%E5%85%89%E7%8C%AB/IMG_20240730_100300.avif", "subtitle": "跑不掉了喵~", "tags": ["ctf"], "file": "content/posts/2024-07-30 光光猫.md", "date": "2024-07-30T00:00:00", "url_name": "2024-07-30-光光猫", "content": "今天去 syama 家帮忙破解了移动光光猫 🐱\n\n因为笔记本太重了,syama 想要在外面用 iPad 远程桌面直连到家里的笔记本上工作,但是移动光猫有防火墙 ipv6 没办法直连,而且用户配置界面并没有办法关掉防火墙,所以想要找到 CMCCAdmin 超管密码。\n\n通常来说超管密码直接找宽带师傅要或者找移动内鬼查就能查到,但是这台是公寓提供的集客宽带所以没办法联系师傅,内鬼也查不到密码。\n\n去网络论坛上看了其他人的破解方法,一月份的时候似乎还可以通过 192.168.1.1/hidden_version_switch.html 之类的网页后门直接开启 telnet,但是试了很多全都被堵住了。看来软件是没办法了,拆机看看\n\n
拆机后的完整布局
\n\n光光猫型号是 SK-D747,拆机之后看到了这个 PCB 的完整布局,中间是铠侠的 128MB E2PROM,旁边有八个测试孔,左侧是光纤接入口(系统里是一个 PCIe 设备...??)右边应该是 CPU 和 DDR3 DRAM,上边是两个 LDO 分别转 3.3V 和 5V,右上方还有一些空的焊盘应该是高配版多出来的网口。\n\n
发现了 E2PROM 芯片旁边的八个测试引脚
\n\n在 E2PROM 旁边的这八个测试孔,四个焊了排针,四个没焊。通常光猫上都会留用来测试的 UART 串口,但是串口只能够用到 TX RX GND 三条线,TX 用来向对方发送,RX 用来接收,GND 作为判断传输电压的参照物。排针上显然并没有文档,没有标记每个引脚代表什么,还可能是 RS232,I2C,或者 SPI 也说不定,那要怎样判断每个测试孔代表着什么呢?\n\n先断电用电表测试线路接通的模式戳一戳好了,因为一条线路的所有焊点都连在一起,所以只要确认了一个点就可以找到所有了。首先 GND(地线)是最容易找到的,因为元件的外壳通常就会接到 GND。把电表的一端点到 USB 口的金属外壳上,另一端轮流点一下每个测试孔就能看出从上面数的口 0、4、7 都是接地。\n\n接下来还剩五个不确定的孔,通电测一下电压好了。孔 1 是 5V,孔 5 是 3V,6 是 0.074V。只看电压其实不是很有头绪呢,把照片发到嵌入式群里,群友猜 5 6 分别是 TX 和 RX,但是插上串口工具发现并没有输出,反过来也没有。\n\n
电表针好长感觉不注意就会把什么戳短路
\n\n突然想到,既然之前买了示波器就试试用示波器看看这些孔的波形好了!虽然之前没有用过但是也不会太难理解吧?打开包装有一个主机和两根测笔,像电表一样,那一定是在测量这两根针之间的波形吧?一根戳上 GND 另一根戳上测试点,点一下自动... 不对啊,看上去无论点哪里都是一个正弦 ~ 波?这看起来也不像是时钟啊?\n\n...然后把照片发给群友问问,发现我完全理解错了(悲)测量到的波形也只是无线电噪音 \n\n实际上示波器的每根测笔是单独的波形,有两条线,侧边的夹子是电压的参照,而测针是相对的信号。读结果的时候也理解错了,虽然看上去是很大幅度的正弦波,但是右边显示着只有 5mV/格... 也就是说看到的这个波形上下差距一共不到 10mV,对于任何信号传输都太小了。居然这么小的电压区别也能测量出来哇\n\n学会使用示波器之后把示波器夹子接地,针点到每个引脚,调到 1V/格 的显示比例,发现脚 2 是稳定 3V,脚 3 有零到 3V 的 TTL 高低电平切换,由此推断 2 3 才是 TX 和 RX。这次接上 UART 终于看到日志输出了!\n\n
用示波器看到了上下切换的数据电平,高低电压分别代表 1 和 0
\n\n接上之后就交给 menci 了。重启路由器,终端打了很多日志,似乎是 uboot 在启动被运营商魔改的 OpenWRT。等开机结束之后导出日志,在里面搜索 \"pwd\" 找到了 root 密码 🌚 真安全。登录进 root 之后去读文件 /userconfig/cfg/db_user_cfg.xml 就拿到了超管密码,然后我累累睡睡,menci 念一串魔法配好了桥接和公网 v6 直通,好厉害\n\n
躺在床上听着魔法少女 menci 调网咒语 ASMR 睡着了
\n\n配网相关的事情可以右转去读读 [menci 的博客](https://blog.men.ci/),虽然我也想学学配 ipv6 但是这两天通宵爆肝实在没有力气啦,等加拿大 Bell 有了 ipv6 再学学好了。\n\n总之这次学会了用示波器很开心!~~会用之后就看到什么都想用示波器点一点了~~"}, {"id": 10, "title": "Maimai 街机音游逆向!", "subtitle": "从拆包到脱壳到魔改遇到的各种坑", "tags": ["ctf"], "file": "content/posts/2023-11-30 Maimai Reversing.md", "date": "2023-11-30T00:00:00", "url_name": "2023-11-30-Maimai-Reversing", "content": "不久之前某位朋友拿到了最新最热的 maimai DX 某版本的 .app 镜像,然后和ta一起折腾了几天拆包脱壳之后终于成功启动了!在这里简单写一下解包过程和学到的事情,作为记录也作为一个教程吧,希望之后尝试解包的人不需要踩同样的坑了。\n\n## 0x1 解压 .app 包\n\n首先,朋友拿到的文件是一个 .app,通常在官方渠道中通过 U 盘或者网络下发给官方的街机框体,这也是所有 Sega 游戏都在用的传统格式。解压起来也已经有工具和教程。首先 .app 是使用 AES128 加密的,是一个对称密钥加密算法,然而已经有人帮忙把真正街机上的密钥复制出来了,接下来就是按教程解密啦,工具和教程都在[这个仓库](https://gitdab.com/SEGA/sega/src/branch/master/tools/Filesystem)里面。\n\n解密之后是一个两层的 VHD 文件。第一层似乎文件元信息是坏的,大部分硬盘工具分区工具解压工具都打不开,不过试了各种方案发现在 Linux 上用 qemu-nbd 可以挂载,虽然会报错。挂载用了下面那一串指令,之后把 /mnt 里面的第二层 VHD 拷出来就可以直接用 7zip 解压了。\n\n```sh\nsudo modprobe nbd\nsudo qemu-nbd -c /dev/nbd0 \"outer.vhd\"\nsudo mount /dev/nbd0 /mnt\n# /mnt 里面应该有 internal_{N}.vhd\n```\n\n下面分别是两层 VHD 的文件头和 file 信息,`conectix` 开头的是第二层,`eb5290 NTFS` 开头的是第一层(以下用旧版中二节奏举例):\n\n\n\nqemu-nbd 加载了第一层之后连 fdisk 都是懵的,即使是这样乱来也能挂载上也真是好神奇:\n\n\n\n## 0x2 Crackproof 脱壳\n\n解开 .app 包之后脱壳反而是最难的步骤,因为没有找到任何针对 SEGA 街机的脱壳教程(所以我才在写这篇教程!)解压好文件之后发现直接运行会闪退,为什么呢?用文件识别工具 [DIE (detect it easy)](https://github.com/horsicq/Detect-It-Easy) 检测一下发现主程序 Sinmai.exe 被一个叫 HyperTech Crackproof 的工具加密过 (aka. “Htpac”)。\n\n\n\n啊但是 Sinmai.exe 明明就只是一个 Unity 启动器,Unity 的话所有游戏的启动器都是一样的,让每个游戏不同的是 {Game}\\_Data 里面的 dll 文件,所以我直接去下了一个[开源 GTA 圣安地列斯](https://github.com/GTA-ASM/SanAndreasUnity)然后把启动器拷出来改名成 Sinmai.exe 就可以了!\n\n\n\n...然后还是没法启动,怎么回事呢?用 `diec` 扫描一下目录里面的所有 exe 和 dll 发现还有四个文件是加密过的:\n\n* amdaemon.exe\n* Sinmai\\_Data/Managed/Assembly-CSharp.dll\n* Sinmai\\_Data/Plugins/Cake.dll\n* Sinmai\\_Data/Plugins/amdaemon_api.dll\n\n查了一下,首先我看到 SEGA 工具库里面有一个叫 [DecryptCrackproof](https://gitdab.com/SEGA/sega/src/branch/master/tools/Crackproof/DecryptCrackproofExe64) 的工具,感觉这个尝试起来是最简单的,所以就先用它试了一下。这个工具确实自动解出来了 amdaemon.exe,但是剩下三个 DLL 都失败了,报错说 CRC32 算法的长度 out of range of valid values。\n\n\n\n看着这个报错,第一眼觉得应该是这个算法实现的问题,毕竟从 stack trace 可以看出传给 CalcChecksum 的参数是 UInt32,而 CRC32 函数接收的参数是 Int32... 会不会是长度超了 Int32 的最高值呢?所以就用 DotPeek 反编译、导出项目重新编译再 Debug 饶了一大圈,然后发现并不是长度超过 Int32 最高值,而是超过了整个文件的长度... 而请求长度又是一串非常不标准的计算算出来的,看来应该是 HyperTech 他们换了加密算法,并不是改一两行就能修好的。\n\n\n\n接下来有点没有头绪,到处查查也没有查到有用信息,有一个 [iatrepair](https://github.com/rakisaionji/iatrepair) 仓库的脱壳思路是开一个 QEMU 有 2GB 内存的 VM 里面跑游戏,等游戏加载完之后把整个系统的内存 dump 出来再解包分析... 感觉这个方法好灵车,而且这个加密也会检测系统是不是 VM。\n\n接下来去群里问了问,群友给我了一份街机系统上会有的 odd.sys 驱动,用 OSRLoader 加载进系统就可以启动游戏了,虽然是黑屏而且不能正常启动 amdaemon。原本以为解密代码在驱动里面,所以先用 IDA Pro 打开驱动看看能不能提取出来,但是发现似乎只有一些 hashing 的 PID 验证和字符串处理,返回一个处理过的字符串,这样看来这个驱动应该是让用户进程上传识别信息来生成真正解密密钥的工具。\n\n\n\n但是感觉要反汇编找到真正解密的代码再重新实现出来有点太复杂了... 还是试试直接从内存里面找吧。\n\n下载一个 CheatEngine 用来查看内存,然后启动游戏。刚开始发现 CheatEngine 的调试器挂到 Sinmai 进程上会报错,上网查了一下说可以试试在设置的调试器选项里开 DBVM 内核驱动模式,再把 Extras 里面的用内核模式浏览内存选上,然后果然就可以挂了!\n\n\n\n接下来打开内存视图,先看了一遍 Memory Regions 里面标注的 DLL 名,但是似乎没有我想要找的 DLL 的样子。那怎么办呢?看看有没有只属于这个 DLL 的可识别信息吧,用 hexed.it 打开上一代别人解包好的 Assembly-CSharp.dll 发现文件末尾有一串元信息,写着 \"InternalName Assembly-CSharp.dll\",每个字符中间间隔着一个 NULL \\\\0 字符。\n\n\n\n所以我在 CE 内存视图里面搜这段文字然后就找到了!正好只有一个匹配结果。然后记录一下最左边的地址,打开内存区域视图找到这个地址所属的区域,右键把整个区域存下来,然后用 hexed.it 打开把 CE 的文件头去掉(截到 DLL 文件头那里)再把文件尾补到整 32 位,保存改名成 DLL 就可以了!\n\n\n\n之后就只差 Cake.dll 和 amdaemon_api.dll 了,可惜这两个我用同样的方法没有解出来。amdaemon_api 在游戏里并没有加载,而 Cake 是一个包含了依赖的 dll,被拆成了很多不同的内存区域导致让它完整结合回去比较麻烦,但是这两个文件在不同版本中几乎没有改动,所以就直接拿上一个版本别人脱好壳的用了(一般会放在 segatools 文件夹里一起发出来)\n\n## 0x3 魔改\n\n可惜脱完 dll 壳并不是直接就能玩,还要去掉一些街机验证。因为 maimai 是一个 Unity 游戏,是用 C# 写的,而众所周知 .NET 运行时的结构和 Java 差不多都是反编译出来几乎直接是源码的,所以魔改思路就是把 Assembly-CSharp 反编译成源码,改好所有的代码报错,改掉验证然后再编译回去啦。\n\n\n\n...结果还在修报错的时候发现朋友已经魔改完了,所以就先用别人的啦,具体需要做哪些魔改等下次拿到最新最热数据再研究好了 qwq\n\n## 0xF 总结\n\n总之这是我第一次做 Windows 二进制程序脱壳,感觉有别人写好的文档和群友帮忙也没有想象的那么难(以前打 CTF 从来都不敢碰 binary 题的呜呜呜)虽然是朋友先解出来魔改完了有点扫兴,但是学到了很多脱壳技巧还是很开心的!现在就差给 [AquaDX](https://github.com/hykilpikonna/AquaDX) 加服务器支持了...(下次再写 qwq"}, {"id": 9, "title": "CyberSci 2023 CTF 网安比赛 Writeup", "subtitle": "你是来打网安还是修 Bug 的?🌚", "tags": ["ctf"], "file": "content/posts/2023-11-18 CyberSci Canada Writeup.md", "date": "2023-11-18T00:00:00", "url_name": "2023-11-18-CyberSci-Canada-Writeup", "content": "我解的五道题都是 Defense 分类的,Defense 规则是修好一个服务器端让攻击方的 PoC 脚本失效就能拿分(这不是我维护服务器日常做的事情吗 ;-;\n\n前三道题「CFP」是一套 PhP + Nginx 写的用来上传作业文档的服务器,后两道题「Swag Shop」是一套 Go GIN 写的购物服务器,全都跑在 docker compose 容器里(然而这两个语言我都没用过呜呜呜)\n\n知道问题的时候修一个 bug 很容易,但是 PoC 机并不会告诉你 bug 在哪里,所以解题的第一步是去查 HTTP log 找到被攻击的方法。\n\n## 前三道题 - Call for Presentations\n\n### Attack 1\n\n前三道题的部署结构我真的太熟悉了,因为我自己的所有服务器都是 Nginx + Docker,直接 `tail -f /var/log/nginx/access.log` 读出所有的请求,整理出一个攻击从开始到结束的所有请求,再 `cat /tmp/nginx-req-body/{n}/` 来读出每个请求内容。看一下请求过程如下:\n\n1. 注册一个帐号\n2. 登录这个帐号\n3. 换一个不同的密码登录这个帐号还登进去了??\n\n啊所以难道给我的这套代码登录甚至没有验证密码的吗 (╯’ – ‘)╯︵ ┻━┻\n\n
欣赏一下屎山代码
\n\n看看源代码 lib.php 确实数据库密码验证逻辑写得乱七八糟,数据库直接 string concat,密码甚至没 hash... 好直接让 GPT4 重写了一遍登录和注册这两个函数,然后 PoC 就失效了,拿分拿分 🌚\n\n
GPT4 帮我修好了!
\n\n(不过我至今没弄懂 PhP 要怎样把 log 打到能看到的地方... 打比赛的时候试了几个不同的方法都看不到 log 就算了算了直接把 curl hydev.org/say 写进去了,发到 /say 的东西会转发到我的 Telegram 上hhhh\n\n### Attack 2\n\n第一个好简单。第二个攻击的请求乍一看好像没什么问题,但是仔细看看就会发现 PoC 机注册的帐号的名字和请求提交的 JWT Token 名字不一样然后还莫名过签证验证了... 为什么呢?\n\n
第二个攻击的 Log
\n\n看一眼代码确实,签名居然不是密码学签名,而是直接把请求 JWT 里面的东西 hash 了一遍,验证签名和 hash 相等???🫠 这种客户端能生成的东西还算签名吗?算了交给 GPT4 去改,果然改完就过了。\n\n### Attack 3\n\n第三个攻击感觉是最简单的,看日志内容发现用户上传了五个不同的文件,但是看请求内容那一步就马上发现不对劲了——五个文件中有一个是 PhP 文件,里面是攻击代码,居然直接以 .php 结尾被传进代码旁边的子目录了。泰经典辣,甚至没有用 GPT4,直接给 portal.php 上传文件的代码加一个文件结尾检查就过了(虽然如果是现实中真的遇到这种情况的话肯定要检测文件内容的)\n\n## 后二道题 - Swag Shop\n\n上一道题因为是 PhP,改完代码直接 `compose up --build -d` 重新跑容器也只要几秒。但是这两道题是用 Go 写的,而 Go 是需要编译的语言,所以发现的第一件事情是 `docker build` 居然要两分钟 (╯’ – ‘)╯︵ ┻━┻\n\n磨刀不误砍柴工,先把本地 Go 环境和依赖装上,把 SSL cert 挪到该放的地方,带缓存的编译重跑就只需要不到一秒了。\n\n\n\n### Attack 1\n\n第一道题非常非常简单,跑起来之后看 HTTP 访问日志马上就看到了一条野生 SQL 语句 👀 原来要修的是 SQL 注入。看看代码,main 里面定义了这个 `/shop/items/:item` 节点的函数是 DescribeItem(),把整个函数拷给 GPT4 重写拿分()\n\n
GPT4 不知道今天帮了我多少忙
\n\n### Attack 2\n\n第二道题我觉得是这五道题里面最难的,因为攻击方法并不明显。整个日志看起来都非常正常,我为了让日志看起来更整洁还隐藏了签名验证的部分,日志里实现的操作都是用户正常使用会用到的,管理员创建了几个商品,删了两个商品,用户读了一次商品列表,然后买了一件,感觉没有哪里不对。\n\n
非常正常的日志
\n\n看了超级超级久还没发现问题,但是把签名打出来发现了不同请求的签名长度不一样,所以猜猜可能是签名验证的问题就去检查了一下签名验证的代码,然后发现了这堆鬼东西:\n\n
他们自己实现了一个 ASN 验证,后面是五百行的我都看不懂的纯正密码学
\n\n不懂就不要随便自己实现加密算法啊啊啊 (╯’ – ‘)╯︵ ┻━┻\n\n密码学真的有太多我看不懂的理论数学,所以我也看不出他们的实现有没有 Bug,但是总之先试试让 GPT4 用标准库重写一遍 VerifyASN 吧... 重写完重跑一遍居然就真的就过了 🌚\n\n## 总结\n\n总之五道 Defense 题全都解出来啦,然后看时间不够快赶不上 One Among Us 的手工活动就赶快走了。感觉很开心!这是我参加的第一个有 Defense 题型的 CTF,感觉是和 Web 题完全反过来的题型。Defense 少见可能是因为这种题比较难封沙盒吧,不过第一次知道原来像我这样的平凡全栈技能也能在 CTF 上起到作用。\n\n原本登记了这次比赛只是因为 UofTCTF 他们组缺人,去之前还觉得自己一年没打过 CTF 了说不定一道题都做不出来呢,结果发现并没有全都忘掉也很开心。居然比同组的那些 UofTCTF 活跃队员分还高一些(不过也是因为 Defense 题更简单啦)总之虽然已经退坑了但是偶尔打打 CTF 还是很好玩的!"},