Android免Root權限通過Hook系統函數修改程序運行時內存指令邏輯

一、知識回顧

在之前一篇文章中,已經介紹了Android中如何修改內存指令改變方法執行邏輯,當時那篇文章的大致流程很簡單,在程序運行起來,dex文件被加載到內存中之後,通過讀取maps文件,獲取dex文件的內存其實地址,然後通過文件頭信息找到指定dex在內存中的數據結構,這裏還需要詳細瞭解Dex文件的格式,不瞭解的同學可以看這篇文章:Android中Dex文件格式解析,然後使用系統函數修改內存讀寫屬性,在通過指定方法名找到該方法在內存的指令地址,然後替換即可。我們可以簡單看一下dex文件被映射到內存之後的地址:

 

二、免root進行native層hook

上面的這種方案有一點不好就是,需要熟悉dex文件格式,然後通過方法名通過地址轉化獲取其內存對應的指令,操作有點繁瑣,而本文將介紹一種簡便的方式,修改起來非常簡單。就是通過hook系統函數來做到這一點。而這種hook功能是免root的,所以只能hook應用內部邏輯。對其他應用程序沒有任何效果,不過這個就已經滿足本文操作的需求了,關於hook系統函數網上已經有現成的框架:https://github.com/ele7enxxh/Android-Inline-Hook,這個框架用法也非常簡單。自己下載之後導入工程即可。下面來看看它的具體用法:

新建一個NDK工程,這個不多多說了,然後把這幾個框架中的文件拷貝到jni目錄下,hook代碼主要在InlineHook.cpp中:

這裏看到我們會用到兩個函數進行hook:

第一個是註冊函數:registerInlineHook

參數:1、原始函數地址,2、hook的新函數地址,3、原始函數的二級指針

第二個是hook函數:inlineHook

參數:1、原始函數地址

這裏爲了演示效果,我們先Hook系統函數puts,我們需要在hook之前定義新函數以及舊函數的函數指針類型,這裏一定要注意,新函數定義類型要和原始函數保持一致。不然hook失敗的:

接下來,我們需要出發這個hook操作,我們可以在java層定義一個native方法,然後加載出發即可:

這裏定義一個native方法了,然後用javah命令生成指定的頭文件即可:

在native方法中開始進行hook操作,運行程序看日誌信息即可:

看到了,我們成功的hook了系統函數puts。接下來我們開始進入本文的正題了,如何hook系統函數來修改程序運行時態指令。

 

三、hook系統加載類函數

在上面的hook操作中可以看到,如果想hook一個函數,需要先找到這個函數的聲明,所以我們第一步需要想好hook哪個系統函數,如何獲取這個函數的聲明?這個不難,因爲我們想修改程序運行時態指令,那麼肯定和dex加載解析過程分不開,這個就簡單了,直接去 [Android源碼/dalvik/libdex/] 下找到DexFile.h頭文件,查看他的內部函數聲明和一些數據結構定義信息:

我們發現了這個函數,爲什麼呢?因爲我們知道一個方法執行之前肯定需要解析類信息加載到內存,而這個函數就是加載類必定運行的函數,在看看這個函數的聲明:

返回值是DexClassDef結構體指針,看看DexClassDef定義結構體:

這個結構體就是描述了一個類的詳細信息,每個字段在這裏不多解釋了,不瞭解的同學可以去看看之前介紹dex文件格式的那篇文章。這裏我們關心的就是類代碼數據的偏移地址,這個值在後面會用到,用它獲取類代碼結構體信息,後面會介紹。

兩個參數是:

第一個參數:DexFile結構體指針

這個結構體包括了整個類的全局信息。後面再獲取其他結構體信息都會用到這個值。

第二個參數:是加載類的名稱

這個參數在這裏也非常重要,因爲我們想修改一個方法的指令,肯定需要通過類去查找的,這個類名就非常重要了。

分析完了這個函數聲明之後,下面就開始操作了hook了,不過還需要做兩件事:

第一件事:因爲我們看到上面涉及到很多dex的結構體定義,所以我們需要手動的把這個系統頭文件DexFile.h拷貝到我們的工程中,我們可以只保留一些有用的結構體定義和函數即可。

第二件事:因爲hook的時候需要原始函數地址的,所以這裏我們需要利用系統函數dlopen和dlsym來獲取指定函數的地址,關於這兩個函數用法網上介紹的知識非常多了,這裏不在詳細介紹了,他們大致的功能就是可以通過函數名獲取so文件中的函數地址。

這裏又要注意,爲了獲取正確的函數名稱,我們需要導出設備中的libdvm.so文件,在設備的/system/lib/libdvm.so下,然後用IDA打開libdvm.so文件:

搜索dexFindClass函數名,然後查看他的代碼位置,獲取導出的函數名。

上面兩件事完成之後,下面就可以開始hook操作了,操作過程和上面hook系統函數puts方式完全一樣:

然後我們使用dlopen和dlsym函數獲取正確的函數地址即可。

hook觸發邏輯,依然是之前定義的native方法:

到這裏,我們還需要做一個操作,就是手動利用DexClassLoader來加載一個我們自己編寫的dex文件,來看看hook是否成功了。所以我們還需要在構建一個工程:

這個工程非常簡單,有一個核心的工具類,類中有一個計算方法:

我們的目的就是把這個方法的乘法改成加法操作。運行這個工程,獲取dex文件,這裏爲了加載簡單,直接把這個dex文件放到SD卡目錄下,然後在回到上面的hook工程,需要在Java成編寫一個加載dex文件的方法:

我們利用DexClassLoader加載之前將CoreDex工程編譯獲取的dex文件,然後加載類利用反射執行計算方法,傳入的參數是2和3,正常結果是乘法也就是6,我們就要把乘法變成加法,讓結果輸出的是5。加載邏輯我們用一個點擊事件來觸發:

在回到native層中的hook代碼:

這裏主要看hook的新函數功能,過程有點複雜,這裏一步一步來詳細分析。首先我們需要過濾處理的類,不能所有的類都做處理,然後通過原始函數,獲取類的DexClassDef結構信息,然後利用系統函數dlsym調用函數dexReadAndVerifyClassData獲取類對應的數據結構信息,這裏依然需要用IDA打開libdvm.so文件查看這個函數的導出名稱:

獲取到類對應的數據結構DexClassData信息,之後就可以獲取類中的方法個數和具體信息了,這裏再來看一下DexClassData數據結構信息,這個結構體在DexClass.h中,我們依然把結構信息拷貝到我們的工程中即可:

有了這個結構體,下面就來獲取方法的個數,這裏的方法分爲類方法和對象方法,在DexDataClassHeader結構體信息中,這裏我們利用系統函數dexGetClassData獲取類的代碼數據結構:

接下來,繼續看如何獲取類中的方法信息:

因爲我們知道那個calculateMoney方法是對象方法,所以這裏直接獲取對象方法結構體信息,然後依次遍歷獲取每個方法,通過系統函數dexGetMethodId獲取DexMethodIds結構體信息:

這裏需要注意的是每個方法都是在內存中依次挨着的,所以直接利用指針操作即可獲取每個方法的結構體信息。然後在利用系統函數dexStringById獲取方法名稱,這個也是系統函數,一樣方式拷貝到工程中來即可:

有了方法名就需要進行過濾了,只處理我們的那個calculateMoney方法,然後在獲取方法對應的數據結構信息DexCode了,依然如此,我們需要把DexCode結構體信息從系統中拷貝到工程中:

然後利用系統函數dexGetCode通過DexMethod結構體獲取DexCode結構體信息

有了DexCode結構體信息之後,我們可以打印方法的原始指令數據:

然後我們因爲需要修改內存指令,所以還需要把內存修改爲可讀屬性:

這裏需要注意的是,修改的起始地址一定是系統內存頁的整數倍,所以需要做一次轉化。修改完內存屬性之後。

 

四、修改指令邏輯

接下來就可以構造指令,然後替換內存指令即可。那麼如何獲取原始指令,怎麼把乘法改成加法呢?這裏就需要利用010Editor軟件了,直接查看這個方法的指令數據:

這裏看到,這個方法有三條指令,但是一條指令是兩個字節,所以一共是6個字節,這裏看到的是十進制的數據了,我們可以把這三個十進制數據轉化成6個十六進制數據:

然後我們現在只需要把乘法指令碼改成加法指令碼即可,這個需要參考Bytecode of Dalvik了:

這裏也看到加法指令就是十六進制的90,也就是十進制的144,所以咋們替換指令就簡單了:

替換指令之後,在此打印指令數據即可,好了,到這裏我們就完成了所有的操作了,下面就來運行看看日誌信息了:

看到日誌,我們成功的把指令146變成了144了,在往下看日誌,就可以看到計算結果是加法了,也就是5:

就這樣我們在內存中修改了這個方法的指令邏輯,把乘法邏輯變成了加法邏輯了。神奇吧。到這裏我們也算介紹完了本文的大致內容了。不過有的同學可以看得沒太明白,沒關係,下面在來總結一下流程:

 

五、流程總結

首先明確我們的目的就是想能修改內存中指定方法的運行指令邏輯,那麼不用之前介紹的那個讀取內存中的dex數據然後靠地址來檢索到指令地址,而是採用hook系統函數來實現:

第一步:就需要找到hook點,每個方法要想運行肯定是先將方法所屬的類加載到內存中,那麼就需要調用系統的函數:dexFindClass,而這個函數的返回值是一個DexClassDef結構體信息。

第二步:通過DexClassDef結構體信息獲取類的數據結構體信息DexClassData,然後獲取類的所有方法信息。

第三步:遍歷方法結構體信息DexMethod,找到我們想要處理的方法信息,然後在獲取其DexCode方法數據結構體信息。

第四步:有了方法數據結構體信息之後,就可以獲取到方法的指令個數和具體指令數據,在修改之前必須修改內存屬性爲可讀寫的。

第五步:通過查閱虛擬機指令集,找到加法指令碼替換原來的乘法指令碼,然後覆蓋內存中的原始指令即可。

所以在這個過程中發現,會涉及到很多數據結構體,不過好在這些結構體信息都定義在DexFile.h和DexClass.h這兩個頭文件中,他們存放在[Android源碼/dalvik/libdex/]目錄下。而這些結構體信息也是相互包含的,下面就來整理一下:

 

本文的目的只有一個就是學習逆向分析技巧,如果有人利用本文技術進行非法操作帶來的後果都是操作者自己承擔,和本文以及本文作者沒有任何關係,本文涉及到的代碼項目可以去編碼美麗小密圈自取,歡迎加入小密圈一起學習探討技術

 

六、總結

當然本文介紹完了之後,有一個很大的用途就是hook虛擬機的一些函數來做一些事情,其實說到這裏本文並不是本次研究的重點,重點是下一篇文章,只是爲了需要hook系統的函數,本文就先做個鋪墊而已。聰明的人應該知道我下一篇文章要介紹啥了,而下一篇文章纔是重點。篇幅原因,不得不將其內容拆分了。看文本文之後,一定要記得一點,不僅在java層可以免root進行hook操作,在native也是可以的。這個知識點對未來安全防護,逆向分析,應用開發都非常重要。

《Android應用安全防護和逆向分析》

點擊立即購買:京東  天貓  

更多內容:點擊這裏

關注微信公衆號,最新技術乾貨實時推送

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章