Meterpreter通讯分析

前几天玩鹤城杯遇到一个 Meterpreter 流量分析,花了一下午时间尝试恢复明文通讯无果,于是打算直接从源码的层面上剖析其通讯流程。

获取源码

我们可以直接在 Metasploit 的 Github 上获取 meterpreter 的源码,在 rapid7/metasploit-payloads

工作流程

寻找入口

由于我并没有足够的二进制经验,也只能依靠自己仅存的开发经验来找。猜测是 DLL 注入,定位入口 c/meterpreter/source/metsrv.c 中的 DllMain,并通过 LPVOID lpReserved,将参数 MetsrvConfig config 传入,其中参数包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// source/common/common_config.h
typedef struct _MetsrvSession
{
union
{
UINT_PTR handle;
BYTE padding[8];
} comms_handle; ///! Socket/handle for communications (if there is one).
DWORD exit_func; ///! Exit func identifier for when the session ends.
int expiry; ///! The total number of seconds to wait before killing off the session.
BYTE uuid[UUID_SIZE]; ///! UUID
BYTE session_guid[sizeof(GUID)]; ///! Current session GUID
} MetsrvSession;

typedef struct _MetsrvTransportCommon
{
CHARTYPE url[URL_SIZE]; ///! Transport url: scheme://host:port/URI
int comms_timeout; ///! Number of sessions to wait for a new packet.
int retry_total; ///! Total seconds to retry comms for.
int retry_wait; ///! Seconds to wait between reconnects.
} MetsrvTransportCommon;

typedef struct _MetsrvConfig
{
MetsrvSession session;
MetsrvTransportCommon transports[1]; ///! Placeholder for 0 or more transports
// Extensions will appear after this
// After extensions, we get a list of extension initialisers
// <name of extension>\x00<datasize><data>
// <name of extension>\x00<datasize><data>
// \x00
} MetsrvConfig;

看起来这里定义了会话以及通讯相关的参数,包括:会话 GUID、UUID、通讯地址、重试参数、退出方式等。

初始化设定

接下来调用了 Init()Init() 调用了 server_setup(),开始了整个通讯过程。这里我们不去关心他实现的细节,直奔协议处理部分。

调用了 remote_allocate() 分配了一个远程会话,其结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef struct _Remote
{
PConfigCreate config_create; ///! Pointer to the function that will create a configuration block from the curren setup.

Transport* transport; ///! Pointer to the currently used transport mechanism in a circular list of transports
Transport* next_transport; ///! Set externally when transports are requested to be changed.
DWORD next_transport_wait; ///! Number of seconds to wait before going to the next transport (used for sleeping).

MetsrvConfig* orig_config; ///! Pointer to the original configuration.

LOCK* lock; ///! General transport usage lock (used by SSL, and desktop stuff too).

HANDLE server_thread; ///! Handle to the current server thread.
HANDLE server_token; ///! Handle to the current server security token.
HANDLE thread_token; ///! Handle to the current thread security token.

DWORD orig_sess_id; ///! ID of the original Meterpreter session.
DWORD curr_sess_id; ///! ID of the currently active session.
char* orig_station_name; ///! Original station name.
char* curr_station_name; ///! Name of the current station.

char* orig_desktop_name; ///! Original desktop name.
char* curr_desktop_name; ///! Name of the current desktop.

PTransportCreate trans_create; ///! Helper to create transports from configuration.
PTransportRemove trans_remove; ///! Helper to remove transports from the current session.

int sess_expiry_time; ///! Number of seconds that the session runs for.
int sess_expiry_end; ///! Unix timestamp for when the server should shut down.
int sess_start_time; ///! Unix timestamp representing the session startup time.

PivotTree* pivot_sessions; ///! Collection of active Meterpreter session pivots.
PivotTree* pivot_listeners; ///! Collection of active Meterpreter pivot listeners.

PacketEncryptionContext* enc_ctx; ///! Reference to the packet encryption context.
} Remote;

这里包含了基本所有远程通讯相关的东西了,同时包含加密相关的 context。

我们看一下加密相关的 context 所对应的数据结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _Aes256Key
{
BLOBHEADER header;
DWORD length;
BYTE key[256/8];
} Aes256Key;

typedef struct _PacketEncryptionContext
{
HCRYPTPROV provider;
HCRYPTKEY aes_key;
int provider_idx;
BOOL valid;
Aes256Key key_data;
BOOL enabled;
} PacketEncryptionContext;

很明显 AES-256 具体的模式未知,我们继续往下看,下面设置了 expiry times,并建立了对应的 transport,每种 transport 对应的 handler 可以在 source/metsrv/server_transport_*.c 中找到,最终将充满函数指针的函数体赋值给 remote->transport。后面就是获取一些列基础信息和进行一系类初始化了,直到后面开始调用 remote->transport->transport_init() 以及进行对应的异常处理。所以我们可以直接去看对应的 server_transport_*.c

从 TCP 的 transport handler 开始 dive deeper

首先上游是调用了 transport_init(),其对应了 tcp 中的 configure_tcp_connection(),最终发现是 server_dispatch_tcp() 来负责处理数据包的接受,使用 packet_receive()接受数据包 同时调用 command_handle() 来处理接收到的指令。

先看下 packet_receive()

packet_receive()

首先我们列一下用到的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// guiddef.h
typedef struct _GUID {
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[ 8 ];
} GUID;
// source/common/common_core.h
typedef struct
{
BYTE xor_key[4]; // 4 bytes
BYTE session_guid[sizeof(GUID)]; // 16 bytes
DWORD enc_flags; // 4 bytes
DWORD length; // 4 bytes 这里定义的是除了 header 外其他部分所有长度
DWORD type; // 4 bytes
} PacketHeader;

其具体流程如下:

  1. 接受一个 PacketHeader 的大小 (经计算为 32 字节)
  2. 对 packet header 的 xor key 进行检查 (xor key没有零字节)
  3. 调用 xor_bytes() 对剩余的 packet header 进行解密
  4. 取出 payload length (header.length - sizeof(TlvHeader))
  5. 根据 payload length 循环接收数据,直到获取到完整的 payload
  6. 检查 payload 中的 GUID 是否为空或者与设置中的一致,符合条件则调用 decrypt_packet() 解密,不符合则继续找对应的 pivot
  7. 对 payload 部分做 xor 处理,根据 enc_flags 以及对应的 context 的完整性决定是否进行解密 其中 enc_flags 为 0x0 则不解密 0x1 使用 AES-256 (source/metsrv/packet_encryption.h) 同时根据下文得出,meterpreter 使用了 AES-256-CBC
  8. 如果上述流程均没有问题,则进入下一步

command_handle()

老样子,列一下我们需要的数据结构:

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
DWORD length;
DWORD type;
} TlvHeader;

typedef struct
{
TlvHeader header;
PUCHAR buffer;
} Tlv;

我们直接看 command_handle() 的源码,其流程如下:

  1. 首先调用了 packet_get_tlv_value_uint(),获取对应的 COMMAND_ID,后面经过一系列调用,最终来到 packet_find_tlv_buf(),这个函数会遍历除去 Packet Header 外所有的部分来找对应的类型。使用到了上面提到的 Tlv 数据结构。在处理 Header 的时候也会检查 type 是否包含 TLV_META_TYPE_COMPRESSED 这一个 flag,是的话则对 payload 部分进行 zlib 解压,同时去掉 flag 中的 TLV_META_TYPE_COMPRESSED,为上层提供透明访问。
  2. 找到 COMMAND_ID 后,根据这个 ID 去寻找对应的拓展,找到拓展后判断是否为 inline,是 inline 则调用 command_process_inline(), 不是 inline 的话就另开线程运行
  3. 这里直接看 command_process_inline(),后面是直接调用了 由第三步 command_locate_extension() 获取到的 handler,最后调用 packet_call_completion_handlers()

这里我们只关心协议,重点关心 packet_find_tlv_buf() 即可,可以看到,这个函数通过逐一读取 TLV 部分的 header,获取长度对整个 buffer 进行遍历。

对应 type 和 meta 的定义我们可以在 source/common/common_core.h 中找到。

先简单写个程序验证下(以刚开始上传 public key 为例):

对称密钥协商与解密

只找到协议大致的解析方式还是远远不够的,我们还需要后面对称密码协商的过程,因此继续往下看:

source/metsrv/packet_encryption.c 中的 request_negotiate_aes_key(): 先生成对应的 AES 密钥,再做使用之前获取的 RSA 公钥进行加密,我们不妨看下 public_key_encrypt() 函数,标准的 RSA 加密,直接使用 pycryptodome 就能解出来。

如果有 enc_flags 为 1,则使用 AES-256-CBC 进行解密,Packet Header 后面的 16 字节做 IV,IV 后面即加密的数据,该程序是解密后将解密后的数据放到了 Packet Header 后,为上层提供透明服务,这里同样,使用 pycryptodome 解出来即可。

模型整理

根据上述的分析,整理得如下模型:

对于所有 traffic,均有:

1
[4-bytes xor key][xor-ed payloads]

其中,这四字节的 xor key 是属于 Packet Header 部分的,后面的数据包括剩余的 Packet Header 以及后续的数据,因此,在处理流量时应当使用 xor key 对后续数据进行异或操作。

在进行异或操作后,整个 packet 大致可分为以下两种情况:

对于没有加密的情况(enc_flags = 0):

1
[32-bytes packet header][variable-length payload]

对于启用加密的情况(enc_flags = 1):

1
[32-bytes packet header][16-bytes IV][variable-length payload]

根据源码可知,目前只有一种加密,即 AES-256-CBC

其中 Packet Header:

1
[4-bytes xor key][16-bytes session GUID][4-bytes enc-flags][4-bytes payload-length(including 8-bytes tlv header length)][4-bytes packet-type]

Payload 部分:

Payload 部分可以由多个 TLV 单元组成,解析过程中,会根据 packet header 里的 length 部分,对整个 payload 进行逐一解析,直到找到其需要的数据。其中,每一个 TLV 单元组成如下:

1
[4-bytes length][4-bytes type][variable-length payload]

TLV_TYPE_SYM_KEY 的 TLV 单元:

1
[4-bytes length][4-bytes type][variable-length encrypted payload]

在协商对称加密密钥过程中,payload 使用了预设的 RSA 公钥进行了加密。

一次通讯过程

在 meterpreter 完成加载后,会发生以下过程:

  1. LHOST 生成 RSA 密钥对,将 core_negotiate_tlv_encryption 与对应的 RSA 公钥一起发送到 RHOST
  2. RHOST 生成 AES 密钥,使用接收到的 RSA 公钥进行加密(即 TLV_TYPE_SYM_KEY),发送回 LHOST
  3. 两端进行测试通信,无差错的话则以后都使用 AES-256-CBC 进行通讯,即 enc_flags 标志位置 1,在 Packet Header 后面插入 16 字节 IV,同时加密 payload 部分
  4. 直到断开连接

代码

这里我简单写了一个程序来对通讯过程进行解密,效果如下:

当前已实现功能:

  • 正确解析 Meterpreter 的数据包
  • 对 Meterpreter 数据包类型进行识别
  • 对 Meterpreter 数据包进行解密
  • 使用私钥提取 Meterpreter 的对称加密密钥
  • 识别 TLV Unit 的 Meta 与类型

代码可以从以下渠道获得: