詳解C++的反調試技術與繞過手法

反調試技術的實現方式有很多,最簡單的一種實現方式莫過於直接調用Windows系統提供給我們的API函數,這些API函數中有些專門用來檢測調試器的,有些則是可被改造為用於探測調試器是否存在的工具,多數情況下,調用系統API函數實現反調試是不明智的,原因很簡單,目標主機通常會安裝主動防禦系統,而作為主動防禦產品默認會加載RootKit驅動掛鉤這些敏感函數的使用,如果被非法調用則會提示錯誤信息,病毒作者通常會使用匯編自行實現這些類似於系統提供給我們的反調試函數,並不會使用系統的API,這樣依附於API的主動防禦的系統將會失效。

1.加載調試符號鏈接文件並放入d:/symbols目錄下.

0:000> .sympath srv*d:\symbols*http://msdl.microsoft.com/download/symbols
0:000> .reload
Reloading current modules

2.位於fs:[0x30]的位置就是PEB結構的指針,接著我們分析如何得到的該指針,並通過通配符找到TEB結構的名稱.

0:000> dt ntdll!*teb*
          ntdll!_TEB
          ntdll!_GDI_TEB_BATCH
          ntdll!_TEB_ACTIVE_FRAME
          ntdll!_TEB_ACTIVE_FRAME_CONTEXT
          ntdll!_TEB_ACTIVE_FRAME_CONTEXT

3.接著可通過dt命令,查詢下ntdll!_TEB結構,如下可看到0x30處ProcessEnvironmentBlock存放的正是PEB結構.

0:000> dt -rv ntdll!_TEB
struct _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=7ffdf000

0:000> dd $teb+0x18
7ffdf018  7ffdf000 00000000 00001320 00000c10
7ffdf028  00000000 00000000 7ffd9000 00000000

而!teb地址加0x30正是PEB的位置,可以使用如下命令驗證.

0:000> dd $teb+0x30
7ffdf030  7ffd9000 00000000 00000000 00000000
7ffdf040  00000000 00000000 00000000 00000000

0:000> !teb
TEB at 7ffdf000
    ExceptionList:        0012fd0c
    StackBase:            00130000
    StackLimit:           0012e000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7ffdf000
    EnvironmentPointer:   00000000
    ClientId:             00001320 . 00000c10
    RpcHandle:            00000000
    Tls Storage:          00000000
    PEB Address:          7ffd9000               # 此處teb地址

上方的查詢結果可得知偏移位置fs:[0x18]正是TEB的基址TEB:7ffdf000

0:000> dd fs:[0x18]
003b:00000018  7ffdf000 00000000 000010f4 00000f6c
003b:00000028  00000000 00000000 7ffda000 00000000

0:000> dt _teb 0x7ffdf000
ntdll!_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"
#include <Windows.h>

DWORD GetPid(){
	DWORD dwPid=0;
	__asm
	{
		mov eax,fs:[0x18]    // 獲取PEB地址
		add eax,0x20         // 加0x20得到進程PID
		mov eax,[eax]
		mov dwPid,eax
	}
	return dwPid;
}

int main()
{
	printf("%d\n",GetPid());
    return 0;
}

獲取線程PID:

#include "stdafx.h"
#include <Windows.h>

DWORD GetPid(){
	DWORD dwPid=0;
	__asm
	{
		mov eax,fs:[0x18]    // 獲取PEB地址
		add eax,0x20         // 加0x20得到進程PID
		add eax,0x04         // 加0x04得到線程PID
		mov eax,[eax]
		mov dwPid,eax
	}
	return dwPid;
}

int main()
{
    printf("%d\n",GetPid());
    return 0;
}

通過標志反調試:

下方的調試標志BeingDebugged是Char類型,為1表示調試狀態.為0表示沒有調試.可以用於反調試.

0:000> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void
#include "stdafx.h"
#include <Windows.h>

int main()
{
    DWORD dwIsDebug = 0;
    __asm
    {
        mov eax, fs:[0x18];      // 獲取TEB  
        mov eax, [eax + 0x30];   // 獲取PEB
        movzx eax, [eax + 2];    // 獲取調試標志
        mov dwIsDebug,eax
    }

    if (1 == dwIsDebug)
    {
        printf("正在被調試");
    }
    else
    {
        printf("沒有被調試");
    }
    return 0;
}

通過API反調試:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

int 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 _teb
ntdll!_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 _peb
ntdll!_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=007f1000

0:000> dd 007f1000 + 0x30
007f1030  007ee000 00000000 00000000 00000000
007f1040  00000000 00000000 00000000 00000000

0:000> r $peb
$peb=007ee000

0:000> dd 007ee000 + 0x2
007ee002  ffff0401 0000ffff 0c400112 19f0775f
007ee012  0000001b 00000000 09e0001b 0000775f

梳理一下知識點我們可以寫出一下反調試代碼,本代碼單獨運行程序不會出問題,一旦被調試器附加則會提示正在被調試。

#include <stdio.h>
#include <windows.h>

int 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 edx
		mov al, [edx + 2]
		mov IsDebug,al
	}
	*/

	if (IsDebug != 0)
		printf("本程序正在被調試. %d", IsDebug);
	else
		printf("程序沒有被調試.");
	getchar();
	return 0;
}

如果惡意代碼中使用該種技術阻礙我們正常調試,該如何繞過呢?如下我們隻需要在命令行中執行dump fs:[30]+2來定位到BeingDebugged的位置,並將其數值改為0然後運行程序,會發現反調試已經被繞過瞭。

ProcessHeap 屬性反調試:

該屬性是一個未公開的屬性,它被設置為加載器為進程分配的第一個堆的位置,ProcessHeap位於PEB結構的0x18處,第一個堆頭部有一個屬性字段,這個屬性叫做ForceFlags和Flags屬性偏移為10,該屬性為0說明程序沒有被調試,非0則說明被調試。

0:000> dt !_peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x018 ProcessHeap      : Ptr32 Void
   +0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION

0:000> r $peb
$peb=006e1000

0:000> dd 006e1000+18
006e1018  00ca0000 775f09e0 00000000 00000000

0:000> dd 00ca0000 + 10
00ca0010  00ca00a4 00ca00a4 00ca0000 00ca0000

要實現反反調試,隻需要將 00ca0000 + 10 位置的值修改為0即可,執行dump ds:[fs:[30] + 0x18] + 0x10 定位到修改即可。

文章出處:https://www.cnblogs.com/lyshark

以上就是詳解C++的反調試技術與繞過手法的詳細內容,更多關於C++ 反調試技術與繞過手法的資料請關註WalkonNet其它相關文章!

推薦閱讀: