[md]上星期趁着放假玩了玩ZjDroid,自己編譯了一個來玩,最終克服萬難總算找齊了源碼,給編譯出來了。
上一篇文章:
雖然,最終拿出來的大數字加強的dex沒能恢複onCreate這個native方法(本人實在太菜),但是其他部分還是能看源碼的。
起因
最近一個朋友給我看了一個愛加密的包,我放到模拟器裡面用我的ZjDroid脫,沒想到這個包卻主動退出了!
我以為是檢測到了ZjDroid,就解除安裝了ZjDroid,結果還是崩,後來上網查才發現,愛加密檢測到是模拟器環境就會主動退出。
這可讓我費腦筋啊!
我手上隻有Android7.1.2的裝置,而目前的ZjDroid隻支援dalvik虛拟機上跑,這可咋辦呢,要我刷機?懶得備份。。。
我記得ZjDroid的源碼最後是4年前更新的,然後作者就不維護了,于是我想能不能學習ZjDroid的原理去适配art呢?
打開as就開始搗鼓了!
稍微嘗試
嘗試在Android7.1.2上面安裝ZjDroid,重新開機,打開上次我拆的應用(就是那個我自己的應用啦)。
看log,除了幾個礙眼的異常以外,沒什麼大狀況出現,
嗯,
發送廣播執行dump_dexinfo指令,然後一下子就崩了。
這個問題,我在上一篇文章裡面就提到過了。
ZjDroid在執行dump_dexinfo指令的時候并沒有用到native層的函數,隻是通過反射擷取dalvik.system.DexFile中的mCookie變量列印出來,但是發生了類型強制轉換的錯誤,錯誤地把long[]類型轉換為了int類型。
解決的辦法是:
查閱Android源碼,對這個openDexFileNative分sdk版本适配
art虛拟機的mCookie
但是還有一個問題就是,
我們要的mCookie究竟是什麼樣子的呢?
通過對ZjDroid的舊版本代碼進行分析發現,在dalvik虛拟機中,mCookie實際上就是一個結構體的記憶體位址,通過這個結構體可以獲得記憶體中dex檔案的位址,然後就能dump出來了。
既然如此,在art裡面的mCookie時一個long[]類型的,我們就很有必要去了解這個東西是怎麼形成的了。
有一些情況下是傳回空指針的,我們就隻看傳回正常值的情況,在第184-194行,
···
184 if (!dex_files.empty()) {
185 jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
186 if (array == nullptr) {
187 ScopedObjectAccess soa(env);
188 for (auto& dex_file : dex_files) {
189 if (linker->FindDexCache(soa.Self(), *dex_file, true) != nullptr) {
190 dex_file.release();
191 }
192 }
193 }
194 return array;
···
這個ConvertDexFilesToJavaArray函數應該是很重要的一個函數
看看它的實作
77static jlongArray ConvertDexFilesToJavaArray(JNIEnv* env,
78 const OatFile* oat_file,
79 std::vector<:unique_ptr dexfile>>& vec) {
80 // Add one for the oat file.
81 jlongArray long_array = env->NewLongArray(static_cast(kDexFileIndexStart + vec.size())); //初始化一個long類型的Java數組
82 if (env->ExceptionCheck() == JNI_TRUE) {//檢查是否出現異常
83 return nullptr;
84 }
85
86 jboolean is_long_data_copied;
87 jlong* long_data = env->GetLongArrayElements(long_array, &is_long_data_copied);//這裡應該是擷取剛剛生成的Java的long類型數組中元素的原始的指針,熟悉c語言的就知道,c中的數組是一塊連續的記憶體結構,通過指針可以讀取數組中的任意一個位置的元素
88 if (env->ExceptionCheck() == JNI_TRUE) {//檢查是否出現異常
89 return nullptr;
90 }
91 // 這裡的kOatFileIndex定義在了dalvik_system_DexFile.h檔案中:值是0;
// http://androidxref.com/7.1.2_r36/xref/art/runtime/native/dalvik_system_DexFile.h#25
92 long_data[kOatFileIndex] = reinterpret_cast(oat_file);//這裡是c++的一種類型轉換的方式,把參數轉化為了uintptr_t類型,而uintptr_t類型是一種指針類型,就把它看作一個指針吧。
93 for (size_t i = 0; i < vec.size(); ++i) {//可以看到,之前在數組中第一個位置放了oat_file的位址,然後接下來從kDexFileIndexStart(這個值是1,也在上面那個檔案裡定義了)開始,把vec數組裡面的東西填之前生成的數組裡。
94 long_data[kDexFileIndexStart + i] = reinterpret_cast(vec[i].get());
95 }
96
97 env->ReleaseLongArrayElements(long_array, long_data, 0);//重新整理數組資訊(比如長度等)
98 if (env->ExceptionCheck() == JNI_TRUE) {//檢查是否出現異常
99 return nullptr;
100 }
101
102 // Now release all the unique_ptrs.
103 for (auto& dex_file : vec) {
104 dex_file.release();
105 }
106
107 return long_array;
108}
可以大概了解到,第一個位置被指派為oat_file這個指針(實際上就是把指向的位址存到了第一個位置裡),然後依次填充vec這個數組裡的東西到之前的long數組裡面,看看這個vec:
在參數清單裡:
std::vector<:unique_ptr dexfile>>& vec
不要慌,看起來很複雜,但其實不難了解:
vec是一個引用,引用的是一個vector(可變長數組)對象,這個對象裡裝的都是unique_ptr類型,這也是一種指針,可以看到這個東西指向的類型是DexFile,上面的那段代碼應該就是把這些指針指向的位址資訊填到long數組裡面了
最終傳回的long類型數組裡面,應該全都是位址。
art中的DexFile
再看看DexFile這個東西:
在
class DexFile {
是一個class,裡面還有結構體比如
struct Header {
之類的,這好像和dex檔案的結構有點關聯了。
繼續往下翻
···
1234 // The base address of the memory mapping.
1235 const uint8_t* const begin_;
1236
1237 // The size of the underlying memory allocation in bytes.
1238 const size_t size_;
1239
1240 // Typically the dex file name when available, alternatively some identifying string.
1241 //
1242 // The ClassLinker will use this to match DexFiles the boot class
1243 // path to DexCache::GetLocation when loading from an image.
1244 const std::string location_;
1245
1246 const uint32_t location_checksum_;
1247
1248 // Manages the underlying memory allocation.
1249 std::unique_ptr mem_map_;
1250
1251 // Points to the header section.
1252 const Header* const header_;
1253
1254 // Points to the base of the string identifier list.
1255 const StringId* const string_ids_;
1256
1257 // Points to the base of the type identifier list.
1258 const TypeId* const type_ids_;
···
這個begin_的描述,似乎是什麼什麼記憶體映射的基位址,下面還有這個塊區域的大小size_,接下來是一些指針,Header,StringId,TypeId啥的,結合相關代碼,我猜測這就是我們要找的dex檔案的資訊了。
總結
總結一下:
Java層擷取到的mCookie是一個long類型數組,裡面都是位址,其中第一個位址是oat_file也就是oat過的檔案的位址,當然現在大多數加強都不會允許虛拟機進行oat操作了,因為oat操作會在儲存中生成優化過的oat檔案,對于加強來說,無疑是自己把代碼給出去了。這也是我們上一篇文章裡,long數組第一個元素為0的原因:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CMiBDNlNWOlBjNjJmZyUmN1I2MzEGNihjYmJWN0UWZk9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
接下來
我們隻需要第二個位置開始的内容,每一個元素都是一個位址,把位址傳到native方法裡,在native層裡,這個位址指向的就是一個DexFile類,而因為類也類似于結構體,也有它的儲存結構,隻要找到begin_和size_的内容就能dump出記憶體中的dex(odex)檔案。
遇到的幾個問題
關于記憶體對不到的問題:
我們知道C++和Java是有很大的差別的,在C++裡面,一個變量,編譯了以後,在運作時你是不能通過變量的名稱來找到這個類的。因為這些變量都變成了位址或者偏移量。隻代表某個記憶體區域。不能像Java那樣通過反射動态擷取。
是以,和結構體類似,想要擷取一個C++對象的某個成員變量,你隻能通過這個對象的位址+這個成員變量在這個對象中的相對位置來擷取到,
前者我們容易得到,而後者,則需要構造一個和生成這個對象的class或者struct一模一樣的class或者struct然後通過指針的形式取得其中的成員變量
看到這裡可能就有人有疑問了:
為什麼我在原始的ZjDroid源碼裡面看到了和Android源碼裡面一樣的結構體定義或者class定義?
為什麼是一模一樣?
那是因為,隻有一模一樣,才能有一樣的偏移量啊!
假設已經取得的DexFile的某個對象的位址adress(假設是long類型),想獲得對象中的成員變量begin_的值,應該用以下的步驟
DexFile *dexFile_ptr = (DexFile *)adress; // cast為DexFile類型的指針
dexFile_ptr -> begin_; // 這樣取得begin_的内容
對于第一個問題:設想如果你的代碼裡面沒有DexFile的定義,怎麼通過編譯?編譯器會告訴你找不到符号
實際上第二句:
dexFile_ptr -> begin_;
可以了解為:
(dexFile_ptr的位址 + begin_這個成員變量在對象裡的相對位置)就是begin_的内容在記憶體中的位置
而這個相對位置,是編譯時決定的,與class的結構有關,與編譯器有關,與平台有關。
關于C++對象的記憶體結構
以下是我個人所了解到的
C++中的函數與類綁定,在對象中不占記憶體
C++中的static成員變量與類綁定,在對象中不占記憶體
挫折
通過dump出這一塊記憶體經過分析可知
記憶體結構對應關系為:
這裡的std::string占了3 * 4 = 12個位元組,而我編譯出來的so裡面,它是隻占了4個位元組的。
這導緻,在那個string之後的内容都發生錯位,也就是說,我編譯出來的class,偏移量和art虛拟機裡面的so檔案裡的不一樣,這導緻向ZjDroid發送backsmali指令無法使用
解決辦法:
遺留問題
由于原始版本的源碼過于老舊,似乎隻适配到Android sdk 17
在高版本上部分api發生改變,會引發異常
已知:
應用敏感行為監控有部分功能不能使用,尤其是網絡相關,比如新版Android删了apache的http庫改用Okhttp,ZjDroid還未跟進。
ZjDroid的backsmali指令雖然擷取dex資訊部分(native層)已經搞定,但是ZjDroid所使用的org.jf.dexlib2等庫是四年前的版本,不支援art,要改為新版的話,要做很多修改。
出爐
歡迎送出改進
檢視Android源碼的網站
grepcode:支援檢視Android5.1.1及以前的源碼,支援檔案比較
http://www.grepcode.com/
androidxref:資源全,但檔案比較功能沒上面的那個好用
http://androidxref.com/
參考