[Android] Build System App

subinJeong·2024년 7월 3일

1. 문제 상황

펌웨어 검토 중 su 를 실행하지 않으면 실행되지 않는 shell command들이 있었다.

type=1400 audit(0.0:1503583): avc: denied { getattr } for path="/vendor/bin/~~" dev="overlay" ino=316 
scontext=u:r:shell tcontext=u:object_r:vendor_file tclass=file permissive=1

type=1400 audit(0.0:1503584): avc: denied { execute } for name="~~" dev="dm-4" ino=316 
scontext=u:r:shell tcontext=u:object_r:vendor_file tclass=file permissive=1

type=1400 audit(0.0:1503585): avc: denied { read open } for path="/vendor/bin/~~" dev="overlay" ino=316 
scontext=u:r:shell tcontext=u:object_r:vendor_file tclass=file permissive=1

type=1400 audit(0.0:1503586): avc: denied { execute_no_trans } for path="/vendor/bin/~~" dev="overlay" ino=316 
scontext=u:r:shell tcontext=u:object_r:vendor_file tclass=file permissive=1

처음에는 파일 접근 권한 문제인 줄 알았으나, logcat 분석 결과 SELinux의 context 문제임을 확인.

  1. 로그 분석
    • scontext = source context, shell 에서 실행하였음.
    • tcontext = target context, vendor_file로 설정되어있음.
    • tclass = target class, 실행 중인 객체의 유형을 나타냄.
  2. SELinux Context
    1. 일반 사용자로 실행:
      • u:r:shell:s0
      • 이 경우, vendor_file 타입의 파일에 접근이 거부.
    2. su 명령어 사용 시:
      • u:r:su:s0 또는 u:r:root:s0와 같이 더 높은 권한을 가진 컨텍스트로 변경
      • 이 경우, vendor_file 타입의 파일에 접근이 허용.

📌 BUT, userdebug build에서는 su를 사용할 수 있지만, user build에서는 사용할 수 없음.

2. 해결 방안

userbuild에서도 사용할 수 있는 Android system 권한을 가진 App을 Build하여 테스트.

2.1 App Build 시, Android System 권한 추가하기

1. AndrodManifest.xml 설정
android:sharedUserId="android.uid.system" 추가

```
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:sharedUserId="android.uid.system">
					...
```

2. Build.gradle(:app) 설정
1. app/key/[mykey]/platform.jks 추가 (Android SDK를 빌드할 때 사용한 platform key를 사용한다.
2. signingConfigs와 buildTypes 추가. 아래의 예시를 참고.

    android {
           signingConfigs {
               [mykey_name]{
                   storeFile file('key/[mykey]/platform.jks')
                   storePassword '[password]'
                   keyAlias = '[keyAlias]'
                   keyPassword '[password]'
               }
           }

       buildTypes {
           release {
               minifyEnabled false
               shrinkResources true
               proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
   
               signingConfig signingConfigs.[mykey_name]
           }
       }
   }

3. BuildVariants = release로 변경하여 빌드 및 설치

나는 이 방법으로 설치하여 확인하였다.

2.2 System priv app에 포함

위의 방식대로 진행하기는 했지만 AndoridManifest.xml에는 위와 같이 보여진다.

💡 Consider removing sharedUserId for new users by adding android:sharedUserMaxSdkVersion="32" to your manifest.

이 상수는 API 수준 29부터 지원 중단되었습니다.

공유 사용자 ID는 패키지 관리자 내에서 비결정적 동작을 일으킵니다. 따라서 사용하지 않는 것이 좋으며 향후 Android 버전에서 삭제될 수 있습니다. 대신 서비스 및 콘텐츠 제공자와 같은 적절한 통신 메커니즘을 사용하여 공유 구성요소 간의 상호 운용성을 용이하게 하세요. 공유 사용자 ID 이전은 지원되지 않으므로 기존 앱에서는 이 값을 삭제할 수 없습니다. 이러한 앱에서는 신규 사용자 설치 시 공유 사용자 ID를 사용하지 않도록 android:sharedUserMaxSdkVersion="32"를 추가합니다.

Android 12 (API 레벨 32) 이후에 더 이상 지원되지 않음을 의미한다 😂
따라서, System의 권한을 얻기 위해 System 빌드 시, preinstall app에 포함시켜야 한다.

mk 파일에 모듈을 포함시켜 빌드해야하는데, 내가 본 sdk 코드를 공개하면 안될것 같고...(aosp 코드를 참고해도 좋음)
빌드 과정에서 적당한 위치의 mk 파일에 아래의 코드를 추가한다.

PRODUCT_PACKAGES += \
    [myModule] \
    ...

모듈의 폴더 내에 Andorid.mk 를 정의하고 모듈을 포함시켜 빌드를 실행하도록한다.

LOCAL_PATH := $(my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := [myModule]
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PRODUCT_MODULE := true
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := [myModule_apk]

이것만 넣고 빌드하면 오류가 나는 경우가있는데 그건 permission 문제때문!

이건 별도의 글로 다시 작성할..예정입니다… 내용이 넘무많소…. (링크를 걸어두겠읍니다꼮꼮꼮)

2.3 Adb command 활용 (only userdebug)

root 및 remount를 실행해야하므로 userdebug build에서만 확인 가능한데, debugging 하는 과정에서 간단히 확인할때는 편리한 방법일것 같다.

adb root && adb remount
adb shell chmod 755 /system/app
adb push "[myApp].apk" /system/app
adb shell sync && adb reboot

(출처 : https://prush.tistory.com/11)

3. 확인 및 결과

adb shell에서 command를 입력하여 확인할 수있다.

userId=1000 이 출력되면 System 권한을 가지고 있는 App으로 설치된 것이다.

  • [myApp_package_name] : myApp의 Package Name을 입력 (ex. com.example.myapplication)
$ dumpsys package [myApp_package_name] | grep userId                                               
    userId=1000
    userId=1000

그 다음 해당 앱에 코드를 추가하여 실행하면

type=1400 audit(0.0:7659): avc: denied { map } for path="/vendor/bin/~~" dev="dm-7" ino=276 
scontext=u:r:system_app:s0 tcontext=u:object_r:vendor_file:s0 tclass=file permissive=1

scontext 값이 system_app으로 변경되어있고 정상적으로 입력 command가 실행된다.
shell command를 입력하는 App을 간단하게 만들었는데 java 코드로는 아래와 같다

[shell_command]에 실행하고자 하는 command를 입력한다.
(이것 외에도 파일이 존재하는지 등의 코드는 별도로 알잘딱 추가^^)

try {
	Process ps = Runtime.getRuntime().exec([shell_commmand]);
    ps.waitFor();
    ps.destroy();
} catch (IOException | InterruptedException e) {
    e.printStackTrace();
    return false;
}

4. Refer

🔗 SELinux 유효성 검사 | Android 오픈소스 프로젝트
🔗 앱 축소, 난독화 및 최적화 | Android Studio | Android Developers
🔗 manifest|Android Developers
🔗 How to Add Pre-built App (System App) in AOSP source code | Stack Overflow

profile
Andorid developer

0개의 댓글