详解C++的反调试技术与绕过手法
反调试技术的实现方式有很多,最简单的一种实现方式莫过于直接调用Windows系统提供给我们的API函数,这些API函数中有些专门用来检测调试器的,有些则是可被改造为用于探测调试器是否存在的工具,多数情况下,调用系统API函数实现反调试是不明智的,原因很简单,目标主机通常会安装主动防御系统,而作为主动防御产品默认会加载RootKit驱动挂钩这些敏感函数的使用,如果被非法调用则会提示错误信息,病毒作者通常会使用汇编自行实现这些类似于系统提供给我们的反调试函数,并不会使用系统的API,这样依附于API的主动防御的系统将会失效。
1.加载调试符号链接文件并放入d:/symbols目录下.
0:000> .sympath srv*d:\symbols*http://msdl.microsoft.com/download/symbols0:000> .reloadReloading current modules
【详解C++的反调试技术与绕过手法】2.位于fs:[0x30]的位置就是PEB结构的指针,接着我们分析如何得到的该指针,并通过通配符找到TEB结构的名称.
0:000> dt ntdll!*teb*ntdll!_TEBntdll!_GDI_TEB_BATCHntdll!_TEB_ACTIVE_FRAMEntdll!_TEB_ACTIVE_FRAME_CONTEXTntdll!_TEB_ACTIVE_FRAME_CONTEXT
3.接着可通过dt命令,查询下ntdll!_TEB结构,如下可看到0x30处ProcessEnvironmentBlock存放的正是PEB结构.
0:000> dt -rv ntdll!_TEBstruct _TEB, 66 elements, 0xfb8 bytes+0x000 NtTib: struct _NT_TIB, 8 elements, 0x1c bytes# NT_TIB结构+0x018 Self: Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes# NT_TIB结构+0x020 ClientId: struct _CLIENT_ID, 2 elements, 0x8 bytes# 保存进程与线程ID+0x02c ThreadLocalStoragePointer : Ptr32 to Void+0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 65 elements, 0x210 bytes# PEB结构
偏移地址0x18是_NT_TIB结构,也就是指向自身偏移0x0的位置.
0:000> r $teb$teb=7ffdf0000:000> dd $teb+0x187ffdf0187ffdf000 00000000 00001320 00000c107ffdf02800000000 00000000 7ffd9000 00000000
而!teb地址加0x30正是PEB的位置,可以使用如下命令验证.
0:000> dd $teb+0x307ffdf0307ffd9000 00000000 00000000 000000007ffdf04000000000 00000000 00000000 000000000:000> !tebTEB at 7ffdf000ExceptionList:0012fd0cStackBase:00130000StackLimit:0012e000SubSystemTib:00000000FiberData:00001e00ArbitraryUserPointer: 00000000Self:7ffdf000EnvironmentPointer:00000000ClientId:00001320 . 00000c10RpcHandle:00000000Tls Storage:00000000PEB Address:7ffd9000# 此处teb地址
上方的查询结果可得知偏移位置fs:[0x18]正是TEB的基址TEB:7ffdf000
0:000> dd fs:[0x18]003b:000000187ffdf000 00000000 000010f4 00000f6c003b:0000002800000000 00000000 7ffda000 000000000:000> dt _teb 0x7ffdf000ntdll!_TEB+0x000 NtTib: _NT_TIB+0x01c EnvironmentPointer : (null) +0x020 ClientId: _CLIENT_ID# 这里保存进程与线程信息0:000> dt _CLIENT_ID 0x7ffdf000# 查看进程详细结构ntdll!_CLIENT_ID+0x000 UniqueProcess: 0x0012fd0c Void# 获取进程PID+0x004 UniqueThread: 0x00130000 Void# 获取线程PID
上方TEB首地址我们知道是fs:[0x18],接着我们通过以下公式计算得出本进程的进程ID.
在Windows系统中如果想要获取到PID进程号,可以使用NtCurrentTeb()这个系统API来实现,但这里我们手动实现该API的获取过程.
获取进程PID:
#include "stdafx.h"#includeDWORD GetPid(){ DWORD dwPid=0; __asm {mov eax,fs:[0x18]// 获取PEB地址add eax,0x20// 加0x20得到进程PIDmov eax,[eax]mov dwPid,eax } return dwPid; }int main(){ printf("%d\n",GetPid()); return 0; }
获取线程PID:
#include "stdafx.h"#includeDWORD GetPid(){ DWORD dwPid=0; __asm {mov eax,fs:[0x18]// 获取PEB地址add eax,0x20// 加0x20得到进程PIDadd eax,0x04// 加0x04得到线程PIDmov eax,[eax]mov dwPid,eax } return dwPid; }int main(){printf("%d\n",GetPid()); return 0; }
通过标志反调试: 下方的调试标志BeingDebugged是Char类型,为1表示调试状态.为0表示没有调试.可以用于反调试.
0:000> dt _pebntdll!_PEB+0x000 InheritedAddressSpace : UChar+0x001 ReadImageFileExecOptions : UChar+0x002 BeingDebugged: UChar+0x003 SpareBool: UChar+0x004 Mutant: Ptr32 Void
#include "stdafx.h"#includeint main(){DWORD dwIsDebug = 0; __asm{mov eax, fs:[0x18]; // 获取TEBmov eax, [eax + 0x30]; // 获取PEBmovzx eax, [eax + 2]; // 获取调试标志mov dwIsDebug,eax}if (1 == dwIsDebug){printf("正在被调试"); }else{printf("没有被调试"); }return 0; }
通过API反调试:
#include #include #includeint main(){STARTUPINFO temp; temp.cb = sizeof(temp); GetStartupInfo(&temp); if (temp.dwFlags != 1){ExitProcess(0); }printf("程序没有被反调试"); return 0; }
反调试与绕过思路
BeingDebugged 属性反调试:
进程运行时,位置FS:[30h]指向PEB的基地址,为了实现反调试技术,恶意代码通过这个位置来检查BeingDebugged标志位是否为1,如果为1则说明进程被调试。
1.首先我们可以使用 dt _teb 命令解析一下TEB的结构,如下TEB结构的起始偏移为0x0,而0x30的位置指向的是 ProcessEnvironmentBlock 也就是指向了进程环境块。
0:000> dt _tebntdll!_TEB+0x000 NtTib: _NT_TIB+0x01c EnvironmentPointer : Ptr32 Void+0x020 ClientId: _CLIENT_ID+0x028 ActiveRpcHandle: Ptr32 Void+0x02c ThreadLocalStoragePointer : Ptr32 Void+0x030 ProcessEnvironmentBlock : Ptr32 _PEB// 此处是进程环境块+0x034 LastErrorValue: Uint4B+0x038 CountOfOwnedCriticalSections : Uint4B+0x03c CsrClientThread: Ptr32 Void+0x040 Win32ThreadInfo: Ptr32 Void+0x044 User32Reserved: [26] Uint4B+0x0ac UserReserved: [5] Uint4B+0x0c0 WOW32Reserved: Ptr32 Void
只需要在进程环境块的基础上 +0x2 就能定位到线程环境块TEB中 BeingDebugged 的标志,此处的标志位如果为1则说明程序正在被调试,为0则说明没有被调试。
0:000> dt _pebntdll!_PEB+0x000 InheritedAddressSpace : UChar+0x001 ReadImageFileExecOptions : UChar+0x002 BeingDebugged: UChar+0x003 BitField: UChar+0x003 ImageUsesLargePages : Pos 0, 1 Bit+0x003 IsProtectedProcess : Pos 1, 1 Bit
我们手动来验证一下,首先线程环境块地址是007f1000在此基础上加0x30即可得到进程环境快的基地址007ee000继续加0x2即可得到BeingDebugged的状态 ffff0401 需要 byte=1
0:000> r $teb$teb=007f10000:000> dd 007f1000 + 0x30007f1030007ee000 00000000 00000000 00000000007f104000000000 00000000 00000000 000000000:000> r $peb$peb=007ee0000:000> dd 007ee000 + 0x2007ee002ffff0401 0000ffff 0c400112 19f0775f007ee0120000001b 00000000 09e0001b 0000775f
梳理一下知识点我们可以写出一下反调试代码,本代码单独运行程序不会出问题,一旦被调试器附加则会提示正在被调试。
#include #includeint main(){ BYTE IsDebug = 0; __asm{mov eax, dword ptr fs:[0x30]mov bl, byte ptr [eax+ 0x2]mov IsDebug, bl } /* 另一种反调试实现方式 __asm{push dword ptr fs:[0x30]pop edxmov al, [edx + 2]mov IsDebug,al } */ if (IsDebug != 0)printf("本程序正在被调试. %d", IsDebug); elseprintf("程序没有被调试."); getchar(); return 0; }
如果恶意代码中使用该种技术阻碍我们正常调试,该如何绕过呢?如下我们只需要在命令行中执行dump fs:[30]+2来定位到BeingDebugged的位置,并将其数值改为0然后运行程序,会发现反调试已经被绕过了。
文章图片
ProcessHeap 属性反调试: 该属性是一个未公开的属性,它被设置为加载器为进程分配的第一个堆的位置,ProcessHeap位于PEB结构的0x18处,第一个堆头部有一个属性字段,这个属性叫做ForceFlags和Flags属性偏移为10,该属性为0说明程序没有被调试,非0则说明被调试。
0:000> dt !_pebntdll!_PEB+0x000 InheritedAddressSpace : UChar+0x001 ReadImageFileExecOptions : UChar+0x002 BeingDebugged: UChar+0x018 ProcessHeap: Ptr32 Void+0x01c FastPebLock: Ptr32 _RTL_CRITICAL_SECTION0:000> r $peb$peb=006e10000:000> dd 006e1000+18006e101800ca0000 775f09e0 00000000 000000000:000> dd 00ca0000 + 1000ca001000ca00a4 00ca00a4 00ca0000 00ca0000
要实现反反调试,只需要将 00ca0000 + 10 位置的值修改为0即可,执行dump ds:[fs:[30] + 0x18] + 0x10 定位到修改即可。
文章出处:https://www.cnblogs.com/lyshark
以上就是详解C++的反调试技术与绕过手法的详细内容,更多关于C++ 反调试技术与绕过手法的资料请关注脚本之家其它相关文章!
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量