< Back to blog
highπŸ”‘Stealer
investigatedMarch 7, 2026publishedMarch 7, 2026

Dissecting a Live Hook Android Banking Trojan C2: Architecture, Exploitation Surface, and What the Operator Got Wrong

#stealer#phishing#social-engineering#credential-theft#c2#brute-force#exploit#apt

Published: 2026-03-08 Tags: threat-intelligence, android-malware, banking-trojan, Hook, ERMAC, C2-analysis, Laravel, Socket.IO TLP: WHITE (IOCs sanitized for public sharing)


TL;DR

A ThreatFox-sourced IOC led to a live Hook Android banking trojan C2 panel at 31.57.216.126. Through systematic probing across 26 phases, we mapped the complete infrastructure: a Laravel/PHP backend, React SPA frontend, Workerman WebSocket relay for real-time VNC and file management, an unauthenticated Socket.IO event bus, and an internet-facing MySQL 8.0.31 instance. Two endpoints lack authentication middleware entirely, the Socket.IO server accepts arbitrary subscriptions without credentials, and the operator's database appears broken -- exposing the C2's full API surface, bot command vocabulary, and architectural weaknesses to anyone willing to look.


1. Discovery and Initial Reconnaissance

The panel surfaced via a same-day ThreatFox report flagging 31.57.216.126 as a Hook C2. Initial HTTP fingerprinting revealed a split-service architecture:

PortServiceRole
22OpenSSHRemote administration
80nginx/1.29.5React SPA frontend
3306MySQL 8.0.31Database (internet-facing)
3434Socket.IO (Workerman)Real-time bot event relay
8000Workerman/4.1.9WebSocket server (VNC/file manager)
8089nginx/1.29.5 -> PHP/8.2.30Laravel API backend

The frontend is a compiled React SPA -- a 3.6MB JavaScript bundle (main.1e9a5134.js) and 673KB CSS bundle -- served through nginx on port 80. The login page features a video background (/assets/images/login_sd.mp4) and multi-language support across English, Turkish, Russian, and Traditional Chinese. The Turkish-language strings (e.g., "ROOT kullanici olustur", "ID botu") suggest a Turkish operator, though the multi-language support is a standard Hook panel feature.

Hook descends from the ERMAC malware family and represents one of the most capable Android banking trojans in active circulation. It combines traditional overlay injection attacks with VNC-based remote control, giving operators hands-on-keyboard access to victim devices.


2. API Surface Mapping

By extracting endpoint paths from the decompiled JavaScript bundle and systematically probing the API, we confirmed 24 live endpoints across the Laravel backend:

Authentication

POST /api/v1/sign-in    {token, password} -> {payload.token, payload.user}
GET  /api/v1/getUserInfo                  -> user profile
GET  /sanctum/csrf-cookie                 -> CSRF cookie (204 No Content)

The sign-in endpoint uses token (operator name) and password (codeword) as field names -- not username/password. This is a Hook panel convention. The panel also supports optional Telegram-based 2FA, indicated by HTTP 406 responses.

Bot Management

POST /api/v1/bots/getBotsList             {page, per_page, filters}
POST /api/v1/bots/sendBotsCommand         {command, payload, botIds}
POST /api/v1/bots/editBotComment          PUT method
DELETE /api/v1/bots/deleteBot
DELETE /api/v1/bots/deleteAllRemovedApp
POST /api/v1/bots/{id}/setBotType
POST /api/v1/bots/{id}/settings/updateBotSettings
POST /api/v1/bots/{id}/injects/updateBotInjections
POST /api/v1/bots/{id}/commands/getCommandsList
POST /api/v1/bots/{id}/fileManager/list
GET  /api/v1/bots/getBotsFilter

Data Exfiltration & Management

POST /api/v1/logs/getLogsList             {type, bot_id, application, page, per_page}
POST /api/v1/injects/getInjectionsList
POST /api/v1/injects/createInjection
POST /api/v1/counts/getCounts
POST /api/v1/statistics/getStats
POST /api/v1/permissions/getPermissionsList
POST /api/v1/smartInjections/getSessions   [NO AUTH MIDDLEWARE]

Operator & Telegram Management

POST /api/v1/accounts/getAccountsList
POST /api/v1/accounts/createAccount
POST /api/v1/accounts/sendTelegramMessage
PUT  /api/v1/accounts/unbindTelegram
PUT  /api/v1/accounts/updateTelegramBot
PUT  /api/v1/accounts/updateTelegramInjection

Role Hierarchy

The JavaScript bundle references three user roles:

  • ROOT -- highest privilege, can create operators
  • ADMIN -- standard operator access
  • SEO -- limited, likely view-only role

3. The Bot Command Vocabulary

Extraction of the command dispatch logic from the JS bundle reveals Hook's full device control capabilities:

CategoryCommands
SMSgetSMS, sendSMS, forwardSMS
CallsmakeCall, forwardCall, calling
ContactsgetContacts, addContact
InjectionstartInject, updateInjects, updateInjectAndListApps
Keyloggingkeylogger
EmailgmailTitles, getGmailMessage
Screenshotscreenshot
AppsopenApp, startApp, deleteApp, getInstalledApps
LocationgetLocation, geolocation
MessagingWhatsApp message sending
FinanceUSSD commands, crypto SEED phrase theft
MediagetImages
Social EngineeringPush notification spoofing
Credential TheftgetAccounts (Android AccountManager)
Remote ControlVNC (via WebSocket port 8000), file manager
Smart InjectionPhishing overlay sessions
DestructionkillMe (self-destruct/uninstall)

The openurl command is particularly notable -- it allows operators to redirect victim browsers to arbitrary URLs, enabling on-the-fly phishing or drive-by download chains.

Panel Route Structure

The frontend routes expose the full operational workflow:

/banks          Banking injection targets
/bots           Bot management (online/offline/uninstalled)
/cards          Stolen credit cards
/crypt          Cryptocurrency theft
/email          Stolen email data
/injections     HTML overlay injection management
/shops          Stolen data marketplace interface
/stealer        Stealer module output
/wallet         Cryptocurrency wallets
/storage/emulated/0   Remote file browser on victim devices
/stats          Operations dashboard
/users          Operator management

4. Vulnerability Analysis: What We Found

Our 26-phase exploitation attempt covered sign-in fuzzing, Laravel debug mode probes, known Laravel CVE paths, auth bypass attempts, JWT forgery, Sanctum CSRF flow analysis, credential brute force, endpoint discovery, Socket.IO exploitation, and MySQL authentication. Here is what yielded results.

4.1 Unauthenticated Endpoints (HIGH)

Phase 14's comprehensive auth check distinguished between endpoints returning 401 Unauthenticated (auth middleware present) and those returning 500 Server Error (no auth middleware -- crashes directly into application code):

Endpoints returning 500 (NO AUTH MIDDLEWARE):
  * /api/v1/sign-in (POST)
  * /api/v1/smartInjections/getSessions (POST)

All other endpoints: 401 Unauthenticated

The smartInjections/getSessions endpoint is the critical finding. This endpoint has no authentication middleware applied -- requests bypass the Sanctum guard entirely and hit the database layer directly. It currently returns 500 because the database is broken, but if the operator fixes their DB, this endpoint would immediately leak SmartInjection phishing session data (stolen credentials captured via overlay attacks) to any unauthenticated caller.

4.2 Socket.IO Accepts Unauthenticated Connections (HIGH)

The Socket.IO server on port 3434 accepts connections with no authentication whatsoever:

# EIO=3 handshake -- no credentials required
GET /socket.io/?EIO=3&transport=polling
# Response:
{"sid":"bc7","upgrades":["websocket"],"pingInterval":20000,"pingTimeout":60000}

We successfully sent subscription events that the server accepted without challenge:

42["subscribe","bots"]   -> accepted
42["subscribe","logs"]   -> accepted
42["subscribe","all"]    -> accepted
42["newBot",{"tag":"test"}]  -> accepted
42["update",{}]          -> accepted

The server crashed (500) when attempting to fulfill subscriptions due to the same database connectivity issue. In a functioning deployment, an attacker could passively monitor all bot check-ins, log entries, and operator activity in real time by subscribing to the event stream -- or inject fake bot events via newBot.

The hardcoded Socket.IO connection URL extracted from the JS bundle:

var lqe = sqe()("http://31.57.216.126:3434/", {transports: ["polling"]});

4.3 MySQL Exposed to the Internet (CRITICAL)

Port 3306 is directly accessible from the internet. MySQL 8.0.31 responds to connection attempts with its full banner:

MySQL 8.0.31 | Auth: caching_sha2_password
Connection IDs observed: 25499-25510 (high activity indicator)

We tested 22 credential combinations across common usernames (root, hook, admin, laravel, forge, homestead, panel, mysql, app, debian-sys-maint) with common passwords. All returned "Access denied." The caching_sha2_password plugin and credential failure suggest the operator at least set non-default DB credentials -- though exposing MySQL to the internet is itself a critical misconfiguration.

4.4 CORS Fully Open (MEDIUM)

Every API response includes:

Access-Control-Allow-Origin: *

This means any website on the internet can make authenticated cross-origin requests to the Hook API. If an operator visits a malicious page while logged in, the attacker's JavaScript can call any API endpoint using the operator's session.

4.5 Sign-In Backend Failure (MEDIUM)

The sign-in endpoint crashes on all valid-looking inputs:

# String credentials -> 500
curl -X POST http://31.57.216.126:8089/api/v1/sign-in \
  -H "Content-Type: application/json" \
  -d '{"token":"admin","password":"admin"}'
# {"message":"Server Error"}  HTTP 500

# Non-string inputs -> 302 redirect (validation layer)
curl -X POST http://31.57.216.126:8089/api/v1/sign-in \
  -H "Content-Type: application/json" \
  -d '{"token":null,"password":null}'
# 302 -> http://31.57.216.126:8089

The behavioral difference is telling: non-string types are caught by Laravel's validation middleware and redirected, while string values pass validation and crash when the application tries to query the users table. APP_DEBUG=false prevents stack trace leakage, returning only {"message":"Server Error"}.


5. What We Tried and What Failed

Thoroughness matters. Here is a summary of techniques that did not yield access:

TechniqueResult
SQL injection on sign-in (' OR 1=1--, " OR ""=")500 (same as normal -- DB broken)
NoSQL injection ({"$gt":""})302 redirect (type validation)
Mass assignment (role=admin, is_admin=true)500 (ignored by Laravel)
Laravel debug tools (/_debugbar, /telescope, /horizon, /log-viewer)404
Sensitive file access (/.env, /storage/logs/laravel.log, /vendor/)404 on port 8089
PHPUnit eval-stdin (/vendor/phpunit/.../eval-stdin.php)404
Swagger/API docs (/swagger, /api/documentation, /docs)404
Bearer token bypass (null, empty, "0", undefined)401
JWT forgery -- alg:none401
JWT forgery -- HS256 with weak secrets (secret, password, hook, key)401
Sanctum session cookie auth flowNo XSRF-TOKEN set
X-Forwarded-For / X-Real-IP bypass (127.0.0.1)401
Laravel Ignition RCE (/_ignition/execute-solution)404
PHP deserialization via malformed session cookies500
Nginx path traversal (/../.env, /..;/.env, URL-encoded)Normalized/blocked
phpinfo endpoints404
OAuth token endpoints (/oauth/token, /oauth/personal-access-tokens)404
Credential brute force (20 common pairs)All 500 (DB down)
MySQL auth brute force (22 user/pass combos)All access denied

6. Architecture Deep Dive

Infrastructure Diagram

                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚   Port 80       β”‚
                         β”‚   nginx/1.29.5  β”‚
                         β”‚   React SPA     β”‚
                         β”‚   (3.6MB JS)    β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β”‚ API calls
                                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Port 3434    β”‚         β”‚   Port 8089     β”‚         β”‚ Port 3306    β”‚
β”‚ Socket.IO    │◄───────►│   Laravel API   │────────►│ MySQL 8.0.31 β”‚
β”‚ (Workerman)  β”‚  events β”‚   PHP/8.2.30    β”‚  ORM    β”‚ (EXTERNAL!)  β”‚
β”‚ NO AUTH      β”‚         β”‚   Sanctum auth  β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β”‚
                                  β”‚ WS relay
                                  β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚   Port 8000     β”‚
                         β”‚  Workerman/4.1.9β”‚
                         β”‚  WebSocket      β”‚
                         β”‚  VNC + FileMgr  β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authentication Flow

The panel uses Laravel Sanctum for API token authentication:

  1. Operator submits token (username) + password via /api/v1/sign-in
  2. Optional: Telegram 2FA challenge (HTTP 406 triggers 2FA flow)
  3. Backend returns a Bearer token stored in localStorage key "token"
  4. All subsequent API requests include Authorization: Bearer <token>
  5. Session cookie hook_session (Laravel encrypted, httponly, samesite=lax) provides server-side session state

Database Failure Root Cause Analysis

The backend MySQL instance is accessible on port 3306 and responds to the MySQL wire protocol, but the Laravel application cannot query it. Possible causes:

  1. Wrong credentials in .env -- Laravel's DB credentials don't match MySQL's configured users
  2. Schema not migrated -- Tables don't exist (would explain crashes on any query)
  3. Permission issue -- Laravel's DB user lacks SELECT/INSERT privileges
  4. Connection pool exhaustion -- Connection IDs near 25,500 suggest heavy traffic

This is notable because it means the C2 was deployed but never properly configured -- or it was configured, used, and then the database was broken (accidentally or through counter-operations).


7. MITRE ATT&CK Mapping

Technique IDNameContext
T1437.001Application Layer Protocol: Web ProtocolsLaravel API + Socket.IO for C2 comms
T1481.002Web Service: Bidirectional CommunicationWebSocket (Workerman) for VNC/file manager
T1406Obfuscated Files or InformationCompiled React SPA, minified JS bundle
T1417.001Input Capture: Keyloggingkeylogger bot command
T1417.002Input Capture: GUI Input CaptureSmart Injections (overlay attacks)
T1516Input InjectionVNC remote control via WebSocket
T1513Screen Capturescreenshot bot command
T1430Location TrackinggetLocation, geolocation commands
T1636.004Protected User Data: SMS MessagesgetSMS, forwardSMS commands
T1636.003Protected User Data: Contact ListgetContacts command
T1636.001Protected User Data: Calendar EntriesOverlay injection targeting banking apps
T1624.001Event Triggered Execution: Broadcast ReceiversSMS interception
T1582SMS ControlsendSMS command
T1616Call ControlmakeCall, forwardCall commands
T1398Boot or Logon Initialization ScriptsPersistence via Android services
T1407Download New Code at RuntimeupdateInjects, openurl commands
T1632.001Subvert Trust Controls: Code Signing PolicySideloaded APK installation

8. Indicators of Compromise

Network Indicators

TypeValueContext
IPv431.57.216.126Hook C2 panel IP
Port80/tcpReact SPA frontend (nginx/1.29.5)
Port3306/tcpMySQL 8.0.31 (internet-facing)
Port3434/tcpSocket.IO event bus (unauthenticated)
Port8000/tcpWorkerman WebSocket (VNC/file manager)
Port8089/tcpLaravel PHP API backend
URLhttp://31.57.216.126:8089/api/v1/sign-inPanel login endpoint
URLhttp://31.57.216.126:3434/socket.io/Socket.IO handshake

Application Fingerprints

IndicatorValue
JS Bundlemain.1e9a5134.js (3,668,590 bytes)
CSS Bundlemain.2a8075f7.css (673,200 bytes)
Session Cookiehook_session (Laravel encrypted)
Auth Token StoragelocalStorage key "token"
Server Headernginx/1.29.5
PHP Version8.2.30
MySQL Version8.0.31
WebSocket FrameworkWorkerman/4.1.9
MySQL Auth Plugincaching_sha2_password
FontMulish (custom)

Hook Panel Detection Signatures

The following URL patterns are characteristic of Hook C2 panels and can be used for hunting:

/api/v1/sign-in
/api/v1/bots/getBotsList
/api/v1/bots/sendBotsCommand
/api/v1/smartInjections/getSessions
/api/v1/counts/getCounts
/api/v1/injects/getInjectionsList
/api/v1/accounts/sendTelegramMessage

HTTP response fingerprint for unauthenticated requests:

{"message":"Unauthenticated."}

With Content-Type: application/json and Access-Control-Allow-Origin: *.

CORS Header (Universal)

Access-Control-Allow-Origin: *

Present on all API responses -- usable as a detection heuristic when combined with the endpoint patterns above.


9. Defensive Recommendations

For threat intelligence teams tracking Hook deployments:

  1. Monitor port 3434 -- Unauthenticated Socket.IO endpoints are a reliable fingerprint for poorly configured Hook panels. A simple EIO=3 handshake probe will confirm.
  2. Probe /api/v1/smartInjections/getSessions -- If this returns anything other than 401, the panel is leaking SmartInjection session data (phished credentials) without authentication.
  3. MySQL on 3306 -- Internet-facing MySQL is common in hasty Hook deployments. Banner grabbing reveals version, connection count (activity level), and auth plugin.
  4. JS bundle hash -- main.1e9a5134.js identifies this specific Hook panel build. Different hashes indicate different builds or versions.

For organizations defending against Hook:

  • Block the IP 31.57.216.126 at the network perimeter
  • Monitor for Hook's characteristic overlay injection behavior on Android devices
  • Implement phishing-resistant MFA to defeat credential overlay attacks
  • Deploy mobile threat defense solutions that detect accessibility service abuse

10. Conclusion

This Hook panel is a textbook example of a threat actor deploying sophisticated malware on a poorly secured infrastructure. The malware itself -- with VNC, smart injections, keylogging, SMS interception, and crypto theft -- represents a formidable threat to Android users. But the operator made fundamental mistakes: an internet-facing MySQL instance, an unauthenticated Socket.IO event bus, a missing auth middleware on a sensitive endpoint, and a wildcard CORS policy.

The broken database is the most interesting detail. Whether it represents a botched deployment, a competed-over panel, or the result of counter-operations, it means this C2 is currently inert -- but the infrastructure is standing and could become active at any time if someone fixes the database connection. We will continue monitoring.


Investigation conducted 2026-03-07. All probing was performed against infrastructure confirmed as malicious C2 via ThreatFox.

Share: