Mobile Application Hacking Diary Ep.2

Android Patching

利用Jadx, dex2jar-jdgui, Bytecode Viewer等工具 decompile App.

NIN: 作者推荐 Bytecode Viewer.


// APKTool
zeq3ul@home:~/Desktop$ java -jar apktool.jar d vulnapp.apk

  • 改 Smali
  • Using apktool to build new app
  • Signing the app using Apk Sign which can automatically sign an apk with the Android test certificate that embed public and private keys within jar.

Android Debugging

Patching App Bypass Root Detection 实例.

NIN: android:debuggable="true" must be set on AndroidManifest.xml. Otherwise, repack your app.

利用工具: AndBug.

AndBug 基本使用介绍

zeq3ul@home:~/Desktop$ adb shell ps -x | grep -i "vulnapp"
u0_a66    1859  57    224432 29428 ffffffff b6ecc5cc S com.example.vulnapp (u:1381, s:277)
zeq3ul@home:~/Desktop$ andbug shell -p 1859
## AndBug (C) 2011 Scott W. Dunlop <swdunlop@gmail.com>

>> classes com.example.vulnapp
## Loaded Classes
        -- com.example.vulnapp.LibraryProvider
        -- com.example.vulnapp.Extend_Main
        -- com.example.vulnapp.Extend_Main$1
        -- com.example.vulnapp.MainActivity

>> ct com.example.vulnapp.MainActivity
## Setting Hooks
        -- Hooked com.example.vulnapp.MainActivity

>> ## trace thread <1> main        (running suspended)
        -- com.example.vulnapp.MainActivity.checkRoot()V:0
        -- this=Lcom/example/vulnapp/MainActivity; <831928579104>
        ...

  • 利用classes command, 可以知道current activity加载了什么 classes.
  • 利用ct command, 可以trace 某个 class 的method.

AndBug 与 Decompiler 的变换

上面的例子中 (Line 18), 可以看到com.example.vulnapp.MainActivity.checkRoot()被调用来检查 root 的情况.

Decompile App 看到其调用 RootWine Library.

// MainActivity.class

...
RootWine rootWine;
public void checkRoot()
  {
  this.rootWine = new RootWine(this);
     if (this.rootWine.isRooted()) {
        //Pop-up toast message then app will not continue
     }
     for (;;) {
        return;
        //App will continue
     }
   }
...

这一次, 我们再利用 AndBug 来 debug app, 不过这次我们关注的是com.johny.rootwine.

zeq3ul@home:~/Desktop$ andbug shell -p 1859
## AndBug (C) 2011 Scott W. Dunlop <swdunlop@gmail.com>
>> classes com.johny.rootwine
## Loaded Classes
-- com.johny.rootwine.util.QLog
-- com.johny.rootwine.Const
-- com.johny.rootwine.RootWine
>> ct com.johny.rootwine.RootWine
## Setting Hooks
-- Hooked com.johny.rootwine.RootWine
>> ## trace thread <1> main        (running suspended)
-- com.johny.rootwine.RootWine.<init>(Landroid/content/Context;)V:0
  -- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main        (running suspended)
-- com.johny.rootwine.RootWine.isRooted()Z:0
  -- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main        (running suspended)
-- com.johny.rootwine.RootWine.detectRootManagementApps()Z:0
  -- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main        (running suspended)
-- com.johny.rootwine.RootWine.isAnyPackageFromListInstalled(Ljava/util/List;)Z:0
  -- this=Lcom/johny/rootwine/RootWine; <831930596104>
  -- packages=Ljava/util/ArrayList; <831930576248>
...
## trace thread <1> main        (running suspended)
-- com.johny.rootwine.RootWine.detectPotentiallyDangerousApps()Z:0
  -- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main        (running suspended)
-- com.johny.rootwine.RootWine.checkForBinary(Ljava/lang/String;)Z:0
  -- this=Lcom/johny/rootwine/RootWine; <831930596104>
  -- filename=su
...

当我们 trace class - com.johny.rootwine.RootWine, 我们发现了method - isRooted() (Line 16). 明显它是用于检查 root 的状态.

接着我们又再 decompile app, 看一看class - com.johny.rootwine.RootWine.

// RootWine.class
...
public boolean isRooted()
  {
    if ((detectRootManagementApps()) || (detectPotentiallyDangerousApps()) || (checkForBinary("su")) || (checkForBinary("busybox")) || (checkForDangerousProps()) || (checkForRWPaths()) || (detectTestKeys()) || (checkSuExists()) || (checkForRootNative())) {}
    for (boolean bool = true;; bool = false) {
      return bool;
    }
  }
...

NIN: 整个过程就是不断地重复 AndBug 与 Decompiler 的过程.

JDB的介入

利用 JDB, 我们可以 attach 到app process 中, 之后设置 breakpoint.

zeq3ul@home:~/Desktop$ adb forward tcp:1337 jdwp:1859
zeq3ul@home:~/Desktop$ jdb -attach localhost:1337


Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...

> stop in com.johny.rootwine.RootWine.isRooted
Set breakpoint com.johny.rootwine.RootWine.isRooted

> Breakpoint hit: "thread=<1> main", com.johny.rootwine.RootWine.isRooted(), line=43 bci=0
<1> main[1] step
Step completed: <1> main[1] "thread=<1> main", com.johny.rootwine.RootWine.detectRootManagementApps(), line=76 bci=0
<1> main[1] step
Step completed: "thread=<1> main", com.johny.rootwine.RootWine.detectRootManagementApps(), line=87 bci=0
<1> main[1]
...

<1> main[1] step
Step completed: "thread=<1> main", com.johny.rootwine.RootWine.checkForBinary(), line=170 bci=0

<1> main[1] locals
Method arguments:
f = instance of java.io.File(id=831931279824)
Local variables:
filename = "su"
pathsArray = instance of java.lang.String[11] (id=831930532976)
result = false
path = "/system/xbin/"
completePath = "/system/xbin/su"
fileExists = true
<1> main[1]

利用 command - locals (Line 23), 我们可以看到 variables 的 value. 如Line 32的值.

我们还可以改变 variables 的 value, 如Line 1. 当改变 value后, 我们可以resume process (Line 13). 至此, 我们就 Bypass 到 Root Detection 了.

<1> main[1] set fileExists=false
 fileExists=false
<1> main[1] locals
Method arguments:
f = instance of java.io.File(id=831931279824)
Local variables:
filename = "su"
pathsArray = instance of java.lang.String[11] (id=831930532976)
result = false
path = "/system/xbin/"
completePath = "/system/xbin/su"
fileExists = false
<1> main[1] resume

Android - Dynamic Instrumentation

Also, we may use frida-tracing to identify used class/method then conduct static analysis to patch the application permanently.

上面提及的 debugging 技巧实属费时费力. 利用 frida, 效率可以加快.

raptor_frida_android_trace.js

用 Frida 测试同一个 App.

zeq3ul@home:~/Desktop$ frida -U -f com.example.vulnapp -l raptor_frida_android_trace.js --no-pause

[REDACTED]

Spawned `com.example.vulnapp`. Resuming main thread!
[Android Emulator 5554::com.example.vulnapp]-> Tracing com.example.vulnapp.LibraryProvider.onCreate [1 overload(s)]
Tracing com.example.vulnapp.MainActivity.onCreate [1 overload(s)]
Tracing com.example.vulnapp.MainActivity.init [1 overload(s)]
...
Tracing com.johny.rootwine.RootWine.isRooted [1 overload(s)]
Tracing com.johny.rootwine.RootWine.detectRootManagementApps [2 overload(s)]
...
*** entered com.example.vulnapp.MainActivity.checkRoot
*** entered com.johny.rootwine.RootWine.isRooted
*** entered com.johny.rootwine.RootWine.detectRootManagementApps
*** entered com.johny.rootwine.RootWine.detectRootManagementApps
arg[0]: null
*** entered com.johny.rootwine.RootWine.checkForBinary
arg[0]: su
retval: true
*** exiting com.johny.rootwine.RootWine.checkForBinary
retval: true

可以看到当 App 开启的时候, script 会知道所有app activities load了的 classes/methods, 包括 arguments, 以及返回的值.

上面的例子可以看到, com.johny.rootwise被 load 了. 用 frida script 篡改 isRooted()的值, 就可以 bypass了.

NIN: Bonus - fridantiroot 包含了多种 bypass root detection 的方法.

# frida --codeshare dzonerzy/fridantiroot -U -f com.example.vulnapp

Android - SSL Pinning Bypass

Objection

# objection -g explore
# android sslpinning disable

Frida Script

Universal frida script

$ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt
$ adb shell "/data/local/tmp/frida-server &"
$ frida --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida -U -f com.example.vulnapp --no-pause

Android - End-2-End Encryption

除了 SSL Pinning 之外, 有时候 App 还会用到 End-2-End Encryption.

  1. 利用raptor_frida_android_trace.js Trace classes和 methods
  2. 找到疑似 encrypt/decrypt class
  3. Convert the arguments object into string through "JSON.stringify()"
  4. 替换加密的字符串

具体例子参见Ref.

Ref

Mobile Application Hacking Diary Ep.2

Extended Reading

Mobile Application Hacking Diary Ep.1

Show Comments