-
-
[原创] 2025-10月Solar应急响应月赛WP!
-
发表于: 2025-10-31 16:11 771
-
1.10月月赛WP
Brainiac
题目文件结构:

Brainiac.exe 程序文件
flag_enc.txt 加密的flag文件
分析:

die结果,x64程序.

运行提示输入参数.




主函数就是一个普通的命令行加密器.关键点在回调里面.


回调内容也不多,但是很关键.表面上是看不出什么的.

但是能注意到,这里有个0是红色高亮的,说明要注意.这其实就是重定位会覆盖的地方,ida十分贴心地标注了.但是没见过的一看可能会不理解.还有下面那些复杂的计算,由于rax为0,ida自动优化了这些计算.所以在f5伪码中看上去,这里的传参就是0x400.这样的优化点有两处.复杂的计算只是一个选择器的代码,不必过多注意.动调起来,注意下这里的变化就真相大白了.

看起来没有更多信息了,开始动调,断点可以直接下在回调里面.
设置好传入的命令行参数.

发生异常,忽略即可.

断下来可以发现,0已经变成了一个64位地址.

现在按f5,得到的就是正确结果了.

这里还有一处障眼法.既然有重定位,那么为什么没有.reloc段呢?



其实只要数据目录的重定位表地址指向了正确的数据(不在文件头中),而且程序开启了动态基址,且未设置去除重定位,就一样能够解析重定位表.

第一处可以看到,直接传入了程序基址,也就说明是修改了文件头的保护属性,第二处将基址先转成数值再加2,则开始执行的地址位于文件头后面2字节.根据题目名字Brainiac,Brain=脑=文件头中的内容,所以查看文件头偏移地址0x2处.


发现该位置是jmp指令.为了降低难度,直接按p即可解析.

cfg大致是这样,加了几条跳转,不影响f5分析.

f5可以得到关键算法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | #include<stdio.h>#include<windows.h>int main(int argc, char** argv){ if (argc != 3) { printf("usage: Dec.exe <input> <output>\n"); return 0; } char* input_path = argv[1]; char* output_path = argv[2]; BOOL success = FALSE; BYTE* file_buf = 0; HANDLE hInputFile = INVALID_HANDLE_VALUE; HANDLE hOutputFile = INVALID_HANDLE_VALUE; HANDLE the_heap = GetProcessHeap(); hInputFile = CreateFile(input_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hInputFile == INVALID_HANDLE_VALUE) { goto cleanup; } DWORD filesz = GetFileSize(hInputFile, NULL); file_buf = (BYTE*)HeapAlloc(the_heap, HEAP_ZERO_MEMORY, filesz); DWORD dwBytesRead = 0; if (!ReadFile(hInputFile, file_buf, filesz, &dwBytesRead, NULL) || (dwBytesRead != filesz)) { goto cleanup; } DWORD fsz_paded = filesz - 16; BYTE* tails = file_buf + fsz_paded; if (strncmp((char*)tails + 8, "ZXLOCKER", 8)) { goto cleanup; } hOutputFile = CreateFile(output_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hOutputFile == INVALID_HANDLE_VALUE) { goto cleanup; } DWORD key = *(DWORD*)tails; DWORD fsz = *(DWORD*)(tails + 4) ^ key ^ 0x89abcdef; DWORD seed = key; for (DWORD i = 0; i < fsz_paded; i += 8) { DWORD* plain = (DWORD*)(file_buf + i); DWORD x = plain[0]; DWORD y = plain[1]; seed = seed * 0xfadebabe + 1919810; plain[1] = x ^ seed; seed = seed * 0xfadebabe + 1919810; plain[0] = y ^ seed; } DWORD dwBytesWritten = 0; if (!WriteFile(hOutputFile, file_buf, fsz, &dwBytesWritten, NULL) || (dwBytesWritten != fsz)) { goto cleanup; } success = TRUE;cleanup: if (hInputFile != INVALID_HANDLE_VALUE) { CloseHandle(hInputFile); } if (hOutputFile != INVALID_HANDLE_VALUE) { CloseHandle(hOutputFile); } if (file_buf) { HeapFree(the_heap, 0, file_buf); } if (success) { printf("TADA!\n"); return 1; } else { printf("ERROR!\n"); return 0; }} |
所以可以写出脚本,这里给了一个完整的命令行解密器.
解密文件,得到flag.flag见前文附件.
工控应急wp
谁把泵关了?
定位 Modbus 写单线圈(Function 0x05) 的数据帧,读取事务 ID、功能码、线圈地址。

Transaction Identifier → 0x1a2b
Function Code → Write Single Coil (5)(即 0x05)
Coil/Output Address → 0x000d 另外 Value=0x0000 表示“关”。
被写入的 NodeId
过滤 tcp.port==4840
右键任一 OPC UA 会话 → Follow TCP Stream
搜索 NodeId= 即可。

工程站域名解析结果
找出对 engws.plant.local 的应答包,读取 A 记录

得到

HMI→工程站首个成功连接时间(UTC)
抓取 445/TCP 的首个 SYN(ACK=0)。HMI 源到工程站目的的第一帧时间即为答案(UTC 到秒)。

将 epoch 转 ISO8601(UTC)

得到flag

对工程站的接口调用(Host 与 URI)
追踪TCP流发现有请求访问POST传输,然后查看发现是对接口的调用
flag为

Ruoyi wp
为了能够看到被劫持效果,从而方便去进行排查
根据提供**配置文件(配置文件实际可以通过解压JAR提取)**进行配置数据库,这里可以用docker 快速部署

下载ruoyi sql文件初始化数据库后即可顺利运行ruoyi
详情请参考官方部署
部署完成后通过Chrome访问 或者手机模式访问api会导致跳转到 f09K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2K6L8$3I4S2M7Y4y4W2j5%4g2J5K9i4c8&6i4K6u0W2j5$3&6Q4x3V1j5`.

我们通过jadx分析该jar包,全局搜索162K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2K6L8$3I4S2M7Y4y4W2j5%4g2J5K9i4c8&6i4K6u0W2j5$3&6Q4x3V1j5`. ,即可发现导致跳转的相关方法

经过测试最终preRedirect方法名是最后的答案

假设我们仅通过静态分析,则推荐的方法是和官方版本的ruoyi进行代码比对,即可发现异常的代码。