补库函数(五)
一、引言
本篇记录内存相关的库函数、系统调用处理。
二、Free
free函数是重要的内存释放函数,原型如下。
void free(void *ptr)
Unidbg在内存管理方面存在一些问题,在内存的释放上尤为明显,munmap以及free都有不低的出错概率。先讨论free,常见报错如下。
Invalid address 0x40175000 passed to free: value not allocated
[crash]A/libc: Invalid address 0x40175000 passed to free: value not allocated
遇到这个报错,最简单的处理方法就是hook free函数,替换它的实现,让它什么都不做就直接返回。这里我用Whale来实现,让它直接返回,且返回值是0,即释放成功之意。
public void patchFree(){
IWhale whale = Whale.getInstance(emulator);
Symbol free = emulator.getMemory().findModule("libc.so").findSymbolByName("free");
whale.inlineHookFunction(free, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("WInlineHookFunction free=" + emulator.getContext().getPointerArg(0));
return HookStatus.LR(emulator, 0);
}
});
}
你可以使用xHook、HookZz或者其他感兴趣的Unidbg内置Hook方案。你可能会觉得这么做有些浪费,很多应该释放的内存没有得到释放,那么也可以根据报错,对报错的待释放内存做处理,比如只有指针地址是0x40175000、0x40176000 的两处释放失败时,代码如下。
public void patchFree(){
IWhale whale = Whale.getInstance(emulator);
Symbol free = emulator.getMemory().findModule("libc.so").findSymbolByName("free");
whale.inlineHookFunction(free, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("WInlineHookFunction free=" + emulator.getContext().getPointerArg(0));
long addr = emulator.getContext().getPointerArg(0).peer;
if(addr == 0x40175000 | addr == 0x40176000){
return HookStatus.LR(emulator, 0);
}else {
return HookStatus.RET(emulator, originFunction);
}
}
});
}
三、munmap
简单谈谈munmap,它的实现位于src/main/java/com/github/unidbg/spi/AbstractLoader.java。它出现的频率太高了,这里有个样本进行展示。
样本:wanggetong.apk(附件详见参考)。Unidbg示例代码如下
package com.munmap;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class MUNMAP extends AbstractJni {
private final AndroidEmulator emulator;
private final DvmObject BlackBox;
private final VM vm;
public MUNMAP() {
emulator = AndroidEmulatorBuilder
.for32Bit()
.addBackendFactory(new Unicorn2Factory(true))
.build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("D:\\Compilation_Enviroment\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\munmap\\wanggetong.apk"));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary("msec", true);
BlackBox = vm.resolveClass("com.autohome.mainlib.common.util.BlackBox").newObject(null);
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
MUNMAP wgt = new MUNMAP();
}
}
运行报错
报错中的0xffffffff是什么,是Unidbg不支持样本所采用的JNI版本?不是。
让我们回忆一下JNI_OnLoad的开发习惯,比如下面这样。
//注册函数映射
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *pEnv = NULL;
//获取环境
jint ret = vm->GetEnv((void**) &pEnv, JNI_VERSION_1_6);
if (ret != JNI_OK) {
LOGE("jni_replace JVM ERROR:GetEnv");
return -1;
}
//在{}里面进行方法映射编写,第一个是java端方法名,第二个是方法签名,第三个是c语言形式签名(括号内表示方法返回值)
JNINativeMethod g_Methods[] = {{"jniOnLoadTest", "()V", (void*) onLoadTest},
{"jniOnload1", "(Lzqc/com/example/Person;)Ljava/lang/String;", (jstring*)onloadTest1}
};
jclass cls = pEnv->FindClass("zqc/com/example/NativeTest");
if (cls == NULL) {
LOGE("FindClass Error");
return -1;
}
//动态注册本地方法
ret = pEnv->RegisterNatives(cls, g_Methods,sizeof(g_Methods) / sizeof(g_Methods[0]));
if (ret != JNI_OK) {
LOGE("Register Error");
return -1;
}
//返回版本
return JNI_VERSION_1_6;
}
JNI_OnLoad的返回值必须是JNI的版本信息,目前支持1.4、1.6、1.8三个版本,当返回三者之一时表示一切正常,如果像表示JNI_OnLoad处理出错的意思,只需要返回其他值即可,按照惯例一般返回-1,也就是十六进制的0xffffffff,比如上面贴的demo代码就是这样。
因为这里Unidbg的报错,并不是说样本真采用了什么其他的JNI版本,而只是表明,样本在早先的JNI_OnLoad处理里遇到了问题,直接走到了return -1。
所以解决它的问题就是日志往上翻,找到最早报错在哪里
可以看到是munmap报错,点进去看看,发现是下图这样一个位置
if (segment == null || segment.size < aligned) {
throw new IllegalStateException("munmap aligned=0x" + Long.toHexString(aligned) + ", start=0x" + Long.toHexString(start));
}
表面是munmap的处理有问题,本质上依然是Unidbg在内存管理、模块加载相关的处理逻辑上不健全,所以这里主动抛出了一个异常。
如果SO加固,比如加个UPX壳,那么往往就会遇到这个报错,如果你只想绕过这个问题,那么只需要注释掉这个throw即可,如果你想彻底解决Unidbg在这个逻辑下的问题,那么需要对比Unidbg ElfLoader逻辑和Linker的差异,找到这个问题的根源。
我这里简单注释一下
然后运行就进入正常的补JNI逻辑。
感兴趣的读者可以自行尝试。
mmap和munmap相关的问题,在Unidbg中很容易出现,而且往往都是发生在加固SO的解密和内存释放阶段,这里要阐述两点。
一是这说明Unidbg在内存加载方面的处理逻辑做不到位,有漏洞。
二是不能因此认为**“Unidbg不能处理加固的SO”**,只不过是SO加固和解密时会更多的使用到系统层的各种功能和特性,而Unidbg对它们的支持度不那么高,相对来说更容易出问题罢了。
参考
[补库函数(五)](https://www.yuque.com/lilac-2hqvv/xdwlsg/bzoykwvuim3hkz2o?# 《补库函数(五)》)
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1621925986@qq.com