2025-11-week3
week3刷题
1. [2019红帽杯]childRE
下载题目后直接IDA打开,然后分析main的反编译伪代码,将一些变量函数名修复一下:

整个main函数有两部分组成,首先就是输入input,然后将输入经过XMMI2_FP_Emulation这个函数将input做相关操作然后存到v4中,再使用sub_1400015C0进一步处理,将结果存放到v5。
第二部分就是将v5通过UnDecorateSymbolName函数生成的字符串存放到outputString当中,用outputString当中的元素当作一个表的索引与其他两个表进行比较,这个就是check部分。
那么先来解这个check部分就能解出来outputString这个字符串是什么了。这里的check当中使用的%23, /23其实就是带余除法,那么只需要将这两个索引*23 + 余数就是outputString的元素:
1 | mapper = bytes.fromhex("31 32 33 34 35 36 37 38 39 30 2D 3D 21 40 23 24 25 5E 26 2A 28 29 5F 2B 71 77 65 72 74 79 75 69 6F 70 5B 5D 51 57 45 52 54 59 55 49 4F 50 7B 7D 61 73 64 66 67 68 6A 6B 6C 3B 27 41 53 44 46 47 48 4A 4B 4C 3A 22 5A 58 43 56 42 4E 4D 3C 3E 3F 7A 78 63 76 62 6E 6D 2C 2E 2F") |
这就求出outputString字符串其实就是:private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)这么一个C++的函数。
再来说UnDecorateSymbolName这个API,其实就是将C++编译之后的粉碎函数名给转成了上面这种人能看明白的形式,那么按照上面的函数声明来用VS定义一个函数,然后输出函数名:
1 | class R0Pxx { |
这里要注意的是__thiscall只在x86下有效,所以要指定编译的平台,运行之后就能跑出粉碎的函数名:?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z。但是者并不是输入,可以看到第一部分经过了XMMI2_FP_Emulation,和sub_1400015C0才变成的这个函数名。来分析一下这个sub_1400015C0作用是什么:
发现当中是进行了递归处理,而这种形式由经验得到就是一棵二叉树,这里在做二叉树的后序遍历。那么由参数猜测,这里传入的参数是v4,那么XMMI2_FP_Emulation应该就是将输入转成一棵树的过程,但是这个函数不太好分析,如果反推树结构的话难度比较大,这里可以使用一种更加方便的方法,就是:映射。
输入一串字符各不相同的字符串,然后看这个转换之后出来的是怎么样的,再映射回去就能求出输入:
1 | mapper = bytes.fromhex("31 32 33 34 35 36 37 38 39 30 2D 3D 21 40 23 24 25 5E 26 2A 28 29 5F 2B 71 77 65 72 74 79 75 69 6F 70 5B 5D 51 57 45 52 54 59 55 49 4F 50 7B 7D 61 73 64 66 67 68 6A 6B 6C 3B 27 41 53 44 46 47 48 4A 4B 4C 3A 22 5A 58 43 56 42 4E 4D 3C 3E 3F 7A 78 63 76 62 6E 6D 2C 2E 2F") |
最后能求出flag{63b148e750fed3a33419168ac58083f5}
2. [SWPU2019]ReverseMe
拿到附件之后IDA打开题目,能够发现这个题目逻辑大概也是两部分,第一部分就是这一块:
将输入和SWPU_2019_CTF进行逐字节异或,紧接着就是下面这一块:
将输入传入这个2125C0的函数当中,然后传入来v30,与v36比较。那么这个v36就是密文。而这里的sub_2125C0传入了一个256,猜测可能是流加密之类的东西,跟进分析:
这个确实就是流密码,有以下部分:
这里先进行了流密钥的生成,然后再用生成的密钥进行加密,而这个密钥是动态生成的,关键函数在:Block[v29] = v9 ^ sub_212150()这个位置,那么这里就需要使用动态调试将对应的密钥提取出来了。
定位到这个指令的位置,这里用windbg调试,顺带将eax的值打印出来,先下个断点:
1 | bp 0x7626C2 ".printf \"EAX: 0x%08x\\n\", @eax; g" |

这里下断点的时候要注意重定位的问题,然后就能将eax跑出来了,接着就是写脚本解题:
1 | import struct |
flag为:flag{Y0uaretheB3st!#@_VirtualCC}
3. [HDCTF2019]MFC
这个题考察的主要是消息机制,而不是静态分析了。因为整个程序被加了VMP,不太可能直接分析,运行程序弹出Dialog:
既然是mfc,那么直接使用xspy看看有没有有用的东西:
发现有一个自定义的消息,那么就往这个窗口发送一下这个消息,接着还有窗口的类比较奇怪,像是什么加密后的结果。先发消息看看:
1 | HWND hWnd = ::FindWindowA( "944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b",NULL); |

找到了,这里是DES的key,而DES的key是8字节64位,所以需要截断,真正的key是:"{I am a " 。结合窗口类的类名,直接赛博厨子一把梭:
最后的flag为:flag{thIs_Is_real_kEy_hahaaa}
4. [D^3CTF]ezJuck
这个题的花指令比较有意思,不能用暴力nop的方式去掉花指令。拿到题目先查壳,然后丢IDA:
main函数上来就是一段花指令,看到开始调用了一个sub_401CC0函数,那么就先来看看这个函数作用是什么吧:
1 | __int64 sub_401CC0() |
可以反汇编,发现里面调用的是一个sub_401C50的函数,继续跟进看看:
1 | __int64 sub_401C50() |
发现是这么一个函数,形式挺像elf文件当中的init_array,也就是调用函数指针数组当中的函数,循环调用完之后又在sub_401510函数当中传入了一个函数的函数地址。那么先来看看这个qword_403350有哪些函数:
有这两个函数,直接跟进550,有一个简单的花指令,去一下即可:
然后就能反编译了:
里面就是一个反调试,如果检测到则修复错误的位置。那么接下来该分析main函数了,由于这个main函数的花指令有点抽象,不好直接静态看,那么直接动调一下,这里的反调试要绕过一下,动调之后,ida自动修复了反汇编:
那么此时直接将上面的call sub_401CC0直接改成jmp到401A2E就可以了,然后就可以反编译了:
main函数的逻辑还是比较清晰的,就是一个变种的tea,然后就能check了。为什么说不能使用nop这种暴力的方法来去花指令,原因就是这里的tea使用的key和detal都是从代码当中获取的,nop掉了就没有了。这里的tea反编译的参数还抽风了,重新F5一下,变回正常了:
然后就是动调获取这后面三个参数的值了,这里面第四个参数就是tea的key,需要获取4个元素的字节数组,而且这个位置刚好是550这个函数有修改的地方。获取完所需要的参数之后就能写脚本解密下面的unk了:
1 | from ctypes import * |
但是此时解出来的是fakeflag。是不是有哪里漏了呢?回想了一下,当初的sub_401C50函数当中,最后return了一个sub_401510,参数传入了一个函数地址的地方还没有分析,来看看sub_401510这个函数有什么作用:
使用了onexit这个函数。这个函数是用来注册函数的,在函数退出的时候会调用onexit注册过的函数,先注册的函数后执行。那么这里就是注册了sub_401C10这一个函数,这个函数当中调用了一个sub_4016BC的函数,这个函数里面包含加密和check,那么看来这个函数就是漏掉的地方了。还有个花指令,处理方法和main函数相同,反编译之后是这样子的:
上一部分的是加密,下面就是校验了。这个加密是一个crc类的加密,用python伪代码翻译大致就是这样子的:
1 | for i in range(32): |
将一个DWORD类型的数值,逻辑左移,如果最高位为1,左移会将1移除,这时候就进行了左移之后异或0x84A6972F。如此往复直到循环结束。
那么这个算法的解密也比较简单,大体就是逻辑右移,通过观察可以发现异或的值0x84A6972F的LSB是1,而左移会在最低为补0,那么异或之后肯定就是1了。所以可以先判断LSB是否是1,如果是1的话就先异或,再右移,再或上0x80000000直到循环结束。
所以这个程序真正的逻辑其实是:魔改tea –> crc加密 –> check。根据分析所得可以写出exp:
1 | from ctypes import * |
这样就能解出,flag 为:d3ctf{ea3yjunk_c0d3_4nd_ea5y_re}
