[binary] KARONTE: Detecting Insecure
Multi-binary Interactions in Embedded Firmware
Loading content...
Abstract
핵심 문제 : 멀티 바이너리
e.g. 웹서버 바이너리, CGI 바이너리, 헬퍼 프로그램, 데몬 프로세스 등 여러 개의 바이너리가 협력해서 하나의 기능을 수행
Unfortunately, the software on these systems is hardware-dependent, and typically executes in unique, minimal environments with non-standard configurations, making security analysis particularly challenging.
기존 분석의 한계
1. single-binary 분석 : 각 바이너리를 따로 분석해서, 바이너리 간 데이터 흐름을 모름
2. dynamic 분석(에뮬레이션, 퍼징) : 환경을 맞추기 어렵고, 깊은 버그 못찾는 경우 많음
KARONTE
”여러 바이너리 사이의 데이터 흐름을 정적으로 추적하자”
→ 멀티 바이너리 간 통신(IPC)을 모델링
In this paper, we present KARONTE, a static analysis approach capable of analyzing embedded-device firmware by modeling and tracking multi-binary interactions.
1. Introduction
1.1 기존 연구
기존에는 펌웨어 취약점 분석 자동화
분석 가능한 요소로 언패킹 → 독립적으로 분석 → 그럼에도 여전히 취약점 존재
여전히 취약점이 존재하는 이유 :
임베디드 기기 자체가 여러 컴포넌트로 구성되어 있고, 서로 상호작용해서 기능을 수행
e.g. 웹 서버가 HTTP 요청을 받아 → 로컬 바이너리를 소켓 등으로 호출하고 → 로컬 바이너리가 외부 명령을 실행
한가지 기능이 프로그램 하나가 아니라 여러 프로그램의 파이프라인으로 이루어져 있음
바이너리 하나만 분석하면 결과가 suboptimal한 이유
예시
웹서버 : 유저 id/pw 입력 받음, 길이를 16자로 제한, 환경변수로 전달
핸들러 바이너리 : 환경변수에서 크리덴셜 읽음, 각각 16바이트 버퍼 2개에 복사, 길이 체크 안함
1. 핸들러만 단독 분석하면 → 입력 출처 불명, 길이 제한 없음, 16바이트 버퍼에 복사 → 버퍼 오버플로우 취약점 발견했다고 보고 → 실제로는 터질 수 없는 버그 [False positive]
2. 네트워크-facing 바이너리만 보면 → 깊고 복잡한 내부 버그를 놓침
에뮬레이터로 전체 과정을 보려고 하기도 했으나
a. Strict assumptions 문제 :
에뮬레이터가 잘 실행되려면 cpu 종류 알아야 하고, 커널 있어야 하고, 디바이스 드라이버 맞아야 하는 등 조건 있지만, 현실은 커널 없음, vendor-specific driver 등 에뮬레이팅 하기 힘든 펌웨어 많음
b. limited success rate(13%~21%)
실제 기기를 분석하는 방법 :
실제 공유기, 카메라를 켜고 네트워크로 공격 + 퍼징
ineffective in discovering deeper and more complex bugs
“효과적인 펌웨어 분석은 여러 바이너리를 함께 고려하고, 그들이 공유하는 데이터를 추론해야 한다”
1.2 KARONTE
바이너리들이 (한정된) IPC(Inter-Process-Communication)를 사용한다는 것에서 착안
→ 임베디드 펌웨어에서 : 환경변수, 파일, 소켓, 커맨드라인 인자 등 반복되는 패턴을 이용해 바이너리 간 연결 관계를 정적으로 추론
모놀리식 임베디드 OS와 임베디드 리눅스 배포판 모두에서 검증 완료
Monolithic embedded OS :
→ RTOS 기반 펌웨어, vendor 커스텀 OS
→ 하나의 큰 바이너리 또는 tightly-coupled 모듈, 웹서버/핸들러/시스템 로직이 강하게 결합
Embedded Linux distribution :
→ OpenWrt, BusyBox 기반 공유기 펌웨어
→ httpd, cgi, /bin/* 등 표준 리눅스와 유사한 구조
즉, 구조가 달라도 멀티 바이너리 + IPC 구조인 경우 KARONTE는 잘 동작함
이론상, 아주 잘 만든 단일 바이너리 정적 분석도 취약점을 찾을 수는 있으나, false positive 문제 등 현실적으로 불가능
→ KARONTE는 오탐률이 매우 작음
2. Background
2.1 IoT attacker model
IoT 기기들은 네트워크를 통해 데이터를 교환함
데이터는 사용자가 직접(웹 인터페이스, 모바일 앱, 로컬 관리 페이지 등) 제공할 수도 있고, 신뢰하는 원격 서비스(벤더 클라우드 서버, 업데이트 서버, 중앙 제어 서버)에서 간접적으로 제공될 수도 있음
KARONTE가 가정하는 공격자 : 네트워크로 요청 보내는 공격자(인터넷 / LAN)이고 확장 가능
2.2 Firmware complexity
바이너리 간 통신이 치명적인 이유
: attacker input은 기기의 outside(i.e. over the network)에서 일어나지만, 실제 버그는 facing network를 하지 않는 binary 에서 발생할 수 있음
web server receives user request
→ invokes serve_request
→ parse the request (parse_URI )
→ web server executes the handler program, passing data via QUERY_STRING environment variable
→ handler retrieves the data and passes it to process_request : this function contains a bug : 유저 리퀘스트의 field op 값이 128바이트 이상인 경우 버퍼 오버플로우 발생
바이너리 데이터를 만드는 쪽 : setter
ex) 웹 서버가 QUERY_STRING 환경변수에 값을 설정
바이너리 데이터를 읽는 쪽 : getter
ex) 핸들러가 getenv("QUERY_STRING") 으로 읽음
KARONTE는 정적으로 LOG_PATH, QUERY_STRING 같은 IPC 매체를 누가 set, get하는지 찾고 연결해서 취약점 판단
2.3 IPC in IoT firmware
유저 입력이 어디로 퍼지는지 자동으로 알아내는 것은 아직 open problem
IPC는 unique key(=data key, 서로 통신하는 양쪽 혹은 여러 프로세스들이 미리 알고 있어야 하는 식별자, 바이너리 안에 하드코딩 되어있음)로 식별 가능
IPC paradigms(finding data key)
Filesdata key = 파일 이름
A가 /tmp/x 에 write
B가 /tmp/x 를 read
둘 다 파일명 /tmp/x 를 미리 알고 있어야 함
Shared memory(공유 메모리)
Environment variables(환경변수)
data key = 환경변수 이름
QUERY_STRING 같은 것
Sockets(소켓)
data key = endpoint
Command line arguments(커맨드 라인 인자)
data key = 실행되는 프로그램 이름
A가 B를 실행하면서 인자로 데이터 전달 :
B —op=AAA
이때 “어떤 프로그램을 실행”했는지 가 연결고리
data key = invoked program 이름(경로)
We present shared data as a tuple (data_key, data)
3. Approach overview
논문 기준으로 Memory corruption(오염)이나 DoS(denial of service, 서비스 거부) vulnerabilities 탐지에 최적화 되어 있지만, 충분히 확장 가능
3.1 KARONTE’s pipeline
Firmware pre-processing : 펌웨어 전처리
KARONTE의 인풋은 펌웨어 샘플로 이루어져 있음
→ KARONTE는 펌웨어 이미지(.bin 등) 를 binwalk로 unpack
Border binaries discovery : 펌웨어 경계 찾기
unpacked firmware 샘플을 분석하고, 밖(네트워크)과 연결되는 바이너리 셋을 밝힘
→ 더 중요한건 그 바이너리 안에서 “공격자 데이터가 참조되는 지점(program points)”도 찾음 (= taint source 위치 후보)
Binary Dependency Graph(BDG) recovery : 바이너리 의존 그래프 복원
border binary의 set이 주어지면 KARONTE는 BDG를 그림
BDG : 방향 그래프. 노드 → 바이너리(프로세스), 엣지 → IPC를 통한 데이터 흐름(의존성)
Communication Paradigm Finder(CPF) 모듈들이 파일/환경변수/소켓/args 등 각 IPC 패러다임을 담당해서 data key를 찾고 연결을 복원함
Multi-binary Data-flow analysis : 멀티 바이너리 데이터 흐름/테인트 분석
BDG 위에서 테인트를 바이너리 경계를 넘겨가며 전파
→ 특정 바이너리 b 안에서 taint가 어디로 흐르는지 추적하고 제약(길이 제한, 비교 조건 등)들도 수집
→ 그리고 b에서 나가는 IPC(엣지)를 따라 taint + constraints 를 다음 바이너리로 전파
Insecure Interactions Detections : 불안전 신호 탐지
공격자 제어 데이터가 위험한 방식으로 사용되는 경로 찾음
e.g. 버퍼 오버플로우 가능한 경로, DoS 유발 경로
3.2 KARONTE’s novelty
BDG를 그려서 누가 누구와 통신하는지를 시스템적으로 모델링
그 위에서 taint + constraints를 정확히 전파해서 복잡한 멀티 바이너리 취약점을 찾고 false positive를 크게 줄임
monolithic embedded OS 여도, 런타임 프로세스가 나뉘면 IPC + data Key 모델 그대로 적용 가능
4. Border binaries discovery
4.1 목표
KARONTE는 네트워크 공격자가 원격으로 트리거할 수 있는 취약점을 찾고자 함
→ 펌웨어 안에서 외부 입력(네트워크)을 받는 프로그램들을 찾아야 함
4.2 defining network facing binary
네트워크 페이싱 바이너리 = 네트워크로 받은 입력을 읽고 parse 하는 컴포넌트
즉 단순히 네트워크로 데이터 받기만 하는 게 아니라 받은 데이터를 해석/파싱하는 코드가 있어야 네트워크-페이싱일 가능성이 높다고 봄
→ 네트워크 서비스는 단순히 “바이트를 읽는 것”에서 끝나지 않고 반드시 그 바이트를
HTTP 헤더로 파싱하거나, 요청 파라미터로 분해하거나, 명령/포맷으로 해석해야 함
→ 즉 네트워크 페이싱 바이너리에서는 파서 로직이 많이 존재!
결론 : 소켓에서 읽은 데이터를 파싱하는 바이너리를 찾기
4.3 기본 특징 3개 : to find parser
Parser : http 헤더를 읽고 줄 단위로 나누고, GET, POST 같은걸 비교하고 포맷을 분기로 해석하는 코드
#bb : number of basic blocks : 분기 없는 직선 구간
#br : number of branches : if-then-else 같은 분기 개수
#cmp : number of conditional statements used in conjunction with memory comparisons : 입력 버퍼의 일부가 “GET”인지 비교, 입력을 해석하려고 비교하는 코드가 많다를 나타내는 지표
#net : metric we call network mark : 이 함수가 HTTP/SOAP 같은 네트워크 메세지를 파싱하는지 확률을 주는 값
#conn : flag we call connection mark (optional) : 소켓에서 읽은 데이터가 실제로 비교/파싱 연산에 사용되었는가
1 ~ 3 : 파서의 구조적 특징. 그러나 설정 파일 파서, 로그 파서도 비슷하게 생김
4 ~ 5 : 네트워크 입력 방향을 강조하는 feature
1#웹 서버가 CGI 실행할 때 :2REQUEST_METHOD=POST
3CONTENT_LENGTH=124QUERY_STRING=user=admin
56#CGI 프로그램에서는 :7char* q = getenv("QUERY_STRING");89# 웹 요청 데이터가 **환경변수**로 전달 !
환경변수는 길이 제한이나 타입 보장을 하지 않고 신뢰성이 없어서 취약점 취급
KARONTE에서는 : 이 환경변수가 어디서 set 되었는지, 이 변수는 어디서 get 되었는지를 연결해서 테인트를 전파함
Taint
→ 보안에서 'taint(테인트)'는 '오염'을 뜻하며, 특히 사용자 입력처럼 신뢰할 수 없는 데이터가 프로그램 흐름을 따라 이동하며 보안 취약점(SQL 주입, 버퍼 오버플로 등)을 유발할 수 있는 상태를 의미하고, 이를 추적하는 오염 분석(Taint Analysis) 기술을 말함
Firmware
: 하드웨어를 제어하기 위해 하드웨어 내부에 고정(Firm)된 소프트웨어
→ 하드웨어와 소프트웨어 사이의 bridge 역할
e.g. 초기 구동 BIOS/UEFI : cpu, 메모리 점검하고 OS 불러옴
하드웨어 제어 : 키보드를 누를 때 신호를 디지털 데이터로 바꾸거나, 프린터 헤드를 움직이는 등 물리적 부품을 직접 조절
Socket
: 네트워크 상에서 두 프로그램이 데이터를 주고 받을 수 있도록 연결해주는 통신 접속점
→ 소켓 주소 = IP 주소 + 포트 번호
Network-facing Web Server
: 특정 포트(80,443 등)를 열고 소켓을 생성하여 클라이언트(브라우저)의 연결을 기다림, HTTP 요청이 오면 해석하고 적절한 응답을 되돌려줌
→ 외부 입력값을 직접 처리하기에 메모리 오염 취약점(bf overflow)이 발생할 경우 시스템 권한 탈취하기 좋은 타겟
→ Nginx, Apache 혹은 임베디드 기기에서 돌아가는 httdp
CGI(Common Gateway Interface) 핸들러
: 웹 서버는 정적인 파일(HTML,이미지)만 보낼 수 있지만, 사용자가 로그인을 하거나 게시물을 올리는 등 동적인 처리가 필요할 때 웹 서버는 별도의 프로그램을 실행시키는데, 이때 사용하는 규칙이 CGI
→ 웹 서버가 처리할 수 없는 동적 요청을 받으면 CGI 핸들러 spawn함
→ 웹 서버는 클라이언트가 보낸 데이터를 환경변수나 표준 입력(stdin)으로 CGI에 넘겨주고, CGI가 계산한 결과물(stdout)을 다시 받아 클라이언트에게 전달