HWAddress Sanitizer 사용하기

Galmaegi
9 min readJul 20, 2020

--

HWAddress Sanitizer은 Android에서 지원하는 llvm기반 메모리 오사용 탐지 도구이다.

장점으로는 기존의 Address Sanitizer에 비해 메모리를 더 적게 사용

  • 포인터 태그를 주소의 최상위 비트에 저장하기 위해 ARMv8의 기능인 TBI를 사용하기 때문
  • Similar CPU overhead (~2x)
  • Similar code size overhead (40–50%)
  • Much smaller RAM overhead (10% — 35%)

감지하는 버그 종류로는

  • Stack and heap buffer overflow/underflow
  • Heap use after free
  • Stack use outside scope
  • Double free/wild free
  • detects stack use after return

최소사양은 아래와 같다.

  • ARMv8의 하드웨어 기능을 사용하기 때문에 ARMv8버전밖에 지원이 안됨(컴파일 시 에러 발생)
  • Android 10, targetApi level을 29로 꼭 설정해야함
  • ndk version 21이상
  • Pixel폰 (aosp빌드가 필수적이기 때문)

먼저 hwasan 라이브러리가 올라가있는 framework를 pixel device에 flash해주어야 한다.(아니면 추측으로는 직접 real device를 rooting한 후 hwasan관련 라이브러리들을 탑재하고 로드하여 사용해도 될 것 같다)

pixel폰에 flash하는 빌드를 사용하는 방법에는 두가지가 있다

  • 안드로이드 ci에 매일 빌드되는 최신버전 사용(추천)
  • aosp 소스코드를 다운로드 받은 후 빌드해서 사용

매일 빌드되는 버전을 추천하고 직접 빌드하는것을 비추하는 이유는

  • aosp소스코드가 매우 큼
  • 처음에 클린빌드를 하는 경우 시간이 너무 오래 걸림

물론 직접 빌드하면 필요한 framework부분들을 수정해서 빌드 할 수 있다는 장점이 있다.

어떻게 사용할까?

  1. Android 이미지를 pixel device에 flash
Android CI

Android CI에선 위의 그림과 같이 매일매일 빌드를 진행하고 있는데, 자신의 phone의 codename과 일치하는 버전을 누른 후 Flash Build를 누르면 이미지를 따로 로컬에 저장하지 않고도 flash를 해준다.(물론 로컬 캐쉬 어딘가에 저장하겠지만…)

자신의 핸드폰 codename을 모른다면 아래의 링크에서 확인하면 된다. (source.android.com 링크를 참조하지 않은 이유는 pixel 4xl의 codename 업데이트가 이 글을 쓰는 시점에 포함되어 있지 않기 때문…)

or

2. App에서 HWAsan을 지원하도록 build.gradle, CMakeLists.txt수정

build.gradle
CMakeLists.txt
Memory fault example

3. 분석

HWAsan을 사용하지 않는다면 간혹 크래쉬가 발생하고 왠만해서는 잘 동작하는 코드이다. (Free된 코드, memory의 범위를 벗어난 코드임에도 불구하고…)

Memory of Crash Logcat
Backtrace of Crash Logcat

하지만 HWAsan을 사용하게 되면 memory, backtrace을 확인할 수 있는데, 이를 참고해 어느 부분에서 크래쉬가 발생했고 왜 발생했는지에 대한 분석이 가능하다.

물론 예시처럼 어거지로 짰을 경우에만 발생하는게 아니라 실제로 개발중인 프로덕트에 적용해봤을때 메모리 오사용의 케이스들에 대해서 크래쉬 -> 리포팅을 해주고 있음을 확인하였다.

4. 어떻게 동작하길래?

아래는 실제로 어떻게 동작하는 코드인지 궁금해서 apk file을 살펴본 과정이다.

$ adb shell pm list packages -f | grep hw
package:/data/app/com.example.hwasanexample-CpCijXmO_3IgUXXfdwM3Ig==/base.apk=com.example.hwasanexample
$ adb pull /data/app/com.example.hwasanexample-CpCijXmO_3IgUXXfdwM3Ig==/base.apk
/data/app/com.example.hwasanexample-CpCijXmO_3IgUXXfdwM3...e pulled, 0 skipped. 18.7 MB/s (3258613 bytes in 0.166s)
$ ls
base.apk
$ java -jar ../apktool_2.4.1.jar d base.apk

먼저 apk file을 획득한 후 apktool로 decompile해준다.

$ ./aarch64-linux-android-readelf --all  -W /Users/user/kkmtest/hwasan_sample/base/lib/arm64-v8a/libnative-lib.so  | pbcopy
$ ./aarch64-linux-android-objdump -d /Users/user/kkmtest/hwasan_sample/base/lib/arm64-v8a/libnative-lib.so | pbcopy

그 후 objdump, readelf로 해당 lib에서 stringFromJNI의 어셈블리 코드 위치, 어셈블리 코드를 아래와 같이 확인한다.

Dynamic symbol table

(Java_com_example_hwasanexample_MainActivity_stringFromJNI는 a1c에 위치하고 있다!)

Decompile code of stringFromJni function

stringFromJNI를 decompile한 코드를 보면 메모리에 접근하는 경우 계속해서 메모리에 잘못 접근하고 있는지 여부를 판단하고 있음을 확인할 수 있다. (retdec decompiler사용)

조금 더 자세한 사항은 아래의 코드들을 참조하여 분석하면 된다.

  • __hwasan_tag_mismatch code
  • __hwasan_tag_mismatch4 code
  • HandleTagMismatch code
  • ReportTagMismatch code

잘못된 정보는 언제든지 답글로 달아주시면 확인하여 반영하겠습니다.

--

--

Galmaegi
Galmaegi

Written by Galmaegi

0 Followers

Android developer

No responses yet