본문 바로가기
JAVA/JSP/안드로이드

잘 짜여진 실제 앱 해부해보기: 안드로이드 리버스 엔지니어링

본문

개발자들은 많은 시간을 들여 앱을 개발하고 있죠. 그렇다면 마지막으로 앱 하나를 분리해본 적은 언제인가요? 컴파일된 애플리케이션의 내부를 보는 과정을 간소화할 수 있다면, 업무에 적용하고 의문점을 해결할 수 있는 중요한 교훈을 더욱 잘 얻을 수 있을 겁니다. 이 강연은 간단한 실제 예제를 통해 안드로이드에 대한 리버스 엔지니어링 툴 세트를 사용해서 최대한 실질적인 이익을 얻을 방법을 보여줍니다. 어떤 것을 해부해보면 배울 수 있는 것이 정말 많습니다!


소개 (0:00)

저는 Jon Reeve이고 이 강연은 “안드로이드 리버스 엔지니어링은 해커만을 위한 것이 아닙니다”라는 주제입니다. 포토 스톡을 제공하는 사이트에 가서 해킹을 검색하면 악성 또는 멀웨어 연구에 대한 이미지가 나오죠. 하지만 제가 말하고자 하는 내용은 이런 이미지의 해커를 위한 것이 아닙니다.

왜 앱을 해부해봐야 할까요? (1:19)

세계에서 가장 큰 단일 머신인 유럽물리입자연구소의 강입자 가속기(LHC)를 생각해 보세요. 빅뱅을 재현하는 실험장치인 이 기계는 입자를 분리해서 내부를 볼 수 있게 합니다. 그것이 세탁기이든 최첨단 기술이든 분리해보는 것에서 많은 것을 얻을 수 있습니다. 애플리케이션도 마찬가지입니다. 소스 코드를 보기 위해서 꼭 소스 코드 자체가 필요하지는 않습니다.

예를 들어 왜 어떤 앱이 특정 권한을 요구하는지 궁금할 수 있습니다. 아마도 카메라로 희한한 일을 하거나 연락처를 모두 읽어서 어딘가로 보낼지도 모릅니다. SMS를 멋대로 보내서 비용이 나가게 할 수도 있습니다. 하지만 그 이유를 파악할 수는 없죠.

개발한 앱에 크래시가 난 경우를 생각해 볼까요? 우리 앱 때문일지도 모르지만 분석 도구나 광고 네트워크 같은 서드 파티 라이브러리 때문일 수도 있는데 이런 경우 소스가 없습니다. 혹은 크래시하는 앱이 직접 만든 앱이 아닐지라도, 개발자로서 새 안드로이드 OS 릴리즈를 사용하다가 맘에 드는 앱이 죽어버린다면 좀 더 많은 정보를 제작사에 줘서 좀 더 빠르게 버그를 픽스하도록 돕고 싶은 생각이 들 수도 있겠죠. 아니면 그 앱의 멋진 기능을 좀 더 들여다 보고 싶을 수도 있습니다.

프리랜서이자 전임 개발자로서 프로젝트를 참여할 때 다른 앱을 보고 비슷한 것을 만들고 싶다고 요청하는 경우가 많았습니다. 물론 앱을 카피할 생각은 없겠지만, 앱이 구성된 내용은 볼 가치가 큽니다.

좋은 시각적 효과가 있을 수도 있습니다. 어쩌면 가능하리라 생각하지도 못한 것을 해냈을지도 모르죠. 예를 들어 삼성 기기에서만 작동하는 카메라 API를 만들었을 수도 있습니다. 아니면 생각해왔던 그대로를 구현했을 수도 있습니다. 관련 라이브러리가 참 많은데, 가장 유명한 것은 무엇일까요? 사람들은 어떤 것을 사용하고 있을까요?

유명한 애플리케이션을 실제로 볼 수 있습니다. 앱을 열고 패키지 구조를 파악하고 어떤 라이브러리를 사용하는지 확인할 수 있죠. 다른 이들의 앱을 리버스 엔지니어링해서 훔치라는 뜻은 아닙니다. 하지만 직접 모든 것을 처음부터 다시 만들어내는 것보다 다른 사람들이 하고 있는 일을 참고하는 것은 유익할 수 있습니다.

APK 얻기 (4:19)

처음으로 해야할 일은 Android Package Kit, 즉 APK를 얻는 일입니다.

$ adb shell pm list packages -f -3

이 명렁어는 기기의 모든 패키지를 간단히 나열해 줍니다. -f는 APK 파일의 위치를 보여주는 파일 이름이고, -3은 서드 파티를 의미합니다.

$ adb pull "$(adb shell pm path $1 | cut -d : -f 2 | tr -d ‘\015’)"

ADB 뉴라인 때문에 불편하고 해석이 필요하긴 하지만, 앞서 말한 명령어 대신 위 명령어를 사용하면 특정 애플리케이션의 APK를 가져올 수 있습니다. Bash Script 사용에 유용합니다.

다른 곳에서 APK를 가져올 수도 있습니다. 단, APK를 호스팅하는 사이트처럼 인터넷에서 받은 APK는 실제로 무엇이 설치될지 알 수가 없습니다. 구글 플레이 스토어와 같이 확실한 곳을 통하는 것이 좋습니다. APK가 사인 됐는지, 그리고 서비스 약관을 위반하지는 않는지 확인해야 합니다.

AAPT (5:45)

APK를 얻은 다음에는 안드로이드 SDK에 포함된 aapt를 써서 가장 기본적인 작업을 할 수 있습니다. 이를 통해 매우 많은 정보를 알 수 있습니다.

이런 개발 뉴스를 더 만나보세요

구독하기
Comments 
$ aapt
Android Asset Packaging Tool
Usage:
 aapt l[ist] [-v] [-a] file.{zip,jar,apk}
 	List contents of Zip-compatible archive.
 aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]
 strings 		Print the contents of the resource table string pool in the APK.
 badging 		Print the label and icon for the app declared in APK.
 permissions 	Print the permissions from the APK.
 resources 		Print the resource table from the APK.
 configurations Print the configurations in the APK.
 xmltree 		Print the compiled xmls in the given assets.
 xmlstrings 	Print the strings of the given compiled xml assets.
aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \
	...
 Package the android resources. It will read assets and resources that are supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R options control which files are output.
aapt r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]
 Delete specified files from Zip-compatible archive.
aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]
 Add specified files to Zip-compatible archive.

위와 같이 정말 많은 일을 할 수 있습니다. 특히 dump 부분에서는 APK에 대한 많은 정보를 얻을 수 있고, GUI 도구를 사용할 수도 있습니다.

저게는 특히 strings 같은 것이 유용했습니다. 자신의 앱에서 이 프로그램을 실행한다면 열어 둔 상태로 놔둔 것이 뭔지 확인할 수 있습니다. 쉽게 앱의 모든 스트링 목록을 가져올 수 있습니다. private API 키를 놔뒀다면 바로 읽을 수 있겠죠. 앱이 사용하고 있는 기본 권한이 뭔지도 볼 수 있습니다. badging과 permissions를 차례로 사용하면 됩니다. 정말 편리하죠.

AAPT Examples

$ aapt dump badging Mysterious.apk

badging과 같은 기본 정보를 알려주는 명령어입니다.

$ aapt dump strings Mysterious.apk

이 명령어로는 앞서 말씀드린 것처럼 흥미로운 스트링들을 볼 수 있습니다.

$ aapt dump xmltree Mysterious.apk AndroidManifest.xml

이 명령어는 전체 manifest를 XML 트리로 덤프합니다. manifest는 XML에서 바이너리 표현으로 바뀌므로 XML 트리를 다시 만들지만, 안드로이드는 이를 읽습니다.

APK (7:17)

APK에는 또 무엇이 들어 있을까요? 다음과 같은 공통 디렉터리도 있습니다.

  • assets/ Raw 파일, 동적으로 적재된 코드 포함 다양한 파일
  • lib/ 네이티브 코드 라이브러리
  • META-INF/ 출처와 무결성 확인을 위한 인증서, 서명, 파일 해시
  • res/ 컴파일되지 않은 리소스
  • AndroidManifest.xml manifest 바이너리 XML 버전
  • classes.dex Dalvik 실행 파일 - VM을 위한 모든 파일
  • resources.arsc 컴파일된 리소스
  • * (기타)

META-INF

META-INF 디렉터리에는 많은 것을 넣을 수 있습니다. 특히 APK의 서명을 나타내는 서명 파일이 위치하는 곳입니다.

여기서 재귀적인 문제가 발생합니다. 만약 APK를 서명하면 서명 파일을 이 디렉터리에 넣어야 하는데 이런 행동을 통해 APK의 서명이 다시 변경되면 곤란하겠죠. 따라서 이 디렉터리는 서명될 때 제외돼야 하죠. 즉, 이 디렉터리의 내용을 수정할 수 있지만 이것이 APK의 서명을 수정하지 않습니다.

서명을 무효로 하지 않고 이 디렉터리의 파일을 변경할 수 있습니다. 기본적으로 이 디렉터리는 서명을 포함하며 이를 통해 서드 파티 APK를 다운로드해서 제대로 된 것인지 확인할 수 있습니다.

다른 컨텐츠

무엇이든 assets 이 될 수 있습니다. 멀웨어를 다루는 경우 실제로 코드처럼 보이지 않도록 조금 난독화되는데 거의 스테가노그래피(Steganography) 항목 수준의 것이 숨겨져 있을 수도 있습니다. 여기에 동적으로 코드를 불러오는 것이 있을 수도 있죠. 멀웨어가 종종 이런 형식을 취합니다.

Classes.dex 에는 Dalvik 클래스로 바뀐 모든 Java 클래스가 포함됩니다. 최근에는 안드로이드 런타임(ART)와 미리 컴파일해주는 AOT(ahead-of-time) compilation으로 전체 전환되거나, 부분적으로 just-in-time(JIT) 방식을 사용하므로 이들 몇몇은 같은 방식으로 사용되지 않을 수도 있지만, 해당 형식이 고정돼 있으므로 모두 APK에 있어야 합니다. 그래서 실제로 이를 제거할 수는 없습니다. 비록 전체가 아니더라도 필요한 파일이며 리버스 엔지니어링을 하는 동안 계속 존재해야 합니다.

lib/ 디렉터리에는 네이티브 코드 라이브러리가 있습니다. 다루기 까다로운 심화 리버스 엔지니어링 내용이므로 이번 글에서는 다루지 않을 예정입니다.

다른 도구 (10:30)

이전에 저는 AAPT에 더해 다른 흥미로운 도구도 사용해 봤습니다.

#!/bin/bash
unzip -d zip-out "$1"
java -jar AXMLPrinter2.jar zip-out/AndroidManifest.xml > AndroidManifest.xml
/opt/dex2jar-0.0.9.15/d2j-dex2jar.sh “$1" # creates “${1%.apk}-dex2jar.jar”
mkdir cfr-extracted && /opt/cfr/cfr.sh “${1%.apk}-dex2jar.jar” --outputdir java-out
java -jar /opt/smali/baksmali-2.0.6.jar -o smali-out zip-out/classes.dex

명령어가 너무 복잡하죠? 예전에는 저런 명령어로 전체 코드 묶음을 돌렸는데 dex2jar와 같은 일을 하는 것은 아주 불완전했습니다. 이렇게 사용하면 디컴파일하는 것을 망가뜨릴 수 있으므로 가장 좋은 버전을 보지 못할지도 모릅니다.

요즘은 제가 Java 1.5, Cupcake 등과 같은 초기 안드로이드 버전에서 작업한 이런 방식을 사용할 필요가 없습니다. 최근에는 Apktool을 사용하면 됩니다.

Apktool

다음과 같은 명령어로 실행할 수 있습니다.

$ apktool d target.apk

d는 “덤프”를 의미합니다. 기본적으로 앞서 보여드린 엄청난 양의 스크립트가 하는 일을 해주며, 덤프부터 시작하는 것을 추천합니다. manifest, 리소스, 여러 XML과 Baksmaling classes.dex를 얻을 수 있습니다.

Smali 에 대해 모르는 분을 위해 설명해 드리면 Dalvik 바이트 코드를 위한 어셈블리 언어입니다. 바이트 코드를 읽는 것보다 가독성은 높지만 바이트 코드와 거의 비슷하다고 볼 수 있습니다. 즉 아주 쉽게 Smali로 되돌릴 수 있죠. Baksmali 는 되돌려서 읽을 수 있는 형태로 바꿉니다.

이런 형태에 익숙해지는 데는 시간이 필요하지만, 점점 읽기 쉬워질 겁니다. 또한 이를 수정하고 다시 컴파일해서 앱을 다시 만들어내는 것도 가능합니다. 사실 Java 상에서 이런 변경을 하면 그 내용을 다시 앱으로 되돌리기에 한계가 있습니다.

reverse-engineering-apktool

리버스 엔지니어링을 하려면 apktool을 쓰면 됩니다. 모든 기본 리소스를 준비했다면 모방하려는 효과를 살펴볼 수 있습니다. 예를 들어 둥근 이미지 뷰를 찾을 수도 있겠죠. 이 예제에서는 라이브러리를 사용했다는 것이 보이므로 같은 효과를 내기 위해 처음부터 다시 만들지 않고 이 라이브러리를 사용할 수 있을 겁니다.

앞서 말한 대로 Smali는 아주 가독성이 뛰어나지는 않지만, 필드와 상속 등을 볼 수 있습니다.

디버깅

기본적으로는 왜 크래시가 나는지 보고 싶으실테죠. 그렇다면 이 방법이 효과있을지도 모릅니다. APK가 얼마나 엉망인지에 달려있고 제작자는 자신의 앱에 이런 디버깅을 하는 것을 원하지 않겠지만, 이 방법이 통한다면 놀라운 것들을 볼 수 있습니다.

디버그 버전이 아닌 버전으로 빌드된 앱을 가져와서 재빌드할 수 있습니다. Apktoll을 사용해서 다시 사인하고 자신의 디바이스에 설치해서 디버거를 붙여서 실행해볼 수 있습니다. 사인에는 기존 키가 아닌 자신의 키를 써도 됩니다. 여기까지 됐다면 아무 문제없이 마음껏 디버거를 쓸 수 있죠.

좀 사기같긴 하지만 여러분 앱의 릴리즈 버전이 이상하게 동작하는데 디버거를 붙일 수 없다면 디버깅이 가능한 릴리즈 버전이 필요할 수도 있습니다. 만약 쉽게 다시 만들 수 없는 예전 빌드라면 다음과 같은 것을 시도해볼 수도 있습니다.

$ apktool d -d -o SomeApp SomeApp.apk
...
$ apktool b -d SomeApp
...
$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore release-key.keystore SomeApp.apk release_key_alias_name

Androguard (15:02)

더 훌륭한 도구도 있습니다. 대화식 도구 모음인 Androguard입니다. Python 기반이므로 스트립팅이 가능하고 모듈화도 잘 돼 있으며 작은 도구의 라이브러리인 플러그 방식도 가능합니다. 또한 대화형 Python 셸 형식으로도 사용할 수 있습니다. 자체 Dalvik 디컴파일러를 포함하는 점도 인상적입니다. 사이트가 업데이트되지 않는 것 같지만 GitHub 프로젝트는 계속 업데이트되고 있으므로 API의 새 문제 등을 해결해줍니다.

$ python androlyze.py -s

이런 명령어로 대화식 작업을 시작할 수 있습니다. AnalyzeAPK라는 명령어를 입력하면 ad,dx 세 가지의 결과를 출력합니다.

$ python androlyze.py -s
Androlyze version 3.0
In [1]: a, d, dx = AnalyzeAPK(“/Users/jon/Desktop/target.apk")
In [2]:

APK를 나타내는 객체가 있고 .dex 파일과 본질적으로 같은 Dalvik VM 형식을 나타내는 객체가 있으며 분석을 나타내는 객체가 있습니다. 거의 모든 것을 분석해주는 미리 분석된 객체입니다.

이제 APK의 메인 액티비티가 뭔지 확인해 볼 수도 있습니다. 간단한 예제이지만 이런 방식으로 여러 가지를 확인해볼 수 있습니다.

$ python androlyze.py -s
Androlyze version 3.0
In [1]: a, d, dx = AnalyzeAPK(“/Users/jon/Desktop/target.apk")
In [2]: a, d, dx
Out [2]:
(<androguard.core.bytecodes.apk.APK at 0x10a62c350>,
 <androguard.core.bytecodes.dvm.DalvikVMFormat at 0x10d7e7850>,
 <androguard.core.analysis.analysis.uVMAnalysis at 0x11b80dad0>)
In [3]: a.get_main_activity()
Out [3]: u'com.example.app.ui.MainHomeActivity'

이하 생략.......   링크 페이지에서 확인

  • 페이스북으로 보내기
  • 트위터로 보내기
  • 구글플러스로 보내기

페이지 정보

최고관리자 작성일17-10-16 12:20 조회251회 댓글0건

첨부파일

댓글목록

등록된 댓글이 없습니다.

JAVA/JSP/안드로이드 목록

게시물 검색

사이트 정보

  • 회사명 주인있소 / 대표 소담
  • 주소 강원도 속초시
  • 사업자 등록번호 123-45-67890
  • 전화 010-2026-0626 / 팩스 없음
  • 통신판매업신고번호 제 OO구 - 123호
  • 개인정보관리책임자 정보책임자명

고객센터

상단으로