unidbg逆向工程:原理与实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.2 使用unidbg修补执行环境并模拟执行

首先将编写的unidbg代码MainActivity.java复制到同包名目录下,改名为MainJni.java,我们将在此文件中编写响应代码。同时,将上述Android项目编译好的APK解压,取得armeabi-v7a下的so文件,复制到同目录中,并重命名为libjni.so。

接下来简单修改一下源代码,如下所示:

这里仅仅是修改了加载so的名称,模拟执行函数的函数名、函数签名以及传入的参数。

修改完后尝试运行,收到如图3-1所示的报错消息。

图3-1 寻找md52()方法失败报错

这是因为我们修改了类文件的文件名,而上述代码传入了该类实例来当作执行so中函数的java对象,导致unidbg根据传入的类实例处理后的函数名无法找到需要调用的Java_com_dta_lesson2_MainActivity_md52()方法。

可以找到2.2.3节中API的内部实现,下断点进行调试,查看处理后得到的最终的函数名,如图3-2所示。

图3-2 调试查看symbolName

由于我们修改了执行类的类名,导致传入该实例后,拼接后的函数名为Java_com_dta_lesson2_MainJni_md52,这与我们想要调用的函数名是不相符的,自然找不到对应的函数。

所以需要将上述代码修改为如下代码:

DvmObject obj=vm.resolveClass("com/dta/lesson2/MainActivity").newObject(null);

首先使用vm.resolveClass()方法,通过传入一个类名字符串的方式来构建一个class,然后使用newObject()方法来实例化对象,这样unidbg便可以通过该对象来获得正确的函数名。

再次运行,仍然报错,如图3-3所示。

图3-3 未设置setJni()方法报错

由于模拟执行的函数中有JNI操作,而不同的so调用的JNI方法有所不同,unidbg无法为其一一实现,只实现了部分常用的JNI接口,缺失的部分则需要读者自己来实现。因此这里提示我们需要设置vm.setJni()来实现缺失的JNI接口部分。

具体做法是:我们需要调用setJni()方法,让MainJni继承AbstractJni类,并将MainJni类实例当作参数传入setJni()方法中。

当我们进入AbstractJni类时,可以发现unidbg已经实现了常用的JNI操作,如图3-4所示。

图3-4 AbstractJni相关实现

修改代码,如图3-5所示。

图3-5 修改代码

修改后运行,发现还是报错,如图3-6所示。

图3-6 没有MessageDigest.update()实现

虽然我们继承了AbstractJni类,并为VM虚拟机设置了JNI接口,但由于AbstractJni并没有相应MessageDigest.update()的实现,所以报错。MessageDigest.update()是代码中调用的第二个JNI接口,第一个调用JNI接口的方法为MessageDigest.getInstance(),为什么它没有报错呢?

我们尝试在AbstractJni中搜索MessageDigest->getInstance,发现unidbg已经对其做了实现,如图3-7所示。

而unidbg对于update()方法则没有做实现,因此会报java.lang.UnsupportedOperationException错误,如图3-8所示。

我们需要重写报错的callVoidMethodV()方法,为unidbg补全缺失的update()方法的具体实现,使之可以继续执行。这个补齐JNI的过程叫作“补环境”。

在MainJni中编写以下代码:

图3-7 AbstractJni实现MessageDigest->getInstance

图3-8 缺失update()方法报错

public void callVoidMethodV(BaseVM vm,DvmObject<?>dvmObject,String

signature,VaList vaList){

if(signature.equals("java/security/MessageDigest->update([B)V")){

MessageDigest messageDigest=(MessageDigest)dvmObject.getValue();

int intArg=vaList.getIntArg(0);

Object object=vm.getObject(intArg).getValue();

messageDigest.update((byte[])object);

return;

}

super.callVoidMethodV(vm,dvmObject,signature,vaList);

}

当执行到该方法时,参数vm为虚拟机,dvmObject为调用该函数的对象,signature为函数的签名,vaList为函数的参数列表。

在调用callVoidMethodV函数前先判断函数的签名是否与update()方法相匹配,该处填写的签名为报错信息给出的签名,如果匹配则执行补齐的自实现代码。

在执行messageDigest.update()方法时,我们首先要取得MessageDigest对象,如我们已知的≮dvmObject就是调用方法的对象,因此可以使用dvmObject.getValue()方法来获得MessageDigest对象,由于我们已经知道它的类型,所以可以将它强制类型转换为MessageDigest类型。

接下来便是取参数了。通过vaList.getIntArg()方法,传入下标,我们便能取得相应的参数。需要注意的是,由于update()方法的参数类型为byte[],而在JNI中没有对象的概念,因此unidbg为非基本类型维护了一个Map引用,如图3-9所示。通过getIntArg()方法取得的只是Map中的key值,还需要通过vm.getObject()方法从VM虚拟机中取得该对象,再通过.getValue()方法取得实际对象的值。

图3-9 unidbg维护的Map引用

之后通过调用messageDigest.update()方法,完成对JNI环境的修补。

修补完update()方法后继续运行,收到缺失digest()方法的报错提示,如图3-10所示。

图3-10 缺失digest()方法报错

根据报错的调用栈,重写callObjectMethodV()方法,代码如下:

public DvmObject<?>callObjectMethodV(BaseVM vm,DvmObject<?>dvmObject,

String signature,VaList vaList){

if(signature.equals("java/security/MessageDigest->digest()[B")){

MessageDigest messageDigest=(MessageDigest)dvmObject.getValue();

byte[]digest=messageDigest.digest();

DvmObject<?>object=ProxyDvmObject.createObject(vm,digest);

vm.addLocalObject(object);

return object;

}

return super.callObjectMethodV(vm,dvmObject,signature,vaList);

}

步骤与之前大致相同,只是多了一个将运算得到的结果digest先代理创建为DvmObject对象,再添加到VM虚拟机的操作。所有的非基本类型、包装类型都需要添加到VM虚拟机的Map映射中,否则在JNI中无法找到该引用。最后将运算得到的结果当作函数返回值返回。

再次运行,继续报错,提示找不到自编写的byte2Hex()方法,如图3-11所示。继续根据报错信息来补全JNI环境。

图3-11 缺失byte2Hex()方法报错

补全后的byte2Hex()方法代码如图3-12所示,这里只是直接调用了编写好的byte2Hex()方法,并将结果转换为StringObject对象并添加到VM虚拟机中。

图3-12 补全后的byte2Hex()方法代码

再次运行,发现成功得到正确的结果,如图3-13所示。

图3-13 成功模拟执行JNI函数

虽然修补环境的工作较为烦琐,但相较于复杂的so中的算法流程而言,使用unidbg的好处不言而喻。