Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2025-40634 — Exploit pour débordement de tampon basé sur la pile trouvé dans le binaire conn-indicator du routeur TP-Link Archer AX50 | Kitploit
Outils/GitHubGitHub/hacefresko/cve-2025-40634
Sécurité des Systèmes EmbarquésSécurité IoTExploitationFuzzingTests d'IntrusionSécurité MatérielleOutil d'Accès à DistanceExploitation de Binaires
GitHubhacefresko/cve-2025-40634

CVE-2025-40634

Exploit pour débordement de tampon basé sur la pile trouvé dans le binaire conn-indicator du routeur TP-Link Archer AX50

Voir le dépôt
31812il y a 11 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2025-40634

Le routeur TP-Link Archer AX50 est vulnérable à un débordement de tampon basé sur la pile dans sa version de firmware 1.0.14 Build 20240108 rel.42655(4555), conduisant à une exécution de code à distance à la fois côté LAN et côté WAN. Cette vulnérabilité a la même cause racine que CVE-2020-10881, découverte par l'équipe Flashback et largement détaillée dans leur série de vidéos à ce sujet (voir références). Cependant, le processus d'exploitation diffère légèrement, donc un nouvel exploit a dû être écrit.

Cause racine

Cette vulnérabilité se produit dans le binaire conn-indicator, chargé de vérifier si le routeur est connecté à Internet en envoyant périodiquement des requêtes DNS et en écoutant leurs réponses sur un port UDP aléatoire entre 32000 et 61000.

La fonction qui reçoit et traite d'abord ces paquets de réponse DNS est TPDns_RecvAndResolve(), située à 0x00405e3c. Lors de la réception d'un paquet avec recvfrom, il est stocké dans buf, qui a une taille de 2960 octets. Ensuite, elle vérifie que le code de retour est correct (RCODE == 0) et vérifie les nombres de questions et de réponses dans le paquet (QDCOUNT et ANCOUNT). Pour traiter les réponses, elle appelle process_resolved_IP() et lui passe un pointeur vers buf, answer_ptr, qui est un pointeur vers l'emplacement des réponses dans le paquet à l'intérieur de buf, le nombre de réponses (ANCOUNT) et d'autres drapeaux :

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 fonction process_resolved_IP(), située à 0x00405818, parcourt chaque réponse et appelle DNS_answer_parser() pour chacune d'elles. Elle passe en arguments le même pointeur vers buf, answer, qui est un pointeur vers la réponse en cours d'analyse dans le paquet original à buf, et un pointeur vers current_answer, qui est un tampon de taille 256 :

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 fonction DNS_answer_parser(), située à 0x004054e0, analyse chaque réponse individuellement, qui consiste en un nom de domaine représenté comme <len><domaine><len><domaine>.... Par exemple, example.com serait représenté par 7example3com. La fonction boucle sur chaque paire <len><domaine> qui constitue le nom de domaine dans la réponse et vérifie que <len> est inférieur à 63 (domain_name & 0xc0 != 0). Ensuite, elle appelle memcpy et copie <len> octets de answer, correspondant à <domaine>, dans current_answer. Elle répète le processus pour la paire suivante de <len><domaine> dans le nom de domaine.

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 );
}

Étant donné que current_answer ne fait que 256 octets, il est possible pour un attaquant d'envoyer un paquet avec une réponse contenant un nom de domaine suffisamment long pour déborder le tampon.

Exploitation

Le processus d'exploitation est très similaire à celui expliqué par l'équipe Flashback pour CVE-2020-10881 dans leur série de vidéos à ce sujet (voir références), avec quelques différences clés :

  1. Les adresses ne sont pas les mêmes
  2. Le code n'est pas exactement le même, donc les décalages et la taille de la pile à certains moments n'étaient pas non plus les mêmes
  3. Alors que le premier et le troisième gadgets ROP étaient les mêmes (situés à des adresses différentes), le second diffère dans l'instruction move, car maintenant le registre qui est déplacé vers $a2 comme argument count pour memcpy() est $s1 au lieu de $s0.
  4. La variable i dans process_resolved_IP() est chargée dans $s5 au lieu de $s8. Ensuite, lorsqu'elle est comparée à $v1, $v1 est prise depuis $sp+616, qui est très loin du tampon débordé, donc la pile a dû être corrompue plus loin à partir de la réponse et de la commande.
  5. Le système de fichiers est en lecture seule sauf /tmp, donc toutes les écritures et lectures doivent être effectuées là. Cela rend impossible que le shell inverse fasse moins de 62 caractères, donc pour le déployer sur la cible, il doit être servi depuis un serveur web et une commande pour le télécharger et l'exécuter doit être envoyée à la cible.

Avec cela à l'esprit, je vais expliquer l'ensemble du processus quand même.

Le binaire conn-indicator a NX activé et ASLR sur tout sauf lui-même :

Télécharger l’outil