← 목록으로

안드로이드 앱 리버싱: APK 분석 기초

[Rev] 리버싱 · 작성: 2026-07-19 19:34:18 · 수정: 2026-07-21 23:30:21 · 조회 9

안드로이드 앱 리버싱을 "APK 압축을 풀면 되는 것"이라고만 알면, 그 안의 코드가 x86 어셈블리가 아니라 **완전히 다른 바이트코드(Dalvik/DEX)**로 되어 있다는 것과, 그걸 왜 사람이 읽을 수 있는 Java 유사 코드로 거의 완벽하게 되돌릴 수 있는지를 놓치게 된다.

구조 레벨: APK 안에 담긴 것

APK는 사실 ZIP 파일이다 — unzip app.apk만 해도 구조가 드러난다.

왜 DEX는 원본 Java 코드로 "거의 완벽하게" 복원되는가

C/C++를 네이티브 기계어로 컴파일하면 변수 이름·타입 정보 상당 부분이 소실되지만, 자바(Kotlin 포함)는 JVM/Dalvik 바이트코드로 컴파일될 때 타입 시스템과 클래스 구조 정보를 바이트코드 안에 그대로 유지해야 한다 — 리플렉션, 클래스 로딩 같은 자바 런타임 기능이 이 메타데이터에 의존하기 때문이다. jadx 같은 디컴파일러는 이 풍부한 메타데이터를 이용해서, DEX 바이트코드를 거의 원본과 다름없는 Java 소스코드로 되돌릴 수 있다 — 이게 안드로이드 리버싱이 네이티브 바이너리 리버싱보다 훨씬 "쉬운" 이유이자, 앱 개발자가 ProGuard/R8로 코드를 난독화(변수·클래스 이름을 의미 없는 문자로 치환)해야 하는 이유이기도 하다.

실전 흐름

  1. jadx-gui로 APK를 통째로 열어 Java 유사 코드로 디컴파일 — 클래스/메서드 이름이 난독화되지 않았다면 로직 파악이 매우 빠르다
  2. apktool d app.apk로 smali(Dalvik 바이트코드의 어셈블리 표현) 추출 — 디컴파일이 실패하거나 코드를 직접 패치해야 할 때는 이 smali 레벨에서 작업한다
  3. 루팅 탐지·인증서 피닝 우회: 앱이 isDeviceRooted() 같은 함수로 루팅 여부를 체크해서 실행을 막는 경우, jadx로 그 함수를 찾은 뒤 (a) smali를 직접 패치해서 항상 false를 반환하게 만들거나, (b) Frida로 동적 계측 글에서 다룬 Interceptor.attach()로 그 함수의 반환값을 런타임에 조작하는 두 가지 방법 중 하나를 쓴다 — 코드를 영구 패치할지, 실행 중에만 바꿀지의 트레이드오프다
  4. 패치 후 재서명: smali를 직접 고쳐 APK를 재빌드하면, 안드로이드는 앱 설치 시 서명을 검증하므로 자체 서명 키로 다시 서명해야 기기에 설치된다 — 원본 개발자의 서명과 달라지므로, 원본 앱을 덮어 설치하는 업데이트 형태로는 못 쓴다는 제약이 있다

여기서 끝나지 않는다

안드로이드 리버싱을 "APK 압축 해제"가 아니라 **"자바 런타임이 유지해야 하는 풍부한 타입 메타데이터를 역이용해 원본에 가까운 소스코드를 복원하는 작업"**으로 이해하면, 왜 난독화(ProGuard/R8)가 안드로이드 앱 보호의 첫 단계로 거의 항상 언급되는지가 같은 논리로 설명된다.

리버싱 카테고리의 글 (7/11)

  1. 리버싱이란 무엇인가: 정적 분석 기초
  2. 어셈블리어 읽기 기초 (x86-64)
  3. 동적 분석: 디버거로 실행 흐름 추적하기
  4. 안티 디버깅 / 안티 리버싱 기법과 우회
  5. 패킹과 언패킹: 실행 파일을 압축/암호화해서 숨기기
  6. Frida로 동적 계측/후킹하기
  7. 안드로이드 앱 리버싱: APK 분석 기초
  8. iOS 앱 리버싱 기초
  9. 크랙미(Crackme) 실습 방법론
  10. PE 포맷 내부 구조: Windows 실행 파일 뜯어보기
  11. 코드 난독화 해제: Control Flow Flattening 분석하기
← Frida로 동적 계측/후킹하기 iOS 앱 리버싱 기초 →