12

[原创][翻译]反调试:调试标志寄存器

 3 years ago
source link: https://bbs.pediy.com/thread-268010.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
[原创][翻译]反调试:调试标志寄存器-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

备注
原文地址:https://anti-debug.checkpoint.com/techniques/debug-flags.html
原文标题:Anti-Debug: Debug Flags
更新日期:2021年6月8日
此文后期:根据自身所学进行内容扩充
因自身技术有限,只能尽自身所能翻译国外技术文章,供大家学习,若有不当或可完善的地方,希望可以指出,用于共同完善这篇文章。

目录

  • 调试标志寄存器

  • 1. 使用Win32 API

  • 1.1. IsDebuggerPresent()

  • 1.2. CheckRemoteDebuggerPresent()

  • 1.3. NtQueryInformationProcess()

  • 1.3.1. ProcessDebugPort进程调试端口

  • 1.3.2. ProcessDebugFlags进程调试标志寄存器

  • 1.3.3. ProcessDebugObjectHandle进程调试对象句柄

  • 1.4. RtlQueryProcessHeapInformation()

  • 1.5. RtlQueryProcessDebugInformation()

  • 1.6. NtQuerySystemInformation()

  • 2. 手工检查

  • 2.1. PEB!BeingDebugged标志寄存器

  • 2.2. NtGlobalFlag

  • 2.3. 堆标志寄存器

  • 2.4. 堆保护

调试标志寄存器
系统表中的特殊标志寄存器,即停留在进程内存中的、由操作系统设置的标志寄存器,可以用来指示进程正在被调试。这些标志寄存器的状态可以通过使用特定的API函数或检查内存中的系统表来验证。
这些技术是恶意软件最常使用的。
1. 使用Win32 API
以下技术使用现有的API函数(WinAPI或NativeAPI),这些函数检查进程内存中的系统结构,以寻找表明进程现在正在被调试的特定标志寄存器。

1.1. IsDebuggerPresent()
函数kernel32!IsDebuggerPresent()确定当前进程是否被用户模式的调试器如OllyDbg或x64dbg调试。一般来说,该函数只检查进程环境块(PEB)的 BeingDebugged 标志寄存器。
如果正在调试进程,可以使用以下代码终止进程:
汇编代码:

call IsDebuggerPresent    
test al, al
jne  being_debugged
...
being_debugged:
push 1
call ExitProcess

C/C++ 代码:

if (IsDebuggerPresent())
ExitProcess(-1);

1.2. CheckRemoteDebuggerPresent()
函数kernel32!CheckRemoteDebuggerPresent()检查一个调试器(在同一台机器的不同进程中)是否连接到当前进程。
C/C++ 代码:

BOOL bDebuggerPresent;
if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) &&
TRUE == bDebuggerPresent)
ExitProcess(-1);

x86 汇编:

lea eax, bDebuggerPresent]
push eax
push -1  ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

x86-64 汇编:

lea rdx, [bDebuggerPresent]
mov rcx, -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

1.3. NtQueryInformationProcess()
函数ntdll!NtQueryInformationProcess()可以从一个进程中检索不同种类的信息。它接受一个ProcessInformationClass参数,该参数指定了你想得到的信息,并定义了ProcessInformation参数的输出类型。

1.3.1. ProcessDebugPort进程调试端口
可以使用ntdll!NtQueryInformationProcess()来检索进程的调试器端口号。有一个记录在案的类ProcessDebugPort,如果进程正在被调试,它会检索到一个等于0xFFFFFFFF(十进制-1)的DWORD值。
C/C++ 代码:

typedef NTSTATUS (NTAPI *TNtQueryInformationProcess)(
IN HANDLE           ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID           ProcessInformation,
IN ULONG            ProcessInformationLength,
OUT PULONG          ReturnLength
);
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");
if (pfnNtQueryInformationProcess)
{
DWORD dwProcessDebugPort, dwReturned;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugPort,
&dwProcessDebugPort,
sizeof(DWORD),
&dwReturned);
if (NT_SUCCESS(status) && (-1 == dwProcessDebugPort))
ExitProcess(-1);
}
}

x86 汇编:

lea eax, [dwReturned]
push eax ; ReturnLength
push 4   ; ProcessInformationLength
lea ecx, [dwProcessDebugPort]
push ecx ; ProcessInformation
push 7   ; ProcessInformationClass
push -1  ; ProcessHandle
call NtQueryInformationProcess
inc dword ptr [dwProcessDebugPort]
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

x86-64 汇编:

lea rcx, [dwReturned]
push rcx    ; ReturnLength
mov r9d, 4  ; ProcessInformationLength
lea r8, [dwProcessDebugPort] 
; ProcessInformation
mov edx, 7  ; ProcessInformationClass
mov rcx, -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugPort], -1
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

1.3.2. ProcessDebugFlags进程调试标志寄存器
一个叫做EPROCESS的内核结构代表一个进程对象,它包含NoDebugInherit字段。这个字段的反值可以通过一个无文件记录的类ProcessDebugFlags(0x1f)来检索。因此,如果返回值为0,则说明有调试器存在。
C/C++ 代码:

typedef NTSTATUS(NTAPI *TNtQueryInformationProcess)(
IN HANDLE           ProcessHandle,
IN DWORD            ProcessInformationClass,
OUT PVOID           ProcessInformation,
IN ULONG            ProcessInformationLength,
OUT PULONG          ReturnLength
);
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");
if (pfnNtQueryInformationProcess)
{
DWORD dwProcessDebugFlags, dwReturned;
const DWORD ProcessDebugFlags = 0x1f;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugFlags,
&dwProcessDebugFlags,
sizeof(DWORD),
&dwReturned);
if (NT_SUCCESS(status) && (0 == dwProcessDebugFlags))
ExitProcess(-1);
}
}

x86 汇编:

lea eax, [dwReturned]
push eax ; ReturnLength
push 4   ; ProcessInformationLength
lea ecx, [dwProcessDebugPort]
push ecx ; ProcessInformation
push 1Fh ; ProcessInformationClass
push -1  ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugPort], 0
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

x86-64 汇编:

lea rcx, [dwReturned]
push rcx     ; ReturnLength
mov r9d, 4   ; ProcessInformationLength
lea r8, [dwProcessDebugPort] 
; ProcessInformation
mov edx, 1Fh ; ProcessInformationClass
mov rcx, -1  ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugPort], 0
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

1.3.3. ProcessDebugObjectHandle进程调试对象句柄
当调试开始时,一个叫做 "debug object "的内核对象被创建。可以通过使用无文献记载的ProcessDebugObjectHandle(0x1e)类来查询这个句柄的值。
C/C++ 代码:

typedef NTSTATUS(NTAPI * TNtQueryInformationProcess)(
IN HANDLE           ProcessHandle,
IN DWORD            ProcessInformationClass,
OUT PVOID           ProcessInformation,
IN ULONG            ProcessInformationLength,
OUT PULONG          ReturnLength
);
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");
if (pfnNtQueryInformationProcess)
{
DWORD dwReturned;
HANDLE hProcessDebugObject = 0;
const DWORD ProcessDebugObjectHandle = 0x1e;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugObjectHandle,
&hProcessDebugObject,
sizeof(HANDLE),
&dwReturned);
if (NT_SUCCESS(status) && (0 != hProcessDebugObject))
ExitProcess(-1);
}
}

x86 汇编:

lea eax, [dwReturned]
push eax ; ReturnLength
push 4   ; ProcessInformationLength
lea ecx, [hProcessDebugObject]
push ecx ; ProcessInformation
push 1Eh ; ProcessInformationClass
push -1  ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [hProcessDebugObject], 0
jnz being_debugged
...
being_debugged:
push -1
call ExitProcess

x86-64 汇编:

lea rcx, [dwReturned]
push rcx     ; ReturnLength
mov r9d, 4   ; ProcessInformationLength
lea r8, [hProcessDebugObject] 
; ProcessInformation
mov edx, 1Fh ; ProcessInformationClass
mov rcx, -1  ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [hProcessDebugObject], 0
jnz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

1.4. RtlQueryProcessHeapInformation()
ntdll!RtlQueryProcessHeapInformation()函数可以用来从当前进程的进程内存中读取堆标志寄存器。
C/C++ 代码:

bool Check()
{
ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE);
if (!SUCCEEDED(ntdll::RtlQueryProcessHeapInformation((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer)))
return false;
ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags;
return dwFlags & ~HEAP_GROWABLE;
}

1.5. RtlQueryProcessDebugInformation()
ntdll!RtlQueryProcessDebugInformation()函数可用于从被请求进程的进程内存中读取某些字段,包括堆标志寄存器。
C/C++ 代码:

bool Check()
{
ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE);
if (!SUCCEEDED(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer)))
return false;
ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags;
return dwFlags & ~HEAP_GROWABLE;
}

1.6. NtQuerySystemInformation()
ntdll!NtQuerySystemInformation()函数接受一个参数,即要查询的信息类别。大多数类都没有被记录下来。这包括SystemKernelDebuggerInformation(0x23)类,它从Windows NT开始就存在了。SystemKernelDebuggerInformation类返回两个标志寄存器的值。al中的KdDebuggerEnabled,和ah中的KdDebuggerNotPresent。因此,如果内核调试器存在,ah中的返回值为零。
C/C++ 代码:

enum { SystemKernelDebuggerInformation = 0x23 };
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION { 
BOOLEAN DebuggerEnabled; 
BOOLEAN DebuggerNotPresent; 
} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; 
bool Check()
{
NTSTATUS status;
SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInfo;
status = NtQuerySystemInformation(
(SYSTEM_INFORMATION_CLASS)SystemKernelDebuggerInformation,
&SystemInfo,
sizeof(SystemInfo),
NULL);
return SUCCEEDED(status)
? (SystemInfo.DebuggerEnabled && !SystemInfo.DebuggerNotPresent)
false;
}
  • 对于IsDebuggerPresent()。将进程环境块(PEB)的BeingDebugged标志设置为0。 更多信息请参见BeingDebugged 标志寄存器反制措施。

  • 对于CheckRemoteDebuggerPresent()和NtQueryInformationProcess():
    由于CheckRemoteDebuggerPresent()调用NtQueryInformationProcess(),唯一的方法是拦截NtQueryInformationProcess()并在返回缓冲区设置以下值。

    • 如果是ProcessDebugPort查询,则为0(或除-1外的任何值)。

    • 如果是ProcessDebugFlags查询,则为非零值。

    • 在ProcessDebugObjectHandle查询的情况下为0。

  • 用RtlQueryProcessHeapInformation()、RtlQueryProcessDebugInformation()和NtQuerySystemInformation()函数反制这些检查的唯一方法是拦截它们并修改返回值:

    • RTL_PROCESS_HEAPS::HeapInformation::Heaps[0]:标志寄存器改为HEAP_GROWABLE,用于RtlQueryProcessHeapInformation()和RtlQueryProcessDebugInformation()。

SYSTEM_KERNEL_DEBUGGER_INFORMATION::DebuggerEnabled为0和系统_KERNEL_DEBUGGER_INFORMATION::DebuggerNotPresent为1。NtQuerySystemInformation()函数在查询SystemKernelDebuggerInformation的情况下。

2. 手工检查
以下方法用于验证系统结构中的调试标志。他们手工检查进程的内存,而不使用特殊的调试API函数。
2.1. PEB!正在调试标志寄存器
这个方法只是检查PEB的BeingDebugged标志寄存器[的另一种方法,而不需要调用IsDebuggerPresent()。
32位进程:

mov eax, fs:[30h]
cmp byte ptr [eax+2], 0
jne being_debugged

64位进程:

mov rax, gs:[60h]
cmp byte ptr [rax+2], 0
jne being_debugged

WOW64 进程:

mov eax, fs:[30h]
cmp byte ptr [eax+1002h], 0

C/C++ 代码:

#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
#endif // _WIN64
if (pPeb->BeingDebugged)
goto being_debugged;

2.2. NtGlobalFlag
进程环境块的NtGlobalFlag字段(32位Windows的0x68偏移,64位Windows的0xBC)默认为0。附加一个调试器并不改变NtGlobalFlag的值。但是,如果进程是由调试器创建的,则将设置以下标志寄存器:

  • FLG_HEAP_ENABLE_TAIL_CHECK (0x10)

  • FLG_HEAP_ENABLE_FREE_CHECK (0x20)

  • FLG_HEAP_VALIDATE_PARAMETERS (0x40)

调试器的存在可以通过检查这些标志寄存器的组合来检测。
32位进程:

mov eax, fs:[30h]
mov al, [eax+68h]
and al, 70h
cmp al, 70h
jz  being_debugged

64位进程:

mov rax, gs:[60h]
mov al, [rax+BCh]
and al, 70h
cmp al, 70h
jz  being_debugged

WOW64 进程:

mov eax, fs:[30h]
mov al, [eax+10BCh]
and al, 70h
cmp al, 70h
jz  being_debugged

C/C++ 代码:

#define FLG_HEAP_ENABLE_TAIL_CHECK   0x10
#define FLG_HEAP_ENABLE_FREE_CHECK   0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS)
#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68);
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC);
#endif // _WIN64
if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED)
goto being_debugged;

2.3. 堆标志寄存器
堆包含两个字段,它们会受到调试器存在的影响。具体如何影响,取决于Windows的版本。这些字段是标志寄存器(Flags)和强标志寄存器(ForceFlags)。

标志寄存器和强标志寄存器的值通常分别设置为HEAP_GROWABLE和0。
当调试器出现时,在Windows NT、Windows 2000和32位Windows XP上,标志寄存器字段被设置为这些标志寄存器的组合。

  • HEAP_GROWABLE (2)

  • HEAP_TAIL_CHECKING_ENABLED (0x20)

  • HEAP_FREE_CHECKING_ENABLED (0x40)

  • HEAP_SKIP_VALIDATION_CHECKS (0x10000000)

  • HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)

在64位的Windows XP和Windows Vista及更高版本中,如果有调试器存在,Flags字段会被设置为这些标志寄存器组合:

  • HEAP_GROWABLE (2)

  • HEAP_TAIL_CHECKING_ENABLED (0x20)

  • HEAP_FREE_CHECKING_ENABLED (0x40)

  • HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)

当调试器出现时,ForceFlags字段被设置为这些标志寄存器的组合:

  • HEAP_TAIL_CHECKING_ENABLED (0x20)

  • HEAP_FREE_CHECKING_ENABLED (0x40)

  • HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)

C/C++ 代码:

bool Check()
{
#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
PVOID pHeapBase = !m_bIsWow64
? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18))
: (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030));
DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater()
? 0x40
: 0x0C;
DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater()
? 0x44 
: 0x10;
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30));
DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater()
? 0x70 
: 0x14;
DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater()
? 0x74 
: 0x18;
#endif // _WIN64
PDWORD pdwHeapFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset);
PDWORD pdwHeapForceFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset);
return (*pdwHeapFlags & ~HEAP_GROWABLE) || (*pdwHeapForceFlags != 0);
}

2.3.堆保护
如果在NtGlobalFlag中设置了HEAP_TAIL_CHECKING_ENABLED标志寄存器,序列0xABABABAB将被附加在所分配的堆块的末端(在32位Windows中为2次,在64位Windows中为4次)。

如果在NtGlobalFlag中设置了HEAP_FREE_CHECKING_ENABLED标志寄存器,如果需要额外的字节来填充空的空间,直到下一个内存块,那么将附加序列0xFEEEFEEE。
C/C++ 代码:

bool Check()
{
PROCESS_HEAP_ENTRY HeapEntry = { 0 };
do
{
if (!HeapWalk(GetProcessHeap(), &HeapEntry))
return false;
while (HeapEntry.wFlags != PROCESS_HEAP_ENTRY_BUSY);
PVOID pOverlapped = (PBYTE)HeapEntry.lpData + HeapEntry.cbData;
return ((DWORD)(*(PDWORD)pOverlapped) == 0xABABABAB);
}

反制措施
对于PEB!BeingDebugged标志寄存器:
将BeingDebugged标志设置为0,这可以通过DLL注入完成。如果你使用OllyDbg或x32/64dbg作为调试器,你可以选择各种反调试插件,如ScyllaHide

#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
#endif // _WIN64
pPeb->BeingDebugged = 0;

对于NtGlobalFlag:
将NtGlobalFlag设置为0,这可以通过DLL注入完成。如果你使用OllyDbg或x32/64dbg作为调试器,你可以选择各种反调试插件,如ScyllaHide

#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
*(PDWORD)((PBYTE)pPeb + 0x68) = 0;
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
*(PDWORD)((PBYTE)pPeb + 0xBC); = 0;
#endif // _WIN64

对于堆标志寄存器:
设置标志寄存器值为HEAP_GROWABLE,ForceFlags值为0,这可以通过DLL注入完成。如果你使用OllyDbg或x32/64dbg作为调试器,你可以选择各种反调试插件,如ScyllaHide

#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
PVOID pHeapBase = !m_bIsWow64
? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18))
: (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030));
DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater()
? 0x40
: 0x0C;
DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater()
? 0x44 
: 0x10;
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30));
DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater()
? 0x70 
: 0x14;
DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater()
? 0x74 
: 0x18;
#endif // _WIN64
*(PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset) = HEAP_GROWABLE;
*(PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset) = 0;

对于堆保护:
手工修补32位的12字节和64位环境下的20字节的堆。拦截kernel32!HeapAlloc()并在其分配后修补堆。

#ifndef _WIN64
SIZE_T nBytesToPatch = 12;
#else
SIZE_T nBytesToPatch = 20;
#endif // _WIN64
SIZE_T nDwordsToPatch = nBytesToPatch / sizeof(DWORD);
PVOID pHeapEnd = (PBYTE)HeapEntry.lpData + HeapEntry.cbData;
for (SIZE_T offset = 0; offset < nDwordsToPatch; offset++)
*((PDWORD)pHeapEnd + offset) = 0;

[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

最后于 2021-6-8 17:47 被梦幻的彼岸编辑 ,原因:

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK