其他
一个自定义classloader的函数抽取壳样本
本文为看雪论坛文章
看雪论坛作者ID:lemn
本文为看雪安卓高研2w班(7月班)优秀学员作品。
下面先让我们来看看学员的学习心得吧!
解题过程
粗略分析一下就是:1. 拿到dexfile 就可以拿到base size2. 拿到artmethod 就可以拿到codeitem offset和method idx3. 计算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
const DexHeader *base = dex_file.pHeader;
size_t size = dex_file.pHeader->fileSize;
获取code item offset和method idx
uint32_t codeItemOffset = artmethod->dex_code_item_offset_;
uint32_t idx = artmethod->dex_method_index_;
hook prettymethod方法 主动调用获得方法名
const std::string &string = prettyMethodFunction(artmethod, artmethod,
true);
通过偏移可以拿到codeitem
long codeItemAddr = (long) base + codeItemOffset;
CodeItem *codeItem = (CodeItem *) codeItemAddr;
这部分代码可以直接dump dex出来
int pid = getpid();
char dexFilePath[100] = {0};
sprintf(dexFilePath, "/sdcard/xxxxx/%p %d LoadMethod.dex", base, size);
mkdir("/sdcard/xxxxx", 0777);
int fd = open(dexFilePath, O_CREAT | O_RDWR, 666);
if (fd > 0) {
ssize_t i = write(fd, base, size);
if (i > 0) {
close(fd);
}
}
...
上述看起来一步一步的确是可以拿得到codeitem,然后进一步拿得到ins的。
但是有一个问题我一直没法解决,就是某些方法会报access violation 异常。我通过搜索发现这个是底层发出来的异常,软件层貌似没法catch。 通过使用frida-fart我发现frida版本的也有这样的问题,但是frida这边可以通过try catch捕捉,让程序继续行走。
而根据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, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
final ClassLoader classLoader = (ClassLoader) param.thisObject;
XcustomBridge.log("DexClassLoader:" + classLoader.toString());
mClassLoaders.add(classLoader);
}
});
XcustomBridge.hookAllConstructors(PathClassLoader.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
final ClassLoader classLoader = (ClassLoader) param.thisObject;
XcustomBridge.log("PathClassLoader:" + classLoader.toString());
mClassLoaders.add(classLoader);
}
});
紧接着加载我们的so,并遍历所有的classloader,执行loadClass操作:
XcustomHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XcustomBridge.log("attach after");
mContext = (Context) param.args[0];
XcustomHelpers.callMethod(Runtime.getRuntime(), "doLoad", "/system/lib/libnative-lib.so", mContext.getClassLoader());
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
Log.e("hook1", Log.getStackTraceString(e));
}
for (int i = 0; i < mClassLoaders.size(); i++) {
ClassLoader classLoader = mClassLoaders.get(i);
TestClassloader(classLoader);
}
fart();
}
}).start();
}
});
将dump下来后bin文件批量恢复后,即可查看到,函数已经恢复了。
然后通过回溯error log可以发现是classloader的锅:
/**
* 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: java.lang.IllegalArgumentException: Expected receiver of type dalvik.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。
那么来看看通常我们是如何获取classlist的:
Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
Object pathList_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 (Error e) {
e.printStackTrace();
}
......
我们是通过获得BaseDexClassLoader的pathList字段从而进一步往下获取classList的。但是由于我们现在是直接继承于classloader,所以我们要想办法获取classlist。 那么这里就有两种方法可以实现了。
第一个方法,fart使用过程中使dump classlist出来:
if (appClassloader instanceof BaseDexClassLoader) {
} else if (appClassloader instanceof ClassLoader) {
List<String> nameList = new ArrayList<>();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(Environment.getExternalStorageDirectory() + "/8958236_classlist.txt")),
"UTF-8"));
String lineTxt = null;
while ((lineTxt = br.readLine()) != null) {
if (!TextUtils.isEmpty(lineTxt)) {
String name = 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 (String name : nameList) {
try {
Log.e("Hook1", "===================>loadClass:" + name);
appClassloader.loadClass(name);
} catch (Exception e) {
Log.e("Hook1", Log.getStackTraceString(e));
}
}
return;
}
因此这里我们直接遍历文件,然后loadClass即可。 接下来我们对dex方法体进行批量恢复后,可以看到函数都恢复了。
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);
int i;
for (i = 0; i < classDefSize; i++) {
long currClassAddr = (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);
int tmpIdx = *idx;
long currTypeIdAddr = ((long) typeIdsOff + 4 * tmpIdx + (long) base);
int *currTypeIdx = (int *) currTypeIdAddr;
__android_log_print(5, "hookso", "currTypeIdx:%ld", *currTypeIdx);
tmpIdx = *currTypeIdx;
long currStringOffAddr = ((long) stringIdsOff + 4 * tmpIdx + (long) base);
int *currStringOff = (int *) currStringOffAddr;
__android_log_print(5, "hookso", "currStringOff:%ld", *currStringOff);
tmpIdx = *currStringOff;
long off = (long) base + tmpIdx;
__android_log_print(5, "hookso", "string off:%p", (void *) off);
const uint8_t *strPtr = (uint8_t *) off;
DecodeUnsignedLeb128(&strPtr);
char *classname = (char *) strPtr;
__android_log_print(5, "hookso", "classname:%s", classname);
}
}
在这里我们可以一层一层的不断获取不断遍历,就可以拿到我们想要的classname:
看雪ID:lemn
https://bbs.pediy.com/user-home-777497.htm
*本文由看雪论坛 lemn 原创,转载请注明来自看雪社区。
好消息!!现在看雪《安卓高级研修班》线下班 & 网课(12月班)开始同步招生啦!以前没报上高研班的小伙伴赶快抓紧机会报名,升职加薪唾手可得!!
推荐文章++++
* Linux Kernel Pwn_2_Kernel UAF
公众号ID:ikanxue
官方微博:看雪安全商务合作:wsc@kanxue.com
求分享
求点赞
求在看