首页
社区
课程
招聘
[原创] ARM栈回溯——从理论到实践,开发ida-arm-unwind-plugin
发表于: 2020-8-22 14:50 20780

[原创] ARM栈回溯——从理论到实践,开发ida-arm-unwind-plugin

2020-8-22 14:50
20780

本文从栈回溯的原理开始,到 arm ehabi 的回溯方式,再到 elf 文件中的 unwind 信息,最后实现一款 ida 里实时进行 arm 栈回溯的插件,覆盖了现代 arm 栈回溯的全部内容,希望能给大家带来帮助。

作品链接:https://github.com/LeadroyaL/IDA_ARM_Unwind

pyelftools的commit 链接:https://github.com/eliben/pyelftools/commit/ee0facee32ae5fc91709c93f9a57a9a7683a3315

故事要从几个月前的一个 arm crash 说起,把 crash 交给新来的小朋友看,他说 IDA 里显示的栈回溯和logcat里显示的栈回溯是不一致的,问我为什么。

我说一直是logcat里看的,是正确的;那么 ida 里只有一层栈回溯肯定是错的,但我却解释不来原因,于是有了本文和一系列研究。

从小老师就教育我们,函数开头一般是这三句话,用于保存堆栈,开辟新的栈空间:

在这种设定下,栈回溯变得非常简单,ebp 就是栈帧,ebp 附近是上一个栈帧,再附近是返回地址。网上相关的文章一搜一大把,这里就不多讲了,找一张网图凑合一下。

我们随便找个 /system/lib/libc.so,再随便编译一个 so,随便找几个函数看一下,发现和x86的不大一样。

观察这几组汇编,前两段 sp 的内容并没有被保存到任意一个寄存器里,但它可以被正确栈回溯,暗示栈回溯信息不在这段汇编里;后两段,把 sp 放到 r7 里,把 sp+8 放到 r7 里,有点像栈帧的感觉,并且函数内也没有覆盖掉 r7 的内容,有点 x86 的感觉。

查阅资料,随着时代发展,arm 有两种 unwind 方式:

使用readelf -u可以查看,字节码长这样:

讲了这么多,终于引出本系列的重点:arm ehabi。

官方文档,复杂但权威:https://developer.arm.com/documentation/ihi0038/b/
看雪有篇不错的文档:原创andorid native栈回溯原理分析与思考

名词解释

和平时逆向相关的,有两部分内容,有个大致认知就行:

本文讲的是背景,没什么技术细节,第二篇讲文件格式。

本系列文章共三篇。本文是第二篇,讲 ELF 文件如何存放和使用 arm ehabi。

Section角度:很久以前,readelf -S 时候一直不理解这两个 section 是做什么的,占空间,放的不是汇编,IDA 打开,里面也是一团意义不明的 data,总觉得没什么用。

Segment角度:readelf -l 时候,EXIDX 就是 .ARM.exidx,感觉还是有点用的,但仍然意义不明;不能直接确认 .ARM.extab 的位置。

好了不废话了,本文只针对 shared_libraryexecutable 的 ehabi 解析,不支持 relocatable;因为 relocatable 拥有大量的 .ARM.exidx section 和重定位,有点复杂。

本文参考:

这个 section 连续存放Entry,视为一个Entry数组。先要处理大小端问题,处理好后,每个 Entry 由两个 uint16 组成,相当于如下 struct:

EHEntry.Offset 意义是函数起始偏移。最高 bit 一定是 0,结合当前偏移(当前 pc )进行使用 prel31 解码,得到 uint64_t。

格式为:

EHEntry.Word1 有三种情况:

EHEntry.Word1 == 1 ,表示 CannotUnwind

最高 bit 为 1,则[31:24]必须为 0b10000000(其实是 personality 为 0,属于 inline compact model),余下 X、Y、Z, 3 个 byte 表示字节码。

最高 bit 为 0,则整个 Word1 使用 prel31 解码,得到 uint64_t,指向真正的数据。必然会落在 .ARM.extab 里。

这个东西就是个计算方式,根据当前的绝对偏移(uint32),与当前的内容(uint32)进行运算,求出绝对偏移(uint64)。

对于 ELF 文件,我们可以假想它基址为 0,从而实现解析;

对于内存中的 ELF 片段,可以通过这个计算,根据当前位置寻找到附近的另一个位置,从而避免重定位;

下图代码中,Address 表示当前内容,Place 表示绝对偏移。

.ARM.extab 作为 .ARM.exidx 的附属存在,存放数据,但无法直接找到每段数据的入口。入口需要借助上文 Entry.Word1,当 Entry.Word1 的最高 bit 为 0 时,prel31解码后一定会指向 .ARM.extab 的内容,这就是入口。

名词解释:personality ——特性,可能没有中文概念。

先读出第一个 uint32_t,进行初步解析,再根据情况进行进一步解析,有以下的情况:

最高 bit 为 0,表示 generic personality,使用 prel31 解码,使用指向的函数进行 unwind。

最高 bit 为 1,表示 arm compact personality[31:28]必须为 0b1000[27:24] 有且仅有有0、1、2三种情况。

0: inline compact model,X、Y、Z, 3 个 byte 表示字节码。

1或者2: [23:16] 表示 more_word(uint_8),表示剩余字节码个数,后面的都是字节码

根据上文,我们可以得到每一处 Entry 及其 unwind方式。我们关心的是使用字节码进行 unwind (即 personality 为0、1、2)的情况,经过解析可以得到 uint8_t[N] 的字节码 ,解析方式在 "Table 4, ARM-defined frame-unwinding instructions" 文件章节。

参考 llvm-readelf 的实现,它的可读性最好,代码在 https://github.com/llvm/llvm-project/blob/master/llvm/tools/llvm-readobj/ARMEHABIPrinter.h ,其中有大量OpcodeDecoder::Decode_XXXXX 函数可以抄。

纯体力活,没什么好说的,官方表格如下:

很遗憾,pyelftools 并未实现解析 arm ehabi 的功能,

我为什么要写解析的功能?一方面因为 pyelftools 平时经常用,想为它做一些贡献;另一方面,我计划写一个 ida-arm-unwind 的插件,缺乏一个 python 库帮我完成解析,在 pyelftools 上补充功能是最合适的。

pull reqeust:https://github.com/eliben/pyelftools/pull/328

merge commit:https://github.com/eliben/pyelftools/commit/ee0facee32ae5fc91709c93f9a57a9a7683a3315

花了不少时间,写了将近 1000 行代码,实现得也比较优雅,大概有如下的功能:

看一下效果:

本文讲了 ELF 里 arm ehabi 的存放和使用。

本系列文章共三篇。本文是第三篇,使用已有的知识,实现 arm stack unwind,给本系列完美地画上句号。

IDA 里有一个不常用的功能,叫打印栈回溯,使用的是常见的 ebp/esp 栈帧技术,没有对 ARM 进行适配,导致调试安卓 so 时完完全全是错的。本文编写一个 ida 插件,正确展示实时的 arm 栈回溯。

设计思路:

效果图,个人感觉还是非常好用的哈:


具体用途看仓库里的 readme 哈。

三篇文章,虽然是按照开发的时间顺序写的,但发生顺序其实是反的,这个流程拖得挺长:

把大量的规范从 c 移植为 python,整个下来花了我大量时间,但对 unwind 本身有了非常深刻的理解,希望 elftools.ehabiida-arm-unwind-plugin 这两个轮子能为行业做贡献吧。

作品链接:https://github.com/LeadroyaL/IDA_ARM_Unwind

pyelftools的commit 链接:https://github.com/eliben/pyelftools/commit/ee0facee32ae5fc91709c93f9a57a9a7683a3315

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-8-23 09:31 被LeadroyaL编辑 ,原因: 错别字
收藏
免费 17
支持
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  orz1ruo   +2.00 2020/08/23 精品文章~
最新回复 (24)
雪    币: 10941
活跃值: (7324)
能力值: ( LV12,RANK:219 )
在线值:
发帖
回帖
粉丝
2
膜拜
2020-8-22 16:41
0
雪    币: 204
活跃值: (74)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
精品啊。
2020-8-22 18:09
0
雪    币: 5330
活跃值: (5464)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
4
码住
2020-8-23 00:07
0
雪    币: 122
活跃值: (148)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
666
2020-8-23 20:07
0
雪    币: 2335
活跃值: (1319)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
6
m
2020-8-24 09:59
0
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
7
赞!
2020-8-24 10:23
0
雪    币: 36
活跃值: (1061)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
666
2020-8-24 10:46
0
雪    币: 1841
活跃值: (1290)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
9
2020-8-24 11:09
0
雪    币: 2308
活跃值: (2200)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
10
不错不错~
2020-8-24 11:37
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
感谢分享!
2020-8-26 17:43
0
雪    币: 6573
活跃值: (3888)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
12
不错 
2020-8-28 15:10
0
雪    币: 916
活跃值: (3434)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
13
大哥牛逼
2020-8-28 16:01
0
雪    币: 156
活跃值: (3801)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
mark了
2020-8-28 16:59
0
雪    币: 161
活跃值: (533)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
可以顺便支持 eh_frame_hdr,eh_frame 就完美了 
2020-8-28 19:44
0
雪    币: 120
活跃值: (1597)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
牛逼
2020-8-29 17:13
0
雪    币: 5330
活跃值: (5464)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
17
谢谢大佬分享 ,mark
2020-8-31 13:24
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
请教一个问题:arm的堆栈回溯,.ARM.exidx EHEntry里保存的函数地址,是什么时候保存进去的,我没有找到函数调用时,写入exidx的调用代码.
2020-8-31 17:05
0
雪    币: 35
活跃值: (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
wx_龙龙_794 请教一个问题:arm的堆栈回溯,.ARM.exidx EHEntry里保存的函数地址,是什么时候保存进去的,我没有找到函数调用时,写入exidx的调用代码.
编译期写进去的吧
2020-9-1 10:50
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
@winkar 编译的时候写进去很难解释通啊,so加载的时候,很多函数才能重定位地址。即使在编译期写入,也应该有汇编代码呀,这部分代码没看到在哪里。
2020-9-1 11:36
0
雪    币: 4752
活跃值: (2923)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
21
wx_龙龙_794 请教一个问题:arm的堆栈回溯,.ARM.exidx EHEntry里保存的函数地址,是什么时候保存进去的,我没有找到函数调用时,写入exidx的调用代码.

我认为是连接时期干的,但是没有强力的证据来辅佐我的猜想。

你可以做这个测试:编译一个.o 文件,观察它。readelf 发现它已经有了 exidx 的内容,每个函数对应一个exidx、edix.rel,它本身是以函数为单位,可以被重定位的。


【函数地址,是什么时候保存进去的】 因此我认为是连接时候干的,有了 rel 信息,连接时候统一计算函数地址不成问题。

最后于 2020-9-1 16:33 被LeadroyaL编辑 ,原因:
2020-9-1 16:32
0
雪    币: 4752
活跃值: (2923)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
22
liowmark 可以顺便支持 eh_frame_hdr,eh_frame 就完美了
没必要呀兄弟,ida 本来就支持这个格式吧
2020-9-1 16:33
0
雪    币: 23
活跃值: (43)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
膜拜
2021-2-21 01:32
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
看看
2023-8-18 11:27
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
请问可以用frida复现这个插件吗
2024-8-20 15:06
0
游客
登录 | 注册 方可回帖
返回
//