1. 程式人生 > >初識Frida--Android逆向之Java層hook (一)

初識Frida--Android逆向之Java層hook (一)

打工是不可能打工的 這輩子不可能打工的,做生意又不會做,就是寫這種東西,才能維持得了生活這樣子。

0x00 文中用到的工具

  • Frida
  • jadx-gui 一個強大的android反編譯工具
  • genymotion模擬器
  • Python2.7以及frida-python庫
  • radare2 反彙編器
  • pycharm

0x01 hook示例的安裝與分析

Frida官網給我們了一個ctf的示例,就以此為例子,開始學習frida在android逆向的使用。
rps.apk 下載地址

安裝

使用genymotion等類似android模擬器安裝好開啟,發現這是一個石頭剪刀布的遊戲應用,簡單的玩了一下,沒什麼特別的,直接分析程式碼吧,看看到底想幹什麼。
這裡寫圖片描述

原始碼分析

使用jadx-gui反編譯,發現app沒有加殼和混淆,當然一來就加殼和混淆的話對我們就太不友好了,接下分析就簡單了,直接看java程式碼。當然也可以使用androidkiller,jeb等其他強大的反編譯工具。
這裡寫圖片描述

在MainActivity中找到OnCreate()方法,可以看到只是簡單的聲明瞭button控制元件以及對應的監聽器。

  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout
.activity_main); this.P = (Button) findViewById(R.id.button); this.S = (Button) findViewById(R.id.button3); this.r = (Button) findViewById(R.id.buttonR); this.P.setOnClickListener(this); this.r.setOnClickListener(this); this.S.setOnClickListener(this);
this.flag = 0; }

繼續檢視button的onclick方法,可以看出cpu是通過隨機陣列出的,其判斷輸贏的方法在this.showMessageTask中。

  public void onClick(View v) {
        if (this.flag != 1) {
            this.flag = 1;
            ((TextView) findViewById(R.id.textView3)).setText("");
            TextView tv = (TextView) findViewById(R.id.textView);
            TextView tv2 = (TextView) findViewById(R.id.textView2);
            this.m = 0;
            this.n = new Random().nextInt(3);  //隨機數0,1,2
            tv2.setText(new String[]{"CPU: Paper", "CPU: Rock", "CPU: Scissors"}[this.n]); //隨機出石頭,剪刀,布
            if (v == this.P) {
                tv.setText("YOU: Paper");
                this.m = 0;
            }
            if (v == this.r) {
                tv.setText("YOU: Rock");
                this.m = 1;
            }
            if (v == this.S) {
                tv.setText("YOU: Scissors");
                this.m = 2;
            }
            this.handler.postDelayed(this.showMessageTask, 1000);//輸贏判斷方法
        }
    }

跟進分析showMessageTask,可以看到如果贏了mainActivity.cnt會+1,但是一旦輸了cnt就會置0,而獲取flag的要求是我們得獲勝1000次,…… :(

private final Runnable showMessageTask = new Runnable() {
        public void run() {
            TextView tv3 = (TextView) MainActivity.this.findViewById(R.id.textView3);
            MainActivity mainActivity;
            //我方:布 CPU:石頭 or 我方:石頭 CUP:剪刀 ,則為贏
            if (MainActivity.this.n - MainActivity.this.m == 1) {
                mainActivity = MainActivity.this;
                mainActivity.cnt++;
                tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));
             //反過來當然是輸咯
            } else if (MainActivity.this.m - MainActivity.this.n == 1) {
                MainActivity.this.cnt = 0;
                tv3.setText("LOSE +0");
             //一樣則打平
            } else if (MainActivity.this.m == MainActivity.this.n) {
                tv3.setText("DRAW +" + String.valueOf(MainActivity.this.cnt));
             //我布  cup:剪刀
            } else if (MainActivity.this.m < MainActivity.this.n) {
                MainActivity.this.cnt = 0;
                tv3.setText("LOSE +0");
            } else {
                mainActivity = MainActivity.this;
                mainActivity.cnt++;
                tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));
            }
            //獲勝1000次則能夠獲取flag
            if (1000 == MainActivity.this.cnt) {
                tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}");
            }
            MainActivity.this.flag = 0;
        }
    };

簡單分析一下獲取flag需要的條件,總結有3個辦法:

  • 分析calc()方法能算出答案,但這個方法在so中,得分析彙編程式碼才行,當然可以嘗試使用ida pro,F5檢視C程式碼分析,前提是演算法不難。

  • 獲取calc函式的返回值,從而計算答案。

  • 還有一個方法就是,直接將MainActivity.this.cnt的值構造成1000。

接下來就用frida,使用後兩種思路來解這個簡單的示例。但在這之前得先了解Frida自帶的Messages機制,瞭解frida怎麼從通過一個python指令碼傳送和接收message訊息是一個提升理解frida的好方法。

0x02 frida自帶的Messages機制與程序互動

先來看看一個Messages的模板,這裡用到的語言分別是python和javascript,他們之間的關係是python作為載體,javascript作為在android中真正執行程式碼。

import frida, sys

//hook程式碼,採用javascript編寫
jscode = """
//javascript程式碼,重點
"""

//自定義回撥函式
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

#重點的4行程式碼
process = frida.get_usb_device().attach('應用完整包名')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

當然如果是對此簡單的使用,只需要編寫jscode,以及填寫你要hook的應用完整包名就行了,不過如果單純只會用可能在以後會被模板限制,所以一探究竟還是很有必要。
可以在cmd中,使用python終端的help()函式找到frida庫的原始碼的絕對路徑。
這裡寫圖片描述
接下來就來具體看看這幾句程式碼做了什麼事情。

process = frida.get_usb_device().attach('應用完整包名')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

首先使用了frida.get_usb_device(),返回了一個_get_device函式,跟進_get_device方法。

def get_usb_device(timeout = 0):
    return _get_device(lambda device: device.type == 'tether', timeout)

在_get_device中,通過get_device_manager()例項化DeviceManager類,並呼叫該類中的enumerate_devices()方法。

def _get_device(predicate, timeout):
    mgr = get_device_manager()                //獲取裝置管理
    def find_matching_device():               //尋找匹配裝置
        usb_devices = [device for device in mgr.enumerate_devices() if predicate(device)]
        if len(usb_devices) > 0:
            return usb_devices[0]
        else:
            return None
    device = find_matching_device()
   ...省略

get_device_manager()程式碼

def get_device_manager():
    global _device_manager
    if _device_manager is None:
        from . import core
        _device_manager = core.DeviceManager(_frida.DeviceManager())
    return _device_manager

DeviceManager中enumerate_devices()方法,可以看到enumerate_devices()方法實際上是返回了一個Device()類的例項化物件List。

class DeviceManager(object):
    def __init__(self, impl):
        self._impl = impl

    def __repr__(self):
        return repr(self._impl)

    //返回了一個Device()類的例項化。
    def enumerate_devices(self):
        return [Device(device) for device in self._impl.enumerate_devices()]

    def add_remote_device(self, host):
        return Device(self._impl.add_remote_device(host))

    def remove_remote_device(self, host):
        self._impl.remove_remote_device(host)

    def get_device(self, device_id):
        devices = self._impl.enumerate_devices()
        if device_id is None:
            return Device(devices[0])
        for device in devices:
            if device.id == device_id:
                return Device(device)
        raise _frida.InvalidArgumentError("unable to find device with id %s" % device_id)

    def on(self, signal, callback):
        self._impl.on(signal, callback)

    def off(self, signal, callback):
        self._impl.off(signal, callback)

繼續跟進Device類中的,就找到了attach()方法。在attach方法這是設定斷點,看看傳入的資料。

這裡寫圖片描述

接下來提供的“應用完整名”是通過self._pid_of()函式去找到對應的程序號pid,然後將pid後通過Session類初始化。到此第一句程式碼過程就算是明白了,最終得到的是一個對應程序號pid的Session例項化物件process。

class Device(object):
    def __init__(self, device):
        self.id = device.id
        self.name = device.name
        self.icon = device.icon
        self.type = device.type
        self._impl = device

    def __repr__(self):
        return repr(self._impl)

    ...節省空間刪除部分方法,詳細內容可自行檢視原始碼

    def kill(self, target):
        self._impl.kill(self._pid_of(target))

    //返回了一個Session的例項化物件
    def attach(self, target):
        return Session(self._impl.attach(self._pid_of(target)))

    def inject_library_file(self, target, path, entrypoint, data):
        return self._impl.inject_library_file(self._pid_of(target), path, entrypoint, data)

    def inject_library_blob(self, target, blob, entrypoint, data):
        return self._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data)

    def on(self, signal, callback):
        self._impl.on(signal, callback)

    def off(self, signal, callback):
        self._impl.off(signal, callback)

    def _pid_of(self, target):
        if isinstance(target, numbers.Number):
            return target
        else:
            return self.get_process(target).pid

第二句,緊接著process.create_script(jscode),可以看到它返回一個Script類的例項化,引數不確定。

def create_script(self, *args, **kwargs):
        return Script(self._impl.create_script(*args, **kwargs))

跟進Script類,可以找到on()方法,在on方法中可以設定自定義回撥函式。

class Script(object):
    def __init__(self, impl):
        self.exports = ScriptExports(self)

        self._impl = impl
        self._on_message_callbacks = []
        self._log_handler = self._on_log

        self._pending = {}
        self._next_request_id = 1
        self._cond = threading.Condition()

        impl.on('destroyed', self._on_destroyed)
        impl.on('message', self._on_message)

   ...節省空間刪除部分類方法,詳細內容可自行檢視原始碼

    def load(self):
        self._impl.load()

   //設定自定義回撥函式
    def on(self, signal, callback):
        if signal == 'message':
            self._on_message_callbacks.append(callback)
        else:
            self._impl.on(signal, callback)

在IDE中可以看到_on_message_callbacks中存放的on_message函式地址。

這裡寫圖片描述
接下來呼叫load()方法,在服務端就啟動javascript指令碼了,至於在frida-server服務端怎麼執行的,可逆向研究一下frida-server,它才是真正的核心。

0x03 Javascript程式碼構造與執行

現在就來使用frida實現剛剛試想的方法。

方法一:獲取calc()返回值

第一種思路就是直接獲取calc的返回值,從native函式定義上知道它的返回值是int型別,當然直接獲取calc函式的返回值是解出問題最簡單的方法。

 public native int calc();

那怎麼獲取calc()函式的返回值呢,這個函式在MainActivity類中,直接引用該類下的calc()方法,不就ok了嗎,原理是這樣,下面就來構造一下Javascript程式碼。

//Java.Perform 開始執行JavaScript指令碼。
Java.perform(function () {
//定義變數MainActivity,Java.use指定要使用的類
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
    //hook該類下的onCreate方法,重新實現它
    MainActivity.onCreate.implementation = function () {
        send("Hook Start...");
        //呼叫calc()方法,獲取返回值
        var returnValue = this.calc();
        send("Return:"+returnValue);
        var result = (1000+returnValue)*107;
        //解出答案
        send("Flag:"+"SECCON{"+result.toString()+"}");
    }
});

JavaScript程式碼就是這樣,如果不是很理解,學習一下JavaScript基礎即可,下面看看完整的python指令碼。

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
Java.perform(function () {
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
    MainActivity.onCreate.implementation = function () {
        send("Hook Start...");
        var returnValue = this.calc();
        send("Return:"+returnValue);
        var result = (1000+returnValue)*107;
        send("Flag:"+"SECCON{"+result.toString()+"}");
    }
});
"""

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

接下來執行一下,看看能否成功。

步驟如下:

  1. 啟動模擬器,使用adb push將對應架構的frida-server檔案push到模擬器中
    /data/local/tmp目錄下。
  2. adb shell 進入/data/local/tmp目錄,啟動frida-server。
  3. 開啟埠轉發
    adb forward tcp:27043 tcp:27043
    adb forward tcp:27042 tcp:27042
  4. 啟動應用後,在命令列等執行python指令碼。

因為hook的是應用的onCreate方法,執行python指令碼的前提是應用首先啟動,這樣才能attach到該應用,所以還得返回模擬器桌面重新啟動應用,這樣它才會執行hook的onCreate()方法,結果如下。
這裡寫圖片描述

方法二:修改cnt的值為1000

第二種思路也比較簡單,我們需要修改cnt的值,但如果直接修改cnt的初始值為1000的話,在遊戲中可能存在不確定因素,比如輸了會置0,贏了cnt值就變成1001了,所以還得控制一下輸贏,而輸贏的條件是電腦出什麼,所以最終hook的方法就在onClick中。
從onClick()中可以知道,控制輸贏的在於修改this.n 和 this.m的值,再來看看原始碼。

 public void onClick(View v) {
        if (this.flag != 1) {
            this.flag = 1;
            ((TextView) findViewById(R.id.textView3)).setText("");
            TextView tv = (TextView) findViewById(R.id.textView);
            TextView tv2 = (TextView) findViewById(R.id.textView2);
            this.m = 0;
            //控制電腦出拳
            this.n = new Random().nextInt(3);
            tv2.setText(new String[]{"CPU: Paper", "CPU: Rock", "CPU: Scissors"}[this.n]);
            if (v == this.P) {
                tv.setText("YOU: Paper");
                this.m = 0;
            }
            if (v == this.r) {
                tv.setText("YOU: Rock");
                this.m = 1;
            }
            if (v == this.S) {
                tv.setText("YOU: Scissors");
                this.m = 2;
            }
            this.handler.postDelayed(this.showMessageTask, 1000);
        }

來看JavaScript程式碼怎麼寫吧

Java.perform(function () {
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
    //hook onClick方法,此處要注意的是onClick方法是傳遞了一個View引數v
    MainActivity.onClick.implementation = function (v) {
        send("Hook Start...");
        //呼叫onClick,模擬點選事件
        this.onClick(v);
        //修改引數
        this.n.value = 0;
        this.m.value = 2;
        this.cnt.value = 999;
        send("Success!")
    }
});

完整python程式碼

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
Java.perform(function () {
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
    MainActivity.onClick.implementation = function (v) {
        send("Hook Start...");
        this.onClick(v);
        this.n.value = 0;
        this.m.value = 2;
        this.cnt.value = 999;
        send("Success!")
    }
});
"""

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

執行python指令碼,任意點選按鈕,答案就出來了。
這裡寫圖片描述

當然,如果so中的calc()函式演算法不難的前提,直接使用ida pro或者radare2分析彙編程式碼也是可以的。這裡給出用radare2反彙編出來的程式碼。可以看到,calc()函式就單純的返回了int值7。
這裡寫圖片描述

0x04 總結

  • 怎麼利用frida進行java層hook
    1.反編譯apk,分析程式碼尋找hook點。
    2.編寫js程式碼,呼叫類的方法或者替換。
    3.在python中執行即可。

相關推薦

初識Frida--Android逆向Javahook ()

打工是不可能打工的 這輩子不可能打工的,做生意又不會做,就是寫這種東西,才能維持得了生活這樣子。 0x00 文中用到的工具 Frida jadx-gui 一個強大的android反編譯工具 genymotion模擬器

Android逆向旅---Hook神器家族的Frida工具使用詳解

常見 fin () 文件的 數值 isp extern dex文件 所有 一、前言 在逆向過程中有一個Hook神器是必不可少的工具,之前已經介紹了Xposed和Substrate了,不了解的同學可以看這兩篇文章:Android中Hook神器Xposed工具介紹 和 Andr

Android逆向旅---NativeHook神器Cydia Substrate使用詳解

一、前言在之前已經介紹過了Android中一款hook神器Xposed,那個框架使用非常簡單,方法也就那幾個,其實最主要的是我們如何找到一個想要hook的應用的那個突破點。需要逆向分析app即可。不瞭解Xposed框架的同學可以檢視:Android中hook神器Xposed使用詳解;關於hook使用以及原理

Android逆向旅---破解某支付軟體防Xposed的hook功能檢測機制過程分析

一、情景介紹最近想寫幾個某支付軟體的外掛,大家現在都知道現在外掛大部分都是基於Xposed的hook功能,包括之前寫了很多的某社交軟體的外掛,所以不多說就直接用Jadx開啟支付軟體之後然後找到想要hoo

Android逆向旅---破解某支付軟體防Xposed等框架Hook功能檢測機制

一、情景介紹最近想寫幾個某支付軟體的外掛,大家現在都知道現在外掛大部分都是基於Xposed的ho

Android逆向旅---靜態方式分析破解視頻編輯應用「Vue」水印問題

https http mpeg 朋友圈 無需 爆破 資料 不可 fill 一、故事背景 現在很多人都喜歡玩文藝,特別是我身邊的UI們,拍照一分鐘修圖半小時。就是為了能夠在朋友圈顯得逼格高,不過的確是挺好看的,修圖的軟件太多了就不多說了,而且一般都沒有水印啥的。相比較短視頻有

Android逆向smali學習

Smali是Android虛擬機器Dalvik反彙編的結果。 Dalvik指令集 指令格式為:[op]-[type](可選)/[位寬,預設4位] [目標暫存器],[源暫存器](可選) 賦值:move*  v1,v2 返回操作:return-[type]  (void ,不帶, ob

Android逆向旅---動態方式破解apk前奏篇 Eclipse動態除錯smail原始碼

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android逆向旅---靜態方式破解微信獲取聊天記錄和通訊錄資訊

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android逆向旅---Android應用的漢化功能 修改SO中的字串內容

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android逆向旅---靜態分析技術來破解Apk

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android逆向路---Android逆向路---讓你的微信地區來自火星

前言 今天看到網友的微信地區是一個魔法學院,微信的地區怎麼可能是魔法學院呢,肯定是這位網友自己搞了一些黑科技,然後改的。他能改,我們也能改,二話不說就開幹。 先來看看我的成果 需要執行環境 xposed環境 root過的android手機 微信最新版,我用的是6.7.3 開始逆向,

android中通過java程式呼叫命令列的一些備註

能呼叫哪些命令? 一般性的, 最常用的命令都能呼叫, 比如cat, cp, top, ls, ps命令, 但用法和linux上的有較大區別, 可通過–help/-h查詢具體的命令用法; 我熟知linux terminal命令列, 但如何知道android都有哪些常用命令呢? 首

Android開發HAL

本文摘自 羅昇陽的《Anroid系統原始碼情景分析》,更新至Android7.0分析 一、概念 一、Android系統為硬體抽象層中的模組介面定義了編寫規範,我們必須按照這個規範來編寫自己的硬體模組介面。 二、Android系統的硬體

Android逆向旅---抖音火山視訊的Native註冊混淆函式獲取方法

一、靜態分析 最近在小密圈中有很多同學都在諮詢有時候有些應用的動態註冊Native函式,在分析so之後發現找不到真的實現函式功能地方,我們知道有時候為了安全考慮會動態註冊Native函式,但是如果只是這麼做的話就會非常簡單,比如這樣的: 這樣的我們熟知Reigster

android逆向多dex(multiDex)檔案apk的逆向

很多大廠的Android App因為業務量大,引用庫多導致其apk包的中的類於方法劇增.這樣就有可能出現因為方法數過多導致編譯失敗的情況.產生這個問題的主因是dex檔案格式的限制.一個DEX檔案中method個數採用使用原生型別short來索引檔案中的方法,也就

Android逆向旅---動態方式破解apk終極篇(加固apk破解方式)

一、前言 今天總算迎來了破解系列的最後一篇文章了,之前的兩篇文章分別為: 第一篇:如何使用Eclipse動態除錯smali原始碼  第二篇:如何使用IDA動態除錯SO檔案 現在要說的就是最後一篇了,如何應對Android中一些加固apk安全防護,在之前的兩篇破

Android逆向旅---解析編譯之後的Resource arsc檔案格式

                一、前言快過年了,先提前祝賀大家新年快樂,這篇文章也是今年最後一篇了。今天我們繼續來看逆向的相關知識,前篇文章中我們介紹瞭如何解析Android中編譯之後的AndroidManifest.xml檔案格式:http://blog.csdn.net/jiangwei09104100

Android逆向旅---動態方式破解apk進階篇 IDA除錯so原始碼

                一、前言今天我們繼續來看破解apk的相關知識,在前一篇:Eclipse動態除錯smali原始碼破解apk 我們今天主要來看如何使用IDA來除錯Android中的native原始碼,因為現在一些app,為了安全或者效率問題,會把一些重要的功能放到native層,那麼這樣一來,我們

Android逆向旅---SO(ELF)檔案格式詳解

第一、前言從今天開始我們正式開始Android的逆向之旅,關於逆向的相關知識,想必大家都不陌生了,逆向領域是一個充滿挑戰和神祕的領域。作為一名Android開發者,每個人都想去探索這個領域,因為一旦你破解了別人的內容,成就感肯定爆棚,不過相反的是,我們不僅要研究破解之道,也要