relationship-gtpc-user derives a single per-subscriber correlation key as Hashkey = md5(IMEI + IMSI + phone_number + vsys_id) from mobile-core GTP-C signaling, and a separate per-tunnel key gtp_uuid = md5(uplink_teid + "|" + downlink_teid). On every update it writes not just the primary user<->TEID row (tsg_galaxy:relation_user_teid) but up to four additional secondary-index rows into tsg_galaxy:gtpc_knowledge_base, one per identifier (IMEI, IMSI, phone number, APN) with a type-digit prefix ("0"/"1"/"2"/"3") and — for phone number and APN — the value's characters reversed before use as the row key, a range-scan-friendly key design that lets any of the four identifiers be used to look up the associated tunnel/session independently.
ParseFunction.java: String md5Str = DigestUtils.md5Hex(entity.getGtp_imei() + entity.getGtp_imsi() + entity.getGtp_phone_number() + entity.getCommon_vsys_id()); entity.setHashkey(md5Str); -- HbaseSink.java getupdateRows(): String imeikey = "0" + gtp.getGtp_imei() +"|"+ vsys_id + "|" + gtp_uuid; String imsikey = "1" + gtp.getGtp_imsi() ...; String pnkey = "2" + reverse(phone_number) ...; String apnkey = "3" + reverse(apn) ...
Defense implications
- Mobile-core correlation is not limited to a single identifier — IMEI, IMSI, phone number, and APN are each independently indexed against the same tunnel/session, so a circumvention app that changes only one of these (e.g. a fresh IMSI from a new SIM, holding the same device/IMEI) does not by itself break the tunnel-to-identity link, since the other identifiers and the still-present TEID pairing continue to resolve the same knowledge-base entries.
Cited artifacts
mesalab_git—galaxy/tsg_olap/relationship-gtpc-user.bundle:src/main/java/com/zdjizhi/utils/functions/ParseFunction.java(source-code)mesalab_git—galaxy/tsg_olap/relationship-gtpc-user.bundle:src/main/java/com/zdjizhi/utils/hbasepackage/HbaseSink.java(source-code)mesalab_git—galaxy/tsg_olap/relationship-gtpc-user.bundle:src/main/java/com/zdjizhi/pojo/Entity.java(source-code)
Provenance pointers only — no leak content is rehosted. Raw material: Enlace Hacktivista.
Related findings
radius-relationship-hbase (galaxy/tsg_olap) builds a bidirectional, MD5-keyed RADIUS accounting index in HBase — a framed-IP-to-account table and its account-to-IP mirror — so any subscriber account or assigned IP can be looked up in O(1) from either direction, per multi-tenant vsys_id. Each direction is fronted by a Guava write-through cache (10M-entry cap, 24h TTL) so lookups do not hit HBase on the hot path.
log-subscriber-hbase-datacenter is a Storm topology that, on a 300-second tick, scans a "national center" HBase cluster (national.hbase.zookeeper.servers) for subscriber_id/account rows changed in that window and replicates them into a separate "branch/regional datacenter" HBase cluster (center.hbase.zookeeper.servers, table sub:subscriber_info) — confirming a two-tier, hub-and-spoke data-federation architecture where subscriber-account correlation data is centrally maintained at a national level and periodically pushed down to per-datacenter local copies for regional/site-level querying.
The VoIP-fusion correlation key linking a SIP signaling record to its RTP media record is not timing/heuristic-based -- it is an exact match between the negotiated media endpoint fields parsed out of the SIP record (from_rtp_ip/from_rtp_port/to_rtp_ip/to_rtp_port, i.e. the SDP-negotiated RTP endpoints carried in the SIP signaling) and the actual observed RTP flow's four-tuple (common_client_ip/port, common_server_ip/port). A separate normalization rule sorts every four-tuple by port then IP (smaller first) before use as a join key, and common_vsys_id is the default grouping/partition key.
VoipCalibrationWindowFunction only auto-merges a candidate group when it is exactly one-to-many (1 RTP matched to N SIP records, or 1 SIP matched to N RTP records); any other cardinality within a correlation-key bucket (e.g. 2 SIP + 2 RTP) is explicitly logged as anomalous ("Not logical") and every record in that bucket is emitted individually as an unmerged "violation" log rather than being forced into a merge or silently dropped.
VoIP correlation runs in two sequential time windows, not one -- an initial tumbling window (voip.calibration.window.time, configured at 30s) attempts to fuse SIP/RTP records sharing a key, and any record still unmatched after that window is held in a second in-memory cache and retried against a longer secondary window (sec.combine.sr.cache.secs, configured at 60s) before finally being emitted unmerged -- giving the pipeline up to ~90s of tolerance for SIP and RTP legs of the same call arriving out of sync.
Both VoIP-correlation implementations are boundary-aware rather than treating every SIP/RTP flow uniformly. The legacy pipeline (log-stream-voip-relation) has a configurable check.inner.network toggle and an isInnerIp() check that classifies purely-internal-network SIP flows into a distinct "sip-in" type which is passed straight to output unmerged (never fed into the correlation cache); the newer Easy-Stream rewrite (sip-rtp-correlation) exposes the same internal/external distinction as first-class Flink Table UDFs (IS_INTERNAL_IP_ADDRESS, HAS_EXTERNAL_IP_ADDRESS).