java导致native非法指令,Java代码引起的NATIVE野指针问题(上)

20170624712_7309.jpg

朴英敏,小米MIUI部门。从事嵌入式开发和调试工作8年多,擅长逆向分析方法,主要负责解决安卓系统稳定性问题。

上周音乐组同事反馈了一个必现Native Crash问题,tombstone如下: pid: 5028, tid: 5028, name: com.miui.player  >>> com.miui.player <<     #01  pc 0002302f  /system/lib/libhwui.so (android::uirenderer::OpenGLRenderer::callDrawGLFunction(android::Functor*, android::uirenderer::Rect&)+322)     #02  pc 00015d91  /system/lib/libhwui.so (android::uirenderer::DrawFunctorOp::applyDraw(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&)+28)     #03  pc 00014527  /system/lib/libhwui.so (android::uirenderer::DrawBatch::replay(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&, int)+74)     #04  pc 00014413  /system/lib/libhwui.so (android::uirenderer::DeferredDisplayList::flush(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&)+218)     #05  pc 0001d1cf  /system/lib/libhwui.so (_ZN7android10uirenderer14OpenGLRenderer15drawDisplayListEPNS0_11DisplayListERNS0_4RectEi.part.47+230)     #06  pc 0006820d  /system/lib/libandroid_runtime.so

崩溃的原因是pc指向了一个没有可执行权限的内存地址上。

初步分析:

对应的代码如下: status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {  if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;  detachFunctor(functor);  ...  interrupt();  => status_t result = (*functor)(DrawGlInfo::kModeDraw, &info);

其中,Functor类重载了()操作符: class Functor {  public:  Functor() {}  virtual ~Functor() {}  => virtual status_t operator ()(int /*what*/, void* /*data*/) { return NO_ERROR; }  };

因此,()操作其实就是调用了Functor类的一个虚函数,它的具体实现目前还不清楚。

对应的汇编代码如下: 23028: aa0b add r2, sp, #44  2302a: 6803 ldr r3, [r0, #0] ; r0是functor,r3 = [r0] = functor.vtlb  2302c: 689d ldr r5, [r3, #8] ; r5 = [r3 + 8] = [functor.vtlb + 8] = Functor.operator()  2302e: 47a8 blx r5 ; call Functor.operator()

崩溃时的寄存器值如下: r0 7ac59c98 r1 00000000 r2 bea7b174 r3 400fc1b8  r4 774c4c88 r5 79801f28 r6 bea7b478 r7 40c12bb8  r8 7c1b68e8 r9 778781e8 sl bea7b478 fp bea7b414  ip 00000001 sp bea7b148 lr 40c07031 pc 79801f28 cpsr 600f0010

可以看到,r5和pc值是相等的,可以知道,确定是崩溃在2302e这一行汇编代码中。

而查看寄存器对应的内存值,发现有点问题: memory near r0:     7ac59c78 00000018 0000001b 735a9b38 23831ef0       7ac59c88 23831ef0 735a9b50 00000018 00000011       7ac59c98 79822328 77768698 00000010 00000022       7ac59ca8 00000000 00000000 00000000 00000003    memory near r3:     400fc198 7c74c000 00200000 00000077 0d44acd8       400fc1a8 00000000 00000000 400fc1a8 400fc1a8       400fc1b8 400fc1b0 400fc1b0 7c04acb8 7c78f008       400fc1c8 7c021d98 7c78ffc0 7983bbf0 7c04bfa8

[r0] = [7ac59c98] = 798223298,这个和r3值(400fc1b8)不一样,

同样

[r3+8] = [400fc1b8 + 8] = 7c04acb8,这个值也和r5值(79801f28)不一样。

这在平时的tombstone里是非常少见的!

乍一看非常不可思议,但仔细想想tombstone的生成过程,就能发现其中的问题。

原来寄存器信息是错位崩溃时的cpu context,保存在崩溃时的线程私有的信号栈和内核栈中,直到debuggerd去获取这个值,它是不会被修改的。

而内存是进程中的各个线程共享的,所以在发生异常到debuggerd打印内存信息这段过程中(其实是相对很长的一个过程),别的线程是有可能修改内存值的。

为了证明别的线程在改这个内存值,在callDrawGLFunction()函数中的若干处打印了Functor和它的vtbl(虚函数表地址)值: status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {  AOGI("functor=%p,vtbl=%p");  sleep(1);  if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;  AOGI("functor=%p,vtbl=%p");  sleep(1);  detachFunctor(functor);  ...  AOGI("functor=%p,vtbl=%p");  sleep(1);  interrupt();  AOGI("functor=%p,vtbl=%p");  sleep(1);  status_t result = (*functor)(DrawGlInfo::kModeDraw, &info);

抓到的log如下: 10-27 21:19:45.794 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0  10-27 21:19:47.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0  10-27 21:19:48.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0  10-27 21:19:49.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0  10-27 21:19:50.804 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0  10-27 21:19:51.804 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x400fc1b8

可以确定确实有别的线程在修改这个值。

这里就存在两个可能性了:

1、别的线程也持有functor指针,并修改内容

2、functor是野指针,对应的内存已经还回系统,其他模块可任意使用。

而对象的vtbl一般是不会修改的,所以2的可能性更大一些。

为了查明是哪个线程在改,对functor指向的内存做了写保护操作: static int** s_saved_vtbl = NULL; static void* s_saved_functor = NULL;  static void  mprotect_local(int** p) {     // 一旦发现vtbl有变化就将对应内存设置为只读     if(p != s_saved_vtbl) {          mprotect((void*)((unsigned int)s_saved_functor&0xfffff000), 4096, PROT_READ);     }     sleep(1); }  status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {     int* ptr = (int*)functor;     s_saved_functor = (void*)ptr;     s_saved_vtbl = (int**)*ptr;      if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;       mprotect_local((int**)*ptr);     detachFunctor(functor);     mprotect_local((int**)*ptr);     ...     mprotect_local((int**)*ptr);     interrupt();       status_t result = (*functor)(DrawGlInfo::kModeDraw, &info);

push到手机中复现问题,很容易抓到访问权限引起的crash。

而每次的crash的线程和位置都不一样,也就是不同的线程在不同的函数中读写这个地址。

这样基本上就确定是野指针问题,进入下一阶段的分析。

关于野指针:

所谓野指针就是一个对象被释放后又被使用,可能是释放的问题,也可能是使用的问题。

我们已经知道使用的位置,接下来要找出是从哪释放的。

找到释放对象的最笨的方法,是在free()函数里打印调用栈。

但这么做有两个问题:

1、log太量多,一秒内可能会有成千上万的malloc/free函数被调用。

2、打印调用栈的函数本身会调用free函数,这样会陷入死循环。

为了解决上面两个问题,需要用到hook技术。

关于hook技术:

要了解hook技术,得先了解外部函数的调用过程。

所谓外部函数就是外部模块中定义的函数。比如,libhwui.so中的某个源文件中调用了malloc函数,而这个malloc函数是libc.so中定义的。

当编译libhwui.so的这个源文件时,对应调用malloc的地方会生成如下的汇编代码: blx addr

这里blx是arm的跳转指令,addr是目标地址,也就是malloc函数的地址,那这个malloc函数的地址如何确定?

这个编译的阶段是无法确定的,只有当运行时进程加载完libc.so以后,malloc函数的地址才能被确定。

所以编译器在编译的时候会在libbinder.so中留出一部分空间作为地址表,专门用于存放外部函数的地址,这个区域叫got表。

每一个本模块调用到的外部函数都对应got表中的一项。

当然got表里面的内容是在进程启动阶段,加载动态库时被连接器linker填充的。

而编译阶段我们只需要将代码写成:

1、从got表对应位置获取外部函数地址

2、跳转到这个外部函数的地址

这个动作需要由若干的指令来完成,所以跳转指令blx addr中的addr其实指向本模块的一组指令: blx cb74 

这组指令所在的区域就是elf文件结构里的plt表,plt表中每一个外部函数都对应一个表项,如:

0000cb74 :

cb74: e28fc600 add ip, pc, #0, 12

cb78: e28cca29 add ip, ip, #167936 ;

cb7c: e5bcf1e8 ldr pc, [ip, #488]! ;

0000c8bc :

c8bc: e28fc600 add ip, pc, #0, 12

c8c0: e28cca29 add ip, ip, #167936 ;

c8c4: e5bcf3b8 ldr pc, [ip, #952]! ;

每一个plt表项都是做相同操作:

1、先获取got表中外目标函数对应的地址(前两行);

2、从got表中获取地址目标函数的地址,并赋给pc寄存器(第三行)。

下面给出got表和plt表在so文件中的位置:

readelf -S libhwui.so

[Nr] Name Type Addr Off Size ES Flg Lk Inf Al

[ 0] NULL 00000000 000000 000000 00 0 0 0

[ 1] .interp PROGBITS 00000134 000134 000013 00 A 0 0 1

[ 2] .dynsym DYNSYM 00000148 000148 002420 10 A 3 1 4

[ 3] .dynstr STRTAB 00002568 002568 0056a4 00 A 0 0 1

[ 4] .hash HASH 00007c0c 007c0c 001134 04 A 2 0 4

[ 5] .rel.dyn REL 00008d40 008d40 002bc8 08 A 2 0 4

[ 6] .rel.plt REL 0000b908 00b908 000a78 08 A 2 7 4

=>[ 7] .plt PROGBITS 0000c380 00c380 000fc8 00 AX 0 0 4

[ 8] .text PROGBITS 0000d348 00d348 01ef30 00 AX 0 0 8

[ 9] .ARM.exidx ARM_EXIDX 0002c278 02c278 001fb8 08 AL 8 0 4

[10] .ARM.extab PROGBITS 0002e230 02e230 000930 00 A 0 0 4

[11] .rodata PROGBITS 0002eb60 02eb60 0036a4 00 A 0 0 4

[12] .fini_array FINI_ARRAY 00034010 033010 000004 00 WA 0 0 4

[13] .data.rel.ro PROGBITS 00034018 033018 001910 00 WA 0 0 8

[14] .init_array INIT_ARRAY 00035928 034928 00000c 00 WA 0 0 4

[15] .dynamic DYNAMIC 00035934 034934 000140 08 WA 3 0 4

=>[16] .got PROGBITS 00035a74 034a74 00058c 00 WA 0 0 4

[17] .data PROGBITS 00036000 035000 00025c 00 WA 0 0 4

[18] .bss NOBITS 0003625c 03525c 000068 00 WA 0 0 4

[19] .comment PROGBITS 00000000 03525c 000010 01 MS 0 0 1

[20] .note.gnu.gold-ve NOTE 00000000 03526c 00001c 00 0 0 4

[21] .ARM.attributes ARM_ATTRIBUTES 00000000 035288 00003e 00 0 0 1

[22] .gnu_debuglink PROGBITS 00000000 0352c6 000010 00 0 0 1

[23] .shstrtab STRTAB 00000000 0352d6 0000dc 00 0 0 1

我们的hook技术就是通过修改so的got表来截获so中的某些外部函数调用。

so的代码段是多个进程共享的,但它的数据段私有的,而got表就是数据段。

所以我们只修改music应用进程的libhwui.so的got表中free函数对应的项,影响范围将大大减少。

那改成什么值呢?一般是我们自己定义的函数,比如: void inject_free(void *ptr)   {     ALOGI("free ptr=%p",ptr);     dumpNativeStack();     dumpJavaStack();     free(ptr); }

为了不影响原来的逻辑,打印完debug信息,还是要调用原来被hook的函数。

有了hook技术后能完美的解决野指针中的两个问题,下面继续分析问题。

点赞 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500866.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

axure怎么做5秒倒计时_罗胖60秒:怎么做一个课程?

今天是罗胖陪伴你的第 2714 天1. 我们在研发课程的时候&#xff0c;有一个很关键的方法&#xff0c;我们内部称之为叫“不是而是”大法。2. 什么意思呢&#xff1f;就是如果你只告诉别人它是什么&#xff0c;那等于啥都没说。比如&#xff0c;我告诉你要经营好一家小店&#xf…

树莓派跑php,在树莓派4上部署nginx+php

本试验是基于树莓派的 buster 版本。一、安装 nginxapt install nginx -y完成安装之后&#xff0c;可以使用 dpkg -l | grep nginx 找到相关的安装包&#xff1a;通过分析 /var/lib/dpkg/status 可以得到 nginx 安装包的依赖关系&#xff1a;nginx --> nginx-full --> li…

php 如何生成微信小程序,微信小程序一键生成平台系统/一键生成小程序开源小程序PHP|ThinkPHP平台源码下载...

源码名称&#xff1a;微信小程序一键生成平台系统/一键生成小程序开源小程序PHP|ThinkPHP平台源码下载购买须知&#xff1a;(请仔细阅读了解)【1】源码一律经测试无误。【2】不提供任何修改和编辑服务&#xff0c;不包安装调试&#xff0c;假如你什么都不懂的请不要拍。【3】假…

java .item,javabb-javaitem-cloud

JavaItem-Cloud项目介绍JavaItem-Cloud是一个新开发的微服务架构平台&#xff0c;基于最新流行的技术SpringBoot、SpringCloud & SpringCloud Alibaba、Vue、Vuex、ElementUI。系统特性后端使用当前流行技术&#xff0c;SpringBoot、SpringCloud、SpringCloud Alibaba、Myb…

软件使用手册模板_我的印象笔记使用手册(精简说明)

在之前的一文中&#xff0c;我已经详细写过我的印象笔记使用的方法了&#xff0c;这次呢&#xff0c;再做一个更新的使用情况和更清楚明了和精简的说明。不居竹&#xff1a;我的印象笔记使用手册​zhuanlan.zhihu.com文章目录&#xff1a;1、知识管理收集知识整理知识输出知识2…

通信之道从微积分到5gpdf_保送清华成博士,华为12年搞通信,他为何如此看待 5G ?| 人物志...

作者 | 伍杏玲 胡巍巍出品 | CSDN(ID&#xff1a;CSDNnews)一位通信专家&#xff0c;清华读了博士&#xff0c;北大从事博士后研究工作&#xff0c;但却只有12年工作经历&#xff1f;明明家里几套房&#xff0c;却热爱骑车出行&#xff1f;他是&#xff1f;他是国内小有名气的通…

mysql封装 javabean,利用Java针对MySql封装的jdbc框架类JdbcUtils完整实现(包含增删改查、JavaBean反射原理,附源码)...

最近看老罗的视频&#xff0c;跟着完成了利用Java操作MySql数据库的一个框架类JdbcUtils.java,完成对数据库的增删改查。其中查询这块&#xff0c;包括普通的查询和利用反射完成的查询&#xff0c;主要包括以下几个函数接口:1、public Connection getConnection() 获得数据库的…

云计算的发展趋势_2020年的云计算发展趋势预测

点击上方“蓝色字体”&#xff0c;选择 “设为星标”关键讯息&#xff0c;D1时间送达&#xff01;SnapLogic公司首席技术官Craig Stewart表示&#xff0c;企业需要为不同的目的使用多个云平台&#xff0c;这将意味着多云应用的普及率将上升。在新的一年即将到来之际&#xff0c…

php x86什么意思,win10x86是什么意思

小编之前也是不知道x86是什么意思的&#xff0c;后来我的一个朋友给我好好的解释了一通我才明白。所以既然小编花了点时间才那个问题弄明白的&#xff0c;那接下来小编就来好好给你们说说x86是什么意思。小编最近发现有小伙伴在问x86是什么意思之类的问题&#xff0c;于是小编借…

php设置路径别名,react设置文件路径别名的具体方法你知道么

文章环境&#xff1a;“react”: “^16.13.1” 版本react官方脚手架默认是将webpack配置隐藏起来了&#xff0c;在进行配置之前需要将webpack给暴露出来。1、输入命令 npm run eject会出现一个命令提示&#xff1a;这是一个单向操作&#xff0c;确认操作后不可逆转/返回?输入 …

redis aof 备份和恢复_Redis 持久化机制的介绍,了解这些流程很重要

我们已经知道对于一个企业级的redis架构来说&#xff0c;持久化是不可减少的。企业级redis集群架构&#xff1a;海量数据、高并发、高可用持久化主要是做灾难恢复&#xff0c;数据恢复&#xff0c;也可以归类到高可用的一个环节里面去&#xff0c;比如你redis整个挂了&#xff…

最近公共祖先_leetcode No.236 二叉树的最近公共祖先

承接二叉搜索树的最近公共祖先。题目链接&#xff1a;二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09;​leetcode-cn.com题目描述&#xff1a;给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T…

oracle连续周数,详细讲解Oracle数据库的“周数计算”

详细讲解Oracle数据库的“周数计算”以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;——日期计算 算第n周的第一天及最后一天是几号 by keynes—— ww的算法为每年 月 日为第一周开始 date …

php 命名空间通俗易懂_PHP进阶由浅入深掌握面向对象开发

视频教程出自黑马程序员PHP基础_由浅入深掌握面向对象开发【讲解方式】系统梳理和分解知识&#xff0c;各个点逐步讲解&#xff0c;由浅入深&#xff0c;通俗易懂&#xff0c;层层深入【课程亮点】1&#xff0c;已经完成PHP基础知识学习的朋友有帮助2&#xff0c;生动形象&…

linux直接用iso文件装服务,linux系统安装iso文件方法

摘要&#xff1a;linux系统下怎么安装iso文件&#xff1f;安装步骤&#xff1a;1、在&#xff0f;mnt目录下&#xff0c;创建相应的iso文件夹&#xff0c;例如iso。2、运行以下命令&#xff0c;挂载iso文...安装步骤&#xff1a;1、在&#xff0f;mnt目录下&#xff0c;创建相应…

深度linux支持安卓,深度操作系统 V20(1003)内测版招募:新增手机助手,支持安卓和 iOS 端,管理手机应用、文件...

IT之家9月28日消息 深度操作系统今天发布深度操作系统 20(1003)内测活动招募说明&#xff0c;IT之家获悉&#xff0c;本次操作系统将新增手机助手&#xff0c;支持安卓和iOS端&#xff0c;方便管理手机应用、文件等。更新说明&#xff1a;1、新增手机助手&#xff0c;支持安卓和…

选择排序(java)

选择排序 选择排序是默认前面都是已经排序好的&#xff0c;然后从后面 选择最小的放在前面排序好的的后面&#xff0c;首先第一轮循环的时候默认的排序好的为空&#xff0c;然后从后面选择最小的放到数组的第一个位置&#xff0c;第二轮循环的时候默认第个元素是已经 排序好的…

asp:dropdownlist如何去掉三角箭头_科目二倒库打轮早会压库角,教练9图详解如何快速调车避免压线?...

大家都知道&#xff0c;科目二考试五项中&#xff0c;最难的项目就是倒车入库。史教练在给科二学员指导时&#xff0c;总是强调&#xff0c;练车时不要只记死点。科目二考试过程瞬息万变&#xff0c;学员在倒库过程中&#xff0c;因为脚下离合没控制好&#xff0c;速度快了&…

linux以太网连接树莓派,如何在没有Internet的情况下直接连接到树莓派

描述Raspberry Pi的多功能性意味着您一定会在室外使用它&#xff0c;而这超出了无线网络的范围。那么&#xff0c;如何在不插入键盘和显示器的情况下与之通信呢&#xff1f;不言而喻&#xff0c;在不实际的情况下&#xff0c;要拖着人为输入设备并随身携带显示器。首先需要满足…

前端设置画布的高度_【后期修图】ps画布设置详解

在图像设计时少不了好的工具&#xff0c;现在当下比较流行的软件属 PS 最好&#xff0c;它的功能强大&#xff0c;是设计者的必备工具之一。在今天的内容中&#xff0c;小编要与大家分享下PS中修改画布大小的实例操作步骤&#xff0c;希望文章对大家以后的工作有所帮助。画布大…