对一个debugme的简单分析

第一次写分析文章,如果有写得不对的地方还请各位大佬多多包涵。
前情提要
前段时间在CM区玩CM的时候发现了这个debugme,想着说不定能发现一些有用的反调试手法,遂对其进行分析。程序在32位和64位环境下启用的anti有点不一样,这篇文章仅讨论64位环境下的anti。
下载链接:
https://www.52pojie.cn/forum.php?mod=viewthread&tid=990071&page=1#pid26875252
准备
OD(带SOD FKVMP和VMP分析插件)
分析过程
程序加了upx壳,先脱壳再继续分析,启动函数有VM代码,我选择尽量不要硬碰硬,从其他地方下手。

程序要从x86切换到x64的运行环境(切换方法可以去百度,这里不讲),这部分代码不可以VM,用OD搜索常量0x33,查看附近有retf的,可以找出所有x86切换到x64的函数。分析出来的结果如下:
00405667 初始化环境
00401AFC 取x64函数地址
00401E23 取得x64地址的数据
00401F1B 执行函数
0040217D 执行函数,该程序只用来调用NtQueryInformationProcess 0x1e
00401ED3 写数据,该程序用来写shellcode
004895DE 存放shellcode
[Asm] 纯文本查看 复制代码

shellcode:
mov r10, rcx
mov eax, 0x??
syscall
ret

程序会取Nt系列函数的服务号,拼接shellcode以后再调用00401F1B执行代码。
我们对00401AFC、00401F1B和0040217D下断,可以得到程序执行的x64函数,注意在执行反调试函数的时候要顺便过掉。程序还会开启新线程进行GetTickCount+Sleep检测,可以把线程先暂停,或者retn CreateThread,免得影响分析。
NtProtectVirtualMemory(修改shellcode地址处的内存属性)
GetTickCount+Sleep Thread
NtSetInformationThread 0x11
NtClose DEADC0DE
NtQueryInformationProcess 0x7
NtQueryInformationProcess 0x1e
NtTerminateProcess(被检测到调试)
过掉NtQueryInformationProcess 0x1e的检测,程序却还是退出,说明在之后有其他反调试手段,比如PEB,或者X86API 等等,慢慢分析排查也行,但是作者给了一个信息,检测PEB的标志。

猜测读取PEB的代码在VM代码里面,我们可以借助FKVMP或者Zeus查找RmFs32的地址,先在RmFs32处下断。

重载程序,过掉前面的反调试,执行完NtQueryInformationProcess 0x1e后,OD会在RmFs32处断下,此时,在004036AA(vmdispatch,可以随便找个handle跟出来)下硬件断点(普通断点貌似会让程序崩溃),并将右下角堆栈区锁定为EBP,F9运行。

在堆栈区可以看到PEB的地址

我们继续F9,并时刻注意堆栈的值。

多次运行后堆栈出现了PEB+0x10(PPROCESS_PARAMETERS *ProcessParameters),程序应该是读取这里了。在hex区转到PEB+0x10处的值。

选中这一大片区域,下内存访问断点,去掉RmFs32和vmdispatch这两处的断点,运行,程序停下后,将读取地址处修改成正常运行的程序的值,不断重复直到程序停在其他断点。修改结果如下:

这几个地方分别是:
_RTL_USER_PROCESS_PARAMETERS->StandardOutput
_RTL_USER_PROCESS_PARAMETERS->WindowFlags
_RTL_USER_PROCESS_PARAMETERS->ShowWindowFlags
有兴趣的同学可以自己去找找资料。

修改完PEB以后,程序会再执行一次NtQueryInformationProcess 0x1e,我们可以在vmretn处下断继续跟踪分析。
程序之后调用GetStartupInfoW,检测的标志跟PEB一样,修改过PEB就不用改了,然后检测x64环境的NtClose和RtlGetNativeSystemInformation的首字节是否为FF,是则退出。
然后跟到00402DC3 (取CRC32),特征为

出了CRC以后,程序就退出了。说明程序可能有CRC校验,应该是校验有没有断点和Patch,校验CRC的代码在VM虚拟机,用VMP分析插件跟踪。

vEsp的值跟CRC的返回值一样

可以对004B3504下硬件写入断点,重载程序分析,这里存放第一次取到的CRC,写入的时间在NtProtectVirtualMemory之后。
之后虚拟机会弹出flag,并用来计算跳转

这样流程就很清楚了,第二次取的CRC后会跟004B3504处比较,我们只要确保两次CRC是相同的就可以通过验证了。
过了CRC验证以后会再从NtSetInformationThread开始检测一遍。同时另一条线程也会一直检测GetTickCount。点击按钮应该也是执行一遍这个流程(这部分没有分析,只是猜测)。
那要怎么从OD运行这个程序?
在00402045下断运行,程序停下后取消断点,然后分别在00401F1B和0040217D处patch如下代码。
00401F1B:B800000000C21C0090
0040217D:B8530300C0C234009090
直接运行程序即可。

点击按钮

反调试手法总结
该程序在x64环境下使用了这些手法来反调试:
检测GetTickCount时间差以及检测GetTickCount在Sleep过后是否还是一样的时间差
NtSetInformationThread 0x11
NtClose DEADC0DE
NtQueryInformationProcess 0x7
NtQueryInformationProcess 0x1e
StandardOutput==0x10001&&WindowFlags==0x401&&ShowWindowFlags==0x1
GetStartupInfoW
NtClose和RtlGetNativeSystemInformation是否被HOOK
CRC32
除了StandardOutput WindowFlags ShowWindowFlags这三个标志位,其他的都是很常见的反调试API,但是在32位程序调用的64位API,反反调试的难度就增加了,逆向这个还是有点收获的。
如何反反调试
该程序反调试调用的Nt函数都是x64的,而且会自己构造shellcode调用,普通的HOOK行不通,遇到这种最好还是用TitanHide这种驱动层的防护(我用虚拟机测试没成功)。
GetTickCount和Sleep这两个组合在一块好像没什么好办法anti,我想到的办法是挂起检测线程再调试,如果大佬们有其他好办法的话请分享一下吧。
最简单的应该就是StandardOutput WindowFlags ShowWindowFlags这三个标志位了,自己写个简单的OD插件就能过掉了。如果不想占用插件位的话,修改下SOD也能达到这个效果。

总结
这个程序反调试手段多,加上主要代码被VM,所以我一开始分析这个东西的时候还是浪费了不少时间在VM代码里了,也就是PEB和CRC的部分,然而分析完这两个部分再看,其实是自己在分析的时候遗漏了不少细节,傻乎乎地硬吃了大部分没用的VM。功力不够,还是要好好学习才行。

THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发
  • 管埋员

    昵称

  • 取消
    昵称