日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Frida Android hook

發布時間:2024/7/23 Android 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Frida Android hook 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

From:https://eternalsakura13.com/2020/07/04/frida/

目錄

1、r0ysue 大佬

2、Frida 環境

2.1 pyenv

2.2 frida 安裝

2.3 安裝 objection

2.4 frida 使用

frida 幫助 和 frida-server 幫助

2.5 frida 開發環境搭建

3、FRIDA 基礎

3.1 frida 查看當前存在的進程

3.2 frida 打印參數和修改返回值

3.3 frida 尋找 instance,主動調用。

3.4 frida rpc

3.5 frida 動態修改

3.6 API List

4、Frida 動靜態結合分析

4.1 Objection

objection 之 啟動并注入內存

objection 之 memory 命令

memory list modules ( 查看加載的 so 庫?)

memory list exports ( 查看 so 庫的導出函數表?)

memory dump(dump 內存空間)? ? ? ?

memory search(搜索 內存空間)

objection 之 android 命令

android heap search instances 類名

android heap execute 實例ID 實例方法

android hooking list activities/services

android intent launch_activity/launch_service activity/服務

android hooking list classes

android hooking search classes display

android hooking list class_methods 類名

android hooking search methods display

hook 類的方法(hook 類里的所有方法 / 具體某個方法)

grep trick 和 文件保存( objection log )

4.2 案例學習

案例學習 1:《仿VX數據庫原型取證逆向分析》

案例學習 2:主動調用爆破密碼

5、Frida hook 基礎(一)

5.1 Frida hook : 打印參數、返回值 / 設置返回值 / 主動調用

5.2 Frida hook : 主動調用靜態/非靜態函數 以及 設置靜態/非靜態成員變量的值

5.3 Frida hook : 內部類,枚舉類的方法?并 hook,trace原型1

5.4 Frida hook : hook 動態加載的 dex,與查找 interface,

5.5 Frida hook : 枚舉 class,trace原型2

frida rpc 枚舉 實現 接口的類

5.6 Frida hook : 搜索 interface 的具體實現類

6、Frida hook 基礎(二)

6.1 spawn / attach

6.2 Frida hook : hook構造函數/打印棧回溯

6.3 Frida hook : 打印棧回溯

6.4 Frida hook : 手動加載 dex 并調用

7、Frida 打印 與 參數構造

gson 打印 Java 對象的內容

char[] / [Object Object]

byte[]

java array 構造

類的多態:轉型 / Java.cast

interface / Java.registerClass

成員內部類 / 匿名內部類

hook enum

打印 hash map

打印 non-ascii

8、Frida native hook : NDK 開發入門

9、Frida native hook : JNIEnv 和 反射

9.1 以 jni字符串 來掌握基本的 JNIEnv用法

9.2 Java 反射

10、Frida 反調試 與 反反調試

11、Frida native hook : 符號 hook JNI、art&libc

11.1 Native函數的Java Hook及主動調用

11.2 jni.h 頭文件導入

11.3 JNI 函數符號 hook

11.4 JNI 函數參數、返回值打印和替換

12、Frida native hook : JNI_Onload / 動態注冊 / inline_hook / native層調用棧打印

12.1 JNI_Onload / 動態注冊原理

12.2 Frida hook RegisterNative

12.3 native 層調用棧打印

12.4 主動調用去進行方法參數替換

12.5 inline hook ( so庫里面的函數?)

13、Frida native hook : Frida hook native app 實戰

14、Frida trace 四件套

14.1 jni trace : trace jni

14.2 strace : trace syscall

14.3 frida-trace : trace libc(or more)

art trace

14.4 hook_artmethod : trace java 函數調用

14.5 修改AOSP源碼打印

15、Frida native hook : init_array 開發和自動化逆向

15.1 init_array原理 (?so 加載、啟動、執行 )

15.2 IDA靜態分析 init_array

15.3 IDA 動態調試 so

15.4 init_array && JNI_Onload "自吐"

JNI_Onload

init_array

15.5 native層未導出函數主動調用(任意符號和地址)

16、C/C++ hook

16.1 Native/JNI層參數打印和主動調用參數構造

16.2 C/C++編成 so 并引入 Frida 調用其中的函數


1、r0ysue 大佬

這篇文章完全來源于 r0ysue 的知識星球,推薦下大佬的星球

2、Frida 環境

github 地址:https://github.com/frida/frida

2.1 pyenv

python 全版本隨機切換,這里提供?macOS上的配置方法

brew update brew install pyenv echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bash_profile下載一個3.8.2,下載真的很慢,要慢慢等 pyenv install 3.8.2pyenv versions sakura@sakuradeMacBook-Pro:~$ pyenv versionssystem * 3.8.2 (set by /Users/sakura/.python-version) 切換到我們裝的 pyenv local 3.8.2 python -V pip -V 原本系統自帶的 python local system python -V

另外當你需要臨時禁用 pyenv 的時候

把這個注釋了然后另開終端就好了。關于卸載某個 python 版本

Uninstalling Python Versions As time goes on, you will accumulate Python versions in your $(pyenv root)/versions directory.To remove old Python versions, pyenv uninstall command to automate the removal process.Alternatively, simply rm -rf the directory of the version you want to remove. You can find the directory of a particular Python version with the pyenv prefix command, e.g. pyenv prefix 2.6.8.

2.2 frida 安裝

如果直接按下述安裝則會直接安裝 frida 和 frida-tools 的最新版本。

pip install frida pip install frida-tools frida --version frida-ps --version

也可以通過 版本號 安裝舊版本的 frida,例如 12.8.0

pyenv install 3.7.7 pyenv local 3.7.7 pip install frida==12.8.0 pip install frida-tools==5.3.0

老版本 frida 和 對應關系,對應關系很好找:

2.3 安裝 objection

安裝命令:

pyenv local 3.8.2 pip install objection objection -hpyenv local 3.7.7 pip install objection==1.8.4 objection -h

2.4 frida 使用

下載 frida-server 并解壓,在這里下載 frida-server :https://github.com/frida/frida/releases

先 adb shell,然后切換到 root 權限,把之前 push 進來的 frida server 改個名字叫 fs,然后運行 frida

adb push /tmp/frida-server-12.8.0-android-arm64 /data/local/tmp mv frida-server-12.8.0-android-arm64 fs chmod 777 fs ./fs

如果要監聽端口,就

./fs -l 0.0.0.0:8888

frida 幫助 和 frida-server 幫助

frida --help

frida-server --help

2.5 frida 開發環境搭建

  • 安裝
    ? ? ? ? git clone https://github.com/oleavr/frida-agent-example.git
    ? ? ? ? cd frida-agent-example/
    ? ? ? ? npm install
  • 使用 vscode 打開此工程,在 agent 文件夾下編寫 js,會有智能提示。
  • npm run watch?會監控代碼修改自動編譯生成 js 文件
  • python 腳本或者 cli 加載 _agent.js 命令:frida -U -f com.example.android --no-pause -l _agent.js
  • 下面是測試腳本

    s1.js

    function main() {Java.perform(function x() {console.log("sakura")}) } setImmediate(main)

    loader.py

    import time import fridadevice8 = frida.get_device_manager().add_remote_device("192.168.0.9:8888") pid = device8.spawn("com.android.settings") device8.resume(pid) time.sleep(1) session = device8.attach(pid) with open("si.js") as f:script = session.create_script(f.read()) script.load() input() #等待輸入

    解釋一下:這個腳本就是先通過 frida.get_device_manager().add_remote_device 來找到 device,然后以 spawn 方式啟動 settings,然后 attach 到上面,并執行 frida 腳本。

    當代碼里面沒有指定端口時,需要手動轉發端口:adb forward tcp:27042 tcp:27042

    import sys import time import fridajs_code = ''' function main() {Java.perform(function x() {console.log("sakura")}) } setImmediate(main); '''def on_message(msg, data):if msg['type'] == 'send':print(f'[*] {msg["payload"]}')else:print(msg)if __name__ == '__main__':select = 1if 1 == select:# ########################### 會自動重啟 app ############################ 會自動重啟 appdevice = frida.get_remote_device()pid = device.spawn(["com.android.settings"])device.resume(pid)time.sleep(1)process = device.attach("com.android.settings")script = process.create_script(js_code)script.load()input('按任意鍵繼續') # 等待輸入elif 2 == select:# ############ 需要先手動啟動 app , 然后才能執行腳本進行 hook ############## get_remote_device 獲取遠程設備 (get_usb_device)  attach 附加進程process = frida.get_remote_device().attach('com.android.settings')script = process.create_script(js_code)script.on('message', on_message) # 綁定 js 回調script.load()sys.stdin.read()pass

    運行結果:

    3、FRIDA 基礎

    3.1 frida 查看當前存在的進程

    frida-ps 命令:

    frida-ps -U?查看通過 usb 連接的 android 手機上的進程。可以通過 grep 過濾就可以找到我們想要的包名。

    sakura@sakuradeMacBook-Pro:~$ frida-ps -UPID Name ----- ---------------------------------------------------3640 ATFWD-daemon707 adbd728 adsprpcd 26041 android.hardware.audio@2.0-service741 android.hardware.biometrics.fingerprint@

    3.2 frida 打印參數和修改返回值

    package myapplication.example.com.frida_demo;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log;public class MainActivity extends AppCompatActivity {private String total = "@@@###@@@";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}fun(50,30);Log.d("sakura.string" , fun("LoWeRcAsE Me!!!!!!!!!"));}}void fun(int x , int y ){Log.d("sakura.Sum" , String.valueOf(x+y));}String fun(String x){total +=x;return x.toLowerCase();}String secret(){return total;} }

    注入的 js 代碼:

    function main() {console.log("Enter the Script!");Java.perform(function x() {console.log("Inside Java perform");var MainActivity = Java.use("myapplication.example.com.frida_demo.MainActivity");// 重載找到指定的函數MainActivity.fun.overload('java.lang.String').implementation = function (str) {//打印參數console.log("original call : str:" + str);//修改結果var ret_value = "sakura";return ret_value;};}) } setImmediate(main);

    查看設備。(?-f 是通過 spawn,也就是重啟 apk 注入 js

    sakura@sakura:~$ frida-ps -U | grep frida 8738 frida-helper-32 8897 myapplication.example.com.frida_demo// -f 是通過 spawn,也就是重啟 apk 注入 js sakura@sakura:~$ frida -U -f myapplication.example.com.frida_demo -l frida_demo.js ... original call : str:LoWeRcAsE Me!!!!!!!!! 12-21 04:46:49.875 9594-9594/myapplication.example.com.frida_demo D/sakura.string: sakura

    3.3 frida 尋找 instance,主動調用。

    function main() {console.log("Enter the Script!");Java.perform(function x() {console.log("Inside Java perform");var MainActivity = Java.use("myapplication.example.com.frida_demo.MainActivity");// overload 選擇被重載的對象MainActivity.fun.overload('java.lang.String').implementation = function (str) {//打印參數console.log("original call : str:" + str);//修改結果var ret_value = "sakura";return ret_value;};// 尋找類型為 classname 的實例Java.choose("myapplication.example.com.frida_demo.MainActivity", {onMatch: function (x) {console.log("find instance :" + x);console.log("result of secret func:" + x.secret());},onComplete: function () {console.log("end");}});}); } setImmediate(main);

    3.4 frida rpc

    function callFun() {Java.perform(function fn() {console.log("begin");Java.choose("myapplication.example.com.frida_demo.MainActivity", {onMatch: function (x) {console.log("find instance :" + x);console.log("result of fun(string) func:" + x.fun(Java.use("java.lang.String").$new("sakura")));},onComplete: function () {console.log("end");}})}) } rpc.exports = {callfun: callFun };

    Python 調用:

    import time import fridadevice = frida.get_usb_device() pid = device.spawn(["myapplication.example.com.frida_demo"]) device.resume(pid) time.sleep(1) session = device.attach(pid) with open("frida_demo_rpc_call.js") as f:script = session.create_script(f.read())def my_message_handler(message, payload):print(message)print(payload)script.on("message", my_message_handler) script.load()script.exports.callfun()

    執行:

    sakura@sakura:~/frida-agent-example/agent$ python frida_demo_rpc_loader.py begin find instance :myapplication.example.com.frida_demo.MainActivity@1d4b09d result of fun(string):sakura end

    3.5 frida 動態修改

    即將手機上的 app 的內容發送到 PC 上的 frida python 程序,然后處理后返回給 app,然后 app 再做后續的流程,核心是理解?send/recv?函數。

    <TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="please input username and password"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><EditTextandroid:id="@+id/editText"android:layout_width="fill_parent"android:layout_height="40dp"android:hint="username"android:maxLength="20"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="1.0"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.095" /><EditTextandroid:id="@+id/editText2"android:layout_width="fill_parent"android:layout_height="40dp"android:hint="password"android:maxLength="20"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.239" /><Buttonandroid:id="@+id/button"android:layout_width="100dp"android:layout_height="35dp"android:layout_gravity="right|center_horizontal"android:text="提交"android:visibility="visible"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.745" /> public class MainActivity extends AppCompatActivity {EditText username_et;EditText password_et;TextView message_tv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);password_et = (EditText) this.findViewById(R.id.editText2);username_et = (EditText) this.findViewById(R.id.editText);message_tv = ((TextView) findViewById(R.id.textView));this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (username_et.getText().toString().compareTo("admin") == 0) {message_tv.setText("You cannot login as admin");return;}//hook targetmessage_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));}});} }

    先分析問題,我的最終目標是讓 message_tv.setText 可以”發送”username為admin的base64字符串。
    那肯定是 hook TextView.setText 這個函數。

    console.log("Script loaded successfully "); Java.perform(function () {var tv_class = Java.use("android.widget.TextView");tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) {var string_to_send = x.toString();var string_to_recv;send(string_to_send); // send data to python coderecv(function (received_json_object) {string_to_recv = received_json_object.my_dataconsole.log("string_to_recv: " + string_to_recv);}).wait(); //block execution till the message is receivedvar my_string = Java.use("java.lang.String").$new(string_to_recv);this.setText(my_string);} });

    Python 腳本:

    import time import frida import base64def my_message_handler(message, payload):print(message)print(payload)if message["type"] == "send":print(message["payload"])data = message["payload"].split(":")[1].strip()print( 'message:', message)#data = data.decode("base64")#data = datadata = str(base64.b64decode(data))print( 'data:',data)user, pw = data.split(":")print( 'pw:',pw)#data = ("admin" + ":" + pw).encode("base64")data = str(base64.b64encode(("admin" + ":" + pw).encode()))print( "encoded data:", data)script.post({"my_data": data}) # send JSON objectprint( "Modified data sent")device = frida.get_usb_device() pid = device.spawn(["myapplication.example.com.frida_demo"]) device.resume(pid) time.sleep(1) session = device.attach(pid) with open("frida_demo2.js") as f:script = session.create_script(f.read()) script.on("message", my_message_handler) script.load() input()

    執行和輸出:

    sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ python frida_demo_rpc_loader2.py Script loaded successfully {'type': 'send', 'payload': 'Sending to the server :c2FrdXJhOjEyMzQ1Ng==\n'} None Sending to the server :c2FrdXJhOjEyMzQ1Ng==message: {'type': 'send', 'payload': 'Sending to the server :c2FrdXJhOjEyMzQ1Ng==\n'} data: b'sakura:123456' pw: 123456' encoded data: b'YWRtaW46MTIzNDU2Jw==' Modified data sent string_to_recv: b'YWRtaW46MTIzNDU2Jw=='

    參考鏈接:https://github.com/Mind0xP/Frida-Python-Binding

    3.6 API List

    • Java.choose(className: string, callbacks: Java.ChooseCallbacks): void
      通過掃描 Java VM 的堆來枚舉 className類 的 live instance。

    • Java.use(className: string): Java.Wrapper<{}>
      動態為 className 生成 JavaScript Wrappe r,可以通過調用$new()來調用構造函數來實例化對象。
      在實例上調用?$dispose()?以對其進行顯式清理,或者等待JavaScript對象被gc。

    • Java.perform(fn: () => void): void
      Function to run while attached to the VM.
      Ensures that the current thread is attached to the VM and calls fn. (This isn’t necessary in callbacks from Java.)
      Will defer calling fn if the app’s class loader is not available yet. Use Java.performNow() if access to the app’s classes is not needed.

    • send(message: any, data?: ArrayBuffer | number[]): void
      任何JSON可序列化的值。
      將JSON序列化后的message發送到您的基于Frida的應用程序,并包含(可選)一些原始二進制數據。
      The latter is useful if you e.g. dumped some memory using NativePointer#readByteArray().

    • recv(callback: MessageCallback): MessageRecvOperation
      Requests callback to be called on the next message received from your Frida-based application.
      This will only give you one message, so you need to call recv() again to receive the next one.

    • wait(): void
      堵塞,直到message已經receive并且callback已經執行完畢并返回

    4、Frida 動靜態結合分析

    4.1 Objection

    • 參考這篇文章:實用FRIDA進階:內存漫游、hook anywhere、抓包https://www.anquanke.com/post/id/197657
    • objection:https://pypi.org/project/objection/

    objection 之 啟動并注入內存

    ?命令:objection -d -g package_name exploresakura@sakura:~$ objection -d -g com.android.settings explore [debug] Agent path is: /Users/sakura/.pyenv/versions/3.7.7/lib/python3.7/site-packages/objection/agent.js [debug] Injecting agent... Using USB device `Google Pixel` [debug] Attempting to attach to process: `com.android.settings` [debug] Process attached! Agent injected and responds ok!_ _ _ ____| |_|_|___ ___| |_|_|___ ___ | . | . | | -_| _| _| | . | | |___|___| |___|___|_| |_|___|_|_||___|(object)inject(ion) v1.8.4Runtime Mobile Explorationby: @leonjza from @sensepost[tab] for command suggestions com.android.settings on (google: 8.1.0) [usb] #

    objection 之 memory 命令

    memory list modules ( 查看加載的 so 庫?)

    modules 就是指的 so 庫

    查看內存中加載的 module,命令:memory list modulescom.android.settings on (google: 8.1.0) [usb] # memory list modules Save the output by adding `--json modules.json` to this command Name Base Size Path ----------------------- ------------ -------------------- ------------------------------------ app_process64 0x64ce143000 32768 (32.0 KiB) /system/bin/app_process64 libandroid_runtime.so 0x7a90bc3000 1990656 (1.9 MiB) /system/lib64/libandroid_runtime.so libbinder.so 0x7a9379f000 557056 (544.0 KiB) /system/lib64/libbinder.so

    memory list exports ( 查看 so 庫的導出函數表?)

    查看庫的導出函數:memory list exports libssl.socom.android.settings on (google: 8.1.0) [usb] # memory list exports libssl.so Save the output by adding `--json exports.json` to this command Type Name Address -------- ----------------------------------------------------- ------------ function SSL_use_certificate_ASN1 0x7c8ff006f8 function SSL_CTX_set_dos_protection_cb 0x7c8ff077b8 function SSL_SESSION_set_ex_data 0x7c8ff098f4 function SSL_CTX_set_session_psk_dhe_timeout 0x7c8ff0a754 function SSL_CTX_sess_accept 0x7c8ff063b8 function SSL_select_next_proto 0x7c8ff06a74

    memory dump(dump 內存空間? ? ? ?

    memory dump all 文件名 memory dump from_base 起始地址 字節數 文件

    memory search(搜索 內存空間

    用法:memory search "<pattern eg: 41 41 41 ?? 41>" (--string) (--offsets-only)

    objection 之 android 命令

    android heap search instances 類名

    在內存堆上搜索類的實例 ,命令:android heap search instances 類名

    sakura@sakura:~$ objection -g myapplication.example.com.frida_demo explore Using USB device `Google Pixel` Agent injected and responds ok![usb] # android heap search instances myapplication.example.com.frida_demo .MainActivity Class instance enumeration complete for myapplication.example.com.frida_demo.MainActivity Handle Class toString() -------- ------------------------------------------------- --------------------------------------------------------- 0x2102 myapplication.example.com.frida_demo.MainActivity myapplication.example.com.frida_demo.MainActivity@5b1b0af

    android heap execute 實例ID 實例方法

    調用實例的方法, 命令:android heap execute 實例ID 實例方法

    android hooking list activities/services

    查看當前可用的 activity 或者 service 命令:android hooking list activities/servicescom.android.settings on (google: 8.1.0) [usb] # android hooking list services com.android.settings.SettingsDumpService com.android.settings.TetherService com.android.settings.bluetooth.BluetoothPairingService

    android intent launch_activity/launch_service activity/服務

    直接啟動 activity 或者服務 命令:android intent launch_activity/launch_service activity/服務示例:這個命令比較有趣的是用在如果有些設計的不好,可能就直接繞過了密碼鎖屏等直接進去。 android intent launch_activity com.android.settings.DisplaySettings

    android hooking list classes

    列出內存中所有的類 :android hooking list classes

    android hooking search classes display

    在內存中所有已加載的類中搜索包含特定關鍵詞的類 命令:android hooking search classes display //搜索類中包含 display 的類com.android.settings on (google: 8.1.0) [usb] # android hooking search classes display [Landroid.icu.text.DisplayContext$Type; [Landroid.icu.text.DisplayContext; [Landroid.view.Display$Mode; android.hardware.display.DisplayManager android.hardware.display.DisplayManager$DisplayListener android.hardware.display.DisplayManagerGlobal

    android hooking list class_methods 類名

    內存中搜索指定類的所有方法 命令:android hooking list class_methods 類名com.android.settings on (google: 8.1.0) [usb] # android hooking list class_methods java.nio.charset.Charset private static java.nio.charset.Charset java.nio.charset.Charset.lookup(java.lang.String) private static java.nio.charset.Charset java.nio.charset.Charset.lookup2(java.lang.String) private static java.nio.charset.Charset java.nio.charset.Charset.lookupViaProviders(java.lang.String)

    android hooking search methods display

    在內存中所有已加載的類的方法中搜索包含特定關鍵詞的方法

    命令:?android hooking search methods display

    com.android.settings on (google: 8.1.0) [usb] # android hooking search methods display Warning, searching all classes may take some time and in some cases, crash the target application. Continue? [y/N]: y Found 5529 classes, searching methods (this may take some time)... android.app.ActionBar.getDisplayOptions android.app.ActionBar.setDefaultDisplayHomeAsUpEnabled android.app.ActionBar.setDisplayHomeAsUpEnabled

    hook 類的方法(hook 類里的所有方法 / 具體某個方法)

    • android hooking watch class 類名
      ? ? 這樣就可以 hook 這個類里面的所有方法,每次調用都會被 log 出來。
    • android hooking watch class 類名 --dump-args --dump-backtrace --dump-return
      ? ? 在上面的基礎上,額外 dump 參數,棧回溯,返回值
      ? ? 示例:android hooking watch class xxx.MainActivity --dump-args --dump-backtrace --dump-return
    • android hooking watch class_method 方法名
      ? ? ? ? // 可以直接 hook 到所有重載
      ? ? ? ? android hooking watch class_method xxx.MainActivity.fun --dump-args --dump-backtrace --dump-return

    grep trick 和 文件保存( objection log )

    objection log 默認是不能用 grep 過濾的,但是可以通過 objection run xxx | grep yyy 的方式,從終端通過管道來過濾。用法如下

    sakura@sakura:~$ objection -g com.android.settings run memory list modules | grep libc Warning: Output is not to a terminal (fd=1). libcutils.so 0x7a94a1c000 81920 (80.0 KiB) /system/lib64/libcutils.so libc++.so 0x7a9114e000 983040 (960.0 KiB) /system/lib64/libc++.so libc.so 0x7a9249d000 892928 (872.0 KiB) /system/lib64/libc.so libcrypto.so 0x7a92283000 1155072 (1.1 MiB) /system/lib64/libcrypto.so

    有的命令后面可以通過?--json logfile?來直接保存結果到文件里。

    有的可以通過查看 .objection (位置:C:\Users\用戶名\.objection )文件里的輸出 log 來查看結果。

    sakura@sakurade:~/.objection$ cat *log | grep -i display android.hardware.display.DisplayManager android.hardware.display.DisplayManager$DisplayListener android.hardware.display.DisplayManagerGlobal

    4.2 案例學習

    案例學習 1:《仿VX數據庫原型取證逆向分析》

    案例學習case1:《仿VX數據庫原型取證逆向分析》

    附件鏈接?:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1082706

    android-backup-extractor工具鏈接:https://github.com/nelenkov/android-backup-extractor

    sakura@sakurade:~/Desktop/frida_learn$ java -version java version "1.8.0_141"sakura@sakuradeMacBook-Pro:~/Desktop/frida_learn$ java -jar abe-all.jar unpack 1.ab 1.tar 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11% 12% 13% 14% 15% 16% 17% 18% 19% 20% 21% 22% 23% 24% 25% 26% 27% 28% 29% 30% 31% 32% 33% 34% 35% 36% 37% 38% 39% 40% 41% 42% 43% 44% 45% 46% 47% 48% 49% 50% 51% 52% 53% 54% 55% 56% 57% 58% 59% 60% 61% 62% 63% 64% 65% 66% 67% 68% 69% 70% 71% 72% 73% 74% 75% 76% 77% 78% 79% 80% 81% 82% 83% 84% 85% 86% 87% 88% 89% 90% 91% 92% 93% 94% 95% 96% 97% 98% 99% 100% 9097216 bytes written to 1.tar.... sakura@sakurade:~/Desktop/frida_learn/apps/com.example.yaphetshan.tencentwelcome$ ls Encryto.db _manifest a db

    裝個夜神模擬器玩

    sakura@sakura:/Applications/NoxAppPlayer.app/Contents/MacOS$ ./adb connect 127.0.0.1:62001 * daemon not running. starting it now on port 5037 * adb E 5139 141210 usb_osx.cpp:138] Unable to create an interface plug-in (e00002be) * daemon started successfully * connected to 127.0.0.1:62001 sakura@sakuradeMacBook-Pro:/Applications/NoxAppPlayer.app/Contents/MacOS$ ./adb shell dream2qltechn:/ # whoami root dream2qltechn:/ # uname -a Linux localhost 4.0.9+ #222 SMP PREEMPT Sat Mar 14 18:24:36 HKT 2020 i686

    肯定還是先定位目標字符串?Wait a Minute,What was happend?

    jadx 搜索字符串

    重點在 a() 代碼里,其實是根據明文的 name 和 password,然后?aVar.a(a2 + aVar.b(a2, contentValues.getAsString("password"))).substring(0, 7)?再做一遍復雜的計算并截取7位當做密碼,傳入 getWritableDatabase 去解密 demo.db 數據庫。

    所以我們 hook一下 getWritableDatabase 即可。

    // 首先查看要注入的進程 frida-ps -U ... 5662 com.example.yaphetshan.tencentwelcome ...// 使用 objection 注入 objection -d -g com.example.yaphetshan.tencentwelcome explore

    看一下源碼

    package net.sqlcipher.database; ... public abstract class SQLiteOpenHelper {...public synchronized SQLiteDatabase getWritableDatabase(char[] cArr) {

    也可以 objection search 一下這個 method

    (samsung: 7.1.2) [usb] # android hooking search methods getWritableDatabase Warning, searching all classes may take some time and in some cases, crash the target application. Continue? [y/N]: y Found 4650 classes, searching methods (this may take some time)...android.database.sqlite.SQLiteOpenHelper.getWritableDatabase ... net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase

    hook 一下這個 method

    [usb] # android hooking watch class_method net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase --dump-args --dump-backtrace --dump-return - [incoming message] ------------------ {"payload": "Attempting to watch class \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m and method \u001b[32mgetWritableDatabase\u001b[39m.","type": "send" } - [./incoming message] ---------------- (agent) Attempting to watch class net.sqlcipher.database.SQLiteOpenHelper and method getWritableDatabase. - [incoming message] ------------------ {"payload": "Hooking \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m.\u001b[92mgetWritableDatabase\u001b[39m(\u001b[31mjava.lang.String\u001b[39m)","type": "send" } - [./incoming message] ---------------- (agent) Hooking net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(java.lang.String) - [incoming message] ------------------ {"payload": "Hooking \u001b[32mnet.sqlcipher.database.SQLiteOpenHelper\u001b[39m.\u001b[92mgetWritableDatabase\u001b[39m(\u001b[31m[C\u001b[39m)","type": "send" } - [./incoming message] ---------------- (agent) Hooking net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase([C) - [incoming message] ------------------ {"payload": "Registering job \u001b[94mjytq1qeyllq\u001b[39m. Type: \u001b[92mwatch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase\u001b[39m","type": "send" } - [./incoming message] ---------------- (agent) Registering job jytq1qeyllq. Type: watch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase ...mple.yaphetshan.tencentwelcome on (samsung: 7.1.2) [usb] #

    hook 好之后再打開這個 apk

    (agent) [1v488x28gcs] Called net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(java.lang.String) ... (agent) [1v488x28gcs] Backtrace:net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(Native Method)com.example.yaphetshan.tencentwelcome.MainActivity.a(MainActivity.java:55)com.example.yaphetshan.tencentwelcome.MainActivity.onCreate(MainActivity.java:42)android.app.Activity.performCreate(Activity.java:6692) ... (agent) [1v488x28gcs] Arguments net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(ae56f99)... ...mple.yaphetshan.tencentwelcome on (samsung: 7.1.2) [usb] # jobs list Job ID Hooks Type ----------- ------- ----------------------------------------------------------------------------- 1v488x28gcs 2 watch-method for: net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase

    找到參數?ae56f99

    剩下的就是用這個密碼去打開加密的 db。

    然后base64解密一下就好了。

    還有一種策略是主動調用,即自己去調用 a 函數以觸發 getWritableDatabase 的數據庫解密。先尋找 a 所在類的實例,然后 hook getWritableDatabase,最終主動調用 a。這里幸運的是 a 沒有什么奇奇怪怪的參數需要我們傳入。

    [usb] # android heap search instances com.example.yaphetshan.tencentwelcome.MainActivity Class instance enumeration complete for com.example.yaphetshan.tencentwelcome.MainActivity Handle Class toString() -------- -------------------------------------------------- ---------------------------------------------------------- 0x20078a com.example.yaphetshan.tencentwelcome.MainActivity com.example.yaphetshan.tencentwelcome.MainActivity@1528f80[usb] # android hooking watch class_method net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase --dump-args --dump-backtrace --dump-return [usb] # android heap execute 0x20078a a (agent) [taupgwkum4h] Arguments net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(ae56f99)

    案例學習 2:主動調用爆破密碼

    附件鏈接:https://bbs.pediy.com/thread-257745.htm

    因為直接找?Unfortunately,note the right PIN :(找不到,可能是把字符串藏在什么資源文件里了。
    review 代碼之后找到校驗的核心函數,邏輯就是將 input 編碼一下之后和密碼比較,這肯定是什么不可逆的加密。

    public static boolean verifyPassword(Context context, String input) {if (input.length() != 4) {return false;}byte[] v = encodePassword(input);byte[] p = "09042ec2c2c08c4cbece042681caf1d13984f24a".getBytes();if (v.length != p.length) {return false;}for (int i = 0; i < v.length; i++) {if (v[i] != p[i]) {return false;}}return true; }

    這里就爆破一下密碼。

    // 查看 app 進程 frida-ps -U | grep qualification 7660 org.teamsik.ahe17.qualification.easy// frida 命令執行 js 進行 hook frida -U -f org.teamsik.ahe17.qualification.easy -l force.js# 上面命令執行成功后,會進入 frida,再輸入 %resume 然后回車,即可讓程序繼續執行

    或者使用 -F 大寫F 參數,frida -U -F?org.teamsik.ahe17.qualification.easy -l force.js

    js 腳本:

    function main() {Java.perform(function x() {console.log("In Java perform")var verify = Java.use("org.teamsik.ahe17.qualification.Verifier")var stringClass = Java.use("java.lang.String")var p = stringClass.$new("09042ec2c2c08c4cbece042681caf1d13984f24a")var pSign = p.getBytes()// var pStr = stringClass.$new(pSign)// console.log(parseInt(pStr))for (var i = 999; i < 10000; i++){var v = stringClass.$new(String(i))var vSign = verify.encodePassword(v)if (parseInt(stringClass.$new(pSign)) == parseInt(stringClass.$new(vSign))) {console.log("yes: " + v)break}console.log("not :" + v)}}) } setImmediate(main)

    ?

    ?...
    not :9080
    not :9081
    not :9082
    yes: 9083

    這里注意 parseInt

    5、Frida hook 基礎(一)

    • 調用 靜態函數非靜態函數
    • 設置 (同名)成員變量
    • 內部類,枚舉類的函數并hook,trace原型1
    • 查找接口,hook動態加載dex
    • 枚舉class,trace原型2
    • objection 不能切換 classloader

    5.1 Frida hook : 打印參數、返回值 / 設置返回值 / 主動調用

    demo 就不貼了,還是先定位登錄失敗點,然后搜索字符串。

    public class LoginActivity extends AppCompatActivity {/* access modifiers changed from: private */public Context mContext;public void onCreate(Bundle bundle) {super.onCreate(bundle);this.mContext = this;setContentView((int) R.layout.activity_login);final EditText editText = (EditText) findViewById(R.id.username);final EditText editText2 = (EditText) findViewById(R.id.password);((Button) findViewById(R.id.login)).setOnClickListener(new View.OnClickListener() {public void onClick(View view) {String obj = editText.getText().toString();String obj2 = editText2.getText().toString();if (TextUtils.isEmpty(obj) || TextUtils.isEmpty(obj2)) {Toast.makeText(LoginActivity.this.mContext, "username or password is empty.", 1).show();} else if (LoginActivity.a(obj, obj).equals(obj2)) {LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1.class));LoginActivity.this.finishActivity(0);} else {Toast.makeText(LoginActivity.this.mContext, "Login failed.", 1).show();}}});}

    LoginActivity.a(obj, obj).equals(obj2)?分析之后可得 obj2 來自 password,由從 username 得來的 obj,經過 a 函數運算之后得到一個值,這兩個值相等則登錄成功。所以這里關鍵是 hook a 函數的參數,最簡腳本如下。

    //打印參數、返回值 function Login(){Java.perform(function(){Java.use("com.example.androiddemo.Activity.LoginActivity").a.overload('java.lang.String', 'java.lang.String').implementation = function (str, str2){var result = this.a(str, str2);console.log("args0:" + str + " args1:" + str2 + " result:" + result);return result;}}) } setImmediate(Login)

    觀察輸入和輸出,這里也可以直接主動調用。

    function login() {Java.perform(function () {console.log("start")var login = Java.use("com.example.androiddemo.Activity.LoginActivity")var result = login.a("1234","1234")console.log(result)}) } setImmediate(login)

    輸出:

    ... start 4e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb 然后 adb shell input text "4e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb"

    接下來是第一關

    public abstract class BaseFridaActivity extends AppCompatActivity implements View.OnClickListener {public Button mNextCheck;public void CheckSuccess() {}public abstract String getNextCheckTitle();public abstract void onCheck();/* access modifiers changed from: protected */public void onCreate(Bundle bundle) {super.onCreate(bundle);setContentView((int) R.layout.activity_frida);this.mNextCheck = (Button) findViewById(R.id.next_check);this.mNextCheck.setOnClickListener(this);Button button = this.mNextCheck;button.setText(getNextCheckTitle() + ",點擊進入下一關");}public void onClick(View view) {onCheck();}public void CheckFailed() {Toast.makeText(this, "Check Failed!", 1).show();} } ...public class FridaActivity1 extends BaseFridaActivity {private static final char[] table = {'L', 'K', 'N', 'M', 'O', 'Q', 'P', 'R', 'S', 'A', 'T', 'B', 'C', 'E', 'D', 'F', 'G', 'H', 'I', 'J', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'o', 'd', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'e', 'f', 'g', 'h', 'j', 'i', 'k', 'l', 'm', 'n', 'y', 'z', '0', '1', '2', '3', '4', '6', '5', '7', '8', '9', '+', '/'};public String getNextCheckTitle() {return "當前第1關";}public void onCheck() {try {if (a(b("請輸入密碼:")).equals("R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=")) {CheckSuccess();startActivity(new Intent(this, FridaActivity2.class));finishActivity(0);return;}super.CheckFailed();} catch (Exception e) {e.printStackTrace();}}public static String a(byte[] bArr) throws Exception {StringBuilder sb = new StringBuilder();for (int i = 0; i <= bArr.length - 1; i += 3) {byte[] bArr2 = new byte[4];byte b = 0;for (int i2 = 0; i2 <= 2; i2++) {int i3 = i + i2;if (i3 <= bArr.length - 1) {bArr2[i2] = (byte) (b | ((bArr[i3] & 255) >>> ((i2 * 2) + 2)));b = (byte) ((((bArr[i3] & 255) << (((2 - i2) * 2) + 2)) & 255) >>> 2);} else {bArr2[i2] = b;b = 64;}}bArr2[3] = b;for (int i4 = 0; i4 <= 3; i4++) {if (bArr2[i4] <= 63) {sb.append(table[bArr2[i4]]);} else {sb.append('=');}}}return sb.toString();}public static byte[] b(String str) {try {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();GZIPOutputStream gZIPOutputStream = new GZIPOutputStream(byteArrayOutputStream);gZIPOutputStream.write(str.getBytes());gZIPOutputStream.finish();gZIPOutputStream.close();byte[] byteArray = byteArrayOutputStream.toByteArray();try {byteArrayOutputStream.close();return byteArray;} catch (Exception e) {e.printStackTrace();return byteArray;}} catch (Exception unused) {return null;}} }

    關鍵函數在??a(b("請輸入密碼:")).equals("R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=")

    這里應該直接 hook a,讓其返回值為?R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=就可以進入下一關了。

    function ch1() {Java.perform(function () {console.log("start")Java.use("com.example.androiddemo.Activity.FridaActivity1").a.implementation = function (x) {return "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL="}}) }

    上面的代碼 hook 了 類中的靜態函數。在寫 hook 的 js 代碼時,靜態 非靜態 區別:

    • hook 靜態函數時候,不需要 overload
    • hook 類的成員函數時,需要 overload

    上面代碼是 hook 并 實現函數,然后調用,

    下面代碼是?hook 但 不實現函數,只調用

    5.2 Frida hook : 主動調用靜態/非靜態函數 以及 設置靜態/非靜態成員變量的值

    總結:

    • 靜態函數直接 use class 然后調用方法,非靜態函數需要先 choose 實例然后調用
    • 設置成員變量的值,寫法是?xx.value = yy,其他方面和函數一樣。
    • 如果有一個成員變量和成員函數的名字相同,則在其前面加一個_,如?_xx.value = yy

    然后是第二關

    public class FridaActivity2 extends BaseFridaActivity {private static boolean static_bool_var = false;private boolean bool_var = false;public String getNextCheckTitle() {return "當前第2關";}private static void setStatic_bool_var() {static_bool_var = true;}private void setBool_var() {this.bool_var = true;}public void onCheck() {if (!static_bool_var || !this.bool_var) {super.CheckFailed();return;}CheckSuccess();startActivity(new Intent(this, FridaActivity3.class));finishActivity(0);} }

    這一關的關鍵在于下面的 if 判斷要為 false,則?static_bool_var?和?this.bool_var?都要為 true。

    if (!static_bool_var || !this.bool_var) {super.CheckFailed();return; }

    這樣就要調用?setBool_var?和?setStatic_bool_var?兩個函數了。

    function ch2() {Java.perform(function () {console.log("start")var FridaActivity2 = Java.use("com.example.androiddemo.Activity.FridaActivity2")// hook 靜態函數 直接調用FridaActivity2.setStatic_bool_var()// hook 動態函數,找到 instance 實例,從 實例中 調用函數方法Java.choose("com.example.androiddemo.Activity.FridaActivity2", {onMatch: function (instance) {instance.setBool_var()},onComplete: function () {console.log("end")}})}) } setImmediate(ch2)

    接下來是第三關

    public class FridaActivity3 extends BaseFridaActivity {private static boolean static_bool_var = false;private boolean bool_var = false;private boolean same_name_bool_var = false;public String getNextCheckTitle() {return "當前第3關";}private void same_name_bool_var() {Log.d("Frida", static_bool_var + " " + this.bool_var + " " + this.same_name_bool_var);}public void onCheck() {if (!static_bool_var || !this.bool_var || !this.same_name_bool_var) {super.CheckFailed();return;}CheckSuccess();startActivity(new Intent(this, FridaActivity4.class));finishActivity(0);} }

    關鍵是讓?if (!static_bool_var || !this.bool_var || !this.same_name_bool_var)為 false,則三個變量都要為 true

    function ch3() {Java.perform(function () {console.log("start")var FridaActivity3 = Java.use("com.example.androiddemo.Activity.FridaActivity3")FridaActivity3.static_bool_var.value = trueJava.choose("com.example.androiddemo.Activity.FridaActivity3", {onMatch: function (instance) {instance.bool_var.value = trueinstance._same_name_bool_var.value = true},onComplete: function () {console.log("end")}})}) }

    注意:類里有一個 成員函數成員變量 都叫做??same_name_bool_var?,這種時候在成員變量前加一個?_,修改值的形式為??xx.value = yy

    5.3 Frida hook : 內部類,枚舉類的方法?并 hook,trace原型1

    總結:

    • 對于內部類,通過?類名$內部類名?去 use 或者 choose
    • 對 use 得到的 clazz 應用反射,如?clazz.class.getDeclaredMethods()?可以得到 類里面聲明的所有方法,即 可以枚舉類里面的所有函數

    接下來是第四關

    public class FridaActivity4 extends BaseFridaActivity {public String getNextCheckTitle() {return "當前第4關";}private static class InnerClasses {public static boolean check1() { return false;}public static boolean check2() { return false;}public static boolean check3() { return false;}public static boolean check4() { return false;}public static boolean check5() { return false;}public static boolean check6() { return false;}private InnerClasses() {}}public void onCheck() {if (!InnerClasses.check1() || !InnerClasses.check2() || !InnerClasses.check3() || !InnerClasses.check4() || !InnerClasses.check5() || !InnerClasses.check6()){super.CheckFailed();return;}CheckSuccess();startActivity(new Intent(this, FridaActivity5.class));finishActivity(0);} }

    這一關的關鍵是讓?if (!InnerClasses.check1() || !InnerClasses.check2() || !InnerClasses.check3() || !InnerClasses.check4() || !InnerClasses.check5() || !InnerClasses.check6())?中的所有 check 全部返回 true。

    其實這里唯一的問題就是尋找內部類?InnerClasses,對于內部類的 hook,通過 類名$內部類名 去 use。

    function ch4() {Java.perform(function () {var InnerClasses = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses")console.log("start")InnerClasses.check1.implementation = function () { return true }InnerClasses.check2.implementation = function () { return true }InnerClasses.check3.implementation = function () { return true }InnerClasses.check4.implementation = function () { return true }InnerClasses.check5.implementation = function () { return true }InnerClasses.check6.implementation = function () { return true }}) }

    利用反射,獲取類中的所有 method 聲明,然后字符串拼接去獲取到方法名,例如下面的 check1,然后就可以批量 hook,而不用像我上面那樣一個一個寫。

    var inner_classes = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses") var all_methods = inner_classes.class.getDeclaredMethods();... public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check1(), public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check2(), public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check3(), public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check4(), public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check5(), public static boolean com.example.androiddemo.Activity.FridaActivity4$InnerClasses.check6()

    hook 類方法 的 所有 重載

    方法 1:

    //目標類 var hook = Java.use(targetClass); //重載次數 var overloadCount = hook[targetMethod].overloads.length; //打印日志:追蹤的方法有多少個重載 console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]"); //每個重載都進入一次 for (var i = 0; i < overloadCount; i++) { //hook每一個重載hook[targetMethod].overloads[i].implementation = function() {console.warn("\n*** entered " + targetClassMethod);//可以打印每個重載的調用棧,對調試有巨大的幫助,當然,信息也很多,盡量不要打印,除非分析陷入僵局Java.perform(function() {var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());console.log("\nBacktrace:\n" + bt);}); // 打印參數if (arguments.length) console.log();for (var j = 0; j < arguments.length; j++) {console.log("arg[" + j + "]: " + arguments[j]);}//打印返回值var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)console.log("\nretval: " + retval);console.warn("\n*** exiting " + targetClassMethod);return retval;} }

    方法 2:

    function hookOneClassAllMethod(clsName) {console.log("開始 hook 一個類的所有方法");// var NetContent = Java.use("com.xbiao.utils.net.NetContent");var clazz = Java.use(clsName); // 得到 classvar all_method = clazz.class.getDeclaredMethods(); // 得到類的所有方法all_method.forEach(function (mth) { // 遍歷類的所有方法var mthName = mth.getName(); // 得到 方法名var all_overload = clazz[mthName].overloads;all_overload.forEach(function (olad) {// hook 重載 argumentTypesolad.implementation = function () {console.log("\r")// printStack()for (var i = 0; i < arguments.length; i++) {var arg_type = olad.argumentTypes[i].className;var arg_val = arguments[i];var arg_fmt = JSON.stringify(arg_val);var msg = mthName + " ---> " + "arg[" + i + ":"+ arg_type +"]:" + arg_fmt;console.log(msg);}var retVal = this[mthName].apply(this, arguments);console.log(mthName + " ---> 返回值:" + retVal);return retVal;}})}) } function main() {Java.perform(() => {Java.enumeratteClassLoaders({onMatch: function(loader){console.log("ClassLoader start");try {if(loader.findClass("com.xbiao.utils.AESdedUtil")){console.log("Successfully found loader")console.log(loader);Java.classFactory.loader = loader ;hookOneClassAllMethod("com.xbiao.utils.net.NetContent")}}catch(error){console.log("find error:" + error)}},onComplete: function (){console.log("ClassLoader end");}});}); } setImmediate(main)

    執行結果:?

    5.4 Frida hook : hook 動態加載的 dex,與查找 interface,

    總結:

    • 通過?enumerateClassLoaders?來枚舉加載進內存的 classloader
    • 再?loader.findClass(xxx)?尋找是否包括我們想要的 interface 的實現類,
    • 最后通過?Java.classFactory.loader = loader?來切換 classloader,從而加載該實現類。

    第五關比較有趣,它的 check 函數是動態加載進來的。

    java 里有 interface 的概念,是指一系列抽象的接口,需要類來實現。

    package com.example.androiddemo.Dynamic;public interface CheckInterface { boolean check(); } ...public class DynamicCheck implements CheckInterface {public boolean check() { return false; } } ... public class FridaActivity5 extends BaseFridaActivity {private CheckInterface DynamicDexCheck = null;...public CheckInterface getDynamicDexCheck() {if (this.DynamicDexCheck == null) {loaddex();}return this.DynamicDexCheck;}/* access modifiers changed from: protected */public void onCreate(Bundle bundle) {super.onCreate(bundle);loaddex();// this.DynamicDexCheck = (CheckInterface) new DexClassLoader(// str, filesDir.getAbsolutePath(), (String) null, getClassLoader()// ).loadClass("com.example.androiddemo.Dynamic.DynamicCheck").newInstance();}public void onCheck() {if (getDynamicDexCheck() == null) {Toast.makeText(this, "onClick loaddex Failed!", 1).show();} else if (getDynamicDexCheck().check()) {CheckSuccess();startActivity(new Intent(this, FridaActivity6.class));finishActivity(0);} else {super.CheckFailed();}} }

    這里有個 loaddex 其實就是先從資源文件加載 classloader 到內存里,再 loadClass DynamicCheck,創建出一個實例,最終調用這個實例的 check。

    所以現在我們就要先枚舉 class loader,找到能實例化我們要的 class 的那個 class loader,然后把它設置成 Java 的默認 class factory 的 loader。

    現在就可以用這個 class loader 來使用 .use 去 import 一個給定的類。

    function ch5() {Java.perform(function () {// Java.choose("com.example.androiddemo.Activity.FridaActivity5",{// onMatch:function(x){// console.log(x.getDynamicDexCheck().$className)// },onComplete:function(){}// })console.log("start")Java.enumerateClassLoaders({onMatch: function (loader) {try {if(loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")){console.log("Successfully found loader")console.log(loader);Java.classFactory.loader = loader ;}}catch(error){console.log("find error:" + error)}},onComplete: function () {console.log("end1")}})Java.use("com.example.androiddemo.Dynamic.DynamicCheck").check.implementation = function () {return true}console.log("end2")}) } setImmediate(ch5)

    todo有一個疑問:https://github.com/frida/frida/issues/1049

    5.5 Frida hook : 枚舉 class,trace原型2

    總結: 通過?Java.enumerateLoadedClasses?來 枚舉類,然后?name.indexOf(str)?過濾一下并 hook。

    接下來是第六關

    import com.example.androiddemo.Activity.Frida6.Frida6Class0; import com.example.androiddemo.Activity.Frida6.Frida6Class1; import com.example.androiddemo.Activity.Frida6.Frida6Class2;public class FridaActivity6 extends BaseFridaActivity {public String getNextCheckTitle() {return "當前第6關";}public void onCheck() {if (!Frida6Class0.check() || !Frida6Class1.check() || !Frida6Class2.check()) {super.CheckFailed();return;}CheckSuccess();startActivity(new Intent(this, FridaActivity7.class));finishActivity(0);} }

    這關是 import 了一些類,然后 調用類里的靜態方法,所以我們枚舉所有的類,然后過濾一下,并把過濾出來的結果 hook 上,改掉其返回值。

    function ch6() {Java.perform(function () {Java.enumerateLoadedClasses({onMatch: function (class_name, handle){if (class_name.indexOf("com.example.androiddemo.Activity.Frida6") != -1) {console.log("class_name:" + class_name + " handle:" + handle)Java.use(class_name).check.implementation = function () {return true}}},onComplete: function () {console.log("end")}})}) }

    frida rpc 枚舉 實現 接口的類

    import sys import frida''' frida rpc 枚舉類 '''def on_message(message, data):if message['type'] == 'send':print("[*] {message['payload']}")else:print(message)hook = """ Java.perform(function(){Java.enumerateLoadedClasses({"onMatch" : function(classname){if(classname.indexOf("com.csair.mbp") < 0){return;}// 實現類 implementstry{var hookCls = Java.use(classname)var interFaces = hookCls.class.getInterfaces();if (interFaces.length > 0) {console.log(classname)for (var i in interFaces) {// 接口類 interFacesconsole.log("\t", interFaces[i].toString())}} }catch(e){console.log(e)}},"onComplete" : function(){}}) }) """process = frida.get_usb_device().attach('com.csair.mbp') script = process.create_script(hook) script.on('message', on_message) print('[*] Running CTF') script.load() sys.stdin.read()

    5.6 Frida hook : 搜索 interface 的具體實現類

    利用反射得到類里面實現的 interface 數組,并打印出來。

    function more() {Java.perform(function () {Java.enumerateLoadedClasses({onMatch: function (class_name){if (class_name.indexOf("com.example.androiddemo") < 0) {return}else {var hook_cls = Java.use(class_name)var interfaces = hook_cls.class.getInterfaces()if (interfaces.length > 0) {console.log(class_name + ": ")for (var i in interfaces) {console.log("\t", interfaces[i].toString())}}}},onComplete: function () {console.log("end")}})}) }

    6、Frida hook 基礎(二)

    • spawn / attach
    • 各種主動調用
    • hook函數 和 hook構造函數
    • 調用棧? /簡單腳本
    • 動態加載自己的 dex

    題目下載地址:https://github.com/tlamb96/kgb_messenger

    6.1 spawn / attach

    firda 的 -f 參數代表 span 啟動:frida -U -f com.tlamb96.spetsnazmessenger -l frida_russian.js --no-pause

    上面命令執行完后,會進入 frida ,然后在輸入 %resume 恢復程序運行

    /* access modifiers changed from: protected */public void onCreate(Bundle bundle) {super.onCreate(bundle);setContentView((int) R.layout.activity_main);String property = System.getProperty("user.home");String str = System.getenv("USER");if (property == null || property.isEmpty() || !property.equals("Russia")) {a("Integrity Error", "This app can only run on Russian devices.");} else if (str == null || str.isEmpty() || !str.equals(getResources().getString(R.string.User))) {a("Integrity Error", "Must be on the user whitelist.");} else {a.a(this);startActivity(new Intent(this, LoginActivity.class));}} }

    這個題目比較簡單,但是因為這個 check 是在?onCreate?里,所以 app 剛啟動就自動檢查,所以這里需要用 spawn 的方式去啟動 frida 腳本 hook,而不是 attach。

    這里有兩個檢查,一個是檢查 property 的值,一個是檢查 str 的值。分別從?System.getProperty?和?System.getenv?里獲取,hook 住這兩個函數就行。

    這里要注意從資源文件里找到 User 的值。

    frida_russian.js?

    function main() {Java.perform(function () {Java.use("java.lang.System").getProperty.overload('java.lang.String').implementation = function (str) {return "Russia";}Java.use("java.lang.System").getenv.overload('java.lang.String').implementation = function(str){return "RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==";}}) } setImmediate(main)

    運行結果截圖:

    接下來進入到 login 功能

    public void onLogin(View view) {EditText editText = (EditText) findViewById(R.id.login_username);EditText editText2 = (EditText) findViewById(R.id.login_password);this.n = editText.getText().toString();this.o = editText2.getText().toString();if (this.n != null && this.o != null && !this.n.isEmpty() && !this.o.isEmpty()) {if (!this.n.equals(getResources().getString(R.string.username))) {Toast.makeText(this, "User not recognized.", 0).show();editText.setText("");editText2.setText("");} else if (!j()) {Toast.makeText(this, "Incorrect password.", 0).show();editText.setText("");editText2.setText("");} else {i();startActivity(new Intent(this, MessengerActivity.class));}}} ...private boolean j() {String str = "";for (byte b : this.m.digest(this.o.getBytes())) {str = str + String.format("%x", new Object[]{Byte.valueOf(b)});}return str.equals(getResources().getString(R.string.password));} ...private void i() {char[] cArr = {'(', 'W', 'D', ')', 'T', 'P', ':', '#', '?', 'T'};cArr[0] = (char) (cArr[0] ^ this.n.charAt(1));cArr[1] = (char) (cArr[1] ^ this.o.charAt(0));cArr[2] = (char) (cArr[2] ^ this.o.charAt(4));cArr[3] = (char) (cArr[3] ^ this.n.charAt(4));cArr[4] = (char) (cArr[4] ^ this.n.charAt(7));cArr[5] = (char) (cArr[5] ^ this.n.charAt(0));cArr[6] = (char) (cArr[6] ^ this.o.charAt(2));cArr[7] = (char) (cArr[7] ^ this.o.charAt(3));cArr[8] = (char) (cArr[8] ^ this.n.charAt(6));cArr[9] = (char) (cArr[9] ^ this.n.charAt(8));Toast.makeText(this, "FLAG{" + new String(cArr) + "}", 1).show();}

    從資源文件里找到 username,密碼則是要算一個 j() 函數,要讓它返回 true,順便打印一下 i 函數 toast 到界面的 flag。

    ( github 代碼中 不是 j() 函數,而是?checkPassword() 函數?)

    var clazz = Java.use("com.tlamb96.kgbmessenger.LoginActivity") clazz.j.implementation = function (){return true} ...var clazz = Java.use("android.widget.Toast") clazz.makeText.overload('android.content.Context', 'java.lang.CharSequence', 'int').implementation = function (x, y, z) {var flag = Java.use("java.lang.String").$new(y)console.log(flag) } ... [Google Pixel::com.tlamb96.spetsnazmessenger]-> FLAG{G&qG13 R0}

    代碼:

    Java.perform(function () {Java.use("com.tlamb96.kgbmessenger.LoginActivity").checkPassword.implementation = function () {return true};Java.use("android.widget.Toast").makeText.overload('android.content.Context', 'java.lang.CharSequence', 'int').implementation = function (x, y, z) {var flag = Java.use("java.lang.String").$new(y);console.log(flag);return this.makeText(x, y, z);} })

    執行結果:

    6.2 Frida hook : hook構造函數/打印棧回溯

    總結:hook 構造函數實現 通過use取得類,然后clazz.$init.implementation = callback?hook 構造函數。

    我們先學習一下怎么 hook 構造函數。

    add(new com.tlamb96.kgbmessenger.b.a(R.string.katya, "Archer, you up?", "2:20 am", true)); ... package com.tlamb96.kgbmessenger.b; public class a { ...public a(int i, String str, String str2, boolean z) {this.f448a = i;this.b = str;this.c = str2;this.d = z;} ... }

    用?$init?來 hook 構造函數

    function printstack() {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }Java.use("com.tlamb96.kgbmessenger.b.a").$init.implementation = function (i, str1, str2, z) {this.$init(i, str1, str2, z)console.log(i, str1, str2, z)printStack("com.tlamb96.kgbmessenger.b.a") }

    打印堆棧:

    function printstack() {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }

    6.3 Frida hook : 打印棧回溯

    打印棧回溯

    function printStack(name) {Java.perform(function () {var Exception = Java.use("java.lang.Exception");var instanceTemp = Exception.$new("Exception");var straces = instanceTemp.getStackTrace();if (straces != undefined && straces != null) {var strace = straces.toString();var replaceStr = strace.replace(/,/g, "\\n");console.log("=============================" + name + " Stack strat=======================");console.log(replaceStr);console.log("=============================" + name + " Stack end=======================\r\n");Exception.$dispose();}}); }

    輸出就是這樣

    [Google Pixel::com.tlamb96.spetsnazmessenger]-> 2131558449 111 02:27 下午 false =============================com.tlamb96.kgbmessenger.b.a Stack strat======================= com.tlamb96.kgbmessenger.b.a.<init>(Native Method) com.tlamb96.kgbmessenger.MessengerActivity.onSendMessage(Unknown Source:40) java.lang.reflect.Method.invoke(Native Method) android.support.v7.app.m$a.onClick(Unknown Source:25) android.view.View.performClick(View.java:6294) android.view.View$PerformClick.run(View.java:24770) android.os.Handler.handleCallback(Handler.java:790) android.os.Handler.dispatchMessage(Handler.java:99) android.os.Looper.loop(Looper.java:164) android.app.ActivityThread.main(ActivityThread.java:6494) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) =============================com.tlamb96.kgbmessenger.b.a Stack end=======================

    6.4 Frida hook : 手動加載 dex 并調用

    總結:編譯出 dex 后,通過 Java.openClassFile("xxx.dex").load() 加載,就可以正常通過 Java.use 調用里面的方法了。

    現在我們來繼續解決這個問題。

    public void onSendMessage(View view) {EditText editText = (EditText) findViewById(R.id.edittext_chatbox);String obj = editText.getText().toString();if (!TextUtils.isEmpty(obj)) {this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.user, obj, j(), false));this.n.c();if (a(obj.toString()).equals(this.p)) {Log.d("MessengerActivity", "Successfully asked Boris for the password.");this.q = obj.toString();this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.boris, "Only if you ask nicely", j(), true));this.n.c();}if (b(obj.toString()).equals(this.r)) {Log.d("MessengerActivity", "Successfully asked Boris nicely for the password.");this.s = obj.toString();this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.boris, "Wow, no one has ever been so nice to me! Here you go friend: FLAG{" + i() + "}", j(), true));this.n.c();}this.m.b(this.m.getAdapter().a() - 1);editText.setText("");} }

    新的一關是一個聊天框。分析一下代碼可知,obj 是我們輸入的內容,輸入完了之后,加到一個 this.o 的 ArrayList 里。關鍵的 if 判斷就是 if (a(obj.toString()).equals(this.p)) 和 if (b(obj.toString()).equals(this.r)),所以 hook a函數b函數,讓它們的返回值等于下面的字符串即可。

    private String p = "V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003"; private String q; private String r = "\u0000dslp}oQ\u0000 dks$|M\u0000h +AYQg\u0000P*!M$gQ\u0000"; private String s;

    但實際上這題比我想象中的還要麻煩,這題的邏輯上是如果通過了 a 和 b 這兩個函數的計算,等于對應的值之后,會把用來計算的 obj 的值賦值給 q 和 s,然后根據這個 q 和 s 來計算出最終的 flag。
    所以如果不逆向算法,通過 hook 的方式通過了 a和b 的計算,obj 的值還是錯誤的,也計算不出正確的 flag。

    這樣就逆向一下算法好了,先自己寫一個 apk,用 java 去實現注冊機。

    可以直接把 class 文件轉成 dex,不復述,我比較懶,所以我直接解壓 apk 找到?classes.dex,并 push 到手機上。
    然后用 frida 加載這個 dex,并調用里面的方法。

    var dex = Java.openClassFile("/data/local/tmp/classes.dex").load(); console.log("decode_P:"+Java.use("myapplication.example.com.reversea.reverseA").decode_P()); console.log("r_to_hex:"+Java.use("myapplication.example.com.reversea.reverseA").r_to_hex()); ... ... decode_P:Boris, give me the password r_to_hex:0064736c707d6f510020646b73247c4d0068202b4159516700502a214d24675100

    7、Frida 打印 與 參數構造

    • 數組 / (字符串)對象數組 / gson / Java.array
    • 對象 / 多態、強轉Java.cast / 接口Java.register
    • 泛型、List、Map、Set、迭代打印

    打印 [object object]

    • 方法 1:先確認 object 是什么類型,比如要打印 p,先 console.log(p.$className) 查看 p 是什么數據類型,然后用 Java.cast 把 p 強制轉為對應類型,強制轉換之后,在調用轉換后類型的輸出方法,通常為 toString()
    • 方法 2:使用 js 里面的 json 類,嘗試 console.log(JSON.stringify(p)),可能打印不出來字符串,一般能打印出 p 的字節數組
    • 方法 3:使用 objection 插件 wallbreak

    $className 參看官方文檔說明:

    gson 打印 Java 對象的內容

    Gson 是谷歌官方推出的支持?JSON 和 Java Object?相互轉換的 Java?序列化/反序列化?庫。

    gson?基本用法:https://blog.csdn.net/chenrenxiang/article/details/80291224? ??https://www.cnblogs.com/baiqiantao/p/7512336.html

    Android Gson使用詳解:https://www.jianshu.com/p/0444693c2639

    Frida?打印 [object] 解決 'gson'?包重名的問題:https://www.52pojie.cn/thread-1167397-1-1.html

    使用 Frida 時,想要打印 Java 對象的內容,可以使用谷歌的 gson包,可以非常優秀的將 Java 對象的內容,以 json 的格式打印出來。

    Java.openClassFile("/data/local/tmp/r0gson.dex").load(); const gson = Java.use('com.r0ysue.gson.Gson'); console.log(gson.$new().toJson(xxx));

    char[] / [Object Object]

    Log.d("SimpleArray", "onCreate: SImpleArray"); char arr[][] = new char[4][]; // 創建一個4行的二維數組 arr[0] = new char[] { '春', '眠', '不', '覺', '曉' }; // 為每一行賦值 arr[1] = new char[] { '處', '處', '聞', '啼', '鳥' }; arr[2] = new char[] { '夜', '來', '風', '雨', '聲' }; arr[3] = new char[] { '花', '落', '知', '多', '少' }; Log.d("SimpleArray", "-----橫版-----"); for (int i = 0; i < 4; i++) { // 循環4行Log.d("SimpleArraysToString", Arrays.toString(arr[i]));Log.d("SimpleStringBytes", Arrays.toString(Arrays.toString(arr[i]).getBytes()));for (int j = 0; j < 5; j++) { // 循環5列Log.d("SimpleArray", Character.toString(arr[i][j])); // 輸出數組中的元素}if (i % 2 == 0) {Log.d("SimpleArray", ",");// 如果是一、三句,輸出逗號} else {Log.d("SimpleArray", "。");// 如果是二、四句,輸出句號} }

    新建一個 Android 項目,把上面代碼放到 onCreate 函數中,再導入缺失的包

    import android.util.Log; import java.util.Arrays;

    ?點擊菜單欄上的 build ---> 生成 apk,成功生成 apk 后,手機安裝 apk,在啟動 frida-server 和端口轉發

    ?執行命令:frida-ps -Ua 查看 apk 的包名

    main.js?

    function main() {Java.perform(function x() {Java.openClassFile("/data/local/tmp/r0gson.dex").load();const gson = Java.use('com.r0ysue.gson.Gson');Java.use("java.lang.Character").toString.overload('char').implementation = function (char) {var result = this.toString(char);console.log("char,result", char, result);return result;}Java.use("java.util.Arrays").toString.overload('[C').implementation = function (charArray) {var result = this.toString(charArray);console.log("charArray,result:", charArray, result)console.log("charArray Object Object:", gson.$new().toJson(charArray));return result;}}) }setImmediate(main)

    ?在執行命令:frida -U -f com.example.myapplication -l .\main.js --no-pause?

    這里的?[C?是 JNI 函數簽名

    byte[]

    function main() {Java.perform(function x() {Java.openClassFile("/data/local/tmp/r0gson.dex").load();const gson = Java.use('com.r0ysue.gson.Gson');Java.use("java.util.Arrays").toString.overload('[B').implementation = function (byteArray) {var result = this.toString(byteArray);console.log("byteArray,result):", byteArray, result)console.log("byteArray Object Object:", gson.$new().toJson(byteArray));return result;}}) }setImmediate(main)

    ?// byte[] bArray = "123abc".getBytes() // java 代碼
    var strTemp = Java.use("java.lang.String").$new(bArray);
    console.log(strTemp)

    java array 構造

    如果不只是想打印出結果,而是要替換原本的參數,就要先自己構造出一個charArray,使用?Java.array 這個API

    /*** Creates a Java array with elements of the specified `type`, from a* JavaScript array `elements`. The resulting Java array behaves like* a JS array, but can be passed by reference to Java APIs in order to* allow them to modify its contents.** @param type Type name of elements.* @param elements Array of JavaScript values to use for constructing the* Java array.*/ function array(type: string, elements: any[]): any[]; Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){var newCharArray = Java.array('char', [ '一','去','二','三','里' ]);var result = this.toString(newCharArray);console.log("newCharArray,result:",newCharArray,result)console.log("newCharArray Object Object:",gson.$new().toJson(newCharArray));var newResult = Java.use('java.lang.String').$new(Java.array('char', [ '煙','村','四','五','家']))return newResult; }

    可以用來構造參數重發包,用在爬蟲上。

    類的多態:轉型 / Java.cast

    可以通過?getClass().getName().toString()來查看當前實例的類型。

    找到一個 instance,通過?Java.cast?來強制轉換對象的類型。

    /*** Creates a JavaScript wrapper given the existing instance at `handle` of* given class `klass` as returned from `Java.use()`.** @param handle An existing wrapper or a JNI handle.* @param klass Class wrapper for type to cast to.*/ function cast(handle: Wrapper | NativePointerValue, klass: Wrapper): Wrapper;

    java 示例代碼( 定義一個 water 類 和 一個 Juice 類,同時 Juice 繼承 water ):

    public class Water { // 水 類public static String flow(Water W) { // 水 的方法// SomeSentenceLog.d("2Object", "water flow: I`m flowing");return "water flow: I`m flowing";}public String still(Water W) { // 水 的方法// SomeSentenceLog.d("2Object", "water still: still water runs deep!");return "water still: still water runs deep!";} } ... public class Juice extends Water { // 果汁 類 繼承了水類public String fillEnergy(){Log.d("2Object", "Juice: i`m fillingEnergy!");return "Juice: i`m fillingEnergy!";}

    js 示例代碼:

    var JuiceHandle = null ; Java.choose("com.r0ysue.a0526printout.Juice",{onMatch:function(instance){console.log("found juice instance",instance);console.log("juice instance call fill",instance.fillEnergy());JuiceHandle = instance;},onComplete:function(){console.log("juice handle search completed!")} }) console.log("Saved juice handle :",JuiceHandle); var WaterHandle = Java.cast(JuiceHandle,Java.use("com.r0ysue.a0526printout.Water")) console.log("call Waterhandle still method:",WaterHandle.still(WaterHandle));

    示例:

    function printHashMap(param_hm){
    ? ? var HashMap = Java.use('java.util.HashMap');
    ? ? var args_map = Java.cast(param_hm, HashMap)
    ? ? send('args_map:' + args_map.toString());
    }

    interface / Java.registerClass

    public interface liquid {public String flow(); }

    frida 提供能力去創建一個新的 java class

    /*** Creates a new Java class.** @param spec Object describing the class to be created.*/ function registerClass(spec: ClassSpec): Wrapper;

    首先獲取要實現的 interface,然后調用 registerClass 來實現 interface。

    function main() {Java.perform(function(){var liquid = Java.use("com.r0ysue.a0526printout.liquid");var beer = Java.registerClass({name: 'com.r0ysue.a0526printout.beer',implements: [liquid],methods: {flow: function () {console.log("look, beer is flowing!")return "look, beer is flowing!";}}});console.log("beer.bubble:",beer.$new().flow()) }) } setImmediate(main)

    成員內部類 / 匿名內部類

    看 smali 或者 枚舉出來的類。

    hook enum

    關于 java 枚舉,從這篇文章了解:https://www.cnblogs.com/jingmoxukong/p/6098351.html

    enum Signal {GREEN, YELLOW, RED } public class TrafficLight {public static Signal color = Signal.RED;public static void main() {Log.d("4enum", "enum "+ color.getClass().getName().toString());switch (color) {case RED:color = Signal.GREEN;break;case YELLOW:color = Signal.RED;break;case GREEN:color = Signal.YELLOW;break;}} } Java.perform(function(){Java.choose("com.r0ysue.a0526printout.Signal",{onMatch:function(instance){console.log("instance.name:",instance.name());console.log("instance.getDeclaringClass:",instance.getDeclaringClass()); },onComplete:function(){console.log("search completed!")}})})

    打印 hash map

    【深入Java基礎】HashMap 的基本用法:https://blog.csdn.net/wxgxgp/article/details/79194360

    Java集合之HashMap的用法:https://blog.csdn.net/weixin_43263961/article/details/86427533

    frida 打印 map

    Java.perform(function(){Java.choose("java.util.HashMap",{onMatch:function(instance){if(instance.toString().indexOf("ISBN")!= -1){console.log("instance.toString:",instance.toString());}},onComplete:function(){console.log("search complete!")}})})

    frida 復雜類型參數打印、參數轉換、調用棧打印:https://blog.csdn.net/weixin_35762183/article/details/106802647

    示例:

    # -*- coding: UTF-8 -*- import frida, sysjsCode = """ Java.perform(function () {/*var clazz = Java.use("xxx");clazz.b.overload('java.util.Map').implementation = function (args1) {var result = "";var keyset = args1.keySet();var it = keyset.iterator();while (it.hasNext()) {var keystr = it.next().toString();var valuestr = args1.get(keystr).toString();console.log(keystr)console.log(valuestr)result += valuestr;}var args = this.b(args1)console.log("出參--", args)return args}*/var HashMap = Java.use('java.util.HashMap');var ShufferMap = Java.use('com.xiaojianbang.app.ShufferMap');ShufferMap.show.implementation = function (map) {var hm = HashMap.$new();hm.put("user","dajianbang");hm.put("pass","87654321");hm.put("code","123456");return this.show(hm);} }); """;def message(message, data):if message["type"] == 'send':print(u"[*] {0}".format(message['payload']))else:print(message)process = frida.get_remote_device().attach("com.xiaojianbang.app") script= process.create_script(jsCode) script.on("message", message) script.load() sys.stdin.read()

    js_code:

    js_code = ''' Java.perform(function() {var clazz = Java.use('com.xxx.xxx');clazz.signUrl.overload('java.lang.String', 'java.util.Map').implementation = function(arg_str, arg_map) { console.log('arg_str:', arg_str);console.log('arg_map:', arg_map); var result = "";var key_set = arg_map.keySet();var key_set_it = key_set.iterator();while(key_set_it.hasNext()){var key_str = key_set_it.next().toString();var value_str = arg_map.get(key_str).toString();console.log(key_str) console.log(value_str) } return this.signUrl(arg_str, arg_map)} }); '''

    js_code?( hashmap 有個 toString 函數,可以直接打印?)

    function printHashMap(param_hm){var HashMap = Java.use('java.util.HashMap');var args_map = Java.cast(param_hm, HashMap)send('args_map:' + args_map.toString()); }

    打印 non-ascii

    https://api-caller.com/2019/03/30/frida-note/#non-ascii

    類名非 ASCII 字符串時,先編碼打印出來,?再用編碼后的字符串去 hook。

    //場景 hook cls.forName尋找目標類的 classloader。 cls.forName.overload('java.lang.String', 'boolean', 'java.lang.ClassLoader').implementation = function (arg1, arg2, arg3) {var clsName = cls.forName(arg1, arg2, arg3);console.log('oriClassName:' + arg1)var base64Name = encodeURIComponent(arg1)console.log('encodeName:' + base64Name);//通過日志確認base64后的非ascii字符串,下面對比并打印classloader//clsName為特殊字符o.?é?if ('o.%CE%99%C9%AB' == base64Name) {//打印classloaderconsole.log(arg3);}return clsName; }

    8、Frida native hook : NDK 開發入門

    https://www.jianshu.com/p/87ce6f565d37

    • Android JNI(一)——NDK與JNI基礎
    • Android JNI學習(二)——實戰JNI之“hello world”
    • Android JNI學習(三)——Java與Native相互調用
    • Android JNI學習(四)——JNI的常用方法的中文API
    • Android JNI學習(五)——Demo演示

    extern "C"?與 名稱修飾 (name mangling)

    • 通過 C++ filt 工具可以直接還原得到原來的函數名
    • https://zh.wikipedia.org/zh-hans/名字修飾
    • 通過 extern "C" 導出的 JNI 函數不會被 name mangling
    • JNI參數基本類型
    • 第一個 NDK程序

    • JNI log

    #define TAG "sakura1328" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)extern "C" JNIEXPORT jstring JNICALL Java_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";LOGD("sakura1328");return env->NewStringUTF(hello.c_str()); } ... public class MainActivity extends AppCompatActivity {private static final String TAG = "sakura";// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = (TextView) findViewById(R.id.sample_text);tv.setText(stringFromJNI());Log.d(TAG, stringFromJNI());}/*** A native method that is implemented by the 'native-lib' native library,* which is packaged with this application.*/public native String stringFromJNI(); }

    9、Frida native hook : JNIEnv反射

    9.1 以 jni字符串 來掌握基本的 JNIEnv用法

    public native String stringWithJNI(String context); ...extern "C" JNIEXPORT jstring JNICALL Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance, jstring context_) {const char *context = env->GetStringUTFChars(context_, 0);int context_size = env->GetStringUTFLength(context_);if (context_size > 0) {LOGD("%s\n", context);}env->ReleaseStringUTFChars(context_, context);return env->NewStringUTF("sakura1328"); }12-26 22:30:00.548 15764-15764/myapplication.example.com.ndk_demo D/sakura1328: sakura

    9.2 Java 反射

    總結:多去讀一下 Java 的反射 API。

    ?Java高級特性 -----?反射?:https://www.jianshu.com/p/9be58ee20dee

    • 查找調用各種 API 接口、JNI、frida/xposed原理的一部分
    • 反射基本 API
    • 反射修改訪問控制、修改屬性值
    • JNI so調用反射進入java世界
    • xposed/Frida hook原理

    這里其實有一個伏筆,就是為什么我們要 trace artmethod,hook artmethod ?????

    是因為有些 so 混淆得非常厲害,然后也就很難靜態分析看出 so 里面調用了哪些 java 函數,也不是通過類似 JNI 的 GetMethodID 這樣來調用的。

    而是通過類似 findclass 這種方法先得到類,然后再反射調用 app 里面的某個 java 函數。

    所以去 hook 它執行的位置,每一個 java 函數對于 Android 源碼而言都是一個 artmethod 結構體,然后 hook 拿到 artmethod 實例后,再去調用類函數,打印這個函數的名稱

    public class MainActivity extends AppCompatActivity {private static final String TAG = "sakura";// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = (TextView) findViewById(R.id.sample_text);tv.setText(stringWithJNI("sakura")); // Log.d(TAG, stringFromJNI()); // Log.d(TAG, stringWithJNI("sakura"));try {testClass();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}public void testClass() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {Test sakuraTest = new Test();// 獲得Class的方法(三種)Class testClazz = MainActivity.class.getClassLoader().loadClass("myapplication.example.com.ndk_demo.Test");Class testClazz2 = Class.forName("myapplication.example.com.ndk_demo.Test");Class testClazz3 = Test.class;Log.i(TAG, "Classloader.loadClass->" + testClazz);Log.i(TAG, "Classloader.loadClass->" + testClazz2);Log.i(TAG, "Classloader.loadClass->" + testClazz3.getName());// 獲得類中屬性相關的方法Field publicStaticField = testClazz3.getDeclaredField("publicStaticField");Log.i(TAG, "testClazz3.getDeclaredField->" + publicStaticField);Field publicField = testClazz3.getDeclaredField("publicField");Log.i(TAG, "testClazz3.getDeclaredField->" + publicField);//對于Field的get方法,如果是static,則傳入null即可;如果不是,則需要傳入一個類的實例String valueStaticPublic = (String) publicStaticField.get(null);Log.i(TAG, "publicStaticField.get->" + valueStaticPublic);String valuePublic = (String) publicField.get(sakuraTest);Log.i(TAG, "publicField.get->" + valuePublic);//對于private屬性,需要設置AccessibleField privateStaticField = testClazz3.getDeclaredField("privateStaticField");privateStaticField.setAccessible(true);String valuePrivte = (String) privateStaticField.get(null);Log.i(TAG, "modified before privateStaticField.get->" + valuePrivte);privateStaticField.set(null, "modified");valuePrivte = (String) privateStaticField.get(null);Log.i(TAG, "modified after privateStaticField.get->" + valuePrivte);Field[] fields = testClazz3.getDeclaredFields();for (Field i : fields) {Log.i(TAG, "testClazz3.getDeclaredFields->" + i);}// 獲得類中method相關的方法Method publicStaticMethod = testClazz3.getDeclaredMethod("publicStaticFunc");Log.i(TAG, "testClazz3.getDeclaredMethod->" + publicStaticMethod);publicStaticMethod.invoke(null);Method publicMethod = testClazz3.getDeclaredMethod("publicFunc", java.lang.String.class);Log.i(TAG, "testClazz3.getDeclaredMethod->" + publicMethod);publicMethod.invoke(sakuraTest, " sakura");}/*** A native method that is implemented by the 'native-lib' native library,* which is packaged with this application.*/public native String stringFromJNI();public native String stringWithJNI(String context); } ... public class Test {private static final String TAG = "sakura_test";public static String publicStaticField = "i am a publicStaticField";public String publicField = "i am a publicField";private static String privateStaticField = "i am a privateStaticField";private String privateField = "i am a privateField";public static void publicStaticFunc() {Log.d(TAG, "I`m from publicStaticFunc");}public void publicFunc(String str) {Log.d(TAG, "I`m from publicFunc" + str);}private static void privateStaticFunc() {Log.i(TAG, "I`m from privateFunc");}private void privateFunc() {Log.i(TAG, "I`m from privateFunc");} } ... ... 12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->class myapplication.example.com.ndk_demo.Test 12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->class myapplication.example.com.ndk_demo.Test 12-26 23:57:11.784 17682-17682/myapplication.example.com.ndk_demo I/sakura: Classloader.loadClass->myapplication.example.com.ndk_demo.Test 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredField->public static java.lang.String myapplication.example.com.ndk_demo.Test.publicStaticField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredField->public java.lang.String myapplication.example.com.ndk_demo.Test.publicField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: publicStaticField.get->i am a publicStaticField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: publicField.get->i am a publicField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: modified before privateStaticField.get->i am a privateStaticField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: modified after privateStaticField.get->modified 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private java.lang.String myapplication.example.com.ndk_demo.Test.privateField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->public java.lang.String myapplication.example.com.ndk_demo.Test.publicField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private static final java.lang.String myapplication.example.com.ndk_demo.Test.TAG 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->private static java.lang.String myapplication.example.com.ndk_demo.Test.privateStaticField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredFields->public static java.lang.String myapplication.example.com.ndk_demo.Test.publicStaticField 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredMethod->public static void myapplication.example.com.ndk_demo.Test.publicStaticFunc() 12-26 23:57:11.785 17682-17682/myapplication.example.com.ndk_demo D/sakura_test: I`m from publicStaticFunc 12-26 23:57:11.786 17682-17682/myapplication.example.com.ndk_demo I/sakura: testClazz3.getDeclaredMethod->public void myapplication.example.com.ndk_demo.Test.publicFunc(java.lang.String) 12-26 23:57:11.786 17682-17682/myapplication.example.com.ndk_demo D/sakura_test: I`m from publicFunc sakura

    memory list modules

    流出加載的 so 庫

    10、Frida 反調試反反調試

    這一節的主要內容就是關于反調試的原理和如何破解反調試,重要內容還是看文章理解即可。
    因為我并不需要做反調試相關的工作,所以部分內容略過。

    Frida 反調試反反調試 基本思路
    (Java層API、Native層API、Syscall)

    • AntiFrida:https://github.com/qtfreet00/AntiFrida
    • frida-detection-demo:https://github.com/b-mueller/frida-detection-demo
    • 多種特征檢測Frida:https://bbs.pediy.com/thread-217482.htm
    • 來自高維的對抗 - 逆向TinyTool自制:https://yq.aliyun.com/articles/71120
    • Unicorn 在 Android 的應用:https://bbs.pediy.com/thread-253868.htm

    11、Frida native hook : 符號 hook JNI、art&libc

    11.1 Native函數的Java Hook及主動調用

    對 native 函數的 java 層 hook 和主動調用和普通 java 函數完全一致,略過。

    11.2 jni.h 頭文件導入

    導入 jni.h,先 search 一下這個文件在哪。

    Error /Users/sakura/Library/Android/sdk/ndk-bundle/sysroot/usr/include/jni.h,27: Can't open include file 'stdarg.h' Total 1 errors Caching 'Exports'... ok

    報錯,所以拷貝一份 jni.h 出來,將這兩個頭文件導入刪掉

    導入成功

    現在就能識別_JNIEnv了,如圖

    11.3 JNI 函數符號 hook

    先查看一下導出了哪些函數。

    extern "C" JNIEXPORT jstring JNICALL Java_myapplication_example_com_ndk_1demo_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";LOGD("sakura1328");return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jstring JNICALL Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI(JNIEnv *env, jobject instance,jstring context_) {const char *context = env->GetStringUTFChars(context_, 0);int context_size = env->GetStringUTFLength(context_);if (context_size > 0) {LOGD("%s\n", context);}env->ReleaseStringUTFChars(context_, context);return env->NewStringUTF("sakura1328"); }

    這里有幾個需要的 API。

    • 首先是找到是否 so 被加載,通過?Process.enumerateModules(),這個API可以枚舉被加載到內存的 modules。
    • 然后通過Module.findBaseAddress(module name)來查找要hook的函數所在的so的基地址,如果找不到就返回null。
    • 然后可以通過findExportByName(moduleName: string, exportName: string): NativePointer來查找導出函數的絕對地址。如果不知道moduleName是什么,可以傳入一個null進入,但是會花費一些時間遍歷所有的module。如果找不到就返回null。
    • 找到地址之后,就可以攔截function/instruction的執行。通過Interceptor.attach。使用方法見下代碼。
    • 另外為了將jstring的值打印出來,可以使用jenv的函數getStringUtfChars,就像正常的寫native程序一樣。
      Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()

    這里是循環調用的 string_with_jni,如果不循環調用,就要主動調用一下這個函數,或者 hook dlopen。

    hook dlopen 的方法(?https://github.com/lasting-yang/frida_dump/blob/master/dump_dex.js )可以參考。

    function hook_native() {// console.log(JSON.stringify(Process.enumerateModules()));var libnative_addr = Module.findBaseAddress("libnative-lib.so")console.log("libnative_addr is: " + libnative_addr)if (libnative_addr) {var string_with_jni_addr = Module.findExportByName("libnative-lib.so", "Java_myapplication_example_com_ndk_1demo_MainActivity_stringWithJNI")console.log("string_with_jni_addr is: " + string_with_jni_addr)}Interceptor.attach(string_with_jni_addr, {onEnter: function (args) {console.log("string_with_jni args: " + args[0], args[1], args[2])console.log(Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())},onLeave: function (retval) {console.log("retval:", retval)console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())var newRetval = Java.vm.getEnv().newStringUtf("new retval from hook_native");retval.replace(ptr(newRetval));}}) } libnative_addr is: 0x7a0842f000 string_with_jni_addr is: 0x7a08436194 [Google Pixel::myapplication.example.com.ndk_demo]-> string_with_jni args: 0x7a106cc1c0 0x7ff0b71da4 0x7ff0b71da8 sakura retval: 0x75 sakura1328

    這里還寫了一個 hook env 里的 GetStringUTFChars 的代碼,和上面一樣,不贅述了。

    function hook_art(){var addr_GetStringUTFChars = null;//console.log( JSON.stringify(Process.enumerateModules()));var symbols = Process.findModuleByName("libart.so").enumerateSymbols();for(var i = 0;i<symbols.length;i++){var symbol = symbols[i].name;if((symbol.indexOf("CheckJNI")==-1)&&(symbol.indexOf("JNI")>=0)){if(symbol.indexOf("GetStringUTFChars")>=0){console.log(symbols[i].name);console.log(symbols[i].address);addr_GetStringUTFChars = symbols[i].address;}}}console.log("addr_GetStringUTFChars:", addr_GetStringUTFChars);Java.perform(function (){Interceptor.attach(addr_GetStringUTFChars, {onEnter: function (args) {console.log("addr_GetStringUTFChars OnEnter args[0],args[1]",args[0],args[1]);//console.log(hexdump(args[0].readPointer()));//console.log(Java.vm.tryGetEnv().getStringUtfChars(args[0]).readCString()); }, onLeave: function (retval) {console.log("addr_GetStringUTFChars OnLeave",ptr(retval).readCString());}})}) }

    11.4 JNI 函數參數、返回值打印和替換

    • libc 函數符號 hook
    • libc 函數參數、返回值打印和替換
      hook libc 的也和上面的完全一樣,也不贅述了。
      所以看到這里,究其本質就是找到導出符號和它所在的so基地址了。
    function hook_libc(){var pthread_create_addr = null;var symbols = Process.findModuleByName("libc.so").enumerateSymbols();for(var i = 0;i<symbols.length;i++){var symbol = symbols[i].name;if(symbol.indexOf("pthread_create")>=0){//console.log(symbols[i].name);//console.log(symbols[i].address);pthread_create_addr = symbols[i].address;}}console.log("pthread_create_addr,",pthread_create_addr);Interceptor.attach(pthread_create_addr,{onEnter:function(args){console.log("pthread_create_addr args[0],args[1],args[2],args[3]:",args[0],args[1],args[2],args[3]);},onLeave:function(retval){console.log("retval is:",retval)}}) }

    12、Frida native hook : JNI_Onload / 動態注冊 / inline_hook / native層調用棧打印

    https://github.com/android/ndk-samples

    12.1 JNI_Onload / 動態注冊原理

    JNI_Onload / 動態注冊 / Frida hook RegisterNative

    • JNI與動態注冊:https://zhuanlan.kanxue.com/article-4482.htm
    • native 方法的動態注冊:https://eternalsakura13.com/2018/02/08/jni2/
    • Frida hook art:https://github.com/lasting-yang/frida_hook_libart

    詳細的內容參見我寫的文章,這里只給出例子。

    Log.d(TAG,stringFromJNI2()); public native String stringFromJNI2(); JNIEXPORT jstring JNICALL stringFromJNI2(JNIEnv *env,jclass clazz) {jclass testClass = env->FindClass("myapplication/example/com/ndk_demo/Test");jfieldID publicStaticField = env->GetStaticFieldID(testClass, "publicStaticField","Ljava/lang/String;");jstring publicStaticFieldValue = (jstring) env->GetStaticObjectField(testClass,publicStaticField);const char *value_ptr = env->GetStringUTFChars(publicStaticFieldValue, NULL);LOGD("now content is %s", value_ptr);std::string hello = "Hello from C++ stringFromJNI2";return env->NewStringUTF(hello.c_str()); } ... JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env;vm->GetEnv((void **) &env, JNI_VERSION_1_6);JNINativeMethod methods[] = {{"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2},};env->RegisterNatives(env->FindClass("myapplication/example/com/ndk_demo/MainActivity"), methods,1);return JNI_VERSION_1_6; }

    12.2 Frida hook RegisterNative

    使用下面這個腳本來打印出 RegisterNatives 的參數,這里需要注意的是使用了enumerateSymbolsSync,它是 enumerateSymbols 的同步版本。
    另外和我們之前通過?Java.vm.tryGetEnv().getStringUtfChars來調用env里的方法不同。
    這里則是通過將之前找到的getStringUtfChars函數地址和參數信息封裝起來,直接調用,具體的原理我沒有深入分析,先記住用法。
    原理其實是一樣的,都是?根據符號找到地址,然后hook符號地址,然后打印參數

    declare const NativeFunction: NativeFunctionConstructor;interface NativeFunctionConstructor {new(address: NativePointerValue, retType: NativeType, argTypes: NativeType[], abiOrOptions?: NativeABI | NativeFunctionOptions): NativeFunction;readonly prototype: NativeFunction; } ... var funcGetStringUTFChars = new NativeFunction(addrGetStringUTFChars, "pointer", ["pointer", "pointer", "pointer"]); var ishook_libart = false;function hook_libart() {if (ishook_libart === true) {return;}var symbols = Module.enumerateSymbolsSync("libart.so");var addrGetStringUTFChars = null;var addrNewStringUTF = null;var addrFindClass = null;var addrGetMethodID = null;var addrGetStaticMethodID = null;var addrGetFieldID = null;var addrGetStaticFieldID = null;var addrRegisterNatives = null;var addrAllocObject = null;var addrCallObjectMethod = null;var addrGetObjectClass = null;var addrReleaseStringUTFChars = null;for (var i = 0; i < symbols.length; i++) {var symbol = symbols[i];if (symbol.name == "_ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh") {addrGetStringUTFChars = symbol.address;console.log("GetStringUTFChars is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc") {addrNewStringUTF = symbol.address;console.log("NewStringUTF is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI9FindClassEP7_JNIEnvPKc") {addrFindClass = symbol.address;console.log("FindClass is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_") {addrGetMethodID = symbol.address;console.log("GetMethodID is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_") {addrGetStaticMethodID = symbol.address;console.log("GetStaticMethodID is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_") {addrGetFieldID = symbol.address;console.log("GetFieldID is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_") {addrGetStaticFieldID = symbol.address;console.log("GetStaticFieldID is at ", symbol.address, symbol.name);} else if (symbol.name == "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi") {addrRegisterNatives = symbol.address;console.log("RegisterNatives is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("_ZN3art3JNI11AllocObjectEP7_JNIEnvP7_jclass") >= 0) {addrAllocObject = symbol.address;console.log("AllocObject is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("_ZN3art3JNI16CallObjectMethodEP7_JNIEnvP8_jobjectP10_jmethodIDz") >= 0) {addrCallObjectMethod = symbol.address;console.log("CallObjectMethod is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("_ZN3art3JNI14GetObjectClassEP7_JNIEnvP8_jobject") >= 0) {addrGetObjectClass = symbol.address;console.log("GetObjectClass is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("_ZN3art3JNI21ReleaseStringUTFCharsEP7_JNIEnvP8_jstringPKc") >= 0) {addrReleaseStringUTFChars = symbol.address;console.log("ReleaseStringUTFChars is at ", symbol.address, symbol.name);}}if (addrRegisterNatives != null) {Interceptor.attach(addrRegisterNatives, {onEnter: function (args) {console.log("[RegisterNatives] method_count:", args[3]);var env = args[0];var java_class = args[1];var funcAllocObject = new NativeFunction(addrAllocObject, "pointer", ["pointer", "pointer"]);var funcGetMethodID = new NativeFunction(addrGetMethodID, "pointer", ["pointer", "pointer", "pointer", "pointer"]);var funcCallObjectMethod = new NativeFunction(addrCallObjectMethod, "pointer", ["pointer", "pointer", "pointer"]);var funcGetObjectClass = new NativeFunction(addrGetObjectClass, "pointer", ["pointer", "pointer"]);var funcGetStringUTFChars = new NativeFunction(addrGetStringUTFChars, "pointer", ["pointer", "pointer", "pointer"]);var funcReleaseStringUTFChars = new NativeFunction(addrReleaseStringUTFChars, "void", ["pointer", "pointer", "pointer"]);var clz_obj = funcAllocObject(env, java_class);var mid_getClass = funcGetMethodID(env, java_class, Memory.allocUtf8String("getClass"), Memory.allocUtf8String("()Ljava/lang/Class;"));var clz_obj2 = funcCallObjectMethod(env, clz_obj, mid_getClass);var cls = funcGetObjectClass(env, clz_obj2);var mid_getName = funcGetMethodID(env, cls, Memory.allocUtf8String("getName"), Memory.allocUtf8String("()Ljava/lang/String;"));var name_jstring = funcCallObjectMethod(env, clz_obj2, mid_getName);var name_pchar = funcGetStringUTFChars(env, name_jstring, ptr(0));var class_name = ptr(name_pchar).readCString();funcReleaseStringUTFChars(env, name_jstring, name_pchar);//console.log(class_name);var methods_ptr = ptr(args[2]);var method_count = parseInt(args[3]);for (var i = 0; i < method_count; i++) {var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));var name = Memory.readCString(name_ptr);var sig = Memory.readCString(sig_ptr);var find_module = Process.findModuleByAddress(fnPtr_ptr);console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));}},onLeave: function (retval) { }});}ishook_libart = true; }hook_libart();

    結果很明顯的打印了出來,包括動態注冊的函數的名字,函數簽名,加載地址和在so里的偏移量,

    [RegisterNatives] java_class: myapplication.example.com.ndk_demo.MainActivity name: stringFromJNI2 sig: ()Ljava/lang/String; fnPtr: 0x79f8698484 module_name: libnative-lib.so module_base: 0x79f8691000 offset: 0x7484

    最后測試一下 yang 開源的一個hook art的腳本,很有意思,trace 出了非常多的需要的信息。

    frida -U --no-pause -f package_name -l hook_art.js ... [FindClass] name:myapplication/example/com/ndk_demo/Test [GetStaticFieldID] name:publicStaticField, sig:Ljava/lang/String; [GetStringUTFChars] result:i am a publicStaticField [NewStringUTF] bytes:Hello from C++ stringFromJNI2 [GetStringUTFChars] result:sakura

    12.3 native 層調用棧打印

    直接使用 frida 提供的接口打印棧回溯。

    Interceptor.attach(f, {onEnter: function (args) {console.log('RegisterNatives called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');} });

    效果如下,我加到了 hook registerNative 的地方。

    [Google Pixel::myapplication.example.com.ndk_demo]-> RegisterNatives called from: 0x7a100be03c libart.so!0xe103c 0x7a100be038 libart.so!0xe1038 0x79f85699a0 libnative-lib.so!_ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi+0x44 0x79f85698e0 libnative-lib.so!JNI_OnLoad+0x90 0x7a102b9fd4 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP8_jstringPS9_+0x638 0x7a08e3820c libopenjdkjvm.so!JVM_NativeLoad+0x110 0x70b921c4 boot.oat!oatexec+0xa81c4

    12.4 主動調用去進行方法參數替換

    使用?Interceptor.replace,不贅述。主要目的還是為了改掉函數原本的執行行為,而不是僅僅打印一些信息。

    12.5 inline hook ( so庫里面的函數?)

    inline hook 簡單理解就是:不是 hook 函數開始執行的地方,而是 hook 函數中間執行的指令
    整體來說沒什么區別,就是把找函數符號地址改成從so里找到偏移,然后加到so基地址上就行,注意一下它的 attach 的 callback。

    /*** Callback to invoke when an instruction is about to be executed.*/ type InstructionProbeCallback = (this: InvocationContext, args: InvocationArguments) => void; type InvocationContext = PortableInvocationContext | WindowsInvocationContext | UnixInvocationContext;interface PortableInvocationContext {/*** Return address.*/returnAddress: NativePointer;/*** CPU registers. You may also update register values by assigning to these keys.*/context: CpuContext;/*** OS thread ID.*/threadId: ThreadId;/*** Call depth of relative to other invocations.*/depth: number;/*** User-defined invocation data. Useful if you want to read an argument in `onEnter` and act on it in `onLeave`.*/[x: string]: any; } ... ... interface Arm64CpuContext extends PortableCpuContext {x0: NativePointer;x1: NativePointer;x2: NativePointer;x3: NativePointer;x4: NativePointer;x5: NativePointer;x6: NativePointer;x7: NativePointer;x8: NativePointer;x9: NativePointer;x10: NativePointer;x11: NativePointer;x12: NativePointer;x13: NativePointer;x14: NativePointer;x15: NativePointer;x16: NativePointer;x17: NativePointer;x18: NativePointer;x19: NativePointer;x20: NativePointer;x21: NativePointer;x22: NativePointer;x23: NativePointer;x24: NativePointer;x25: NativePointer;x26: NativePointer;x27: NativePointer;x28: NativePointer;fp: NativePointer;lr: NativePointer; }

    我的 so 是自己編譯的,具體的匯編代碼如下,總之這里很明顯在 775C 時,x0 里保存的是一個指向 "sakura"?這個字符串的指針。(其實我也不是很看得懂 arm64 了已經,就隨便 hook 了一下)
    所以 hook 這個指令,然后?Memory.readCString(this.context.x0);打印出來,結果如下

    .text:000000000000772C ; __unwind { .text:000000000000772C SUB SP, SP, #0x40 .text:0000000000007730 STP X29, X30, [SP,#0x30+var_s0] .text:0000000000007734 ADD X29, SP, #0x30 .text:0000000000007738 ; 6: v6 = a1; .text:0000000000007738 MOV X8, XZR .text:000000000000773C STUR X0, [X29,#var_8] .text:0000000000007740 ; 7: v5 = a3; .text:0000000000007740 STUR X1, [X29,#var_10] .text:0000000000007744 STR X2, [SP,#0x30+var_18] .text:0000000000007748 ; 8: v4 = (const char *)_JNIEnv::GetStringUTFChars(a1, a3, 0LL); .text:0000000000007748 LDUR X0, [X29,#var_8] .text:000000000000774C LDR X1, [SP,#0x30+var_18] .text:0000000000007750 MOV X2, X8 .text:0000000000007754 BL ._ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *) .text:0000000000007758 STR X0, [SP,#0x30+var_20] .text:000000000000775C ; 9: if ( (signed int)_JNIEnv::GetStringUTFLength(v6, v5) > 0 ) .text:000000000000775C LDUR X0, [X29,#var_8] .text:0000000000007760 LDR X1, [SP,#0x30+var_18] function inline_hook() {var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");if (libnative_lib_addr) {console.log("libnative_lib_addr:", libnative_lib_addr);var addr_775C = libnative_lib_addr.add(0x775C);console.log("addr_775C:", addr_775C);Java.perform(function () {Interceptor.attach(addr_775C, {onEnter: function (args) {var name = this.context.x0.readCString()console.log("addr_775C OnEnter :", this.returnAddress, name);},onLeave: function (retval) {console.log("retval is :", retval) }})})} } setImmediate(inline_hook()) Attaching... libnative_lib_addr: 0x79fabe0000 addr_775C: 0x79fabe775c TypeError: cannot read property 'apply' of undefinedat [anon] (../../../frida-gum/bindings/gumjs/duktape.c:56618)at frida/runtime/core.js:55 [Google Pixel::myapplication.example.com.ndk_demo]-> addr_775C OnEnter : 0x79fabe7758 sakura addr_775C OnEnter : 0x79fabe7758 sakura

    到這里已經可以總結一下我目前的學習了,需要補充一些 frida api 的學習,比如 NativePointr 里居然有個 readCString,這些 API 是需要再看看的。

    13、Frida native hook : Frida hook native app 實戰

    • 破解 Frida 全端口檢測的 native層反調試
      • hook libc 的 pthread_create 函數
    • 破解 TracePid 的 native 反調試
      • target:Android反調試技術整理與實踐:https://gtoad.github.io/2017/06/25/Android-Anti-Debug/
      • solve : hook libc的fgets函數
    • native 層修改參數、返回值
    • 靜態分析 JNI_Onload
    • 動態 trace 主動注冊 & IDA溯源
    • 動態trace JNI、libc函數 & IDA溯源
    • native層主動調用、打調用棧
    • 主動調用 libc 讀寫文件

    首先看下 logcat

    n/u0a128 for activity com.gdufs.xman/.MainActivity 12-28 05:53:26.898 26615 26615 V com.gdufs.xman: JNI_OnLoad() 12-28 05:53:26.898 26615 26615 V com.gdufs.xman: RegisterNatives() --> nativeMethod() ok 12-28 05:53:26.898 26615 26615 D com.gdufs.xman m=: 0 12-28 05:53:26.980 26615 26615 D com.gdufs.xman m=: Xman

    sakura@sakuradeMacBook-Pro:~/gitsource/frida-agent-example/agent$ frida -U --no-pause -f com.gdufs.xman -l hook_reg.js ... [Google Pixel::com.gdufs.xman]-> [RegisterNatives] method_count: 0x3 [RegisterNatives] java_class: com.gdufs.xman.MyApp name: initSN sig: ()V fnPtr: 0xd4ddf3b1 module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x13b1 [RegisterNatives] java_class: com.gdufs.xman.MyApp name: saveSN sig: (Ljava/lang/String;)V fnPtr: 0xd4ddf1f9 module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x11f9 [RegisterNatives] java_class: com.gdufs.xman.MyApp name: work sig: ()V fnPtr: 0xd4ddf4cd module_name: libmyjni.so module_base: 0xd4dde000 offset: 0x14cd

    initSN

    感覺意思應該是從?/sdcard/reg.dat?里讀一個值,然后和?EoPAoY62@ElRD?進行比較。
    最后setValue,從導出函數看一下,最后推測第一個參數應該是JNIEnv *env,然后就看到了給字段m賦值。

    aveSN

    這個看上去就是根據str的值,去變換”W3_arE_whO_we_ARE”字符串,然后寫入到/sdcard/reg.dat里

    結合一下看,只要initSN檢查到/sdcard/reg.dat里是EoPAoY62@ElRD,應該就會給m設置成1。
    只要m的值是1,就能走到work()函數的邏輯。

    參考:?frida 的 file api:https://frida.re/docs/javascript-api/#file

    function main() {var file = new File("/sdcard/reg.dat",'w')file.write("EoPAoY62@ElRD")file.flush()file.close() } setImmediate(main())

    這樣我們繼續看 work 的邏輯

    v2 是從 getValue 得到的,看上去就是 m字段的值,此時應該是1,一會 hook 一下看看。

    [NewStringUTF] bytes:輸入即是flag,格式為xman{……}!

    callWork 里又調用了 work 函數,死循環了。

    那看來看去最后還是回到了initSN,那其實我們看的順序似乎錯了。
    理一下邏輯,n2執行完保存到文件,然后n1 check一下,所以最后還是要逆n2的算法,pass。

    14、Frida trace 四件套

    14.1 jni trace : trace jni

    https://github.com/chame1eon/jnitrace

    pip install jnitraceRequirement already satisfied: frida>=12.5.0 in /Users/sakura/.pyenv/versions/3.7.7/lib/python3.7/site-packages (from jnitrace) (12.8.0) Requirement already satisfied: colorama in /Users/sakura/.pyenv/versions/3.7.7/lib/python3.7/site-packages (from jnitrace) (0.4.3) Collecting hexdump (from jnitrace)Downloading https://files.pythonhosted.org/packages/55/b3/279b1d57fa3681725d0db8820405cdcb4e62a9239c205e4ceac4391c78e4/hexdump-3.3.zip Installing collected packages: hexdump, jnitraceRunning setup.py install for hexdump ... doneRunning setup.py install for jnitrace ... done Successfully installed hexdump-3.3 jnitrace-3.0.8

    usage:?jnitrace [options] -l libname target
    默認應該是 spawn 運行的,

    • -m?來指定是?spawn?還是?attach
    • -b?指定是?fuzzy?還是?accurate
    • -i <regex>?指定一個正則表達式來過濾出方法名,例如?-i Get -i RegisterNatives?就會只打印出名字里包含 Get 或者 RegisterNatives 的 JNI methods。
    • -e <regex>?和?-i?相反,同樣通過正則表達式來過濾,但這次會將指定的內容忽略掉。
    • -I <string>?trace 導出的方法,jnitrace 認為導出的函數應該是從 Java 端能夠直接調用的函數,所以可以包括使用 RegisterNatives 來注冊的函數,例如?-I stringFromJNI -I nativeMethod([B)V,就包括導出名里有 stringFromJNI,以及使用 RegisterNames 來注冊,并帶有 nativeMethod([B)V 簽名的函數。
    • -o path/output.json,導出輸出到文件里。
    • -p path/to/script.js,用于在加載 jnitrace 腳本之前將指定路徑的 Frida 腳本加載到目標進程中,這可以用于在 jnitrace 啟動之前對抗反調試。
    • -a path/to/script.js,用于在加載 jnitrace 腳本之后將指定路徑的 Frida 腳本加載到目標進程中
    • --ignore-env,不打印所有的 JNIEnv 函數
    • --ignore-vm,不打印所有的 JavaVM 函數
    sakura@sakura:~/Desktop/frida_learn/lib/armeabi-v7a$ jnitrace -l libmyjni.so com.gdufs.xman Tracing. Press any key to quit... Traced library "libmyjni.so" loaded from path "/data/app/com.gdufs.xman-X0HkzLhbptSc0tjGZ3yQ2g==/lib/arm"./* TID 28890 */355 ms [+] JavaVM->GetEnv355 ms |- JavaVM* : 0xefe99140355 ms |- void** : 0xda13e028355 ms |: 0xeff312a0355 ms |- jint : 65542355 ms |= jint : 0355 ms ------------------------Backtrace------------------------355 ms |-> 0xda13a51b: JNI_OnLoad+0x12 (libmyjni.so:0xda139000)/* TID 28890 */529 ms [+] JNIEnv->FindClass529 ms |- JNIEnv* : 0xeff312a0529 ms |- char* : 0xda13bdef529 ms |: com/gdufs/xman/MyApp529 ms |= jclass : 0x81 { com/gdufs/xman/MyApp }529 ms ------------------------Backtrace------------------------529 ms |-> 0xda13a539: JNI_OnLoad+0x30 (libmyjni.so:0xda139000)/* TID 28890 */584 ms [+] JNIEnv->RegisterNatives584 ms |- JNIEnv* : 0xeff312a0584 ms |- jclass : 0x81 { com/gdufs/xman/MyApp }584 ms |- JNINativeMethod* : 0xda13e004584 ms |: 0xda13a3b1 - initSN()V584 ms |: 0xda13a1f9 - saveSN(Ljava/lang/String;)V584 ms |: 0xda13a4cd - work()V584 ms |- jint : 3584 ms |= jint : 0584 ms ------------------------Backtrace------------------------584 ms |-> 0xda13a553: JNI_OnLoad+0x4a (libmyjni.so:0xda139000)/* TID 28890 */638 ms [+] JNIEnv->FindClass638 ms |- JNIEnv* : 0xeff312a0638 ms |- char* : 0xda13bdef638 ms |: com/gdufs/xman/MyApp638 ms |= jclass : 0x71 { com/gdufs/xman/MyApp }638 ms -----------------------Backtrace-----------------------638 ms |-> 0xda13a377: setValue+0x12 (libmyjni.so:0xda139000)/* TID 28890 */688 ms [+] JNIEnv->GetStaticFieldID688 ms |- JNIEnv* : 0xeff312a0688 ms |- jclass : 0x71 { com/gdufs/xman/MyApp }688 ms |- char* : 0xda13be04688 ms |: m688 ms |- char* : 0xda13be06688 ms |: I688 ms |= jfieldID : 0xf1165004 { m:I }688 ms -----------------------Backtrace-----------------------688 ms |-> 0xda13a38d: setValue+0x28 (libmyjni.so:0xda139000)

    14.2 strace : trace syscall

    strace 跟蹤進程中的系統調用:https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/strace.html

    14.3 frida-trace : trace libc(or more)

    ? ? ? ? frida-trace:https://frida.re/docs/frida-trace/

    ? ? ? ? Usage:frida-trace [options] target

    frida-trace -U -i "strcmp" -f com.gdufs.xman ...5634 ms strcmp(s1="fi", s2="es-US")5635 ms strcmp(s1="da", s2="es-US")5635 ms strcmp(s1="es", s2="es-US")5635 ms strcmp(s1="eu-ES", s2="es-US")5635 ms strcmp(s1="et-EE", s2="es-US")5635 ms strcmp(s1="et-EE", s2="es-US")

    art trace

    hook artmethod:https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_artmethod.js

    14.4 hook_artmethod : trace java 函數調用

    https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_artmethod.js

    14.5 修改AOSP源碼打印

    改 aosp 源碼 trace 信息:https://bbs.pediy.com/thread-255653-1.htm

    15、Frida native hook : init_array 開發和自動化逆向

    15.1 init_array原理 (?so 加載、啟動、執行 )

    常見的保護都會在 init_array 里面做,關于其原理,主要閱讀以下文章即可。

    • IDA 調試 android so 的 .init_array 數組:https://www.cnblogs.com/bingghost/p/6297325.html
    • Android NDK中.init段和.init_array段函數的定義方式:https://www.dllhook.com/post/213.html
    • Linker 學習筆記( so 加載、啟動、執行):https://wooyun.js.org/drops/Android Linker學習筆記.html

    15.2 IDA靜態分析 init_array

    // 編譯生成后在.init段 [名字不可更改] extern "C" void _init(void) {LOGD("Enter init......"); }// 編譯生成后在.init_array段 [名字可以更改] __attribute__((__constructor__)) static void sakura_init() {LOGD("Enter sakura_init......"); } ... ... 2016-12-29 16:51:23.017 5160-5160/com.example.ndk_demo D/sakura1328: Enter init...... 2016-12-29 16:51:23.017 5160-5160/com.example.ndk_demo D/sakura1328: Enter sakura_init......

    IDA快捷鍵shift+F7找到segment,然后就可以找到.init_array段,然后就可以找到里面保存的函數地址。

    15.3 IDA 動態調試 so

    • 打開要調試的 apk,找到入口

    sakura@sakura:~/.gradle/caches$ adb shell dumpsys activity top | grep TASK TASK com.android.systemui id=29 userId=0 TASK null id=26 userId=0 TASK com.example.ndk_demo id=161 userId=0
    • 啟動 apk,并讓設備將處于一個Waiting For Debugger的狀態
      ? ? ? ? adb shell am start -D -n com.example.ndk_demo/.MainActivity
    • 執行 android_server64
    sailfish:/data/local/tmp # ./android_server64 IDA Android 64-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017 Listening on 0.0.0.0:23946...
    • 新開一個窗口使用forward程序進行端口轉發:adb forward tcp:23946 tcp:23946

    adb forward tcp:<本地機器的網絡端口號> tcp:<模擬器或是真機的網絡端口號>

    例:adb [-d|-e|-s?] forward tcp:6100 tcp:7100 表示把本機的6100端口號與模擬器的7100端口建立起相關,當模擬器或真機向自己的7100端口發送了數據,那們我們可以在本機的6100端口讀取其發送的內容,這是一個很關鍵的命令,以后我們使用jdb調試apk之前,就要用它先把目標進程和本地端口建立起關聯

    • 打開IDA,選擇菜單Debugger -> Attach -> Remote ARM Linux/Android debugger

    • 打開IDA,選擇菜單Debugger -> Process options, 填好,然后選擇進程去attach。

    • 查看待調試的進程?adb jdwp
    sakura@sakuradeMacBook-Pro:~$ adb jdwp 10436
    • 轉發端口adb forward tcp:8700 jdwp:10436,將該進程的調試端口和本機的8700綁定。

    • jdb連接調試端口,從而讓程序繼續運行?jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

    • 找到斷點并斷下。

    打開 module

    找到 linker64

    找到 call array 函數

    下斷,并按 F9 斷下

    最終我確實可以調試到?.init_array?的初始化,具體的代碼分析見?Linker學習筆記?這里。

    15.4 init_array && JNI_Onload "自吐"

    JNI_Onload

    目標是找到動態注冊的函數的地址,因為這種函數沒有導出。

    JNINativeMethod methods[] = {{"stringFromJNI2", "()Ljava/lang/String;", (void *) stringFromJNI2}, }; env->RegisterNatives(env->FindClass("com/example/ndk_demo/MainActivity"), methods, 1);

    首先:jnitrace -m spawn -i "RegisterNatives" -l libnative-lib.so com.example.ndk_demo

    525 ms [+] JNIEnv->RegisterNatives 525 ms |- JNIEnv* : 0x7a106cc1c0 525 ms |- jclass : 0x89 { com/example/ndk_demo/MainActivity } 525 ms |- JNINativeMethod* : 0x7ff0b71120 525 ms |: 0x79f00d36b0 - stringFromJNI2()Ljava/lang/String;

    然后:objection -d -g com.example.ndk_demo run memory list modules explore | grep demo

    sakura@sakuradeMacBook-Pro:~$ objection -d -g com.example.ndk_demo run memory list modules explore | grep demo [debug] Attempting to attach to process: `com.example.ndk_demo` Warning: Output is not to a terminal (fd=1). base.odex 0x79f0249000 106496 (104.0 KiB) /data/app/com.example.ndk_demo-HGAFhnKyKCSIpzn227pwXw==/oat/arm64/base.odex libnative-lib.so 0x79f00c4000 221184 (216.0 KiB) /data/app/com.example.ndk_demo-HGAFhnKyKCSIpzn227pwXw==/lib/arm64/libnative...

    offset = 0x79f00d36b0 - 0x79f00c4000 = 0xf6b0

    這樣就找到了

    init_array

    沒有支持 arm64,可以在安裝 app 的時候?adb install --abi armeabi-v7a?強制讓 app 運行在32位模式

    這個腳本整體來說就是 hook callfunction,然后打印出 init_array 里面的函數地址和參數等。

    從源碼看,關鍵就是 call_array 這里調用的 call_function,第一個參數代表這是注冊的 init_array 里面的 function,第二個參數則是 init_array 里存儲的函數的地址。

    template <typename F> static void call_array(const char* array_name __unused,F* functions,size_t count,bool reverse,const char* realpath) {if (functions == nullptr) {return;}TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);int begin = reverse ? (count - 1) : 0;int end = reverse ? -1 : count;int step = reverse ? -1 : 1;for (int i = begin; i != end; i += step) {TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);call_function("function", functions[i], realpath);}TRACE("[ Done calling %s for '%s' ]", array_name, realpath); } function LogPrint(log) {var theDate = new Date();var hour = theDate.getHours();var minute = theDate.getMinutes();var second = theDate.getSeconds();var mSecond = theDate.getMilliseconds()hour < 10 ? hour = "0" + hour : hour;minute < 10 ? minute = "0" + minute : minute;second < 10 ? second = "0" + second : second;mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;var time = hour + ":" + minute + ":" + second + ":" + mSecond;var threadid = Process.getCurrentThreadId();console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);}function hooklinker() {var linkername = "linker";var call_function_addr = null;var arch = Process.arch;LogPrint("Process run in:" + arch);if (arch.endsWith("arm")) {linkername = "linker";} else {linkername = "linker64";LogPrint("arm64 is not supported yet!");}var symbols = Module.enumerateSymbolsSync(linkername);for (var i = 0; i < symbols.length; i++) {var symbol = symbols[i];//LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {call_function_addr = symbol.address;LogPrint("linker->" + symbol.name + "---" + symbol.address)}}if (call_function_addr != null) {var func_call_function = new NativeFunction(call_function_addr, 'void', ['pointer', 'pointer', 'pointer']);Interceptor.replace(new NativeFunction(call_function_addr,'void', ['pointer', 'pointer', 'pointer']), new NativeCallback(function (arg0, arg1, arg2) {var functiontype = null;var functionaddr = null;var sopath = null;if (arg0 != null) {functiontype = Memory.readCString(arg0);}if (arg1 != null) {functionaddr = arg1;}if (arg2 != null) {sopath = Memory.readCString(arg2);}var modulebaseaddr = Module.findBaseAddress(sopath);LogPrint("after load:" + sopath + "--start call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);if (sopath.indexOf('libnative-lib.so') >= 0 && functiontype == "DT_INIT") {LogPrint("after load:" + sopath + "--ignore call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);} else {func_call_function(arg0, arg1, arg2);LogPrint("after load:" + sopath + "--end call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);}}, 'void', ['pointer', 'pointer', 'pointer']));} }setImmediate(hooklinker)

    我調試了一下 linker64,因為沒有導出 call_function 的地址,所以不能直接 hook 符號名,而是要根據偏移去 hook,以后再說。
    其實要看?init_array,直接 shift+F7 去 segment 里面找?.init_array?段就可以了,這里主要是為了反反調試,因為可能反調試會加在 init_array 里,hook call_function 就可以讓它不加載反調試程序。

    15.5 native層未導出函數主動調用(任意符號和地址)

    現在我想要主動調用 sakura_add 來打印值,可以 ida 打開找符號,或者根據偏移,總之最終用這個 NativePointer 指針來初始化一個 NativeFunction 來調用。

    extern "C" JNIEXPORT jint JNICALL Java_com_example_ndk_1demo_MainActivity_sakuraWithInt(JNIEnv *env, jobject thiz, jint a, jint b) {// TODO: implement sakuraWithInt()return sakura_add(a,b); } ... int sakura_add(int a, int b){int sum = a+b;LOGD("sakura add a+b:",sum);return sum; }

    function main() {var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");console.log("libnative_lib_addr is :", libnative_lib_addr);if (libnative_lib_addr) {var sakura_add_addr1 = Module.findExportByName("libnative-lib.so", "_Z10sakura_addii");var sakura_add_addr2 = libnative_lib_addr.add(0x0F56C) ;console.log("sakura_add_addr1 ", sakura_add_addr1);console.log("sakura_add_addr2 ", sakura_add_addr2)}var sakura_add1 = new NativeFunction(sakura_add_addr1, "int", ["int", "int"]);var sakura_add2 = new NativeFunction(sakura_add_addr2, "int", ["int", "int"]);console.log("sakura_add1 result is :", sakura_add1(200, 33));console.log("sakura_add2 result is :", sakura_add2(100, 133)); } setImmediate(main()) ... ... libnative_lib_addr is : 0x79fa1c5000 sakura_add_addr1 0x79fa1d456c sakura_add_addr2 0x79fa1d456c sakura_add1 result is : 233 sakura_add2 result is : 233

    16、C/C++ hook

    //todo

    16.1 Native/JNI層參數打印和主動調用參數構造

    jni的基本類型要通過調用jni相關的api轉化成c++對象,才能打印和調用。
    jni主動調用的時候,參數構造有兩種方式,一種是Java.vm.getenv,另一種是hook獲取env之后來調用jni相關的api構造參數。

    16.2 C/C++編成 so 并引入 Frida 調用其中的函數

    總結

    以上是生活随笔為你收集整理的Frida Android hook的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    国产一级片免费观看 | 国产精品一区二区在线观看 | 日韩中文字幕电影 | 日韩啪啪小视频 | 久保带人 | 日韩高清精品免费观看 | 国产高清一 | 国产成年免费视频 | 91日韩国产 | 99视频免费观看 | av九九九 | 中文字幕色在线视频 | 亚洲欧美成人在线 | 久久久片 | 四虎影视精品成人 | 天天操月月操 | 国产成人久久精品 | 在线中文字母电影观看 | 美女视频一区二区 | 免费看的视频 | 草久在线播放 | 亚洲九九精品 | 久久高视频 | 国产精品黄色 | 欧美一级免费高清 | 中文字幕乱码日本亚洲一区二区 | 国产一二三精品 | 成人中心免费视频 | 久久黄色网| 99自拍视频在线观看 | 91精品欧美一区二区三区 | 日本中文字幕电影在线免费观看 | 国产二区视频在线 | 日日夜夜免费精品 | 天天摸天天舔天天操 | 久久综合九色综合97婷婷女人 | 精品一区二区三区久久久 | 在线电影日韩 | 91av免费在线观看 | 97视频精品| 视频一区二区精品 | 日本一区二区三区免费观看 | 成年人在线看片 | 精品影院一区二区久久久 | 国产麻豆剧果冻传媒视频播放量 | 国产亚洲欧美在线视频 | 国产精品黄色在线观看 | 久产久精国产品 | 超碰免费97 | 999视频在线播放 | 手机在线日韩视频 | 91精品久久久久久久久久久久久 | 久久精品99久久久久久 | 亚洲视屏在线播放 | 国产精品破处视频 | 少妇性bbb搡bbb爽爽爽欧美 | 黄色av影院 | 500部大龄熟乱视频使用方法 | 日韩影视在线观看 | 蜜臀久久99精品久久久酒店新书 | 精品一区在线看 | 婷婷综合av | 99精品国产视频 | 精品999国产 | 久久久国产精品一区二区中文 | 久草精品资源 | www.国产在线视频 | 亚洲理论片在线观看 | 日韩免费大片 | 免费日韩一区二区三区 | 婷婷av网 | 91精品少妇偷拍99 | 久久久香蕉视频 | 深爱激情婷婷网 | 深爱激情久久 | 综合精品在线 | 午夜av不卡 | 午夜精品一区二区三区视频免费看 | 日韩.com| 久久久久黄色 | www.狠狠色.com | 色婷婷激情电影 | 久久天天拍| 久久综合天天 | 久久久综合电影 | 一区二区三区在线观看免费视频 | www.天天色.com | 日韩视频免费 | 奇米网网址 | 黄色片网站 | 日韩免费在线一区 | 国产人成免费视频 | 在线看日韩av | 国产资源免费在线观看 | 欧美ⅹxxxxxx| 天天久久综合 | 国产美腿白丝袜足在线av | 国产精品丝袜久久久久久久不卡 | 国产精品自产拍在线观看蜜 | 日韩av区 | 中文视频在线播放 | 久久韩国免费视频 | 男女免费视频观看 | 国产精品尤物视频 | 天天综合网天天 | 日本婷婷色 | 成人一区不卡 | 国产大片免费久久 | 色婷婷狠| 成人国产电影在线观看 | 日韩免费网址 | 国产青春久久久国产毛片 | 天天综合网在线 | 欧美国产精品久久久久久免费 | 精品在线你懂的 | 色在线免费视频 | 最近中文字幕大全 | 欧美另类老妇 | 久久综合毛片 | 国产一级黄色av | 超碰大片| 久久综合久色欧美综合狠狠 | 亚洲精品福利视频 | 国产成人亚洲在线电影 | 日韩av男人的天堂 | 久草在线国产 | 亚洲 欧美 国产 va在线影院 | 久久精品91视频 | 亚洲黑丝少妇 | 日韩精品中文字幕av | 最近最新mv字幕免费观看 | 91日韩免费| 欧美巨大荫蒂茸毛毛人妖 | 国产 欧美 日产久久 | 日韩欧美网址 | 久久夜色精品国产欧美一区麻豆 | 国产综合香蕉五月婷在线 | 亚洲3级 | 91精品福利在线 | 久久久国内精品 | 亚洲粉嫩av | 日韩国产欧美在线播放 | 亚洲精品午夜久久久久久久久久久 | 天堂视频中文在线 | 免费观看一级 | 亚洲.www | 亚洲国产成人久久综合 | 久久久久成人免费 | www.看片网站| 成人羞羞免费 | 国产精品久久久久久爽爽爽 | 91毛片在线观看 | 国产中文字幕一区 | 日韩在观看线 | 视频在线观看91 | 久久99精品久久久久久清纯直播 | 国产视频亚洲精品 | 久久精品国产免费看久久精品 | 欧美影院久久 | 国产中文字幕一区二区 | 精品国产乱码久久久久久天美 | 99精品视频中文字幕 | 人成午夜视频 | 丁香av在线| 久久综合偷偷噜噜噜色 | 亚洲精品视频在线观看免费视频 | 久久久久成 | 国产精品嫩草影视久久久 | 99国产精品视频免费观看一公开 | 欧美激情精品久久久久久变态 | 国产91精品看黄网站 | 国产专区欧美专区 | 成人av在线直播 | 免费在线观看av片 | 日本精品久久久久中文字幕 | 黄色小网站在线 | 美腿丝袜一区二区三区 | 午夜精品区 | 国产精品一区二区三区四区在线观看 | 久久激情五月激情 | 99热这里只有精品久久 | 日日夜夜91 | 色99在线| 中文av不卡| 在线免费观看黄色小说 | 97超碰在线资源 | 五月天婷婷狠狠 | 在线观看的黄色 | 亚洲天堂激情 | 亚洲妇女av | av电影不卡在线 | 99精品国产兔费观看久久99 | 亚洲a网| 欧美性视频网站 | 999成人免费视频 | 国产精品99久久久久人中文网介绍 | 夜夜爽www | av一级久久 | 亚洲va在线va天堂va偷拍 | 国产黄色免费电影 | 国产一区观看 | 欧美日韩国产精品爽爽 | 日韩精品欧美专区 | 尤物九九久久国产精品的分类 | 免费黄色在线播放 | 999超碰| 999毛片 | 色www永久免费| 免费日p视频 | 国产在线中文 | www.色国产 | 一区二区三区免费网站 | 99re国产视频 | 91福利视频久久久久 | 亚洲一区美女视频在线观看免费 | 国产精品久久久久久久久久免费 | 日韩精品久久久久久中文字幕8 | 欧美日韩不卡在线观看 | 亚洲精品视频网 | 国产精品视频全国免费观看 | 日韩av中文在线 | 国产视频在线观看一区二区 | 色综合a| 99精品视频在线观看播放 | 最新国产精品拍自在线播放 | 日韩在线在线 | 麻豆久久精品 | 中文字幕第 | 91精品国自产在线观看欧美 | 免费国产在线精品 | 婷婷五月色综合 | 91精品国产欧美一区二区成人 | 美女视频网站久久 | 精品国产1区二区 | 日韩欧美在线中文字幕 | www.日日操.com| 国产精久久 | 九九免费在线观看视频 | 一区二区三区在线看 | 国产精品一二三 | 日韩大片在线看 | 激情深爱.com | 久久成人黄色 | 婷婷深爱网 | 久久夜色精品亚洲噜噜国4 午夜视频在线观看欧美 | 午夜精品久久久久久 | 久久69精品久久久久久久电影好 | 国产视频中文字幕 | 国产成人精品福利 | 亚洲成年片 | 成人av影视在线 | 国产精品自产拍在线观看桃花 | 欧美性受极品xxxx喷水 | 国产精品青草综合久久久久99 | 91精品国产一区二区三区 | 五月婷婷av | 黄色的网站免费看 | 日韩乱码中文字幕 | 国产日本在线播放 | 91精品国产乱码久久桃 | 97超碰人人澡人人爱学生 | 国产r级在线观看 | 中文字幕在线观看免费高清完整版 | 美女视频黄的免费的 | 日韩毛片精品 | 日日麻批40分钟视频免费观看 | 91手机在线看片 | 国产一区二区视频在线播放 | 欧美日本不卡视频 | 992tv人人草 黄色国产区 | 五月婷婷电影网 | 97视频在线看 | 江苏妇搡bbbb搡bbbb | 成年人视频在线免费 | 成人高清在线观看 | 69av久久 | 伊色综合久久之综合久久 | 国产91精品一区二区 | 国产精品成人在线观看 | 免费观看一级 | 精品视频在线免费观看 | 婷婷亚洲综合 | 欧美激情第十页 | 最新av在线播放 | 国产中文字幕一区二区三区 | 国产精品亚洲片在线播放 | 欧美一二三区在线播放 | 婷婷在线资源 | 四虎影视8848aamm | 午夜视频福利 | 成人在线免费视频 | 国产探花视频在线播放 | 在线视频日韩精品 | 五月天最新网址 | 91在线一区二区 | 亚洲免费国产 | 国产亚洲资源 | 中文字幕中文字幕在线一区 | 99久久9 | 91视频国产高清 | 一区 二区电影免费在线观看 | 成片视频在线观看 | 欧美日韩一区二区三区不卡 | 日韩精品专区在线影院重磅 | 免费在线观看日韩欧美 | 麻豆一二三精选视频 | 日韩女同av | 久久欧美在线电影 | 欧美日韩在线视频一区 | 少妇精品久久久一区二区免费 | 国产成人三级三级三级97 | 成人h视频在线 | 久久久久伊人 | 欧美精品久久久久久久免费 | 天天操综合网站 | 国产精品久久久久久爽爽爽 | av性网站| 色综合久久久久久中文网 | 久久国产亚洲精品 | 久久天天躁夜夜躁狠狠85麻豆 | 精品专区一区二区 | 91最新地址永久入口 | 久久99在线观看 | 91传媒在线观看 | av免费观看高清 | 欧美色图亚洲图片 | 国产成人黄色 | 国产成人精品一区二区三区免费 | 久久成人精品视频 | 又色又爽又黄高潮的免费视频 | 日韩综合第一页 | av中文字幕亚洲 | 久久综合电影 | 亚洲资源网 | 在线观看日韩 | 精品久久久久久综合日本 | 91精品久久久久久久久久入口 | 欧美另类v | 丰满少妇在线观看资源站 | 91爱爱网址 | 成人午夜精品福利免费 | 亚洲一二视频 | 人人添人人澡人人澡人人人爽 | 精品免费久久久久久 | 中文字幕亚洲五码 | 亚洲国产福利视频 | 亚洲 欧美 国产 va在线影院 | 亚洲精品456在线播放第一页 | 免费高清男女打扑克视频 | 免费久草视频 | 欧美久久久久久久久久久久久 | 黄色a在线观看 | 五月婷婷六月丁香激情 | 高清精品久久 | 丁香在线视频 | 99草视频| 欧美99久久| 国产精品久久久久影院日本 | 日韩精品三区四区 | 国产在线欧美 | 日韩免费在线一区 | 日韩黄在线观看 | 国产 字幕 制服 中文 在线 | 国产精选在线观看 | 美女网站免费福利视频 | 91成人精品一区在线播放69 | 天天干,天天射,天天操,天天摸 | 成年人电影免费在线观看 | 免费日韩一区 | 国产成人333kkk | 四虎成人精品永久免费av | 97超碰香蕉 | 日韩在线观看一区二区三区 | 久久免费视频国产 | 精品国产区 | www亚洲精品 | 久久综合操 | 亚洲免费精品视频 | www.国产毛片 | 国产一区二区三区午夜 | 成人精品视频 | 亚洲国产精品影院 | 久久欧美在线电影 | 久久五月天婷婷 | www.香蕉视频 | 最新99热 | 成人污视频在线观看 | 日本精品一区二区三区在线观看 | 国产一区二区在线影院 | 操操操日日日干干干 | 九色激情网 | 91精品啪在线观看国产81旧版 | 久久中文字幕在线视频 | 日韩黄色av网站 | 最近中文字幕大全中文字幕免费 | 亚洲国产精品激情在线观看 | 欧美91精品国产自产 | 国产亚洲精品久久久久久久久久久久 | 中国一级片免费看 | www.久久精品视频 | 午夜色大片在线观看 | 色99中文字幕 | 天天草天天插 | 精品国产乱码久久 | 亚洲视频久久久久 | 亚洲成人av一区二区 | 国产精品国产三级国产不产一地 | 日韩av免费在线看 | 日本久久成人中文字幕电影 | 久久精品国产99国产 | 色噜噜在线观看视频 | 97色婷婷成人综合在线观看 | 久久精品视频日本 | 欧美综合国产 | 欧美福利片在线观看 | 999国内精品永久免费视频 | av高清一区| 丁香花在线观看视频在线 | 人人爽人人乐 | 欧美大片第1页 | 国产精品aⅴ | 香蕉视频国产在线观看 | 日韩精品不卡 | 中文字幕国产一区二区 | 久久老司机精品视频 | 亚洲成人资源在线观看 | 国产在线观看二区 | 亚洲精品视频在线播放 | 国产在线视频资源 | 黄色午夜| 久久久精品小视频 | 国产精品免费久久久久久 | 日韩一区二区三免费高清在线观看 | 色先锋资源网 | 色欲综合视频天天天 | 日韩av一区二区在线播放 | 色偷偷88888欧美精品久久久 | 亚洲精品黄色在线观看 | 日韩不卡高清视频 | 欧美日韩天堂 | 国产手机在线观看 | 黄色成人在线观看 | 天天干,天天射,天天操,天天摸 | 五月导航 | 伊人久在线| 国产精品日韩在线 | 8x8x在线观看视频 | 久久综合婷婷国产二区高清 | 一区二区三区污 | 亚洲伦理一区二区 | 亚洲精品乱码久久久久久蜜桃91 | 久久96国产精品久久99漫画 | 欧美激情综合五月色丁香 | 最近2019中文免费高清视频观看www99 | 日韩av一区二区在线 | 精品国产欧美 | 中文字幕一区二区三区在线观看 | 久久国产综合视频 | 天天躁天天操 | 国产精品一区在线观看 | 日韩三级视频在线观看 | 亚洲综合激情网 | 青青久草在线 | 在线成人免费av | 日韩免费观看视频 | 国产精品一区二区电影 | 国产在线a免费观看 | 国产97色在线 | 最新国产一区二区三区 | 黄a网| 国产成人精品久久亚洲高清不卡 | 一区二区 久久 | 国产精品成人av在线 | 中文字幕超清在线免费 | 亚洲综合色网站 | adn—256中文在线观看 | 中文字幕av有码 | 91av在线播放| 97成人在线免费视频 | 国产精品成人久久久 | 色资源网在线观看 | 亚洲精品综合在线观看 | 日韩在线三级 | 免费高清男女打扑克视频 | 男女视频91| 91久久偷偷做嫩草影院 | 在线中文字幕电影 | 综合伊人久久 | 欧美日韩亚洲在线观看 | av在线播放免费 | 丁香五月亚洲综合在线 | 91亚洲激情 | 97天堂 | 韩国av三级 | 超级碰碰免费视频 | 天天干,狠狠干 | 免费在线激情电影 | 久久福利| 久久综合桃花 | 国产精品乱码久久久 | 免费在线观看黄网站 | 精品一区电影 | 91麻豆国产福利在线观看 | 国产高清日韩 | 99久久99久久免费精品蜜臀 | 中文字幕一区在线 | a特级毛片 | 亚洲人成人天堂h久久 | 国产精品免费大片视频 | 九九久久成人 | 久久精品三 | 人操人| 国产亚洲精品久久久网站好莱 | www.97色.com | 二区三区在线 | 亚洲激情视频 | 国产精品一区二区久久 | 国产小视频福利在线 | 草久久av | 麻豆传媒视频在线 | 亚洲在线免费视频 | 欧美另类亚洲 | 亚洲精品xx | 久99久精品视频免费观看 | 国产精品无av码在线观看 | 97成人精品视频在线观看 | 成人国产精品久久久 | 人操人| 成人a级大片 | av天天澡天天爽天天av | 久久精品三 | 国产精品1区2区3区在线观看 | 久久久国产一区二区三区四区小说 | 91久久精品日日躁夜夜躁国产 | 日本中文字幕网址 | 色偷偷88欧美精品久久久 | 国产一级91| 成人av网页 | 伊人成人激情 | 日韩精品中文字幕av | 丁香婷婷成人 | 国语精品免费视频 | 日批视频在线观看免费 | 99re久久资源最新地址 | 色网站在线免费观看 | 久久久免费精品国产一区二区 | 中文字幕在线观 | 国产精品99精品久久免费 | 91av电影在线观看 | 中文字幕中文中文字幕 | 91视频高清 | 日本久久久久久久久久 | 97人人澡人人爽人人模亚洲 | 国产精品久久嫩一区二区免费 | 9992tv成人免费看片 | 毛片精品免费在线观看 | 天堂av高清| 黄色的网站免费看 | 欧美人zozo | 久草网站 | 2021久久 | 亚州av成人 | 麻豆免费观看视频 | 日韩av片免费在线观看 | 久草精品在线播放 | 成年人视频在线观看免费 | 国产精品久久在线观看 | 精品国产区 | 99精品久久久久久久久久综合 | 亚洲 综合 国产 精品 | 色婷婷97 | 欧美性一级观看 | 在线观看一级片 | av免费网站| 午夜av免费在线观看 | 毛片区| 国产特级毛片aaaaaa高清 | 国产不卡一二三区 | 亚洲精品免费看 | 国产精品一区在线观看你懂的 | 人人舔人人舔 | 日韩黄色影院 | 日韩中文字幕国产 | av综合av | 国产中文字幕一区二区 | 免费在线成人av电影 | av天天干 | 久久免费毛片视频 | 狠狠操综合 | 久久的色 | 在线视频中文字幕一区 | 一区二区三区在线观看 | 国产精品成人自产拍在线观看 | 欧美性生活免费看 | 天天人人 | 中文字幕在线观看视频一区 | 日韩美在线观看 | 黄色特级片 | 视频福利在线观看 | 亚洲一级二级三级 | 国产一级二级三级在线观看 | 久草精品资源 | 亚洲精品国偷自产在线99热 | 97超碰人人澡 | 欧美黄色免费 | 久久国产精品免费观看 | 中文字幕在线观看视频一区二区三区 | 97色在线观看 | 美女免费视频观看网站 | 久久久久国产一区二区 | 亚洲国产成人在线播放 | 91av精品 | 91在线在线观看 | 久久精品亚洲一区二区三区观看模式 | 99爱视频| 国产精品黄网站在线观看 | 国产在线a不卡 | 草久电影 | 免费黄色特级片 | 国产xx在线 | 九九热只有精品 | 国产精品欧美一区二区三区不卡 | 91免费在线视频 | 人人爽人人爽人人爽 | 国产中文视 | 国产精品午夜免费福利视频 | 久久久久久97三级 | 亚洲婷婷综合色高清在线 | 特级免费毛片 | 久久久久久久久影院 | 国产亚洲免费的视频看 | 毛片黄色一级 | 99视频国产精品 | 98涩涩国产露脸精品国产网 | 亚洲精品美女久久17c | 国产精品色视频 | 欧美性生活小视频 | 国产高清 不卡 | 欧美精品在线观看 | 久久久影院官网 | 国产精品一区二区你懂的 | 超碰97免费观看 | 国产大陆亚洲精品国产 | 日韩视频在线播放 | 中文av字幕在线观看 | 中文字幕2021 | 欧美久久久久久久久中文字幕 | www91在线| 99精品视频中文字幕 | 中文字幕国内精品 | 精品国产人成亚洲区 | www.夜夜| 中文字幕视频观看 | 99riav1国产精品视频 | 中文字幕在线观看免费 | 中文字幕在线观看不卡 | 亚洲三级影院 | 午夜视频色 | 福利一区在线视频 | 久热国产视频 | 国内丰满少妇猛烈精品播放 | 精品资源在线 | 精品视频999 | 亚洲国产成人精品在线 | 欧美日韩国产二区三区 | 色播五月激情五月 | 中文字幕激情 | 免费色视频网站 | 91人人爽久久涩噜噜噜 | 五月天婷婷在线播放 | 色综合天天色综合 | 国产成人亚洲在线观看 | 91黄色小网站 | 日韩成人av在线 | 激情综合亚洲精品 | 成人夜晚看av | 九九久久影视 | av片一区二区 | 久久九九精品 | 99热九九这里只有精品10 | 成人在线播放免费观看 | 丁香六月天 | 日韩精品一区二区三区视频播放 | 在线观看av片 | 2019中文在线观看 | 国产精品一区二区中文字幕 | 色播六月天 | 亚洲v欧美v国产v在线观看 | 日韩在线视频在线观看 | 欧美色图亚洲图片 | 97色国产 | 国产欧美中文字幕 | 日韩精品一区二区三区水蜜桃 | 特级西西444www大胆高清无视频 | 国产精品video爽爽爽爽 | 久久综合一本 | 99性视频 | 国产精品人人做人人爽人人添 | av在线com| 日韩欧美电影 | 午夜免费视频网站 | 免费看国产视频 | 日韩成人精品一区二区三区 | 色婷婷国产精品 | 亚洲视频免费在线看 | 久久亚洲私人国产精品 | www.av在线.com| 奇米影视8888 | 国产欧美精品一区二区三区 | www,黄视频 | 欧美 亚洲 另类 激情 另类 | 欧美另类交在线观看 | 日韩三级精品 | 欧美日韩一区二区在线 | 最新日韩在线 | 一区二区中文字幕在线观看 | 久久久久久久久久久精 | 久草免费色站 | 精品国产人成亚洲区 | 五月婷婷激情网 | 国产亚洲精品成人av久久ww | 成人黄色中文字幕 | 超级碰碰碰免费视频 | 激情网第四色 | 国产中的精品av小宝探花 | 播五月婷婷 | 香蕉在线播放 | 亚洲视频免费在线观看 | 亚洲一区二区麻豆 | 欧美日韩不卡在线观看 | 玖玖在线播放 | 亚洲在线国产 | 最新中文字幕在线资源 | 久草在在线 | 免费在线播放av电影 | 婷婷国产在线观看 | 中文字幕在线观 | 五月天欧美精品 | 久久综合毛片 | 美女精品久久久 | 国产精品九九久久99视频 | 人人爽人人做 | 日韩在线视频一区二区三区 | 久久久蜜桃 | 丰满少妇高潮在线观看 | 亚洲日韩欧美视频 | 91在线免费播放视频 | 一本色道久久精品 | 国产黄av | 中文字幕在线高清 | 九九九在线 | 四虎在线免费观看视频 | 在线免费观看黄色av | 久草在线免费新视频 | 波多野结衣电影一区二区 | 免费视频18 | 精品国产一区二区三区久久 | 8090yy亚洲精品久久 | 日韩av手机在线看 | 又污又黄的网站 | av在线超碰 | 波多野结衣电影久久 | 午夜精品一区二区三区可下载 | 一区二区精品在线视频 | 91大神视频网站 | 中文在线 | 国产高清在线看 | 亚洲精品黄色 | 久久一区二区三区超碰国产精品 | 免费国产ww | 麻豆影视网站 | 99欧美精品| 欧洲高潮三级做爰 | 奇米7777狠狠狠琪琪视频 | 又色又爽又激情的59视频 | 亚洲码国产日韩欧美高潮在线播放 | 欧美精品在线观看免费 | 伊人狠狠干 | 精品国偷自产国产一区 | 97视频入口免费观看 | 视频在线观看入口黄最新永久免费国产 | 免费在线观看中文字幕 | 三级黄色欧美 | 91成熟丰满女人少妇 | 久久综合久久久久88 | 亚洲国产精品成人综合 | 在线观看免费中文字幕 | 亚洲国产成人精品在线 | 日韩精品专区在线影院重磅 | 久草综合在线观看 | 国产一级性生活 | 高清不卡免费视频 | 亚洲精品在线一区二区 | 9在线观看免费高清完整版 玖玖爱免费视频 | 黄色三几片 | 精品一区中文字幕 | 久久久精品一区二区三区 | 玖玖在线免费视频 | av在线官网 | 亚洲国产网址 | 911久久香蕉国产线看观看 | 中文字幕日韩精品有码视频 | 四虎在线观看精品视频 | 日韩黄色一区 | 国产高清中文字幕 | 人人舔人人 | 亚洲免费国产 | 激情视频免费在线 | 国产精品入口麻豆www | 久久精品男人的天堂 | 人人讲| 黄色av免费在线 | 色噜噜日韩精品一区二区三区视频 | 成年人视频在线免费 | 最新国产精品拍自在线播放 | 成人午夜剧场在线观看 | 狠狠躁18三区二区一区ai明星 | 中文字幕久久精品一区 | 天天操天天操天天操天天操天天操天天操 | 波多野结衣精品 | 人人艹人人 | 欧美日韩免费一区二区 | 国产高清在线免费观看 | 日韩精品久久久 | 日韩免费一区 | 日本黄色黄网站 | 99热在| 亚洲91av| 九九免费在线视频 | 国产在线观看 | 久草在线一免费新视频 | 日韩在线高清免费视频 | 97精产国品一二三产区在线 | 激情丁香月 | 久久国产一区二区 | 天天干天天干天天操 | 国产一区视频在线观看免费 | 人人插人人插 | 午夜婷婷在线观看 | 操操操日日日 | 少妇bbr搡bbb搡bbb | 激情综合色播五月 | 国产亚洲午夜高清国产拍精品 | 久久久久久久久久久网 | 欧美黄色成人 | 不卡av在线免费观看 | av大片免费在线观看 | 在线播放91 | 日本xxxxav | 国产99久| 六月激情丁香 | 99色资源 | 国产精品免费高清 | 97超碰成人 | 狠狠色狠狠色合久久伊人 | 国产亚洲婷婷 | 欧美日韩中文在线观看 | 美女免费视频网站 | 国产成人精品一区二区三区网站观看 | 久草综合在线观看 | 国产中文字幕免费 | 综合天堂av久久久久久久 | 亚洲综合狠狠干 | 在线a人片免费观看视频 | 在线直播av | 国产不卡毛片 | 国产高清不卡在线 | 91片黄在线观 | 国产精品国产精品 | 天天干人人干 | 亚洲黄色av | 国产九九热视频 | 婷婷网在线 | 国产一区二区三区免费在线 | 毛片在线播放网址 | 在线看一区二区 | 亚洲三级av | 高清av在线| 97综合在线| 精品视频在线免费 | 狠狠狠色丁香婷婷综合激情 | 波多野结衣理论片 | 91久久国产自产拍夜夜嗨 | 亚洲色图 校园春色 | 最近2019好看的中文字幕免费 | 亚洲乱亚洲乱亚洲 | 草在线视频 | 色视频在线免费 | 久久亚洲福利视频 | 国产精品久久久久久久午夜片 | 九九九九色 | 色噜噜色噜噜 | 日日操日日插 | 久久久久久国产精品久久 | 99综合久久 | 亚洲香蕉在线观看 | 日韩色av色资源 | 成人黄色片在线播放 | 免费观看91视频大全 | 精品在线观看一区二区 | 91色吧| 国产一级二级在线观看 | 在线视频观看亚洲 | 久久夜色精品国产欧美乱极品 | 久久久久成人精品免费播放动漫 | 日本精品在线看 | 婷婷黄色片 | 91av99 | 中文字幕av播放 | 亚洲国产精品久久久 | 国产又粗又猛又黄又爽视频 | 免费观看黄色12片一级视频 | 在线 视频 一区二区 | 国产原创中文在线 | 91一区二区三区在线观看 | 公与妇乱理三级xxx 在线观看视频在线观看 | 欧美一级电影在线观看 | 免费日韩一区二区三区 | 国产在线观看av | 夜色资源站wwwcom | 成年人视频在线免费播放 | 亚洲精品视频在线免费 | 麻豆果冻剧传媒在线播放 | 狠狠色丁香九九婷婷综合五月 | 国产精品免费观看视频 | 欧洲激情综合 | 97超碰资源网 | 日韩免| 91视频 - 88av | www久久99 | 深爱婷婷久久综合 | 插插插色综合 | 在线视频欧美日韩 | 中文字幕永久 | 久久久免费播放 | 99久久er热在这里只有精品66 | 日韩xxxx视频 | 黄色免费大全 | 日韩国产欧美在线视频 | 亚洲视频免费在线观看 | 欧美综合干 | 黄色av电影免费观看 | 97超碰人人澡人人爱学生 | 亚洲国产精品成人综合 | 久精品视频免费观看2 | 午夜精品一区二区三区四区 | 国产精品一区二区美女视频免费看 | 夜夜躁天天躁很躁波 | 国产一区欧美日韩 | 中文字幕免费看 | 亚洲欧洲中文日韩久久av乱码 | 日韩精品免费在线观看视频 | 国产尤物在线视频 | 91高清在线 | 久久午夜色播影院免费高清 | 亚洲va韩国va欧美va精四季 | 美女免费黄视频网站 | www日韩| 久久久久久久影视 | 天天草综合网 | 就要色综合 | 天天爽夜夜爽精品视频婷婷 | www.五月天激情| 精品国产一区二区三区久久久 | 九九色视频 | 亚洲综合网站在线观看 | 国产精品欧美一区二区 | 成全在线视频免费观看 | 夜夜干天天操 | 国产护士av| 中文字幕在线观看91 | 在线观看黄色免费视频 | 国产视频欧美视频 | 人人爱夜夜操 | 超碰人人做 | 日韩精品国产一区 | 狠狠色噜噜狠狠狠 | 久久66热这里只有精品 | 狠狠躁夜夜a产精品视频 | 亚洲视频一级 | 亚洲精品网站在线 | 精品乱码一区二区三四区 | 国产精品久久影院 | 97超碰人人澡人人爱 | 日韩欧美国产精品 | 久草在线在线视频 | 久操中文字幕在线观看 | 人人插人人玩 | 91成人精品在线 | 99久高清在线观看视频99精品热在线观看视频 | 亚洲精品一区中文字幕乱码 | 国产免费视频在线 | 婷婷丁香狠狠爱 | 四虎国产 | 亚洲伦理中文字幕 | 免费色视频在线 | 中文在线√天堂 | 热99久久精品 |