본문 바로가기

Technical Docs/Android

[분석] UnCrackable-Level1.apk - 2 (Secret String)

UnCrackable-Level1.apk - 1 (Rooted)

 

OWASP에는 모바일 보안 테스트 할 수 있는 앱을 제공함

분석을 하면서 참고해야하는 부분을 풀이할 예정

 

진단 환경

  • 단말 : Nexus6P - 7.1.1
    • jadx-gui : Dex to Java decompiler
    • APK Easy Tool : Decompile, Compile, Signing 등 쉽게해주는 툴
  • APP : UnCrackable-Level1.apk

https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android

 

GitHub - OWASP/owasp-mstg: The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security testing an

The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes the technical processes for verifying the controls listed in...

github.com

 

1. Frida

2. 코드 패치(나중에)

3. gdb(나중에)

 

분석 1 - 앱 구동 확인

루팅 우회 후 다음 시도할 내용은 암호 문자열 찾는 것으로 추측됨

 

정적분석 진행

1. sg.vantagepoint.a.c 클래스에 verify 메소드를 확인할 수 있음

위에서 알림창에서 확인한 "Nope That's not it. Try agmin." 문구를 확인할 수 있음

obj는 사용자가 입력함 암호 문자열 이고, 

a.a(obj)의 리턴 값을 가지고 암호 문자열 검증을 함

 

a.a(obj) 메소드를 보면 암호화 문자열을 확인할 수 있음

"sg.vantagepoint.a.a.a(arg1, arg2)" 에서 암호화된 문자열을 복호화한 뒤, str(사용자가 입력한 값)이 같은지 확인함

   - sg.vantagepoint.a.a.a(암/복호화 키 값 , 암호화된 문자열)

   - sg.vantagepoint.a.a.a(""8d127684cbc37c17616d806cf50473cc", "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=")

 

 

"sg.vantagepoint.a.a.a(arg1, arg2)"를 확인하면, "AES/ECB/PKCS7Padding" 암호화 방식을 확인할 수 있음

ECB 모드를 사용하기 때문에 IV(Initialization vector)를 사용하지 않음

  * CBC 모드는  IV(Initialization vector)를 사용함

암호화관련작성

 

여기서 우리는 다양한 방법을 이용해 암/복호화 할 수 있음

1. Frida 후킹

  - 장점 : 복호화된 데이터 쉽게 추출 가능, javascript 재사용 가능, SecretKey 및 IV 추출 쉬움

  - 단점 : Frida 없이 할 경우 값 추출 어려움

 

2. 소스코드 작성

  - 장점 : SecretKey 및 IV만 알고 있으면 재사용 편의

  - 단점 : SecretKey 및 IV을 사전에 알고 있어야 함

 


풀이 1 - 1 FRIDA

 

주목적은 단순히 "Success"문구를 보여주는게 아니라 복호화된 문자열 찾는것임

1. 복호화된 암호 문자열 추출

위에서 "sg.vantagepoint.a.a.a(arg1, arg2)" 리턴 값은 Secret 문자열이란걸 확인함

a.ovarload('[B', '[B')를 지정해준 이유는 우리가 후킹할 함수를 명확히 지정해주는 거와 같음

overload()나중에정리

    public static byte[] a(byte[] bArr, byte[] bArr2) {  

 

리턴 값은 문자열 형태가 아닌 아스키 형태로 출력되어 문자열로 변환해줘야 함

String.forCharCode() 함수는 아스키 코드를 문자열로 변경 시켜주며, 우리가 원하는 문자열로 만들어줄 수 있음

String.fromCharCode();
    var AESCheck = Java.use('sg.vantagepoint.a.a');
    AESCheck.a.overload('[B', '[B').implementation = function (args1, args2) {
      var ret = this.a(args1, args2);
      console.log(ret) // 73,32,119,97,110,116,32,116,111,32,98,101,108,105,101,118,101
      var secretString = ""
      for (var i = 0; i < ret.length; i++) {
        var secretString = secretString + String.fromCharCode(ret[i]);
      }
      console.log("[+] Secret String\t->", secretString)
      return ret;
    }

 

코트 실행하면 암호화 문자열 복호화 됨을 확인할 수 있음 

암호화 문자열 복호화문자열
5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc= I want to believe

 

복호화 문자열 입력하면 끝

 

최종 소스

/*
frida -l test.js -U --no-pause -f owasp.mstg.uncrackable1
*/
var AESCheck = Java.use('sg.vantagepoint.a.a');
AESCheck.a.overload('[B', '[B').implementation = function (args1, args2) {
    var ret = this.a(args1, args2);
    var secretString = ""
    for (var i = 0; i < ret.length; i++) {
        var secretString = secretString + String.fromCharCode(ret[i]);
    }
    console.log("[+] Secret String\t->", secretString)
    return ret;
}
더보기

UnCrackable-Level1 최종 소스

/*
frida -l test.js -U --no-pause -f owasp.mstg.uncrackable1
*/
Java.perform(function () {
  FileExists()
  AESDecryptData()

  function FileExists() {
    var checkRoot = [
      //sg.vantagepoint.a.c()
      "su",

      //sg.vantagepoint.a.b()
      "test-keys",

      //sg.vantagepoint.a.c()
      "/system/app/Superuser.apk",
      "/system/xbin/daemonsu",
      "/system/etc/init.d/99SuperSUDaemon",
      "/system/bin/.ext/.su",
      "/system/etc/.has_su_daemon",
      "/system/etc/.installed_su_daemon",
      "/dev/com.koushikdutta.superuser.daemon/"
    ]

    var File_exists = Java.use('java.io.File');
    File_exists.exists.implementation = function () {
      var File_Name = File_exists.getName.call(this);
      if (checkRoot.indexOf(File_Name) > -1) {
        console.log(File_Name)
        return false;
      }
      return this.exists.call(this);
    }
  }

  function AESDecryptData() {
    var AESCheck = Java.use('sg.vantagepoint.a.a');
    AESCheck.a.overload('[B', '[B').implementation = function (args1, args2) {
      var ret = this.a(args1, args2);
      var secretString = ""
      for (var i = 0; i < ret.length; i++) {
        var secretString = secretString + String.fromCharCode(ret[i]);
      }
      console.log("[+] Secret String\t->", secretString)
      return ret;
    }
  }
});