z2a.0x01

Zero2Auto 0x01 — SendSafe: Blowfish ECB C2 Decryption

PUBLISHED: APR 21, 2026

Executive Summary

sendsafe.bin is a spam bot that hides its C2 configuration inside the PE resource section, encrypted with Blowfish ECB using a hardcoded 16-byte key. At runtime the binary locates resource type 0xA / ID 0xE2, decodes the ASCII hex string stored there, decrypts it block-by-block, and connects to the recovered address. The recovered C2 is 195.2.240.119:50055/50056 advertising itself as Enterprise Mailing Service. The main functionality for the encryption and set up can be found at va (virutal address) 0x0004762da0.

Static Analysis — Resource Parsing

The binary calls FindResourceA(NULL, 0xE2, 0xA) to locate the encrypted blob. Inside the PE resource tree the path is: Type 0xA → ID 0xE2 → Language entry 0. The raw data at that node is a null-terminated ASCII hex string — each byte of ciphertext is encoded as two hex characters, e.g. BF for 0xBF.

The string is 112 characters long (56 bytes of ciphertext), terminated by 0x00, followed by the literal bytes PAD 0x14 0x03 which are section padding and not part of the payload. Splitting on the null byte before any further processing is required — rstrip(b'\x00') will silently include the padding because the trailing bytes are non-null. Measuring the null-terminated hex string directly `len(encrypted_c2.split(b'\x00')[0])` returns 112 bytes which sub_44e5b0 halves. This fills up 7 blowfish blocks.

# Terminates at the first null, ignores trailing padding bytes
hex_string = encrypted_c2.split(b'\x00')[0].decode('ascii')

The decompiled resource loading sequence confirms the flow — the resource is located, loaded into memory, and copied into a local buffer before being passed to the decryption routine. The key bytes are then set up inline immediately after:

local_5ff4 = FindResourceA((HMODULE)0x0,(LPCSTR)0xe2,(LPCSTR)0xa);
local_5fe4 = LoadResource((HMODULE)0x0,local_5ff4);
local_5ff0 = LockResource(local_5fe4);
_strcpy(local_54c,local_5ff0);
local_5fd8 = sub_44e5b0(local_14c,(char *)local_54c,0x100);
if (0 < (int)local_5fd8) {
  local_24 = 7;
  local_23 = 0x54;
  local_22 = 8;
  local_21 = 0xd5;
  local_20 = 0x67;
  local_1f = 0x85;
  local_1e = 0x9e;
  local_1d = 0xe2;
  local_1c = 0xaf;
  local_1b = 0xe3;
  local_1a = 0xc6;
  local_19 = 0xa9;
  local_18 = 0x8f;
  local_17 = 0x3b;
  local_16 = 0xc;
  local_15 = 0x5b;
  local_5ffc = 0;
  local_5ff8 = 0;

Encryption Scheme — Blowfish ECB

The decryption routine at sub_4220e0 implements Blowfish in ECB mode. Each 8-byte ciphertext block is decrypted independently — there is no IV, no chaining, and no dependency between blocks. This is the critical detail: CBC mode with a zero IV decrypts the first block correctly (because XOR with zero is a no-op) but produces garbage for every subsequent block, which is exactly what led to initial misidentification of the mode.

The resource stores ciphertext as ASCII hex — each real byte is encoded as two characters, e.g. BF = one byte 0xBF. Converting to actual bytes: 112 hex chars / 2 = 56 bytes. Blowfish operates on 8-byte blocks, so: 56 bytes / 8 = 7 blocks. Blowfish runs its 16-round process once per block — 7 times total — and concatenates the results.

Each 8-byte block is split into two 32-bit halves: L (high) and R (low). Per round, a P-array entry is XOR'd into L (injecting key material), L is passed through the F-function (which uses key-derived S-boxes), the result is XOR'd into R, then L and R swap. After 16 rounds, a final XOR with the last two P-array entries (P17, P18) produces the output block. L holds half the data — the key lives in the P-array and S-boxes set up before encryption starts.

 # Every block decrypted independently
cipher = Blowfish.new(KEY, Blowfish.MODE_ECB)

Key Recovery

The Blowfish key is hardcoded immediately before the call to sub_4220e0 in the payload setup function. It is 16 bytes wide and sits in the .text section as a contiguous byte sequence — no obfuscation, no runtime construction:

KEY = bytes([0x07, 0x54, 0x08, 0xD5, 0x67, 0x85, 0x9E, 0xE2,
             0xAF, 0xE3, 0xC6, 0xA9, 0x8F, 0x3B, 0x0C, 0x5B])

The key sequence 07 54 08 D5 67 85 9E E2 AF E3 C6 A9 8F 3B 0C 5B can be used directly as a YARA string to pivot across samples and identify variants that reuse the same key material.

Decryption Script

The script below recovers the C2 string from any sample that follows the same resource layout. Change the path and key bytes to adapt to new variants.

import pefile
from Crypto.Cipher import Blowfish

KEY = bytes([0x07, 0x54, 0x08, 0xD5, 0x67, 0x85, 0x9E, 0xE2,
             0xAF, 0xE3, 0xC6, 0xA9, 0x8F, 0x3B, 0x0C, 0x5B])

pe = pefile.PE("sendsafe.bin")

encrypted_c2 = None
for rsrc in pe.DIRECTORY_ENTRY_RESOURCE.entries:
    if rsrc.id == 0xa:
        for entry in rsrc.directory.entries:
            if entry.id == 0xe2:
                offset = entry.directory.entries[0].data.struct.OffsetToData
                size   = entry.directory.entries[0].data.struct.Size
                encrypted_c2 = pe.get_memory_mapped_image()[offset:offset+size]
                break

if encrypted_c2 is None:
    print("Resource not found")
else:
    # Split at null — trailing PAD bytes are not part of the hex string
    hex_string = encrypted_c2.split(b'\x00')[0].decode('ascii')

    if len(hex_string) % 2 != 0:
        hex_string += '0'

    raw_encrypted = bytes.fromhex(hex_string)
    block_data = raw_encrypted[:len(raw_encrypted) - (len(raw_encrypted) % 8)]

    # ECB — each 8-byte block decrypted independently, no IV
    cipher = Blowfish.new(KEY, Blowfish.MODE_ECB)
    decrypted = cipher.decrypt(block_data)

    c2 = decrypted.split(b'\x00')[0].decode('ascii', errors='ignore')
    print(f"C2: {c2}")

Running against the sample recovers: 195.2.240.119:50055/50056;Enterprise Mailing Service

YARA Rule

rule SendSafe_Blowfish_Hardcoded_Key
{
    meta:
        description = "Detects SendSafe spam bot variant with hardcoded Blowfish key"
        author      = "malcode.tech"
        date        = "2026-04-17"
        reference   = "195.2.240.119:50055/50056"

    strings:
        // 16-byte Blowfish key hardcoded in .text before sub_4220e0
        $blowfish_key = { 07 54 08 D5 67 85 9E E2 AF E3 C6 A9 8F 3B 0C 5B }

        // ASCII hex-encoded ciphertext prefix (first 8 bytes of encrypted C2)
        $enc_c2_prefix = "BF8744B38F94407E"

    condition:
        uint16(0) == 0x5A4D and
        (
            $blowfish_key or
            $enc_c2_prefix
        )
}

Indicators of Compromise (IOCs)

#Network #Config #CryptoKey

C2: 195.2.240[.]119:50055
C2: 195.2.240[.]119:50056
C2 Label: Enterprise Mailing Service
Blowfish Key: 07 54 08 D5 67 85 9E E2 AF E3 C6 A9 8F 3B 0C 5B
Resource Type: 0xA / ID: 0xE2
Cipher: Blowfish-ECB, 16 rounds, no IV

← Back to Reports