0. 개요

  여기서는 안드로이드 앱의 다운로드에서부터 설치, 시작까지의 과정을 다루기로 한다. 아직 안드로이드에 익숙하지 않고 인터넷에 자료가 거의 없으며 꾸준히 업데이트되고 있음으로 인해 정확한 자료가 아닐 가능성이 포함되어 있을 것으로 예상한다. 하지만 찾아보며 공부하는 사람들의 입장에서 키워드 제공의 의미라도 있길 바라며 정리한 내용을 올린다.





1. 다운로드

  일반적으로 앱은 구글 플레이 스토어에서 설치하는 방식이 대부분일 것이다. 물론 직접 apk 파일을 다운로드 받아서 설치하는 방식도 존재한다. 구글 플레이 스토어에서 악성코드가 발견되었다는 뉴스가 자주 보이기는 하지만 기본적으로 악성코드는 직접 설치 방식을 악용한다. 예를들어 블랙 마켓에서 무료 버전이라고 올려놓지만 내부에 악의적인 코드가 존재하는 경우가 있을 수 있고 성인물 사이트에서 성인 앱을 위장해서 설치하게 유도하는 방법이 있을 수 있다. 마지막으로 가장 대표적인 방식은 SMS/MMS를 이용한 피싱이다.





2. 설치

  변경 및 생성된 결과물을 기반으로 설명해 보겠다. 먼저 apk 파일 부터 보자면 시스템 앱들 즉 사전에 설치된 apk들은 /system/app/에 저장되어 있다. 사용자가 설치한 앱은 /data/app/<appname>/에 base.apk라는 이름으로 복사되며 Native 라이브러리는 /data/app/<appname>/lib/*.so 형태로 저장된다.


[ /data/app/<appname>/base.apk ]

[ /data/app/<appname>/lib/*.so ]


  apk에서 추출된 dex 파일은 /data/dalvik-cache/data@app@<appname>@classes.dex로 복사된다. 그리고 애플리케이션의 데이터는 /data/data/<appname>/과 서브디렉터리에 저장된다. 마지막으로 /data/data/<appname>/lib은 위의 /data/app-lib/<appname>/libapp.so에 대한 심볼릭 링크이다.


[ /data/dalvik-cache/data@app@<appname>@classes.dex ]

[ /data/data/<appname>/ ]

[ /data/data/<appname>/lib ]


  참고로 설치 과정에서 AndroidManifest.xml의 내용이 파싱되어 패키지 정보가 /data/system/packages.xml과 /data/system/packages.list에 추가된다.


[ /data/system/packages.xml ]

[ /data/system/packages.list ]


  이제 dex 파일에 대해서 조금 더 설명하기 위해 odex라는 확장자를 설명하도록 하겠다. 먼저 dexopt라는 유틸리티가 존재하는데 dex 파일을 pre-optimization한 .odex 파일 즉 optimized DEX 파일로 변환해주는 역할을 한다. 이것은 설치 과정에서 일어나는데 저장된 파일을 보면 .dex 확장자를 갖지만 실제로는 .odex 파일이다. Dalvik 가상 머신에서 실행되는 바이트 코드인 것은 같지만 최적화되어 성능을 향상시켜 준다.


  이 개념을 기반으로 하여 설명하겠다. 최근 안드로이드에서는 Dalvik VM이 아니라 ART 즉 Android RunTime이 사용된다. 이 메커니즘은 바이트 코드를 가상 머신에서 실행시키는 방식이이 아니라 설치 시점에 컴파일하여 ELF 파일 포맷을 만들며 이것을 실행하는 것이다. 앞에서 dexopt를 통해 .odex를 만드는 것이 아닌 dex2oat를 통해 oat 파일 포맷의 바이너리를 만든다. 이렇게 기능적으로도 많은 차이가 나지만 호환성을 위해서 이것도 Dalvik VM과 같은 과거의 이름을 확장자로 갖는다. 예를들어 과거에는 /data/dalvik-cache/<arch>/data@app@<packagename>.<appname>-1@base.apk@classes.dex였으며 안드로이드 6.0부터는 /data/app/oat/<pachagename>.<appname>/<arch>/base.odex이 되었다. 즉 어느 방식이든지 내부의 포맷은 달라도 이름은 같은 것이다.


[ /data/dalvik-cache/<arch>/data@app@<packagename>.<appname>-1@base.apk@classes.dex ]

[ /data/app/oat/<pachagename>.<appname>/<arch>/base.odex ]





2. 실행

  앞에서도 언근했듯이 안드로이드 5.0부터는 Dalvik VM 대신 ART를 사용한다. ART는 애플리케이션들과 시스템 서비스들에서 사용되는 런타임으로서 가상 머신을 통해 실행시키는 메커니즘이 아니라 런타임 환경을 제공하는 방식이다. apk의 내용을 보면 dex 파일이 존재하는데 이것은 Dalvik VM에서 실행할 수 있는 바이트 코드가 존재한다. 이것을 통해 Dalvik VM은 JIT 방식을 사용하였다. 하지만 가상 머신에서 실행시킨다는 것은 속도 등의 면에서 많이 부족할 수 밖에 없다. 그래서 앱 설치 시에 dexopt를 통해 .odex 파일로 최적화하지만 가상 머신에서 실행된다는 것 자체는 차이가 없기 때문에 한계가 존재하였다.


  ART 메커니즘은 AOT 즉 ahead-of-time 컴파일을 방식을 사용한다. 즉 apk의 dex 파일을 컴파일하여 oat 포맷 더 정확히는 elf 바이너리 형태로 만든다. 이것은 네이티브 기계어로서 가상 머신에서가 아닌 바이너리로서 동작할 수 있게 된다. 참고로 과거 방식에서는 설치 시에 dexopt를 통해 .odex 파일을 만들었다면 ART는 dex2oat를 이용해 oat 파일 포맷으로 앱을 컴파일한다. 물론 앞에서도 언급하였듯이 하위 호환을 위해 확장자는 oat가 아니라 dex 또는 odex가 사용된다. 참고로 안드로이드 7.0부터는 AOT와 JIT(just-in-time) 컴파일의 조합을 이용한다.


  이런 차이를 기반으로 비록 이제는 사용되지 않지만 Dalvik VM부터 시작하여 ART 런타임 메커니즘을 비교하면서 알아보도록 하겠다. 안드로이드 부팅 후 가장 먼저 시작되는 프로세스인 Init은 /system/core/rootdir/init.rc를 읽고 여기에 적힌 내용을 수행한다. 여러가지가 있겠지만 여기서는 앱의 실행과 관련된 것을 위주로 보겠다. Init은 /system/bin/app_process를 통해 AndroidRuntime 객체를 생성하며 이것은 DalvikVM을 실행시킨다. 그리고 DalvikVM에 Zygote를 실행하도록 요청한다.


  조금 더 진행하기 전에 Zygote에 대해서 설명하도록 하겠다. 이것은 안드로이드 앱에서 공통적으로 필요로 하는 자원, 라이브러리 등을 미리 로드시킨 후 대기하다가 새로운 애플리케이션 실행 시에 간단하게 자신을 fork 하는 메커니즘이다. 만약 애플리케이션 실행 시 마다 메모리 및 자원을 할당하고 로드하고 등의 작업을 한다면 부하가 많이 걸릴 것이다. 대신 Zygote라는 프로세스를 미리 만들어 놓아서 대기시킨 후 애플리케이션 실행 시에 fork를 통해 복사하여 필요한 부분 즉 실행시킬 애플리케이션과 관련된 부분만 처리해 준다면 중복된 행위를 통한 낭비를 줄일 수 있을 것이다.


  앞에서 DalvikVM을 실행시킨 후 Zygote를 실행하도록 요청한다고 하였다. 이 말은 Zygote 자체도 네이티브 바이너리가 아니라 가상 머신 위에서 돌아가는 바이트 코드 형태로 존재한다는 것을 의미한다. 즉 다른 애플리케이션과 차이가 없는 것이다. 그렇기 때문에 Zygote를 fork()한 후 실행시킬 애플리케이션을 로드하여 이 애플리케이션도 DalvkVM 위에서 실행될 수 있게 한다.


  이제부터는 ART 런타임 메커니즘을 알아보겠다. 이것은 가상 머신 방식이 아닌 런타임 환경을 제공하는 방식이다. 하지만 이 메커니즘이 Dalvk VM과 비교해서 완전히 바뀐 것은 아니다. Zygote를 이용하는 방식의 장점에 따라 이것을 수용하였고 단지 차이가 있다면 가상 머신을 이용하는 방식이 아니라는 점이다.


  app_process는 DalvikVM을 실행시키고 이것에 Zygote를 실행하도록 요청하지 않고 단지 Zygote를 실행시킨다. Zygote가 시작되면 ART 즉 libart.so를 로드하고 system/framework/<arch>/boot.oat, system/framework/<arch>/boot.art를 로드한 후 클래스 링킹 과정을 거친다. boot.art는 pre-initialized 클래스 및 객체들의 힙 등을 가진 이미지로서 기본적으로 프레임워크 함수와 실행 가능한 코드의 실제 주소 사이의 매핑 테이블을 제공한다. boot.oat는 pre-compiled 코드를 담고 있는 elf 파일인데 모든 안드로이드 프레임워크 바이트코드의 컴파일된 바이너리를 갖는다. 앱에서 프레임워크 함수를 호출하기 위해서는 boot.art 매핑 테이블에 쿼리하고 boot.oat의 텍스트 섹션에서 실제 코드를 호출하게 된다. 이것은 미리 zygote의 초기화 과정 즉 클래스 링킹 과정을 통해 프레임워크 라이브러리들의 클래스 멤버들에 접근할 수 있게 된다.


  이후부터는 Dalvik VM 방식과 비슷하다. 먼저 SystemServer 프로세스를 fork하는데 이것으로부터 다시 Framework Services, Package manger, Activicty Manager(Launcher) 등이 fork된다. 마지막으로 ActiveManagerService와 상호작용하기 위한 소켓을 생성하고 기다린다.


  이제 앱을 실행해 보도록 하자. 화면에서 클릭함으로써 Launcher의 onClick() Callback이 호출되고 이것은 Binder를 통해 Activity Manager의 StartActivity()를 호출한다. Activity Manager는 이 요청을 받으면 startViaZygote()를 호출하는데 Zygote가 이것을 받은다면 fork()한 후 Application Binding 과정이 시작된다. 이것은 프로세스를 실행할 애플리케이션에 어태치하는 과정이다. 여러 과정을 거쳐서 makeApplication() 메소드가 실행되는데 이것은 앱 관련 클래스를 메모리에 로드해 준다. 이후 Activity Manager는 realStartActivity()를 시작으로 하여 프로세스를 launch 시킨다.


  지금까지의 설명을 보충하자면 ART 메커니즘으로 바뀜으로서 윈도우나 리눅스에서처럼 oat 즉 elf 바이너리를 직접 실행할 수 있는지 궁금할 수 있다. 하지만 Zygote는 초기에 boot.art 및 boot.oat를 통한 초기화, ART 런타임 로드 등의 작업을 미리 하였고 이렇게 대기 중인 프로세스를 fork()한 후 oat 바이너리를 로드하여 실행하는 메커니즘이었다. 그렇기 때문에 이 많은 과정 없이 순수하게 oat 하나만으로 무엇인가를 할 수는 없다.




Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함