al-khaser
最近在研究开源项目al-khaser在野恶意软件使用的常用技术
github https://github.com/LordNoteworthy/al-khaser
Anti Debug
这一部分主要是通过各种函数去确定当前进程是否处于被调试的状态。
VOID exec_check(int(*callback)(), TCHAR* szMsg)
两参数,第一参数是一个回调函数指针
第二参数是一个需要输出打印的字符串
功能 通过回调函数的返回值以及第二参数szMsg,在函数print_results(result, szMsg)进行具体的判断输出打印并记录到日志中
exec_check(&IsDebuggerPresentAPI, TEXT("Checking IsDebuggerPresent API () "));
/*这是一个Win32 Debugging API ,用于判断进程是否被调试*/
exec_check(&IsDebuggerPresentPEB, TEXT("Checking PEB.BeingDebugged "));
/*从进程中获得PEB 判断pPeb->BeingDebugged 是否被置为1,从而判断是否被调试 */ Process Environement Block (BeingDebugged)
exec_check(&CheckRemoteDebuggerPresentAPI, TEXT("Checking CheckRemoteDebuggerPresentAPI () "));
/*这是一个Win32 Debugging API ,同时可用于检查被监控线程是否被调试,*/
exec_check(&NtGlobalFlag, TEXT("Checking PEB.NtGlobalFlag "));Process Environement Block (NtGlobalFlag)
/*从PEB中取NTGlobalFlag,当进程被调试时,NTGlobalFlag会被置为
FLG_HEAP_ENABLE_TAIL_CHECK (0x10), FLG_HEAP_ENABLE_FREE_CHECK(0x20), 和 FLG_HEAP_VALIDATE_PARAMETERS(0x40),
0x70 = FLG_HEAP_ENABLE_TAIL_CHECK |
FLG_HEAP_ENABLE_FREE_CHECK |
FLG_HEAP_VALIDATE_PARAMETERS
*/
exec_check(&HeapFlags, TEXT("Checking ProcessHeap.Flags "));ProcessHeap (Flags)Flags在不同版本的操作系统中位置不一,通过IsWindowsVistaOrGreater()判断大概版本
/*先获得ProcessHeap的地址,由于pHeapFlags在不同版本的操作系统中位置不一,需要IsWindowsVistaOrGreater()判断大概版本,从而利用pProcessHeap与偏移获得pHeapFlags的地址 */
exec_check(&HeapForceFlags, TEXT("Checking ProcessHeap.ForceFlags "));ProcessHeap (ForceFlags)ForceFlags在不同版本的操作系统中位置不一,通过IsWindowsVistaOrGreater()判断大概版本
/*先获得ProcessHeap的地址,由于ForceFlags在不同版本的操作系统中位置不一,需要IsWindowsVistaOrGreater()判断大概版本,从而利用pProcessHeap与偏移获得pHeapFlags的地址 */
exec_check(&NtQueryInformationProcess_ProcessDebugPort, TEXT("Checking NtQueryInformationProcess with ProcessDebugPort "));- NtQueryInformationProcess (ProcessDebugPort) 返回IsRemotePresent
/*该函数调用NtQuerySystemInformation来获得IsRemotePresent,当函数成功时IsRemotePresent不为 0*/
exec_check(&NtQueryInformationProcess_ProcessDebugFlags, TEXT("Checking NtQueryInformationProcess with ProcessDebugFlags "));
/*该函数调用NtQuerySystemInformation来获得NoDebugInherit,当函数成功时返回值为STATUS_SUCCESS,NoDebugInherit被置为0,表示正在被调试*/
exec_check(&NtQueryInformationProcess_ProcessDebugObject, TEXT("Checking NtQueryInformationProcess with ProcessDebugObject "));
/*该函数调用NtQuerySystemInformation来获得当前进程调试对象句柄,当函数成功 hDebugObject不为0表示正在被调试 */
exec_check(&NtSetInformationThread_ThreadHideFromDebugger, TEXT("Checking NtSetInformationThread with ThreadHideFromDebugger "));
/*将ThreadInformationClass设置为0x11(ThreadHideFromDebugger),用于隐藏调试器中的线程,调用之后调试器与该线程不再关联*/
exec_check(&CloseHandle_InvalideHandle, TEXT("Checking CloseHandle with an invalide handle "));
/*当进程被调试时,调用CloseHandle并传入无效句柄,会得到一个STATUS_INVALID_HANDLE的异常,函数返回TRUE*/
exec_check(&UnhandledExcepFil
terTest, TEXT("Checking UnhandledExcepFilterTest "));
/*当一个未知异常发生,且没有记录的应对方法时,
UnhandledExceptionFilter()会最后被调用
三种返回值
//EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以优雅地结束了
//EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
//EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行
RaiseException(EXCEPTION_FLT_DIVIDE_BY_ZERO, 0, 0, NULL);
该函数启用一个进程并使用结构化异常处理来处理私有的,软件生成的和应用程序定义的异常。
一个异常处理程序在引发异常并导致异常调度的时候需要经过以下步骤的处理:
1.如果有调试器,系统首先尝试通知进程的调试器。
2.如果这一进程不被调试,或者相关的调试器不处理异常,系统将尝试通过搜索发生异常的线程的堆栈来定位一个基于帧的异常处理程序。 系统首先会搜索当前栈帧,然后继续向后搜索栈帧。
3.如果没有找到基于帧的异常处理程序,或没有基于帧的异常处理程序处理该异常,系统将会第二次尝试通知进程的调试器。
4.如果该进程仍未被调试,或者相关的调试器不处理异常,系统以异常类型为基础提供默认的处理程序。 对于大多数异常,默认动作是调用ExitProcess函数。
即 当进程被调试时,会通知进程的调试器,而不会调用UnhandledExceptionFilter
*/
exec_check(&OutputDebugStringAPI, TEXT("Checking OutputDebugString "));
/*This Works only in Windows XP/2000
OutputDebugString()用于输出字符串给调试器显示。
我们通过GetLastError是否被置为Val,来判断是否有调试器与进程关联。
*/
exec_check(&HardwareBreakpoints, TEXT("Checking Hardware Breakpoints "));
/*硬件断点,intel下的,用Dr0-Dr7七个寄存器控制,在CONTEXT结构体中可以查询这些寄存器的状态*/
exec_check(&SoftwareBreakpoints, TEXT("Checking Software Breakpoints "));
/*软件断点 INT 3 获得函数地址和大小,以字节为单位查询是否存在0xCC */
exec_check(&Interrupt_0x2d, TEXT("Checking Interupt 0x2d "));
/*
AddVectoredExceptionHandler()注册一个定向的情况处理
__int2d()是一个用asm写的函数,利用指令 2D 测试进程是否被调试,当进程没有关联调试器,会返回一个异常,
当情况为异常断点时需要我们手动将Rip或Eip++
*/
exec_check(&Interrupt_3, TEXT("Checking Interupt 1 "));
/*
AddVectoredExceptionHandler()注册一个定向的情况处理
__debugbreak() Microsoft专用将在代码中引起断点,并在其中提示用户运行调试程序。当进程没有关联调试器,会返回一个异常,
当情况为异常断点时不需要我们手动将Rip或Eip++,原因 Int 2D instruction already increased EIP/RIP
*/
exec_check(&MemoryBreakpoints_PageGuard, TEXT("Checking Memory Breakpoints PAGE GUARD: "));
/*分配一块动态内存,将RET写入内存中,然后将这块内存标记成保护,再以函数的形式调用该地址。当进程被调试时,遇到RET指令会返回之前的栈,执行下一个语句。
反之,产生一个STATUS_GUARD_PAGE_VIOLATION的异常,说明进程没有被调试*/
exec_check(&IsParentExplorerExe, TEXT("Checking If Parent Process is explorer.exe: "));
/*判读父进程是不是创建窗口的
获得了创建窗口的线程的ID号且当进程只有一个线程的话,进程ID号也为此。
获得了父进程ID
*/
exec_check(&CanOpenCsrss, TEXT("Checking SeDebugPrivilege : "));
/*当进程被调试且被提权到administrator时,该进程可以打开Csrss进程,*/
exec_check(&NtQueryObject_ObjectTypeInformation, TEXT("Checking NtQueryObject with ObjectTypeInformation : "));
/*当调试开始,调试对象就会被创建,我们可以通过判断该对象是否存在从而判读进程是否被调试。通过NtCreateDebugObject获得DebugObjectHandle,利用NtQueryObject获得ObjectInformation
从而得到ObjectInformation 即调试对象的信息 */
exec_check(&NtQueryObject_ObjectAllTypesInformation, TEXT("Checking NtQueryObject with ObjectAllTypesInformation : "));
/*Support for Win10
当一个调试开始后,一个调试对象就会生成。我们可以通过判断该对象是否存在从而判读进程是否被调试。
两遍调用NtQueryObject,第一遍得到对象大小,申请一块内存,第二遍将对象全部信息放到这块内存里,
通过对象全部信息查找是否存在DebugObject这个对象,
*/
exec_check(&NtYieldExecutionAPI, TEXT("Checking NtYieldExecution : "));
/*NtYieldExecution使当前线程放弃自己的时间片,让已进入队列的线程执行,当没有线程执行或操作系统不允许切换发送时,函数返回STATUS_NO_YIELD_PERFORMED促使SwitchToThread返回0
当我们在被调试情况下,由于单步执行 时常会导致无法切换线程。这是一个不可靠的办法*/
exec_check(&SetHandleInformatiom_ProtectedHandle, TEXT("Checking CloseHandle protected handle trick : "));
/*创建了一个互斥体对象,利用SetHandleInformation将我们互斥体对象句柄标志改为HANDLE_FLAG_PROTECT_FROM_CLOSE
,最后关闭句柄。当进行调试时,会走到try-except 语句中return TRUE ,反之不进except 语句 return FALSE*/