免责声明
合法使用原则:文中提及的技术、工具或案例,仅用于授权范围内的安全测试、防御研究或合规技术分享,未经授权的网络攻击、数据窃取等行为均属违法,需承担法律责任。
风险自担与责任豁免:文章内容基于公开信息整理,不保证技术的准确性、完整性或适用性。读者需自行评估技术应用风险,若因不当使用导致任何法律后果或损失,均由使用者自行承担,与本公众号及作者无关。
法律管辖与提示:本公众号坚决拥护相关法律法规,反对任何危害网络安全的行为,读者需严格遵守法律法规。
为什么要使用动态API函数加载技术
-
在之前写的加载器中,都是直接使用Windows API函数来运行shellcode(静态导入API函数),但是在传统的 PE(Portable Executable)文件结构(如exe)中,当程序需要调用外部 dll(动态链接库)
中的函数时(Windows API函数就来自kernel32.dll
),会使用导入地址表(IAT); -
这个导入表存储了程序所调用的所有外部函数的地址(比如使用的Windows API函数)及其所在 dll
,这也使得攻击者写的恶意代码容易被安全工具和分析人员发现; -
操作系统在加载程序时会填充IAT中的每个条目,使其指向实际的函数地址,这种机制会暴露恶意软件的行为特征,很多杀软(比如Windows Defender)就会识别到程序的IAT中所包含的Windows API函数,因为敏感Windows API函数的使用通常表明了恶意行为,以此来识别恶意文件 -
将通过静态导入API函数的shellcode加载编译成exe后使用PE Bear工具打开,可以清晰地看见调用的Windows API函数
-
为了避免被安全工具轻易发现,可以采用动态API函数加载技术,这样可以使得在程序的导入表中不出现或者尽量少出现敏感的WindowsAPI函数名称,从而绕过杀软的查杀
动态API函数加载的基本实现(创建线程的方式执行shellcode)
原理
-
在代码层面,Windows API函数都是在 kernel32.dll
中获取的,所以此时目的就是动态获取所需要的Windows API函数的内存地址 -
可以使用 GetProcAddress
这个Windows API函数,通过指定的函数名,在kernel32.dll
中找到所需要的Windows API函数的内存地址
步骤
-
第一步:对于我们需要使用的Windows API函数需要重新定义一个新的函数指针类型,以 VirtualAlloc
函数举例(可以简单理解为重新定义VirtualAlloc
函数)
typedefLPVOID(WINAPI* newVirtualAlloc)(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
typedef
:这是C/C++的一个关键字,此处用于定义新的函数指针类型 LPVOID
:表示一个指针类型为 LPVOID
,与VirtualAlloc
函数原型的类型一致WINAPI
:这是Windows API的调用约定(通常为 __stdcall
),可以确保与Windows API函数正确交互*
:表示定义的是一个函数指针 newVirtualAlloc
:表示这个新函数指针类型的名称 ();
:括号内的内容是函数指针指向的函数的参数列表,与 VirtualAlloc
函数原型的参数保持一致-
注意:指针类型( LPVOID
)和函数的参数列表(();
)这两部分的内容可以通过查询微软官网描述的函数原型来得知;也可以在VS中按住键盘的Ctrl键
点击函数名,查看函数原型的语法,在VS中还能查看到Windows API的调用约定(WINAPI
)
-
第二步:动态获取Windows API函数VirtualAlloc的地址,并将其赋值给一个函数指针变量,以 VirtualAlloc
函数举例
newVirtualAlloc newVA = (newVirtualAlloc)GetProcAddress(GetModuleHandle(L"Kernel32.dll"),"VirtualAlloc");
newVA
:变量名,类型是前面定义好的 newVirtualAlloc
,这个变量用于存储VirtualAlloc
函数的地址GetModuleHandle(L"Kernel32.dll")
:通过 GetModuleHandle
这个Windows API函数指定模块(DLL)的句柄,这里指定的模块就是kernel32.dll
GetProcAddress
:通过这个Windows API函数获取 kernel32.dll
模块中指定函数的内存地址,第一个参数为模块的句柄(由GetModuleHandle
返回),第二个参数就是指定的函数名称("VirtualAlloc"
)(newVirtualAlloc)
:将 GetProcAddress
返回值由通用的指针类型强制转换为之前定义的特定函数指针类型newVirtualAlloc
-
第三步:将原代码(静态导入的代码)中涉及到的Windows API函数名全部转换为新的变量名,比如:
-
原来申请内存的代码是: LPVOID newsc = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
-
此时把 VirtualAlloc
改为得到newsc
:LPVOID newsc = newVA(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
完整代码
-
此时再将编译后的exe用PE-Bear打开,可以看到导入表中已经没有原来的 VirtualAlloc
等函数了,只有GetProcAddress
和GetModuleHandle
这两个
动态API函数加载的进阶实现(创建线程的方式执行shellcode)
-
通过上面的方法能够实现初步的动态API函数加载,但是导入表中仍然会出现 GetProcAddress
函数,所以还可以继续处理代码,让导入表中不显示GetProcAddress
函数,进一步隐藏 -
由于x64无法编写内联汇编代码,所以要另创一个asm文件来进行编写
-
将以下代码复制进新建的 GetInitializationOrderModuleList.asm
文件中
-
接着随后鼠标右键单击新建的asm文件,选择属性,在常规选项处将从生成中排除 设置为否,项类型设置为自定义生成工具
-
在自定义生成工具选项处,在命令行框输入 ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm
,在输出框输入$(IntDir)%(FileName).obj
-
打开项目属性,设置C/C++属性下代码生成模块的安全检查选项为禁用安全检查
-
然后在主程序中写入以下代码,拼接自己的shellcode即可
-
这个段代码对 GetProcAddress
这个Windows API函数进行了自实现
-
此时将编译后的exe用PE-Bear打开,可以看到导入表中也没有 GetProcAddress
函数了
原文始发于微信公众号(AegisGuard):绕过Windows Defender,当然你也可以!!!(理论+实践)
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论