#include "AntiDebug.h" #include #include #include "ProcessInfo.h" #include "Util.h" #include "TraceLog.h" #include "Settings.h" #include "PinLocker.h" #include "TinyTracer.h" #include "win_paths.h" #include "win_constants.h" #define ANTIDBG_LABEL "[ANTIDEBUG] --> " /* ================================================================== */ // Global variables used by AntiDebug /* ================================================================== */ ADDRINT pebAddr = 0; ADDRINT heapFlags = 0; ADDRINT heapForceFlags = 0; std::vector loadedLib; /* ==================================================================== */ // Leveraging the existing paramToStr, extracts only the string after '->' /* ==================================================================== */ std::wstring paramToStrSplit(VOID* arg1) { if (arg1 == NULL) { return L"0"; } std::wstring inStr = paramToStr(arg1); size_t arrowPos = inStr.find(L"->"); if (arrowPos != 0) { // Extract the substring after the arrow symbol and contained in quotes std::wstring secondPart = inStr.substr(arrowPos + 3); size_t startPos = secondPart.find('"'); if (startPos != std::string::npos) { size_t endPos = secondPart.rfind('"'); if (endPos != std::string::npos) { secondPart = secondPart.substr(startPos + 1, endPos - startPos - 1); } } return secondPart; } return L"0"; } /* ==================================================================== */ // Function to check if is native 32 bit or not /* ==================================================================== */ BOOL IsNativeOs32(void) { BOOL isNativeOs32 = FALSE; #ifndef _WIN64 BOOL isWow64 = FALSE; WINDOWS::IsWow64Process(WINDOWS::GetCurrentProcess(), (WINDOWS::PBOOL)&isWow64); isNativeOs32 = !isWow64; #endif return isNativeOs32; } /* ==================================================================== */ // Callback function to be executed when memory is accessed /* ==================================================================== */ VOID AntidebugMemoryAccess(ADDRINT addr, UINT32 size, const ADDRINT insAddr) { PinLocker locker; if (isWatchedAddress(insAddr) == WatchedType::NOT_WATCHED) return; std::wstringstream ss; ADDRINT RvaFrom = addr_to_rva(insAddr); // Check the accessed memory address for antidebug tricks if (addr == pebAddr + 2) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "PEB!BeingDebugged accessed"; } else if (addr == 0x7ffe02d4) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "KUSER_SHARED_DATA accessed https://anti-debug.checkpoint.com/techniques/debug-flags.html#kuser_shared_data"; } else #ifdef _WIN64 if (addr == pebAddr + 0xBC) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "PEB!NtGlobalFlag accessed"; } else if (addr == heapFlags || addr == heapForceFlags) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "Heap Flags accessed https://anti-debug.checkpoint.com/techniques/debug-flags.html#manual-checks-heap-flags"; } #else if (addr == pebAddr + 0x68) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "PEB!NtGlobalFlag accessed"; } else if (addr == heapFlags || addr == heapForceFlags) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "Heap Flags accessed https://anti-debug.checkpoint.com/techniques/debug-flags.html#manual-checks-heap-flags"; } #endif if (!ss.str().empty()) { // Write out to log std::wstring argsLineW = ss.str(); std::string s(argsLineW.begin(), argsLineW.end()); traceLog.logLine(s); } } /* ==================================================================== */ // Process API calls (related to AntiDebug techniques) /* ==================================================================== */ VOID AntidebugProcessFunctions(const ADDRINT Address, const CHAR* name, uint32_t argCount, VOID* arg1, VOID* arg2, VOID* arg3, VOID* arg4, VOID* arg5, VOID* arg6, VOID* arg7, VOID* arg8, VOID* arg9, VOID* arg10) { PinLocker locker; if (isWatchedAddress(Address) == WatchedType::NOT_WATCHED) return; std::wstringstream ss; ADDRINT RvaFrom = addr_to_rva(Address); /////////////////////////////////////////////////////////////////////////////// // Check known function names for antidebug tricks, then parameters (if needed) /////////////////////////////////////////////////////////////////////////////// // TODO: not sure if it's better to use isStrEqualI or strcmp if (strcmp(name, "IsDebuggerPresent") == 0) { // function kernel32!IsDebuggerPresent() ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!IsDebuggerPresent https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-isdebuggerpresent"; } else if (strcmp(name, "CheckRemoteDebuggerPresent") == 0) { // function kernel32!CheckRemoteDebuggerPresent() ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!CheckRemoteDebuggerPresent https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-checkremotedebuggerpresent"; } else if (strcmp(name, "NtQueryInformationProcess") == 0) { // function ntdll!NtQueryInformationProcess with ProcessInformationClass == 7 (ProcessDebugPort) if (int((size_t)arg2) == PROCESSDEBUGPORT) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!NtQueryInformationProcess (ProcessDebugPort) https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-ntqueryinformationprocess-processdebugport"; } else // function ntdll!NtQueryInformationProcess with ProcessInformationClass == 0x1f (ProcessDebugFlags) if (int((size_t)arg2) == PROCESSDEBUGFLAGS) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!NtQueryInformationProcess (ProcessDebugFlags) https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-ntqueryinformationprocess-processdebugflags"; } else // function ntdll!NtQueryInformationProcess with ProcessInformationClass == 0x1e (ProcessDebugObjectHandle) if (int((size_t)arg2) == PROCESSDEBUGOBJECTHANDLE) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!NtQueryInformationProcess (ProcessDebugObjectHandle) https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-ntqueryinformationprocess-processdebugobjecthandle"; } } else if (strcmp(name, "RtlQueryProcessHeapInformation") == 0) { // function ntdll!RtlQueryProcessHeapInformation() // FIXME possible improvement: check access to the buffer parameter of the function ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!RtlQueryProcessHeapInformation https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-checks-rtlqueryprocessheapinformation"; } else if (strcmp(name, "RtlQueryProcessDebugInformation") == 0) { // function ntdll!RtlQueryProcessDebugInformation() ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!RtlQueryProcessDebugInformation https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-checks-rtlqueryprocessdebuginformation"; } else if (strcmp(name, "NtQuerySystemInformation") == 0) { // function ntdll!NtQuerySystemInformation() with first parameter set to 0x23 (SystemKernelDebuggerInformation) if (int((size_t)arg1) == SYSTEMKERNELDEBUGGERINFORMATION) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!NtQuerySystemInformation (SystemKernelDebuggerInformation) https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-checks-ntquerysysteminformation"; } } else if (strcmp(name, "HeapWalk") == 0) { // kernel32!HeapWalk() function to extract Heap blocks and check the tail ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!HeapWalk https://anti-debug.checkpoint.com/techniques/debug-flags.html#manual-checks-heap-protection"; } else if (strcmp(name, "CsrGetProcessId") == 0) { // kernel32!OpenProcess() function on the csrss.exe - CsrGetProcessId get the csrss.exe PID ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!OpenProcess/CsrGetProcessId https://anti-debug.checkpoint.com/techniques/object-handles.html#openprocess"; } else if (strcmp(name, "CreateFileA") == 0 || strcmp(name, "CreateFileW") == 0) { // kernel32!CreateFileX called on the module itself with Exclusive access, or on loaded libraries // Check only exclusive accesses for optimization if (int((size_t)arg3) == 0) { IMG img = IMG_FindByAddress(Address); if (IMG_Valid(img)) { // Get the module name from image std::string moduleName = IMG_Name(img); // Get the module name from arguments std::wstring argStr = paramToStrSplit(arg1); // Convert from wide string for comparison std::string _argStr(argStr.begin(), argStr.end()); // Check if open is done on module if (util::isStrEqualI(_argStr, moduleName)) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!CreateFile on module https://anti-debug.checkpoint.com/techniques/object-handles.html#createfile"; } else { // Check if open is done on loaded libraries for (int i = 0; i < loadedLib.size(); i++) if (util::isStrEqualI(_argStr, loadedLib[i])) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!CreateFile on loaded lib https://anti-debug.checkpoint.com/techniques/object-handles.html#loadlibrary"; } } } } } else if (strcmp(name, "SetUnhandledExceptionFilter") == 0) { // kernel32!SetUnhandledExceptionFilter() function to set a specific handler ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!SetUnhandledExceptionFilter https ://anti-debug.checkpoint.com/techniques/exceptions.html#unhandledexceptionfilter"; } else if (strcmp(name, "RaiseException") == 0) { // kernel32!RaiseException() with DBG_CONTROL_C or DBG_RIPEVENT if (int((size_t)arg1) == DBG_CONTROL_C || int((size_t)arg1) == DBG_RIPEVENT) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!RaiseException() https://anti-debug.checkpoint.com/techniques/exceptions.html#raiseexception"; } } else if (strcmp(name, "LoadLibraryA") == 0 || strcmp(name, "LoadLibraryW") == 0) { // Track LoadLibraryX to detect access to LOAD_DLL_DEBUG_INFO // Get the library name from argument std::wstring argStr = paramToStrSplit(arg1); // Convert from wide string for comparison std::string _argStr(argStr.begin(), argStr.end()); loadedLib.push_back(_argStr); } else //////////////////////////////////// // If AntiDebug level is == 2 (Deep) //////////////////////////////////// if (m_Settings.antidebug > 1) { if (strcmp(name, "NtQueryObject") == 0) { // ntdll!NtQueryObject() to access DebugObject (with ObjectTypesInformation as 2nd argument) if (int((size_t)arg2) == OBJECTTYPESINFORMATION) { ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ ntdll!NtQueryObject(with ObjectAllTypesInformation) https://anti-debug.checkpoint.com/techniques/object-handles.html#ntqueryobject"; } } } if (!ss.str().empty()) { // Write out to log std::wstring argsLineW = ss.str(); std::string s(argsLineW.begin(), argsLineW.end()); traceLog.logLine(s); } } /* ==================================================================== */ // Collect some infos at Thread start, to be used later in checks /* ==================================================================== */ VOID ThreadStart(THREADID threadid, CONTEXT* ctxt, INT32 flags, VOID* v) { PinLocker locker; // Checks only the first thread if (threadid != 0) { return; } #ifdef _WIN64 // Read the value from the memory address pointed by GS:[60h] and save it in the global variable ADDRINT gsValue; PIN_GetContextRegval(ctxt, REG_SEG_GS_BASE, reinterpret_cast(&gsValue)); gsValue += 0x60; // Save PEB Address PIN_SafeCopy(&pebAddr, reinterpret_cast(gsValue), sizeof(pebAddr)); // Get Heap flags addresses (https://anti-debug.checkpoint.com/techniques/debug-flags.html#manual-checks-heap-flags) ADDRINT heapBase; PIN_SafeCopy(&heapBase, reinterpret_cast(pebAddr + 0x30), sizeof(heapBase)); ADDRINT heapFlagsOffset = WINDOWS::IsWindowsVistaOrGreater() ? 0x70 : 0x14; ADDRINT heapForceFlagsOffset = WINDOWS::IsWindowsVistaOrGreater() ? 0x74 : 0x18; heapFlags = heapBase + heapFlagsOffset; heapForceFlags = heapBase + heapForceFlagsOffset; #else // Read the value from the memory address pointed by FS:[30h] and save it in the global variable ADDRINT fsValue; PIN_GetContextRegval(ctxt, REG_SEG_FS_BASE, reinterpret_cast(&fsValue)); fsValue += 0x30; // Save PEB Address PIN_SafeCopy(&pebAddr, reinterpret_cast(fsValue), sizeof(pebAddr)); // Get Heap flags addresses (https://anti-debug.checkpoint.com/techniques/debug-flags.html#manual-checks-heap-flags) ADDRINT heapBase; ADDRINT heapBaseOffset = IsNativeOs32() ? 0x18 : 0x1030; PIN_SafeCopy(&heapBase, reinterpret_cast(pebAddr + heapBaseOffset), sizeof(heapBase)); ADDRINT heapFlagsOffset = WINDOWS::IsWindowsVistaOrGreater() ? 0x40 : 0x0C; ADDRINT heapForceFlagsOffset = WINDOWS::IsWindowsVistaOrGreater() ? 0x44 : 0x10; heapFlags = heapBase + heapFlagsOffset; heapForceFlags = heapBase + heapForceFlagsOffset; #endif } //* ==================================================================== */ // "CloseHandle" instrumentation, detects invalid handlers /* ===================================================================== */ VOID AntidebugCloseHandle(ADDRINT Address, ADDRINT regGAX) { PinLocker locker; if (isWatchedAddress(Address) == WatchedType::NOT_WATCHED) return; if (regGAX == 0) { // Invalid closure std::wstringstream ss; ADDRINT RvaFrom = addr_to_rva(Address); ss << std::hex << RvaFrom << DELIMITER << ANTIDBG_LABEL << "^ kernel32!CloseHandle https://anti-debug.checkpoint.com/techniques/object-handles.html#closehandle"; if (!ss.str().empty()) { // Write out to log std::wstring argsLineW = ss.str(); std::string s(argsLineW.begin(), argsLineW.end()); traceLog.logLine(s); } } } /* ==================================================================== */ // Add single function /* ==================================================================== */ bool AntidebugMonitorAdd(IMG Image, char* fName, uint32_t argNum, const std::string& dllName) { RTN funcRtn = RTN_FindByName(Image, fName); if (RTN_Valid(funcRtn)) { RTN_Open(funcRtn); RTN_InsertCall(funcRtn, IPOINT_BEFORE, AFUNPTR(AntidebugProcessFunctions), IARG_RETURN_IP, IARG_ADDRINT, fName, IARG_UINT32, argNum, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_FUNCARG_ENTRYPOINT_VALUE, 6, IARG_FUNCARG_ENTRYPOINT_VALUE, 7, IARG_FUNCARG_ENTRYPOINT_VALUE, 8, IARG_FUNCARG_ENTRYPOINT_VALUE, 9, IARG_FUNCARG_ENTRYPOINT_VALUE, 10, IARG_END ); RTN_Close(funcRtn); return true; } return false; } /* ==================================================================== */ // Add to monitored functions all the API needed for AntiDebug. // Called by ImageLoad /* ==================================================================== */ VOID AntidebugMonitorFunctions(IMG Image) { // API needed for Antidebug const std::string dllName = util::getDllName(IMG_Name(Image)); if (util::iequals(dllName, "ntdll")) { AntidebugMonitorAdd(Image, "CsrGetProcessId", 0, dllName); AntidebugMonitorAdd(Image, "NtQueryInformationProcess", 5, dllName); AntidebugMonitorAdd(Image, "RtlQueryProcessHeapInformation", 1, dllName); AntidebugMonitorAdd(Image, "RtlQueryProcessDebugInformation", 3, dllName); AntidebugMonitorAdd(Image, "NtQuerySystemInformation", 4, dllName); if (m_Settings.antidebug > 1) { // For Deep or above AntidebugMonitorAdd(Image, "NtQueryObject", 5, dllName); } } if (util::iequals(dllName, "kernel32")) { AntidebugMonitorAdd(Image, "LoadLibraryW", 1, dllName); AntidebugMonitorAdd(Image, "LoadLibraryA", 1, dllName); AntidebugMonitorAdd(Image, "GetProcAddress", 2, dllName); AntidebugMonitorAdd(Image, "CreateFileW", 6, dllName); AntidebugMonitorAdd(Image, "CreateFileA", 7, dllName); AntidebugMonitorAdd(Image, "OpenProcess", 3, dllName); AntidebugMonitorAdd(Image, "IsDebuggerPresent", 0, dllName); AntidebugMonitorAdd(Image, "CheckRemoteDebuggerPresent", 2, dllName); AntidebugMonitorAdd(Image, "HeapWalk", 2, dllName); AntidebugMonitorAdd(Image, "CloseHandle", 1, dllName); AntidebugMonitorAdd(Image, "SetUnhandledExceptionFilter", 1, dllName); AntidebugMonitorAdd(Image, "RaiseException", 4, dllName); } // CloseHandle return value hook RTN funcRtn = RTN_FindByName(Image, "CloseHandle"); if (!RTN_Valid(funcRtn)) return; // failed RTN_Open(funcRtn); RTN_InsertCall(funcRtn, IPOINT_AFTER, AFUNPTR(AntidebugCloseHandle), IARG_RETURN_IP, IARG_REG_VALUE, REG_GAX, IARG_END); RTN_Close(funcRtn); }