一个用于绕过杀软加载fscan的加载器

admin 2025年5月17日23:43:31评论2 views字数 3704阅读12分20秒阅读模式

在学习fscan的源码时,偶然间查看到了这篇文章:技术幻影-揭开fscan免杀的面纱,才知道有CGO这个技术,于是学习了一下,并且想尝试一下反射式DLL注入能不能应用在CGo编译的DLL上,于是有了本项目FscanLoader,具体效果演示如下

微步云沙箱
一个用于绕过杀软加载fscan的加载器
vt
一个用于绕过杀软加载fscan的加载器
360核晶
一个用于绕过杀软加载fscan的加载器
火绒
一个用于绕过杀软加载fscan的加载器
 
Windows Defender
一个用于绕过杀软加载fscan的加载器

0x01 CGO简介

顾名思义,CGO就是C和Go的结合,是一种允许C和Go代码混合编写的技术,它提供了强大的互操作性,可以在Go代码中直接写C的代码,也可以在C代码中调用Go的函数,让C和Go进行无缝衔接。 一个简单的例子:

package main/*#include <stdio.h>extern void sayGo();static void sayC() {	sayGo();    printf("hello from Cn");}*/import "C"import "fmt"//export sayGofunc sayGo() {	fmt.Print("hello from gon")}func main() {	C.sayC()}
一个用于绕过杀软加载fscan的加载器

 

可以看到,我们既能在Go代码中通过C包调用C的函数,也能将Go的代码导出给C进行调用

并且最关键的是,Go原生的编译器只允许将Go代码编译成可执行文件,不能编译成动态链接库,但是CGO允许将Go代码编译成动态链接库,前提是需要配置相应的Go环境。 可以通过执行 go env 命令来查看

一个用于绕过杀软加载fscan的加载器

其中比较关键的几个环境变量:

  • AR: CGO中用于指定静态库的归档工具, 默认是GCC使用到的ar,用于生成.a格式的静态库文件
  • CC: CGO中用于C代码的编译器,默认为gcc
  • CGO_CFLAGS: CGO中用于传递给C编译器的标志,也就是参数
  • CGO_LDFLAGS: CGO中用于传递给链接器的标志
  • CGO_ENABLED: 用于表示是否启用CGO,为1表示启用,只有在启用状态下才能使用CGO进行编译
  • CXX: CGO中用于指定C++代码的编译器,默认为g++
  • CGO_CXXFLAGS: CGO中用于传递给C++编译器的标志

执行命令将上面示例代码编译成dll

go build -buildmode=c-shared -ldflags="-w -s" -o demo.dll main.go
一个用于绕过杀软加载fscan的加载器

在Windows下是不直接支持使用GCC环境进行编译的,所以如果要使用CGO的话,需要额外安装环境,一般是安装MinGW环境,然后使用gcc来编译(CGO貌似不支持MSVC编译环境,也有可能是我理解不够,欢迎各位师傅指导帮助)

0x02 反射式DLL注入简介

在Windows上,如果需要将一个dll加载,通常是通过调用LoadLibrary由操作系统来进行加载,要调用这个API的话,dll必须是以文件形式存储,并且操作系统加载dll时会触发各种回调函数,也容易被hook。 反射式DLL注入就是通过自己实现一个加载函数去模拟LoadLibrary的加载过程,在内存中将一个PE文件修复成一个映像文件,从而避免了dll文件落地和调用LoadLibrary 反射式DLL注入需要对PE文件的结构有一定的了解,可以参考PE文件结构详解,反射式dll加载相关介绍也有较多介绍,可以参考反射DLL注入原理解析,这里就不再赘述了,这里说下反射式注入中几个需要注意的点:

0x03 编译器优化问题

反射式加载函数要用到的变量必须位于栈上,其它文章将所需的字符串写在加载函数中,例如:

WCHAR kernel32[] = { L'K', L'e', L'r', L'n', L'e', L'l', L'3', L'2', L'.', L'd', L'l', L'l', L'�' };WCHAR ntdll[] = { L'n', L't', L'd', L'l', L'l', L'.', L'd', L'l', L'l', L'�' };WCHAR user32[] = { L'U', L's', L'e', L'r', L'3', L'2', L'.', L'd', L'l', L'l', L'�' };CHAR virtualAlloc[] = { 'V''i''r''t''u''a''l''A''l''l''o''c''�' };CHAR virtualProtect[] = { 'V''i''r''t''u''a''l''P''r''o''t''e''c''t''�' };CHAR rtladdFunctionTable[] = { 'R''t''l''A''d''d''F''u''n''c''t''i''o''n''T''a''b''l''e''�' };CHAR ntFlushInstructionCache[] = { 'N''t''F''l''u''s''h''I''n''s''t''r''u''c''t''i''o''n''C''a''c''h''e''�' };CHAR loadLibraryA[] = { 'L''o''a''d''L''i''b''r''a''r''y''A''�' };

如果此时开启了编译器优化的话,调试程序会发现有些字符串并不是直接写在栈空间,而是通过取固定偏移量地址中的值给xmm0寄存器,然后再放入栈中

一个用于绕过杀软加载fscan的加载器

这个固定偏移量已经是加载后的,如果在加载前使用的话就会因为内存访问异常而崩溃,即使不崩溃也不能取到正确的值,所以说需要禁止编译器对代码进行优化

0x04 获取PE基址问题

反射式加载时需要确定当前PE文件的基址,然后通过PE基址去进行后续的解析和修复。 获取PE基址的一般思路就是先获取eip/rip寄存器的值,然后向前遍历,直到遇到PE文件的DOS魔数和PE魔数。 在64位下,获取当前rip寄存器的值,可以直接通过当前函数地址来获取,例如:

 

int main() {	ULONG_PTR address = (ULONG_PTR)main;	cout << "address: " << address << endl;return 0;}
一个用于绕过杀软加载fscan的加载器

取函数地址对应的汇编代码为:

48 815 E6 FF FF FF    lea rdx, [rip - 0x1a]

也就是取rip前26个字节的地址,刚好就是main函数的入口地址,这是因为64位下的lea指令支持相对寻址,所以64位下能直接通过函数名称获取当前函数的地址。 而这段程序在32位下的汇编代码为:

一个用于绕过杀软加载fscan的加载器

取函数地址对应的汇编代码为:

C7 45 FC 00 10 F6 00    mov dword ptr [ebp - 4], 0x00F61000

可以看到是直接将一个硬编码的地址进行赋值,这个硬编码的地址是加载之后才正确的值,原因是32位下lea指令不支持相对寻址,所以32位下反射式注入不能直接通过函数名来获取eip寄存器的值,而是通过以下汇编代码来获取eip的值:

ULONG_PTR address;__asm {call $ + 5	pop eax	mov address, eax}

简单解释下这段汇编代码:

  • call $ + 5 : $表示当前指令的地址(eip),$ + 5表示eip+5, 由于call指令本身占5字节,所以相当于什么都不做,但是会把下一条指令的地址压入到栈中
  • pop eax: 从栈中弹出值给eax,此时eax中存储的是eip寄存器的值
  • mov address, eax: 将eax的值赋值给address变量,此时address变量存储的就是eip的值了

eip/rip是特殊寄存器,不能直接访问,所以需要通过这种方式间接获取

一个用于绕过杀软加载fscan的加载器

由于fscan编译的dll体积比较大,所以说不能简单的通过DOS魔数和PE魔数来寻找PE基址,需要加入一些其它的自定义特征来避免找错PE。 在PE文件中,IMAGEDOSHEADER结构体中除了emagic和elfanew以外的成员都是无关紧要的,可以将其更改成自定义特征值

一个用于绕过杀软加载fscan的加载器

也可以修改IMAGEDOSSTUB结构体,它里面所有成员都是可以随意修改的,但是IMAGEDOSSTUB结构体的大小并不是固定的

一个用于绕过杀软加载fscan的加载器

0x05 加载器

用于加载fscan.dll的加载器参考了CS Shellcode的实现逻辑,具体可参考CobaltStrike Shellcode分析,解析TEB结构体动态查找所需的API地址,然后调用API将fscan.dll写入内存,然后查找反射式加载函数,完成fscan.dll的加载

0x06 后话

Go程序由Go Runtime来管理,这次尝试将Go编译的dll进行反射式加载,发现也能成功加载,并不会道导致Go Runtime的异常。 loader不是只能用来加载fscan.dll,稍作变更也可用于加载其他反射式dll,同样fscan.dll也不一定要通过该loader来加载,其他的例如白加黑也能加载。

原文始发于微信公众号(安全的黑魔法):一个用于绕过杀软加载fscan的加载器

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月17日23:43:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一个用于绕过杀软加载fscan的加载器https://cn-sec.com/archives/4076624.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息