frida - learn by example

0. Background

0.1 Instrumentation

This process of tracing, profiling, and debugging the execution of an app during runtime is called Instrumentation.

frida can be injected into running processes across multiple platforms. The type of injection will allow us to inspect the state of different objects, variables and execution threads.

For example, to register the parameters that a certain method receives or to modify the value returned by it.

0.2 Installation

Client

$ sudo pip install frida

Server

https://build.frida.re/frida/

$ curl -O https://build.frida.re/frida/android/arm/bin/frida-server
$ adb push frida-server /data/local/tmp/
$ adb shell "chmod 755 /data/local/tmp/frida-server"
$ adb shell "/data/local/tmp/frida-server &"

1. Basics - Commands

# listen on 127.0.0.1:27042 (the default)
$ frida-server

# listen on all interfaces
$ frida-server -l 0.0.0.0

# listen on a specific interface
$ frida-server -l 192.168.1.3

# listen on a specific interface and port
$ frida-server -l 192.168.1.3:1337

# connect to specific IP
$ frida-trace -H 192.168.1.3 -i "open*"

# connect to specific IP/port
$ frida-trace -H 192.168.1.3:1337 -i "open*"
frida-ps -U  
$ frida-trace -i "open" -U com.android.chrome 

# pass the -f option to Frida to let it spawn the process itself.
$ frida-trace -i open -U -f com.android.chrome

# Not recommended. App will get killed after 2 seconds. Use %resume to bring back the app UI.
$ frida -U -f com.android.chrome

# Sometimes it would time out, sometimes doesnt. No idea why.
$ frida -U --no-pause -f com.android.chrome
frida-ls-devices

frida-trace -D <device_id> -f com.apple.AppStore -I libcommonCrypto.dylib

frida-ps -Uai

frida-discover  

2. Basics - How to write payload

2.1 Payloads in Javascript

Tell Java to execute some code for us.

Java.perform(function(){  
  /* 
  ...
  do sth
  ...
  */

})
// obtain a Javascript obj capable of 
// handling interactions with each class
var myClass = Java.use(com.mypackage.name.class)


// Create a new instance of a class
var myClassInstance = myClass.$new();

// Access methods and attributes of the class
var result = myClassInstance.myMethod("param");

// overwrite the implementation of a method
myClass.myMethod.implementation = function(param){  
  // do sth
}

What if we have 3 methods called myMethod:

  1. Requires no parameters
  2. Requires two arrays of bytes
  3. Requires the context of the application and a Boolean value

We need to use overload.

// If two methods of a class have the same name
// you need to use 'overload'

myClass.myMethod.overload().implementation = function(){  
  // do sth
}

myClass.myMethod.overload("[B", "[B").implementation = function(param1, param2) {  
  // do sth
}

myClass.myMethod.overload("android.context.Context", "boolean").implementation = function(param1, param2){  
  // do sth
}

overload有以下几种参数类别:

    .overload()
    .overload('java.lang.String')
    .overload('android.app.Activity')
    .overload('int')
    .overload('[B') // byte array
    .overload('float')
    .overload('android.content.Context')
    .overload('[C')
    .overload('android.content.Context', 'android.view.View')
    .overload('android.app.Activity', 'com.cherrypicks.hsbcpayme.model.object.PayMeNotification')
    .overload('android.content.Context', 'boolean')
    .overload('android.content.Context', 'int')
    .overload('android.content.Context', 'java.lang.String')
    .overload('android.app.Activity', 'int')
    .overload('java.lang.String', 'java.lang.String')
    .overload('android.content.Context', 'android.graphics.Bitmap')
    .overload('java.lang.String', 'java.io.File')
    .overload('android.content.Context', 'java.lang.String', 'java.util.List')
    .overload('java.lang.String', 'java.lang.String', 'java.lang.String')
    .overload('java.lang.String', '[B', '[B')
    .overload('java.lang.String', 'java.lang.String', 'android.content.Context')
    .overload('android.app.Activity', 'com.cherrypicks.hsbcpayme.model.object.PayMeNotification', 'int')
    .overload('[B', '[B', '[B')
    .overload('android.content.Context', 'java.lang.String', 'java.lang.String')
    .overload('android.app.Activity', 'int', 'int', 'int', 'boolean')

2.2 Python Binding

// to inject the Js Code, we must identify the device
// and link a session to the process we want to implement

session = frida.get_usb_device(1).attach("com.your.package.name")

script = session.create_script(javascript)  
script.load()
script.on('message', on_message)


def on_message(message, data):  
  if message['type'] == 'send':
    print(message['payload'])
  elif message['type'] == 'error':
    print(message['stack'])

3. Basics - ways to spawn processes

3.1 Use REPL

➜ frida -U -f com.package.name
     ___
    / _  |   Frida 9.1.18 - A world-class dynamic instrumentation framework
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at http://www.frida.re/docs/home/
Waiting for USB device to appear...  
Spawned `com.package.name`. Use %resume to let the main thread start executing!  
[USB::Samsung SM-G9200::['com.package.name']]-> %resume
[USB::Samsung SM-G9200::['com.package.name']]-> %load exploit.js

3.2 Use Javascript Binding

➜ frida -U -l exploit.js -f com.package.name 

Javascript Template:

setImmediate(function() { //prevent timeout  
    console.log("[*] Starting script");

    Java.perform(function() {

      myClass = Java.use("com.package.name.class.name");
      myClass.implementation = function(v) {
         // do sth.
      }

    })
})

3.3 Use Python Binding

➜ python exploit.py

Python Template:

import frida,sys

def print_result(message):  
            print "[*] %s" %(message)

def on_message(message, data):  
            print_result(message['payload'])

jscode = """

Java.perform(function () {

    // do sth
});
"""

process = frida.get_usb_device(1).attach("com.package.name")

script = process.create_script(jscode)  
script.on('message', on_message)

print "[*] Bruteforcing PIN code"

script.load()  
sys.stdin.read()

4. Examples

4.1 Example 1 - Extend frida-trace [Android]

Tracing API calls in Burp with Frida – Cedric's Cruft 介绍了如何extend 现有 frida-trace的功能. 不过没有看懂.

4.2 Example 2 - Interceptor API [iOS]

mopsled - Log iOS method arguments with Frida 介绍了如何利用 API - Interceptor.

  1. 利用 Hopper分析目标程序的相关 function - sendMessageWithText.
  2. 在 REPL, 利用 frida 查看#1中搜索到的 ObjC.classes.MessageViewController, 探索其用法
  3. 撰写interceptSendMessage.js (Official API Doc)

4.3 Example 3 - Root Detection Bypass, Hook the password [Android]

Hacking Android apps with FRIDA (I, II). Post分为两 Part. 第一 Part 是笔者找到的对于 frida 基本功能介绍得最深刻的文章. 第二 Part 介绍了 Root Detection Bypass, 和 hook the password.

该App是一个crackme,启动的时候,它会检查手机是否已root. 另外password已'hard-code' 在binary之中。

查看source code.

Bypass Root Detection

    Java.perform(function() {

      bClass = Java.use("sg.vantagepoint.uncrackable1.b");
      bClass.onClick.implementation = function(v) {
         console.log("[*] onClick called");
      }
      console.log("[*] onClick handler modified")

    })

Decrypt Password

  aaClass = Java.use("sg.vantagepoint.a.a");
    aaClass.a.implementation = function(arg1, arg2) {
        retval = this.a(arg1, arg2);
        password = ''
        for(i = 0; i < retval.length; i++) {
           password += String.fromCharCode(retval[i]);
        }

        console.log("[*] Decrypted: " + password);
        return retval;
    }

Final Exploit: Github

4.4 Example 4 - Brute Force PIN [Android]

MDSec Blog: Instrumenting Android Applications with Frida 介绍了如何brute force PIN.

NIN: 这个apk跟 4.3 - hacking-android-apps-with-frida-2不一样. hacking-android-apps-with-frida-2 由于是一个 crackme, 密码已写进 binary 中, 通过改写程序的 implementation, 让其自动打印个密码. 这个例子不是 crackme, 不需要写进 binary, 相反, 它是把用户定义的PIN加密后储存在 Local Storage. 由于是加密的, 计算打印出来也没用, 因为你要输入的是加密前的 PIN, 不是加密后的 PIN.

该App会将用户的PIN以加密的形式储存在本地。

查看Source Code.

// com.github.orangegangsters.lollipin.lib.managers.AppLockActivity:282
// this line tells us how to call checkPasscode()

this.mLockManager.getAppLock().checkPasscode(this.mPinCode)  

Brute Force PIN

Java.perform(function () {

    var LockManager = Java.use("com.github.orangegangsters.lollipin.lib.managers.LockManager");
    var LockManagerInstance = LockManager.getInstance();
    var AppLock = LockManagerInstance.getAppLock();

    for(var i=1230; i<1235; i++)
    {
        var result = AppLock.checkPasscode(i+"");
        send(i + ": " + result);
    }
});

Final Exploit: Github

# Brute force
➜  4. brute_force_pin python exploit.py
[*] Bruteforcing PIN code
[*] Testing PIN 1230: false
[*] Testing PIN 1231: false
[*] Testing PIN 1232: false
[*] Testing PIN 1233: false
[*] Testing PIN 1234: true

4.5 Example 5 - Introduction to Frida [MacOS]

Introducción a Frida - SecurityInside.info 西班牙文 - (Google Translate).

典型的crackme. 有别于 mobile, 它利用内存地址, 不是很看得懂.

gcc poc.c  
➜  frida-learning rabin2 -s ./poc
[Symbols]
vaddr=0x100000000 paddr=0x00000000 ord=000 fwd=NONE sz=0 bind=GLOBAL type=FUNC name=__mh_execute_header  
vaddr=0x100000dd0 paddr=0x00000dd0 ord=001 fwd=NONE sz=0 bind=GLOBAL type=FUNC name=_encrypt_arg  
vaddr=0x100000e60 paddr=0x00000e60 ord=002 fwd=NONE sz=0 bind=GLOBAL type=FUNC name=_main  
vaddr=0x100000f1a paddr=0x00000f1a ord=003 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.printf  
vaddr=0x100000f20 paddr=0x00000f20 ord=004 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.scanf  
vaddr=0x100000f26 paddr=0x00000f26 ord=005 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.srand  
vaddr=0x100000f2c paddr=0x00000f2c ord=006 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.strcmp  
vaddr=0x100000f32 paddr=0x00000f32 ord=007 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.strlen  
vaddr=0x100000f38 paddr=0x00000f38 ord=008 fwd=NONE sz=0 bind=LOCAL type=FUNC name=imp.time  
vaddr=0x100000dd0 paddr=0x00000dd0 ord=009 fwd=NONE sz=0 bind=LOCAL type=FUNC name=func.100000dd0  
vaddr=0x100000e60 paddr=0x00000e60 ord=010 fwd=NONE sz=0 bind=LOCAL type=FUNC name=func.100000e60  
// ps 查看 PID on Mac
➜ ps
  PID TTY           TIME CMD
17013 ttys001    0:00.03 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp its  
17015 ttys001    0:00.51 -zsh  
20605 ttys001    0:00.00 ./poc  
15460 ttys002    0:00.03 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp its  
15462 ttys002    0:00.45 -zsh  
18074 ttys003    0:00.03 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp its  
18076 ttys003    0:00.74 -zsh  
20378 ttys003    0:00.04 r2 ./poc  
20007 ttys004    0:00.03 /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp its  
20009 ttys004    0:00.33 -zsh

4.6 Example 6 - Get Encryption Key [Android]

SECCON 2015 – Reverse engineering Android APK 2 – 400 writeup – Cedric's Cruft

该例子中App用了End-to-End Encryption, 当App发送request出去前,App会先加密username & Password.

查看Source Code.

使用AES加密,但是Key是obfuscated.

利用frida,将c.a中的两个parameter print出来,就能获得Encryption Key.

Print Encryption Key

Java.perform(function () {  
    var c = Java.use("kr.repo.h2spice.yekehtmai.c")
    c.a.implementation = function (str1, str2) {
        console.log("String1: " + str1)
        console.log("String2: " + str2)
    }
});

Final Exploit: Github

有了Key之后,我们就可以replicate整个加密过程,对所有的payload先加密,再利用Burp发送出去。

Replicate Encryption: Github

4.7 Example 7 - SSL Pinning Bypass [Android]

该App implement了SSL Pinning. Pin的certificate被加载在Bouncy Castle key store中。

BKS is a keystore format provided by the popular third party Java cryptographic library provider -- BouncyCastle. It is a keystore similar to the JKS provided by Oracle JDK.

查看Source Code.

App 会先把 BKS 中的 certificate 全部 load出来 (this.a()),然后再调用CompositeX509TrustManager class 的checkClientTrustedcheckServerTrusted判断现时使用的 certificate是否包含其中.

将SSLPinning的function - a(Context context, String[] strArr, String str)中所需的三个参数 print 出来, 由此我们可以获得 bks, 以及其 password.

➜  ssl_pinning_bypass python exploit.py
[*] SSLPinning
[*] com.onlycoin.android.CoinApplication@40e5333
[*] coincert.bks,coincert_new.bks,coincert_amz.bks
[*] laggardness287{satisfactoriness
{u'columnNumber': 1, u'description': u"Error: Implementation for a expected return value compatible with 'javax.net.ssl.SSLSocketFactory'.", u'fileName': u'input', u'lineNumber': 1, u'type': u'error', u'stack': u"Error: Implementation for a expected return value compatible with 'javax.net.ssl.SSLSocketFactory'.\n    at a (input:1)"}
'payload'  

Post-Exploit

将 APK 解压, 搜索得知 bks文件存在于apk_unzip/assets.

➜  ssl_pinning_bypass find . -name *.bks
./apk_unzip/assets/coincert.bks
./apk_unzip/assets/coincert_amz.bks
./apk_unzip/assets/coincert_new.bks

查看 bks 里面包含什么.

bcprov-jdk15on-156.jar, 下载地址: https://www.bouncycastle.org/latest_releases.html

Ref: https://goo.gl/eWhzTS

➜  ssl_pinning_bypass keytool -list -v -keystore ./apk_unzip/assets/coincert.bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk15on-156.jar" -storetype BKS -storepass "laggardness287{satisfactoriness"

Keystore type: BKS  
Keystore provider: BC

Your keystore contains 1 entry

Alias name: ca  
Creation date: Apr 15, 2016  
Entry type: trustedCertEntry

Owner: C=US,PostalCode=94107,ST=CA,L=San Francisco,STREET=185 Berry St\, Suite 1510,O=Coin Inc,OU=PremiumSSL Wildcard,CN=*.coin.vc  
Issuer: C=GB,ST=Greater Manchester,L=Salford,O=COMODO CA Limited,CN=COMODO RSA Organization Validation Secure Server CA  
Serial number: 118778e14b7c10d64dca229ea0bbbf3d  
Valid from: Wed Mar 11 08:00:00 CST 2015 until: Mon May 09 07:59:59 CST 2016  
Certificate fingerprints:  
   MD5:  C3:D3:BF:18:AA:9C:B6:87:7A:54:EE:F1:B9:CD:F2:E1
   SHA1: C0:4D:2C:4A:64:4D:45:DF:CB:22:76:24:56:23:86:95:A3:AF:7E:44
   SHA256: 48:EB:AE:3E:FA:DA:5A:01:D8:4F:5E:FD:2B:D5:81:F8:CA:A4:AD:0F:52:01:50:5E:5F:17:E4:5B:56:E9:99:37
   Signature algorithm name: SHA256WITHRSA
   Version: 3


*******************************************
*******************************************

我们要做的就是将 Burp 的 certificate加入其中.

Ref: https://goo.gl/ieS2xY

keytool -importcert -v -trustcacerts -file "my_certificate_pathmy_certificate.cer" -alias myAlias -keystore "my_keystore_path/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "provider_path/bcprov-jdkxx-xxx.jar" -storetype BKS -storepass "my_password"

$ keytool -importcert -v -trustcacerts -file /path/to/burp.cer -providerpath /path/to/bcprov-jdk15on-156.jar -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storetype BKS -keystore /path/to/coincert.bks

然后再 repack App, 重新安装即可.

Final Exploit: Github

4.8 Sieve

Pentesting Android Apps Using Frida - NotSoSecure


5. Troubleshooting

5.1 Time out

当出现time out 的情况时,

➜  frida-learning python chrome.py
Traceback (most recent call last):  
  File "chrome.py", line 21, in <module>
    session = frida.get_usb_device().attach("com.android.chrome")
  File "/Library/Python/2.7/site-packages/frida/__init__.py", line 72, in get_usb_device
    return _get_device(lambda device: device.type == 'tether', timeout)
  File "/Library/Python/2.7/site-packages/frida/__init__.py", line 103, in _get_device
    raise TimedOutError("timed out while waiting for device to appear")
frida.TimedOutError: timed out while waiting for device to appear  

查看是否使用

frida.get_usb_device(1)  

官方的教程是

frida.get_usb_device()  

v9+ 似乎API已更改。

其次, 可以尝试

// Your Frida client talks to the frida-server over a TCP socket so next you need to forward the relevant TCP ports over the USB connection (or wifi if you prefer) using ADB:

$ adb forward tcp:27043 tcp:27043
$ adb forward tcp:27042 tcp:27042

6. Using frida on Android without root

Using Frida on Android without root · John Kozyrakis ~ blog 介绍了如何 manual 添加frida-gadget到 apk 中. 重新打包的 apk 自动开启 tcp socket, 等待 frida 连接, 其实质就是 repackage attack.

manual 工作漫长, 容易出错, 幸好 appmon的 apk_builder 已经将流程自动化.

Watch on YouTube

AppMon APK Builder is a part of the AppMon project, it let's you build APK files that can be installed on both 32-bit & 64-bit non-rooted devices. Tested on macOS 10.12.4 and supports the latest Android 7.0 (Nougat).

6.1 安装

参考官方 wiki - 1 & 2

sudo -H pip install argparse frida flask termcolor dataset  

安装android-sdk, 并将 $ANDROID_HOME/build-tools, $ANDROID_HOME/platform-tools, $ANDROID_HOME/tools加到$PATH之中.

6.2 打包

python apk_builder.py --apk /path/to/your/app.apk  

6.3 运行

运行 app, adb shell登陆到device 上, 运行netstat -ln, 可以看到它开启了一个 localhost:27042的 tcp socket.

➜  ~ adb shell
shell@zerofltechn:/ $ netstat -ln  
Active Internet connections (only servers)  
Proto Recv-Q Send-Q Local Address           Foreign Address         State  
tcp        0      0 127.0.0.1:27042         0.0.0.0:*               LISTEN  
raw        0      0 :::58                   :::*                    7  

NOTE: 此时我们追寻的 process name 是Gadget.

➜  frida-ps -U
Waiting for USB device to appear...  
 PID  Name
----  ------
9756  Gadget  
➜ frida -U Gadget
     __
    / _  |   Frida 9.1.18 - A world-class dynamic instrumentation framework
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at http://www.frida.re/docs/home/

[USB::Samsung SM-G9200::Gadget]-> %resume

此时开启 app,会出现一个白屏.


7. Mitigation

Anti-Frida (Android)


8. Bonus

Byte to String

function bin2string(array){  
        var result = "";
        for(var i = 0; i < array.length; ++i){
            result+= (String.fromCharCode(array[i]));
        }
        return result;
    }

Ref