정적, 동적 분석 방법과 APK 추출, Smali 코드 분석, ARM 아키텍처, Frida 를 이용한 동적 후킹에 대한 내용입니다.
정적 분석은 애플리케이션을 실행하지 않고 코드와 리소스를 분석하는 방법이다.
안드로이드 파일 관리자 앱을 활용한 추출 방법
개발자 옵션의 USB 디버깅을 활성화 한 후 ADB 명령어를 사용하는 방법
#설치된 패키지 목록 확인
adb shell pm list packages -f | grep 패키지명
#APK 파일 추출
adb pull /data/app/패키지경로/base.apk ./추출할파일명.apk
Smali 는 Android DEX 바이트코드를 표현하는 어셈블리 언어로, APK 파일을 디컴파일하여 얻을 수 있다.
V : void (반환값 없음)
Z : boolean (불린)
B : byte (바이트)
S : short (16비트 정수)
C : char (문자)
I : int (32비트 정수)
J : long (64비트 정수)
F : float (32비트 부동소수점)
D : double (64비트 부동소수점)
L : 클래스 타입 (예: Ljava/lang/String;)
[ : 배열 (예: [I는 int 배열)
Static 메서드 : 'invoke-static'으로 호출되며, 'this' 인수가 암묵적으로 전달되지 않는 메서드
Direct 메서드 : 'invoke-direct'로 호출되며, 생성자나 private 메서드가 해당된다. vtable 개입 없이 직접 호출된다.
Virtual 메서드 : 'invoke-virtual'로 호출되며, 자식 클래스에서 override될 수 있는 메서드, 클래스와 관련된 vtable을 사용하여 호출된다.
SO 라이브러리 로드:
const-string v0, "라이브러리명"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
로그 출력:
const-string v8, "log-tag"
invoke-static {v1}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v9
invoke-static {v8, v9}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
Toast 문자열 띄우기:
const/4 v0, 0x1
const-string v1, "메시지 내용"
invoke-static {p0, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
CISC (Complex Instruction Set Computer):
RISC (Reduced Instruction Set Computer):
ARM 모드 : 32비트 명령어 세트를 사용하는 기본 모드
Thumb 모드 : 16비트 명령어 세트를 사용하는 모드, 메모리 효율성을 높이기 위해 도입됨.
ARM 아키텍처에서는 AAPCS에 따라 레지스터가 정의된다.
r0-r3 : 함수 인자 및 반환값
r4-r11 : 지역 변수 저장
r12 : IP (Intra-Procedure-call scratch register)
r13(SP) : 스택 포인터
r14(LR) : 링크 레지스터 (복귀 주소)
r15(PC) : 프로그램 카운터 (다음 실행할 명령어 주소)
데이터 이동:
MOV r1, r2 ; r2 값을 r1으로 이동
시프트 연산:
ASR r1, r2, #4 ; r2를 오른쪽으로 4비트 시프트 (부호 유지)
LSR r1, r2, #4 ; r2를 오른쪽으로 4비트 시프트 (0으로 채움)
LSL r1, r2, #4 ; r2를 왼쪽으로 4비트 시프트
ROR r1, r2, #4 ; r2를 오른쪽으로 4비트 회전
곱셈 연산:
UMULL r4, r5, r1, r2 ; 부호 없는 곱셈 (64비트 결과)
SMULL r4, r5, r1, r2 ; 부호 있는 곱셈 (64비트 결과)
분기 명령어:
B <address> ; 무조건 분기
BL <address> ; 서브루틴 호출 (LR에 복귀 주소 저장)
BNE <address> ; 0이 아닌 경우 분기
BEQ <address> ; 0이면 분기
CMP r1, r2 ; r1과 r2 비교
메모리 접근:
LDR r1, [r2] ; 메모리에서 레지스터로 데이터 로드
STR r1, [r2] ; 레지스터에서 메모리로 데이터 저장
LDMFD sp!, {r4-r6, pc} ; 스택에서 여러 레지스터 복원
STMFD sp!, {r4-r6, lr} ; 스택에 여러 레지스터 저장
동적 분석은 애플리케이션을 실제로 실행하면서 런타임 동작을 관찰하고 조작하는 분석 기법이다.
함수 후킹은 소프트웨어 구성 요소 간에 발생하는 함수 호출, 메시지 이벤트 등을 중간에서 바꾸거나 가로채는 기법. 함수가 실행 직후와 반환 직전에 임의의 코드를 삽입하여 실행시키거나, 함수 실행 흐름을 임의의 코드로 변경할 수 있다.
Frida 타켓 프로세스에 프레임워크 agent을 인젝션하여 파이프를 만들어 놓고, 그 파이프를 통해 통신하며 인라인 후킹을 수행하는 도구. 프로세스의 메모리 영역에 Frida agent 가 삽입되는 방식으로 동작한다.
Spawn 방식
frida -U -l [script.js] -f [package name]
Attach 방식
frida -U -l [script.js] -n [앱 이름]
frida -U -l [script.js] -p [PID]
프로세스 제어 :
//Spawn 방식
var device = frida.get_usb_device();
var pid = device.spawn(["com.package.name"]);
//Attach 방식
var process_session = device.attach(pid);
통신 방법:
car op = recv('input', function(value){
// 실행될 함수
});
op.wait();
//메시지 전송
send("전송할 데이터");
기본 후킹 구조:
Java.perform(function() {
var targetClass = Java.use("com.example.TargetClass");
targetClass.targetMethod.implementation = function(param1, param2) {
console.log("함수 호출됨: " + param1 + ", " + param2);
// 원래 함수 호출
var result = this.targetMethod(param1, param2);
console.log("반환값: " + result);
return result;
};
});
메서드 오버로딩 처리:
Java.perform(function() {
var targetClass = Java.use("com.example.TargetClass");
// 특정 시그니처 지정
targetClass.overloadedMethod.overload('java.lang.String', 'int').implementation = function(str, num) {
console.log("String, int 버전 호출");
return this.overloadedMethod(str, num);
};
});
함수 주소 후킹:
Interceptor.attach(ptr("0x12345678"), {
onEnter: function(args) {
console.log("함수 진입");
console.log("인자1: " + args[0]);
console.log("인자2: " + args[1]);
},
onLeave: function(retval) {
console.log("함수 종료");
console.log("반환값: " + retval);
}
});
SO 라이브러리 후킹:
Java.perform(function() {
// System.loadLibrary 후킹
var System = Java.use("java.lang.System");
System.loadLibrary.implementation = function(libname) {
console.log("라이브러리 로드: " + libname);
var result = this.loadLibrary(libname);
// 라이브러리 로드 후 네이티브 함수 후킹
if (libname === "target_library") {
var nativeFunc = Module.findExportByName("libtarget_library.so", "native_function");
if (nativeFunc) {
Interceptor.attach(nativeFunc, {
onEnter: function(args) {
console.log("네이티브 함수 호출");
}
});
}
}
return result;
};
});
네트워크 패킷을 캡쳐하여 분석하는 도구
중간자 공격 방식으로 HTTP/HTTPS 통신을 가로채고 분석할 수 있는 도구
앱이 설치된 후 생성되는 주요 디렉터리 구조는 다음과 같다.
/data/data/패키지명/
├── cache/ # 앱에서 사용하는 캐시 파일
├── databases/ # SQLite DB 파일 저장
├── lib/ # 앱에서 사용하는 .so 파일 저장
├── shared_prefs/ # SharedPreferences 파일
└── files/ # 앱에서 생성하는 일반 파일
MobSF (Mobile Security Framework): 모바일 앱 보안 분석을 위한 통합 플랫폼으로 정적/동적 분석을 모두 지원
QARK (Quick Android Review Kit): 안드로이드 앱의 보안 취약점을 자동으로 탐지하는 도구
AndroBugs: 안드로이드 앱의 잠재적 보안 문제를 식별하는 정적 분석 도구
Xposed Framework: 안드로이드 시스템을 수정하지 않고도 앱의 동작을 변경할 수 있는 프레임워크
Objection: Frida 기반의 런타임 모바일 익스플로잇 도구킷으로, SSL 핀닝 우회, 파일시스템 접근, 메모리 검색 등의 기능을 제공
루팅 탐지 우회: 앱에서 루팅된 환경을 탐지하는 로직을 우회하는 기법
SSL 핀닝 우회: 앱에서 구현된 SSL 인증서 고정을 우회하여 HTTPS 통신을 분석할 수 있게 하는 기법
안티 디버깅 우회: 앱에서 디버깅을 탐지하고 차단하는 메커니즘을 우회하는 기법