개요
smali 코드는 안드로이드 애플리케이션의 Dalvik 가상 머신 코드를 표현한 언어이다. Dalvik VM은 안드로이드 앱의 실행을 위한 가상 머신이며(지금은 ART를 사용한다.), smali는 이 VM에서 동작하는 바이트 코드를 사람이 읽기 쉽고 이해하기 쉬운 형태로 변환하여 나타낸다. 즉, Smali는 Dalvik VM이나 ART(안드로이드 런타임)에서 동작하는 바이트 코드의 어셈블리어로 Java 코드(Java 실행 코드가 들어있는 Dex)를 디컴파일한 결과물이다. 따라서, 안드로이드 앱을 대상으로 내부 동작 조작을 위한 소스코드를 변조할 때 Java 코드를 디컴파일한 산출물인 smali 코드를 수정하게 되며, 이러한 일련의 과정을 "리패키징"이라고 한다. 이번 포스팅에서는 smali 코드 분석 시 알아야 할 기초 지식에 관하여 기술하고자 한다.
smali 컴파일 과정
smali 코드는 일종의 텍스트 파일로 ".smali" 확장자를 가지며, 안드로이드 애플리케이션의 Java 코드(Dex)를 디컴파일한 결과물이다. [그림 1]은 애플리케이션 개발 시 작성한 Java 코드(Kotlin으로 작성해도 Java로 컴파일된다.)가 실행 파일인 APK파일까지 순차적으로 컴파일되는 과정을 보여주고 있으며, "apktool"과 같은 도구를 이용하여 역으로 Dex 파일을 디컴파일 할 경우 smali 코드 파일을 추출할 수 있게 된다.
아래의 [그림 2]는 Anditer 앱의 Dex 파일을 디컴파일 산출물인 smali 코드 중 일부로 메서드 호출, 변수 할당, 제어문 등 Java 코드와 비슷한 구조를 가지고 있다.
smali 어셈블리 지시자
아래 [표 1]은 smali 코드에서 사용되는 지시자이다.
구문 이름 | 설명 |
.class | 클래스 이름 |
.super | 상속 관계의 상위 클래스 이름 |
.source | 소스 이름 |
.field | 멤버 변수 정의 |
.method | 함수 이름 및 시작 지점 |
.register | 함수에서 사용되는 임시 변수(레지스터 변수) |
.end method | 함수 종료 지점 |
public | 접근 제어자로 외부에서 접근 가능 |
protected | 접근 제어자로 상속 관계에 있는 하위 클래스에서만 접근 가능 |
private | 접근 제어자로 클래스 내부에서만 접근 가능 |
.parameter | 매개변수 지시어 |
.prologue | 함수 진입 부문을 나타내는 지시어 |
.line xxx | 소스 코드와 바이트 코드 간의 줄 번호 연결을 나타내는 지시어 |
[표 1] smali 어셈블리 지시자
smali 데이터 타입
아래 [표 2]는 smali 코드에서 사용되는 데이터 타입을 Java 코드에서 사용하는 데이터 타입과 매칭한 표이다.
smali 코드 데이터 타입 | Java 코드 데이터 타입 |
V | void |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long |
F | float |
D | double |
string | String |
Lxxx/xxx/xxx | object |
[표 2] smali 코드 데이터 타입
smali 제어문
아래 [표 3]은 smali 어셈블리 코드에서 사용되는 제어문에 관한 내용을 정리한 표이다.
구문 이름 | 설명 |
const | 정수, 부동 소수점 등의 상수 값을 정의하는 지시어 |
const-string | 문자열 상수를 정의하는 지시어 |
const-wide | 64비트 부동 소수점 상수 값을 정의하는 지시어 |
return | 명령어 결과 값 반환 |
if-eq | if-eq [register1], [register2], :[label] 두 값이 서로 같을 경우에 지정된 레이블로 분기 (a = b)와 동일 |
if-ne | if-ne [register1], [register2], :[label] 두 값이 서로 다를 경우에 지정된 레이블로 분기 (a! = b)와 동일 |
if-eqz | if-eqz [register], :[label] 레지스터의 값이 0일 경우에 지정된 레이블로 분기 (a == 0)와 동일 |
if-nez | if-nez [register], :[label] 레지스터의 값이 0이 아닐 경우에 지정된 레이블로 분기 (a != 0)와 동일 |
if-ge | if-ge [register1], [register2], :[label] 첫 번째 값이 두 번째 값보다 크거나 같을 경우에 지정된 레이블로 분기 (a >= b)와 동일 |
if-le | if-le [register1], [register2], :[label] 첫 번째 값이 두 번째 값보다 작거나 같을 경우에 지정된 레이블로 분기 (a <= b)와 동일 |
goto | goto :[label] 지정된 레이블로 무조건적으로 분기하는 명령어 |
switch | sparse-switch vA, [:label_1, :label_2, ...] 조건 분기 구문 중 하나로, 주어진 값에 따라 분기 경로를 선택하는 명령어 Java 언어에서 사용되는 switch ~ case문과 동일 |
iget | 객체의 인스턴스 변수에 저장된 값을 불러오는 명령어 |
[표 3] smali 코드 데이터 타입
smali 코드에서 사용되는 레지스터 변수
[ v 레지스터 ]
[그림 3]의 "v"로 시작하는 변수는 가상 레지스터 변수를 의미하며, 뒤에 숫자는 첫 번째, 두 번째, 세 번째 변수를 뜻한다. 또한, 해당 가상 레지스터는 smali 코드에서 데이터를 저장하고 조작 시 사용되며, 실제로는 메모리 상에 위치하지 않고 프로그램의 실행 중에만 임시로 사용되는 변수로 주로 연산, 비교, 조건 분기 구문 등에서 활용된다.
[ p 레지스터 ]
[그림 4]의 "p"로 시작하는변수는 매개 변수 레지스터로 함수로 전달되는 인수 값을 저장하는데 사용된다. 정적 함수(static Function) 함수에서는 "p0"은 첫 번째 매개 변수이고 "p1"은 두 번째 매개 변수이다. 다만, 정적 함수가 아닌 그 외에 함수에서 "p0"은 함수의 컨텍스트 정보로 코드 상 "this"에 해당되며, "p1"이 첫 번째 매개 변수가 된다.
smali 구문 해석 실습
아래 코드는 특정 앱의 smali 코드이며, 구문을 해석한 것이다.
.method private static final onCreate$lambda-2(Lkotlin/jvm/internal/Ref$IntRef;Lcom/zj/wuaipojie/ui/ChallengeSecond;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/view/View;)Z // 메서드 반환 값 타입은 boolean으로 거짓 또는 참 값을 반환한다.
.registers 7 //레지스터 갯수
.line 33 //실제 코드가 위치한 줄 번호
iget p0, p0, Lkotlin/jvm/internal/Ref$IntRef;->element:I //p0(첫 번째 파라미터) 레지스터 변수에 할당된 p0의 인자 값을 읽어 온다.
const/4 p5, 0x1 //p5 레지스터 변수에 16진수의 0x1 값 할당
const/16 v0, 0xa //v0 레지스터 변수에 10이라는 값이 할당되며, 16진수에서 a는 10을 의미한다.
if-ge p0, v0, :cond_15 // p0의 값이 v0의 값보다 크거나 같은지(즉, p0의 값이 10보다 크거나 같은지)를 판단하고, 크거나 같을 경우 :cond_15 레이블로로 분기한다.
.line 34
check-cast p1, Landroid/content/Context; //컨텍스트 객체
const-string p0, "메세지" //"메세지"라는 문자열을 p0 레지스터에 할당
check-cast p0, Ljava/lang/CharSequence; //CharSequence 객체 참조
invoke-static {p1, p0, p5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
//Toast 팝업 출력 시 텍스트 등의 정보를 p1에 저장
move-result-object p0 //위 구문의 결과가 p0으로 전달된다.
invoke-virtual {p0}, Landroid/widget/Toast;->show()V //사용자에게 Toast 팝업을 표시하는 명령어
goto :goto_31 //"goto_31" 레이블로 이동
:cond_15 //분기 레이블
invoke-virtual {p1}, Lcom/zj/wuaipojie/ui/ChallengeSecond;->testFuncs()Z //testFuncs 함수의 반환값이 참인지(즉, 결과가 1인지) 확인한다.
move-result p0 //위 구문의 결과가 p0 레지스터에 할당된다.
if-eqz p0, :cond_43 //p0 레지스터 값이 0이면 "cond_43" 레이블로 분기한다.
const p0, 0x7f0d0018 //arsc(안드로이드 리소스)에서 ID 인덱스 값이 "0x7f0d0018"인 자료 추출하여 p0 레지스터에 저장한다.
.line 37
invoke-virtual {p2, p0}, Landroid/widget/ImageView;->setImageResource(I)V //setImageResource() 함수를 호출하여 p0에 저장된 이미지로 ImageView 이미지를 변경한다.
'안드로이드 기타 > ETC' 카테고리의 다른 글
Digital Forensics Challenge 2023 CTF 모바일 문제 풀이 (1) | 2023.10.04 |
---|---|
ADB를 이용하여 액티비티를 강제로 실행시키는 방법 (0) | 2023.08.17 |
Burp Proxy 인증서를 디바이스에 설치하는 2가지 방법 (0) | 2023.08.14 |