Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-40634 — TP-Link Archer AX50 라우터의 conn-indicator 바이너리에서 발견된 스택 기반 버퍼 오버플로에 대한 익스플로잇 | Kitploit
도구/GitHubGitHub/hacefresko/cve-2025-40634
Embedded Systems SecurityIoT SecurityExploitationFuzzingPenetration TestingHardware SecurityRemote Access ToolBinary Exploitation
GitHubhacefresko/cve-2025-40634

CVE-2025-40634

TP-Link Archer AX50 라우터의 conn-indicator 바이너리에서 발견된 스택 기반 버퍼 오버플로에 대한 익스플로잇

저장소 보기
3181211개월 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2025-40634

TP-Link Archer AX50 라우터는 펌웨어 버전 1.0.14 Build 20240108 rel.42655(4555)에서 스택 기반 버퍼 오버플로우 취약점에 영향을 받으며, LAN과 WAN 양쪽에서 원격 코드 실행으로 이어집니다. 이 취약점은 Flashback 팀이 발견하고 그들의 비디오 시리즈에서 자세히 다룬 CVE-2020-10881과 동일한 근본 원인을 가지고 있습니다(참조 자료 확인). 다만 익스플로잇 과정이 다소 달라서 새로운 익스플로잇을 작성해야 했습니다.

근본 원인

이 취약점은 라우터가 인터넷에 연결되어 있는지 확인하는 conn-indicator 바이너리에서 발생합니다. 이 바이너리는 주기적으로 DNS 쿼리를 보내고 32000에서 61000 사이의 임의의 UDP 포트에서 응답을 수신합니다.

이러한 DNS 응답 패킷을 수신하여 처음 처리하는 함수는 0x00405e3c에 위치한 TPDns_RecvAndResolve()입니다. recvfrom으로 패킷을 수신하면 2960바이트 크기의 buf에 저장합니다. 그런 다음 반환 코드가 올바른지(RCODE == 0) 확인하고 패킷의 질문 수와 답변 수(QDCOUNT 및 ANCOUNT)를 확인합니다. 답변을 처리하기 위해 process_resolved_IP()를 호출하면서 buf에 대한 포인터, buf의 패킷 내에서 답변이 위치한 곳을 가리키는 포인터인 answer_ptr, 답변 수(ANCOUNT) 및 기타 플래그를 전달합니다:

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

  [...]

0x00405818에 위치한 process_resolved_IP() 함수는 각 답변을 순회하며 각각에 대해 DNS_answer_parser()를 호출합니다. 인자로는 buf에 대한 동일한 포인터, 원본 패킷(buf) 내부에서 현재 파싱 중인 답변을 가리키는 포인터인 answer, 그리고 크기 256의 버퍼인 current_answer에 대한 포인터를 전달합니다:

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

0x004054e0에 위치한 DNS_answer_parser() 함수는 각 답변을 개별적으로 파싱합니다. 각 답변은 <len><domain><len><domain>... 형태로 표현된 도메인 이름으로 구성됩니다. 예를 들어 example.com은 7example3com으로 표현됩니다. 이 함수는 답변의 도메인 이름을 구성하는 각 <len><domain> 쌍을 반복하면서 <len>이 63보다 작은지(domain_name & 0xc0 != 0) 확인합니다. 그런 다음 memcpy를 호출하여 <domain>에 해당하는 answer의 <len> 바이트를 current_answer로 복사합니다. 도메인 이름의 다음 <len><domain> 쌍에 대해 이 과정을 반복합니다.

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

current_answer는 256바이트에 불과하므로, 공격자는 버퍼를 오버플로우할 만큼 큰 도메인 이름이 포함된 답변을 담은 패킷을 보낼 수 있습니다.

익스플로잇

익스플로잇 과정은 Flashback 팀이 CVE-2020-10881에 대해 비디오 시리즈에서 설명한 것과 매우 유사합니다(참조 자료 확인). 몇 가지 주요 차이점이 있습니다:

  1. 주소가 동일하지 않습니다.
  2. 코드가 정확히 동일하지 않아서 오프셋과 일부 시점의 스택 크기도 달랐습니다.
  3. 첫 번째와 세 번째 ROP 가젯은 동일했지만(서로 다른 주소에 위치), 두 번째 가젯은 move 명령어에서 차이가 있습니다. 이제 memcpy()의 count 인자로 $a2에 이동되는 레지스터가 $s0 대신 $s1이기 때문입니다.
  4. process_resolved_IP()의 i 변수는 $s8 대신 $s5에 로드됩니다. 그런 다음 $v1과 비교할 때, $v1은 $sp+616에서 가져오는데 이는 오버플로우된 버퍼에서 매우 멀리 떨어져 있어서 답변과 명령어보다 더 멀리까지 스택을 변조해야 했습니다.
  5. 파일 시스템은 /tmp를 제외하고 읽기 전용이므로 모든 쓰기와 읽기는 /tmp에서 이루어져야 합니다. 이로 인해 리버스 셸을 62자 미만으로 만드는 것이 불가능해졌고, 따라서 대상에 배포하려면 웹 서버에서 제공하고 다운로드하여 실행하는 명령을 대상에 보내야 합니다.

이 점을 염두에 두고 어쨌든 전체 과정을 설명하겠습니다.

conn-indicator 바이너리는 NX가 활성화되어 있고 ASLR은 바이너리 자체를 제외한 모든 곳에 적용됩니다:

도구 다운로드