1. 前言
2. 原理
3. 可利用的点
4. 实践开始
5. 总结
01
前言
为了避免杀软检测到代码中的shellcode,可以对shellcode进行加密,执行时输入key,使用本地分离、远程加载等方式。后面几种很容易实现,效果也很好,但是操作麻烦,而且除了远程加载外都不能使用在钓鱼中;如果只用第一种方式,这种方法的好处是可以避免使用独立的程序,并且可以在不暴露key的情况下解密shellcode。不过,由于加密算法和密钥都是硬编码的,想靠加密算法如des、ase、rc4等免杀很难,它们可能会存在被杀软检测并逆推还原出原shellcode等问题。为了提高静态免杀能力,可以在运行代码时动态生成key,而不是在代码中硬编码它。
02
原理
动态生成key的原理是利用外部运行环境点作为加密算法的key。注意,使用这些外部运行环境点作为加密算法的key并不能完全保证杀软无法逆推出原始shellcode。一些先进的杀软可能会监视这些环境点,并尝试通过分析它们的变化来检测潜在的恶意行为。为了确保最大的安全性,最好配合更复杂的加密算法来加密shellcode。
03
可利用的点
叫chatgpt列出了一些可利用的点:
- 文件路径:使用某个固定的文件路径作为key。例如,使用"C:WindowsSystem32kernel32.dll"的最后一个字符作为key。
- 注册表键值:使用注册表中某个固定的键值作为key。例如,使用"HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionProductName"的值的前三个字符作为key。注意,读取键值并不会被杀软拦截。
- 硬件信息:使用硬件信息作为key。
- 系统信息:使用系统信息作为key。例如,使用Windows版本号的最后一个数字作为key。
- 时间戳:使用当前时间戳作为key,例如使用Unix时间戳或Windows FILETIME格式的时间戳。
- 系统环境变量:使用系统环境变量作为key,例如使用%USERNAME%的前三个字符作为key。
- 硬件ID:使用硬件ID作为key,例如使用主板序列号的最后两个字符作为key。
- 进程ID:使用当前进程的进程ID作为key。
- 网络信息:使用网络信息作为key,例如使用本地IP地址的最后一个数字作为key。
- 文件内容:使用某个文件的内容作为key,例如使用某个固定的文件的MD5或SHA256散列作为key。
- 进程信息:使用当前进程的一些信息作为key,例如使用进程名的第一个字符和PID的最后一个字符。
- 用户信息:使用当前用户的一些信息作为key,例如使用用户名的前两个字符和SID的最后一个字符。
- 系统时间:使用当前系统时间作为key,例如使用系统时间的分钟数和秒数。
- CPU信息:使用CPU信息作为key,例如使用CPU序列号的最后几个字符。
- Windows API函数:使用Windows API函数返回值的一部分作为key,例如使用GetTickCount()的低16位作为key。
- 系统驱动程序:使用系统驱动程序作为key,例如使用某个已安装的驱动程序的文件名的第一个字符和版本号的最后一个字符。
- 系统路径:使用系统路径作为key,例如使用%SYSTEMROOT%的最后一个字符作为key。
- 系统语言:使用系统语言作为key,例如使用系统语言的代码页编号作为key。
- 系统内存信息:使用系统内存信息作为key,例如使用物理内存大小的最后一个字节作为key。
这些外部运行环境点在不同的系统配置中可能会有所不同,因此在选择一个用作key的环境点时,需要进行一些测试以确保它在特定环境中是稳定的,并且不能被杀软检测到。
04
实践开始
以下仅在同一shellcode加载器及同一加密算法下实践。
- 用GetModuleHandleA获取kernel32.dll的路径,取最后一个字符“L”当作key:
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HMODULE hModule = GetModuleHandleA("kernel32.dll");
if (hModule == NULL)
{
cout << "GetModuleHandle failed." << endl;
return -1;
}
char szPath[MAX_PATH];
DWORD dwSize = GetModuleFileNameA(hModule, szPath, MAX_PATH);
if (dwSize == 0)
{
cout << "GetModuleFileName failed." << endl;
return -1;
}
cout << "The path of kernel32.dll is: " << szPath << endl;
return 0;
}
过360火绒静态加动态,defender静态,卡巴直接不过。
- 通过注册表读取操作系统版本,取第一个个字符“W”为key:
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HKEY hKey = NULL;
DWORD dwType = REG_SZ;
char szValue[MAX_PATH] = { 0 };
DWORD dwSize = sizeof(szValue);
LONG lRet = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion", 0, KEY_READ, &hKey);
if (lRet != ERROR_SUCCESS)
{
cout << "RegOpenKeyEx failed." << endl;
return -1;
}
lRet = RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)szValue, &dwSize);
if (lRet != ERROR_SUCCESS)
{
cout << "RegQueryValueEx failed." << endl;
RegCloseKey(hKey);
return -1;
}
cout << "The value of ProductName is: " << szValue << endl;
RegCloseKey(hKey);
return 0;
}
过360火绒静态加动态,defender静态,卡巴直接不过。
- 获取网卡名的第一个字符“{”作为key:
#include <windows.h>
#include <iphlpapi.h>
#include <iostream>
#pragma comment(lib, "iphlpapi.lib")
using namespace std;
int main()
{
PIP_ADAPTER_INFO pAdapterInfo = NULL;
ULONG ulSize = 0;
DWORD dwRet = GetAdaptersInfo(pAdapterInfo, &ulSize);
if (dwRet == ERROR_BUFFER_OVERFLOW)
{
pAdapterInfo = (PIP_ADAPTER_INFO)malloc(ulSize);
dwRet = GetAdaptersInfo(pAdapterInfo, &ulSize);
}
if (dwRet == ERROR_SUCCESS)
{
PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
while (pAdapter)
{
cout << "Adapter name: " << pAdapter->AdapterName << endl;
cout << "MAC address: ";
for (int i = 0; i < pAdapter->AddressLength; i )
{
printf("X", pAdapter->Address[i]);
if (i < pAdapter->AddressLength - 1)
{
printf("-");
}
}
cout << endl;
pAdapter = pAdapter->Next;
}
}
else
{
cout << "GetAdaptersInfo failed." << endl;
}
if (pAdapterInfo)
{
free(pAdapterInfo);
}
return 0;
}
- 取时间截的第一位数字1为key:
#define _CRT_SECURE_NO_WARNINGS
#include <time.h>
#include <iostream>
using namespace std;
int main()
{
time_t now = time(NULL);
cout << "Current time: " << now / 1000000000 << endl;
return 0;
}
过360火绒静态加动态,defender静态,卡巴直接不过。
- 通过环境变量%USERNAME%,取第一位字符“A”为key:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
char* username = getenv("USERNAME");
if (username != NULL)
cout << "USERNAME is: " << username << endl;
else
cout << "USERNAME is not defined" << endl;
return 0;
}
过360火绒静态加动态,defender静态,卡巴静态加动态。
- 通过硬件ID
代码太长,这里不放了。
defender静态,卡巴一直卡死在99。
defender静态,卡巴不过。
- 使用线程ID,转为hash,由于hash第一位总为1,可以当作密钥:
defender静态,卡巴不过。
实践过程中,我逐渐意识到问题,可能并不是卡巴查杀很厉害,而是编译器的问题。在测试过程中,卡巴查杀经常卡在1%或99%,等很长时间,重复试了几次,最后卡巴直接删除了exe:
卡巴可能并没有检测出shellcode,而是基于一种风险指数,虽然没有检测出shellcode,但风险指数偏高,于是直接列为恶意软件直接删除了。这种风险指数与编译器有关。vs默认的编译器更容易被杀软认为恶意软件,vs默认的编译器几乎被所有杀软标记重点了。后面切换vs的intel C 编译器(需要自行安装),重新对前面的几个环境点进行了测试,切换intel C 编译器后均过卡巴静态加动态且扫描很快,没有出现像vs默认的编译器一直卡住的情况:
然后上传了VT测试,静态全过:
不使用动态生成key,明文密钥,使用intel c 编译,VT:
05
总结
使用动态生成key静态免杀效果良好。在使用vs默认编译器的情况下,上面的多个环境点测试均过360、火绒,defender过静态,卡巴除了利用环境变量外其它均不过;在切换vs的intel c 编译器后360、火绒、卡巴均过,defender过静态。使用intel c 编译免杀效果良好,但是安装麻烦,占内存
锦鲤安全
一个安全技术学习与工具分享平台
点分享
点收藏
点点赞
点在看