[forensics/C2 PCAP Analysis] PolyU x NuttyShell Cybersecurity CTF 2025 Writeup

# Challenge Details
<table>
  <tr>
    <td>Name</td> <td>C2 Pcap Analysis</td>
  </tr>
    <tr>
    <td>Author</td> <td>Nightsedge</td>
  </tr>
  <tr>
    <td>Final Solves</td> <td>5</td>
  </tr>
  <tr>
  <td>Final Points</td> <td>496</td>
  </tr>
  <tr>
  <td>Description</td>
  <td>We detected a C2 backdoor.
Luckily because of our great cybersecurity infrastructure, we are able to decrypt the abnormal traffic.
But what C2 framework is it? Seems not the common C2...</td>
  </tr>
</table>

# Introduction
We are given a pcap file named **"possible_c2_traffic_2025.pcap"**, which matches the description and name of this challenge, adhering to the theme of "C2". This is quite useful later on.

Opening the pcap file in wireshark to analyse, we find something similar to the screenshot below: A bunch of DNS + TCP + HTTP requests.

![Screenshot 2025-04-25 at 15.48.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/8bbfb26b-f1ba-41bd-82c0-e3d48287feb9.png)

Looking through the DNS request we find that they are mostly used for microsoft services, in particular teams. It's also interesting to see that there exists a `hkctf-night01.local`, yet the DNS stuff isn't quite useful yet.

However, taking a look at the singular TCP/HTTP stream, we find that contents are basically the same thing, and it appears that the client is sending quite a lot of stuff to the server with POST requests, as seen by "POST /".

[Online sources](https://github.com/strandjs/IntroLabs/blob/master/IntroClassFiles/Tools/IntroClass/pcap/AdvancedC2PCAPAnalysis.md) suggest that the dumped requests should look something like the picture below, however this is not the case, as it feels to be encrypted by some sort of algorithm.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/9ce9864e-a569-494a-9496-99b896083658.png)

This is further backed by the challenge description mentiong that this "seems not the common C2", which is interesting.

# Identifying Framework

If we take a look at the encrypted requests, we find a recurring pattern in these requests: after the HTTP headers, the first portion of the body reads "de ad be ef"!

![Screenshot 2025-04-25 at 16.00.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/2dce4266-25ea-4b08-811c-4d122b9ef015.png)

While trying to look up how to identify C2 frameworks, we found that C2 often uses identifiable traffic patterns. This perfeclty matches with our previous observation of recurring "deadbeef" at the start of payload bodies!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/f27ab155-bd44-4470-b39d-8fd2aeedc5b1.png)

Searching **"deadbeef" c2** shows the following results which suggest that the C2 traffic captured in the pcap seems to be of the "Havoc C2 Framework"...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/4ea19401-1ace-43f6-82b6-6ae97d35e537.png)

[The first link](https://www.immersivelabs.com/resources/blog/havoc-c2-framework-a-defensive-operators-guide) tells us that:
1. Encryption is in AES256 CTR mode
2. The first packet show the AES(32 bytes) + IV(16 bytes) Key, 12 bytes after the **Magic Byte Header**, "deadbeef"
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/e6803b56-14c6-4e49-a3a7-66e55c438e77.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/3b5d0401-7ef2-44a7-96e4-19ee00aba9d8.png)
> "Having obtained the keys, we then developed a methodology to help defensive operators acquire them from both packet capture and memory, detailed below. After setting everything up, we ran the demon on the target machine with Wireshark packet capture enabled. This allowed us to monitor all the HTTP and TCP traffic between the demon and the teamserver.Upon analyzing the first packet in the capture, we noticed that the first bytes said dead beef, which is a magic byte value, shown in the red box in the picture below."

Applying this to the first packet of the captured requests, 

```hex
00000000  50 4f 53 54 20 2f 20 48  54 54 50 2f 31 2e 31 0d   POST / H TTP/1.1.
00000010  0a 43 61 63 68 65 2d 43  6f 6e 74 72 6f 6c 3a 20   .Cache-C ontrol: 
00000020  6e 6f 2d 63 61 63 68 65  0d 0a 43 6f 6e 6e 65 63   no-cache ..Connec
00000030  74 69 6f 6e 3a 20 4b 65  65 70 2d 41 6c 69 76 65   tion: Ke ep-Alive
00000040  0d 0a 50 72 61 67 6d 61  3a 20 6e 6f 2d 63 61 63   ..Pragma : no-cac
00000050  68 65 0d 0a 43 6f 6e 74  65 6e 74 2d 54 79 70 65   he..Cont ent-Type
00000060  3a 20 2a 2f 2a 0d 0a 55  73 65 72 2d 41 67 65 6e   : */*..U ser-Agen
00000070  74 3a 20 4d 6f 7a 69 6c  6c 61 2f 35 2e 30 20 28   t: Mozil la/5.0 (
00000080  57 69 6e 64 6f 77 73 20  4e 54 20 36 2e 31 3b 20   Windows  NT 6.1; 
00000090  57 4f 57 36 34 29 20 41  70 70 6c 65 57 65 62 4b   WOW64) A ppleWebK
000000A0  69 74 2f 35 33 37 2e 33  36 20 28 4b 48 54 4d 4c   it/537.3 6 (KHTML
000000B0  2c 20 6c 69 6b 65 20 47  65 63 6b 6f 29 20 43 68   , like G ecko) Ch
000000C0  72 6f 6d 65 2f 39 36 2e  30 2e 34 36 36 34 2e 31   rome/96. 0.4664.1
000000D0  31 30 20 53 61 66 61 72  69 2f 35 33 37 2e 33 36   10 Safar i/537.36
000000E0  0d 0a 43 6f 6e 74 65 6e  74 2d 4c 65 6e 67 74 68   ..Conten t-Length
000000F0  3a 20 33 32 35 0d 0a 48  6f 73 74 3a 20 31 39 32   : 325..H ost: 192
00000100  2e 31 36 38 2e 31 31 33  2e 31 33 30 0d 0a 0d 0a   .168.113 .130....
===
00000110  00 00 01 41 de ad be ef  4f 40 bb 84 00 00 00 63   ...A.... O@.....c
===
00000120  00 00 00 00 d6 b6 7c 00  44 2e be a0 fa de e2 fa   ......|. D.......
00000130  a8 50 1c 22 34 42 ee 62  4e 00 12 1e 12 14 a6 9c   .P."4B.b N.......
00000140  7c 32 2e 92 22 84 1e ca  72 04 f0 10 b8 a0 4c ba   |2.."... r.....L.
00000150  84 22 4c a8 ab 65 6a cb  fd f3 ef c6 fa 1a ff c6   ."L..ej. ........
00000160  c8 56 3b fb 5e 3b 9d 16  05 d9 c3 67 b6 2f 7f e9   .V;.^;.. ...g./..
00000170  b5 62 b7 6f b1 3c ab c4  f7 68 d7 a6 dc ba 34 cf   .b.o.<.. .h....4.
00000180  18 30 7e cc 1c e3 b8 8b  6d 0a ce 66 f4 98 f1 dd   .0~..... m..f....
00000190  2d cd 3f 28 1f 31 42 70  ec 15 f6 62 c3 89 46 9d   -.?(.1Bp ...b..F.
000001A0  04 f3 86 39 38 75 2e 97  64 56 e2 f6 52 96 ed dd   ...98u.. dV..R...
000001B0  e4 a0 3e 66 55 e1 2e 7d  b5 06 a2 13 da f8 d6 75   ..>fU..} .......u
000001C0  2a 96 9c f6 ad 48 be 26  03 26 bf e1 cf 27 29 96   *....H.& .&...').
000001D0  34 f5 f4 49 80 78 b3 61  82 11 1a 8d 6d f5 80 bc   4..I.x.a ....m...
000001E0  46 41 c5 f6 71 6d ed 34  e8 2a b4 45 a8 ab 59 a7   FA..qm.4 .*.E..Y.
000001F0  d8 9c e0 0d bf e8 8f 76  a4 a0 7c 04 c4 b2 b0 af   .......v ..|.....
00000200  c9 e4 9e c8 d9 c1 c1 01  89 f2 e4 30 6b 7a ab 20   ........ ...0kz. 
00000210  5b f6 e2 d4 eb ee 4a 39  b1 47 fd 47 20 b1 72 4c   [.....J9 .G.G .rL
00000220  86 73 08 cd 5b 5d 2c 42  13 e4 37 12 72 c6 cc 93   .s..[],B ..7.r...
00000230  7a 75 d3 ac 19 4d 39 b0  4e 88 3c c7 15 d1 4b ac   zu...M9. N.<...K.
00000240  f8 1b 45 20 5d f1 98 cd  1e 55 68 68 3c 25 ba de   ..E ]... .Uhh<%..
00000250  b0 d6 10 db f8                                     .....
```

Taking the AES Key (first 32 bytes), which begins 12 bytes after "deadbeef", we get `d6b67c00442ebea0fadee2faa8501c223442ee624e00121e1214a69c7c322e92`.

Similarly, we treat the next 16 bytes as our IV Key: `22841eca7204f010b8a04cba84224ca8`.

Using the rest of the payload as input, we can plug the values into [CyberChef](https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'d6b67c00442ebea0fadee2faa8501c223442ee624e00121e1214a69c7c322e92'%7D,%7B'option':'Hex','string':'22841eca7204f010b8a04cba84224ca8'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)&input=YWI2NTZhY2JmZGYzZWZjNmZhMWFmZmM2Yzg1NjNiZmI1ZTNiOWQxNjA1ZDljMzY3YjYyZjdmZTliNTYyYjc2ZmIxM2NhYmM0Zjc2OGQ3YTZkY2JhMzRjZjE4MzA3ZWNjMWNlM2I4OGI2ZDBhY2U2NmY0OThmMWRkMmRjZDNmMjgxZjMxNDI3MGVjMTVmNjYyYzM4OTQ2OWQwNGYzODYzOTM4NzUyZTk3NjQ1NmUyZjY1Mjk2ZWRkZGU0YTAzZTY2NTVlMTJlN2RiNTA2YTIxM2RhZjhkNjc1MmE5NjljZjZhZDQ4YmUyNjAzMjZiZmUxY2YyNzI5OTYzNGY1ZjQ0OTgwNzhiMzYxODIxMTFhOGQ2ZGY1ODBiYzQ2NDFjNWY2NzE2ZGVkMzRlODJhYjQ0NWE4YWI1OWE3ZDg5Y2UwMGRiZmU4OGY3NmE0YTA3YzA0YzRiMmIwYWZjOWU0OWVjOGQ5YzFjMTAxODlmMmU0MzA2YjdhYWIyMDViZjZlMmQ0ZWJlZTRhMzliMTQ3ZmQ0NzIwYjE3MjRjODY3MzA4Y2Q1YjVkMmM0MjEzZTQzNzEyNzJjNmNjOTM3YTc1ZDNhYzE5NGQzOWIwNGU4ODNjYzcxNWQxNGJhY2Y4MWI0NTIwNWRmMTk4Y2QxZTU1Njg2ODNjMjViYWRlYjBkNjEwZGJmOA) and get the payload shown in the screenshot, which reads "HKCTF-NIGHT01"! This aligns with the "hkctf-night01.local" domain we found earlier, suggesting that we successfully decrypted the first packet.
*(Night refers to Nightsedge the author?)*

![Screenshot 2025-04-25 at 16.18.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/8f1a882e-9762-4bf8-9e10-742e01a131a7.png)

# Exploitation

Decrypting the rest of the packets is surprisingly easy, since we already have the AES and IV Key, we just take the stuff, which is (12 bytes) after deadbeef and decrypt it with the same values.

We do this with the script below:
```py
# file with hex dump from wireshark
with open('dump', 'r') as f:
    hex_dump = f.read()

split = hex_dump.split("POST / H TTP/1.1.\n")
res = [x for x in split if "de ad be ef" in x]

for req in res:
    lines = req.split("\n")[:-1]
    hex_values = []
    for line in lines:
        if line.strip():
            hex_values.extend(line[9:58].split())

    hex = "".join(hex_values).split("deadbeef")[1] 
    ciphertext = binascii.unhexlify(hex[24:])
```

Then, simply automate the decrypting process we did in CyberChef, to decode it!
```py
def decrypt_aes_ctr(key, nonce, ciphertext):
    ctr = Counter.new(128, initial_value=int.from_bytes(nonce, byteorder='big'))
    cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
    plaintext = cipher.decrypt(ciphertext)
    return plaintext

key = binascii.unhexlify("d6b67c00442ebea0fadee2faa8501c223442ee624e00121e1214a69c7c322e92")
nonce = binascii.unhexlify("22841eca7204f010b8a04cba84224ca8")
```
```py
for req in res:
    # ...
    plaintext = decrypt_aes_ctr(key, nonce, ciphertext)

    print(plaintext.decode('ascii',errors='ignore'))
```

Redirecting the output to a file, we use `CTRL + F` to look for the flag format of `PUCTF25{...}`, and profit!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067930/95140c3e-f853-40d0-a888-f0878c5600a6.png)