Chapter 11 What is the first TLS 1.3 client random that was used to establish a connection with protonmail.com
?
11.1 Objects in memory/packages loaded from preceding chapters
Objects available:
read_zeek_log()
— helper function to read Zeek log files (Chapter 3)packets
— data frame of PCAP packet data (Chapter 4)conn
— Zeekconn.log
data (Chapter 5)hosts
—tshark
host/IP file (Chapter 8)ftp
— Zeekftp.log
data (Chapter 10)
Packages:
{tidyverse}
11.2 Question Setup
We’ve mazed into encrypted, technical territory with our new quest to seek out the first TLS 1.3 client random that was used to establish a connection with protonmail.com
? TLS (transport layer security) is what your browser (and other clients) use to keep data away from prying eyes. TLS connections must be setup/established through a handshake process and we’ve been asked to pick out a certain element from the first handshake made to a connection to ProtonMail, an end-to-end encrypted email service hosted in Switzerland.
This quest expects us to know about this handshake and where to look for the associated data. In the most TLS exchange algorithm (RSA) the first message in this handshake is what is known as the “client hello” message. Your client send a “hey there” greeting to the server, telling it what TLS version and cipher suites it supports plus a string of random bytes boringly known as the “client random.” This is target of our quest.
While we’ve generated many files from the PCAP, we’re going to have to poke at it again to solve this question with the least amount of frustration. The tshark
filters contain a cadre of tls
filters, one of which is tls.handshake.extensions_server_name
which can be used to search for server names specified in the Server Name Indication (SNI) TLS extension field. Since this name will be in the client hello, we can filter on it and then identify the first client random.
11.3 Solving the quest with tshark
custom filters
We really don’t need R at all since we can create a display filter and choose what fields to output right on the tshark
command line:
system("tshark -r maze/maze.pcapng -e tls.handshake.random -Tfields tls.handshake.extensions_server_name == 'protonmail.com'", intern=TRUE)
## [1] "24e92513b97a0348f733d16996929a79be21b0b1400cd7e2862a732ce7775b70"
## [2] "32b9d36ddc0a2cc8c46811a50114ee2425c3dbf67be6b3d76f186ef25551548a"
## [3] "be82534e4aaef468ac88fe15473dd429bd7c4051c7a032d51d7979f36d76fdc7"
## [4] "492fc3d932fb6426bb9d7a087cc5e2ae8fd4a0f6826f8736fb0f1ad225b962f4"
## [5] "721f28c2a407810abfdbf7d9c8be8e3f452cc80d52e6f738fa158e521ff2f20e"
## [6] "ddfb32c96ba450dee42f208944d96bebad751298ce3471cb8e06ee112e37493c"
The first element is the target of our quest.
11.4 Solving the quest with R and tshark
custom filters
TLS JSON output in tshark
is ginormous, so filtering as much as possible before outputting the JSON is paramount.
We’ll take a similar strategy to the pure tshark
version above and grab all the handshakes for Proton Mail. The -T json
unsurprisingly generates JSON output and you are encouraged to bring that file up in vim
, Sublime Text, or even pipe it through jq
to see how complex the structure is. We’ll do the same in R:
system("tshark -T json -r maze/maze.pcapng tls.handshake.extensions_server_name == 'protonmail.com' > maze/proton-q.json")
library(jsonlite, include.only = "fromJSON")
<- fromJSON("maze/proton-q.json")
proton_hands
str(proton_hands, 3) # look at the structure of the object, only going 3 nested levels deep
## 'data.frame': 6 obs. of 4 variables:
## $ _index : chr "packets-2021-04-29" "packets-2021-04-29" "packets-2021-04-29" "packets-2021-04-29" ...
## $ _type : chr "doc" "doc" "doc" "doc" ...
## $ _score : logi NA NA NA NA NA NA
## $ _source:'data.frame': 6 obs. of 1 variable:
## ..$ layers:'data.frame': 6 obs. of 5 variables:
## .. ..$ frame:'data.frame': 6 obs. of 15 variables:
## .. ..$ eth :'data.frame': 6 obs. of 5 variables:
## .. ..$ ip :'data.frame': 6 obs. of 19 variables:
## .. ..$ tcp :'data.frame': 6 obs. of 24 variables:
## .. ..$ tls :'data.frame': 6 obs. of 1 variable:
We can see that each packet has many layers (and not all packets must or will have the same layer types in a given PCAP). This data structure contains extracted handshakes associated with the protonmail.com
SNI, and all of this information will be in the tls
structure.
str(proton_hands$`_source`$layers$tls, 2)
## 'data.frame': 6 obs. of 1 variable:
## $ tls.record:'data.frame': 6 obs. of 4 variables:
## ..$ tls.record.content_type: chr "22" "22" "22" "22" ...
## ..$ tls.record.version : chr "0x00000301" "0x00000301" "0x00000301" "0x00000301" ...
## ..$ tls.record.length : chr "512" "512" "512" "512" ...
## ..$ tls.handshake :'data.frame': 6 obs. of 31 variables:
This structure include the tls.handshake
, so we can wind down a twisty path to get to our quarry:
# grab the first handshake random
$`_source`$layers$tls$tls.record$tls.handshake$tls.handshake.random[1] proton_hands
## [1] "24:e9:25:13:b9:7a:03:48:f7:33:d1:69:96:92:9a:79:be:21:b0:b1:40:0c:d7:e2:86:2a:73:2c:e7:77:5b:70"
There’s one “nit” left, which is that the challenge answer box expects a value with no colons. We can quickly fix that:
gsub(":", "", proton_hands$`_source`$layers$tls$tls.record$tls.handshake$tls.handshake.random[1])
## [1] "24e92513b97a0348f733d16996929a79be21b0b1400cd7e2862a732ce7775b70"