[Zero-day] 심화 분석 2

goldenGlow_21·2025년 3월 19일

에뮬레이팅

sudo ./run.sh -d dlink dcs932lb1_v200_b6.bin

  • 한나절 동안 이것만 함 젠장

네트워크 및 서비스 관련 취약점 탐색

후보 선정

1. /bin/alphapd

  • 이진 실행 파일(바이너리)이고, 보통 네트워크 관련 서비스에 사용됨
  • 이전에 nipcauserverify() 함수 분석을 진행했던 것으로 보아, 인증 관련 기능을 포함할 가능성이 높음

2. /etc_ro/web/cgi/system.cgi

  • AdminPassword, AdminID 값을 다루고 있는 것으로 보아, 관리 인터페이스 관련 가능성 있음
  • 특정 URL을 통해 시스템 정보를 유출하거나, 관리 기능을 조작할 수 있는 취약점이 있을 가능성 있음

3. /sbin/firewall.sh

  • 방화벽 설정을 담당하는 스크립트
  • 내부적으로 네트워크 정책을 잘못 적용하면, 임의의 포트가 열릴 가능성이 있음

4. /bin/gcmd

  • 이름으로 볼 때 "command" 실행과 관련될 가능성이 있음
  • 혹시 명령어 인젝션(Command Injection) 같은 취약점이 존재할 수도...?

/bin/alphapd

main

v5 = fopen("/var/run/nvramd.pid", "r");
if ( !v5 ) {
    trace(16, "waiting for nvram_daemon", v6);
    Sleep(1);
    if ( v4 >= 15 ) {
        trace(0, "please execute nvram_daemon first!", v8);
        return -1;
    }
}
  • /var/run/nvramd.pid 파일이 존재하지 않으면 최대 15초 동안 기다린 후 실행 종료
  • nvram_daemon 프로세스가 실행 중이어야 웹 서버가 정상 작동
  • nvram은 환경 설정 값을 저장하는 비휘발성 메모리(NVRAM)와 관련!
v10 = nvram_bufget(0, "SecondHTTPPortEnable");
v11 = (_BYTE *)nvram_bufget(0, "SecondHTTPPort");
if ( v10 && !strcmp(v10, "3") && v11 && *v11 )
    v3 = atoi(v11);
  • 기본적으로 웹 서버 포트는 80으로 설정됨
  • SecondHTTPPortEnable이 3으로 설정되어 있고, SecondHTTPPort 값이 존재하면 해당 포트 사용
  • nvram_bufget()을 통해 가져오는 값이 올바르게 검증되지 않으면 취약점이 발생할 가능성
sub_4069B4(15, sub_406938);
sub_4069B4(2, sub_406938);
signal(13, 1);
  • sub_4069B4() 함수는 신호 핸들링 관련 함수일 가능성이 높음
  • signal(13, 1); → SIGPIPE 시그널 무시 (SIGPIPE는 파이프가 끊어졌을 때 발생하는 시그널)
if ( websStartupServer(v3) >= 0 ) {
    trace(0, "Running at address %s:%d\n", &websSrvIpAddr);
    websParaOpen();
    websSetFormOpen();
    websEnableErrorMessage();
    websStartAuthentication();
    httpd_main();
    websEndAuthentication();
    websDisableErrorMessage();
    websSetFormClose();
    websParaClose();
    websShutdownServer();
}
  • websStartupServer(v3) → 웹 서버 실행 (포트 v3)
  • websStartAuthentication(); → 인증 시스템 활성화
  • httpd_main(); → 웹 서버가 동작하는 메인 루프
  • websEndAuthentication(); → 인증 시스템 종료
  • websShutdownServer(); → 서버 종료

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 값을 가져옴
  • 만약 word_44D1B8과 같은 값이면 모든 사용자(websEnableAllUser())에 대해 인증을 비활성화하는 것으로 보임
  • 결론: AccessControlEnable이 특정 값이면 인증이 필요 없을 가능성?

httpd_main()

int httpd_main()
{
  int result; // $v0
  int v1; // $a2
  unsigned int SysInfoLong; // $v0
  unsigned int v3; // $a0
  unsigned int v4; // $v1
  int v5; // $v0

  for ( result = gotsigterm; !gotsigterm; result = usleep(1000) )
  {
    SysInfoLong = getSysInfoLong(56);
    v3 = SysInfoLong >> 9;
    v4 = SysInfoLong;
    v5 = (SysInfoLong >> 9) & 1;
    if ( v5 || (v5 = v3 & 1, ((v4 >> 15) & 1) != 0) || (v5 = v3 & 1, (v4 & 0x10000) != 0) )
    {
      if ( v5 )
      {
        setSysInfoLong(58, 512);
      }
      else if ( ((v4 >> 15) & 1) != 0 )
      {
        setSysInfoLong(58, 0x8000);
      }
      else if ( (v4 & 0x10000) != 0 )
      {
        setSysInfoLong(58, 0x10000);
      }
      websEndAuthentication();
      nvram_close(0);
      nvram_init(0);
      websStartAuthentication();
      trace(16, "reload configuration!\n", v1);
    }
    websSocketEventPoll();
    usleep(1000);
    websCgiReapChildren();
    usleep(1000);
    websFrameReapChildren();
    usleep(1000);
    websStreamReapChildren();
    usleep(1000);
    websTimeoutProcess();
  }
  return result;
}
  • httpd_main()은 웹 서버의 메인 루프 역할
  • getSysInfoLong(56) 값을 확인하여 특정 비트가 설정되었는지 검사
    • 이 값이 변하면 setSysInfoLong(58, 값)으로 설정하고 설정을 다시 불러옴
    • 이후 websEndAuthentication(), nvram_close(0), nvram_init(0), websStartAuthentication()가 호출
  • websSocketEventPoll()을 계속 실행하여 웹 서버가 요청을 처리하도록 함

getSysInfoLong(int a1)

int __fastcall getSysInfoLong(int a1)
{
  int v2; // $s1
  int v4; // [sp+18h] [-8h] BYREF

  v2 = open("/dev/gpio", 0);
  v4 = 0;
  if ( v2 >= 0 )
  {
    ioctl(v2, (a1 << 8) | 0x20080, &v4);
    close(v2);
  }
  return v4;
}
  • /dev/gpio 디바이스 파일을 오픈
  • ioctl(v2, (a1 << 8) | 0x20080, &v4);
    • a1을 왼쪽으로 8비트 시프트한 후 0x20080과 OR 연산
    • ioctl() 호출을 통해 GPIO에서 값을 가져옴
  • 가져온 값이 v4에 저장되며 반환

GPIO 인터페이스를 직접 조작할 수 있으려나

websCgiReapChildren()

int websCgiReapChildren()
{
  int result; // $v0
  int i; // $s2
  int *v2; // $s1
  int v3; // $s0
  const char *v4; // $a1
  int v5; // $a3
  int v6; // [sp+18h] [-8h] BYREF

  result = dword_491720;
  for ( i = 0; dword_491720 >= i; result = dword_491720 < i )
  {
    while ( 1 )
    {
      v2 = *(int **)(4 * i + dword_4934A0);
      if ( v2 )
      {
        v3 = *v2;
        if ( !websReapChildren(v2[1], (int)&v6) )
          break;
      }
      result = dword_491720 < ++i;
      if ( dword_491720 < i )
        return result;
    }
    websConnClose(v3, 200);
    v4 = "child(%d) exited, status=%d\n";
    v5 = (unsigned __int16)(v6 & 0xFF00) >> 8;
    if ( (v6 & 0x7F) != 0 && (v5 = v6 & 0x7F, v4 = "child(%d) killed (signal %d)\n", (unsigned __int8)v6 == 127) )
      trace(16, (int)"child(%d) stopped (signal %d)\n", v2[1], (unsigned __int16)(v6 & 0xFF00) >> 8);
    else
      trace(16, (int)v4, v2[1], v5);
    dword_491720 = FreeEntry(&dword_4934A0, i);
    free(v2);
    ++i;
  }
  return result;
}
  • 웹 서버에서 CGI 프로세스의 종료 상태를 관리
  • websReapChildren(v2[1], (int)&v6)를 호출하여 자식 프로세스 종료 여부 확인
  • 자식 프로세스가 종료 시
    • websConnClose(v3, 200); 실행
    • trace(16, ...);를 통해 종료 로그 남김
    • FreeEntry(&dword_4934A0, i);를 통해 목록에서 제거

websReapChildren(int a1, int a2)

BOOL __fastcall websReapChildren(int a1, int a2)
{
  return waitpid(a1, a2, 1) != a1;
}
  • waitpid(a1, a2, 1)

    • a1 프로세스의 상태를 확인하고, 종료 여부를 반환
    • 1 옵션은 Non-blocking 모드 (즉, 기다리지 않고 바로 반환)
  • return waitpid(a1, a2, 1) != a1;

    • waitpid()의 반환값이 a1과 같지 않다면 TRUE 반환
    • 프로세스가 종료되지 않았다면 TRUE 반환
    • 프로세스가 종료되었으면 FALSE 반환

setSysInfoLong(int a1, int a2)

int __fastcall setSysInfoLong(int a1, int a2)
{
  int result; // $v0
  int v5; // $s2
  int v6; // [sp+18h] [-8h] BYREF

  result = open("/dev/gpio", 0);
  v5 = result;
  v6 = a2;
  if ( result >= 0 )
  {
    ioctl(result, (a1 << 8) | 0x20081, &v6);
    return close(v5);
  }
  return result;
}
  • open("/dev/gpio", 0);

    • /dev/gpio 파일을 열어 GPIO 장치에 접근
    • 결과가 0 이상이면 성공, 그렇지 않으면 실패
  • ioctl(result, (a1 << 8) | 0x20081, &v6);

    • a1 값을 왼쪽으로 8비트 이동(shift) 후, 0x20081과 OR 연산
    • v6 = a2;를 사용해 GPIO 값을 변경
  • close(v5);

    • 파일을 닫고 정상 종료

/etc_ro/web/cgi/system.cgi

CameraName=%%CameraName();%%
Location=%%Location();%%
AdminID=%%AdminID();%%
AdminPassword=%%AdminPassword();%%
LEDControl=%%LEDControl();%%
SnapshotURLAuthentication=%%SnapshotURLAuthentication();%%
  • %%Function();%% 형태의 매크로(템플릿 엔진) 사용
  • 각 필드는 실제 값이 할당되기 전의 변수 자리 표시자(Placeholder)

취약점 찍어보기

관리자 계정 정보 (AdminID, AdminPassword) 유출 가능성
  • CGI 스크립트가 실행될 때, 해당 값이 HTML 소스에서 그대로 보일 수 있음
curl -X GET http://<대상 ip>/cgi/system.cgi
  • 수정되지 않은 기본값(Default Credentials)이 존재할 가능성
    - 일부 장치에서는 기본값(admin/admin)이 그대로 저장됨
    - /etc_ro/Wireless/RT2860AP/RT2860_default_novlan 파일에도 Password=admin으로 설정되어 있음

  • 해당 값이 nvram_bufget()에서 로드될 가능성
    - nvram_bufget(0, "AdminPassword") 등을 사용하여 불러올 가능성이 있음
    - 이전 alphapd 분석에서 nvram_bufget()을 통해 설정을 불러오는 부분이 확인됨

LEDControl & SnapshotURLAuthentication 조작 가능성
  • LEDControl 필드 변경을 통해 물리적 LED 동작을 조작 가능
    - 보안 카메라 LED를 강제로 끄거나, 원격으로 조작하여 존재를 숨길 가능성
    - 웹 요청으로 LED를 조작할 수 있는지 확인 필요

curl -X POST http://<대상 ip>/cgi/system.cgi -d "LEDControl=off"

  • SnapshotURLAuthentication를 비활성화하면 인증 없이 스트림 접근 가능
    - 보안 카메라의 스냅샷 URL 인증을 비활성화하여 누구나 접근 가능하도록 만들 가능성
    - 정상적으로 변경되면, 인증 없이 직접 스냅샷 URL 접근 가능할 것...

curl -X POST http://<target_ip>/cgi/system.cgi -d "SnapshotURLAuthentication=0"

http://<target_ip>/image/jpeg.cgi

/sbin/firewall.sh

주요 기능

  • iptables 규칙 초기화 (iptablesAllFilterClear())

    • 모든 필터링 규칙 제거 (iptables -F -t filter)
    • INPUT, OUTPUT, FORWARD 기본 정책을 ACCEPT로 설정
  • 방화벽 초기화 (init())

    • IP 및 포트 필터링 체인 생성
    • MAC 주소 필터링 체인 생성
    • 포트 포워딩 체인 생성
    • DMZ 체인 설정
  • 방화벽 종료 (fini())

    • 정의되어 있지만, 실제로 동작하지 않음 (빈 함수)

취약점 찍어보기

iptablesAllFilterClear() 문제점
  • 모든 방화벽 규칙을 삭제한 후 기본 정책을 ACCEPT로 설정
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
  • 모든 인바운드/아웃바운드/포워딩 트래픽을 허용: 방화벽이 비활성화된 것과 동일
  • 공격자가 방화벽을 초기화(firewall.sh init)하면 모든 트래픽이 필터링 없이 통과 가능.
DMZ_CHAINPORT_FORWARD_CHAIN 조작 가능성
  • iptables -t nat을 이용해 포트 포워딩 및 DMZ 설정을 변경할 수 있음
iptables -t nat -I PREROUTING 1 -j port_forward
iptables -t nat -I PREROUTING 2 -j DMZ
  • 포트 포워딩 규칙이 높은 우선순위로 추가됨 (PREROUTING 1, 2)
  • DMZ 설정이 강제 적용될 경우, 내부 네트워크의 기기들이 외부에서 접근 가능할 수 있음
init() 함수 실행 시 우선순위 문제
  • 방화벽 초기화 이후에 iptablesAllFilterRun()iptablesAllNATRun()이 주석 처리되어 있음
#iptablesAllFilterRun();
#iptablesAllNATRun();
  • 원래는 추가적인 방화벽 규칙을 설정해야 하는데, 이 부분이 실행되지 않음
  • init() 실행 후 방화벽 설정이 완전하지 않거나 비정상적으로 동작할 가능성 존재

공격 시나리오 짜보기

방화벽 무력화 공격

curl -X GET http://<대상 ip>/cgi-bin/system.cgi?cmd=firewall.sh+init

  • 방화벽을 초기화하여 모든 트래픽을 허용
포트 포워딩 강제 설정

iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80

  • 원격에서 내부 IP(192.168.1.100)의 웹 서버로 트래픽을 우회
DMZ 강제 활성화

iptables -t nat -A PREROUTING -j DMZ

  • 내부 네트워크 기기 전체가 외부에서 접근 가능해짐

/bin/gcmd

주요 기능

  • gcmdmotion detection(움직임 감지)과 관련된 기능을 수행하는 것으로 판단
  • 실행 중 특정 GPIO 핀(ioctl-gcmd_read_user1_sig_flag)을 모니터링하여 동작을 감지
  • 감지된 동작을 /tmp/gotocamera/ 경로에 이미지로 저장하고, 이후 dscamd 프로세스를 호출하여 관련 데이터를 json 형식으로 전달하는 기능이 존재
profile
안드로이드는 리눅스의 꿈을 꾸는가

0개의 댓글