상세 컨텐츠

본문 제목

[PAPER] HALucinator: Firmware Re-hosting Through Abstraction Layer Emulation

ANALYSIS/Paper Review

by koharin 2022. 5. 12. 14:52

본문

728x90
반응형

 # Abstract

**firmware analysis**

임베디드 기기의 증가로, firmware 분석이 중요해졌다. 하지만 하드웨어와 펌웨어 사이 의존성으로 인해 firmware 동적 분석에 어려움이 있다.

**abstraction**

개발자들은 Hadrware Abstraction Layers(HALs)과 같은 abstraction을 사용하여 코드 개발을 하는데, 이 연구에서는 이 abstraction을 펌웨어 리호스팅과 분석의 기반으로 사용했다. HAL 함수의 대체물을 제공하여 펌웨어의 하드웨어 의존성을 제거했다. 먼저 펌웨어에 라이브러리 함수를 위치시키고, full-system emulator에 이 함수들의 구현을 제공하는 방식으로 이루어진다.

**HALucinator**

펌웨어 리호스팅이 가능한 prototype 시스템인 HALucinator를 제안한다.

**Overview**

1. library matching 기술 소개 - firmware 바이너리에서 라이브러리 함수 식별하는 기술

2. handler, peripheral model을 사용한 리호스팅 과정

3. HALucinator에 AFL fuzzer를 사용하여 HALucinator의 실용성 입증

# 1. Introduction

임베디드 시스템은 더 많은 기능을 위해 인터넷에 연결되고 있다. 이러한 연결로 인해 새로운 보안 문제를 가져온다. 하지만 임베디드 시스템의 펌웨어를 검사하는 일은 귀찮고 시간이 많이 든다.

개발자들은 거의 모든 작업을 개발자 버전의 디바이스를 포함하는 물리적 testbed 위에서 펌웨어를 생성하고 테스트하고 있다.

하드웨어 종속성 때문에 테스트 기반 개발이나 퍼징 등이 어렵거나 불가능하다. 또한 적은 수의 breakpoint, watchpoint를 가지고 있어서 펌웨어를 대상으로 동적 분석 진행에 제한이 있다. stripped 되어있거나 debugging port를 disable 되어 있고(JTAG와 같은), 코드의 환경적 종속성 때문에 동적 분석이 어렵다.

펌웨어 re-hosting으로 알려진 emulation은 펌웨어를 컴퓨터에서 동작하도록 하고 물리적 장치에서보다 더 실행에 대한 통찰력을 제공한다. 하지만, 임베디드 하드웨어에서의 이질성은 펌웨어 emulation에서의 큰 장벽이다.

통합된 chip 디자인은 (ARM based System on Chip - SoC와 같은) chip 주변 기기와 메모리 레이아웃에 따라 다르기 때문에 emulator에서 각 chip의 특성에 특화되도록 하여 지원해야 한다. QEMU emulator는 30개 보다 적은 ARM 기기를 지원하고, Intel의 SIMICS는 많은 CPU와 주변 기기들을 지원하긴 하지만, 하드웨어 레벨에서 시스템 전체 모델을 구성하려면 분석이 필요하다. 대부분 임베디드 시스템들은 회로 보드에 센서, 저장 기기, 네트워크 구성요소와 같은 다른 요소들을 탑재해놓는다. 이러한 주변 기기들을 지원하는 emulator는 존재하지 않는다.

다양한 하드웨어를 emulate하기 위한 방법은 emulator에서 지원하지 않는 주변 기기와 통신하여 하드웨어에 전송하도록 하는 기기 샘플에 의존하는 것이다. 이러한 “hardware-in-the-loop”(HIL) 방식은 원래 하드웨어의 가용성에 대한 scale testing 능력을 제한하고, 제한된 instrumentation과 분석 가능성을 제공한다. 다른 기술로는 녹화를 하고 다시 재생하거나 하드웨어로부터 데이터를 모델링하는 것이다. 하지만 기기 자체에 trace recording 기능을 제공해야 하고, 기록된 경로는 emulator에서 제한된 실행을 가질 수밖에 없다.

펌웨어 개발에 어려움이 있기 때문에, chip vendor나 여러 third party에서는 **HALs(Hardware Abstraction Layers)**를 제공하고 있다. HALs는 프로그래머가 고수준 하드웨어 연산을 할 수 있도록 제공하는 소프트웨어 라이브러리이다. 펌웨어 실행 시에는 chip이나 시스템에 대한 정보는 숨길 수 있다. 이러한 기능으로 vendor 사이에서 코드를 이식하기 쉬워졌다. 또한 HALs로 작성된 펌웨어는 하드웨어 종속성이 적어졌다.

high-level abstraction layer과 High-Level Emulation (HLE)을 사용해서 임베디드 시스템의 emulation 기술 구현

먼저 펌웨어 이미지에서 하드웨어와 상호작용하는 역할을 하는 HAL 함수 식별했다. 이후 펌웨어 관점에서 동일한 일을 하도록 하였다. (예를 들면 ethernet packet을 전송하고 펌웨어에 그러한 액션을 알리는 일) high-level emulation 위한 첫 번째 단계는 펌웨어 이미지 내에서 정확하게 HAL 함수를 식별하도록하는 것이다. 개발자는 디버깅 심볼을 가지고 있기 때문에 자신의 코드에서 re-host가 가능하지만, 그 외의 분석가는 strip된 펌웨어 이미지에서 라이브러리와 애플리케이션 코드를 풀어야 한다. 대부분 HALs는 오픈소스이고 특정 compiler와 함께 패킹되기 때문에 HALs의 소스 코드를 사용할 수 있는 것으로 이 일을 줄일 수 있었다.

다음으로 직접 만든 함수로 HAL 함수를 대체하였다. 각 대체되는 함수(handler)는 수동으로 생성되기 때문에 펌웨어 사이에서 동일한 미들웨어 라이브러리를 사용하거나 동일한 vendor인 칩 간에 확장될 수 있다. mBed OS(ARM 오픈소스)는 140개가 넘는 보드와 16개의 서로 다른 제조사에서 나온 하드웨어를 사용한다. emulator에서 mBed 함수를 식별하고 가져오기 위해, I/O를 외부로 상호작용 가능하고 mBed OS를 사용하여 펌웨어 emulation이 가능하도록 high level 구현으로 대체했다. 또한 handler는 perihperal model(serial port, bus controller와 같은 하드웨어 주변 기기 추상화로서 기능하는)을 사용할 수 있고 에뮬레이터 환경과 호스트 환경 사이에서 통신할 수 있는 지점으로서 역할을 한다. 이는 handler의 생성이 주변 기기 클래스로 확장될 수 있도록 한다.

handler는 DMA(Direct Memory Access) 주변 기기를 통해 ethernet frame을 전달하는 일을 복잡하게 처리할 수 있다. 하지만 외부와 통신하는 handler는 HAL 함수의 인자(어떤 ethernet 기기와 통신하는지, 전달할 데이터에 대한 pointer, 데이터의 길이 등)를 peripheral model이 일을 수행하는데 사용할 수 있는 데이터로 해석한다. handler는 emulation에서는 존재하지 않는 하드웨어 개념(power, clocking과 같은)에 대해서는 액션을 수행하지 않기도 한다.

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/cb725efd-21ad-4046-afa1-935dac54ff55/Untitled.png)

이러한 아이디어를 prototype system인 HALucinator에 구현하였다. QEMU emulator 위에서 동작하는 high-level emulator 환경이다. HALucinator는 blob 펌웨어부터 ARM Cortext-M 아키텍처를 위해 여러 chip vendor를 지원한다. ethernet, WiFi, IEEE 802.15.4 radio와 같은 주변 기기도 처리할 수 있다. 이 시스템은 펌웨어를 돌리고 외부와 통신이 가능하다. 무선 네트워크, app-enabled 기기, hybrid emulated 환경에 초점을 맞춘 사례 연구를 소개한다. 기기는 원래 하드웨어 없이 HALucinator에서 이러한 시스템을 충분히 emulate할 수 있다.

**AFL 퍼저와 함께 HALucinator를 사용하여 HALucinator를 보안적 분석에 적용 가능함을 입증한다.** Shellphish CTF팀은 2019 Embedded Security Challenge에서 HALucinator를 사용하여 우승했다.

**Contribution**

1. 실제 하드웨어 없이 system emulator(QEMU)를 사용하여 바이너리 펌웨어를 emulation하는 것이 가능하도록 했다. 이를 위해 **HALs (abstraction library)를 사용**했다.
2. 기존의 library matching 기술을 발전시켜서 펌웨어에서 함수를 intercept하는데 더 편리하도록 했다.
3. high-level emulation system인 **HALucinator**를 제안한다. **handler와 peripheral model을 사용**하여 **펌웨어 퍼징과 emulation**이 가능하다.
4. 접근 방법의 실용성을 16개의 실제 펌웨어 샘플을 이용하여 사례 연구를 통해 보였고, HALucinator가 적은 노력으로 복잡한 기능을 emulate하는데 성공했음을 입증했다. 펌웨어 퍼징을 통해 Contiki OS에서 use-after-free, memory disclosure, buffer overflow 취약점을 발견했다. (CVE-2019-9183, CVE-2019-8359)

# 2. Motivation

모든 전자 기기는 펌웨어를 실행하는 CPU를 가지고 있다. CPU의 복잡성의 증가와 ubiquitous connectivity의 등장으로 펌웨어의 복잡성은 더 증가하였다. 펌웨어의 부담을 덜기 위해 추상적 방법으로 하드웨어와 통신하기 위한 여러 라이브러리(HAL 등) 등장하였다.

개발자들을 위해 microcontroller 제조사는 HALs를 개발했고, HALs는 microcontrollers의 familiy를 위한 abstraction을 제공하여 하나의 HAL로 여러 microcontroller를 다룰 수 있었다. STMicroelectronic의 STM32Cube HAL는 모든 Cortex-M 기반의 microcontroller를 지원한다. 이러한 microcontroller들은 원래 서로 다른 회사에서 설계되었다. 또한 제조사의 HALs는 제조사의 IDE니 다른 개발자 도구(Keil, IAR 등)에서 사용할 수 있다. HAL는 임베디드 OS에도 포함되었다.

HALs를 사용해서 어떻게 펌웨어가 빌드되는지 이해하는 것은 HALucinator가 어떻게 펌웨어 emulation이 가능하도록 하는지에 대한 근본적인 부분이다.

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fa5cf76d-dc46-41f0-8f38-18afd2afa451/Untitled.png)

Figure 2a는 HALucinator가 emulate하는데 설계한 소프트웨어와 하드웨어 구성 요소를 보여준다. 시스템을 emulate할 때, on-chip peripheral와 off-chip 하드웨어는 존재하지 않지만 대부분 시스템은 이러한 요소들과 통신한다. 지원하지 않는 peripheral에 접근할 때 QEMU halt가 발생하는것을 발견했다. (섹션 5)

## 2.1 Emulating Hardware and Peripherals

**임베디드 펌웨어를 emulation하는 목적을 달성하기 위해서는 먼저 펌웨어가 돌아가는 환경을 emulate**해야 한다. 이는 CPU의 instruction set과 memory 특성을 포함하는 환경이다. 현대 CPU는 on-chip peripheral로 전체 기능을 제공한다. CPU 위에서 실행되는 코드는 이러한 기능을 Memory-Mapped I/O (MMIO)를 통해 제공한다. (peripheral의 제어와 데이터 레지스터가 메모리 공간에 접근하는)

리호스팅을 어렵게 하는 부분은 펌웨어의 off-chip 기기와의 통신이다. (sensor, actuator, 외부 저장 기기, communication 하드웨어) 각 제품은 custom-designed 회로 보드를 가지기 때문에 각 펌웨어에서 완전한 실행 환경을 구성하는 일은 매우 특이하다. 현재 있는 emulation tool (QEMU, SIMICS)은 적은 CPU를 지원하고, 더 적은 on-chip, off-chip를 지원한다. 이 툴을 사용하려면 on-chip, off-chip 기기는 펌웨어에서 사용하는 MMIO 레지스터 인터페이스에 구현해야 한다. (??) 이것은 각 기기에 대한 로직 이해가 필요해서 시간이 많이 걸리는 일이다.

## 2.2 The Firmware Stack

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fa5cf76d-dc46-41f0-8f38-18afd2afa451/Untitled.png)

Figure 2a에서 HTTP Server의 소프트웨어와 하드웨어 스택을 볼 수 있다. 웹페이지를 통해 HTTP Server가 온도를 제공한다고 예를 들어보자. 애플리케이션은 온도 센서 제조사가 제공하는 라이브러리의 API를 사용해서 온도를 얻는다. 이 라이브러리는 microcontroller가 제공하는 12C HAL를 사용해서 12C 버스를 통해 off-chip 온도 센서와 통신한다. 페이지에서 온도를 요청하면, HTTP Server는 OS 라이브러리의 API를 사용해서 요청을 보내고, TCP 메시지를 응답으로 받는다. 그럼 OS는 다른 라이브러리(Lightweight IP, lwIP) 가 제공하는 TCP stack을 사용한다. lwIP는 TCP 메시지를 ethernet frame으로 변환하고 frame을 물리적인 ethernet port를 사용해서 전송하기 위해 Ethernet HAL을 사용한다.

펌웨어 내 기능성은 미들웨어 라이브러리와 HALs 위에서 빌드되어서 복잡도가 높은 현대 기기들의 개발 시간을 줄일 수 있다. 이런 라이브러리들은 대부분 chip 제조사에서 만든 SDK (Software Development Kit)에서 사용 가능하다. SDK는 예시 애플리케이션, 미들웨어 라이브러리(OS 라이브러리, protocol stack, on-chip 주변 기기들을 위한 HALs를 포함)들을 포함한다. 이러한 라이브러리들은 low-level 기능을 제공해서 애플리케이션을 물리적인 하드웨어로부터 분리한다.

**HALucinator에서 펌웨어와 하드웨어 종속성을 분리하기 위해, 미들웨어/라이브러리 또는 HAL 이런 계층 중 하나를 가로채야 했고, 그것이 제공하는 기능을 대신할 것을 넣는다.** Figure 2b에서 확인할 수 있다.

펌웨어가 chip vendor의 HAL을 사용함과 동시에 interrupt와 DMA와 같은 하드웨어와의 더 복잡한 상호작용이 일어난다. high level에서는 어떤 라이브러리가 사용되는지 알 수 없지만, 이 계층에서 구축된 handler는 기기 간 이식성이 좋다. 또한 선택한 계층은 섹션 5에서 확인할 수 있듯이 분석의 효율성에 영향을 줬다.

어떤 라이브러리를 펌웨어가 사용하는지에 달려있다. **이 연구에서는 HAL level에서의 펌웨어 리호스팅에 초점을 맞춘다.**  HALucinator를 평가할 때는 다른 계층(미들웨어 같은)에서의 emulation도 조사해본다.

## 2.3 High-Level Emulation

확장 가능한 펌웨어 emulation이 가능한 high-level emulation을 먼저 살펴본다.

미들웨어 라이브러리나 HALs 수에 따라 emulation 이 느려진다. 동일한 제조사부터 device family를 가지는 큰 그룹의 기기는 동일한 programmer-facing library abstraction을 공유한다. STMicroelectronics는 모든 Cortex-M 기기를 위한 통합된 HAL 인터페이스를 제공한다. mBed와 같은 high-level 라이브러리는 여러 제조사의 기기를 위한 abstraction을 제공하고 protocol stack (lwIP와 같은)은 상세한 통신 protocol를 추상화한다. 이러한 라이브러리를 가로채는 것은 서로 다른 제조사를 가지는 기기들을 emulate할 수 있다.

HALs는 하드웨어를 추상화하기 때문에 제작한 handler도 이러한 단순성을 상속받았다. high-level emulation은 하드웨어의 low-level에 대한 이해의 필요성을 제거했다. 따라서 **handler는 low-level MMIO 조작을 하지 않아도 되고 단순히 상응하는 HAL 함수를 가로채서 적절한 인자를 대응하는 peripheral model에 넘겨주고 펌웨어가 원하는 값을 돌려주면 된다.**

**이러한 접근은 handler를 능동적으로 개발할 수 있었다.** **분석가가 신경쓰지 않거나 emulator에서 필요하지 않는 peripheral은 함수를 우회하도록 handler를 구현하고 실행 성공 시에 대한 리턴값만 넘겨주면 된다. 외부 입력과 출력이 필요할 때는, 호스트가 필요한 통신을 잘 하도록 충실도가 높은 handler가 필요하다.**

예를 들면, STM32Cube HAL의 HAL_TIM_OscConfig 함수는 여러 시간과 시계 파라미터를 설정하고 교정한다. 만약 처리되지 않으면 펌웨어는 함수 내 무한 루프에 들어서게 된다. emulator는 시계나 진동자 개념이 없기 때문에 이 함수의 handler는 실행 성공에 대한 리턴값인 0을 반환하도록 해주면 된다. HAL_Ethernet_RX_Frame이나 HAL_Ethernet_TX_Frame 함수와 같이 충실도가 높은 handler는 네트워크 기능을 잘 수행하도록 ethernet frame을 잘 보내고 받아야 한다.

이러한 접근방법은 하나의 emulation에서도 여러 충실도 레벨을 handler에 설정할 수 있도록 한다.

# 3. Design

high-level emulation을 위한 디자인을 위해

(1) library matching을 통해 HAL library 함수를 펌웨어에 위치시킨다.

(2) HAL 함수에 대응하는 high-level 대체물을 넣는다.

(3) emulate된 펌웨어에서 외부와 상호작용할 수 있도록 한다.

HALucinator는 여러 펌웨어와 분석 상황을 고려해서 동작하도록 설계되었다. HALucinator가 사용하는 여러 구문과 요소를 소개하기 위해 연결된 컴퓨터로부터 보내진 문자를 출력하기 위해 펌웨어에서 serial port를 사용하는 예시를 들어본다. 하드웨어 초기화 코드에서, 펌웨어는 serial 데이터를 보내고 받기만 하면 된다. 분석가는 기기의 CPU가 STM32F4 microcontroller인 것을 알고 STMicroelectric의 HAL 라이브러리 데이터베이스와 함께 LibMatch를 사용한다. 이것은 바이너리에서 HAL_UART_Receive와 HAL_UAR_Transmit으로 식별된다. 그럼 분석가는 HALucinator에  contfiguration(handler의 집합)을 만든다. handler가 없으면 분석가가 만들어줘야 한다. 두 HAL 함수는 serial port, buffer 포인터, 길이를 인자로 사용한다. handler는 serial port에 대한 peripheral model에서 사용 가능한 형태로 인자를 해석한다. 그럼 I/O Server는 데이터를 serial port peripheral model과 머신의 터미널 사이에서 데이터를 전송한다. HALucinator에서 실행되는 펌웨어는 여떤 콘솔 프로그램에서든 터미널에서 사용 가능하다.

다음 섹션에서 더 자세한 HALucinator의 기능을 설명할 것이다.

## 3.1 Prerequisites

HALucinator가 능동성을 제공하기 때문에 타겟 펌웨어를 고려했을 때 필요한 준비물은 적다.

1. 먼저, 분석가는 **기기에 대한 펌웨어**를 가지고 있어야 한다. HALucinator은 “blob” 펌웨어 이미지를 중점적으로 본다.
2. HALucinator을 사용해서 emulating하는 동안 하드웨어는 필요없고 **어떤 기기를 emulate해야 하는지에 대한 정보**만 있으면 된다. HALucinator는 **펌웨어를 로드하는데 필요한 정보에 대한 파라미터(아키텍처나 메모리 레이아웃)가 필요**하다.
3. 또한 분석가는 **HALs**, OS library, middleware, networking stack과 같은 **라이브러리**를 가지고 있다고 여긴다. 그리고 chip vendor가 컴파일하는데 사용하는 toolchain도 가지고 있어야 한다.
4. HALucinator는 **펌웨어 코드를 돌리고 HALucinator의 instrumentation을 지원하기 위한 emulator를 필요로 한다.** 이는 메모리 레이아웃을 알고, high-level handler를 위한 특정 주소를 후킹하는 능력, 그리고 emulator의 레지스터와 메모리에 접근할 수 있게 한다.

이 연구에서는 ARM Cortex-M 기기를 중심으로 설명하는데, 이 기기의 memory map이 표준화되어있고 vendor가 제공하는 메뉴얼이 잘 써있고 메모리에서의 펌웨어 위치를 펌웨어 blob 스스로 읽을 수 있다. 그리고 QEMU와 같은 일반적인 에뮬레이터를 사용한다. 각 Cortex-M vendor는 오픈소스 HAL과 compiler, configuration을 제공한다.

펌웨어를 가지고 있고, CPU vendor를 알아서 vendor의 SDK를 가지고 있으면 HALucinator를 사용할 수 있다.

## 3.2 LibMatch

high-level emulation을 주요 요소는 emulation이 사용할 수 있도록 프로그램 내에 abstraction 장소를 확보하는 능력이다.

third party에서는 emulation을 진행하기 전에 라이브러리를 위치시키는 능력이 필요하기 때문에, 코드나 오픈소스 펌웨어 프로젝트를 사용해서 리호스팅하려는 개발자는 이미 컴파일 시에 이 정보를 가지고 있다.

stripped  바이너리에서 함수 주소를 찾는 문제를 해결할 방법은 임베디드 CPU 아키텍처에서 지원이 적다.

((LibMatch 개발 배경))

펌웨어 그 자체 때문에 library matching이 복잡하다. 펌웨어 라이브러리 함수는 크기에 최적화되어있어서 두 함수가 비슷한 코드여도 완전 다른 목적으로 동작할 수도 있다. 여러 작은 HAL 함수들은 I/O 연산과 같은 컴파일 타임에서 프로세서 정의일 수 있다. 물론 이러한 함수들도 주변기기에 따라 다른 용도로 사용될 수 있다. 펌웨어 라이브러리 함수의 특이한 기능은 라이브러리 부분이 아닌 코드에서 함수를 호출하는 것이다. desktop 라이브러리로 실행을 하여 일을 수행하고 caller에 반환해준다. 일반적인 펌웨어 기능은 아니고 overrides를 포함하는 HAL에서 흔히 볼 수 있는 패턴이다. 개발자가 HAL에서 심볼을 override하거나 명시적으로 callbacks를 사용하면 코드 pointer는 함수 인자로 전달된다. **따라서 완전히 제대로 동작하는 handler를 만드려면 라이브러리 함수의 이름과 주소뿐만 아니라 그들이 호출하는 애플리케이션 코드도 커버해야 한다.**

((LibMath에 대한 설명))

이러한 문제를 해결하기 위해 LibMatch를 개발했다. LibMatch는 binary-to-libary matching을 위해 프로그램 내 함수 context을 활용했다. LibMatch는 라이브러리의 unlink한 바이너리 object 파일의 control flow 그래프를 추출해서 **HAL 함수에 매치할 데이터베이스를 생성**한다.

다음 스텝으로 수행된다:

1. **Statistical comparison**
    
    **(프로그램에서의 함수, 데이터베이스 내 라이브러리 함수) 쌍**에 대해 세 개의 metric(**기본 블록 수, control flow graph edge, 함수 호출**)을 비교한다.
    
    이 세 개의 메트릭에서 다른 점이 있는 함수는 match되지 않으므로 non-match쌍을 제거한다.
    
2. **Basic Block Comparison**
    
    이전 단계에서 매칭된 함수 쌍은 이후 블록 내용으로 비교한다.
    
    **두 함수 기본 블록의 IR 내용이 정확히 일치하면 두 함수가 match된다고 본다.**
    
    하지만 포인터나 포인터로서 사용되는 오프셋, relocation target은 바이너리 IR 코드나 라이브러리마다 다를 수 있어서 배제한다. 도달할 수 없는 점프나 호출은 무시한다. 이런 비교 메트릭은 단순하고 더 복잡한 매칭 방법이 존재하지만, match가 true이면 high-confidence match인 것으로 trade-off를 만들었다.
    
3. **Contextual Matching**
    
    이전 단계에서 생성된 일치 집합에는 collision도 있고, 이것으로는 함수를 식별 해낼 수 없다. 따라서 대상 프로그램 내에서 함수의 문맥을 활용하여 프로그램 내에서 일치하는 위치를 찾아서 어떤 다른 함수가 될 수 있는지 추론해봄으로써 이러한 문제를 명확히 한다. 두 개의 프로그램의 call graph로 matching을 정제하는 툴도 있는데, 두 번째 프로그램이 라이브러리의 데이터베이스이기 때문에 이것이 불가능하다. 데이터베이스 내 라이브러리는 unlink되어있고 call graph가 없다. 또한 HALs은 많은 비슷한 이름의 함수들이 있기 떄문에 특정 라이브러리 내에서 함수의 call graph를 추론할 수 없다. 따라서 **caller context와 callee context를 사용**해서 효과적으로 실제 라이브러리의 call graph와 근사하도록 하고 collision을 줄였으며 라이브러리 데이터베이스와 타겟 사이에서 다르도록 함수 이름을 붙였다.
    
    **caller context는 충돌을 해결하기 위해 사용**한다. **각 가능한 충돌에 대해, 라이브러리의 디버깅 정보를 사용해서 호출된 함수 이름을 추출**했다. 모든 호출된 함수와 각각 매칭해보면서 타겟 바이너리에 애매모호한 함수와 동일한 호출된 함수를 가지고 있는지 확인해본다. **만약 타겟에서의 함수 이름 셋과 충돌된 매치가 동일하면, 해당 매치는 계속 유효**하게 되고 그렇지 않으면 제거된다.
    
    callee context을 위해, step 2에서이 결과 함수 매칭에 library 객체 내 디버깅 심볼을 기반으로 이름을 붙인다. 만약 함수가 충돌되어도 해결할 수 있다. 함수가 데이터베이스에 없으면 이름을 붙일 수 있다.
    
    caller context와 callee context 과정이 재귀적으로 진행된다.
    
4. **Final Match**
    
    유효한 매치는 타겟 바이너리 내 함수에서 할당된 유일한 이름이면 식별될 수 있다.
    

## 3.3 High-level Emulation

함수 식별 이후, emulator는 **선택된 함수의 실행을 교체**해서 emulate된 펌웨어가 제대로 실행할 수 있도록 해야 한다. 이러한 intercept된 함수는  기기의 on-chip이나 off-chip 주변 기기와 연관이 있고 **수동으로 구현**된다.

이 과정을 단순화하기 위해 라이브러리마다 필요한 구현을 각 **handler**로 나누었다. 이 과정으로 각 주변 기기는 각 매치된 HAL 함수를 위한 특화된 하나의 작은 handler만 사용해서 한 번만 쓰면 된다.

### Handlers

HAL의 코드에 대한 대체물로 handler를 설명한다. handler 생성은 수동으로 하지만, 각 HAL이나 library 당 한번만 하면 되고 분석하는 펌웨어에 독립적이다. 각 HAL 함수는 함수 인자, 리턴값, 내부 의미가 다르지만 모든 handler는 단순하고 종류가 적다.

큰 HAL이 있을 수 있지만 대부분 펌웨어는 함수의 작은 섹션만 사용해서 괜찮다.

handler

1. 분석가가 HALucinator에서 바이너리를 돌리면 모든 I/O 접근이 보고된다. 이는 아직 handler에 의해 대치되지 않은 부분이다.
2. 펌웨어가 멈추거나 의도된 기능을 하지 못하면, 분석가는 handler를 구현한다.

이 과정이 반복되면서 handler들은 큰 coverage를 가지게 되고 더 정확한 기능을 수행하게 된다. library matching이 이루어지지 않았거나 emulation를 위한 함수 이름이 없어도 이 과정은 수행될 수 있다.

### Peripheral Models

Peripheral model은 특정 주변 기기가 해야만 하는 어떤 클래스나 타입인지에 대한 본질적인 측면을 처리한다. emulator와 외부 사이의 인터페이스 역할을 제공한다.

예를 들면, serial port를 위한 peripheral model은 데이터 전송과 수신에 대한 buffer만 있다. HAL의 serial 전송과 수신 함수가 호출되면 관련 handler가 peripheral model을 사용해서 필요한 기능을 수행한다.

### I/O Server

리호스팅된 펌웨어가 완전히 동작하도록 하려면 CPU 외부 기기와 통신해야 한다. 따라서 펌웨어와 데이터를 주고받을 수 있게 하기 위해, 각 peripheral model은 데이터를 보내거나 받고 interrupt를 발생시키기 위한 인터페이스를 가지고 있다. 인터페이스는 I/O Server를 통해 노출된다. I/O 서버는 publish/subscribe 설계 패턴을 사용한다. 이 패턴에 따라 peripheral model이 처리하는 topic을 publish 또는 subscribe한다. 예를 들면, Ethernet model은 “Ethernet.Frame” topic 위에서 메시지를 주거나 받는다. “Ethernet.Frame” 는 다른 기기와 연결해서 ethernet frame을 받을 수 있도록 한다.

ethernet model은 I/O Server에 의해 만들어진 메시지를 보내서 host ethernet 인터페이스, 다른 emulate된 시스템 또는 둘다와 연결될 수 있다. 모든 I/O를 중심화하면 emulate된 펌웨어의 모든 외부 통신을 프로그램이 조정할 수 있다.

### Peripheral Accesses Outside a HAL

HAL를 handler와 peripheral model로 교체해도 여전히 펌웨어에는 MMIO 접근이 발생한다. HALucinator는 모든 외부 I/O handler를 사용자에게 알려준다.크래시 없이 하드웨어와 바로 통신하는 코드에서 읽기는 0을 반환하고 쓰기는 무시하도록 한다. MMIO 연산, 특히 쓰기 연산은 안전하게 무시하도록 할 수 있다. HAL에서 사용되지 않는 케이스는 섹션 7에서 논의한다.

## 3.4 Fuzzing with HALucinator

high-level emulation으로 퍼징과 같은 자동화된 동적 분석을 할 수 있다.

### Fuzzed Input

타겟에 어떤 input을 넣을지 결정해야 한다. HALucinator은 fuzz 라는 peripheral model을 제공한다. `**fuzz`는 handler에서 사용될 때 fuzzer의 input stream에서 데이터를 가져와서 handler로 보낸다.** 임베디드 시스템에서는 여러 종류의 입력 소스가 있을 수 있어서 분석자가 fuzz에 넣을 것을 유연하게 선택할 수 있게 한다.

### Termination

퍼저에 입력을 주면, 퍼징된 펌웨어는 종료가 되어야 한다. 현대 퍼저는 일반적으로 desktop 프로그램을 타겟으로 하고 있어서 입력이 없을 때 종료되도록 하고 있다. 하지만 이런 로직이면 펌웨어는 종료되지 않는다. **따라서 `fuzz` 라는 모델을 설계할 때 프로그램 실행 중에 크래시가 발생하지 않으면 퍼저를 종료하도록 하는 시그널을 보내서 프로그램을 종료할 수 있도록 했다.**

### Non-determinism

펌웨어는 완전히 정의할 수 없는 행위를 가지고 있어서, 퍼저가 정확한 coverage metric을 가지려면 이러한 것은 제거해야 한다. 이러한 것은 instrumentation을 통해 프로그램에서 제거되는데, HALucinator에서도 제공한다. **HALucinator는 식별되면 (rand(), time() 등) randomness로 생성되는 함수에 대한 static handler를 제공한다.**

### Timers

정의할 수 없는 경우 중 하나는 타이머로, clock rate를 보장할 수 없기 때문에 이는 정의할 수 없는 행위가 된다. 따라서 실행된 block 수에 timer rate를 묶어서 정의 가능한 행위가 되도록 하는 **Timer peripheral model을 제공**한다.

### Crash Detection

**emulation handler는 스스로 체크하는 기능을 제공한다.** 예를 들면 인자의 pre-condition을 체크(예. pointer 유효성, 양수 버퍼 길이 등)한다. 또한 emulation은 컴파일 시간에 처리되는 instrumentation을 추가할 수 있다. HALucinator는 malloc, free 심볼이 가능하면 heap-checking 을 제공한다.

### Input Generation

퍼징은 mutation 알고리즘에 따라 만들어지는 입력이 필요하다. HALucinator의 fully-interactive 모드는 기기와 통신하는데 사용할 수 있고 **라이브러리 호출에 대한 리턴값을 로깅할 수 있다. 이는 퍼징의 seed로 사용할 수 있다.**

# 4. Implementation

## LibMatch Implementation

`angr` 의 VEX 기반 IR, control flow graph 복원, 유연한 아키텍처 지원 기능을 사용한다. angr의 CFG (control flow graph) recovery 분석을 통해 매칭에 필요한 자료가 수집된다. angr의 CFG (control flow graph) recovery 분석은 VEX IR 구문과 그들의 내용 위에서 작동하는 블록 내용 비교를 포함한다.

**Cortex-M 아키텍처를 위한 LibMatch 구현 시 angr를 확장해서 Cortex-M calling convension, missing instruction, function start detection, indirect jump resolution 기능을 추가했다.**

실행 시 HAL과 라이브러리를 컴파일해서 얻어진 unlinked object file을 사용하여 알고있는 함수의 데이터베이스를 생성한다. 이후 펌웨어 내에 심볼 없이 함수를 위치시키는데 이 데이터베이스를 사용한다. 펌웨어에 대해 LibMatch가 실행될 때, 식별된 함수와 함수의 주소 리스트와 collision 노트가 만들어진다.

## HALucinator Implementation

HALucinator는 Python으로 구현했고 Avatar를 사용해서 전체 시스템 실행 환경을 구축했다.

### **HALucinator input**

- memory layout (Flash나 RAM의 사이즈와 위치와 같은)
- 함수 리스트 (함수와 연관된 handler에서 함수를 가로채기 위해)
- LibMatch에서 만든 함수와 주소 리스트
    - **함수 주소는 각 함수의 첫 번째 instruction에서 breakpoint를 걸어서 함수를 intercept 하는데 사용**
    - **breakpoint에서 중단 시 해당 실행에 handler를 등록한다.**

### **Handler**

- Python 으로 구현
- 각 함수는 하나 이상의 펌웨어의 HAL나 라이브러리를 커버한다.
- emulator 레지스터나 메모리에 읽고 쓸 수 있음
- 펌웨어 내 함수를 호출할 수 있다.
- peripheral model과 상호작용 가능

### **Peripheral model**

- Python 로 구현
- 요구되는 기능 구현하기 위해 system library나 I/O Server를 사용한다.
- 하드웨어 real-time clock에서 시간을 가져오기 - 호스트 시스템의 time() 함수 호출해서 구현

### I/O Server

ZeroMQ messaging 라이브러리를 이용해서 publish-subscribe system으로 구현했다.  호스트 시스템에서 peripheral model로 이벤트를 전달하기 위해, I/O Server는 emulator의 peripheral model과 연결한다.

## Fuzzing with HALucinator

HALucinator에서 펌웨어 퍼징 기능을 제공하기 위해 QEMU engine을 AFL Unicorn으로 교체했다.

AFL-Unicorn은 유연한 API로 QEMU의 ISA emulation 특성을 결합했고 AFL에서 사용되는 coverage instrumentation과 fork-server 능력을 제공한다.

AFL-Unicorn에 추가한 기능

1. HALucinator의 peripheral hardware support 기능 (Unicorn, AFL-Unicorn에서 지원 X BUT 펌웨어 퍼징에 필요함)
2. interrupt controller model (ARM의 Cortex-M interrupt 지원 위해, 펌웨어 퍼징에 필요)

AFL-Unicorn은 여러 실행 에러(invalid memory access, invalid instruction 등)를 상응하는 프로세스 시그널(SIGSEGV 등)로 해석해서 crash를 탐지한다.

# 5. Evaluation

HALucinator의 확장 가능한 emulation 목표 위해

1. 펌웨어 내 HAL 함수의 정확한 식별
2. handler를 통해 HAL 함수를 대치
3. 합리적인 handler 생성
4. 의미있는 펌웨어 동적 분석 (퍼징?)

이러한 4가지 목표를 LibMatch의 HAL 식별 기능을 통해 16가지 애플리케이션을 emulate해서 평가하고, HALucinator를 network-connected 애플리케이션 퍼징에 이용한다.

[실험 대상]

16개 펌웨어 샘플 (각 펌웨어는 서로 다른 개발 보드 사용)

## Experiment Setup

STMicroelectronic 펌웨어

- STMicroelectronic 보드는 Cortex-M4 microcontroller를 사용하는데, QEMU에서 Cortex-M4 instruction 지원 적음
- 따라서 Cortex-M3 instruction set으로 컴파일함

Atmel 펌웨어

- Atmel Studio 7으로 컴파일
- Cortex-M0 ISA

NXP 펌웨어

- SDK “release” configuration으로 컴파일
- Cortex-M3
- 바이너리에서 모든 심볼은 strip되어있음

## 5.1 Library Identification in Binaries

먼저 LibMatch의 펌웨어 프로그램 내 함수 주소 복원 효율성 평가한다.

LibMatch에서는 HAL과 관련 미들웨어 라이브러리들이 제공하는 모든 함수 셋에 매칭하려고 노력했다. 각 펌웨어 내의 심볼 정보를 사용하고, 각 함수 주소를 HAL 데이터베이스 내의 주소와 비교해서 결정한다.

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2c45dd0f-780d-4493-9297-3ce68cbdc374/Untitled.png)

Table 1에서는 LibMath를 사용한 Context Matching과 LibMatch를 사용한 Context Matching 결과를 보여준다.

각 지표는 다음을 의미한다

“Correct” - 정확히 식별한 함수 수

“Collision”, “Incorrect”, “Missiong” -  LibMatch가 정확히 식별할 수 없었던 함수 수

“External” - HAL 외부 라이브러리 함수인데 LibMatch가 정확히 context matching한 함수 수

테스트 결과, LibMatch를 사용하지 않으면 평균 74.5%의 라이브러리 함수를 매칭할 수 있고, LibMatch를 사용하면 평균 87.4%의 라이브러리 함수를 매칭할 수 있다.

Context Matching은 펌웨어 리호스팅에 필요한 함수를 식별한다. STMicroelectronics’s PLC application를 예로 들면, 이 바이너리를 리호스팅하려면 각 라이브러리에 대한 handler는 이 callback들을 호출해야 한다. 따랏 라이브러리 데이터베이스 내 있지 않아도 콜백들의 이름을 복원하는 일은 리호스팅 과정에서 필요하다.

충돌은 unlabel되는 함수의 가장 큰 원인이다. 함수 중에서는 서로 다른 이름으로 비슷한 기능을 하는 코드들이 있다. 예를들면, STM32 HAL의 HAL_TIM_PWM_Init 함수와 HAL_TIM_OC_Init 함수 코드는 거의 같은데 서로 다른 데이터를 처리한다. 하지만 이러한 비슷한 코드 문맥을 구별하기에는 충분하지 않다.  사용되지 않는 interrupt handler들은 동일한 내용을 가지고 있었어서 충돌을 일으켰다. interrupt handler이기 때문에 직접적으로 호출되지 않아서 문맥을 통해 해결되기 어려웠다. 이러한 요인들이 library matching에 어려움을 주었다.

LibMatch의 “Incorrect” 매치의 경우 링킹 과정에서 라이브러리 함수 이름이 변하는 케이스에 해당한다. LibMath는 하나의 함수 매치를 찾지만 링킹 과정에서의 이름 변화로 잘못된 매치를 하게 된다. 정확도 측정 기준은 이름이기 때문에 “Incorrect”로 분류했다.

“Missing” 매치는 애플리케이션이 심볼을 재정의해서 매치할 수 없는 경우, angr의 CFG recovery 수행 시 버그가 있는 2가지 경우에 해당한다.

결론적으로 unmatch나 충돌된 함수는 high-level emulation에서 필요하지 않고, 100%보다 적은 LibMatch의 정확도는 HALucinator에 영향을 주지 않는다.

## 5.2 Scaling of High-Level Emulation

### Handlers and Human Effort

handler 구현은 수동 작업이다.

이 부분에서의 **평가는 시간, 코드 복잡도를 기준**으로 한다.

**평가 방법**

**handler를 3개로 분류**한다:

- Trivial handler: 함수가 올바르게 실행됐는지에 대한 상수만 리턴한다. 함수 intercept하기 위한 정보 필요 X
- Translating handler: intercept된 함수 파라미터를 peripheral model의 action으로 해석한다. 모델에 맞는 데이터 가져온 후 해당 모델을 호출해야 한다. 함수 파라미터 정보가 필요하고, 모델로 전달할 값을 읽고 모델로에 적절한 함수 파라미터 값을 써야 한다. 예를 들면 ENET_SendFrame에 대한 handler는 함수 파라미터로 frame buffer와 length를 읽은 후, ethernet model로 전달한다.
- Internal Logic: 교체된 함수에 대한 내부 로직에 대한 이해가 필요한 부분

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1f6ec691-28ae-47b3-9f1c-1538a0f37b2d/Untitled.png)

Table 2는 3개의 handler로 분류하였을 때 생성된 handler 수를 보여준다.

펌웨어 샘플에 대해, 내부 함수가 어떻게 intercept되는지에 대한 적은 이해로 85%가 넘는 handler가 구현될 수 있음을 확인할 수 있다.

내부 로직에 대한 이해가 필요한 13%의 handler의 경우, HAL가 자신의 global state를 조작하는 겨우이다. 예를 들면, Atmel Ethernet, 6LowPAN 연구에서 EXTI(exteral interrupt controller)가 사용되는데, 이는 하나의 CPU interrupt에 여러 외부 interrupt를 매핑한다. EXTI의 ISR (Interrupt Service Routine)은 MMIO 레지스터에서 interrupt 에 대한 ID를 찾아본 후 이 ID를 적절한 callback을 global array에서 찾는데 사용한다. HALucinator는 global array에 접근할 수 없기 때문에 바로 적합한 callback을 찾을 수 없어서 EXTI handler 구현(MMIO status 레지스터를 읽고 쓰기 위한 를 위한)이 필요하다. 이는 chip 레벨의 이해가 필요하기 때문에 Internal Logic 케이스로 분류된다.

### Scaling Across Devices

기기 사이에서 확장될 수 있게 하나의 HAL의 emulation을 어떻게 HLE가 가능하도록 하는지 검증하기 위해, 서로 다른 보드와 CPU를 사용하는 NXP MCUXpresso HAL 을 사용해서 실험을 진행했다.

family에 제조사에 상관없이, 동일한 HAL를 공유한다. 20개의 uart_polling 인스턴스의 경우, 20개의 서로 다른 개발보드에서 가져올 수 있다. UART는 거의 모든 보드에서 사용 가능하고 다른 주변 기기에서는 보드에 따라 다르다. 동일한 NXP UART handler와 peripheral model을 사용해서 20개의 펌웨어 샘플을 돌리고 transmit handler, receive handler, default handler의 3가지 handler를 사용했다. 총 29개의 함수가 intercept됐다.

동일한 handler와 model으로 여러 제품 family를 지원할 수 있음을 확인할 수 있다. clock과 power 초기화 함수에 대한 이름을 식별하는 부분에서만 어려움이 있었다.

## 5.3 Interactive Emulation Comparison

**QEMU, Avatar, HALucinator을 사용해서 16개의 펌웨어 리호스팅을 진행**했다.

**QEMU에서 제공하는 Avatar의 기본 configuration을 사용해서 하드웨어 없이 QEMU에 펌웨어를 로드해서 실행했다**. 이 configuration에서는 QEMU에서 지원하지 않는 MMIO에서 fault가 발생한다. 모든 MMIO에 대한 읽기, 쓰기는 물리적인 하드웨어로부터 가져오는 것이다. **HALucinator는 LibMacth에서 찾은 함수를 활용해서 충분한 수의 HAL 함수를 가로채서 펌웨어가 물리적인 하드웨어에서 실행했을 때 필요한 기능을 수행할 수 있도록 했다. MMIO 실행을 위해서 MMIO handler를 구현해서 읽기에서는 0을 반환(성공 시 반환값)하고 쓰기는 무시하도록 했다.**

만약 실제 하드웨어에서처럼 에뮬레이ㅡ된 시스템에서 동일한 기능을 수행할 수 있으면 “correct”로 여긴다.

[결과]

TCP/IP - 물리적 하드웨어에서처럼 동일한 데이터 전송 가능

HTTP Server 펌웨어에서 동일한 페이지 접근 가능

FatFs - 적절한 파일에 읽기,쓰기 가능

6LoWPAN - 물리적인 하드웨어에서처럼 동일한 순서로 UART로 메시지 보낼 수 있음

UART - UART 통해 데이터를 보내고 받을 수 있고, 예상되는 응답을 줌

PLC - 안드로이드 프로그래밍 앱에 연결, loadder 로직 로드에 성공하고 실행 가능

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a0c35c6b-7494-44f3-acf4-b1135c9e2085/Untitled.png)

Table 3는 각 펌웨어에서 사용된 HALucinator에서 모델링된 라이브러리와 인터페이스를 보여준다.

[QEMU에서 수치]

BB (Basic Block)

- 실행된 블록 수를 의미한다.
- 이는 얼마나 펌웨어가 실행됐는지를 의미

EBC (External Behavior Correct)

- 실제 하드웨어에서 펌웨어를 돌렸을 때의 외부 입/출력 행위를 나타낸다.

[Avatar 수치]

Fwd R/W

- 보드에 읽고 쓴 횟수 (Avatar가 올바르게 메모리 요청을 처리하는지 검증)

[HALucinator 수치]

MMIO

- MMIO에서 처리된 주소 수

Funcs

- intercept된 함수 수 (HALucinator를 사용해서 펌웨어 돌리는데 얼마나 작업이 필요한지 측정)

이 실험은 실제 하드웨어에서의 펌웨어 기능과 비교했을 때 얼마나 HALucinator가 복잡한 펌웨어를 emulate하여 동일한 기능을 해주는지를 보여준다.

HALucinator는 평균 1000 basic block을 실행했고 이는 Avatar의 10배에 해당한다.

서로 다른 4개의 보드, 서로 다른 3개의 제조사에서의 emulation은 광범위한 하드웨어의 지원에 대한 HLE의 능력을 입증하고, 동일한 peripheral model 재사용 가능함은 vendor와 하드웨어 플랫폼 사이에서 확장 가능함을 보여준다.

## 5.4 Fuzzing with HALucinator

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/60b39b84-1a95-4c09-b056-17ebeae82ad2/Untitled.png)

Table 4에서는 테스트에서 사용된 펌웨어와 HALucinator의 emulation이 네트워크와 연결된 펌웨어를 퍼징하는데 유용함을 보여준다. AFL를 사용해서 퍼징했을 때의 통계를 보여준다. 원래 하드웨어와의 종속성을 제거했기 때문에 하드웨어의 전체 기능을 사용해서 이 실험을 진행할 수 있었다.

WYCNINWYC는 임베디드 환경에서의 crash detection 기준으로 진행했다.

serial port 모델을 fuzz 모델로 대체했고, 퍼징에 crash 발생하지 않는 XML 입력을 입력으로 사용했다. 5개의 crash 중 4개의 crash를 트리거했고,

# 7. Limitations and Discussion

## Use and Availability of HALs

HAL가 오픈소스나 SDK 형태로 사용 가능해야 한다.

LibMatch 데이터베이스는 펌웨어 컴파일 환경과 유사해야 하고, QEMU는 microcontroller 아키텍처를 지원해야 한다.

이러한 제한사항이 있어야 펌웨어 분석하는 HALucinator의 적용 가능성을 높일 수 있다.

HAL가 펌웨어에서 사용되지 않거나 HAL를 분석가가 사용할 수 없으면 LibMatch는 emulation을 위한 인터페이스를 식별할 수 없다.

## Library Matching

LibMatch는펌웨어 내 HAL과 라이브러리를 찾는 목적으로 library matching 알고리즘을 확장해서 구현했다.

LibMatch 제약은 컴파일러나 라이브러리 버전을 알 수 없을 때이다.

# 8. Conclusion

펌웨어 리호스팅을 위한 high-level emulation concept을 연구하고 임베디드 “blob” 펌웨어를 분석했다. abstraction을 위해 hardware abstraction layer에서 사용 가능하도록 library matching을 향상시켰다. 구현 시 펌웨어와 chip 모델 간에 재사용 가능하도록 abstract 요소로 나누었다.

HALucinator는 동적 분석과 퍼징을 위한 시스템에 이러한 기술을 결합한 첫 번째 시스템이다. 여러 복잡한 perpherial과 서로 다른 vendor의 CPU, HAL를 가지는 16개의 펌웨어 샘플을 리호스팅했고, 적은 노력과 실제 하드웨어 접근 없이 리호스팅이 가능했다.

마지막으로 HALucinator를 통해 펌웨어에서 버그를 발견할 수 있었다.

HALucinator: https://github.com/embedded-sec/halucinator

Halucinator-fuzzer: https://github.com/ucsb-seclab/hal-fuzz

728x90
반응형

관련글 더보기