## https://sploitus.com/exploit?id=EE876DA9-1DB6-57D0-AC51-30AD1C13E53A
# CVE-2024-20154: NB-IoT SIB1-NB Stack Overflow in MediaTek MT6769 Baseband
**Classification:** CWE-121 โ Stack-based Buffer Overflow
**Severity:** Critical (MediaTek bulletin) ยท 8.8 High, Attack Vector: Adjacent (CISA-ADP)
**Type:** Remote Code Execution โ no user interaction, no prior association
**Disclosure:** MediaTek Security Bulletin, January 6, 2025 https://corp.mediatek.com/product-security-bulletin/January-2025
**Analyzed target:** Samsung Galaxy A14 SM-A145R โ MT6769 family (Helio G80), within MediaTek's affected chipset list - Firmware was emulated under safe conditions.
**Status:** Patched.
---
## Background and motivation
This was my first published baseband research. I come from a background far away from telecoms
infrastructure, lawful interception mediation layers, stingray and IMSI-catcher analysis,
and embedded device security โ I had not previously done deep firmware reverse
engineering on a cellular modem. I wanted to prove to myself that a structured analytical
methodology adapts across targets, and that familiarity with a specific platform can be
replaced by rigorous chain-tracing. NB-IoT stood out because it sits at a genuinely
dangerous intersection: the protocol is designed for constrained IoT devices, the attack
surface is pre-association, and the modem stack processes it regardless of what the handset
user is doing.
When the patched firmware was analyzed and the
vulnerable pattern confirmed absent, the AI system used for mass analysis of the firmware before targeting specific functions
independently matched the reconstructed bug class, conditions, and affected firmware family to the description of CVE-2024-20154.
The technical conclusions are the analyst's own.
---
## 1. Introduction
The phone in your pocket contains at least two separate computers. The one you interact with
runs Android. The other โ the baseband โ runs completely independently, handles all radio
communication, and is almost entirely invisible to the operating system above it. Android may
be fully patched. The browser may be sandboxed. The user may never tap a malicious link. None
of that matters if the vulnerable code is in the modem firmware that processes radio signals
before the application processor is involved.
CVE-2024-20154 is exactly that kind of vulnerability.
A malformed NB-IoT system-information broadcast causes the MediaTek modem firmware to accept
an attacker-controlled scheduling count, carry that count through the RRC-to-L1 configuration
path without ever clamping it, and eventually use it as the loop bound for a stack-writing
loop inside the NB-IoT broadcast-channel handler. When the count exceeds the destination
arrays' capacity, the loop writes past them, reaches saved registers on the stack, and
overwrites the saved return address. The function then restores the corrupted value into the
return-address register and jumps to it.
What makes the severity what it is:
- The vulnerable code path is exercised during cell camping โ after synchronizing to a cell
but before any RRC connection, any authentication, any user interaction.
- The input is an over-the-air broadcast. The phone cannot authenticate the source.
- The baseband firmware in the analyzed build runs with no ASLR, no stack canaries, no
non-executable stack, and no control-flow integrity. A saved-return-address overwrite
translates directly to program-counter control.
The vulnerability was published in MediaTek's January 6, 2025 Security Bulletin with a
Critical severity rating, affecting the LR12A modem family among others. Samsung incorporated
the fix into their February 2025 Security Maintenance Release.
**This post does not publish a weaponized exploit and is not reproducible from what is
published here.** The goal is to show where the chain breaks, why each layer failed to stop
it, and what it takes to validate a baseband bug responsibly when you cannot attach a
debugger to the live modem.
---
## 2. Target and Environment
### 2.1 Device and firmware
Primary target: Samsung Galaxy A14 (SM-A145R). The radio subsystem is driven by a MediaTek
baseband processor in the MT6769 chipset family (Helio G80). The MT6769 family is explicitly
listed in MediaTek's affected chipset list for CVE-2024-20154.
```
AP/CP firmware: A145RXXU1AWD1
Modem software: MOLY LR12A.R3.TC10.6M.A14.PR.SP.V1.P5
Build date: 2023-04-18
```
The baseband firmware is not Android code. It is a separate embedded system on the radio
subsystem of the SoC with its own CPU, its own RTOS, and its own memory space, outside the
Android process sandbox.
### 2.2 Modem architecture
Analysis of the extracted binary shows the modem processor runs MIPS32 with MIPS16e2
compressed instructions in little-endian mode. MIPS16e2 is a 16-bit encoding extension for
embedded code-size reduction โ consistent with MediaTek's approach for Helio-generation
basebands, confirmed by independent published baseband research on this SoC family.
The operating system is Nucleus RTOS, providing task scheduling, IPC message queues, and a
pool-based memory allocator. There is no kernel/user privilege separation, no memory
protection unit enforcement between tasks, and no hardware stack-guard mechanism.
All addresses in this post are virtual addresses, as loaded in Ghidra at base `0x90000000`.
### 2.3 Mitigations (observed in analyzed build)
| Mitigation | Status | Effect |
|---|---|---|
| ASLR | Absent | Firmware addresses are static and predictable from the image |
| Stack canary | Absent | `SAVE`/`RESTORE` stores callee-saved registers with no guard value |
| NX / W^X | Absent | Stack memory is executable |
| CFI | Absent | Return addresses are not validated against any policy |
### 2.4 Analysis approach
Three parallel tracks:
**Static analysis.** Samsung firmware package โ CP partition extraction โ `md1img.img` โ
Ghidra (MIPS LE 32-bit, base `0x90000000`) with MediaTek engineering symbols recovered from
the firmware debug section using the NCC Group `mtk_bp` toolset.
**Dynamic validation.** Unicorn Engine (MIPS32 emulation) was used to execute specific
firmware routines in isolation across two phases. Phase 1 attempted to prove the unclamped
copy of `si_count` into channel context through the native instruction pair. Phase 2 executed
the vulnerable loop on real firmware bytes and confirmed that the firmware's own instructions
corrupt the saved return address. Where Phase 1 could not run fully natively โ because the
RTOS service-object environment required by the CPHY dispatch path was not reconstructed โ
the side effect was modeled directly and labeled as such in all output.
**Radio-side validation.** srsRAN 4G with a ZMQ loopback โ software-only, no RF emission โ
confirmed that the test payload survives NB-IoT PHY encoding and transport-block delivery.
---
## 3. Attack Surface: NB-IoT and SIB1-NB
### 3.1 Pre-association attack surface
NB-IoT (Narrowband Internet of Things) is 3GPP Release 13, designed to connect constrained
IoT devices using existing licensed LTE spectrum. It is implemented in a wide range of modern
cellular SoCs including those in consumer smartphones.
While in RRC_IDLE, before any RRC connection is established, a device searching for service
will:
1. Synchronize with the cell's timing signals (NPSS/NSSS)
2. Decode the Master Information Block over NPBCH (640 ms transmission window)
3. Decode SIB1-NB from NPDSCH (2560 ms schedule)
4. Use the scheduling information in SIB1-NB to locate additional system information blocks
At step 3, the modem processes a message from an entity it has not authenticated, before any
connection or user interaction. A rogue transmitter satisfying normal cell selection
conditions will be processed.
```
+------------------+ +---------------------+
| Rogue Base Stn | | Target UE (Modem) |
+--------+---------+ +----------+----------+
| |
| NPSS/NSSS sync |
|--------------------------------------->|
| MIB-NB (640 ms cycle) |
|--------------------------------------->|
| SIB1-NB (malformed, si_count > 8) |
|--------------------------------------->| โ vulnerability triggered
| [no RRC connection established] |
```
### 3.2 The scheduling count field
`SIB1-NB` is defined in 3GPP TS 36.331. Its `schedulingInfoList` field carries how many
System Information messages the cell broadcasts, constrained by the spec to a maximum of 8
entries (`1..maxSI-Message-NB-r13 = 8`). This is a protocol-layer constraint. The
memory-safety constraint โ that the list length must not exceed the capacity of the
destination arrays โ must be enforced separately by the firmware.
It was not.
---
## 4. Firmware Extraction and Symbol Recovery
Firmware was obtained from a Samsung CP package and extracted using the NCC Group `mtk_bp`
toolset:
```
md1img.img โ md1_extract.py โ 000_md1rom (17.8 MB code image)
โ 017_md1_dbginfo (XZ-compressed CATI debug symbols)
```
The CATI debug section was decompressed and parsed with `mtk_dbg_extract.py symbols`, then
imported into Ghidra via `ImportSymbolsScript.py`. The result was full internal function
names throughout the modem stack โ ERRC layer, L1 channel management, IPC subsystem, and the
NB-IoT BCCH handler chain โ allowing semantics-guided chain reconstruction.
All function names in this post come from MediaTek's own embedded debug symbols extracted
from the firmware image.
---
## 5. Vulnerability
### 5.1 The vulnerable loop
`el1_ch_nbcch_resume_req` (`0x90213940`) handles the NB-IoT broadcast-channel resume event.
Its MIPS16e2 function prologue:
```asm
90213940: save 0xE8, ra, s0-s1
```
The `SAVE` instruction decrements sp by `0xE8` and stores callee-saved registers downward:
```
old_sp (= new_sp + 0xE8)
new_sp + 0xE4 saved ra โ overflow target
new_sp + 0xE0 saved s1
new_sp + 0xDC saved s0
new_sp + 0x98 si_sched_arr [34 halfwords = 68 bytes]
new_sp + 0x78 si_type_arr [32 bytes]
new_sp + 0x00 โ stack pointer after SAVE
```
From Ghidra decompile of the actual firmware binary:
```c
for (uVar6 = 0; uVar6 The number of SI scheduling entries must never exceed the destination array capacity.
3GPP provides the intended protocol bound (8 entries). The firmware needed to enforce the
memory-safety bound at every layer where the count becomes an index or loop limit. In the
vulnerable build, the count traveled from the SIB1-NB broadcast field through the ERRC
decoder, into the CPHY_CFG_REQ message, across an IPC boundary into the L1 task, into
channel context BSS, and into a stack-writing loop โ without any layer clamping it.
---
## 6. The Call Chain โ How It Was Found
### 6.1 The two-path confusion
Two structurally similar but distinct paths can deliver a 0x760-byte configuration buffer to
`el1_ch_nbcch_main`:
| Path | Source | `[+0x99]` value | Relevance |
|---|---|---|---|
| Path A (ERRC โ L1 IPC) | ERRC builds CPHY_CFG_REQ from decoded SIB1-NB | `schedulingInfoList.count` from OTA | **The vulnerable path** |
| Path B (L1 internal) | `el1_ch_scs_ind_send` builds internal IPC body | Hardcoded `1` | Not vulnerable |
Path B confirmed the message format โ byte `+0x99` is the SI count consumed by
`el1_ch_nbcch_start`. Because its count is always hardcoded to 1, it cannot overflow. The
externally influenced path is Path A.
### 6.2 The writer search
Pattern-searching the firmware for any instruction writing to offset `+0x99` produced noise:
T1 (direct `sb`, ~100 hits), T2 (split base, 5 hits), T3 (computed offset, 0), T4
(overlapping `sh`/`sw`, ~465). The ERRC channel management functions were absent from the
`msg_send6` cross-reference list because ERRC uses `errc_com_send_msg`. An emulation probe
confirmed the write was ERRC-side: running the Unicorn harness from the L1 dispatcher and
watching for writes to the buffer's `+0x99` offset captured nothing from the L1 side.
```
[RUN] dispatcher=0x90214418 watching IPC_msg+0x99
NO writes to IPC_msg+0x99 caught from dispatcher.
Write happened before el1_ch_nbcch_main โ confirmed ERRC-side.
```
### 6.3 Resolution via IPC routing key
Following `sap_id = 0x501F` in `errc_com_send_msg` identified the routing to
`el1_chmgm_errc_cfg_req_in_idle`, which stores the CPHY_CFG_REQ pointer at
`L1_ctx[+0x323C]` and begins the downstream dispatch.
### 6.4 Confirmed call chain
```
SIB1-NB schedulingInfoList.count (demonstrator: 40)
โ
โผ [ERRC task]
errc_chm_ch_ctrl_req_hdlr
โ errc_chm_call_ctrl
โ errc_chm_l1_main
โ errc_chm_l1_call_ctrl
โ errc_chm_l1_snd_cphy_cfg_req โ allocates 0x760-byte CPHY_CFG_REQ
โ errc_chm_l1_set_cphy_req_nbcch_cfg
โ errc_chm_l1_set_bcch_inf
โ errc_chm_l1_set_bcch_si_reception โ Layer A: CPHY_CFG_REQ[+0x99] = 40
โ errc_com_send_msg(sap=0x501F) โ IPC to L1
โ
โผ [L1 task]
el1_ch_rcv_ilm (sap 0x501F)
โ el1_chmgm_errc_cfg_req_in_idle โ stores CPHY_CFG_REQ @ L1_ctx[+0x323C]
โ el1_chmgm_nbcch_handler
โ el1_ch_nbcch_main
โ el1_ch_nbcch_cphy_cfg_req_process โ no si_count validation
โ el1_ch_nbcch_start @ 0x90213444 โ Layer B: lbu + sb, no clamp
โ
โผ
ch_ctx[0x40A] = 40 (persists in BSS)
โ
โผ [state machine advances to 0x0B, event 0x2C]
el1_ch_nbcch_resume_req @ 0x90213940 โ Layer C: loop bound from ch_ctx[0x40A]
โ
โผ
stack overflow โ jrc ra โ PC = attacker-controlled
```
---
## 7. Validation
This specific SM-A145R model does not appear to exercise
the vulnerable NB-IoT code path in normal ship-build operation, which is why hybrid
emulation and static analysis were used rather than direct hardware reproduction.
**Proven statically.** The firmware binary contains the vulnerable instruction pair. The call
chain is reconstructed from symbols, cross-references, and decompiled function bodies.
**Executed natively in emulation.** The loop inside `el1_ch_nbcch_resume_req` ran on real
MediaTek firmware bytes in Unicorn Engine (MIPS32). The firmware's own `sh` instruction at
`0x90213B02` wrote to the saved return-address slot. The `RESTORE` instruction loaded the
corrupted value into `$ra`, and `jrc ra` transferred control.
**Modeled explicitly.** The `lbu`/`sb` copy in `el1_ch_nbcch_start` could not be run fully
natively because the RTOS callback-table dispatch path within `el1_ch_nbcch_cphy_cfg_req_
process` expected live Nucleus heap objects the flat emulator did not provide. The resume
handler (`el1_ch_nbcch_resume_req`) in Phase 2 reads only from BSS-resident channel context
and does not hit that same dispatch path, which is why Phase 2 ran natively without
requiring the same scaffolding. The Phase 1 side effect โ one byte from `IPC_msg[+0x99]`
written to `ch_ctx[+0x40A]` โ was modeled directly and labeled `[PHASE1-MODEL]`.
**Not claimed.** A complete end-to-end over-the-air hardware reproduction.
### 7.1 Anti-tamper
The harness enforced one rule: no hook was permitted to write the proof marker into the saved
return-address slot. Every non-firmware memory write was tracked.
```
[PROOF-W] saved_RA write: pc=0x90213b02 addr=new_sp+0xE4 value=0xbeef
[PROOF-W] saved_RA write: pc=0x90213b02 addr=new_sp+0xE6 value=0xdead
[ANTI-TAMPER] no hook wrote RA=0xDEADBEEF โ firmware only
anti_tamper_fail = False
```
`0x90213B02` is the `sh` instruction inside the loop body. The firmware placed these bytes at
the predicted stack offset. In little-endian storage, the halfwords `0xBEEF` and `0xDEAD`
at `new_sp+0xE4` and `new_sp+0xE6` combine to `[ef be ad de]` = `0xDEADBEEF`.
### 7.2 Emulation result
```
[REGS]
RA = 0xdeadbeef โ firmware wrote this; decisive proof
PC = 0x00000000 โ Unicorn unmapped-fetch artifact, not a hardware exception vector
[STACK] new_sp+0xE4: [ef be ad de] โ little-endian 0xDEADBEEF
[PC-EXPLAIN] PC=0 is an unmapped-fetch artifact; decisive proof is
RA=0xDEADBEEF + stack bytes [ef be ad de] + [PROOF-JRC]
CONFIRMED: saved RA = 0xDEADBEEF
```
### 7.3 ZMQ delivery
To confirm the test payload survives NB-IoT PHY encoding and transport-block delivery, srsRAN
4G with a ZMQ loopback (no RF emission) was used. The payload was a deliberately
non-conformant vector โ the ASN.1 SIZE constraint on `schedulingInfoList` was relaxed to
allow 40 entries, with pycrate round-trip decode confirming the count field at byte 14.
```
SIB1 received
SIB2 activated
exit 0
```
This confirms transport-layer delivery. Firmware-side ASN.1 behavior is established by the
Layer A static analysis.
---
## 8. Control-Flow Mechanics
### 8.1 Two phases, one task, persistent state
The `el1_ch` task has one stack. Phase 1 and Phase 2 are two separate IPC events processed
sequentially by the same task, with the stack fully unwinding between them. The count
persists in channel context BSS, not on the stack:
```
EVENT: CPHY_CFG_REQ (Phase 1)
el1_ch_nbcch_start
lbu v0, 0x99(s0)
sb v0, 0x40A(s1) โ ch_ctx[0x40A] = attacker value, written to BSS
returns โ stack fully unwound
EVENT: resume (state 0x0B, msg 0x2C) (Phase 2)
el1_ch_nbcch_resume_req โ frame 0xE8, reads ch_ctx[0x40A] as loop bound
loop ร 40 โ saved RA corrupted
jrc ra โ attacker-controlled PC
```
`ch_ctx[0x40A]` lives in BSS and retains the attacker's value until the modem resets or a
subsequent channel configuration overwrites it.
### 8.2 Stack frame
```
old_sp (= new_sp + 0xE8)
new_sp + 0xE4 saved ra
new_sp + 0xE0 saved s1
new_sp + 0xDC saved s0
new_sp + 0x98 si_sched_arr [34 halfwords = 68 bytes]
new_sp + 0x78 si_type_arr [32 bytes]
```
Frame size confirmed by emulation measurement. Array positions confirmed by the emulation
result โ RA written at iteration 38, consistent with `si_sched_arr` starting at `new_sp+0x98`.
---
## 9. Evidence
### 9.1 Gates before the vulnerable loop
| Gate | Condition | Where checked |
|---|---|---|
| NB-IoT active path | `cfg_type == 1` | `el1_ch_nbcch_cphy_cfg_req_process` |
| Channel not complete | `done_flag == 0` | `ch_ctx[0x438]` |
| State machine `0x0B` | NBCCH in resume state | `el1_ch_nbcch_main` dispatch |
| si_count nonzero | `ch_ctx[0x40A] > 0` | Loop condition |
| Band class โค 2 | Valid NB-IoT mode | Entry of `el1_ch_nbcch_resume_req` |
| `el1_chmgm_cell_info_get` nonzero | Cell in service table | Called in `el1_ch_nbcch_start` |
### 9.2 Functions confirmed not to clamp si_count
| Function | Address | Role | Clamp? |
|---|---|---|---|
| `errc_chm_l1_set_bcch_si_reception` | ERRC range | Writes CPHY_CFG_REQ[+0x99] | **None** |
| `el1_ch_nbcch_cphy_cfg_req_process` | `0x90213F80` | Routes CPHY config into L1 | N/A โ does not read si_count |
| `el1_chmgm_cell_info_get` | `0x9020E0D0` | Validates EARFCN/PCI | N/A |
| `el1_ch_nbcch_start` | `0x90213444` | Copies count to BSS | **None** |
| `el1_ch_nbcch_resume_req` | `0x90213940` | Uses count as loop bound | **None** |
---
## 10. Patch Analysis
### 10.1 Versions
| Build | Modem firmware | Build date |
|---|---|---|
| Vulnerable | `A145RXXU1AWD1`, MOLY `LR12A...V1.P5` | 2023-04-18 |
| Patched | `A145RXXUDDZC2`, MOLY `LR12A...V3.P8` | 2025-04-23 |
Build dates confirmed by extracting `md1_dbginfo` metadata from both images. Patch ID:
MOLY00720348 ยท Issue ID: MSV-2392.
### 10.2 What changed
**`el1_ch_nbcch_start` (`0x90213444` in vulnerable binary):** The `lbu`/`sb` instruction
pair is absent. The direct copy of `IPC_msg[+0x99]` into `ch_ctx[+0x40A]` is gone.
**`el1_ch_nbcch_resume_req` (`0x90213940` in vulnerable binary):** The stack-writing loop
over `ch_ctx[0x40A]` is absent. The architecture where an untrusted byte becomes a loop bound
over fixed-size stack arrays no longer exists. The function body is replaced with a different
dispatch structure.
**`errc_chm_l1_set_bcch_si_reception`:** The unbounded counter loop is replaced with
validation-oriented helper calls.
### 10.3 New validation functions
Two functions present in the patched binary are absent from the vulnerable binary:
```
el1_ch_nbcch_param_check
el1_ch_scell_param_check
```
A single-line bounds check would appear as a few added instructions inside an existing
function at the same address. What the patched binary shows is an architectural revision:
the NBCCH scheduling path was redesigned so that the `si_count`-as-loop-bound pattern no
longer exists anywhere in the path.
### 10.4 CVE attribution
The vulnerability described here matches CVE-2024-20154 as published by MediaTek on January
6, 2025. Confirmation basis:
- Affected firmware family (LR12A) matches MediaTek's bulletin.
- Vulnerability class โ stack overflow, missing bounds check, RCE from rogue base station,
no user interaction โ matches the CVE description and NVD record.
- The patched firmware removes exactly the code structures identified as vulnerable.
- Patch ID MOLY00720348 confirmed from MediaTek's bulletin and the Android Security Bulletin
(A-376809176).
- AI-assisted analysis independently matched the reconstructed pattern to CVE-2024-20154
before manual confirmation.
---
## 11. Ethics and Responsible Disclosure
### 11.1 What this post does not contain
No weaponized exploit. No firmware binary content. No malformed payload bytes. No
step-by-step procedure for triggering the vulnerability against a live device. The
information needed to reproduce a working attack โ complete payload construction for the
specific modem decoder, RTOS heap-object scaffolding for full Phase 1 emulation, over-the-air
radio configuration โ is deliberately absent.
### 11.2 Why ZMQ loopback and emulation are the ethical approach
ZMQ loopback means no signal was ever transmitted over the air. No real device was targeted.
No carrier network was involved. The proof runs entirely in a contained software environment
on hardware owned by the analyst. This is the correct approach for validating a pre-association
radio vulnerability โ transmitting a malformed broadcast would affect any device within range.
Unicorn emulation demonstrates that the vulnerable behavior is in the firmware binary itself,
reproducibly, independent of any specific device state or radio environment. This is a
technically stronger claim than a single hardware crash, and it avoids deploying anything
over the air.
### 11.3 MediaTek intellectual property
All analysis was performed on firmware legitimately obtained from a consumer device and from
Samsung's publicly released firmware packages. No proprietary documentation was used.
Internal MediaTek struct definitions and IPC message formats are described only to the extent
necessary to explain the memory-safety failure. They are not published as specifications.
---