ANDITER

ANDITER는 실제 안드로이드 앱에서 사용되는 보안 코드 탐지(루팅, 디버그, 무결성, 프리다 등) 방법을 배우고 공격자 관점에서의 우회 방안을 테스트할 수 있는 모바일 침투 테스팅 앱입니다. ANDITER는 루팅, 디버깅, 애뮬레이터, 프리다, SSL Pinning, 무결성, 동적 로딩, 잠금화면, 네이티브 총 9가지 챕터로 구성되어 있는데요. 각 챕터를 실습하며 내용을 정리해보도록 하겠씁니다.


1. 루팅(ROOTING)

1-1. BYPASS PACKAGES : 패키지 탐지 방식  

com.playground.anditer.RootingDetector 클래스를 살펴보면 인스턴스 변수(rootingPackages)에 리스트 형태로 값이 할당된 것을 확인할 수 있습니다. 해당 값들은 루팅 기기에서 발견할 수 있는 문자열인데요. 현재 앱에서는 isCheckRootingInstalled() 메소드 내부에서 getPackageInfoCompat(...)를 호출한 뒤 rootingPackages에 할당된 패키지가 기기 내부에 존재하는지 확인하여 루팅 여부를 판별하고 있습니다.


이러한 루팅 탐지를 우회하려면 코드 패치, 함수 후킹, 동적 디버깅 중 하나를 선택할 수 있는데요. 이번 포스팅에서는 프리다를 사용한 함수 후킹을 통해 우회를 진행해보겠습니다.

[FRIDA] 패키지명 검사 함수(isCheckRootingInstalled()) 후킹

// hooking.js
Java.perform(function () {
  var RootingDetector = Java.use('com.playground.anditer.RootingDetector');
 
  RootingDetector.getPackageInfoCompat.overload(
    'android.content.pm.PackageManager',
    'java.lang.String',
    'int'
  ).implementation = function (packageManager, packageName, i) {
    console.warn('\n[+] Method (getPackageInfoCompat) has been called !');
    console.log(`\tArgmunet 1 (packageManager): ${packageManager}`);
    console.log(`\tArgmunet 2 (packageName): ${packageName}`);
    console.log(`\tArgmunet 3 (i): ${i}`);
 
    var fakePackageName = 'isNotExist';
 
    return this.getPackageInfoCompat(packageManager, fakePackageName, i);
  };
});

함수에서 사용되는 인자(packageName; 패키지 이름)의 값을 임의의 값으로 변조하면 getPackageInfoCompat 리턴 값으로 PackageInfo가 반환되지 않기 때문에 예외처리 문으로 분기되어 “false” 값을 반환하고 결과적으로 기기에 설치된 애플리케이션의 패키지 이름으로 루팅을 탐지하는 로직을 우회할 수 있게 됩니다.


1-2. BYPASS BINARIES : 바이너리 파일 탐지 방식  

안드로이드 기기를 루팅하면 관리자 권한을 사용하기 위해 특정 바이너리(ex; su, busybox)들이 사용되는데요. Bypass Binaries 파트에서는 루팅 시에 설치·사용되는 바이너리의 존재 여부를 확인하여 루팅 디바이스를 탐지합니다. 탐지 우회는 동일하게 함수 후킹으로 진행하겠습니다.

소스코드를 살펴보면 isCheckRootingBinary() 함수에 의해 앱에 정의된 PATH/BINARY 파일이 기기에 존재하는지를 확인하여 루팅 여부를 검사하고 있습니다.


함수 후킹은 2가지 방법이 존재하는데요. isCheckRootingBinary, File().exists() 2가지 함수 중 1가지를 선택하여 리턴 값을 변조하면 바이너리 루팅 탐지 우회가 가능합니다. exists() 함수를 후킹하면 아래와 같이 코드를 작성하여 우회할 수 있습니다.

Java.perform(function () {
  var File = Java.use('java.io.File');
  File.exists.implementation = function () {
    var stackTrace = Java.use('android.util.Log').getStackTraceString(
      Java.use('java.lang.Exception').$new()
    );
 
    if (
      stackTrace.includes(
        'com.playground.anditer.RootingDetector.isCheckRootingBinary'
      )
    ) {
      if (this.exists()) {
        console.log();
        console.warn('[*] File.exists() called inside isCheckRootingBinary()');
        console.log(
          '\tTarget File: ' + this.getAbsolutePath() + 'Return Value: ',
          this.exists()
        );
      }
 
      return false;
    }
 
    return this.exists();
  };
});

위 코드를 실행하면 아래와 같이 바이너리 파일 탐지를 통한 루팅 탐지 로직을 우회할 수 있습니다.


1-3. BYPASS COMMAND EXECUTION : 명령어 실행 가능 여부 탐지 방식

명령어 실행을 통한 디바이스 루팅 탐지 파트에서는 아래 함수의 인자/리턴 값을 변조하는 방식으로 탐지를 우회할 수 있습니다. 실습에서는 3번 방법으로 우회를 시도해보았습니다.

  • (1) Runtime.getRuntime.exec(new String[]{"which", "su"}) · · ·
  • (2) String readText = TextStreamsKt.readText(bufferedReader)
  • (3) Intrinsics.areEqual(readText, "")
  • (4) isCheckRootingExec

아래와 같이 후킹 코드를 작성하여 Intrinsics 클래스의 areEqual 함수를 재정의하면
함수의 실행 결과가 항상 거짓(false) 값을 반환하도록 유도할 수 있습니다.

Java.perform(function () {
    var Intrinsics = Java.use('kotlin.jvm.internal.Intrinsics');
    Intrinsics.areEqual.overload('java.lang.Object', 'java.lang.Object').implementation = function(args1, args2) {
        var stackTrace = Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new());
        if (stackTrace.includes('com.playground.anditer.RootingDetector.isCheckRootingExec')) {
            console.log(`Orignial Intrinsics.areEqual(${args1}, ${args2}) => ${this.areEqual(args1, args2)}`);
            var tmpArg = "";
            console.log(`Modified Intrinsics.areEqual(${args1}, ${args2}) => ${this.areEqual(tmpArg, args2)}`);
            return this.areEqual(tmpArg, args2);
        }
        return this.areEqual(args1, args2);
    }
 
});


1-4. BYPASS BUILD-TAGS : build.prop[keys] 비정상 값 탐지 방식

build.prop 파일은 안드로이드의 시스템 정보가 저장되는 파일로 루팅 디바이스의 경우에는 해당 파일 내의 ro.build.keys 태그 값이 test-keys로 설정되어 있는 경우가 있습니다. 따라서 ro.build.keys 값을 확인하여 루팅 여부를 탐지하고 있는지 확인해 보아야 합니다.


단말기의 ro.build.keys 값은 아래의 명령어를 실행하여 직접 확인할 수 있습니다.
만약 test-keys가 발견되었다면 build.prop 파일의 내용을 변경하거나
함수 후킹 등의 방법을 사용하여 값을 변조하여 우회하면 됩니다.

cat /system/build.prop | grep -i test-keys
getprop | grep -i test-keys

1-5. BYPASS WRITABLE : 특정 디렉터리 쓰기 가능 여부 탐지 방식

안드로이드는 기본적으로 중요 디렉토리에 대한 쓰기 권한이 제한되어 있지만 루팅 디바이스의 경우에는 이러한 디렉토리에도 쓰기 권한이 할당된 경우가 있습니다. 따라서, 안드로이드 기기 내에 타 사용자에게 쓰기(W) 권한이 부여됐는지 확인하는 코드가 있는지 확인해야 합니다.


위와 같이 Files.getPosixFilePermissions(...)로 파일의 권한을 확인하고 있는 경우에 해당 함수를 후킹하여 “OTHERS_WRITE” 값을 덤프 값으로 덮어씌우거나 간단하게 isCheckRWFile 함수의 리턴값을 변조하면 우회가 가능합니다.


1-6. BYPASS SYSTEM PROPERTY : build.prop[secure] 비정상 값 탐지 방식

루팅 패치 프로그램을 통한 디바이스 루팅 방식은 크게 두 가지로 나눠진다.첫 번째는 리커버리(복구 모드)를 통한 커스텀 바이너리 패치 방식이고 두 번째는 Android OS 초기 버전에서 사용되던 방법으로 default.prop 파일의 ro.secure 값을 변경해 루팅 커널 이미지를 제작하는 방식입니다. 이 경우 루팅을 진행하지 않은 순정 디바이스 경우 시스템 속성인 ro.secure와 ro.adb.secure 값이 1인 반면에 루팅 디바이스는 1외의 값으로 설정됩니다.


아래 이미지를 살펴보면 ro.secure, ro.adb.secure 값이 0인지 검사하여 맞으면 루팅 디바이스로 탐지하고 있는 것을 확인할 수 있다. 이러한 경우에는 getsystemproperty.prop 함수의 인자/리턴 값을 변조하거나 isCheckForProps 함수의 리턴값을 변조하면 수월하게 우회할 수 있습니다.


1-7. BYPASS CHECK PROCESS : 프로세스 상태 탐지 방식

소스코드를 살펴보면 isCheckRootingProcesses 함수에서 기기 내부에서 동작하는 프로세스를 검사하고 있음을 확인할 수 있습니다. isRootCommand 함수를 살펴보면 su 바이너리를 실행하여 관리자 권한을 획득하고 인자로 넘어온 ps -ef 명령어를 실행하고 있습니다. 이 때 ps -ef 의 실행 결과에 magisk 문자열이 존재하면 매지스크에 의해 루팅된 디바이스에서 앱이 실행되는 것으로 판단하게 됩니다.


위 코드는 isCheckRootingProcesses() 함수나 isEmpty() 후킹을 통해 우회가 가능해보이는데요.
간단하게 isCheckRootingProcesses() 함수의 리턴값을 변조하는 방식을 사용해보겠습니다.

Java.perform(function () {
    var RootingDetector = Java.use('com.playground.anditer.RootingDetector');
 
    RootingDetector.isCheckRootingProcesses.implementation = function() {
        console.log(this.isCheckRootingProcesses());
        return this.isCheckRootingProcesses();
    }
});

이렇게 ANDITER 루팅 탐지 챕터의 모든 실습 문제를 풀어봤습니다.


REFERENCE

  1. https://github.com/naroSEC/Anditer?tab=readme-ov-file
  2. https://www.igloo.co.kr/security-information/anditer%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ec%95%88%eb%93%9c%eb%a1%9c%ec%9d%b4%eb%93%9c-%ec%9c%84%ed%98%91-%ed%83%90%ec%a7%80-%eb%b0%8f-%ec%9a%b0%ed%9a%8c-%eb%b0%a9%ec%95%88-part-1/  
  3. https://naro-security.tistory.com/10