绕过检测之Executor内存马浅析(内存马系列篇五)

写在前面

前面已经从代码层面讲解了Tomcat的架构,这是内存马系列文章的第五篇,带来的是Tomcat
Executor类型的内存马实现。有了前面第四篇中的了解,才能更好的看懂内存马的构造。

前置

什么是Executor

Executor是一种可以在Tomcat组件之间进行共享的连接池。

我们可以从代码中观察到对应的描述:

The Executor implementations provided in this package implement
ExecutorService, which is a more extensive interface. The ThreadPoolExecutor
class provides an extensible thread pool implementation. The Executors class
provides convenient factory methods for these Executors.
Memory consistency effects: Actions in a thread prior to submitting a
Runnable object to an Executor happen-before its execution begins, perhaps
in another thread.

image-20220912172909884.png

Executes the given command at some time in the future. The command may
execute in a new thread, in a pooled thread, or in the calling thread, at
the discretion of the Executor implementation.
Params: command – the runnable task
Throws: RejectedExecutionException – if this task cannot be accepted for
execution
NullPointerException – if command is null

对于他的作用,允许为一个Service的所有Connector配置一个共享线程池。

在运行多个Connector的状况下,这样处理非常有用,而且每个Connector必须设置一个maxThread值,但不希望Tomcat实例并发使用的线程最大数永远与所有连接器maxThread数量的总和一样高。

这是因为如果这样处理,则需要占用太多的硬件资源。相反,您可以使用Executor元素配置一个共享线程池,而且所有的Connector都能共享这个线程池。

分析流程

通过上篇文章的分析我们知道,

在启动Tomcat的时候首先会。

调用启动类,并传入参数start预示着Tomcat启动:

image-20220912175409785.png

这里调用start方法进行相关配置的初始化操作,

一直走到了org.apache.catalina.startup.Catalina类中load方法中调用了。this.getServer().init()方法进行Server的初始化操作,

即调用了LifecycleBase#init方法,进而调用了initInternal方法,即来到了他的实现类StandardServer#initInternal中来了。

image-20220912180117577.png

上篇中也提到过,将会循环的调用所有service的init方法,进而调用了StandardService#initInternal方法进行初始化,调用了Engine#init方法,因为没有配置Executor,所以在初始化的时候不会调用他的init方法,之后再调用mapperListener.init()进行Listener的初始化操作,在获取了所有的connector之后将会循环调用其init方法进行初始化。

image-20220912180558740.png

在初始化结束之后将会调用start方法

image-20220912193917307.png

即调用了Bootstrap#start方法,进而调用了Server.start方法

来到了StandardService#startInternal方法,紧跟着调用了上面调用了Init方法的start方法,成功启动Tomcat。

正文

接下来我们来分析一下为什么选用Executor来构造内存马,和如构造内存的流程。

分析注入方式

在成功开启了Tomcat之后,我们可以在Executor中的execute方法中打下断点,

image-20220912195616660.png

之后运行访问8080端口

在前面那一篇文章中我们知道Acceptor是生产者,而Poller是消费者,

在执行Endpoint.start()会开启Acceptor线程来处理请求。

在其run方法中存在

  1. 运行过程中,如果Endpoint暂停了,则Acceptor进行自旋(间隔50毫秒);

  2. 如果Endpoint终止运行了,则Acceptor也会终止;

  3. 如果请求达到了最大连接数,则wait直到连接数降下来;

  4. 接受下一次连接的socket。

这一步己经在运行Tomcat容器的时候已经进行了,

在我们访问Tomcat的页面之后将会创建一个线程,并调用target属性的run方法,这里的target就是Poller对象(消费者)。

image-20220912201105821.png

即调用了NioEndpoint$Poller#run方法,跟进

public void run() {
    while(true) {
        boolean hasEvents = false;

        label58: {
            try {
                if (!this.close) {
                    hasEvents = this.events();
                    if (this.wakeupCounter.getAndSet(-1L) > 0L) {
                        this.keyCount = this.selector.selectNow();
                    } else {
                        this.keyCount = this.selector.select(NioEndpoint.this.selectorTimeout);
                    }

                    this.wakeupCounter.set(0L);
                }

                if (!this.close) {
                   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值