DEX 구조

Android/Reversing 2020. 3. 2. 13:13

 

 

DEX(Dalvik Executable) 파일의 구조를 한번 알아보자!!

 

 

1. DEX 파일이란??

Dex 파일에는 Android 런타임에서 궁극적으로 실행되는 코드가 포함되어 있다.

즉 binary 파일로 기계어로 되어있다. 이를 디컴파일하면 smali 코드가 되는 것.

 

 

2. DEX 사용 이유??

우선 Android App은 Java 코드로 만든다.

Java 소스는 .class로 컴파일되어 JVM 위에서 돌아가지만 여러가지 이유(저작권이라하나?.. 갑자기 단어가 생각안남)로 JVM을 사용하지 않고 ART(Android Runtime)라는 것을 따로 만들어서 사용한다. ART 위에서 동작시키기 위해서는 .class 파일을 .dex 파일로 만드는 과정이 필요하다.

대부분 모바일 장치는 메모리, 처리 성능 및 배터리 수명이 제한되므로 JVM보다 ART가 우수한 성능을 제공한다.

ART는 AOT(Ahead-of-Time) 컴파일링 방식과 JIT(Just-in-Time) 컴파일링 방식을 모두 사용하는데

이 두가지 컴파일링 방식의 차이는 추후에 정리해서 올리겠다. 

 

 

3. DEX 파일 구조

dex 파일은 두 가지 부분으로 나눌 수 있다.

 

dex file format

1. 메타데이터를 포함하는 파일 헤더(header)

2. 특정 정보를 가지고있는 섹션(string_ids, type_ids, proto_ids, ...)

 

3.1. 파일 헤더

파일 헤더의 각 부분을 자세하게 살펴보자.

 

1) magic - ubyte[8]

File Magic Number 값이다.

dex 파일 포맷의 시작 byte(magic number)는 정해져있다.

64 65 78 0A 30 33 35 00

dex 파일 포맷의 시작 byte

처음 8 byte에 'dex'와 버전 번호가 있어야 한다.

여기서 035는 현재 targetSdkVersion API가 23?(정확하지 않음) 일 때 35이다.

4번 째 byte는 개행 문자이고 마지막 8번 째 byte는 null 값이다.

 

 

2) checksum - uint

파일의 나머지 부분(magic, checksum 필드만 제외)에 관한 adler32 체크섬이며 파일 손상을 감지하는 데 사용된다.

만약에 app을 다운로드 도중 파일 내의 byte 값이 손상된 경우 checksum 값과 일치하지 않게되며

Android Framework는 APK 설치를 거부한다.

 

checksum header

 

3) signature - ubyte[20]

파일의 나머지 부분(magic, checksum, signature 제외)의 SHA-1 해시이며 파일을 고유하게 식별하는 데 사용되며 Multidex와 같은 기능에 사용된다.

 

signature header

※ Multidex란?

안드로이드 개발 시 규모가 커지게 되면 코드가 많아지고 라이브러리도 많아지게 된다.

이 때 65536(64K)개 이상의 함수(Method)를 초과할 경우 dex로 컴파일할 수 없다.

이를 해결하기 위해 Multidex 개념이 나오게 됬다.

Multidex는 64K 메소드가 넘는 dex 파일을 여러개로 쪼개주고 쪼개진 dex를 읽을 수 있게 해준다.

MainActivity.java -> MainActivity.class -> MainActivity1.dex, MainActivity2.dex

 

 

4) file_size - uint

헤더를 포함한 전체 파일의 크기

 

file_size header

리틀 엔디안 방식으로 읽으면 되므로 file_size의 값은 0x005956C4이다. 

10진수 값으로 변환해보면 5854916 byte이다.

실제로 classes.dex 파일의 크기를 확인해보면 같은 것을 알 수 있다.

 

실제 classes.dex 파일 크기

 

5) header_size - uint

헤더(전체 섹션)의 크기이다. 값은 0x70(112 byte)로 정해져 있다.

 

header_size header

 

6) endian_tag - uint

dex 파일은 빅 엔디안, 리틀 엔디안 인코딩을 모두 지원한다.

하지만 표준은 리틀 엔디안 방식을 사용한다. 

uint ENDIAN_CONSTANT = 0x12345678;               // 빅 엔디안
uint REVERSE_ENDIAN_CONSTANT = 0x78563412;   // 리틀 엔디안

dex 파일의 Endian Constant 헤더

0x78563412 이므로 리틀 엔디안 방식을 사용한다는 것을 확인할 수 있다.

 

 

7) link_size, link_off - uint

link_size는 연결 섹션의 크기를 나타낸다. 파일이 정적으로 연결되지 않은 경우 0을 갖는다.

link_off는 파일의 시작 부분에서 연결 섹션까지의 오프셋이며 link_size가 0인 경우 0이다.

오프셋이 0이 아니면 link_data 섹션으로의 오프셋이어야 한다.

 

link_size, link_off header

 

8) map_off - uint

파일의 시작 부분에서 맵 항목까지의 오프셋. 

오프셋은 0이 아니어야 하며, data 섹션으로의 오프셋이어야한다.

데이터는 map_list에서 지정한 형식이어야 한다.

 

map_off header

map_off 값은 0x005955E8 이다.

 

9) string_ids_size, string_ids_off - uint

string_ids_size는 문자열 식별자 목록의 문자열 수이다.

string_ids_off는 파일의 시작 부분에서 문자열 식별자 목록까지의 오프셋. 

오프셋이 0이 아닌 경우 string_ids 섹션의 시작 부분까지여야 한다.

 

DEX 파일에는 각 특징마다 고유의 섹션을 가지고 있다.

여기서 말하는 오프셋은 해당 섹션의 위치를 나타내는 값이다.

string_ids 섹션은 dex 파일 내에서 사용하는 모든 문자열을 저장하는 영역이다.

 

string_ids_size와 string_ids_off header

string_ids_size는 0x0000B110

string_ids_off는 0x00000070

 

10) type_ids_size, type_ids_off - uint

type_ids_size는 형식 식별자 목록의 요소 개수.

type_ids_off는 파일의 시작 부분에서 형식 식별자 목록까지의 오프셋. 

오프셋이 0이 아닌 경우 type_ids 섹션의 시작 부분까지여야 한다.

type_ids 섹션은 string_ids 영역에 저장된 문자열의 성격을 저장하고 있는 영역이다.

 

 

 

11) proto_ids_size, proto_ids_off - uint

proto_ids_size는 프로토타입 식별자 목록의 요소 개수.

proto_ids_off는 파일의 시작 부분에서 프로토타입 식별자 목록까지의 오프셋. 

오프셋이 0이 아닌 경우 proto_ids 섹션의 시작 부분까지여야 한다.

proto_ids 섹션은 dex 파일 내에서 함수의 구조를 저장하고 있는 영역이다.

 

 

 

12) field_ids_size, field_ids_off - uint

field_ids_size는 필드 식별자 목록의 요소 개수.

field_ids_off는 파일의 시작 부분에서 필드 식별자 목록까지의 오프셋. 

오프셋이 0이 아닌 경우 field_ids 섹션의 시작 부분에 있어야 한다.

field_ids 섹션은 클래스의 이름, type, class, package 이름을 저장하는 영역이다.

 

 

 

13) method_ids_size, method_ids_off - uint

method_ids_size는 메서드 식별자 목록의 요소 개수.

method_ids_off는 파일의 시작 부분에서 메서드 식별자 목록까지의 오프셋. 

오프셋이 0이 아닌 경우 method_ids 섹션의 시작 부분에 있어야 한다.

method_ids 섹션은 method의 이름, type, 소속 class 이름을 저장하는 영역이다.

 

 

 

14) class_defs_size, class_defs_off - uint

class_defs_size는 클래스 정의 목록의 요소 개수.

class_defs_off는 파일의 시작 부분에서 클래스 정의 목록까지의 오프셋. 

오프셋이 0이 아닌 경우 class_defs 섹션의 시작 부분까지여야 한다.

class_defs 섹션은 class에 대한 전체적인 정보와 데이터에 대한 기초 정보를 저장하는 영역이다.

 

 

 

15) data_size, data_off - uint

 

data_size는 바이트 단위로 나타낸 data 섹션의 크기. sizeof(unit)의 짝수 배수여야 한다.

data_off는 파일의 시작 부분에서 data 섹션 시작 부분까지의 오프셋. 

 

 

 

 

이렇게 특징별로 섹션이 나누어져있고 헤더 정보에서 특정 섹션에 대한 정보와 위치를 찾아갈 수 있다.

 

'Android > Reversing' 카테고리의 다른 글

ELF Parser  (0) 2020.03.09
Smali Code  (0) 2020.03.04
안드로이드 APK 구성  (0) 2020.03.02
안드로이드 앱 종류  (0) 2020.03.01
안드로이드 리버스 엔지니어링 공부 순서  (0) 2020.03.01

WRITTEN BY
Bugday

,