[Zero-day] 심화 분석

goldenGlow_21·2025년 3월 19일

자동화 분석 도구 사용

EMBA

  • 묘하게 파일 개수가 적은 느낌이...

주요 cgi 스크립트/예상 취약점 개별 분석

upload.cgi

Follow-Up: FTP

  • FTP 관련 설정 파일이 존재하는지 확인
┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ ls -alR | grep ftp

-rwxrwxr-x  1 kali kali  52464 Feb 15 20:10 tftpupload
-rw-r--r-- 1 kali kali  5068 Feb 15 20:10 errrftp.htm
lrwxrwxrwx 1 kali kali   17 Feb 15 20:10 ftpd -> ../../bin/busybox
lrwxrwxrwx 1 kali kali   17 Feb 15 20:10 ftpputimage -> ../../bin/busybox

tftpupload, errrftp.htm 검사 결과

  • FTP 서비스

    • ftpd: /bin/busybox에 대한 심볼릭 링크
      - BusyBox의 내장 FTP 서버를 사용 중
    • ftpputimage: /bin/busybox에 대한 심볼릭 링크
      - FTP를 통해 특정 이미지를 업로드하는 기능일 가능성 존재
  • TFTP 서비스

    • tftpupload
      - 파일 업로드와 관련된 바이너리 실행 파일 존재
    • errrftp.htm
      - 웹 인터페이스에서 FTP 관련 설정을 보여주는 HTML 파일

tftpupload 바이너리 분석

  • 파일 업로드 관련 메시지 포함
recvfrom
***tftp server, listen port: 69***
/proc/mtdrmtd support not enable?
/bin/mtd_write -o %d -l %d write %s Kernel
write kernel
/bin/mtd_write -o %d -l %d write %s Bootloader
write bootloader
  • TFTP(Trivial File Transfer Protocol) 서버가 포트 69에서 실행됨

  • mtd_write 관련 메시지 포함: 플래시 메모리에 직접 기록하는 기능을 가질 가능성 존재

  • 로그 메시지 및 업로드 실패 처리

cannot open upgrade file !!!
cannot stat upgrade file !!!
cannot mmap upgrade file !!!
upgrade file size is too small!!!
upgrade file with different signature !!!
upgrade file without signature !!!
upgrade file with bad checksum !!!
  • 파일 업로드 후, 서명(signature)과 체크섬 검증을 수행하는 것으로 보임

확인 결과, tftp 서비스가 바로 돌아가는 건 아닌 것으로 보임

관리자 계정과 인증 관련 취약점 확인

RT2860_default_novlan

┌──(kali㉿kali)-[~/…/cpio-root/etc_ro/Wireless/RT2860AP]
└─$ cat RT2860_default_novlan 
#The word of "Default" must not be removed
Default
WebInit=1
HostName=ralink
Login=admin
Password=admin
...
  • 하드코딩?
  • 기기가 초기화되거나 설정을 리셋하면, 공격자가 기본 계정(admin/admin)을 이용해 쉽게 접근할 수 있음

비밀번호 저장 방식 분석 - getHEXString

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "getHEXString" ./etc_ro/web/

./etc_ro/web/function.js:function getHEXString(instr)
./etc_ro/web/advanced.htm:  if ((frm.AdminPassword.value == getHEXString(frm.OldPassword.value)) && ((frm.NewPassword.value == frm.RetypePassword.value)))
./etc_ro/web/advanced.htm:     if (frm.AdminPassword.value != getHEXString(frm.OldPassword.value))
  • /etc_ro/web/function.jsgetHEXString() 함수가 존재
  • advanced.htm에서 getHEXString()을 사용하여 비밀번호를 변환하는 것으로 판단
  • 비밀번호가 평문으로 저장되는 것이 아니라 HEX 변환된 값으로 저장될 가능성 존재
  • 암호화된 것이 아니라 단순한 문자열 변환(HEX encoding)일 가능성
<FORM ACTION="/setSystemAdmin" METHOD="POST" autocomplete="off">
<input type="hidden" name="AdminID" value="admin">
<input type="hidden" name="AdminPassword" value="%%AdminPassword(2);%%">
  • 비밀번호가 숨겨진 입력 필드(hidden input)에 저장
  • 공격자가 클라이언트 측에서 직접 패스워드를 변경하여 서버로 전송할 가능성

비밀번호 검증 로직 확인 - alphad

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "getHEXString" ./bin ./sbin ./usr/bin ./usr/sbin

grep: ./bin/alphapd: binary file matches

alphad 파일 분석

nipcauserverify

int __fastcall nipcauserverify(int a1)
{
  int v1; // $v1
  const char *v3; // $v0
  int v4; // $a1
  char v5; // $a2
  int v6; // $v1
  int v7; // $a0
  int v8; // $v0
  _DWORD v10[16]; // [sp+20h] [-40h] BYREF

  v1 = *(_DWORD *)(a1 + 172);
  switch ( v1 )
  {
    case 3:
      strcpy((char *)v10, "group=Administrator\r\n");
      goto LABEL_8;
    case 2:
      strcpy((char *)v10, "group=Controller\r\n");
      goto LABEL_8;
    case 1:
      v4 = *(_DWORD *)"group=User\r\n";
      v3 = "group=User\r\n";
      goto LABEL_6;
  }
  v3 = "group=None\r\n";
  if ( !v1 )
  {
    v4 = *(_DWORD *)"group=None\r\n";
LABEL_6:
    v5 = v3[12];
    v6 = *((_DWORD *)v3 + 1);
    v7 = *((_DWORD *)v3 + 2);
    v10[0] = v4;
    v10[1] = v6;
    v10[2] = v7;
    LOBYTE(v10[3]) = v5;
  }
LABEL_8:
  v8 = strlen(v10);
  websWriteNormalHeader(a1, 200, v8, "application/Dlink-inf", 0);
  websWriteFmt(a1, (const char *)v10);
  return websConnClose(a1, 200);
}
  • 사용자 그룹 확인
v1 = *(_DWORD *)(a1 + 172);
  • a1 + 172 오프셋의 값을 확인하여 사용자 권한 등급(v1)을 판별

  • switch (v1) 문을 사용하여 v1 값에 따라 다른 그룹을 할당

  • 그룹별 권한 처리

switch (v1)
{
    case 3:
        strcpy((char *)v10, "group=Administrator\r\n");
        goto LABEL_8;
    case 2:
        strcpy((char *)v10, "group=Controller\r\n");
        goto LABEL_8;
    case 1:
        v4 = *(_DWORD *)"group=User\r\n";
        v3 = "group=User\r\n";
        goto LABEL_6;
}
  • v1 == 3 → "Administrator" (최고 권한)

  • v1 == 2 → "Controller" (중간 관리자)

  • v1 == 1 → "User" (일반 사용자)

  • v1 == 0 → "None" (권한 없음)

  • 웹 응답 처리

v8 = strlen(v10);
websWriteNormalHeader(a1, 200, v8, "application/Dlink-inf", 0);
websWriteFmt(a1, (const char *)v10);
return websConnClose(a1, 200);
  • websWriteNormalHeader(): 웹 서버 응답 헤더 작성
  • websWriteFmt(): 응답 본문에 사용자 그룹 정보를 출력
  • websConnClose(a1, 200);: HTTP 응답을 종료 (200 OK)

websStartAuthentication()

int websStartAuthentication()
{
  int v0; // $v0

  v0 = nvram_bufget(0, "AccessControlEnable");
  if ( !strcmp(v0, &word_44D1B8) )
    websEnableAllUser();
  return websEnableHtmlFile();
}
  • nvram_bufget(0, "AccessControlEnable")

    • NVRAM 설정에서 AccessControlEnable 값을 가져옴
    • 접근 제어 기능(Access Control)이 활성화되어 있는지 확인하는 역할
  • !strcmp(v0, &word_44D1B8)

    • word_44D1B8는 비교 대상 문자열
    • 만약 AccessControlEnable 값이 특정 값과 일치하면 모든 사용자 인증을 허용(websEnableAllUser)
  • websEnableAllUser();

    • 모든 사용자에 대한 인증을 우회할 가능성

websGetAuthenticateRealm(int a1)

int __fastcall websGetAuthenticateRealm(int a1)
{
  _DWORD *v1; // $s0
  char *v3; // $a1

  v1 = &websForceAuthenticPathList;
  v3 = off_491294;
  if ( !off_491294 )
    return 0;
  while ( webncasestrcmp(*(_DWORD *)(a1 + 136), (int)v3) )
  {
    v1 += 2;
    v3 = (char *)v1[1];
    if ( !v3 )
      return 0;
  }
  return *v1;
}
  • 보호된 웹 경로(websForceAuthenticPathList) 목록을 확인하여 인증이 필요한지 판단
  • webncasestrcmp(*(_DWORD *)(a1 + 136), (int)v3)
    • 요청된 경로와 보호된 경로를 비교
  • 일치하면 인증이 필요, 없으면 0 반환(즉, 인증 우회 가능?)

어떤 경로가 보호되고 있는지 websForceAuthenticPathList 값을 확인해야 함

websEndAuthentication()

int websEndAuthentication()
{
  int v0; // $v0

  v0 = nvram_bufget(0, "AccessControlEnable");
  if ( !strcmp(v0, &word_44D1B8) )
    websDisableAllUser();
  return websDisableHtmlFile();
}
  • websStartAuthentication()와 유사하지만, 인증 시스템을 종료하는 역할
  • 만약 AccessControlEnable 값이 특정 값이면, 모든 사용자의 인증을 비활성화(websDisableAllUser)
  • NVRAM 값(AccessControlEnable)을 조작할 경우, 인증이 비활성화될 가능성 존재

명령어 실행 취약점 확인

alphapd 실행 파일에서 명령어 실행 함수 검색

┌──(kali㉿kali)-[~/…/_50040.extracted/_3B6000.extracted/cpio-root/bin]
└─$ strings ./alphapd | grep -E "system|popen|exec"

system
execve
please execute nvram_daemon first!
/cgi/system.cgi
/cgi/isystem.cgi
Can't execve %s: %s
Stream CGI process file is not executable

CGI 스크립트에서 명령 실행 코드 검색

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "system" ./etc_ro/web/cgi/

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "popen" ./etc_ro/web/cgi/

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "exec" ./etc_ro/web/cgi/
  • ./etc_ro/web/cgi/에서 system(), popen(), exec() 함수 호출이 없음
  • CGI 스크립트 자체에서 명령 실행을 직접 수행하지 않는 것으로 보임

웹 인터페이스에서 명령어 실행 가능성 확인

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "cmd" ./etc_ro/web/
grep: ./etc_ro/web/api/aplug.class: binary file matches
./etc_ro/web/html.htm:1 3 docmd.htm

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "shell" ./etc_ro/web/

┌──(kali㉿kali)-[~/…/_dcs932lb1_v200_b6.bin.extracted/_50040.extracted/_3B6000.extracted/cpio-root]
└─$ grep -Ri "ping" ./etc_ro/web/
grep: ./etc_ro/web/api/aplugLiteDL.cab: binary file matches
  • ./etc_ro/web/html.htm: 1 3 docmd.htm
    - "docmd.htm"이 존재하는 것으로 보이며, 이 파일이 명령어 실행과 관련 있을 가능성 존재
  • ./etc_ro/web/api/aplugLiteDL.cab가 바이너리 매칭됨
    - ping 관련 코드를 포함한 바이너리가 있을 가능성 존재
profile
안드로이드는 리눅스의 꿈을 꾸는가

0개의 댓글