好些天没更新学习记录了,今天才想起这个事情,一看时间距离上一次记录都过了半个月,看来时间真的过得很快2333
这段时间因为各种原因或者借口把学习记录了,orz
这里记录一下最近看的反调试
该函数是Win32 API所提供的一个检测程序是否位于调试状态的函数,也是最为常见的一个反调试函数,其实现代码如下
BOOL myIsDebuggerPresent(VOID)
{
DWORD flag;
_asm
{
mov eax, fs:[30h]
mov al, byte ptr [eax+2]
mov flag, eax
}
return flag;
}
也就是说,IsDebuggerPresent函数的原理在于通过TEB读取了PEB中偏移为0x2的一个字节处的值,该字节为PEB中的BeingDebugged标志。
众所周知,OllyDbg可以通过插件做到无视这个函数,也就是通过这一原理清除了BeingDebugged标志。尽管如此,我们也可通过这一标志继续往下研究,从而弄懂该插件的部分原理。
NtGlobalFlag是PEB中偏移为0x68的一个标志。当IsDebugged标志被为TRUE时,NtGlobalFLag也会得到一个0x70的值。所以,从某种意义上来说,NtGlobalFlag是IsDebugged的孪生兄弟。
NtGlobalFLag也可用于检测调试器,其代码实现为
BOOL mycheckdebug()
{
DWORD flag;
__asm
{
mov eax,fs:[30h]
movzx eax,[eax+68h]
and eax,0x70
mov flag,eax
}
return flag; //返回值为70h或0
}
但实际上,NtGlobalFLag的作用远不止如此
当BeingDebugged为TRUE时,NtGlobalFlag设置了FLG_HEAP_VALIDATE_PARAMETERS,于是之后RtlCreateHeap函数就会调用RtlDebugCreateHeap去创建调试堆。值得一提的是,调试堆中进行了某种填充,编写如下代码测试:
LPVOID GetHeap(VOID)
{
return HeapAlloc(GetProcessHeap(), NULL, 0x100);
}
使用ida调试时也确实看到堆中杯填充成了一些数字
所以可以利用这一特性编写一个反调试函数,其功能为在堆中搜索这些填充的特殊标记
LPVOID GetHeap(SIZE_T nSIZE)
{
return HeapAlloc(GetProcessHeap(), NULL, nSIZE);
}
BOOL IsDebugHeap(VOID)
{
LPVOID HeapPtr;
PDWORD ScanPtr;
ULONG nMagic = 0;
HeapPtr = GetHeap(0x100);
ScanPtr = (PDWORD)HeapPtr;
__try {
for (;;)
switch (*ScanPtr++) {
case 0xABABABAB:
case 0xBAADF00D:
case 0xFEEEFEEE:
nMagic++;
break;
}
}
__except (GetExceptionCode()== EXCEPTION_ACCESS_VIOLATION) {
return (nMagic >= 10) ? TRUE : FALSE;
}
}
除了IsDebuggerPresent,MSDN中还提供了另一个用于检测调试器的函数,其声明如下
BOOL CheckRemoteDebuggerPresent(
HANDLE hProcess,
PBOOL pbDebuggerPresent
);
第一个参数是进程句柄,第二参数用于存放结果。返回值表示函数是否执行成功.
当然,这个函数的关键点在于其原理与PEB中的IsDebugged段无关
我们可以在ida中看到CheckRemoteDebuggerPresent的汇编代码经过反编译之后的伪代码
signed int __stdcall kernelbase_CheckRemoteDebuggerPresent(int a1, _DWORD *a2)
{
int v2; // eax
int v4; // [esp+4h] [ebp-4h]
if ( a1 && a2 )
{
v2 = (ntdll_NtQueryInformationProcess)(a1, 7, &v4, 4, 0);
if ( v2 >= 0 )
{
*a2 = v4 != 0;
return 1;
}
sub_767D5FA0(v2);
}
else
{
(ntdll_RtlRestoreLastWin32Error)(87);
}
return 0;
}
在这个函数中,继续调用了一个函数—NtQueryInformationProcess。这是一个Native API,其函数原型如下
__kernel_entry NTSTATUS NtQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);
这个函数会根据不同的 ProcessInformationClass 查询进程对象的信息。我们可以看到在CheckRemoteDebuggerPresent中调用这个函数时,对应的信息值为7,其意义可以参考下图
数字7代表着ProcessDebugPort,这个值是系统用来与调试器通信的端口句柄。PEB中的IsDebugged字段被清除不会影响调试,但如果调试端口被设置为0,系统就不会向调试器发送调试事件通知,那么调试器也就不会正常工作了。
尽管ProcessDebug不仅支持Query操作,也支持Set操作,但只有当Debugport本身就为0时该值才能被置0。
同样,这也是一个Native APi,可以设置一个与线程相关的信息,其原型如下
NTSYSAPI NTSTATUS ZwSetInformationThread(
HANDLE ThreadHandle,
THREADINFOCLASS ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
第二个参数 ThreadInfomationClass 列表如下
其中,17代表的ThreadHideFromDebugger,可以禁止调试器产生调试事件,实例代码如下
#include
#include
#include
typedef DWORD (WINAPI *ZW_SET_INFORMATION_THREAD)(HANDLE, DWORD, PVOID, ULONG);
#define ThreadHideFromDebugger 17
VOID DisableDebugEvent(VOID)
{
HINSTANCE hModule;
ZW_SET_INFORMATION_THREAD ZwSetInformationThread;
hModule = GetModuleHandleA("Ntdll");
ZwSetInformationThread =
(ZW_SET_INFORMATION_THREAD)GetProcAddress(hModule, "ZwSetInformationThread");
ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, NULL);
}
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
DisableDebugEvent();
return 0 ;
}
但实际上这个函数并不是ProcessDebugport设置为0,不过有异曲同工之妙。
当进程与一个调试端口关联起来时,就会通知调试器发生了LOAD_DLL_DEBUG_EVENT事件,然后调用DbgkMapViewOfSection函数,如果HideFromDebugger为TRUE,那么该函数就会提前结束,调试器无法知道之后发生的事情。
想了想,以后还是不要轻易,比较写一篇这个总结也不算很费时间嘛