본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android 보안] 패키지 관리 #2

by 돼지왕 왕돼지 2018. 4. 19.
반응형

[android 보안] 패키지 관리 #2


.tmp, ACTION_PACKAGE_ADDED, android runtime, androidmanifest.xml, apk 무결성, apk 설치 과정, app-lib, appdirobserver, Art, CAP_CHOWN, CAP_DAC_OVERRIDE, codepath, data/app, data/app-lib, data/app-private, data/dalvik-cache, data/data, dex 파일 위치, dex2oat, dexopt, FLAG_UPDATED_SYSTEM_APP, forward lock, framework-res.apk, InstallAppProgress, installd, installd 데몬, installer 클래스, installPackage, installPackageWithVerificationAndEncryption, INSTALL_PACKAGES, jarfile, media container service, mkuserdata, mountservice, obb, opaque binary blob, pacakgemanagerservice, packageinstaller, PackageInstallerAtivity, PackageSettings, PACKAGE_REPLACED, PM, resourcePath, scanPackageLI, SD 카드, secure container, Settings.Global.INSTALL_NON_MARKET_APPS, Sharpening, side loading, signature protection, systeem/priv-app, system/app, system/framework, system/priv-app, system/vendor/app, updated-package, userdata, userdata 파티션, vmdl, vold 데몬, [android 보안] 패키지 관리 #2, 권한, 권한 허용과 설치 과정 시작, 다중 사용자, 데이터 디렉터리 생성, 디바이스 암호화, 루트 권한, 마운트 서비스, 매니페스트 다이제스트, 미디어 컨데이너 서비스, 미디어 컨테이너 서비스, 보안 컨테이너, 볼륨 관리, 사이드 로딩, 샤프닝, 서명 인증서, 서명 정보, 심볼릭 링크, 안드로이드 런타임, 알 수 없는 출처, 암호화된 APK 의 설치, 앱 디렉터리 감시자, 앱 디렉터리의 복사, 앱 패키지와 데이터의 위치, 외장 메모리, 유료 앱 설치, 임시 파일, 제조사 앱, 지역 패키지의 설치, 최적화된 dex 생성, 파일 및 디렉터리 구조, 패키지 매니저, 패키지 매니저 서비스, 패키지 스캐닝, 패키지 파싱과 검증, 포워드 락, 포워드 락 구현, 활성화된 컴포넌트

3.3. APK 설치 과정


-

사이드 로딩(Side loading) 은 구글 플레이 스토어 같은 정식 스토어를 통하지 않고 앱을 직접 내려받아 설치하는 방법을 말한다.



-

APK 파일을 앱 디렉터리에 직접 복사하면 패키지 매니저가 자동으로 탐지하고 설치한다.

패키지 매니저는 앱 디렉터리에 변화가 있는지 늘 감시한다.



* 3.3.1 앱 패키지와 데이터의 위치


-

/system/vendor/app/ 디렉터리에는 제조사 고유의 앱이 저장된다.

사용자 설치 앱은 읽고 쓸 수 있는 userdata 파티션에 설치되며 언제든지 교체 및 제거할 수 있다.

대부분의 사용자 설치 앱은 /data/app/ 디렉터리에 설치된다.



-

시스템 앱이든 사용자 설치 앱이든 데이터는 /data/data/ 디렉터리에 있는 userdata 파티션에 만들어진다.

userdata 파티션에는 사용자 설치 앱에 대한 최적화된 DEX 파일(/data/dalvik-cache/),

시스템 패키지 데이터베이스(/data/system/packages.xml), 

이외 시스템 데이터베이스와 설정 파일이 들어간다.




* 3.3.2. 활성화된 컴포넌트


** PackageInstaller 시스템 앱


-

PackageInstaller 는 APK 파일을 처리하는 기본 담당자로서 패키지 관리에 대한 기본 정보를 GUI 형태로 보여준다.

그리고 VIEW 나 INSTALL_ACTION 인텐트 액션과 함께 APK 파일의 URI 를 받으면 패키지 정보를 분석하고 앱이 요구하는 권한을 설치 확인 화면에 보여준다.



-

사용자가 디바이스의 보안 설정에서 [알 수 없는 출처] 옵션을 활성화한 경우에만 PackageInstaller 앱을 설치할 수 있다.



-

“알 수 없는 출처”는 정확히 무엇을 의미하는 걸까?

화면 설명에 따르면 “플레이 스토어 이외에 다른 출처에서 가져온 앱” 이지만 실제 “알 수 없는 출처” 의 정의는 폭이 약간 더 넓다.

PackageInstaller 가 실행되면 APK 설치를 요청한 앱의 UID 와 패키지 정보를 가져와 /system/priv-app/ 디렉터리에 설치된 권한 있는 앱인지를 검사한다.

설치를 요청한 앱이 권한이 없으면 “알 수 없는 출처”라고 판단한다.



-

사용자가 설치 다이얼로그에서 [동의] 버튼을 누르면 PackageInstaller 는 패키지 매니저 서비스(PackageManageService)를 호출하며, 패키지 매니저 서비스가 실제 설치하는 작업을 수행한다.



** pm 명령어


-

pm 명령은 시스템 패키지 매니저의 기능 일부를 명령행 인터페이스로 제공한다.

안드로이드 셸에서 pm install 을 실행해 패키지를 설치하고, pm uninstall 을 실행해 패키지를 제거한다.

그리고 안드로이드 디버그 브리지(Android Debug Bridge, ADB)는 adb install/uninstall 명령을 제공한다.



-

PackageInstaller 와는 달리 pm install 은 [알 수 없는 출처] 시스템 옵션에 의존하지 않으며 GUI 를 보여주지 않는다.




** 패키지 매니저 서비스


-

패키지 매니저 서비스는 APK 파일을 파싱하고, 앱 설치를 시작하고, 패키지를 업그레이드 및 제거하고, 패키지 데이터베이스를 유지하며, 권한을 관리한다.



-

패키지 매니저 서비스는 다양한 옵션으로 패키지를 설치할 수 있는 여러 installPackage() 메서드를 제공한다.

그 중 가장 범용적인 것이 installPackageWithVerificationAndEncryption() 메서드다.

이 메서드는 암호화된 APK 파일을 설치하고 검증 에이전트를 통해 패키지를 검증한다.



** Installer 클래스


-

패키지 매니저 서비스는 안드로이드 시스템 서비스 중에서 가장 많은 권한을 가진 서비스 중 하나이긴 하지만,

시스템 서버 프로세스(system UID로 실행) 안에서 실행되므로 루트 권한은 없다.

그렇지만 앱 디렉터리를 생성, 삭제, 변경하려면 수퍼 유저 권한이 필요하므로 패키지 매니저 서비스는 이 작업을 installd 데몬에 위임한다.

installer 클래스는 /dev/socket/installd 유닉스 도메인 소켓을 통해 installd 데몬에 연결하고 insalld 에 전달하는 명령을 캡슐화한다.



** installd 데몬


-

installd 데몬은 앱 및 (다중 사용자 디바이스의 경우) 사용자 디렉터리 관리 기능을 시스템 패키지 매니저에 제공하는 높은 권한을 가진 네이티브 데몬이다.

그리고 새로 설치된 패키지의 최적화된 DEX 파일을 생성하는 dexopt 명령을 실행하기도 한다.



-

installd 데몬은 installd 지역 소켓을 통해 접근할 수 있는데, 이 소켓은 system UID 로 실행되는 프로세스만 접근할 수 있다.

또한 installd 데몬은 초기 안드로이드 버전에서는 루트 사용자로 실행되었지만, 이제는 루트 사용자 대신 자신이 생성한 앱의 디렉터리와 파일을 앱의 사용자와 그룹 UID 로 설정하기 위해 리눅스의 CAP_DAC_OVERRIDE 와 CAP_CHOWN 기능을 이용한다.




** 마운트 서비스


-

마운트 서비스(MountService)는 앱에서 확장 파일로 사용하는 불투명 2진 블랍(Opaque Binary Blob, OBB) 파일뿐만 아니라 SD 카드와 같은 외장 메모리를 마운팅한다. 그리고 디바이스 암호화 및 암호 패스워드를 변경하는 데도 사용된다.



-

마운트 서비스는 비시스템 앱이 접근하면 안 되는 앱 파일을 보관하는 보안 컨테이너(Secure Container)도 관리한다.

보안 컨테이너는 암호화되어 있으며 “포워드 락(Forward Lock)” 이라는 DRM 방식을 구현하기 위해 사용된다.

포워드 락은 주로 유료 앱을 설치할 때 사용되며, 디바이스에서 APK 파일을 꺼내 다른 디바이스로 복사 및 재배포하지 못하게 보장한다.




** vold 데몬


-

마운트 서비스가 볼륨 관리에 대한 대부분의 API 를 제공하지만, system 사용자로 실행되므로, 실제 디스크 볼륨을 마운트하고 해제하는 데 필요한 권한은 갖고 있지 않다.

디스크 볼륨을 관리하는 작업들은 루트 사용자로 실행되는 vold 데몬에서 구현한다.



-

vold 는 /dev/socket/vold 유닉스 도메인 소켓을 통해 지역 소켓 인터페이스를 제공하는데,

이 소켓은 루트와 mount 그룹에 속한 프로세스만 접근할 수 있다.

마운트 서비스를 실행하는 system_server 프로세스의 보조 GID 에 mount(GID 1009)가 속해 있으므로 마운트 서비스가 vold 의 명령 소켓에 접근할 수 있다.

불륨을 마운트하고 해제하는 것 외에 vold 는 파일 시스템을 생성 및 포맷할 수 있으며 보안 컨테이너를 관리할 수 있다.




** 미디어 컨테이너 서비스


-

미디어 컨테이너 서비스(Media Container Service)는 APK 파일을 최종 설치 위치나 암호화된 컨테이너에 복사하고, 패키지 매니저 서비스가 이동식 저장소에 있는 파일에 접근할 수 있게 한다.



-

앱 마켓 등 원격에서 획득한 APK 파일은 안드로이드의 다운로드 매니저(DownloadManager)서비스를 통해 내려받으며, 내려받은 파일은 다운로드 매너지의 콘텐트 제공자 인터페이스를 통해 접근한다.

패키지 매니저는 내려받은 APK 파일에 접근할 수 있는 권한을 미디어 컨테이너 서비스에 임시로 부여해 APK 파일이 암호화되어 있으면 미디어 컨테이너 서비스는 먼저 파일을 복호화한다.

암호화된 컨테이너를 요청한 경우 미디어 컨테이너 서비스는 마운트 서비스에 암호화된 컨테이너 생성을 위임하고 APK 의 보호된 부분(코드와 에셋 모두)을 새로 생성된 컨테이너에 복사한다.

컨테이너로 보호할 필요가 없는 파일은 파일 시스템에 직접 복사된다.




** 앱 디렉터리 감시자


-

앱 디렉터리 감시자(AppDirObserver)는 APK 파일이 들어가는 앱 디렉터리가 변경되지 않는지 감시하는 컴포넌트로서 발생한 이벤트에 따라 패키지 매니저 서비스의 적절한 메서드를 호출한다.

APK 파일이 시스템에 추가될 때 앱 디렉터리 감시자는 패키지를 검사하고 난 후 앱을 설치하거나 업데이트한다.

APK 파일이 제거될 때 앱 디렉터리 감시자는 제거 과정을 시작하며, 이때 앱 디렉터리와 시스템 패키지 데이터베이스에 들어 있는 앱 항목을 삭제한다.



-

감시하는 디렉터리마다 전담하는 객체가 하나씩 만들어진다.

system 파티션에서는 /system/framework/ (framework-res.apk 프레임워크 리소스 패키지가 들어 있다. ), /system/app/, /system/priv-app/ 및 /system/vendor/app/ 디렉터리를 감시한다.

userdata 파티션에서는 /data/app/ 및 /data/app-private/ 디렉터리를 감시한다.




* 3.3.3. 지역 패키지의 설치


** 패키지 파싱과 검증


-

지역 APK 파일을 열면 application/vnd.android.package-archive 처리기가 실행된다.

이 처리기는 일반적으로 PackageInstaller 시스템 앱의 PackageInstallerAtivity 로 구현된다.

PackageInstallerActivity 는 먼저 설치를 요청한 앱을 신뢰할 수 있는지 검사한다.

설치를 요청한 앱을 신뢰할 수 없고 Settings.Global.INSTALL_NON_MARKET_APPS 가 false 이면, PackageInstaller 는 경고 다이얼로그를 출력하고 설치 과정을 끝낸다.



-

설치가 허용되면 PackageInstallerActivity 가 APK 파일을 파싱하고 AndroidManifest.xml 파일과 패키지 서명에서 정보를 수집한다.

java.util.jar.JarFile 및 연관된 클래스를 이용해 각 항목에 대한 서명 인증서를 추출하면서 APK 파일의 무결성이 자동으로 검증된다.

JARFile 클래스에는 전체 파일이나 특정 항목에 대한 서명을 검증하는 공개된 메서드가 없으므로, 이렇게 인증서를 추출하면서 무결성을 검증한다. ( 시스템 앱은 암묵적으로 신뢰되며, APK 파일을 파싱할 때 AndroidManifest.xml 파일의 무결성만 검증한다. 그러나 사용자 앱이나 시스템 앱의 업데이트 패키지처럼 시스템 이미지 안에 포함되지 않은 패키지의 경우 APK 안에 들어 있는 모든 항목들을 검증한다. )



-

AndroidManifest.xml 파일의 해시값도 APK 를 파싱할 때 계산되어 이후 설치 단계에 전달되는데, 이 해시값을 이용해 설치 다이어로그에서 동의 버튼을 누른 후 APK 를 복사할 때 APK 파일이 변경되지 않았는지를 검증한다.



cf)

또 하나 주목할 것은 설치 시  APK 파일의 무결성을 검증할 때에는 표준 자바 라이브러리 클래스를 사용하지만, 실행 시 달빅 가상 머신이 APK 파일을 로딩할 때에는 자체적으로 구현한 ZIP/JAR 파일 파서를 사용한다는 점이다.

이 두 구현의 미묘한 차이점이 여러 안드로이드 버그의 원인이 되었다.

APK 파일을 파싱할 때 시스템 패키지 매니저가 StrictJarFile 을 사용하므로, 달빅과 패키지 매니저가 동일한 방식으로 APK 파일을 검증하도록 보장한다.

이렇게 통합된 구현은 향후에 나올 안드로이드 버전에 포함될 것이다.




** 권한 허용과 설치 과정 시작


-

동의 버튼을 누르면 PackageInstallerActivity 는 APK 파일, 매니페스트 다이제스트 외에 참조자 URL 등의 설치 메타데이터, 설치자 패키지명, 설치를 요청한 프로세스의 UID 를 InstallAppProgress 엑티비티에 전달한다.

그러면 InstallAppProgress 액티비티가 APK URI 와 설치 메타데이터를 패키지 매니저 서비스의 installPackageWithVerificationAndEncryption() 메서드에 전달해 실제 설치 과정을 시작한다.



-

설치 메서드는 먼저 호출자가 INSTALL_PACKAGES 권한을 갖고 있는지 확인한다.

이 권한은 signature 보호 수준을 갖고 있으며 시스템 앱을 위해 예약된 권한이다.

그리고 설치 메서드는 다중 사용자 디바이스의 경우 호출한 사용자가 앱을 설치하도록 허용되어 있는지 확인하고 나서, 선호하는 설치 위치(내부 혹은 외부 저장소)를 결정한다.




** 앱 디렉터리의 복사


-

그 다음 APK 파일을 앱 디렉터리 (/data/app/)에 복사한다.

파일을 복사하기 위해 패키지 매니저 서비스는 먼저 앱 디렉터리에 임시 파일(vmdl로 시작하고 .tmp 확장자를 갖는다.)을 생성하고, 미디어 컨테이너 서비스에 복사를 위임한다.

파일을 복호화해야 하거나 (포워드 락을 거는 경우) 암호화된 컨테이너가 생성될 수 있으므로 파일을 직접 복사하지 않는다.



-

APK 파일이 성공적으로 복사되면 APK 에 포함되어 있는 모든 라이브러리는 시스템의 네이티브 라이브러리 디렉터리(/data/app-lib/) 밑의 전용 앱 디렉터리에 복사된다.

그리고 임시 APK 파일과 라이브러리 디렉터리는 패키지명에 기반해 최종적인 이름으로 변경된다.(APK 의 경우 com.example.app-1.apk, 라이브러리 디렉터리의 경우 /data/app-lib/com.example.app-1/ 과 같은 이름을 갖는다.).

마지막으로 APK 파일 권한이 0644 로 설정되고 이 파일의 SELinux 컨텍스트가 설정된다.



cf)

기본적으로 APK 파일은 시스템 전체에서 읽을 수 있으므로 다른 모든 앱에서 접근할 수 있다.

이렇게 함으로써 앱의 공개 리소스를 공유하기가 쉬워지고 설치된 모든 패키지의 목록을 보여줘야 하는 서드파티 런처 등의 앱도 개발할 수 있다.

그러나 이 기본 권한은 누구나 디바이스에 있는 APK 파일을 추출할 수 있게 허용하므로, 앱 마켓을 통해 배포된 유료 앱의 경우에는 문제가 될 수 있다.

APK 파일에 포워드 락을 걸면 APK 리소스가 공개되어도 코드와 에셋에 대한 접근은 제한할 수 있다.




** 패키지 스캐닝


-

패키지 매니저 서비스의 scanPackageLI() 메서드를 호출해 패키지를 스캐닝한다.



-

앱을 새로 설치한 경우, 패키지 매니저는 먼저 패키지명, 코드 경로, 만약 패키지에 포워드 락이 걸렸다면 별도의 리소스 경로, 네이티브 라이브러리 경로를 담고 있는 PackageSettings 구조체를 생성한다.

그런 다음 새로운 패키지에 UID 를 할당하고, 이 UID 를 PackageSettings 구조체에 저장한다.

일단 새로운 앱이 UID 를 갖게 되면 앱의 데이터 디렉터리를 생성할 수 있다.




** 데이터 디렉터리 생성


-

패키지 매니저 서비스는 앱 디렉터리를 생성하고 소유자를 설정할 충분한 권한을 갖고 있지 않기 때문에 패키지명, UID, GID, seinfo(SELinux 가 사용한다)를 파라미터로 installd 데몬에 install 명령을 호출함으로써 디렉터리 생성을 installd 에 위임한다.



-

installd 데몬은 패키지 데이터 디렉터리, 공유 네이티브 라이브러리 디렉터리, 지역 라이브러리 디렉터리를 생성한다.

그런 다음 패키지 디렉터리 권한을 751로 설정하고 지역 라이브러리 디렉터리에 있는 앱의 네이티브 라이브러리에 대한 심볼릭 링크를 생성한다.

마지막으로 패키지 디렉터리의 SELinux 컨텍스트를 설정하고 패키지 디렉터리의 소유자를 앱에 할당된 UID/GID 로 변경한다.



-

이 때 시스템에 사용자가 두 명 이상인 경우에는 mkuserdata 명령을 installd 에 보내 각 사용자에 대한 데이터 디렉터리를 생성한다.

필요한 디렉터리가 모두 생성되면 제어권을 다시 패키지 매니저 서비스로 넘기고, 패키지 매니저 서비스는 패키지 안에 들어 있는 네이티브 라이브러리를 추출해 앱의 네이티브 라이브러리 디렉터리로 복사하고 /data/data/com.example.app/lib/ 에 심볼릭 링크를 생성한다.




** 최적화된 DEX 생성


-

다음 단계에서는 앱 코드에 대한 최적화된 DEX 를 생성한다.

이 작업도 dexopt 명령을 installd 에 보내 위임한다.

installd 데몬은 dexopt 프로세스를 포크하고, dexopt 프로세스는 /data/dalvik-cache/ 디렉터리에 최적화된 DEX 파일을 생성한다.

(최적화 절차를 샤프닝(Sharpening)이라고도 부른다.)



cf)

디바이스가 안드로이드 버전 4.4에서 소개된 실험적인 안드로이드 런타임(Android Runtime, ART)을 사용하고 있다면 installd 는 최적화된 DEX 를 생성하지 않고, dex2oat 명령을 이용해 네이티브 코드를 생성한다.




** 파일 및 디렉터리 구조


-

APK 파일, 추출된 네이티브 라이브러리 파일, 두 파일 모두 system 이 소유자이며 시스템 전체에서 읽을 수 있다.

최적화된 DEX 의 소유자는 system 이며 그룹은 all_a215 라는 특별한 그룹으로서, 앱을 설치한 디바이스 사용자 모두가 들어 있다.

이렇게 하면 다중 사용자 디바이스에서도 사용자마다 사본을 만들지 않고 동일한 최적화된 DEX 파일을 공유하게 되어 디스크 공간을 절약할 수 있다.



-

lib/ 디렉터리는 단순히 /data/app-lib/ 에 들어있는 앱의 공유 라이브러리 디렉터리에 대한 심볼릭 링크일 뿐이다.



** 새로운 패키지 pakcages.xml 에 추가


-

다음 단계에서는 패키지를 시스템 패키지 데이터베이스에 추가한다.



** 패키지 속성


-

최상위 요소인 <package> 요소는 설치 위치와 버전 등 각 패키지의 가장 핵심적인 속성을 담고 있다.


name - 패키지명

codePath - 패키지의 전체 경로

resourcePath - 패키지에서 공개한 부분(기본 리소스 패키지 및 매니페스트)에 대한 전체 경로, 포워드 락이 걸린 앱에서만 설정

nativeLibraryPath - 네이티브 라이브러리가 지정된 디렉터리의 전체 경로

flags - 앱에 연결된 플래그

ft - APK 파일 시각( System.currentTimeMillis() 가 반환하는 밀리초 단위의 유닉스 시각 )

it -  앱이 처음 설치된 시각

ut - 앱이 최근 업데이트된 시각

version - 앱 매니페스트의 versionCode 속성에서 지정한 패키지 버전 번호

userId - 앱에 할당된 커널 UID

installer - 앱을 설치한 앱의 패키지명

sharedUserId - 매니페스트의 sharedUserId 속성에서 지정한, 패키지의 공유 사용자 ID



** 컴포넌트와 권한 갱신


-

packages.xml 항목을 생성한 후 패키지 매니저 서비스는 새로 설치된 앱의 매니페스트에 정의된 모든 안드로이드 컴포넌트를 스캐닝하고 메모리에 상주하는 내부 컴포넌트 레지스트리에 추가한다.

그런 다음 앱이 선언한 모든 권한 그룹과 권한을 스캐닝하고 권한 레지스트리에 추가한다.



cf)

앱이 정의한 커스텀 권한은 먼저 설치된 앱이 이기는 전략으로 등록된다.

예를 들어 A 앱과 B 앱이 P 권한을 정의하고 있는데, A 앱이 먼저 설치되면 A 가 정의한 권한이 등록되고 B 가 정의한 권한은 무시된다.


먼저 설치된 앱이 이기는 전략 때문에 권한 수준이 낮아질 수 있다.

예를 들어 A 는 normal 수준, B 는 signature 수준을 정의함으로써 A 가 정의한 P 권한 수준이 B 가 정의한 P 권한 수준보다 낮음에도 A 앱이 먼저 설치되면 P 권한을 요청한 앱은 B 앱과 동일한 키로 서명하지 않아도 컴포넌트에 접근할 수 있게 된다.

따라서 컴포넌트를 보호하기 위해 커스텀 권한을 사용할 때에는 현재 등록된 권한이 앱에서 기대하는 보호 수준으로 등록되어 있는지 반드시 확인해야 한다.



-

마지막으로 패키지 데이터베이스에 대한 변경 내용( 패키지 항목과 새로운 권한 ) 을 디스크에 저장하고, 패키지 매니저 서비스는 ACTION_PACKAGE_ADDED 노티피케이션을 보내 새로 추가된 앱을 다른 컴포넌트들에 알려준다.






** 3.3.4. 패키지 업데이트


-

거의 동일한 단계로 수행된다.



** 서명 검증


-

먼저 새로운 패키지가 기존 패키지와 동일한 서명자에 의해 서명되어 있는지 검사한다.

이 규칙을 “동일 출처 정책(Same Origin Policy)”, 혹은 “TOFU(Trust On First Use)” 라고 한다.



cf) 

서명 인증서가 같은지 비교할 때 인증서를 PKI 측면에서 제대로 검증하지는 않는다.

즉 유효 시간, 신뢰하는 발급자, 철회(Revocation) 여부 등을 검사하지 않는다.



-

PackageManagerService.compareSignatures() 메서드를 호출해 인증서가 같은지를 검사한다.



-

Signature 클래스는 APK 파일에 연결된 DER 인코딩된 서명 인증서의 래퍼이다.



-

안드로이드의 인증서 비교 방식은 인증서를 단순히 2진값으로 비교하므로 당연히 CA 나 만료 기간에 대해서는 아무것도 모른다.

따라서 앱을 설치한 후 이 앱을 업데이트하려면 동일한 인증서로 서명해야 한다. ( 시스템 앱의 경우에는 다르다. )



-

안드로이드에 여러 개의 서명이 있는 경우는 드물지만 없지는 않다.

원래 앱을 두 명 이상의 서명자가 서명했다면 업데이트도 모든 서명자가 원래의 인증서를 이용해 서명해야 한다.

개발자 서명 인증서의 유효 기간이 만료되거나 서명키를 분실하면 앱을 업데이트할 수 없기 때문에 새로운 앱을 배포해야 한다.



** 비시스템 앱 업데이트


-

비시스템 앱의 업데이트는 본질적으로 기존 데이터 디렉터리만 유지한 채 앱을 다시 설치하는 과정이다.

먼저 업데이트할 패키지의 프로세스를 모두 종료시킨다.

그런 다음 내부 구조체와 패키지 데이터베이스에서 패키지를 제거한다. (이와 동시에 앱이 등록한 컴포넌트도 모두 제거한다.)

다음으로 패키지 매니저 서비스가 scanPackageLI() 메서드를 호출해 패키지를 스캐닝하게 만든다.

이 스캐닝은 패키지의 코드, 리소스 경로, 버전, 타임스탬프를 갱신한다는 점을 제외하고는 새로 설치하는 과정과 동일하게 진행된다.


패키지 매니페스트를 스캐닝하고 정의된 컴포넌트는 모두 시스템에 등록한다.

그리고 패키지에 대한 권한을 다시 부여해 업데이트된 패키지에서 정의된 권한에 모두 일치하도록 보장한다.

마지막으로 업데이트된 패키지 데이터베이스를 디스크에 저장하고 PACKAGE_REPLACED 시스템 브로드캐스트를 발송한다.



** 시스템 앱 업데이트


-

사용자 설치 앱과 마찬가지로, 미리 설치된 앱(일반적으로 /system/app/ 디렉터리에 들어간다)은 구글 플레이 스토어 등의 완전한 앱 배포 서비스를 통하지 않고도 업데이트할 수 있다.

그렇지만 system 파티션이 읽기 전용으로 마운트되기 때문에 업데이트는 /data/app/ 에 설치되고 원래 앱은 변경되지 않는다.

<package> 항목 외에 업데이트된 앱은 <updated-package> 항목을 가질 수도 있다.



-

업데이트의 codePath 속성은 /data/app/ 디렉터리에 있는 새로운 APK 의 경로로 설정된다.

업데이트된 앱은 원래 앱의 권한과 UID 를 상속받으며 flags 속성에 FLAG_UPDATED_SYSTEM_APP(0x80)을 추가해 시스템 앱의 업데이트된 버전이라고 표시한다.



-

시스템 앱은 OTA 시스템 업데이트를 통해 system 파티션에서 바로 갱신될 수도 있다.

이 경우 업데이트된 시스템 APK 는 다른 인증서로 서명해도 된다.

설치자가 system 파티션에 쓸 수 있는 권한을 갖고 있으면 서명 인증서가 바뀌어도 신뢰할 수 있다고 생각하기 때문이다.

UID 및 파일과 권한은 그대로 유지된다.

다만 패키지가 공유 사용자의 일부인 경우에는 다른 앱도 영향을 받기 때문에 서명을 갱신할 수 없다.

이와 반대로 새로운 시스템 앱이 현재 설치된 동일한 패키지명의 비시스템 앱과 다른 인증서로 서명된 경우에는 비시스템 앱을 먼저 제거한다.



* 3.3.5. 암호화된 APK 의 설치


-

안드로이드 4.1부터는 ASEC 컨테이너를 이용한 포워드 락 지원과 함께 암호화된 APK 의 설치도 지원하게 되었다.



-

암호화된 APK 는 구글 플레이 스토어 클라이언트로 설치하거나 안드로이드 쉘에서 pm 명령으로 설치할 수 있지만,

시스템의 PackageInstaller 는 암호화된 APK 를 지원하지 않는다.

구글 플레이 스토어의 설치 흐름은 통제할 수 없기 때문에 암호화된 APK 를 설치하려면 pm 명령을 사용하거나 직접 설치 앱을 만들어야 한다.



** 암호화된 APK 의 생성 및 설치


-

암호화된 APK 를 지원하기 위해 안드로이드 4.1 에서는 adb install 명령에 파라미터 3개가 추가되었다.

—algo, —key, —iv 파라미터이다.

이들은 각각 암호화 알고리즘, 키, 초기화 벡터(initialization venctor)를 지정한다.

그러나 추가된 이 파라미터들을 사용하려면 먼저 암호화된 APK 를 만들어야 한다.



-

OpenSSL 의 enc 명령을 이용해 APK 파일을 암호화할 수 있다.

$ openssl enc -aes-128-cbc -K [암호화키] -iv [벡터값] -in my-app.apk -out my-app-enc.apk


$ adb install —algo ‘AES/CBC/PKCS5Padding’ —key [암호화키] —iv [벡터값] my-app-enc.apk



-

실제 APK 파일은 /data/app/ 디렉터리에 복사되는데, 암호화된 APK 파일의 해시와 비교해보면 다른 파일이라는 것을 알 수 있다.

해시값이 원래의 암호화되지 않은 APK 파일의 해시와 동일하므로, 설치 시에 전달한 암호 파라미터(알고리즘, 키, 초기화 벡터)로 APK 가 복호화되어 있음을 알 수 있다.



** 구현 및 암호화 파라미터


-

installPackageWithVerificationAndEncryption 에서 Verificationparams 는 패키지 검증에 사용되는 파라미터를 담고 있다.

ContainerEncryptionParams 클래스는 —algo, —key, —iv 옵션으로 전달된 값을 담고 있다.



** 암호화된 APK 의 무결성 검사 및 설치


-

파일 복호화와 무결성 검증을 제외하면 설치 과정은 암호화되지 않은 APK 설치 과정과 동일하다.




* 3.3.6. 포워드 락


-

설치된 APK 파일은 안드로이드에서 누구나 읽을 수 있기 때문에 상용 디바이스에서도 앱을 추출하기가 비교적 쉽다.

OS 의 유연성을 잃지 않고도 사용자가 유료 앱을 다른 사용자에게 전달하지 못하도록 초기 안드로이드 버전에서는 포워드 락(“복사 방지”라고도 부른다)를 도입했다.



-

포워드 락의 기본 개념은 앱 패키지를 리소스 및 매니페스트를 담고 있는 전체가 읽을 수 있는 부분(/data/app/ 디렉터리)과 실행 코드를 담고 있는 system 사용자만 읽을 수 있는 부분 (/data/app-private/ 디렉터리), 이렇게 두 부분으로 나눈다.

코드 패키지는 파일시스템 권한으로 보호되는데, 상용 디바이스에서 사용자들은 접근할 수 없지만 루트 권한을 갖고 있는 디바이스에서는 추출될 수 있다.

따라서 이 초기 포워드 락 메커니즘은 금세 사라지고 “구글 플레이 라이선싱(Google Play Licensing)” 이라는 온라인 앱 라이선싱 서비스로 교체되었다.



-

구글 플레이 라이선싱은 앱 보호 구현을 OS 에서 앱 개발자로 옮겼다는 문제로 인해 호불호가 갈렸다.

안드로이드 4.1(JellyBean)에서 포워드 락 구현을 다시 설계해 이제는 APK  를 암호화된 컨테이너에 저장할 수 있게 되었다.

암호화된 컨테이너는 디바이스 고유 키가 있어야 마운트할 수 있다.



** 안드로이드 4.1(JellyBean). 포워드 락 구현


-

암호화된 앱 컨테이너를 포워드 락 메커니즘으로 사용하는 것은 안드로이드 버전 4.1부터 시작되었지만,

암호화된 컨테이너는 원래 안드로이드 2.2에서 소개되었다.

당시에는 대부분의 안드로이드 디바이스의 내장 저장소가 제한되어 있었고, 마이크로 SD 카드를 사용하는 이동식 저장소는 상대적으로 큰 공간(수 기가바이트)을 갖고 있었다.

파일을 쉽게 공유할 수 있도록 이동식 저장소는 FAT 파일시스템으로 포맷되어 있었는데, FAT 파일시스템은 권한 통제 기능이 없어서 결국 SD 카드에 있는 파일은 어느 앱이든 읽고 쓸 수 있었다.



-

안드로이드 2.2에서는 사용자가 앱을 이동식 저장소로 옮기고자 할 때 사용자가 SD 카드에 저장된 유료 앱을 복사하지 못하도록 암호화된 파일 시스템 이미지 파일을 만들어 APK 를 이 안에 저장했다.

그런 다음 시스템은 암호화된 이미지의 마운트 지점을 생성하고 리눅스의 디바이스 맵퍼를 이용해 마운트했다.

안드로이드는 각 앱의 파일을 실행 시에 마운트 포인트에 로딩했다.



-

안드로이드 4.1은 이 개념을 계승해 컨테이너에 ext4 파일시스템을 사용했으며, ext4 파일시스템은 파일 권한을 설정할 수 있게 되었다.

여기에서 리소스와 매니페스트 파일이 들어 있는 res.zip 은 시스템 전체에서 읽을 수 있는 반면, 전체 APK 가 들어 있는 pkg.apk 파일은 시스템과 앱의 전용 사용자만 읽을 수 있다.

실제 앱 컨테이너는 /data/app-asec/ 디렉터리에 .asec 확장자를 가진 파일로 저장된다.



** 암호화된 앱 컨테이너


-

암호화된 앱 컨테이너는 “안드로이드 보안 외장 캐시(Android Secure External Cache)” 혹은 간단히 “ASEC 컨테이너” 라고 한다.

시스템 볼륨 데몬(vold)은 ASEC 컨테이너 관리 기능을 구현하며, 마운트 서비스는 프레임워크 서비스가 이 기능을 사용할 수 있게 인터페이스를 제공한다.

안드로이드 쉘에서 포워드 락이 걸린 앱을 관리하기 위해 vold 데몬에 접근하려면 vdc 명령행 유틸리티를 사용할 수 있다.



-

vdc asec list 명령은 마운트된 ASEC 컨테이너와 네임스페이스 ID 를 나열한다.

vdc asec path 명령은 지정한 ASEC 컨테이너의 마운트 지점을 보여주고,

vdc asec unmount 명령은 지정한 컨테이너를 언마운트한다.



-

ASEC 컨테이너 암호화 알고리즘 및 키 길이는 안드로이드 2.2에서 앱을 SD 카드로 복사할 때 구현한 암호화 알고리즘과 동일하다.

Twofish 알고리즘을 사용하며 128 비트 키는 /data/misc/systemkeys/ 디렉터리에 저장된다.



-

pm install 명령에 -l 옵션을 주거나 패키지 매니저의 installPackage() 메서드를 호출할 때 INSTALL_FORWARD_LOCK 플래그를 지정함으로써 앱에 포워드 락을 건다.




** 포워드 락 걸린 APK 설치


-

포워드 락이 걸린 APK 를 설치할 때에는 추가로 두 단계를 거친다.

보안 컨테이너를 생성해 마운트하고 APK 파일에서 공개 리소스 파일들을 추출한다.

암호화된 APK 파일의 경우와 마찬가지로, 이 과정은 미디어 컨테이너 서비스가 담당하며 APK 를 앱 디렉터리에 복사할 때 처리된다.

그러나 미디어 컨테이너 서비스는 보안 컨테이너를 생성하고 마운트할 권한이 없으므로 마운트 서비스가 제공하는 createSecureContainer(), mountSecureContainer() 등의 메서드를 호출해 vold 데몬에 컨테이너 관리를 위임한다.




* 3.3.7. 암호화된 앱과 구글 플레이


-

암호화된 앱이든 암호화되지 않은 앱이든 사용자의 개입 없이 앱을 설치하려면 시스템 권한이 요구되므로, 시스템 앱만 이 앱을 설치할 수 있다.

구글 플레이 스토어 클라이언트는 암호화된 앱과 포워드 락을 모두 이용한다.



-

무료 앱은 복호화되고 APK 가 /data/app/ 디렉터리에 설치되는 반면, 유료 앱은 /data/app-asec/ 디렉터리에 암호화된 컨테이너를 생성하고 /mnt/asec/<패키지명> 디렉터리에 마운트한다.



-

안드로이드 디바이스에서 루트 권한을 갖고 있으면 포워드 락이 걸린 APK 나 컨테이너 암호화 키를 추출할 가능성은 여전히 남아 있다.






3.4. 패키지 검증


-

패키지 검증은 “앱 검증(Application Verification)” 이라는 이름으로 안드로이드 버전 4.2에서 공식 기능으로 소개되었고, 나중에는 2.3 이후의 모든 안드로이드 버전과 구글 플레이 스토어에 이식되었다.



-

패키지 검증을 가능하게 하는 기반은 OS 에 들어가 있지만 안드로이드는 내장된 검증기를 제공하지 않는다.



-

가장 널리 사용되는 패키지 검증기는 구글 플레이 스토어 클라이언트에 들어 있으며, 구글에서 제공하는 앱 분석 기반 구조의 지원을 받는다.

앱 분석 기반 구조는 구글이 “유해 가능성이 있는 앱” (흔히 악성 코드, 말웨어라고 부른다) 으로부터 안드로이드 디바이스를 보호하기 위해 설계되었다.



-

패키지 검증 기능을 활성화시키면 APK 를 설치하기 전에 검증기로 검사하고, 검증기가 유해하다고 판단하면 시스템은 경고 메시지를 출력하거나 설치를 중단한다.

검증 기능은 이 기능이 지원되는 디바이스에서는 기본적으로 활성화되어 있지만 앱 데이터를 구글에 전송하므로 처음 사용할 때 사용자의 승인을 받아야 한다.

앱 검증은 시스템 환경 설정의 [보안] 화면에서 [앱 설치 전 확인] 옵션을 통해 켜거나 끌 수 있다.




* 3.4.1. 패키지 검증을 위한 안드로이드 지원 기능


-

패키지 검증도 패키지 매니저 서비스에 구현되어 있으며, 안드로이드 4.0(API 14)부터 사용할 수 있었다.

패키지 검증은 하나 이상의 검증 에이전트에 의해 수행되는데, 검증 에이전트에는 필수 검증기(Required Verifier)와 선택적으로 사용할 수 있는 보조 검증기(Sufficient Verifier)가 있다.

필수 검증기와 최소 하나의 보조 검증기가 앱에 대한 긍정적인 반응을 보이면 앱이 안전하다고 판단한다.



-

앱은 PACKAGE_NEEDS_VERIFICATION 액션과 APK 파일을 MIME 형으로 선언하는 인텐트 필터를 가진 브로드캐스트 리시버를 선언함으로써 자신을 필수 검증기로 등록할 수 있다.



-

자신을 필수 검증기로 선언하려면 PACKAGE_VERIFICATION_AGENT 권한이 있어야 한다.

이 권한은 시스템 앱에서 사용하는 signature 보호 수준의 권한이므로 (signature|system), 시스템 앱만 필수 검증기가 될 수 있다.



-

앱은 자신의 매니페스트에 <package-verifier> 태그를 추가하고 이 태그의 속성에 보조 검증자의 패키지명과 공개 키를 나열함으로써 자신을 보조 검증기로 등록할 수 있다.



-

검증기가 설치되어 있고 Sttings.Global.PACKAGE_VERIFIER_ENABLE 시스템 설정이 true 로 설정되어 있으면,

패키지 매니저 서비스는 패키지를 설치할 때 앱을 검증한다.

APK 를 설치 대기 큐에 추가하고 ACTION_PACKAGE_NEEDS_VERIFICATION 브로드캐스트를 등록된 검증기에 보내면서 검증이 시작된다.



-

이 브로드캐스트 메시지에는 고유한 검증 ID 와 검증할 패키지에 대한 다양한 메타데이터가 들어 있다.

검증기는 검증 ID 와 검증 상태를 인자로 verifyPendingInstall() 메서드를 호출해 응답한다.

이 메서드를 호출하려면 PACKGE_VERIFICATION_AGENT 권한이 있어야 하므로 비시스템 앱은 패키지 검증 과정에 참여할 수 없다.

verifyPendingInstall() 메서드가 호출된 때마다 패키지 매니저 서비스는 설치 대기하고 있는 앱이 충분한 검증을 받았는지를 확인한다.

충분히 검증받은 경우에는 설치 대기 중인 앱을 큐에서 제거하고, PACKAGE_VERIFIED 브로드캐스트 메시지를 보내고, 패키지 설치 절차를 시작한다.

검증기가 패키지를 거부하거나 패키지가 할당된 시간 안에 충분한 검증을 받지 못했다면 INSTALL_FAILED_VERIFICATION_FAILURE 오류로 설치는 실패한다.




* 3.4.2. 구글 플레이 구현


-

구글의 앱 검증 구현 모듈은 구글 플레이 스토어 클라이언트에 들어 있다.

구글 플레이 스토어 앱은 자신을 필수 검증기로 등록하고 [앱 설치 전 확인] 옵션이 켜져 있는 경우 구글 플레이 스토어 클라이언트 자신이나 PackageInstaller 앱, 혹은 adb install 을 통해 앱을 설치하고자 할 때마다 브로드캐스트 메시지를 받는다.



-

구글 플레이의 구현은 오픈 소스가 아니라서 내부에 대한 정보는 거의 공개되어 있지 않지만, 구글의 안드로이드 도움말에서 “유해한 앱 차단” 페이지를 보면 “앱을 설치하려고 할 때 기기에서 로그 정보, 앱 관련 URL, 기기 ID, 휴대기기 OS 버전, IP 주소 등의 앱 식별 정보를 구글로 전송할 수 있다"고 설명한다.



-

실제로 구글 플레이 스토어 검증기가 유일한 검증기이므로, 패키지의 설치나 설치 거부에 대한 여부는 구글 온라인 검증 서비스의 응답에만 의존한다.





3.5. 요약


-

안드로이드 앱 패키지(APK 파일)은 JAR 파일 포맷을 확장해 리소스, 코드, 매니페스트 파일을 담고 있다.

APK 파일은 JAR 파일 코드 서명 포맷으로 서명되지만, 모든 파일을 동일한 인증서 집합으로 서명해야 한다.

안드로이드는 코드 서명자 인증서를 이용해 앱과 업데이트의 출처가 동일하다는 것을 확인하며 앱 간의 신뢰 관계를 설정한다.



-

APK 파일은 /data/app/ 디렉터리에 복사해 설치하며, 각 앱의 전용 데이터 디렉터리는 /data/data/ 디렉터리 밑에 생성한다.



-

안드로이드는 포워드 락 걸린 앱을 구현하기 위해 암호화된 APK 파일과 보안 앱 컨테이너를 지원한다.

암호화된 앱은 앱 디렉터리에 복사되기 전에 자동으로 복호화된다.

포워드 락 걸린 앱은 시스템 전체에서 접근할 수 있는 리소스와 매니페스트 부분, OS 만 접근할 수 있는 전용 암호화된 컨테이너에 저장되는 비공개 코드 및 Asset 부분으로 나누어진다.



-

안드로이드는 하나 이상의 검증기에 정보를 요청해 앱을 설치하기 전에 선택적으로 검증할 수 있다.

현재 가장 널리 사용되는 검증기는 구글 플레이 스토어 클라이언트 앱에 구현되어 있으며, 유해한 앱을 탐지하기 위해 구글 온라인 앱 검증 서비스를 사용한다.





정리 목차


3. 패키지 관리

     3.1. 안드로이드 앱 패키지 구성


     3.2. 코드 서명

          3.2.1. 자바 코드 서명

          3.2.2. 안드로이드 코드 서명


     3.3. APK 설치 과정

          3.3.1. 앱 패키지와 데이터의 위치

          3.3.2. 활성화된 컴포넌트

          3.3.3. 지역 패키지의 설치

          3.3.4. 패키지 업데이트

          3.3.5. 암호화된 APK 의 설치

          3.3.6. 포워드 락

          3.3.7. 암호화된 앱과 구글 플레이


     3.4. 패키지 검증

          3.4.1. 패키지 검증을 위한 안드로이드 지원 기능

          3.4.2. 구글 플레이 구현


     3.5. 요약





반응형

댓글