2020.8.14----学习记录

学习记录

好些天没更新学习记录了,今天才想起这个事情,一看时间距离上一次记录都过了半个月,看来时间真的过得很快2333
这段时间因为各种原因或者借口把学习记录了,orz

这里记录一下最近看的反调试

关于peb中的BeingDebugged

1. IsDebuggerPresent

该函数是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标志。尽管如此,我们也可通过这一标志继续往下研究,从而弄懂该插件的部分原理。

2. NtGlobalFlag

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的作用远不止如此

3. Heap Magic

当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;
	}
}

Native

1. CheckRemoteDebuggerPresent

除了IsDebuggerPresent,MSDN中还提供了另一个用于检测调试器的函数,其声明如下

BOOL CheckRemoteDebuggerPresent(
	HANDLE hProcess,
	PBOOL pbDebuggerPresent
);

第一个参数是进程句柄,第二参数用于存放结果。返回值表示函数是否执行成功.

当然,这个函数的关键点在于其原理与PEB中的IsDebugged段无关

2. ProcessDebugPort

我们可以在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,其意义可以参考下图

2020.8.14----学习记录_第1张图片

数字7代表着ProcessDebugPort,这个值是系统用来与调试器通信的端口句柄。PEB中的IsDebugged字段被清除不会影响调试,但如果调试端口被设置为0,系统就不会向调试器发送调试事件通知,那么调试器也就不会正常工作了。

尽管ProcessDebug不仅支持Query操作,也支持Set操作,但只有当Debugport本身就为0时该值才能被置0。

3. ZwSetInformationThread

同样,这也是一个Native APi,可以设置一个与线程相关的信息,其原型如下

NTSYSAPI NTSTATUS ZwSetInformationThread(
  HANDLE          ThreadHandle,
  THREADINFOCLASS ThreadInformationClass,
  PVOID           ThreadInformation,
  ULONG           ThreadInformationLength
);

第二个参数 ThreadInfomationClass 列表如下

2020.8.14----学习记录_第2张图片

其中,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,那么该函数就会提前结束,调试器无法知道之后发生的事情。

后记

想了想,以后还是不要轻易,比较写一篇这个总结也不算很费时间嘛

你可能感兴趣的:(日常总结)