-
-
[原创]独行孤客CrackMe-第五题的writeup(一路的艰辛)
-
发表于: 2017-6-11 01:02 3714
-
程序的特点:MFC程序,反调试,隐蔽的驱动文件,驱动反调试等。
1 MFC程序的特点
MFC程序使用了大量的深度封装的类,这些类除了结构复杂的特点,还有虚函数。虚函数支持了C++的多态性。在C++内存模型中,如果想访问一个对象的虚函数,需要做两件事,首先判断该对象的类型(找到虚函数表,对象前一个DWORD指向该虚表),然后根据索引访问虚表中的函数。这意味着大量使用虚函数和继承的MFC程序有很多张虚表,每有一个实现了虚函数的类,就可能有一张虚表。在MFC中,对虚函数很少有直接的访问,通常是通过虚表地址加上偏移来计算实际调用地址,很不直观。
幸运的是该MFC程序没有在虚函数上绕弯路,尽管如此,深入分析仍然很困难。
2 隐蔽的驱动文件
MFC仍然是一个win32程序,离不开消息事件的驱动,离不开窗口过程的处理。
使用IDA打开程序,没有受到任何阻挠,来到 0x40C14E处的 call _WinMain@16,有兴趣的可以阅读CWinApp类的源码,CWinApp类封装了win32程序的几个关键函数。
连续跟随调用地址,便会看到几个头疼的虚函数调用。
上面的eax便是虚函数表的地址,仅靠代码是无法知道这些对应什么函数调用。
当程序运行在非xp系统时,提示“请在xp平台中重新运行CrackMe",搜索关键字"xp",然后ctrl+x找到唯一引用,来到sub_4013E0。
快速浏览一下,调用了GetSystemMenu和AppendMenuA(创建系统菜单相关的调用),和SendMessageA(msg=80h,查找得知是让图标与窗口关联的消息),还有PostMessageA(msg=12h,让程序退出的消息)
在程序退出提示框之前有一个call sub_402210,判断是否是符合的xp系统。
如果是xp系统,然后 jnz short loc_4014E9
经过各种字符串的操作后, jnz short loc_401587
细细分析一下 sub_401AA0
突然意识到创建服务传入的BinaryPathName=...\vmxdrv.sys在哪里,原来在 sub_401F20处创建了一个驱动文件在
.text:00401566 call sub_401F20
下一行指令下断点,在该程序当前目录下会找到一个vmxdrv.sys驱动文件。
第一次分析驱动文件,经过查找相关资料,得知访问驱动文件,需要一下几个关键函数 CreateFile,DeviceIoControl,WriteFile,ReadFile。
CreateFile是通过"\\.\vmxdrv"作为文件名访问服务的,DeviceIoControl传入控制码,而WriteFile和ReadFile是从驱动设备写入和读取数据。
找到DeviceIoControl的两个调用 sub_4022A0 和sub_401D50。
简单分析下sub_4022A0,发现该调用只是简单发送一个0x222004H的IoControlCode,调用该过程的父调用恰好位于上述的打开vmxdrv服务之后,而且调用了5次该过程。
接下来分析sub_401D50,经一下分析,得知该过程的作用就是往驱动设备发送输入的字符串,然后读取输出,并将输出的16个字节变成长度为32的HexString。
该过程的caller分析如下
3分析驱动文件
驱动文件的入口是DriverEntry,尽量参考一些驱动资料。
通常在加载驱动结束前有重要的一个MajorFunction。
再来分析 sub_1071A 设备控制处理
然后分析输入处理 sub_1061C
最后分析读取处理
经过以上简单分析,驱动文件的算法如下:
1 接受IoControlCode=222004H后,设置dword_114D8=1,并使DebugPort清零
2 接受输入时,当dword_114D8=1时,先讲字符串与byte{1,1,2,3,4,5}对应相加,然后使用MD5加密,最后设置dword_114DC=1
3 处理读取时,判断dword_114DC=1,如果为1,则输出加密后的16个字节,否则固定输出某些字节
隐藏的dword_114E0,存放IoGetCurrentProcess()返回值,猜测和多线程有关。
而MFC程序的加密算法大致如下:
1 接受6位字符串输入
2 向驱动设备输入该6位字符串
3 读取加密处理后的16个字节,然后转成HexString
4 再次HexString进行MD5加密处理,再次转成HexString
5 讲HexString与"888aeda4ab"比较,似乎有些不对劲,32长度的HexString如何与10长度的比较,肯定还有一些不清楚的地方。
4反调试
1 IsDebuggerPresent 这是一个很常见的反调试。
如果eax=1,则跳到loc_4018C2。
由于这个函数不需要任何参数,所以无需调整堆栈,分别使用5个nop指令和一个xor eax, xor eax替换掉call 和 test指令
2 头疼的DeviceIoControl
当在调试该程序时,运行到222004H设备控制码时便会抛出内核访问异常,程序无法正常调试。
本来想对驱动文件patch,不让DebugPort置零,然后创建服务。但是这会导致驱动校验无法通过。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!