攻防演练新利器:Suo5工具的深度分析与检测技巧

admin 2025年5月24日16:07:08评论0 views字数 7854阅读26分10秒阅读模式
背景
在之前几次攻防演练中,经常遇到红队利用suo5工具来搭建隧道的场景,例如哥斯拉插件一键注入suo5内存马等。
因此,希望通过分析该工具,结合zeek,对其进行检出。
简介
suo5 是一个高性能 HTTP 隧道代理工具,它基于双向的 Chunked-Encoding 构建,旨在通过常规的网络通信协议,绕过网络限制,实现数据的隐蔽传输。其工作原理基于客户端与服务端之间的特殊通信协议,结合 SOCKS5 代理,实现对网络请求的转发和隧道化。
原理

攻防演练新利器:Suo5工具的深度分析与检测技巧

如果有一个webshell,想要进一步去探索内网,就可以借助该webshell搭建一个socks5代理,将流量通过该webshell传递到内网。
首先需要上传一个服务端的jsp文件到目标server中,也就是上图中的proxybridge,随后在本机的客户端连接该jsp文件,连接成功后,会在本地开启一个socks5服务(服务端不会),借助该服务访问更内层的server。也就是说,图中socks5handler需要实现socks5服务,并开启监听,如果有请求,就将信息封装为http内容发送给proxybridge,proxybridge解析好之后,与目标机器进行连接,并按照同样的方式将response最终返回给用户。
前面的功能reGeorg和Neo-reGeorg已经实现了,suo5与他们的区别在于他是基于Chunked-Encoding来构建的:

攻防演练新利器:Suo5工具的深度分析与检测技巧

解决了前者延迟较高的问题。
分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许 HTTP 由应用服务器发送给客户端应用(通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在 HTTP 协议 1.1 版本(HTTP/1.1)中提供。
通常,HTTP应答消息中发送的数据是整个发送的,Content-Length消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。通常数据块的大小是一致的,但也不总是这种情况。
整体架构
suo5由客户端和服务端两部分组成:
客户端:在本地运行,开启一个 SOCKS5 代理服务器,监听本地指定端口(默认127.0.0.1:1111)。本地应用程序(如浏览器)通过配置 SOCKS5 代理,将网络请求发送给 suo5 客户端。
服务端:部署在目标服务器上,用于接收来自客户端的特殊 HTTP 请求,解析并处理后,与目标服务器建立实际的网络连接。
通信模式
suo5支持全双工和半双工两种通信模式,以适应不同的网络环境和限制。
全双工模式(Full-Duplex)
长连接:客户端与服务端之间建立 HTTP/HTTPS 长连接,能够同时进行数据的发送和接收。
效率较高:适用于服务端允许长时间保持连接的情况,数据传输效率较高。
半双工模式(Half-Duplex)
短连接:每次需要发送或接收数据时,客户端都需要与服务端建立新的 HTTP/HTTPS 连接。
场景更多:适用于服务端或网络环境不支持长连接的情况,但相对效率较低。
通信流程
  • 检测ConnectMode:客户端向服务端发送一个HTTP请求,来确定采用全双工还是半双工模式。
  • 测试隧道:通过客户端本地的socks5代理,发送一条测试请求到服务端,并开始建立连接。
  • 数据传输
  • 关闭连接
如何连接
本地虚拟机使用的环境为tomcat9:

攻防演练新利器:Suo5工具的深度分析与检测技巧

在本机(客户端)运行suo5文件:

攻防演练新利器:Suo5工具的深度分析与检测技巧

将suo5.jsp上传到目标server(虚拟机)中,连接即可:

攻防演练新利器:Suo5工具的深度分析与检测技巧

如图所示即为连接成功,全双工模式。
查看本地1111端口:

攻防演练新利器:Suo5工具的深度分析与检测技巧

google开启socks代理,访问github:

攻防演练新利器:Suo5工具的深度分析与检测技巧

至此,代理连接成功。
代码分析
接下来结合代码进行分析,聚焦于握手和建立连接的阶段。
版本为最新的1.3.1。
目录结构
首先是目录结构:

攻防演练新利器:Suo5工具的深度分析与检测技巧

assets目录下包含服务端需要部署的代码(后门脚本),支持.NET、Java和PHP3种语言 。
ctrl中的代码实现了客户端的核心控制逻辑,包括配置解析、网络通信、数据处理等。
netrans目录下包含了网络传输相关的辅助模块,包括自定义协议的实现、读写缓冲等。
CheckConnectMode
客户端
直接在CheckConnectMode函数里下断点:

攻防演练新利器:Suo5工具的深度分析与检测技巧

首先会随机一个数作为body体的数据长度,并确保长度大于32,接下来设置requestheader:

攻防演练新利器:Suo5工具的深度分析与检测技巧

Referer忽略,UA可能是一个静态特征,往前找config:

攻防演练新利器:Suo5工具的深度分析与检测技巧

继续往前:

攻防演练新利器:Suo5工具的深度分析与检测技巧

如果不指定UA,默认值为:Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3,到服务端代码中(suo5.jsp)能够看到,对UA存在校验,如果不一致,直接退出:

攻防演练新利器:Suo5工具的深度分析与检测技巧

因此,如果要修改UA特征,需要在客户端和服务端同步修改。
设置好UA后,需要赋值content-type:

攻防演练新利器:Suo5工具的深度分析与检测技巧

这里的键值对是以常量的形式定义在handler.go代码中:

攻防演练新利器:Suo5工具的深度分析与检测技巧

最后是body的部分,通这里的随机长度为260,body为随机值,将数据写入通道,再进入reader,最终由req发送:

攻防演练新利器:Suo5工具的深度分析与检测技巧

查看wireshark抓取的报文:

攻防演练新利器:Suo5工具的深度分析与检测技巧

content-type,UA以及请求体均和代码分析的一致,104是260的16进制,0代表数据到此结束。
服务端
在suo5.jsp中,接收到第一个http请求,会直接进入tryFullDuplex函数:

攻防演练新利器:Suo5工具的深度分析与检测技巧

直接从请求体中读取前 32 个字节,并将读取的数据写入response,返回给客户端:

攻防演练新利器:Suo5工具的深度分析与检测技巧

抓取报文如下:

攻防演练新利器:Suo5工具的深度分析与检测技巧

完成第一次交互之后,根据duration,来决定使用全双工还是半双工模式进行后续的数据传输(如果响应时间在3s内,说明请求没有被缓存,采用全双工模式):

攻防演练新利器:Suo5工具的深度分析与检测技巧

TestTunnel

攻防演练新利器:Suo5工具的深度分析与检测技巧

客户端
书接前文,在CheckConnectMode之后,就确定好了通信模式:

攻防演练新利器:Suo5工具的深度分析与检测技巧

接着启动socks5代理服务器srv,默认在本地1111端口:

攻防演练新利器:Suo5工具的深度分析与检测技巧

创建 socks5Handler 实例来处理 SOCKS5 连接,再开一个协程,启动 SOCKS5 服务器,当有新连接时,调用 ClientEventHandler Handle方法,该方法又会调用 socks5Handler.Handle 方法处理连接:

攻防演练新利器:Suo5工具的深度分析与检测技巧

即:

攻防演练新利器:Suo5工具的深度分析与检测技巧

这里到了重点的testTunnel函数:

攻防演练新利器:Suo5工具的深度分析与检测技巧

按照前面的分析,会创建一个goroutine,来处理此次连接,跟进代码:

攻防演练新利器:Suo5工具的深度分析与检测技巧

执行完client.Dial后,会起一个goroutine,是从gofunc()中的Serve开始的:

攻防演练新利器:Suo5工具的深度分析与检测技巧

最终Serve进入另一个goroutine:

攻防演练新利器:Suo5工具的深度分析与检测技巧

即ClientEventHandler.handle:

攻防演练新利器:Suo5工具的深度分析与检测技巧

继续到Inner.Handle,即socks5Handler.Handle:

攻防演练新利器:Suo5工具的深度分析与检测技巧

执行到handleConnect,这里CmdConnect是常量,重点跟handleConnect:

攻防演练新利器:Suo5工具的深度分析与检测技巧

攻防演练新利器:Suo5工具的深度分析与检测技巧

首先是一个8位的随机id,随后通过buildBody来生成具体的请求内容:

攻防演练新利器:Suo5工具的深度分析与检测技巧

先算内层的newActionCreate:

攻防演练新利器:Suo5工具的深度分析与检测技巧

状态码ActionCreate为0x00,剩下的分别是随机id,host和port,并组合成一个字典:

攻防演练新利器:Suo5工具的深度分析与检测技巧

有了参数m的值,下面到buildBody:

攻防演练新利器:Suo5工具的深度分析与检测技巧

netrans.NewDataFrame(marshal(m)).MarshalBinary(),先看marshal(m),再看netrans.NewDataFrame(xx),最后是xx.MarshalBinary().
先进行简单的序列化,[键长度1字节][键内容][值长度4字节][值内容][键长度1字节][键内容][值长度4字节][值内容] ...的形式:

攻防演练新利器:Suo5工具的深度分析与检测技巧

生成一个新的数据帧,其中格式为数据长度,随机数和数据内容:

攻防演练新利器:Suo5工具的深度分析与检测技巧

加密代码
最后是MarshalBinary:

攻防演练新利器:Suo5工具的深度分析与检测技巧

加密的逻辑是将body长度以大端方式写入前四个字节,第五个字节为秘钥(即NewDataFrame中生成的随机值),后面的每个字节都与秘钥进行异或操作。
返回的result最终作为body体发送给server:

攻防演练新利器:Suo5工具的深度分析与检测技巧

下面来过一遍加密流程:

攻防演练新利器:Suo5工具的深度分析与检测技巧

这里的id为G7Xbi6lH,h为127.0.0.1,p为0,抓包如下:

攻防演练新利器:Suo5工具的深度分析与检测技巧

00000119  33 32 0d 0a                                        32..0000011D  00 00 00 2d 65 67 04 06  65 65 65 64 65 67 0c 01   ...-eg.. eeedeg..0000012D  65 65 65 6d 22 52 3d 07  0c 53 09 2d 64 0d 65 65   eeem"R=. .S.-d.ee0000013D  65 6c 54 57 52 4b 55 4b  55 4b 54 64 15 65 65 65   elTWRKUK UKTd.eee0000014D  64 55                                              dU0000014F  0d 0a                                              ..
可以写一个简单的python解密脚本:
import structimport iodef hex_str_to_bytes(hex_str_list):    hex_str = ' '.join(hex_str_list)    hex_str = hex_str.replace('n'' ').replace('r'' ').replace('t'' ')    byte_arr = bytearray()    for byte_str in hex_str.strip().split():        byte_arr.append(int(byte_str, 16))    return bytes(byte_arr)def unmarshal(data):    header = data[:5]    length = int.from_bytes(header[:4], byteorder='big')    xor_key = header[4]    print(f"Data length: {length}")    print(f"XOR key: 0x{xor_key:02X}")    if length > 1024 * 1024 * 32:        raise ValueError("invalid length")    encrypted = data[5:5+length]    decrypted = bytes([b ^ xor_key for b in encrypted])    print(f"Decrypted data: {decrypted.hex()}")    result = {}    i = 0    while i < len(decrypted) - 1:        key_len = decrypted[i]        i += 1        print(f"Key length: {key_len}")        if key_len < 0 or i + key_len >= len(decrypted):            raise ValueError("key length error")        key = decrypted[i:i+key_len].decode()        i += key_len        print(f"Key: {key}")        if i + 4 >= len(decrypted):            raise ValueError("value length error")        value_len = int.from_bytes(decrypted[i:i+4], byteorder='big')        i += 4        print(f"Value length: {value_len}")        if value_len < 0 or i + value_len > len(decrypted):            raise ValueError("value error")        value = decrypted[i:i+value_len]        i += value_len        print(f"Value: {value.hex()}")        result[key] = value    return resulthex_data_list = ['33 32 0d 0a','00 00 00 2d 65 67 04 06 65 65 65 64 65 67 0c 01','65 65 65 6d 22 52 3d 07 0c 53 09 2d 64 0d 65 65','65 6c 54 57 52 4b 55 4b 55 4b 54 64 15 65 65 65','64 55','0d 0a']data = hex_str_to_bytes(hex_data_list)separator = data.find(b'rn')if separator != -1:    data = data[separator+2:]else:    passif data.endswith(b'rn'):    data = data[:-2]try:    result = unmarshal1(data)    for k, v in result.items():        try:            v_str = v.decode('utf-8')            print(f"{k}{v_str}")        except UnicodeDecodeError:            v_hex = v.hex()            print(f"{k}: 0x{v_hex}")except Exception as e:    print(f"Error during decoding: {e}")
解密结果如下:

攻防演练新利器:Suo5工具的深度分析与检测技巧

另外,这里针对全双工和半双工模式,加密代码是一样的,唯一的区别在于content-type,如果是全双工:

攻防演练新利器:Suo5工具的深度分析与检测技巧

如果是半双工:

攻防演练新利器:Suo5工具的深度分析与检测技巧

服务端
简单过一下服务端返回response的逻辑:

攻防演练新利器:Suo5工具的深度分析与检测技巧

以全双工为例,根据content-type进行判断,进到processDataBio:

攻防演练新利器:Suo5工具的深度分析与检测技巧

首先设置header,接着写response:

攻防演练新利器:Suo5工具的深度分析与检测技巧

加密方式和客户端是一致的,看下newStatus:

攻防演练新利器:Suo5工具的深度分析与检测技巧

解密:

攻防演练新利器:Suo5工具的深度分析与检测技巧

特征分析
UA
默认UA为:
Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.1.2.3
Header
CheckConnectMode发送的第一个http请求中,content-type为application/plain

testTunnel发送的第二个http请求中:

  • 全双工:content-type为application/octet-stream

  • 半双工:content-type为application/x-binary

response均包含:

  • Transfer-Encoding: chunked

  • X-Accel-Buffering: no

Body
在CheckConnectMode过程中,request发送一个大于32字节的随机值,response认证后,返回requestbody的前32字节。
加密方式
采用随机的密钥进行异或加密
Else
通过如下特征码进行特定的通信行为:
0x00:创建连接(ActionCreate)
0x01:传输数据(ActionData)
0x02:关闭连接(ActionDelete)
0x03:心跳包(ActionHeartbeat)
脚本编写
Roadblocks
request和response识别错误
在编写脚本过程中,发现无法匹配到testTunnel阶段的日志,修改代码如下:

攻防演练新利器:Suo5工具的深度分析与检测技巧

攻防演练新利器:Suo5工具的深度分析与检测技巧

执行结果表明,第一个HTTP请求被正确标记为check_mode,第二个HTTP请求未被重新分析,而是继承了之前响应的response标签,出现了错误。
进一步debug发现是http_message_done的触发有问题,长连接的状态下,response的event可能在request的evnet之前触发,导致在匹配request的body体时为空,检测失败:

攻防演练新利器:Suo5工具的深度分析与检测技巧

解决方案:通过trans_depth来区分一个长连接中的不同流。
异或解密
从检测的阶段来看,解密的部分有两块,分别为testTunnel的requestbody和testTunnel的responsebody,考虑到requestbody的长度更长,存储更多数据,可能会消耗更多性能,这里依赖于responsebody来作检测。
但是在zeek中直接进行异或计算来解密较为困难,并且性能开销相对较大。
这里对加密算法和解密的数据作进一步分析:
在testTunnel的http请求中,response返回的body内容是固定的:

攻防演练新利器:Suo5工具的深度分析与检测技巧

即,键值对的键为s,值为状态码,这里是actioncreate,值为00,所以response的body解密后的内容恒为s:0x00.在加密方式不变的情况下,密文数据的长度也是固定的,即12字节:

攻防演练新利器:Suo5工具的深度分析与检测技巧

在这12个字节中,组成如下:

攻防演练新利器:Suo5工具的深度分析与检测技巧

目前的特征为:
  • body长度为12
  • 前四字节为00 00 00 07
结合解密后键长度(1字节):0x01,键内容('s'):0x73,值长度(4字节): 0x00 0x00 0x00 0x01,值内容(0x00): 0x00
即:
  • 第6个字节密钥异或等于0x01(键长度)
  • 第7个字节与密钥异或等于0x73('s')
  • 第8-11个字节与密钥异或等于0x00, 0x00, 0x00, 0x01(值长度)
  • 第12个字节与密钥异或等于0x00(值)
再:
  • 任何值与自身异或等于0(A ^ A = 0)
  • 任何值0异或等于自身(A ^ 0 = A)
可知:第8,9,10,12字节的值等于异或密钥(第5个字节),第6个字节的值和第11个字节的值相同(均为01XOR密钥)
最终得出如下特征:
  • body长度为12
  • 前四字节为00 00 00 07
  • 第6字节=第11字节
  • 第8,9,10,12字节的值=第5字节

攻防演练新利器:Suo5工具的深度分析与检测技巧

性能开销大
V1
主要的性能开销是由于如下几个原因:
  • 异或算法进行加解密
  • 使用了eventhttp_entity_data,每次遇到body都会触发
  • 存储数据量较大
解决方案:
  • 直接检测特定位置的值
  • 添加严格的前置匹配条件,不满足直接过滤或者删除session
  • 先通过body长度筛选,如果满足条件,只储存需要匹配的最少字节数(32)
V2
最终采用如下方案:
  • 只检测stage2的流量(相比stage1更特殊,足够作为判断依据)
  • filter先检测最uncommon的部分
  • 只检测response中前12字节是否符合特征(如果检测长度为12,需要接收到所有的responsebody,如果body过大很影响效率)
检测逻辑
V1
stage 1

攻防演练新利器:Suo5工具的深度分析与检测技巧

stage 2

攻防演练新利器:Suo5工具的深度分析与检测技巧

V2

攻防演练新利器:Suo5工具的深度分析与检测技巧

检测效果
先开启zeek,监听网卡。
然后启动suo5client,修改默认ua,禁用心跳包,禁用gzip,修改默认本地端口,修改默认POST方法(改为GET):

攻防演练新利器:Suo5工具的深度分析与检测技巧

两个阶段均可检出。
部分debug附录
攻防演练新利器:Suo5工具的深度分析与检测技巧
表明在第一个http请求过程中,http_message_done事件,response先于request触发

原文始发于微信公众号(Crush Sec):攻防演练新利器:Suo5工具的深度分析与检测技巧

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月24日16:07:08
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   攻防演练新利器:Suo5工具的深度分析与检测技巧https://cn-sec.com/archives/4094123.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息