CRITICALAPR_02_202625_MIN_READ

eSIM_BLEED

93 vulnerabilities in lpac + OpenEUICC. A CVSS 9.8 kill chain that lets a network attacker steal your SIM credentials. Every open-source eSIM tool ships with TLS validation disabled.

sim_card

SOURCE_BLEED_TEAM

eSIM Security Research

93VULNS
9.8MAX_CVSS
6KILL_CHAINS

// TL;DR

lpac is the most popular open-source eSIM LPA client. OpenEUICC is the leading Android eSIM management app that uses lpac via JNI. Together, they form the backbone of the open-source eSIM ecosystem. We found that both projects ship with TLS certificate validation completely disabled, meaning every SM-DP+ connection is vulnerable to trivial man-in-the-middle attacks. Combined with integer overflows in the DER parser and unchecked JNI pointer casts, a network attacker can steal subscriber credentials (Ki, OPc, IMSI) or achieve remote code execution.

01 // DAMAGE_REPORT

12CRITICAL
26HIGH
33MEDIUM
22LOW
DOMAINCRITHIGHMEDLOWTOTAL
MEMORY / PARSER51011834
NETWORK / TLS352212
DRIVER / OS257519
JNI BRIDGE235-10
ANDROID-38718

02 // THE_TRIPLE_THREAT

Three classes of vulnerability combine to form the deadliest attack surface. Each is dangerous alone. Together, they create a CVSS 9.8 kill chain.

lock_open
THREAT_01

TLS VALIDATION: OFF

CRITICAL8.1

Both lpac HTTP backends — curl and WinHTTP — ship with certificate validation completely disabled. This isn't a misconfiguration. It's hardcoded.

driver/http/curl.c:91-92C
libcurl._curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
libcurl._curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
driver/http/winhttp.c:71C
WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS,
    &(DWORD){SECURITY_FLAG_IGNORE_UNKNOWN_CA}, sizeof(DWORD));

OpenEUICC doubles down with AllowAllTrustManager — a TrustManager where checkServerTrusted() simply returns without doing anything. The Lint warning is suppressed with @SuppressLint.

This means any certificate is accepted. Self-signed, expired, wrong domain, revoked — all pass. A coffee-shop Wi-Fi attacker with DNS spoofing can impersonate any SM-DP+ server and intercept eSIM profile downloads containing subscriber master keys.

IMPACT

GSMA SGP.22 RSP security model completely invalidated. CVE-assignable as standalone finding.

data_object
THREAT_02

DER PARSER: CHAINABLE OVERFLOW

CRITICAL8.1

The custom DER/ASN.1 parser in euicc/derutil.c is the backbone of all eSIM protocol handling. It has three chainable integer overflows that corrupt the entire parse tree.

derutil.c:40-52 — Length field overflowC
if (result->length & 0x80) {
    uint8_t lengthlen = result->length & 0x7F;  // max 127, no cap
    result->length = 0;
    for (int i = 0; i < lengthlen; i++) {
        result->length = (result->length << 8) | *cptr;
        // lengthlen >= 5 → uint32_t wraps around
        cptr++; rlen--;
    }
}
derutil.c:61 — self.length poisoningC
result->self.length = result->value - result->self.ptr + result->length;
// length=0xFFFFFFFE + 3-byte header → self.length=1 (overflow)
// Every subsequent unpack_next() offset is now corrupted
derutil.c:66-75 — Underflow to ~4GB readC
cptr = prev->self.ptr + prev->self.length;
rlen = buffer_len - (cptr - buffer);
// When self.length is corrupted: cptr > buffer + buffer_len
// rlen wraps to ~4,294,967,295 → reads far beyond buffer
IMPACT

Chain: crafted length field → self.length poisoned → unpack_next reads arbitrary memory. Every ES10a/b/c and ES8+ parser path is affected. Reachable from network via SM-DP+ response.

memory
THREAT_03

JNI BRIDGE: ARBITRARY MEMORY READ

CRITICAL7.8

OpenEUICC calls lpac through JNI. The bridge code in lpac-jni.c casts Java jlong values directly to C pointers — without any validation — at 15 call sites.

lpac-jni.c:273 — stringDeref: double dereferenceC
// Java passes any 64-bit value as jlong "handle"
// C side blindly casts and double-dereferences:
return toJString(env, *((char **) curr));
// curr = attacker-controlled address → arbitrary memory read primitive

Additionally, ~30 JNI return values (GetStringUTFChars, FindClass, calloc) are never checked for NULL. The bridge is where Java's type safety ends and C's pointer arithmetic begins — and there are no guardrails at the crossing.

IMPACT

Arbitrary memory read from Java side. Combined with zero sensitive data clearing, previous session ICCID/Ki/IMEI can be recovered from heap.

03 // KILL_CHAIN: REMOTE PROFILE HIJACKING

CVSS 9.8REMOTE / NO_AUTH / NO_INTERACTION
01
DNS_SPOOF

Attacker poisons DNS: SM-DP+ domain resolves to attacker IP

02
TLS_BYPASS

lpac connects with self-signed cert — VERIFYPEER=0 accepts anything

03
PAYLOAD_INJECT

Attacker returns crafted ES9+ JSON with base64-encoded malicious DER

04
BASE64_OVERFLOW

base64_decode_len() integer overflow → small buffer allocated, large write

05
DER_CHAIN

Length field manipulation → self.length poisoned → unpack_next OOB R/W

06
HEAP_CORRUPT

Heap metadata overwrite → code execution

07
EXFILTRATE

Subscriber credentials extracted: Ki, OPc, IMSI — SIM cloning achieved

The absence of build hardening flags (no FORTIFY_SOURCE, no stack-protector, no PIE/RELRO) means there are no speed bumps between heap corruption and code execution. This is a textbook chain from network position to full subscriber compromise.

04 // ASAN_CONFIRMED: THE ZERO-LENGTH ICCID

One finding was independently confirmed with AddressSanitizer crash reproduction. A zero-length ICCID TLV (5A 00) in an SM-DP+ response triggers an integer underflow in hexutil.c:

hexutil.c:107 — bin2gsmbcdC
// bin_len == 0 (from zero-length ICCID TLV)
int n = euicc_hexutil_bin2hex(output, output_len, bin, bin_len);
// n = 0
n -= 1;
// n = -1, but cast to uint32_t = 0xFFFFFFFF
gsmbcd_swap_chars(output, n);
// Attempts to swap 4,294,967,295 bytes → ASAN: heap-buffer-overflow

REPRODUCTION STATUS

ASAN crash confirmed by external researcher (Jhury Kevin Lastre). Trigger requires only a single malformed TLV in SM-DP+ response. No authentication needed from attacker's side when TLS validation is disabled.

05 // MORE_KILL_CHAINS

KC-2

MALICIOUS_eUICC → RCE

PHYSICAL7.5

Malicious SIM card → DER length=0xFFFFFFFE → self.length overflow → ICCID malloc(1) → heap overflow → no stack protector → RCE

KC-3

MALICIOUS_APP → PROFILE_THEFT

ANDROID7.5

Malicious app → lpa: URI intent (exported, no permission guard) → attacker SMDP injection → TLS bypass → profile theft

KC-4

WINHTTP_STACK_OVERFLOW → RCE

WINDOWS8.1

Malicious QR code → hostname > 256 chars → sizeof/wchar mismatch → stack buffer overflow → no ASLR/canary → direct RCE

KC-5

LOCAL_SUPPLY_CHAIN

LOCAL7.8

ENV var injection (APDU_UQMI_PROGRAM) + SO hijacking (dlopen no verify) + Magisk TMPDIR race → arbitrary execution including root

KC-6

JNI_HANDLE → MEMORY_READ

LOCAL7.0

jlong handle manipulation → double pointer deref → arbitrary memory read → recover ICCID/Ki from heap (no zeroing)

06 // ROOT_CAUSE_ANALYSIS

93 findings reduce to 5 systemic patterns. Fix the patterns, not just the symptoms.

01

ZERO-LENGTH DEFENSE ABSENT

bin2hex, bin2gsmbcd, base64_decode — none handle len=0. TLV length=0 is valid BER/DER.

02

INTEGER OVERFLOW GUARDS MISSING

2*bin_len, length*2+1, (len+2)/3*4+1 — every size calculation is unguarded. uint32_t overflow → small malloc → massive write.

03

RETURN VALUES IGNORED

bin2hex/bin2gsmbcd return -1 on error. Every ES8/ES9/ES10 parser ignores it and proceeds with corrupted data.

04

JNI RETURNS UNCHECKED

GetStringUTFChars, FindClass, calloc — 30+ call sites with no NULL check. The Java-to-C boundary has zero safety net.

05

BUILD HARDENING ABSENT

No FORTIFY_SOURCE, no stack-protector, no PIE/RELRO. Exploit development difficulty drops from 'hard' to 'trivial'.

07 // WHAT_IS_AT_STAKE

An eSIM profile contains:

Ki

Subscriber authentication key. With this, an attacker can clone the SIM and receive all calls/SMS.

OPc

Operator variant algorithm config. Combined with Ki, enables full Milenage authentication bypass.

IMSI

International mobile subscriber identity. Enables tracking, targeted interception, and identity theft.

These are the crown jewels of mobile security. SM-DP+ profile download is the only moment these credentials traverse a network. GSMA designed SGP.22 with TLS mutual authentication specifically to protect this moment. lpac removes that protection entirely.

08 // NOT_ALL_BAD

Credit where due. Both projects have genuine security strengths:

  • check_circleGSMA CI root certificate pinning— When TLS validation IS active, OpenEUICC pins to GSMA CI roots, not the system CA store
  • check_circleHTTPS enforcement— HttpInterfaceImpl rejects non-https:// URLs
  • check_circleposix_spawnp over system()— uqmi uses explicit argv, preventing shell injection
  • check_circlePrivilege separation— OpenEUICC separates privileged and unprivileged builds at compile time
  • check_circleEuiccService permission guard— BIND_EUICC_SERVICE signature protection on the system service

09 // FINAL_ASSESSMENT

The open-source eSIM stack has fundamental security issues that go beyond individual bugs. TLS validation disabled as a default, combined with a custom ASN.1 parser written without integer overflow guards, and a JNI bridge with no type safety — these are architectural problems that require architectural solutions.

The most urgent fix is trivially simple: set CURLOPT_SSL_VERIFYPEER to 1. One line of code closes the most dangerous attack vector. The DER parser and JNI bridge require deeper work, but the TLS fix is a one-line change that should have been made years ago.

If you're using lpac or OpenEUICC — or any tool built on them — assume your eSIM provisioning traffic has been interceptable by any network-adjacent attacker.

eSIMGSMASGP.22lpacOpenEUICCTLSDERJNISAST

RESPONSIBLE DISCLOSURE IN PROGRESS. FULL TECHNICAL DETAILS WILL BE RELEASED AFTER VENDOR PATCHES ARE AVAILABLE.