天天看點

android art虛拟機安裝,[筆記]ZjDroid适配art虛拟機的嘗試

[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的原因:

android art虛拟機安裝,[筆記]ZjDroid适配art虛拟機的嘗試

接下來

我們隻需要第二個位置開始的内容,每一個元素都是一個位址,把位址傳到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出這一塊記憶體經過分析可知

記憶體結構對應關系為:

android art虛拟機安裝,[筆記]ZjDroid适配art虛拟機的嘗試

這裡的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/

參考