上期回顧:java
上一篇,分享了Android平臺的抓包分析方法和技巧(固然不限於Android,其餘移動平臺也都適用),讓咱們從網絡通訊層面,瞭解樣本的客戶端與服務端如何通訊,進而判斷是否存在隱私竊取的行爲,是否符合《網絡安全法》,以及《App 違法違規收集使用我的信息行爲》的規定等等,這是一個必須掌握的技能。可是,若是咱們想知道程序某個函數的功能,可是函數加密了,加密算法又比較複雜或者加殼技術也比較難,鑑於時間成本,並不能硬碰硬,就得思考有沒有辦法不破解算法,還能獲得想要的結果,答案是:有的,本篇文章目的就是解決工做中,遇到算法難以破解,或者沒法下手時,換一種思路來達到解決問題的目的,這裏咱們稱它爲Android Hook機制與實踐詳解。android
在研究Hook機制與實踐相關知識以前,還有一個smali代碼注入問題,有必要先了解一下,這個應用範疇與Hook很類似,也常常會用到,常言道「無論黑貓白貓,能解決問題的就是好貓」。咱們都知道Android安裝包是一個ZIP壓縮包,核心代碼在壓縮包中的classes.dex和動態庫so文件,而smali代碼就是classes.dex文件通過baksmali反編譯後生成的代碼。git
回想一下,一個完整的Android程序反編譯後代碼量仍是比較大,而且反編譯後smali代碼相比java代碼可讀性更差,有的人會問,那爲何還要去讀smali代碼,直接讀java代碼不就好了,局部代碼固然能夠,可是,通常狀況下,反編譯生成的smali代碼比java代碼相對要全。那怎麼辦?經常使用的方法就是關鍵信息查找法+修改代碼法(也叫smali注入法)。好比:阿里聚安全曾經舉辦的安全競賽,要求必定時間內,破解其算法,獲得答案。github
這種狀況,通常代碼中會加入不少無效的代碼,目的就是爲了干擾破解者,或者,咱們想知道一個函數的參數,一個函數的返回值等,均可以在代碼中添加log語句,讓程序運行時,自動打印相關參數。
算法
smali代碼注入,意思就是在反編譯後生成的smali代碼中插入咱們須要的smali代碼,讓其運行後,打印出須要的結果,以免逆向分析大量的代碼,或者破解難度比較大的算法,這算一種投機取巧的辦法,有點勝之不武,哎呀,兵不厭詐嘛。shell
使用方法:swift
步驟1:java -jar baksmali.jar classes.dex -o smaliapi
步驟2: 採用關鍵信息查找法,找到須要插入代碼的地方,加入咱們本身的smali代碼;
安全
步驟3:java -jar smali.jar smali -o classes.dexbash
步驟4: 運行獲得結果。
這個方法,編寫smali對應的代碼是問題關鍵,下面是一些經常使用的語句,供參考:
# 最經常使用增長log信息const-string v1, "TAG"invoke-static {v1,v2} ,Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I // V2是要輸出的字符串# 打印函數參數const-string v1, "TAG"new-instance v3, Ljava/lang/StringBuilder;const-string v4, "XXXX"invoke-direct {v3, v4}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)Vinvoke-virtual {v3, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;move-result-object v3invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;move-result-object v3invoke-static {v1, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
# 封裝經常使用的注入代碼到文件中,文件名爲 MyCrack.smali(自定義).class public LMyCrack;.super Ljava/lang/Object;.source "MyCrack.java".method public static log(Ljava/lang/String;)V .locals 1 .prologue const-string v0, "info" invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I return-void.end method
.method public static I(I)V .locals 2 .prologue const-string v0, "info_int" invoke-static {p0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; move-result-object v1 invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I return-void.end method
.method public static J(J)V .locals 2 .prologue const-string v0, "info_long" invoke-static {p0, p1}, Ljava/lang/String;->valueOf(J)Ljava/lang/String; move-result-object v1 invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I return-void.end method
.method public static puts(Ljava/lang/String;)V .locals 7 .prologue :try_start_0 const-string v3, "/sdcard/debug.txt" new-instance v2, Ljava/io/FileOutputStream; const/4 v5, 0x1 invoke-direct {v2, v3, v5}, Ljava/io/FileOutputStream;-><init>(Ljava/lang/String;Z)V .line 19 new-instance v4, Ljava/io/OutputStreamWriter; const-string v5, "gb2312" invoke-direct {v4, v2, v5}, Ljava/io/OutputStreamWriter;-><init>(Ljava/io/OutputStream;Ljava/lang/String;)V .line 21 invoke-virtual {v4, p0}, Ljava/io/OutputStreamWriter;->write(Ljava/lang/String;)V const-string v5, "\r\n" invoke-virtual {v4, v5}, Ljava/io/OutputStreamWriter;->write(Ljava/lang/String;)V .line 23 invoke-virtual {v4}, Ljava/io/OutputStreamWriter;->flush()V .line 25 invoke-virtual {v4}, Ljava/io/OutputStreamWriter;->close()V .line 27 invoke-virtual {v2}, Ljava/io/FileOutputStream;->close()V :try_end_0 .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 .line 37 :cond_0 :goto_0 return-void .line 30 :catch_0 move-exception v0 .line 34 const-string v5, "debug" const-string v6, "file write error" invoke-static {v5, v6}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I goto :goto_0.end method
# 把上面的MyCrack.smali放到反編譯後的smali根目錄,在源代碼中注入調用代碼,格式以下
# 加入Log信息,保存了字符串類型的寄存器vx,添加代碼:invoke-static {vx}, LMyCrack;->log(Ljava/lang/String;)V# 加入Log信息,保存了int類型的寄存器vx,添加代碼:invoke-static {vx}, Lcrack;->I(I)V# 加入Log信息,保存了long類型的寄存器vx,添加代碼:# [注意:vx爲要查看的寄存器,同時確保vx+1在上面的代碼中沒有被使用過,且在.locals聲明的可以使用的寄存器範圍內。]invoke-static {vx, vx+1}, Lcrack;->J(J)V# 加入Log信息,將字符串輸出到文本,添加代碼:# [注意:字符串「\r\n」]invoke-static {vx}, Lcrack;->puts(Ljava/lang/String;)V
# 還有一種辦法就是寫一段java代碼編譯程Android程序,而後再反編譯生成smali代碼使用。
注意:smali注入方法使用也是有侷限性的,首先前提是必須apk文件能夠重打包,也就是當咱們插入相關代碼後,必須可以重打包成完整的apk文件,安裝後還要可以正常運行,不然就失敗了,咱們就得換種思路來解決,這就到本篇文章提到到Hook,好了下面咱們來看Hook。
preload Hook 在PC平臺、Linux平臺使用的比較多,像inline hook,got hook,這裏咱們只提Linux平臺和Android有關係的內容,還記得以前文章提到過,ptrace有一個很重要的特色就是一個進程只能被一個進程ptrace,若是你本身調用ptarce,這樣其它程序就沒法經過 ptrace調試或者向你的程序進程注入代碼,以達到反調試的效果。
在Linux中,LD_PRELOAD是一個環境變量,它能夠影響程序運行時的動態連接,它容許你在程序運行前優先加載提早定義的動態連接庫。這個功能主要就是用來有選擇性的載入不一樣動態連接庫中的相同函數。經過這個環境變量,咱們能夠在主程序和其動態連接庫的中間加載別的動態連接庫,甚至覆蓋正常的函數庫。根據LD_PRELOAD的這個特性,就能夠編寫Hook函數的動態連接庫,提早放入到LD_PRELOAD環境變量中,實現對原始函數的替換。
以ptrace函數爲例,編寫動態連接庫libpreload.so文件,在其中定義一個ptrace函數,實現對原始函數的替換。ptrace函數原型:int ptrace(intrequest, int pid, int addr, int data)
long ptrace(int request, pid_t pid, void *addr, void *data){ LOGE("my ptrace request:%d pid:%d\n", request, pid); if( request == 16 && pid == current_pid ) { LOGE("hook__request is PTRACE_ATTACH and pid == fork()\n"); return -1; } else { LOGE("hook___request is not PTRACE_ATTACH\n"); long (*original_ptrace)(int request, pid_t pid, void *addr, void *data); original_ptrace = dlsym(RTLD_NEXT, "ptrace"); return (*original_ptrace)(request, pid, addr, data); } return 0;}
加入export LD_PRELOAD="/path/libpreload.so",將libpreload.so放入LD_PRELOAD環境變量中,實現對ptrace函數的Hook。
更多preload Hook 知識參考:https://www.jianshu.com/p/f78b16bd8905
Android平臺,不須要這麼複雜,咱們能夠利用開源框架xposed,Cydia Substrate,Frida實現對指定函數的Hook,已達到解決咱們遇到問題的目的。
Xposed框架是一套開源的框架服務,能夠在不修改apk文件的狀況下影響程序運行,經過替換system/bin/App_process程序控制Zygote進程,使得App_process在啓動過程當中加載XposedBridge.jar包,從而完成對Zygote進程及其建立的Dalivk虛擬機的劫持。
Xposed框架的實現基於一個Android本地服務應用Xposed Installer與一個提供API的XposedBridgeApi-54.jar文件。所以,在安裝和使用Xposed框架以前,首先須要安裝XposedInstall.apk本地服務應用,在獲取ROOT權限以後,點擊「安裝/更新」激活框架,激活成功的界面如圖1 所示。
圖1 XposedInstaller激活成功界面
案例實踐:
步驟1:建立工程android2.3.3(其餘版本也能夠),能夠不用activity。
步驟2:修改AndroidManifest.xml,代碼以下。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package=" com.demo.test" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="15" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <meta-data android:name="xposedmodule" android:value="true" /> <meta-data android:name="xposeddescription" android:value="Easy example" /> <meta-data android:name="xposedminversion" android:value="54" /> </application></manifest>
第一個meta-data中的xposedmodule表示本App將做爲Xposed的一個模塊,第二個meta-data表示Xposed的最低版本號,第三個meta-data是對該模塊的描述。
步驟3:在工程目錄下新建一個lib文件夾,放入下載好的XposedBridgeApi-54.jar包,而後在Eclipse工程裏選中「XposedBridgeApi-54.jar」,右鍵單擊「->Build Path->Add to Build Path」。
步驟4:新建一個類專門用於掛鉤操做,代碼以下。
package com.example.xposedHookdemo;import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;import android.content.Context;import android.util.Log;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.XposedBridge;import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;public class HookClass implements IXposedHookLoadPackage { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { Log.i("package", lpparam.packageName); if (!lpparam.packageName.equals("com.demo.test")) return; /* 第一個參數爲包名.類名 第二個參數固定爲lpparam.classLoader 第三個參數爲方法名(要被掛鉤的函數名) 第四個參數爲函數參數(用類型.class表示) 第五個參數爲函數參數(用類型.class表示) 最後一個參數爲new XC_MethodHook () {...} */ findAndHookMethod("com.demo.test.MainActivity", lpparam.classLoader, " test1", int.class, int.class, new XC_MethodHook (){ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // 掛鉤函數以前執行的代碼 Log.i("Before Hook", "Before Hook"); Integer para1 = (Integer)param.args[0]; Integer para2 = (Integer)param.args[1]; Log.i("para1: ", para1.toString()); Log.i("para2: ", para2.toString()); } protected void afterHookedMethod(MethodHookParam param) throws Throwable { // 掛鉤函數以後執行的代碼 } }); }}
注意:XC_MethodHook()中有兩個重要的內部函數:beforeHookedMethod()和afterHookedMethod()。重寫這兩個函數能夠實現對任意方法的掛鉤,beforeHookedMethod()完成執行掛鉤函數前須要完成的自定義操做,afterHookedMethod()完成執行掛鉤函數以後須要完成的自定義操做。
步驟5:指定模塊運行入口。在assert目錄下新建xposed_init文件,編譯好後,在Xposed中啓用這個模塊(也就是打上勾),如圖2所示,而後重啓手機以後,便可看見掛鉤後的效果。
圖2 啓用激活模塊
步驟6: 新建測試程序示例
新建一個測試程序,該程序功能很是簡單,定義一個加法函數test1,而後利用Xposed框架掛鉤該函數,並打印函數的值,代碼以下所示:
package com.demo.test;import android.App.Activity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.view.View$OnClickListener;import android.widget.Button;import android.widget.TextView;
public class MainActivity extends Activity { Button btn_ok; TextView mTextView; public MainActivity() { super(); } public int test1(int x, int y) { return x + y; } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(2130903040); this.btn_ok = this.findViewById(2131230721); this.mTextView = this.findViewById(2131230720); this.btn_ok.setOnClickListener(new View$OnClickListener() { public void onClick(View v) { MainActivity.this.mTextView.Append("\n test1:" + MainActivity.this. test1(50, 50)); Log.v("demo_test", "Java code is runing"); } }); }}
步驟7:驗證測試結果。
運行測試程序,在eclipse的DDMS中能夠看到已經Hook的test1函數,打印函數的兩個參數值,如圖3所示。本示例是在函數運行前掛鉤,用戶也能夠自行測試在函數運行後掛鉤,即在afterHookedMethod()中實現代碼。
圖3 測試函數打印界面
官方教程:
https://github.com/rovo89/XposedBridge/wiki/Development-tutorial
另外,推薦學習網絡上共享的開源項目ZjDroid,進一步掌握xposed框架及應用。
# 命令執行結果adb shell logcat -s zjdroid-shell-{package name}# ZjDroid是基於Xposed Framewrok的動態逆向分析模塊,逆向分析者能夠經過ZjDroid完成如下工做:1、DEX文件的內存dump (適用於早期的壓縮殼)2、基於Dalvik關鍵指針的內存BackSmali,有效破解加固應用3、敏感API的動態監控 (適用於樣本的敏感函數監控) adb shell logcat -s zjdroid-apimonitor-{package name} 4、指定內存區域數據dump 5、獲取應用加載DEX信息。6、獲取指定DEX文件加載類信息。7、dump Dalvik java堆信息。(經過java heap分析工具分析處理)8、在目標進程動態運行lua腳本。(經過Lua腳本動態調用java代碼,能夠動態調用解密函數,完成解密)
好了,本篇文章就分享到此。
本文分享自微信公衆號 - App安全紅寶書(apphongbaoshu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。