안드로이드 루팅 탐지와 우회

Clean Code Big Poo·2023년 11월 28일
0

Android

목록 보기
2/2
post-thumbnail

Overview

안드로이드 보안 강의를 들으며 루팅탐지와 우회하는 방법에 대해 알아보자.

  1. 개발자가 루팅을 탐지하는 방법
  2. 루팅을 탐지하는 것을 피하는 방법
  3. 루팅을 탐지하는 것을 피하는 것을 방지하는 방법 <- 내가 원하는 부분 ㅜ

루팅

관리자 권한(루트 권한)을 얻는 것

장점 : 커스텀 테마 적용, 통신사 어플 삭제, CPU 오버클럭으로 성능 최대화 가능
단점 : AS불가, 벽돌폰, 보안 취약

1. 부트로더(bootloader) 언락

부트 로더(OS가 시동되기 전에 관련 작업을 세팅하는 과정)를 언락하여 기기 내부 메모리에 기록이 가능하도록 세팅

2. 커스텀 리커버리 프로그램 설치

안드로이드 폰에 리커버리 프로그램이 내장되어 있는 것을 다른 프로그램을 대체한다. 메모리나 캐시를 조작하기 위함이다.

3. 루팅

위 과정을 거치고 나서 관리자 권한을 얻는다.

루팅환경 만들기, 기본적인 루팅 탐지

su 체크

sudo는 root가 아닌 사용자가 root 권한으로 실행하는 것.
su는 현재 계정에서 root 계정으로 전환하는 것

루팅된 디바이스의 경우, su파일 존재 여부를 확인하여 루팅 여부를 판별 할 수 있음.

busybox 체크

busybox는 루팅된 장치에 종종 설치된다.
이 것은 linux 명령어를 제공하는 바이너리 파일이고, busybox가 실행되면 루팅된 디바이스라고 생각할 수 있다.

su, busybox 체크 코드

/// su와 busybox 탐지되는 경로 리스트
private String[] binaryPaths= {
        "/data/local/",
        "/data/local/bin/",
        "/data/local/xbin/",
        "/sbin/",
        "/su/bin/",
        "/system/bin/",
        "/system/bin/.ext/",
        "/system/bin/failsafe/",
        "/system/sd/xbin/",
        "/system/usr/we-need-root/",
        "/system/xbin/",
        "/system/app/Superuser.apk",
        "/cache",
        "/data",
        "/dev"
};

/// su 사용 확인
private boolean checkForSuBinary() {
    return checkForBinary("su"); // function is available below
}

/// su binary 파일 체크 
private boolean checkForBinary(String filename) {
    for (String path : binaryPaths) {
        File f = new File(path, filename);
        boolean fileExists = f.exists();
        if (fileExists) {
            return true;
        }
    }
    return false;
}

/// busybox 사용 확인
private boolean checkForSuBinary() {
    return checkForBinary("su"); // function is available below
}

/// busybox binary 파일 체크
private boolean checkSuExists() {
    Process process = null;
    try {
        process = Runtime.getRuntime().exec(new String[]
                {"/system /xbin/which", "su"});
        BufferedReader in = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
        String line = in.readLine();
        process.destroy();
        return line != null;
    } catch (Exception e) {
        if (process != null) {
            process.destroy();
        }
        return false;
    }
}

패키지, 앱 탐지

루팅 관련 프로세스가 동작하고 있는지 혹은, 루팅 관련 패키지나 앱이 설치되어 있는지 확인

코드

private void getInstallApllList(){
  PackageManager packageManager = mContext.getPackageManager();
  
  Intent intent = new Intent(Intent.ACTION_MAIN);
  intent.addCategory(Intent.CATEGORY_LAUNCHER);
  
  List<ResolveInfo> AppInfos = packageManager.queryIntentActivities(intent, 0);
  
  for (ResolveInfo info : AppInfos) {
    ActivityInfo ai = info.activityInfo;
    // 앱 이름 탐지
    Log.d("app name", ai.loadLabel(packageManager).toString());
    // 앱 패키지 탐지
    Log.d("app package Name",""+ai.packageName);
    
    int resId = ai.applicationInfo.icon;
    //icon
   }
}

테스트 키 탐지

코드

private boolean detectTestKeys() {
    String buildTags = android.os.Build.TAGS;
    return buildTags != null && buildTags.contains("test-keys");
}

폴더 권한 확인

구글 플레이스토어를 통한 설치 확인

앱을 구글 플레이 스토어를 통해 다운받고, 앱을 디컴파일하여 루팅 탐지부분을 제거하고 앱을 사용할 수도 있다. 그러므로 플레이 스토어에서 받은 상태 그대로인지 확인하는 방법이다.

코드

  void getInstaller() {
    // 플레이 스토어에서 설ㅣ했을 경우, "[설치한 앱의 패키지 명]" 으로 프린트
String installer = this.getPackageManager().getInstallerPackageName(this.getPackageName());
	if (installer == null) {
      // developer
      Log.d(TAG, "Installer package is null");
      System.exit(0);
     } else {
	      Log.d(TAG, "Installer package is not null : " + installer);

          switch (checkContains(installer)) {
            case "com.android.vending":// Google Play Store
            case "samsungapps":// Galaxy Apps
            case "packageinstaller":// Android Package Installer
            case "com.skt.skaf.A000Z00040":// ONE STORE
            	break;
            default: // 위 스토어들 제외 앱 종료
              System.exit(0);
              break;
            }
          return;
  	}	
  }

Store url

null - developer
com.android.vending - google play
com.amazon.venezia - amazon app
com.sec.android.app.samsungapps - samsung app store
samsungapps://ProductDetail/com.sec.chaton


1) Google Play Store(구글 플레이스토어)com.android.vending
2) ONE STORE(원스토어)com.skt.skaf.A000Z00040
3) Galaxy Apps(갤럭시 앱스)com.sec.android.app.samsungapps
4) Samsung Smart Switchcom.sec.android.easyMover.Agent
5) Android Package Installercom.google.android.packageinstaller
6) Samsung Mate Agentcom.samsung.android.mateagent

루팅 탐지 피하기

su 바이너리 파일 탐지 피하기

파일 이름을 바꿈으로써 쉽게 우회 가능

'su' -> 다른 이름으로 변경

Hooking

FRIDA

scriptable 한 DBI(Dynamic Binary Instrumentation) 프레임워크로 JS Injection 을 통해 후킹한다.

FRIDA 설치

frida 설치는 별도의 글에 정리하겠음!
설치하다 실패함 ㅎ

디컴파일 프로그램

apk 파일로 빌드된 앱을 소스코드로 변환하는 프로그램. 컴파일의 반대.

프로가드 해독

코드 난독화 및 안드로이드 앱 용량을 줄여주는 프로그램

덱스가드

프로가드보다 강력한 기능을 제공하는 유료 서비스

무결성 검사

앱에서의 무결성 검사란 앱이 누군가에 의해서 조작되었는지 검사하는 것을 뜻한다.

용량 체크

원본 apk파일과 비교해서 용량이 변동되지 않았는지 체크하는 방법도 있습니다.

서명 인증서 사용

앱을 배포하기 위해 Signing을 하게 된다. Signing시에 사용되는 키스토어 파일이 갖고 있는 키와, 이 키스토어로 서명된 앱이 가지고 있는 키를 비교하는 방법으로 무결성을 검증할 수 있다.

같은 서명 키로 앱은 서명키에 대한 Hash값이 동일하기 때문에 이를 비교하여 무결성을 검증할 수 있다. 누군가 임의로 수정 후 다시 컴파일 하였다면 키가 달라지기 때문이다.

구글 플레이 스토어 -> 앱 선택 -> 설정 -> 앱 서명 메뉴에 SHA 인증서가 있다

최초 배포시 사용한 서명 키를 추출하여 사용한다. 서명키와 업로드 키는 한 쌍이다!

구글 플레이 스토어 서명키 확인

kotlin

    private void getSignature(){
       @SuppressLint("PackageManagerGetSignatures")
       PackageInfo packageInfo = null;
       TextView pkgInfoTxt = (TextView) findViewById(R.id.pkg_text_view);
       try{
           packageInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), PackageManager.GET_SIGNATURE)
           String sigString = "";

           for(Signature sig : packageInfo.signatures){
               MessageDigest md = MessageDigest.getInstance("SHA-512");

               md.update(sig.toByteArray());
               String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
               sigString += "["+currentSignature+"], ";
           }
           pkgInfoTxt.setText("sigs : "+sigString);

       }catch(e){
           e.printStackTrace();
       }
   }

java

 @SuppressLint("PackageManagerGetSignatures")
   private void getSignature() {
       PackageInfo packageInfo = null;
      //TextView pkgInfoTxt = findViewById(R.id.pkg_text_view); // 레이아웃에 정의된 TextView ID 사용

       try {
           packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURE);
           String sigString = "";

           for (Signature sig : packageInfo.signatures) {
               MessageDigest md = MessageDigest.getInstance("SHA-512");

               md.update(sig.toByteArray());
               String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
               sigString += "[" + currentSignature + "], ";
           }
           Log.d(TAG, "sigs : " + sigString);

       } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
           e.printStackTrace();
       }
   }

flutter

Future<void> getSignature() async {
   // 키스토어 파일 경로
   String keystoreFilePath = '/경로/키스토어.jks'; // 실제 경로로 변경

   // 키스토어 비밀번호 및 키 비밀번호
   String keystorePassword = '키스토어_비밀번호';
   String keyPassword = '키_비밀번호';

   // keystore 파일 읽기
   File keystoreFile = File(keystoreFilePath);
   List<int> keystoreBytes = await keystoreFile.readAsBytes();

   // SHA-256 해시 생성
   List<int> sha256Bytes = sha256.convert(keystoreBytes).bytes;

   // SHA-256 해시를 문자열로 변환
   String sha256String = base64.encode(sha256Bytes);

   // 결과 출력
   print('SHA-256 해시: $sha256String');
 }

참조

0개의 댓글