文章总结: 本文介绍利用AI自动化对fscan进行二开以规避杀软检测。核心步骤包括:去除banner、命令行参数及内部字符串等敏感特征,并通过执行闭环确保修改彻底;采用资源加载器将fscan转为shellcode加载,通过动态加载隐藏导入表及API哈希隐藏字符串,最终实现免杀。该方法提供了可操作的技术路径,但需注意其潜在的法律风险。 综合评分: 86 文章分类: 渗透测试,红队,内网渗透,安全工具,免杀
AI自动化fscan二开
云*3 云*3
乌雲安全
2026年7月20日 08:32 重庆
在小说阅读器读本章
去阅读
作者:云*3
来源:https://xz.aliyun.com/news/92390
一、去除fscan敏感特征以及编译
首先,让AI阅读相关fscan魔改的文章,然后制定二开计划。
根据网上搜索的文章,fscan的敏感特征为以下3个:banner特征、命令行参数、内部大量的字符串。
banner特征: 实验了几次,AI都能完美执行,直接注释调banner,当然这个也是去除敏感特征里面最简单的。
**命令行参数:**AI在修改过程中,实现了很多次,基本上都能完整的执行,但存在几个小问题,修改后的命令行参数可能会重合、修改参数没有全部修改导致杀软依旧能根据参数判断出有扫描特征,这些让AI第二次复查时,都能解决。
内部大量的存在“fscan字眼”字符串:这个Ai执行的最不好,执行时经常会出现大规模的特征字符串残留,虽然在大量残留下,我发现杀软依然无法进行有效检测,但为了精益求精,决定更进一步。
1)利用执行闭环去除敏感字符串
常规的检测逻辑就是,将字符串修改后—–>go编译项目——->用检测字符串工具string.exe去检查是否存在遗漏。
(备注:因为用garble混淆编译后是不可能能检查到残余字符串,只有在用go原生编译情况下,才能看出字符串是否完整修改)
我在这里定义了一个技能,其中涉及到这个检测逻辑的如下,让AI写了一个迭代流程。利用string.exe进行检查闭环,让Ai能够知道什么情况下自己能输出,什么情况下不能输出编译结果
编译 → strings.exe 查 "fscan" → 有输出?→ 源码定位 → 修改源码 → 回到编译 ↓ 无输出 ✅ 通过
示例具体操作:
# 1. 正常编译(不加 garble/UPX)go build -ldflags="-s -w" -trimpath -o test.exe .
# 2. powershell用 strings.exe 查 "fscan" 残留.\strings.exe test.exe | Select-String "fscan" | Where-Object { $_ -notmatch "gc|sync\.|panic|defer|sweep|runtime" }
不直接是 strings.exe test.exe | Select-String “fscan”的原因是:Select-String "fscan" 只会匹配包含 fscan 的所有行,里面混杂大量 Go runtime 无害日志(gc、sync、runtime 等),干扰你判断真正恶意硬编码特征;加 Where-Object 是过滤掉 Go 运行时自带的噪音行,只保留业务代码里真实写死的 fscan 字符串。
利用闭环之前,发现存在多处fscan特征
利用闭环之后,未发现该特征
defscangui.exe是fscan在插件中定义的已知杀软进程,这个不算fscan特征
2)频繁出现报错,将反思放进skill,反复迭代
编译过程中出现大量报错,将反思放入skill中,使得能够反复迭代,最后利用该技能可以非常快的速度下改好fscan。
图示是当时遇到的问题,几次编译都严重破坏了源码
二、利用资源加载器落地
常规流程是:将fscan.exe转化为shellcode,然后用加载器加载到内存中,再利用加载器执行,使得行为更加隐蔽。
这里使用了一个前人的加载器demo,但从微步看到已经杀烂了。
调试信息中也有泄露,可以看到它的用户名应该是12275
于是对该加载器进行二开,来避免检测
1)利用执行闭环混淆导入表
1-1:动态加载隐藏导入表
导入表:Windows 下编译生成的 EXE/DLL 是PE 格式,导入表是 PE 里的一段数据,作用是记录程序运行时需要从哪些 DLL、调用哪些外部函数。
蓝队和杀软可以从导入表中找到高危信息,比如
GetCurrentProcess #获取自身进程句柄,通常为了修改自身内存属性,比如把一段数据区的内存从可读改成 PAGE_EXECUTE_READWRITE(可执行)VirtualAlloc #分配可执行内存,常用于shellcode,比如利用 PAGE_EXECUTE_READWRITE将shellcode赋予读写执行权限VirtualAlloc( NULL, fileSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
于是尝试用 自定义函数来隐藏导入表
可以在运行时使用 GetProcAddress、GetModuleHandle 或 LoadLibrary 动态加载这些函数,因此在检查时它不会出现在导入表中。
| | | |
| — | — | — |
| 方式 | 导入表里有记录吗? | 为什么 |
| 直接调用MessageBoxA(...) | ✅ 有 | 编译器能看到你用了,提前写进清单(所以静态分析可以分析到) |
| 动态加载GetProcAddress | ❌ 没有 | 编译器看不到你具体要哪个函数,清单是空的(动态下才调用,静态就分析不到了) |
于是我们需要一个执行闭环,让AI混淆后,知道什么情况下才算混淆成功。我们引入dumpbin工具
dumpbin是 Visual Studio 自带的一个”二进制文件体检工具”,它是微软官方提供的,随 Visual Studio 一起安装,不需要额外下载。可以在命令行里面快速查看导入表信息。(其他几个工具全部都是界面工具,不太适合AI操作)
使用时,需要将dumpbin.exe设置为环境变量,方便使用
dumpbin /IMPORTS loader.exe
示例使用如下:
让AI混淆之后,自动用dumpbin进行检查,分析导入表是否成功混淆,设置工作流如下
目标:不干扰原先cpp文件逻辑的前提下重新混淆cpp文件,使得能达到导入表隐藏的效果要求:1、按照导入表隐藏.md的方法,使用自定义函数隐藏导入表2、只混淆对杀软认为有风险的导入表,比如GetCurrentProcess、VirtualAlloc步骤:混淆后编译生成新loader.exe,使用dumpbin /IMPORTS loader.exe进行检查,看看有没有隐藏导入表成功,成功后才执行后续步骤完成后运行一键转Shellcode工具.bat,将项目内fscan.exe放入后生成output.bin。然后执行loader.exe -h 127.0.0.1看是否能正常运行,直到能正常运行为止,一旦运行超时没有输出即刻重新混淆原始cpp,重新执行上述步骤
这里有个坑点:闭环执行需要逻辑上必须闭环,比如fscan生成shellcode的oubput.bin,如果直接让它操作output.bin,AI一旦出问题会觉得是流程有问题,而不是它代码写错了,然后一下子去操作fscan,一下子就去找其他东西,会非常有发散性,但最终总是无法解决问题。
代码片段如下
int main() { const char* filename = "output.bin"; XXXXXXXXXXX
// ========== 动态加载敏感 API,避免出现在 IAT 中 ========== HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); if (!hKernel32) { printf("[!] 获取 kernel32.dll 句柄失败\n"); return 1; }
pCreateFileA _CreateFileA = (pCreateFileA) GetProcAddress(hKernel32, "CreateFileA"); pGetFileSize _GetFileSize = (pGetFileSize) GetProcAddress(hKernel32, "GetFileSize"); pVirtualAlloc _VirtualAlloc = (pVirtualAlloc) GetProcAddress(hKernel32, "VirtualAlloc"); pReadFile _ReadFile = (pReadFile) GetProcAddress(hKernel32, "ReadFile"); pVirtualFree _VirtualFree = (pVirtualFree) GetProcAddress(hKernel32, "VirtualFree"); pCloseHandle _CloseHandle = (pCloseHandle) GetProcAddress(hKernel32, "CloseHandle"); pFlushInstructionCache _FlushInstructionCache = (pFlushInstructionCache)GetProcAddress(hKernel32, "FlushInstructionCache"); pGetCurrentProcess _GetCurrentProcess = (pGetCurrentProcess) GetProcAddress(hKernel32, "GetCurrentProcess"); pGetLastError _GetLastError = (pGetLastError) GetProcAddress(hKernel32, "GetLastError");
if (!_CreateFileA || !_GetFileSize || !_VirtualAlloc || !_ReadFile || !_VirtualFree || !_CloseHandle || !_FlushInstructionCache || !_GetCurrentProcess || !_GetLastError) { printf("[!] 动态加载 API 失败\n"); return 1; }
顺利完成混淆,之前结果
利用AI混淆之后结果,只剩了GetProcAddress和GetModuleHandleA,因为动态加载需要调用这两个API,如下所示,所以这两个API去不掉特征
fnVirtualAllocEx pVirtualAllocEx = GetProcAddress(GetModuleHandleA("KERNEL32.DLL"), "VirtualAllocEx");
该方法还有一个缺陷,虽然导入表中看不到了,但在字符串里面仍然能看到VirtualAlloc等APi,蓝队依然可以发现问题。该问题放到1-2进行解决
1-2:API 哈希隐藏字符串
前文可知,调用的字符串还是可以从exe当中识别到字符串,于是可以预先把敏感 API 名称和 DLL 名称用哈希算法转成固定数字,代码中只保留这些无意义的哈希值,运行时遍历系统内存中的字符串并实时计算哈希进行比对,从而彻底避免在文件中留下任何敏感字符串明文。
同样设置一个执行的闭环,完成后让AI自己用strings.exe来测试可用性。除了开始遇到点环境的问题,然后就一次性解决了。
目标:不干扰原先cpp文件逻辑的前提使用导入表混淆技能方案4,使得能达隐藏exe中的敏感字符串步骤:混淆后编译生成新loader.exe,使用dumpbin /IMPORTS loader.exe进行检查,看看有没有隐藏导入表成功,成功后才执行string.exe loader.exe | findstr CPP文件中混淆的API,看看有没有隐藏成功,成功后再执行下一步完成后运行一键转Shellcode工具.bat,将项目内fscan.exe放入后生成output.bin。然后执行loader.exe -h 127.0.0.1看是否能正常运行,直到能正常运行为止,一旦运行超时没有输出即刻重新混淆原始cpp,重新执行上述步骤
代码片段如下
// ========== 方案4: API哈希 — 隐藏导入表 ==========#define INITIAL_SEED 7
DWORD HASHA(const char* String) { SIZE_T Index = 0; DWORD Hash = 0; SIZE_T Length = lstrlenA(String);
while (Index != Length) { Hash += String[Index++]; Hash += Hash << INITIAL_SEED; Hash ^= Hash >> 6; } Hash += Hash << 3; Hash ^= Hash >> 11; Hash += Hash << 15; return Hash;}
测试结果:字符串成功隐藏
最终效果也是可以免杀
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:乌雲安全 云3 云3《AI自动化fscan二开》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。





![[前沿技术]GraphQLAPI安全测试](/images/random/titlepic/3.jpg)



评论