(작성중)JuCify: A Step Towards Android Code Unification for Enhanced Static Analysis

Hunjison·2022년 1월 21일
0

Reading Papers

목록 보기
7/18

평가

0. Abstract

[Android에서 사용되는 Native 코드에 대해서 Dex bytecode와 결합된 call graph를 만들 수 있게 해준다고 함. Soot Framework(FlowDroid 계열) 위에서 동작하며, 도구(아마도 FlowDroid)의 정확성을 올려준다고 함.]
안드로이드 앱 패키지에서 네이티브 코드는 흔하다, Dex bytecode와 JNI를 통해 공존하고 서로 상호작용하면서. 그러나, 가장 최고의 분석 접근들은 네이티브 코드를 간과하였다. 이러한 한계는 앱의 실행 코드에 대해 완전한 시각을 가지지 못한 광범위 정적 분석의 타당성의 위협이다. 이러한 문제를 해결하기 위해 안드로이드 앱의 모든 코드를 통합한 모델을 구축하는 것을 목표로 새로운 접근을 하였다. JuCify는 네이티브 코드와 바이트 코드의 call graph를 추출하고 결합하여, 일반적인 안드로이드 분석 프레임워크에서 즉시 이용 가능한 모델을 생성한다. JuCify는 Soot 기반으로 만들어졌다. 우리는 상당한 양의 네이티브 코드로부터 호출되는 자바 함수들이 call-graphs에서 "도달할 수 없는" 상태가 되는지, 통합 모델 없이는,에 대해 실증적인 연구를 수행하였다. JuCify를 이용하면, 우리는 정적 분석 도구가 결제 라이브러리 코드의 호출 등 안드로이드 프레임워크의 민감한 코드를 호출하는 경우들을 발견하게 할 수 있다. 추가적으로, JuCify는 최고의 (정적 분석) 도구들이 더 나은 정확성을 달성하고 네이티브 코드를 통한 데이터 유출을 더 잘 탐지할 수 있게 해준다. 마지막으로, JuCify를 통해 네이티브 코드를 통해 민감한 데이터 유출을 찾아낼 수 있다는 것을 보여준다.

1. Introduction

[네이티브 코드 중요, 덜 연구된 분야. 네이티브 코드와 바이트 코드를 통합한 모델을 제시함. Jimple을 지원하여 기존 연구들이 이용하기 편하게 하였음.]
안드로이드 앱 분석은 지난 10년간 가장 활발한 분야였다. 정적 분석은 특히 다양한 접근과 도구를 생산했는데, 버그 탐지, 보안성 점검, 악성코드 탐지 등 다양한 분야에서 이용되었다. 널리 이용되는 최고의 접근들, FlowDroid와 같이,은 Dex bytecode에 집중하여 분석하였다. 불행하게도, 최근 연구들이 악성코드가 그들의 행위를 숨기기 위해서 혹은 샌드박스 탈출을 구현하기 위해서 네이티브 코드에서 빌드된다는 것을 밝혀냈다.
안드로이드 앱에서 네이티브 코드를 고려해야 하는 이유는, 보통 앱과 악성 앱에서 네이티브 코드의 사용량이 급격하게 늘어나고 있기 때문이다. 우리의 실증적인 조사는 62.9%의 앱이 그들 패키지에 네이티브 코드를 포함하는 것으로 밝혔다. 그러나 네이티브 코드는 앱 보안성 점검에서 거의 고려되고 있지 않다. 그것이 몇가지 난제를 가지고 있기 때문에 ~~ 조사들에서 간과되었다.
연구자들이 네이티브 코드 문제를 해결하기 위한 방법을 제시할 때(JN-SAF, DroidNative, NativeGuard, TaintArt 등), taint 추적을 위한 통합된 분석, native entry point 탐지, 머신러닝 특징 추출은 일반적으로 무의미하다(ad-hoc). 실제로 이러한 연구들은 바이트코드와 네이티브코드의 나뉘어진 분석 결과를 합치는 방법으로, 기술을 개발하였다. 그러므로 그들은 명시적인 통합 모델을 생산하지는 않은 셈이다.
우리의 연구는 안드로이드 코드에 대한 통합 모델을 생성하는 방법으로 통합-앱 분석의 공백을 채우는 것이 모굪이다. 우리는 JuCify를 제시한다, 바이트코드와 네이티브 코드의 경계를 무너뜨리고, 따라서 정적 분석의 일반적인 한계를 다룰 수 있는 프레임 워크이다. JuCify는 바이트코드와 네이티브 코드를 통합된 모델로 합치려는 최초의 시도이다. Jimple Intermediate Representation을 JuCify 통합 모델에서 사용하였다. Jimple은 널리 이용되는 Soot 프레임워크의 내부 representation에 이용되며, 실제로도 많은 정적 분석 연구들에서 역할을 하고 있다. Jimple을 지원함으로써, 다양한 분석들이 네이티브 코드를 즉시 고려할 수 있는 기회를 제공한다.

This paper,

JuCify는 네이티브 코드를 고려하여 앱의 통합된 모델을 생성하는 멀티-스텝 정적 분석 프레임워크이다. JuCify는 1) Dex bytecode와 네이티브 코드 사이의 호출을 가져오기 위해 symbolic execution을 이용하고, 2) native call-graph를 미리 계산하며, 3) Dex bytecode와 native call-graph를 합치고, 4) 새롭게 생성된 함수들을 휴리스틱-베이스로 정의된 Jimple statement로 덧붙인다.

Contribution

  • JuCify를 제시, 더 나은 정적 분석을 하기 위해 안드로이드 앱 코드의 통합 모델. JuCify가 앱 패키지 내부의 바이트코드와 네이티브 코드를 통합하는 Jimple 코드를 생성하도록 구현함.
  • JuCify 생성 모델에 대한 평가를 수행함. JuCify는 안드로이드 앱의 call-graph를 더 낫게 할 수 있음. JuCify는 call-graph에서 이전에는 연결되지 않았던 것들을 연결함.
  • data flow 추적 업무에서 통합된 앱 모델을 평가함. JuCify가 FlowDroid의 정확성을 끌어 올리는 것을 증명하였음. 0% 부터 82%까지, 또 recall을 0% 부터 100%까지 바이트코드-네이티브 데이터 추적을 타겟으로 한 벤치마크에서 성능이 나왔음.
  • JuCify를 실제 안드로이드 앱을 대상으로 평가했을 때, 현존하는 분석 도구들이 네이티브 코드를 통해 유출되는 민감한 정보를 발견할 수 있게 할 수 있음.
  • 오픈 소스로 Github에 공개함.

2. Background & Motivation

자바와 코틀린은 안드로이드 앱 개발을 지원하는 2개의 큰 축이다. 그들의 프로그램은 Dex bytecode로 컴파일되고, 앰 패키지 내부에 DEX 파일의 형태로 포함된다. 그럼에도 불구하고, Java Native Interface(JNI) 덕분에, 네이티브 코드 기능들도 안드로이드 앱에서 접근이 가능하다. 그들은 C/C++ 등으로 컴파일되어 .so 바이너리로 포함되게 된다.

2.1. Java Native Interface(JNI)

[JNI 예시, 코드로 동작 원리 이해]
JNI는 주어진 언어로 작성된 프로그램이 다른 언어의 서브루틴을 호출할 수 있도록 하는 FFI의 일종이다. JNI는 Java를 네이티브로, 또 그 반대도 지원한다.

2.1.1. Java to native code

Listing 1
Listing 1은 JNI로 Java로 부터 C++으로 쓰여진 네이티브 함수를 호출하는 것을 보여준다. 관련된 자바 함수는 native 키워드와 함께 쓰인다. 정적으로는 특별한 이름 규칙이 있다. 예를 들어 Java 함수가 nativeGetImei라면, 네이티브 함수는 Java_com_example_nativeGetImei이다.
Listing 2
동적으로는, 개발자가 네이티브 함수의 이름을 마음대로 붙일 수 있다, 그러나 JNI에게 자바 함수와 어떻게 매핑할 것인지 알려주어야 한다. 따라서 개발자들은 1) 자바 네이티브 함수를 JNINativeMethod 구조를 이용하여 대응하는 네이티브 함수에 매핑해야 한다. 2) 특정한 JNI Interface function인, JNI_OnLoad를 이용하여 매핑을 등록한다. 3) Android VM으로부터 JNI_OnLoad 내부의 RegisterNatives를 호출한다.

2.1.2. Native to Java

JNI가 있으면, 네이티브 코드 내에서 자바 오브젝트를 생성하고 조작할 수 있다. 자바 오프젝트의 필드와 메소드도 네이티브 코드로부터 접근 가능하고, JNI Interface 함수로부터 실행할 수도 있다. 결과적으로 Java reflection처럼, 메소드와 클래스를 얻기 위해 string을 사용하여, 자바 메소드를 호출할 수 있다.

Listing 1과 2는 Java 와 C++의 상호작용을 묘사했는데, 실제로 JuCify에서는 apk 레벨의 분석을 수행한다. 따라서 Dex bytecode와 compiled native code 간에 상호 호출이다.

Motivating Example

[Native로 동작하는 악성코드의 흐름을 Java 분석 만으로는 추적할 수 없는 예시.]
바이너리 정적 코드 분석은 컴파일된 코드가 적절한 분석을 하기 어렵기 때문에 그 자체로 도전과제이다. 비록 최고의 안드로이드 정적 분석 도구 접근 방법이 정교하지만, 대부분의 경우 네이티브 코드를 간과하였고, 몇몇은 고려하였다. Listing 1에서 보여준 예시처럼, 우리는 네이티브 코드를 정적 분석에서 꼭 고려하여야 한다.
First, Listing 1에서 onCreate() 함수 내부에서 imei가 반환되고 이것이 Log.d()로 향하는데, taint tracking의 관점에서 getImei(source) to Log.d(sink)이다. 그러나 대부분의 접근들을 이러한 흐름을 놓친다.
Second, malicious()는 자바 코드에서 호출되지 않기 때문에, call-graph에서 빠지고 "도달 불가능"으로 여겨진다. 따라서 분석되지 않으며, 현존하는 도구들이 함수 내부의 악성 코드를 탐지하기에 실패한다.
Figure 1
이 예시에서의 예상되는 call-graph를 나타낸 Figure 1에서, 현존하는 최고의 접근들은 Green 노드까지 분석이 가능하다. 그러나, Red 노드는 네이티브 코드를 분석 가능해야만 접근할 수 있다. 네이티브 코드 분석의 어려움을 극복하려는 여러 시도들이 있다. 그러나, 그들은 특정 분석에 치중하고, 각자의 솔루션을 제시하였다. 그와 반대로 우리는 명시적인 하나의 모델을 만들 것이고, 그것을 통해 바이트 코드와 네이티브 코드를 동시에 분석이 가능하도록 적용될 수 있다.

3. Approach

주어진 안드로이드 앱에서 JuCify는 Dex bytecode와 네이티브 코드를 하나의 통합된 모델로 통합하고 이 모델을 Jimple representation으로 인스턴스화하는 것을 목표로 한다. JuCify의 개념에 대해 설명하고, 네이티브 행동을 가늠하기 위해 어떻게 앱을 다룰지를 간략히 소개한다. 공간적 제약 때문에 모든 Jimple에 대한 모든 기술적 디테일을 다루지는 않았다. Github에서 참고 바람!

3.1. Call graph as Unified Preliminary Model

Call Graph(CG)는 CG = (V,E)의 형태로 정의될 수 있는데, V는 함수들을 나타내는 꼭지점의 집합이고, 𝐸 ⊆ {(𝑢,𝑣)|𝑢,𝑣 ∈ 𝑉}∀(𝑢,𝑣) ∈ 𝐸와 같은 모서리들의 집합이다.

JuCify는 Figure 2와 같은 전체적 구조를 갖는다. 우선, NativeDiscloser라는 서브모듈이 native call graph를 만들고, 바이트코드와 네이티브코드 사이에 상호 호출을 추출한다. 그 이후에 native call graph는 정리되고, Soot에 적응하기 위해 준비된다. 결국에 모든 call graph가 bytecode-native 함수 호출로 인해 통합된다.

Step 0: Native Call Graph

Native 프로그램의 call graph를 만드는 일은 사소하지 않다. 많은 연구가 여기에 직면했고 몇몇 솔루션을 제공하였다. 그 중에서 Angr을 이용하였다. NativeDiscloser 내부에 래핑되어 있다.

Step 1: Bytecode-Native Code Invocation Extraction

총 4가지 스텝으로 구성된다. 1) 바이트코드 메소드 정보 검색 2) 엔트리 메소드 호출 추출 (바이트코드 에서 네이티브) 3) 네이티브 함수 호출을 추적하고, exit 메소드 호출을 추출. (??? 4개라며 ㅋㅋ )

Step 1.1 : Methods info extraction은 바이트코드 메소드의 정보를 추출하는 과정이다. 정적으로 등록된 함수에 대해서 메소드 호출 추출을 하기 위해 필요한 시그니처 정보들을 채우는 것을 목표로 한다. 우리는 AndroGuard를 이용하여 업무를 수행하였다.

Step 1.2 : Entry method invations extraction. 엔트리 메소드 호출은 바이트코드로부터 네이티브 함수를 호출하는 것이다. 이러한 호출에 대해서 Java native method를 entry function과 매치해야 한다. (entry method는 자바, entry function은 C++). 업무를 수행하기 위해 동적, 정적 등록을 신경써야 한다. 정적으로 등록된 함수들은 naming convention을 통해 쉽게 발견될 수 있다. 그러나 JNI interface 함수 호출을 통해 동적으로 등록하는 것은, 보다 정교한 테크닉이 요구된다. 우리는 symbolic execution을 이용하였다.
NativeDiscloser은 input으로 라이브러리 파일(.so)과 이전 단계의 함수 정보를 받는다. 그것은 우선 각각의 바이너리의 심볼 테이블을 스캔하여, 1) 정적으로 등록된 네이티브 함수와 2) JNI_OnLoad 함수를 찾아 동적 등록 함수를 발견하려고 한다. 그 이후에 JNI_OnLoad가 존재하면 이 함수가 symbolic execution으로 분석되는 것이다. <-를 위해서 Angr에 의존한다.
Symbolic Execution의 간단한 개념

Step 1.3 : Exit method invocation extraction. 우리는 네이티브 코드로부터 바이트코드 메소드 호출을 찾아왔다. 우리는 이러한 바이트코드 메소드를 exit method라고 부른다. 우리는 이러한 exit method가 특정 JNI Interface를 호출함으로써 call 되는 것을 Section 2.1.2에서 설명했다. JNI 함수 호출에 대한 정보를 수집하는 것은 꽤 어려운 일이다.
실제로, 이것을 극복하기 위해, NativeDiscloser는 Step 1.2에서 수집한 모든 정보를 symbolic execution하여, exit method 호출을 찾고, entry 와 exit method 호출 간에 관계를 구축한다.
더 나아가 exit method는 native function chain 내부 깊은 곳에서도 호출될 수 있다. 그러나 symbolic execution은 native 함수 사이의 경계에 대해서 알아차리지 못한다.(??) 그러한 이유로, 우리는 exit method를 검색하는 중에 추적 메커니즘을 구현하였다. 우리는 각각의 네이티브 함수의 시작 주소에 의존하여 네이티브 함수의 스택을 유지하고, 새로운 함수를 스택으로 push한다, 그것의 시작 주소가 도달된 경우에. 스택으로부터 함수를 pop하는 것은 네이티브 함수의 리턴 주소에 도달한 경우에 이루어지는데, 그것은 네이티브 함수에 들어갈 때의 특정 레지스터나 메모리 주소에서 획득할 수 있다(예를 들면, ARM에서의 link register LR). 이것을 통해 우리는 네이티브 함수가 exit method 호출을 하는지를 알 수 있게 되었다.
(Link Register, 함수 호출 전 LR 에 리턴 주소를 저장하고 점프함(함수 호출 시 리턴주소 스택 활용 X) 출처: https://hyunmini.tistory.com/80 [Hyunmini])

TBA

profile
비전공자 출신 화이트햇 해커

0개의 댓글