最近開始使用Cydia Substrate框架進行逆向分析。
Cydia是什麼東東我就不多說了,自行百度,聽說它可以越獄什麼的,修改手機配置什麼的,但這裏只是通過一個例子,介紹如何使用Cydia Substrate框架進行hook Android java代碼。
相比Xposed框架,Cydia是有一定的優勢的,根據我目前使用的情況,暫時有如下兩點:
一是,Cydia能hook除了第一個smali文件夾外的第二第三個smali文件夾(如下圖)。
這話怎說?APKTool反編譯一個APK時,有時會分割成好幾個smali文件,Xposed只能hook第一個smali文件夾下的smali文件,有點坑,但Cydia卻都可以hook。
二是,Cydia能hook本地方法,而Xposed不可以。
基於以上兩個優點,個人認爲Cydia比Xposed好用,Xposed侷限性較大,起碼目前是這麼認爲的~
但有個技巧的方法只能在Xposed上用,就是通過刻意拋異常,根據異常日誌找方法調用路徑,這個在逆向分析常用的技巧無法再Cydia上用,因爲Cydia拋異常後貌似不會以日誌的形式打印出來,反正我試了很多次都不行。。。
開始說說如何使用吧
-------------------------------------------------------
1. 首先,下載com.saurik.substrate.apk,然後安裝,手機必須得root過。這個是Cydia Substrate框架,需要另寫模塊加載進去。
安裝之後點擊 “Link Substrate Files” 即可。
2. 確定需要hook的對象APK中的方法,編寫模塊代碼。
這裏,我將hook一個apk裏被混淆過的方法,並說明注意點。
因爲混淆過,所以類名和方法名都被改成a,b,c等
該方法所在類:com.dianping.h.f.a.o
方法名:b,
參數:com.dianping.nvnetwork.u 類
返回值 :com.dianping.nvnetwork.u 類
好,開始寫模塊,先在Android Studio裏創建一個工程,該工程不需要Activity,在工程創建一個java文件,該文件就是模塊代碼,然後下載Cydia Substrate SDK,解壓後將其放到libs文件夾下,並將jar包“Add as library”。
接下來,打開AndroidManifest,配置好一個meta-data和一個permission即可:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.samuelzhan.substratehook">
<application>
<meta-data android:name="com.saurik.substrate.main"
android:value=".Module"/>
</application>
<uses-permission android:name="cydia.permission.SUBSTRATE"/>
</manifest>
然後,編寫模塊代碼:
public class Module {
static void initialize(){
MS.hookClassLoad("com.dianping.h.f.a.o",
new MS.ClassLoadHook() {
public void classLoaded(Class<?> resources) {
Method method;
try {
// 不能使用本進程的ClassLoader,必須使用hook對象進程的ClassLoader
// Class<?> u=ClassLoader.getSystemClassLoader().loadClass("com.dianping.nvnetwork.u");
// Class<?> u = Class.forName("com.dianping.nvnetwork.u").getClass();
Class<?> u= resources.getClassLoader().loadClass("com.dianping.nvnetwork.u");
// getMethod只能獲取public方法,getDeclareMethod能獲取所有方法
// method = resources.getMethod("b", u);
method = resources.getDeclaredMethod("b", u);
} catch (Exception e) {
Log.d("zz", "exception: "+e.toString());
method = null;
}
if (method != null) {
final MS.MethodPointer old = new MS.MethodPointer();
MS.hookMethod(resources, method, new MS.MethodHook() {
@Override
public Object invoked(Object o, Object... objects) throws Throwable {
// before hook
Log.d("zz", "hook 成功");
Object result = old.invoke(o, objects);
// after hook
return result;
}
}, old);
}
}
});
}
第一,模塊和hook對象的進程是兩個進程,所以使用ClassLoader加載 u 類時要從resources那裏getClassLoader,這是獲取hook對象的ClassLoader,不然你加載不了 u 類而拋異常ClassNotFound(當然,如果只是使用java原生的類,即不是自定義的類的話,那麼直接 .class後綴就可以獲取到 class<?>了)。
第二,獲取該類下的方法時,使用getDeclaredMethod可以獲取所有方法,包括public,private,protected,而getMethod只能獲取public。
因爲我hook的那個函數是protected的,但由於筆者之前一直用getMethod而導致沒法hook到這方法。。。
Method method = resources.getDeclaredMethod("b", u);
第三,在方法被hook時,回調invoked方法,在此方法內有一句
Object result = old.invoke(o, objects);
該行代碼的上面和下面分別對應Xposed的beforeMethodHook和afterMethodHook兩個回調方法,如下圖:
3. 模塊寫好後,導出項目,然後安裝到手機上,手機若安裝了Cydia SubStrate,則會在通知欄提醒軟重啓。
重啓手機後,我們運行我們的目標APK即可以被Cydia Substrate 模塊識別到,並打印出相關日誌。
------------------------------------------------
到此爲止,Cydia Substrate的基本使用已到此爲止。
附加內容(可以忽略):
我上面提到的參數和返回值都是 u類實例,我之所以要hook這個 u類,是因爲該 u 類內有個變量 InputStream,混淆後以 g 字符命名。
爲了看到這個InputStream的內容,我必須通過反射找到該類的字段g,所以模塊的寫法會有所變化,但變化不大,增加了Field變量的獲取,直接代碼:
public class Module {
static void initialize(){
MS.hookClassLoad("com.dianping.h.f.a.o",
new MS.ClassLoadHook() {
public void classLoaded(final Class<?> resources) {
try {
Class<?> u= resources.getClassLoader().loadClass("com.dianping.nvnetwork.u");
final Method method = resources.getDeclaredMethod("b", u);
final Field field=u.getDeclaredField("g");
final MS.MethodPointer old = new MS.MethodPointer();
MS.hookMethod(resources, method, new MS.MethodHook() {
@Override
public Object invoked(Object o, Object... objects) throws Throwable{
// before hook
InputStream is1=(InputStream)field.get(objects[0]);
String before=byte2HexString(inputStream2Byte(is1));
Log.d("zz", "Before HexString:"+before);
is1.reset();
Object result = old.invoke(o, objects);
// after hook
InputStream is2=(InputStream)field.get(result);
String after=byte2HexString(inputStream2Byte(is2));
Log.d("zz", "After HexString:"+after);
is2.reset();
return result;
}
}, old);
} catch (Exception e) {
Log.d("zz", "exception: "+e.toString());
}
}
});
}
public static byte[] inputStream2Byte(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int n = 0;
while ((n = is.read(buffer)) != -1) {
baos.write(buffer, 0, n);
}
return baos.toByteArray();
}
public static byte[] hexString2Byte(String hex) {
int len = (hex.length() / 2);
hex=hex.toUpperCase();
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
運行結果:
好了,Cydia的介紹到此爲止,其實還有其它功能的,比如hook C/C++的方法,這個日後再繼續研究。