Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2025-40634 — Exploit para estouro de buffer baseado em pilha encontrado no binário conn-indicator no roteador TP-Link Archer AX50 | Kitploit
Ferramentas/GitHubGitHub/hacefresko/cve-2025-40634
Segurança de Sistemas EmbarcadosSegurança IoTExploraçãoFuzzingTestes de PenetraçãoSegurança de HardwareFerramenta de Acesso RemotoExploração de Binários
GitHubhacefresko/cve-2025-40634

CVE-2025-40634

Exploit para estouro de buffer baseado em pilha encontrado no binário conn-indicator no roteador TP-Link Archer AX50

Ver Repositório
31812há 11 mesesRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2025-40634

O roteador TP-Link Archer AX50 é vulnerável a um estouro de buffer baseado em pilha na versão de firmware 1.0.14 Build 20240108 rel.42655(4555), levando à execução remota de código tanto na LAN quanto na WAN. Essa vulnerabilidade tem a mesma causa raiz do CVE-2020-10881, descoberta pela equipe Flashback e amplamente detalhada em sua série de vídeos sobre o assunto (consulte as referências). No entanto, o processo de exploração difere um pouco, portanto, um novo exploit teve que ser escrito.

Causa raiz

Esta vulnerabilidade ocorre no binário conn-indicator, que é responsável por verificar se o roteador está conectado à internet, enviando periodicamente consultas DNS e escutando suas respostas em uma porta UDP aleatória entre 32000 e 61000.

A função que recebe e processa primeiro esses pacotes de resposta DNS é TPDns_RecvAndResolve(), localizada em 0x00405e3c. Ao receber um pacote com recvfrom, ele é armazenado em buf, que tem um tamanho de 2960 bytes. Em seguida, verifica se o código de retorno está correto (RCODE == 0) e verifica os números de perguntas e respostas no pacote (QDCOUNT e ANCOUNT). Para processar as respostas, ela chama process_resolved_IP() e passa um ponteiro para buf, answer_ptr, que é um ponteiro para onde as respostas estão localizadas dentro do pacote em buf, o número de respostas (ANCOUNT) e outros sinalizadores:

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

  [...]

Função process_resolved_IP(), localizada em 0x00405818, percorre cada resposta e chama DNS_answer_parser() para cada uma delas. Ela passa como argumentos o mesmo ponteiro para buf, answer, que é um ponteiro para a resposta atual sendo analisada dentro do pacote original em buf, e um ponteiro para current_answer, que é um buffer de tamanho 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 );
}

Função DNS_answer_parser(), localizada em 0x004054e0, analisa cada resposta individualmente, que consiste em um nome de domínio representado como <len><domain><len><domain>.... Como exemplo, example.com seria representado como 7example3com. A função itera por cada par de <len><domain> que constituem o nome de domínio na resposta e verifica se <len> é menor que 63 (domain_name & 0xc0 != 0). Em seguida, chama memcpy e copia <len> bytes de answer, correspondentes a <domain>, para current_answer. Repete o processo para o próximo par de <len><domain> no nome de domínio.

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

Como current_answer tem apenas 256 bytes de comprimento, é possível para um atacante enviar um pacote com uma resposta contendo um nome de domínio grande o suficiente para estourar o buffer.

Exploração

O processo de exploração é muito semelhante ao explicado pela equipe Flashback para o CVE-2020-10881 em sua série de vídeos sobre o assunto (veja as referências), com algumas diferenças importantes:

  1. Os endereços não são os mesmos
  2. O código não é exatamente o mesmo, então offsets e o tamanho da pilha em alguns momentos também não eram os mesmos
  3. Enquanto o primeiro e o terceiro gadgets ROP eram os mesmos (localizados em endereços diferentes), o segundo difere na instrução move, pois agora o registrador que é movido para $a2 como argumento count para memcpy() é $s1 em vez de $s0.
  4. A variável i em process_resolved_IP() é carregada em $s5 em vez de $s8. Então, quando comparada a $v1, $v1 é obtido de $sp+616, que está muito longe do buffer estourado, então a pilha teve que ser corrompida ainda mais a partir da resposta e do comando.
  5. O sistema de arquivos é somente leitura, exceto /tmp, então todas as escritas e leituras devem ser feitas lá. Isso torna impossível que o shell reverso tenha menos de 62 caracteres, então, para implantá-lo no alvo, ele deve ser servido a partir de um servidor web e um comando para baixá-lo e executá-lo deve ser enviado ao alvo.

Com isso em mente, explicarei todo o processo de qualquer forma.

O binário conn-indicator tem NX ativado e ASLR em tudo, exceto nele mesmo:

Baixar ferramenta