抽取样本java实验报告_一个自定义classloader的函数抽取壳样本
原標題:一個自定義classloader的函數抽取殼樣本
本文為看雪論壇文章
看雪論壇作者ID:lemn
本文為 看雪安卓高研2w班(7月班)優秀學員作品。
下面先讓我們來看看學員的學習心得吧!
學員感想
本題目出自2W班7月第三題。
題目要求:基于frida實現的fart的一個版本是通過對ClassLink類中的LoadMethod函數進行hook實現對函數粒度的脫殼的。請編寫基于xposed實現的fart版本插件,能夠實現對
函數粒度的脫殼。
根據題目要求,可以知道,我們通過hook LoadMethod即可得到DexFile進而得到base和size,即可dump出dex。如果有函數抽取,即遍歷所有的類得到codeitem后還原即可。
本次實踐分為兩個部分,一個即是原始思路,照葫蘆畫瓢參照FART的脫殼思路去編寫代碼,遇到困難暫時沒法解決的時候,轉變思路二,改用FART配合xposed去實現。
ps. 題目附件請點擊“閱讀原文”下載。
解題過程
首先我們可以分析frida-fart是如何實現demp dex的。
粗略分析一下就是:
1. 拿到dexfile 就可以拿到base size
2. 拿到artmethod 就可以拿到codeitem offset和method idx
3. 計算codeitem的長度
4. dump出來
那么我們在so層也可以畫葫蘆試試。測試安卓版本為8.1,部分代碼如下:
void*pVoid = old_loadmethod3(thiz, thread, dex_file, it, klass, artmethod);
__android_log_print( 5, "hookso", "pVoid ptr:%p", pVoid);
獲取base和size
constDexHeader *base = dex_file.pHeader;
size_tsize = dex_file.pHeader->fileSize;
獲取code item offset和method idx
uint32_tcodeItemOffset = artmethod->dex_code_item_offset_;
uint32_tidx = artmethod->dex_method_index_;
hook prettymethod方法 主動調用獲得方法名
conststd:: string& string= prettyMethodFunction(artmethod, artmethod,
true);
通過偏移可以拿到codeitem
longcodeItemAddr = ( long) base + codeItemOffset;
CodeItem *codeItem = (CodeItem *) codeItemAddr;
這部分代碼可以直接dump dex出來
intpid = getpid;
chardexFilePath[ 100] = { 0};
sprintf(dexFilePath, "/sdcard/xxxxx/%p %d LoadMethod.dex", base, size);
mkdir( "/sdcard/xxxxx", 0777);
intfd = open(dexFilePath, O_CREAT | O_RDWR, 666);
if(fd > 0) {
ssize_ti = write(fd, base, size);
if(i > 0) {
close(fd);
}
}
...
上述看起來一步一步的確是可以拿得到codeitem,然后進一步拿得到ins的。
但是有一個問題我一直沒法解決,就是某些方法會報access violation 異常。我通過搜索發現這個是底層發出來的異常,軟件層貌似沒法catch。
通過使用frida-fart我發現frida版本的也有這樣的問題,但是frida這邊可以通過try catch捕捉,讓程序繼續行走。
因為暫時無法解決這個問題,所以我放棄這個方向。嘗試用fart進行dump。
這里偷懶,就直接用寒冰大佬的xp+fart rom繼續dump,就沒有自己編譯源碼了。這里環境android6.0。
那么這時候思路就轉變了,通過hook loadMethod方法可以拿到artmethod,
而根據fart代碼,dumpArtMethod這個方法,傳入artmethod即可dump。
先執行原來的loadMethod邏輯
void*pVoid = old_loadmethod3(thiz, thread, dex_file, it, klass, artmethod);
__android_log_print( 5, "hookso", "pVoid ptr:%p", pVoid);
然后通過so層hook dumpArtmethod函數,并將參數傳入
try{
dumpArtMethodFunction(artmethod);
} catch(...){
}
上述通過so層調用FART的dumpArtMethodFunction方法,可以dump出dex,以及classlist。這是FART自帶的功能。
而在java層,我們需要遍歷所有類。首先hook掉DexClassLoader和PathClassLoader的構造函數,并將classloader存起來。
因為是自定義的Xposed,而所以名字為XcustomBridge。其實這里就是Xposed,僅供參考,不可照抄:
XcustomBridge.hookAllConstructors(DexClassLoader.class, newXC_MethodHook {
@Override
protectedvoidafterHookedMethod(MethodHookParam param)throwsThrowable{
super.afterHookedMethod(param);
finalClassLoader classLoader = (ClassLoader) param.thisObject;
XcustomBridge.log( "DexClassLoader:"+ classLoader.toString);
mClassLoaders.add(classLoader);
}
});
XcustomBridge.hookAllConstructors(PathClassLoader.class, newXC_MethodHook {
@Override
protectedvoidafterHookedMethod(MethodHookParam param)throwsThrowable{
super.afterHookedMethod(param);
finalClassLoader classLoader = (ClassLoader) param.thisObject;
XcustomBridge.log( "PathClassLoader:"+ classLoader.toString);
mClassLoaders.add(classLoader);
}
});
緊接著加載我們的so,并遍歷所有的classloader,執行loadClass操作:
XcustomHelpers.findAndHookMethod(Application.class, "attach", Context.class, newXC_MethodHook {
@Override
protectedvoidafterHookedMethod(MethodHookParam param)throwsThrowable{
super.afterHookedMethod(param);
XcustomBridge.log( "attach after");
mContext = (Context) param.args[ 0];
XcustomHelpers.callMethod(Runtime.getRuntime, "doLoad", "/system/lib/libnative-lib.so", mContext.getClassLoader);
newThread( newRunnable {
@Override
publicvoidrun{
try{
Thread.sleep( 30* 1000);
} catch(InterruptedException e) {
e.printStackTrace;
Log.e( "hook1", Log.getStackTraceString(e));
}
for( inti = 0; i < mClassLoaders.size; i++) {
ClassLoader classLoader = mClassLoaders.get(i);
TestClassloader(classLoader);
}
fart;
}
}).start;
}
});
將dump下來后bin文件批量恢復后,即可查看到,函數已經恢復了。
但是如果點多幾個函數查看,會發現部分函數并沒有復原:
那么通過回想,我們可以得知,還原的代碼其實都是app啟動過程中調用過的方法,所以dump下來后,是包含代碼的。因此這個殼也是函數抽取殼,而且恢復后不會復原。
而查看FART dump出來的ins文件,發現也缺了很多數據,那么這里可以猜測是某個環節出了問題導致沒有dump出來。
然后通過回溯error log可以發現是classloader的鍋:
/**
* 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: java.lang.IllegalArgumentException: Expected receiver of typedalvik.system.BaseDexClassLoader, but got com.bytedance.frameworks.plugin.core.DelegateClassLoader
* 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: at java.lang.reflect.Field.get(Native Method)
* 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3.getFieldOjbect(Hook3.java:206)
* 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3.TestClassloader(Hook3.java:259)
* 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3.fart(Hook3.java:240)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3 $3$1.run(Hook3.java:95)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at java.lang.Thread.run(Thread.java:818)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: java.lang.NullPointerException: null receiver
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at java.lang.reflect.Field.get(Native Method)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3.getFieldOjbect(Hook3.java:206)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3.TestClassloader(Hook3.java:260)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3.fart(Hook3.java:240)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at com.l.sevenclasshook.hook.Hook3 $3$1.run(Hook3.java:95)
* 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: at java.lang.Thread.run(Thread.java:818)
那么這里我們知道了這個app有自定義的classloader。
根據提示,我們可以發現這是一個直接繼承于classloader的類。
那么來看看通常我們是如何獲取classlist的:
Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
ObjectpathList_object = getFieldOjbect( "dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
Object[] ElementsArray = ( Object[]) getFieldOjbect( "dalvik.system.DexPathList", pathList_object, "dexElements");
Field dexFile_fileField = null;
try{
dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
} catch(Exception e) {
e.printStackTrace;
} catch( Errore) {
e.printStackTrace;
}
......
我們是通過獲得BaseDexClassLoader的pathList字段從而進一步往下獲取classList的。但是由于我們現在是直接繼承于classloader,所以我們要想辦法獲取classlist。
那么這里就有兩種方法可以實現了。
第一個方法, fart使用過程中使dump classlist出來:
if(appClassloader instanceofBaseDexClassLoader) {
} elseif(appClassloader instanceofClassLoader) {
List< String> nameList = newArrayList<>;
BufferedReader br = null;
try{
br = newBufferedReader( newInputStreamReader( newFileInputStream( newFile(Environment.getExternalStorageDirectory + "/8958236_classlist.txt")),
"UTF-8"));
StringlineTxt = null;
while((lineTxt = br.readLine) != null) {
if(!TextUtils.isEmpty(lineTxt)) {
Stringname = lineTxt.replace( "L", "").replaceAll( "/", ".").replace( ";", "");
Log.e( "hook1", "after replace name:"+ name);
nameList.add(name);
}
}
br.close;
} catch(Exception e) {
Log.e( "hook1", Log.getStackTraceString(e));
}
for( Stringname : nameList) {
try{
Log.e( "Hook1", "===================>loadClass:"+ name);
appClassloader.loadClass(name);
} catch(Exception e) {
Log.e( "Hook1", Log.getStackTraceString(e));
}
}
return;
}
因此這里我們直接遍歷文件,然后loadClass即可。
接下來我們對dex方法體進行批量恢復后,可以看到函數都恢復了。
少部分沒有恢復的函數,是因為這個classloader沒有找到類,報class not found。接下來我們可以將所有classloader都跑一遍這個list即可。
第二種方法:其實fart也是解析DexFile從而拿到classlist的。既然有了整個dexFile文件,那么有啥不能解析的呢。
if(size == 8958236) {
u4 classDefSize = dex_file.pHeader->classDefsSize;
u4 classDefsOff = dex_file.pHeader->classDefsOff;
__android_log_print( 5, "hookso", "base:%p", ( void*) base);
__android_log_print( 5, "hookso", "classDefSize:%ld classDefsOff:%p", classDefSize,
( void*) classDefsOff);
u4 typeIdsOff = dex_file.pHeader->typeIdsOff;
u4 stringIdsOff = dex_file.pHeader->stringIdsOff;
__android_log_print( 5, "hookso", "typeIdsOff:%p stringIdsOff:%p", ( void*) typeIdsOff,
( void*) stringIdsOff);
inti;
for(i = 0; i < classDefSize; i++) {
longcurrClassAddr = ( long) classDefsOff + i * 32+ ( long) base;
__android_log_print( 5, "hookso", "currClass ptr:%p", ( void*) currClassAddr);
int*idx = ( int*) (currClassAddr);
__android_log_print( 5, "hookso", "currClassIdx:%i", *idx);
inttmpIdx = *idx;
longcurrTypeIdAddr = (( long) typeIdsOff + 4* tmpIdx + ( long) base);
int*currTypeIdx = ( int*) currTypeIdAddr;
__android_log_print( 5, "hookso", "currTypeIdx:%ld", *currTypeIdx);
tmpIdx = *currTypeIdx;
longcurrStringOffAddr = (( long) stringIdsOff + 4* tmpIdx + ( long) base);
int*currStringOff = ( int*) currStringOffAddr;
__android_log_print( 5, "hookso", "currStringOff:%ld", *currStringOff);
tmpIdx = *currStringOff;
longoff = ( long) base+ tmpIdx;
__android_log_print( 5, "hookso", "string off:%p", ( void*) off);
constuint8_t *strPtr = (uint8_t *) off;
DecodeUnsignedLeb128(&strPtr);
char*classname = ( char*) strPtr;
__android_log_print( 5, "hookso", "classname:%s", classname);
}
}
在這里我們可以一層一層的不斷獲取不斷遍歷,就可以拿到我們想要的classname:
拿到classname后我們就可以用這個去遍歷loadClass后再dump dex即可。
可以看到這個題目最后想要說的就是自定義classloader如何進行函數抽取還原,那么這里我們需要熟悉Dexfile的文件結構以及了解classloader如何使用。
看雪ID:lemn
好消息!!現在看雪《安卓高級研修班》線下班 & 網課(12月班)開始同步招生 啦!以前沒報上高研班的小伙伴趕快抓緊機會報名,升職加薪唾手可得!!
戳“閱讀原文 ”一起來充電吧!返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的抽取样本java实验报告_一个自定义classloader的函数抽取壳样本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 访问 网络swf_JAVA访问
- 下一篇: java swing面试题_Java面试