Using BreakingPoint to Test DNS over HTTPS (DoH) Services - Part 2

September 27, 2019 by Vincent Du

Part 1 of this blog series is here.

Decrypting TLS

BreakingPoint users can easily verify the client-side SSL traffic in a separate test with TLS actions removed from the DNS over HTTPS (DoH) Superflows. To decrypt the actual DoH traffic from the server-side, a “Raw” flow and three actions - “delay”, “conditional request”, and “raw” are added into the test ClientSim Superflow.

The “Raw” flow uses UDP as the L4 Transport: 


The “Conditional Request” will match everything from the server-side by using regex “([\x00-\xff]+)” and a “goto” statement pointing to the “Raw Message” action upon regex match:


The “Raw Message” is an action belonging to the “Raw” flow that uses UDP; the “string” field is a token “##bpsvar(0)##” that will match and extract every byte from the previous action: 


The PCAP of this test should have a UDP packet sent out by the client right after the DoH server-side application data, which is the cleartext of the DoH server response. The actual DNS response is in the HTTP payload. In our test, one response starts with “4b fe 81 80” (“4b fe” is the message ID, and “81 80” is the QR with response bit set, OPCode, AA, TC fields all being 0, and RD field being 1). 


Decoding Gzip and Chunked

Next, we used “DNS Query over HTTPS(JSONAPI)” query action to repeat the test. Observing the UDP packet, we noticed that the HTTP payload is not the JSON message as we expected. But why?

Further inspecting the HTTP headers in the UDP payload we found “Content-Encoding: gzip” and “Transfer-Encoding: chunked”. With this information, we can decode the payload. For some reason, the DoH server is not responding to every DNS query with the desired answer. However, we know “1f 8b” is the magic byte for gzip, so searching for a UDP packet with it would lead us to the right packet to decode.

After we located the right UDP packet containing the DoH server response, we can copy the “Hex Stream” by right-clicking the packet in Wireshark, then select “Copy -> Bytes -> Hex Stream”:


Ruby IRB can be used to decode the payload hex stream. We know ‘1f 8b’ is the start of the gzip chunk, and there should always be a chunk with 0 length in the end, so the actual payload are the bytes between “1f 8b” and “00000d0a0d0a”.

With these commands in IRB to perform the decode, we can see the JSON message in the end.

2.5.3 :011 > str = "1f8b08000000000002ffab560a2e492c292d56b25230d0510a7106d2698939c5a93a4a412e407649512988e908673a22449d5d108a034b538b4b32f3f394aca215aa95f212735381724aa91589b90539a97ac9f9b97a4a3a4a2595052061c3da58a03979c5e5a9454429073a2bc407c43033b734d6514a492c4904a9b634d633b430d1333234d3333651aa8dad0500cac2c6b3cb00"
2.5.3 :012 > zipstr=str.scan(/../).map { |x| x.hex.chr }.join
 => "\x1F\x8B\b\x00\x00\x00\x00\x00\x02\xFF\xABV\n.I,)-V\xB2R0\xD0Q\nq\x06\xD2i\x899\xC5\xA9:JA.@vIQ)\x88\xE9\bg:\"D\x9D]\x10\x8A\x03KS\x8BK2\xF3\xF3\x94\xAC\xA2\x15\xAA\x95\xF2\x12sS\x81rJ\xA9\x15\x89\xB9\x059\xA9z\xC9\xF9\xB9zJ:J%\x95\x05 a\xC3\xDAX\xA09y\xC5\xE5\xA9ED)\a:+\xC4\a\xC403\xB74\xD6QJI,I\x04\xA9\xB64\xD63\xB40\xD1324\xD336Q\xAA\x8D\xAD\x05\x00\xCA\xC2\xC6\xB3\xCB\x00"

2.5.3 :014 > require 'zlib'
 => true
2.5.3 :015 > gz =
 => #<Zlib::GzipReader:0x0000561951833108>
2.5.3 :016 > data =
 => "{\"Status\": 0,\"TC\": false,\"RD\": true,\"RA\": true,\"AD\": true,\"CD\": false,\"Question\":[ {\"name\": \"\",\"type\": 1}],\"Answer\":[ {\"name\": \"\",\"type\": 1,\"TTL\": 16793,\"data\": \"\"}]}"

Comparing this JSON object with a query made from cURL client or web browser to resolve “” host, we can confirm the responded to BreakingPoint DoH ClientSim test with exactly the same data.


The Ixia BreakingPoint Application and Threat Intelligence (ATI) Subscription provides bi-weekly updates of the latest application protocols and attacks for use with Ixia platforms.