Computer Science%/Mobile App

Frida usage history

ch4rli3kop 2020. 3. 6. 10:52
반응형

Frida usage history

열심히 후킹 공부를 합시다. (재밌네)

how to hack uncrackable1?

owasp에서 제공하는 android 문제를 풀어봅시당 ㅎㅅㅎ

Analysis Function

기능 분석

MainActivity

package sg.vantagepoint.uncrackable1;
​
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import owasp.mstg.uncrackable1.R;
import sg.vantagepoint.a.b;
import sg.vantagepoint.a.c;
​
public class MainActivity extends Activity {
   private void a(String str) {
       AlertDialog create = new Builder(this).create();
       create.setTitle(str);
       create.setMessage("This is unacceptable. The app is now going to exit.");
       create.setButton(-3, "OK", new OnClickListener() {
           public void onClick(DialogInterface dialogInterface, int i) {
               System.exit(0);
          }
      });
       create.setCancelable(false);
       create.show();
  }
​
   /* access modifiers changed from: protected */
   public void onCreate(Bundle bundle) {
       if (c.a() || c.b() || c.c()) {
           a("Root detected!");
      }
       if (b.a(getApplicationContext())) {
           a("App is debuggable!");
      }
       super.onCreate(bundle);
       setContentView(R.layout.activity_main);
  }
​
   public void verify(View view) {
       String str;
       String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
       AlertDialog create = new Builder(this).create();
       if (a.a(obj)) {
           create.setTitle("Success!");
           str = "This is the correct secret.";
      } else {
           create.setTitle("Nope...");
           str = "That's not it. Try again.";
      }
       create.setMessage(str);
       create.setButton(-3, "OK", new OnClickListener() {
           public void onClick(DialogInterface dialogInterface, int i) {
               dialogInterface.dismiss();
          }
      });
       create.show();
  }
}

onCreate() 함수에서는 루팅 탐지와 디버깅 가능한 어플인지 확인하는 작업을 거친다. 해당 함수 내에 있는 a() 함수는 같은 클래스 내에 선언되어 있는 함수로 바로 System.exit()를 날려버리는 함수다. 루팅 탐지의 경우 디바이스 내의 /sbin 디렉토리나 그 밖의 디렉토리 내에 슈퍼 계정과 관련된 파일이 존재하는지로 루팅을 판단함. 해당 앱의 debuggable을 판단하는 것은 앱의 Manifest.xmltag를 이용하여 판단한다.

verify() 함수의 경우 Resources/res/layout/activity_main.xml 내에서 verify 버튼이 눌리면 실행되는 것을 확인할 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
   <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content">
       <EditText android:id="@+id/edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="@string/edit_text" android:layout_weight="1"/>
       <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_verify" android:onClick="verify"/>
   </LinearLayout>
   <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">
       <TextView android:padding="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/thanks" android:layout_alignParentBottom="true"/>
   </RelativeLayout>
</LinearLayout>


sg.vantagepoint.uncrackable1.a

package sg.vantagepoint.uncrackable1;
​
import android.util.Base64;
import android.util.Log;
​
public class a {
   public static boolean a(String str) {
       byte[] bArr;
       String str2 = "8d127684cbc37c17616d806cf50473cc";
       byte[] bArr2 = new byte[0];
       try {
           bArr = sg.vantagepoint.a.a.a(b(str2), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
      } catch (Exception e) {
           StringBuilder sb = new StringBuilder();
           sb.append("AES error:");
           sb.append(e.getMessage());
           Log.d("CodeCheck", sb.toString());
           bArr = bArr2;
      }
       return str.equals(new String(bArr));
  }
​
   public static byte[] b(String str) {
       int length = str.length();
       byte[] bArr = new byte[(length / 2)];
       for (int i = 0; i < length; i += 2) {
           bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
      }
       return bArr;
  }
}

str2는 복호화 키로 사용되는 십육진수 배열이며, b() 함수를 통해 바이트 배열로 바뀐다. 이 후, base64 인코딩 된 값이 디코딩되어 str2를 키로 복호화되어 bArr에 저장된다. a() 함수의 인자로 들어오는 str은 사용자가 창에 입력했던 스트링이다. str.equals() 함수를 통하여 복호화된 것인지 확인한다.

sg.vantagepoint.a.a

package sg.vantagepoint.a;
​
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
​
public class a {
   public static byte[] a(byte[] bArr, byte[] bArr2) {
       SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
       Cipher instance = Cipher.getInstance("AES");
       instance.init(2, secretKeySpec);
       return instance.doFinal(bArr2);
  }
}

bArr은 키로 사용되는 값이다. 키 스팩을 구성 및, decrypt_mode(2) 타입으로 인스턴스를 초기화하고, bArr2를 복호화한 값을 리턴한다.

How to hook?

존나리 frida 사용해서 슥슥삭삭하면 됨.

근데 어디를 어떻게 후킹해야할까?를 생각해봅시다.ㅇ

대충 success 메세지를 보는게 목적이라면 sg.vantagepoint.uncrackable1.a 클래스의 a() 함수가 true를 리턴하도록 후킹을 해도 됩니다. 하지만, 시크릿 메시지를 보는 것이 목표이기 때문에 sg.vantagepoint.a.a 클래스의 a() 함수를 후킹하여 복호화된 값을 출력하도록 해서 시크릿 키를 알아내겟읍니다.

import sys, frida
​
def on_message(message, data):
   print( "[%s] -> %s" % (message, data))
​
PACKAGE_NAME = 'owasp.mstg.uncrackable1'
​
jscode = '''
Java.perform(function(){
  console.log("Hooking call to System.exit");
  var exit = Java.use("java.lang.System");
  exit.exit.implementation = function () {
      console.log("System.exit called");
      //send("java.lang.System - exit() bypass ");
  }
​
  var aaClass = Java.use("sg.vantagepoint.a.a");
  aaClass.a.implementation = function(arg1, arg2){
      console.log("[*] Hooking a.class");
      var retval = this.a(arg1, arg2);
      var password = '';
      for (var i=0;i<retval.length; i++){
          password += String.fromCharCode(retval[i]);
      }
      console.log("[*] Decrypted : " + password);
      return retval;
  }
​
  var aClass = Java.use("sg.vantagepoint.uncrackable1.a");
  aClass.a.implementation = function(arg1){
      console.log("[*] this is test");
      this.a(arg1);
      return true;
  }
});
'''
​
try:
   device = frida.get_usb_device(timeout=10) # usb 장치를 찾음
   print(device)
   pid = device.spawn([PACKAGE_NAME])  # 해당 패키지를 실행함. zygote64를 후킹하여 fork()를 수행하는데 이것때문에 spawn이라고 부르는 것 같음. pid를 가져옴
   print("[*] App is starting ... \n[+] pid : {}".format(pid))
   process = device.attach(pid)   # 프로세스에 접근
   device.resume(pid)  # 재실행함. attach 한 뒤에 재실행해야함.
   script = process.create_script(jscode)  # 스크립트 생성..
   script.on('message', on_message)    # send 함수를 이용해서 PC와 단말기간의 통신을 수행할 수 있음.
   print("[+] Helllo world")
   script.load()   # 스크립트를 로드함
   sys.stdin.read()  #
except Exception as e:
   print(e)

ETC

OnCreate() 함수 내에 있는 다음의 c.a() 등을 후킹하려고 시도했으나, 왜인지 잘 걸리지 않았다.

   /* access modifiers changed from: protected */
   public void onCreate(Bundle bundle) {
       if (c.a() || c.b() || c.c()) {
           a("Root detected!");
      }
       if (b.a(getApplicationContext())) {
           a("App is debuggable!");
      }
       super.onCreate(bundle);
       setContentView(R.layout.activity_main);
  }


삽질 결과, 후킹 걸어놓고 앱을 홈으로 갔다가 다시 실행시키던지 종료했다가 다시 실행시키던지 하니까 OnCreate(), OnStart() 함수가 호출되서 후킹이 정상적으로 된다.

아래 코드는 테스트 코드임.

import sys, frida
​
def on_message(message, data):
   print( "[%s] -> %s" % (message, data))
​
PACKAGE_NAME = 'owasp.mstg.uncrackable1'
​
jscode = '''
Java.perform(function(){
​
  console.log("[+] Hooking call to Check Root");
  var acClass = Java.use("sg.vantagepoint.a.c");
  console.log(acClass.a.overloads);
  acClass.a.overloads[0].implementation = function(){
      console.log("Bypass c.a()");
      send("Bypass c.a()");
      return false;
  }
  acClass.b.overloads[0].implementation = function(){
      console.log("Bypass c.a()");
      send("Bypass c.a()");
      return false;
  }
  acClass.c.overloads[0].implementation = function(){
      console.log("Bypass c.a()");
      send("Bypass c.a()");
      return false;
  }
   
  console.log("[+] Hooking call to MainActivity.a");
  var main = Java.use("sg.vantagepoint.uncrackable1.MainActivity");
  main.onStart.overload().implementation = function() {
      send("MainActivity.onStart() HIT");
      console.log("MainActivity.onStart() HIT");
      this.onStart.overload().call(this);
  }
​
  main.a.implementation = function(arg1){
      console.log("MainActivity.a called!");
      console.log(arg1);
  }
​
  console.log("[+] Hooking call to System.exit");
  var exit = Java.use("java.lang.System");
  exit.exit.implementation = function () {
      console.log("System.exit called");
      //send("java.lang.System - exit() bypass ");
  }
​
  console.log("[+] Hooking call to sg.vantagepoint.a.a");
  var aaClass = Java.use("sg.vantagepoint.a.a");
  aaClass.a.implementation = function(arg1, arg2){
      var retval = this.a(arg1, arg2);
      var password = '';
      for (var i=0;i<retval.length; i++){
          password += String.fromCharCode(retval[i]);
      }
      console.log("[*] Decrypted : " + password);
      return retval;
  }
​
  console.log("[+] Hooking call to sg.vantagepoint.uncrackable1.a");
  var aClass = Java.use("sg.vantagepoint.uncrackable1.a");
  aClass.a.implementation = function(arg1){
      console.log("Go to Success");
      this.a(arg1);
      return true;
  }
​
});
'''
​
# '''
# main.onCreate.implementation = function(x) {
#         send("MainActivity.onCreate() HIT");
#         console.log("MainActivity.onCreate() HIT")
#         this.onCreate.overload().call(this);
#     }
​
# console.log("[*] Hooking call to java.io.File");
#     var fileClass = Java.use("java.io.File");
#     fileClass.exists.implementation = function(){
#         var name = fileClass.getName.call(this);
#         console.log(name);
#         if (name.indexOf("su") !== -1){
#             console.log("File Bypass");
#             return false;
#         }
#         return this.exists.call(this);
#     }
# '''
​
​
try:
   device = frida.get_usb_device(timeout=10)
   print(device)
   pid = device.spawn([PACKAGE_NAME])
   print("App is starting ... pid : {}".format(pid))
   process = device.attach(pid)
   device.resume(pid)
   script = process.create_script(jscode)
   script.on('message', on_message)
   print("[+] Helllo world")
   script.load()
   sys.stdin.read()
except Exception as e:
   print(e)

how to hack uncrackable2?

2단계 ㄱㄱ

sg.vantagepoint.uncrackable2.MainActivity

package sg.vantagepoint.uncrackable2;
​
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
import android.os.SystemClock;
import android.support.v7.app.c;
import android.view.View;
import android.widget.EditText;
import owasp.mstg.uncrackable2.R;
import sg.vantagepoint.a.a;
import sg.vantagepoint.a.b;
​
public class MainActivity extends c {
   private CodeCheck m;
​
   static {
       System.loadLibrary("foo");
  }
​
   /* access modifiers changed from: private */
   public void a(String str) {
       AlertDialog create = new Builder(this).create();
       create.setTitle(str);
       create.setMessage("This is unacceptable. The app is now going to exit.");
       create.setButton(-3, "OK", new OnClickListener() {
           public void onClick(DialogInterface dialogInterface, int i) {
               System.exit(0);
          }
      });
       create.setCancelable(false);
       create.show();
  }
​
   private native void init();
​
   /* access modifiers changed from: protected */
   public void onCreate(Bundle bundle) {
       init();
       if (b.a() || b.b() || b.c()) {
           a("Root detected!");
      }
       if (a.a(getApplicationContext())) {
           a("App is debuggable!");
      }
       new AsyncTask<Void, String, String>() {
           /* access modifiers changed from: protected */
           /* renamed from: a */
           public String doInBackground(Void... voidArr) {
               while (!Debug.isDebuggerConnected()) {
                   SystemClock.sleep(100);
              }
               return null;
          }
​
           /* access modifiers changed from: protected */
           /* renamed from: a */
           public void onPostExecute(String str) {
               MainActivity.this.a("Debugger detected!");
          }
      }.execute(new Void[]{null, null, null});
       this.m = new CodeCheck();
       super.onCreate(bundle);
       setContentView((int) R.layout.activity_main);
  }
​
   public void verify(View view) {
       String str;
       String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
       AlertDialog create = new Builder(this).create();
       if (this.m.a(obj)) {
           create.setTitle("Success!");
           str = "This is the correct secret.";
      } else {
           create.setTitle("Nope...");
           str = "That's not it. Try again.";
      }
       create.setMessage(str);
       create.setButton(-3, "OK", new OnClickListener() {
           public void onClick(DialogInterface dialogInterface, int i) {
               dialogInterface.dismiss();
          }
      });
       create.show();
  }
}


foo library를 후킹하는 것

반응형