Ask Your Question
2

Calling existing TLS Lua dissector on TLS tunneled traffic gives wrong dissection.

asked 2024-07-16 07:07:56 +0000

Linxiao Yu gravatar image

updated 2024-07-22 08:49:52 +0000

Hi, everyone. I'm working on a Lua dissector for a proxy protocol called Trojan, which use TLS to tunnel HTTPS traffic (i.e., there is a TLS layer protecting the inner HTTPS traffic). I could decrypt the outer TLS and do some dissection. I'm sorry that it seems to requires 60 points to upload pictures, so I'll try my best to describe my problem.

Before I call the existing TLS dissector to dissect inner HTTPS payload, I successfully decrypt the outer TLS, and add the inner payload to the dissection tree. The dissection tree looks like this:

... Lower Layers...
Tranport Layer Security <---- The outer TLS layer
Trojan Protocol <---- The root of Trojan protocol tree
++++ Trojan Tunneled Data [...]: 160303007a020... <---- The inner HTTPS payload

As you could see, the '160303...' is the first bytes of a typical TLS packet. Now I want to use the existing TLS dissector to dissect inner payload by

Dissector.get('tls'):call(remaining_buffer, pktinfo, tree)

The first several TLS handshake packets are dissected correctly, like this:

... Lower Layers...
Tranport Layer Security <---- The outer TLS layer
Trojan Protocol <---- The root of Trojan protocol tree
++++ Trojan Tunneled Data [...]: 160303007a020... <---- The inner HTTPS payload
++++++ Tranport Layer Security <---- The inner TLS layer
++++++++ TLS 1.3 Record Layer: Handshake Protocol: Client Hello
++++++++ ...More TLS dissection info...

However, when the inner payload is TLS Application Data, the whole dissection seems not to work, the dissection tree becomes:

... Lower Layers...
Tranport Layer Security <---- The outer TLS layer
++++ Application Data <---- No further decoding, even the previous Trojan layer disappears.

Furthermore, I found that these Application Data packets are even not decrypted. However, if I don't call the built-in TLS dissector, the inner payload is decrypted, which seems weird to me.

UPDATE 1: I find that I could share related images about this problem through Github, I have written a related markdown file attached with pictures. If you are interested, please check it for better details. Sorry for that inconvenience.:)

UPDATE 2: I'm using Wireshark Version 4.3.0 (v4.3.0rc1-256-g49164027c622), built from the source following the official instructions. I'm using Windows 11, and Lua version is 5.4.2.

UPDATE 3: If you want to reproduce the issue, I have upload the related Trojan .pcapng file and the Lua file. I found that the built-in HTTPS dissector will override my dissector, i.e., the code

 DissectorTable.get("tcp.port"):add(port, trojan)   
 DissectorTable.get("tls.port"):add(port, trojan)

will not work unless I disable the HTTPS heuristic dissection. Now this issue has been solved by a quicker fix by adding:

    DissectorTable.get("tls.alpn"):add("h2", trojan)
    DissectorTable.get("tls.alpn"):add("http/1.1", trojan)

UPDATE 4: Now the inner TLS dissection could be used properly, thanks to @johnthacker. I have released the updated code on Github. However, the actual HTTP payload is not handled properly (see the comment for more information), which may still be the ... (more)

edit retag flag offensive close merge delete

Comments

TLS tunneled within TLS is difficult. However, first things, can you please give the version of Wireshark you are using, e.g. copied from Help -> About Wireshark?

johnthacker gravatar imagejohnthacker ( 2024-07-16 21:52:59 +0000 )edit

@johnthacker Thanks for your reply! I'm using Wireshark Version 4.3.0 (v4.3.0rc1-256-g49164027c622), which is built from source following the official instruction. Now I find that I could share my related images through Github, and I have updated my question to add more information.

Linxiao Yu gravatar imageLinxiao Yu ( 2024-07-17 01:52:34 +0000 )edit

What causes the HTTP2 dissector to be called is the ALPN, because there's no option to ignore that and prefer the port based table. So the only thing you need to do to have your dissector called is to comment out the registration dissector_add_string("tls.alpn", "h2", http2_handle);

However, for your testing purposes you could just register your dissector to that ALPN in the Lua code:

DissectorTable.get("tls.alpn"):add("h2", trojan)

That doesn't solve the TLS tunnelling issue but it's a quicker way of getting your dissector called despite the bogus ALPN

johnthacker gravatar imagejohnthacker ( 2024-07-17 17:45:15 +0000 )edit

1 Answer

Sort by ยป oldest newest most voted
0

answered 2024-07-18 00:03:37 +0000

johnthacker gravatar image

However, if I don't call the built-in TLS dissector, the inner payload is decrypted, which seems weird to me.

This is a sign that the dissector is using the new keys from the second handshake and replacing the earlier keys, and trying them on the outer TLS layer, which doesn't work. The problem is that the TLS session is stored in a conversation created using the addresses, ports, and port types, none of which change from the interposing dissector. You can verify this by having the TLS dissector write to a log file

You can work around this by artificially changing the port type and then changing it back, which will cause it to create a different session for the tunneled protocol:

    local save_port_type = pktinfo.port_type
    pktinfo.port_type = _EPAN.PT_NONE -- any value other than _EPAN.PT_TCP

    Dissector.get("tls"):call(tvb, pktinfo, tunnel_tree)

    pktinfo.port_type = save_port_type

You will then need to deal with a second issue, reassembling some of the inner TLS records that are split across multiple packets. The pinfo (or pktinfo in Lua) struct has a member can_desegment that has to be at least 1 for desegmentation to be allowed in a dissector. It is decremented by 1 each time a dissector is called. Dissectors which know that defragmentation is possible should set it to 2, so that it has the value 1 in the next dissector:

   local save_port_type = pktinfo.port_type
    pktinfo.port_type = _EPAN.PT_NONE
    local save_can_desegment = pktinfo.can_desegment
    pktinfo.can_desegment = 2 -- should really be pktinfo.saved_can_desegment

    Dissector.get("tls"):call(tvb, pktinfo, tunnel_tree)

    pktinfo.port_type = save_port_type
    pktinfo.can_desegment = save_can_desegment

For tunneling purposes, there's another value in pinfo called saved_can_desegment, which tunneling dissectors like Socks use. That has the value in the previous dissector, and is used for tunneling dissectors that shouldn't decrement the value, but also want the value in the previous layer (in case, for example, some error means that TCP didn't think that desegmentation was possible.) That's what should be used, except it's not exposed to Lua currently. I'll fix that.

Do that, and add DissectorTable.get("tls.alpn"):add("h2", trojan) to your enableDissector() registration (to override the value for the ALPN), and it will work.

edit flag offensive delete link more

Comments

Thanks for your suggestions, it works! Now I could view the proper dissected inner TLS payload. However, when I successfully decrypt the inner TLS content (i.e., the HTTP payload), and want to handle the inner HTTP payload by calling:

-- Previous TLS dissector call
pktinfo.can_desegment = save_can_desegment

Dissector.get("http-over-tls"):call(tvb, pktinfo, tunnel_tree)

the HTTP (no matter HTTP/1.1 and HTTP/2) packets are not handled correctly. For HTTP/1.1, there are many Continuation records; and for HTTP/2, there are many Ignored Unknown Record or Malformed Frame. The related packet file are put on HTTP/1.1 pcap and HTTP/2 pcap, you could check it if it's convenient.:)

By reading Wireshark user guide, I understand it should be the reassembly issue again. So according to your code, I tried the following but failed:

    local save_port_type = pktinfo.port_type
    pktinfo.port_type = _EPAN.PT_NONE
    local save_can_desegment = pktinfo ...
(more)
Linxiao Yu gravatar imageLinxiao Yu ( 2024-07-18 09:44:28 +0000 )edit

@johnthacker I have upload related markdown with images, the related Lua code and related capture files (HTTP/1.1 and HTTP/2) on Github, and these captures are injected with TLS secrets for both outer TLS and inner HTTPS payload, you could check it if it's convenient.:)

I have solved the Lua Error: "...wireshark\epan\proto.c:7992: failed assertion "fixed_item->parent == tree" by manually create a new subtree. According to your answer that TLS session is stored in a conversation created using the addresses, ports, and port types, I tried to use different port_type defined in address.h:

Dissector.get("tls"):call(tvb, pktinfo, tunnel_tree)
if f_data() ~= nil then  <-- call data.data field
    local data_tvb = f_data().range:tvb()  <-- Manually create a new tvb by using decrypted data.data field
local app_tree = tunnel_tree:add(pf_TLS_app_data, data_tvb)  
local save_inner_port_type = pktinfo.port_type  
pktinfo.port_type = _EPAN.PT_SCTP  <-- Another new value ...
(more)
Linxiao Yu gravatar imageLinxiao Yu ( 2024-07-21 08:23:39 +0000 )edit

One more issue is that I found the HTTP payload will first be dissected as Trojan tunneled data, i.e., if the frame contains several TLS records inside the TLS tunnel, when I decrypt the inner TLS using another keylogfile (I've _injected_ TLS secrets for the outer TLS), each inner TLS record will be dissected again as Trojan tunneled data, as shown below:

... Lower Layers ...
Tranport Layer Security <---- The outer TLS layer
Trojan Protocol <---- The root of Trojan protocol tree
++++ TLS Record <---- Inner TLS record (decrypted)
++++++++ Encrypted Application Data: ...
++++ TLS Record <---- Inner TLS record (decrypted)
++++++++ Encrypted Application Data: ...
++++ Trojan Protocol <---- The dissector is called again
++++++++ Trojan Tunneled Data
++++++++++++ Data <---- The data is the decrypted application data of the first Inner TLS record
++++ Trojan Protocol <---- The dissector is called again
++++++++ Trojan Tunneled Data
++++++++++++ Data <---- The data is the decrypted application data of ...(more)

Linxiao Yu gravatar imageLinxiao Yu ( 2024-07-21 08:31:34 +0000 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

Stats

Asked: 2024-07-16 07:07:56 +0000

Seen: 194 times

Last updated: Jul 22