本文为看雪论坛优秀文章
看雪论坛作者ID:hjhjl222
由于我们的设备需要集成快牙,快牙有扫码功能,但是扫码功能出现了异常,能扫描,但是不能正确的识别二维码。大概率还是我们虚拟摄像头导致的。有过扫码开发的同学都比较清楚,扫码一般是集成zxing或者zbar的sdk(当然也有自己写的,比如淘宝)。废话不多说,直接开整。从界面入手,获取到当前界面信息。打开扫码界面,过滤Camera日志。
由于我们在摄像头在framework层给Camera传参的时候带上了界面信息,方便虚拟摄像头做特殊用途判断,故此很容易拿到当前是GroupScanQrcodeFragment。然后我们打开反编译后的快牙源码找到这个类。然后大致搜索下result关键字,看看有没有可用信息。
那么也很明显了,这里就是处理结果的地方,无论成功还是失败都会sendMessage到这个handle上进行处理。这里有处理成功的结果,那我们就找找这个message.what为R.id.decode_succeeded是在哪里发送的。
通过全局搜索也找到了这个发送的关键函数,并且看到ZbarManager.decode这个关键解码函数,由此可知这货应该是用Zbar的sdk进行扫码的,那接下来就用frida插桩探测一下。
首先分享下我加载类的方式:
• framework中的类我们直接使用
• 一般情况下,我们都用这个方法来获取classloader,这种情况适合使用默认的classloader来加载的类
对于app自定义的类,我通常通过插桩Application的onCreate函数,来拿到application实例,并调用其getClassLoader函数来拿到classloader来loadClass。当然也可以hook ClassLoader的构造函数,并且保存下来,用到的时候遍历容器来loadClass。这样加载类的成功率更高。
这种方法必须通过spawn启动,因为Application.onCreate的时机比较早,并且这样不会漏过任何一个application,比如加载特定模块时才会创建的application。
回到正题,我们hook了a类的a方法,并且hook了ZbarManager的decode方法,看看这两个方法有没有被调用,如果调用到这个字节数组是否有数据,返回结果是否为空。
以下是输出的日志:
通过日志我们可以发现ZbarMananger的decode方法被调用了,但是结果为null,然后入参的字节数组长度不为0,我们先不深究这些数据正不正确,至少是有输入,但是没输出,很明显问题出在解码这块了。那么继续分析这个decode方法。
直接就是个native函数,那就导出这个libzbar.so,用ida看看吧。
exports面板搜索decode找到这个函数,然后shift+f11导入64位Android Arm,然后修改结构体,并且对其进行一番解读后,得到下图:
直接去hook解析图像信息的函数,看看有没有走到。
找到函数偏移,然后frida hook,我这里对hookNative的地址做了简单的封装,方便代码复用。
通过日志可以看到函数已经被调用了,这是不能正常解析的情况。
接下来再试试正常的手机,并且hook 得到结果的函数,也就是zbar_symbol_get_data。
通过这个函数我们很容易找到了结果:
然后再切换回我们真正开发的设备看看。
很遗憾,没有任何输出,说明根本没走到这里,也就是说在zbar_image_first_symbol函数时的返回值不满足要求,继续hook。
得到如下日志,也就验证了我的猜想,应该是图像解析函数出来问题。
再进到zbar_image_first_symbol函数里去看看。
只是取了convert_image的字段里的某个值(注意convert_image+104的偏移),那咱就去看看这个值是在解析图像信息的哪里赋的值。
v11是通过上面的zbar_symbol_set_ref函数和v16的值换算过来的,于是hook下这个函数。
可以看到这个v16的值是1,也就确定了是从下图的代码块过来的。
再来看看zbar_symbol_set_ref内部逻辑。
因为a2 = 1所以 a2 <= 0 必然条件不满足,不会走到条件成立的代码块,所以不做任何操作,从上面的日志也验证了入参和返回值的指针没有任何变化,能正常识别的pixel也是一样。
推演分析到这我感觉我的思路可能错了,既然是已经到解析图片信息这里了,那么问题很可能出现在图片本身,而非流程。于是我又回到ZbarManager.decode函数,把参数里的字节数组保存下来,并且转化一份成jpg格式的图片看看,pixel和我们的设备到底有什么不同。
下面是将yuv420sp格式的数据转成jpg后的图片,左图是pixel,右图为我们的设备,两个相反。嗯…至少图像上除了颠倒也没啥区别。
这个是经过zbar算法处理后的图片,那我们在把相机抛上来的原始数据再保存成图片看看,找到如下源码。
hook一下,并且保存bArr吧。
得到如下两张图,左图pixel,右图我们的设备。
原图和zbar处理过的图片对比好像做了旋转90度,然后去掉一个通道色值数据。好像也没别的太大区别。上面我们说倒除了保存图片以外,还保存了一份decode的bArr原始数据。既然没有很好的办法,那我们就抛开设备因素,拿着手机和开发设备的数据,通过unidbg主动解析这个函数,看看结果咋样。在通过unidbg调用前,我们必须拿到这个函数的其他参数。
pixel:
开发设备:
然后把两份decode前的数据文件、apk,so等拷贝到unidbg工程里。
这里我对unidbg做了模块化封装,所以代码看起来比较简洁,并且能支持Spring,服务器部署。
运行的结果和在设备中一样,pixel的能识别出结果,而我们的开发设备结果null。
所以也就排除了设备本身环境的影响。那么结合上面本身图片数据完整,但是旋转了180度,所以猜测很可能就是识别区域导致的,重新再省视下byteArray后面的那几个参数。1080,1920肯定是图片的宽高,而后面这几个225、247、630、743参数应该就是识别区域相关的。于是去找源码。
这似乎就是我们之前没注意过的细节。
再结合这张图片(左pixel、右开发设备)和这几个数据,(pixel:225、247、630、743;开发设备:300、166、480、500)
妥妥的开发设备是从x=300、y=160的位置开始识别的,这个位置哪有二维码的数据。于是我估算了一组起始识别的坐标x=220,y=1200,也就是如下图运行。
运行结果:
果然都正常识别了。
总结一下:因为我们的虚拟摄像头给的预览数据,经zbar计算后旋转了180度导致原先的识别区域没有二维码信息,导致了识别失败,所以只需要让开发人员适配下快牙扫码时预览数据在原先旋转90度的基础上再旋转180度,到270度(和pixel一样),就能通过扫码了。
看雪ID:hjhjl222
https://bbs.pediy.com/user-home-926631.htm
# 往期推荐
2.Android4.4和8.0 DexClassLoader加载流程分析之寻找脱壳点
3.实战DLL注入
球分享
球点赞
球在看
点击“阅读原文”,了解更多!