在本专栏前两篇文章中,带领大家讲解了逆向加密算法,AES,TEA,RC4,Base64加密算法,并带领大家识别各种密码算法特征,这一篇文章来带领大家学习在逆向过程中的动态调试:IDA动态调试,反调试技术。
IDA作为一款强大的逆向集成工具,对于动态调试也有比较好的支持。利用IDA自带的动态调试功能可以完成大部分的动态调试任务。
动态调试是一种观察程序运行状态的一种手段。
逆向工程中动态调试的目的主要有:
IDA调试器支持的特性
动态调试是一种观察程序运行状态的一种手段。
逆向工程中的动态调试的目的主要有:验证静态分析结果和观察程序运行时的数据。
而为了防止我们对程序进行调试,程序开发者通常会设置反调试来检测自己开发的程序是否正在被调试。
但是不管是什么反调试,都有绕过方法,反调试与反反调试一直处于对抗状态,安全研究人员在其中各施手段。
下面我们将江街道Windows和Linux下常见的反调试。
很多常见的反调试都是用这个来判断的,比如
IsDebugPresent()
等,那么PEB是什么呢?
Process Environment Block(进程环境块),存放进程信息的一个结构体
eg:
InheritedAddressSpace:UChar
ReadImageFileExecOptions:UChar
BeingDebugged:UChar调试标志
SpareBool:UChar
Mutant:Ptr32 Void
ImageBaseAddress:Ptr32 Void 映像基址
Ldr:Ptr_32PEB_LDR_DATA进程加载模块链表
TEB(Thread Environment Block,线程环境块)结构体也是必须要知道的,该结构体包含进程中运行线程的各种信息,进程中的每一个线程都对应着一个TEB结构体。
NtTib:——NT_TIB
EnvironmentPointer:PTR32 Void
Clientld:_CLENT_ID:当前进程ID
ActiveRpHandle:Ptr32 Void
ThreadLocalStoragePointer:Ptr32 Void
ProcessEnvironmentBlock:Ptr_32PEB 当前进程的PEB指针
LastErrorValue:Unit4B
MOV EAX,DWORD PTR FS:[0x30h]; //FS:[0x30h]就是AddressOfPEB
MOV EAX,DWORD PTR FS:[0x30h]; //FS:[0x18为AddressOfTEB
MOV EAX,DWORD PTR DS:[EAX+0x30]; //DS:[EAX+0x30h]为AddressOfPEB
很简单,如果进程处于被调试状态,那么这个成员的值就会被设置为1,非调试状态下运行时,其值被设置为0.
IsDebuggerPresent()
这个函数就是通过检测这个值来判断程序是否正在被调试
mov eax,dword ptr fs:[18]; //获取TEB地址
mov eax,dword ptr ds:[eax+30]; //通过TEB获取PEB的地址
mov eax byte ptr da:[eax+2]; //获取PEB偏移为2的结构体元素BeginDebugged,并返回这个值
retn
破解方法:在获取到PEB地址之后,使用调试器跳转到PEB地址将偏移为2的BeginDebugged成员设置为0即可。
这是是利用进程堆(HEAP)进行反调试
PEB.ProcessHeap成员是指向HEAP结构体的指针
GetProcZessHeap()
的实现原理:
mov eax,dword ptr fs:[0x18]; //TEB的起始地址
mov eax,dword ptr ds:[eax+30]; //PEB的地址
mov eax,dword ptr ds:[eax+18]; //PEB.Processheap的地址
获取到HEAP结构体之后,比较两个重要的成员就是Flags和ForceFlags,进程正常运行时,Heap.Flags成员(+0xC)的值为0x2,Heap.ForceFlags成员(+0x10)的值为0.进程处于被调试状态时,这些值也会随之改变。
破解方法:利用调试器获取到Heap结构体之后将Heap.Flags与Heap.ForceFlags的值重新设置为2和0即可。
调试进程的时候,PEB.NtGlobalFlag成员(+0x68)的值会被设置为0x70,检测该成员就可以判断进程是否处于被调试状态。
0x70时一下Flags值按位或的结果:
FLG_HEAP_ENABLE_TAIL_CHECK(0x10)
FLG_HEAP_ENABLE_FREE_CHEVK(0x20)
FLG_HEAP_VALIDATE_PARAMETERS(0x40)
破解方法:
重新将PEB.NtGlobalFlag的值设置为0即可
这里有个技巧,如果是将运行中的进程附加到调试器时,NtGlobalFlag的值不变。
NtQueryInformationProcess()
函数MSDN技术文档解释NtQueryInformationProcess函数
函数功能:检索有关指定进程的信息。
函数原型:
__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);
参数解释:
ProcessHandle:要为其检索信息的进程句柄。
ProcessInformationClass:要检索的进程信息的类型。
ProcessInformation:指向调用应用程序提供的缓冲区的指针,函数将请求的信息写入其中。 写入的信息的大小因 ProcessInformationClass 参数的数据类型而异。
ProcessInformationLength:指向的缓冲区的大小(以字节为单位)。
ReturnLength:指向变量的指针,其中函数返回所请求信息的大小。 如果函数成功,则这是 由 ProcessInformation 参数指向的缓冲区中写入的信息的大小 (如果缓冲区太小,则为成功接收信息) 所需的最小缓冲区大小。
第二个PEOCESSINFOCLASS类型的参数反调试选用的结构体成员:
ProcessDebugPort,//0x7
ProcessDebugObjectHandle,//0x1E
PrecessDebugFlags, //0x1F
ProcessDebugPort(0x7)
:
当一个进程处于调试状态时,系统就会为它分配一个调试窗口(DebugPort),当PEOCESSINFOCLASS的ProcessDebugPort的值为7,调用NtQueryInformationProcess就可以获取调试端口,如果没有调试则返回0.
CheckRemoteDebuggerPresent()
函数:
MSDN官方文档解释CheckRemoteDebuggerPresent函数
用来检测进程是否处于被调试状态,CheckRemoteDebuggerPresent()函数不仅可以用来检测当前进程,还可以用来检测其他进程是否处于被调试状态。查看该API,实际上调用了NtQueryInformationProcess
ProcessDebuggerObjectHandel(0x1E)
调试进程时会生成调试对象(Debug Object)。函数的第二个参数值为ProcessDebug-ObjectHandle(0x1E)时,调用函数后通过第三个擦拭农户就能获取调试对象句柄。进程处于调试状态时,调试对象句柄的值就存在,若进程处于非调试状态,则调试对象句柄值为NULL。
ProcessDebugFlags(0x1F)
检测DebugFlags(调试标志)的值来判断进程是否处于被调试状态,函数的第二个参数设置为这个是,调用函数后通过第三个参数即可获取调试标志的值,为0则进程处于被调试状态,为1,则进程处于非调试状态。
破解方法:
对该哈桑农户在第二个参数为特定的值的时候返回的第三个参数的值进行操作,特定的参数值就是前面提起的那个三个,若只是少调用几次API,那么我们直接手动操作输出值(先找到调用NtQueryInformationProcess之前压入的第三个参数的地址,跳转到对应地址,然后调用完之后将其值修改为非调试的值,如果是多次的话,我们就可以使用API钩取这个函数,然后在钩取函数中根据第二个参数的值,来修改第三个参数在返回的值。
NtQuerySystemInformation()
系统函数,用来获取当前运行的多种OS信息
这里给出MSDN官方文档的解释,但是该函数好像已经过时。MSDN官方文档
NtQueryObject()函数
这里给出MSDN官方文档地址,大家可以过去看看:官方文档
类似的给第二个参数赋予某个值,调用API后,相关信息的结构体指针就会被返回到第三个参数中。
破解方法:
在调用ntdll.ZwQueryObject()API的时候,查看栈可以看到压入的第二个参数的值,我们将这个值改为0之后,就无法探测到调试了。
也可以直接钩取ntdll.ZwQueryObject这个API,修改输入值或修改结果值也能不被探测到。