1. 程式人生 > >執行時修改記憶體中的Dalvik指令來改變程式碼邏輯

執行時修改記憶體中的Dalvik指令來改變程式碼邏輯

一、前言
最近在弄脫殼的時候發現有些加固平臺的加固方式是修改了dex檔案結構,然後在載入dex到記憶體的時候,在進行dex格式修復,從而達到了apk保護的效果,那麼在dex載入到記憶體的時候,如何進行dex格式的修復呢?其實原理就是基於執行時修改記憶體中的Dalvik資料,本文就來用一個簡單的例子來介紹一下如何在記憶體中去修改Dalvik指令程式碼來改變程式碼本生的執行邏輯。
首先來說一下案例場景:

本文的案例就是將一個類的方法改變他的執行邏輯,原來的邏輯是加法操作,現在改成乘法操作。

二、案例介紹
這裡在TestAdd類中有一個加法的操作方法,然後我們在native層去修改Dalvik指令,修改這個方法為乘法操作
這裡寫圖片描述


這裡我們可以看看TestAdd類的實現:
這裡寫圖片描述
只是一個簡單的加法操作,下面來呼叫執行測試:
這裡寫圖片描述
測試結果如下:
這裡寫圖片描述
看到了測試結果,發現是乘法,而不是加法的結過,好了,到這裡我們就演示完了專案結構,下面來開始說說如何修改的。
三、程式碼解析
這裡把修改指令的邏輯程式碼放在了native層做的,其實放到Java層也是可以的,但是會發現在操作記憶體位元組的時候,Java會顯得很無力,因為他沒有指標,指標真的很好很強大,特別是在操作檔案和記憶體位元組的時候,那寫程式碼都很爽的。
在MainActivity中定義了一個native方法:

public static native int
modifyBytecode();

我們使用javah指令產生標頭檔案:
這裡寫圖片描述
執行命令的時候,注意目錄和類全名沒有後綴。
把產生的標頭檔案拷貝到工程中:
這裡寫圖片描述
四、原理解析
先說一下dex的檔案結構

到這裡我們就介紹完了上面幾種資料結構,下面來總結一下:
首先用一張圖來看看:

1》首先介紹了Uleb128資料結構,這個資料結構是貫穿所有的資料結構的,所以這個資料結構是最基本的。
2》然後介紹了string_ids_item資料結構,這裡需要把dex中所有的字串解析出來存放在一個常量池中,後續的資料結構會通過索引值來獲取。比如type_ids_item資料結構中的idx欄位指向的就是這個索引值。
3》然後介紹了type_ids_item資料結構,這裡的欄位主要就是指向字串常量池的索引值,解析完這個資料結構之後也是需要用一個常量池儲存索引值,後續的資料結構會通過索引值來獲取,比如class_idx資訊。
4》然後介紹了class_def_item資料結構,這裡的欄位主要是類的具體資料的偏移地址,類的索引值class_idx(這個是通過TypeId池獲取)。
5》然後介紹了method_id_item資料結構,這裡主要是開始解析方法id內容,其中name_idx欄位是字串常量池中的值(通過StringId池獲取),以及這個方法所在類的class_idx值。
6》然後介紹了class_data_item資料結構,這個結構主要儲存的是這個類對應程式碼的元資訊,其中direct_methods thod欄位和virtual_methods欄位就是儲存的是類對應的指令元資訊。他們的結構是encoded_method。
7》然後介紹了encoded_method資料結構,這個結構中主要儲存的是這個方法對應的元資訊,其中method_idx_diff欄位是上面得到的Method型別的id常量池索引值,code_off欄位是這個方法指令對應的相對地址。
8》最後介紹了code_item資料結構,其中insns欄位就是儲存的是方法的指令資料。
來看看各個資料結構之間的關係:
這裡寫圖片描述


這裡就算全部解析完了本文需要用到的資料結構,以及這些結構相互引用關係,這裡還需要注意的是:這裡的一些結構的解析地址是存放在dex的頭部資訊中的:
這裡寫圖片描述
下面我們在看看程式碼怎麼去實現它,首先我們通過上面的例子看到,有一個字串索引常量池,而我們現在如果想改一個方法的指令程式碼的話,肯定得找到他對應的code_off地址即可,現在我們有的入口條件就是:方法名+類名,所以思路是這樣的:
類名=》字串索引常量池=》型別TypeId=》類的定義元資訊ClassDefItem=》類的程式碼元資訊ClassDataItem

方法名=》字串索引常量池+類的TypeId=》方法Id

方法Id+類程式碼元資訊ClassDataItem=》方法的元資訊EncodeMethod=》方法的指令程式碼結構Code
有了這個思路,下面來看具體程式碼吧:

第一步:獲取應用的記憶體資料地址,讀取/proc/pid/maps檔案
這裡寫圖片描述
第二步:解析出dex檔案的記憶體地址
因為我們知道一個程序的maps記憶體中是有很多資料的,所以這裡需要先找到dex資料對應的起始地址:
這裡寫圖片描述