定位arm固件基址
本文主要参考了ARM设备固件装载基址定位的研究
这篇论文中提到的手工定位的arm固件基址
的方法。同时,论文中还提出了其它几种自动定位固件基址的方法,主要是:
- 基于函数入口表的装载基址定位方法
- 基于字符串地址集合的装载基址定位方法
- 基于文字池匹配的装载基址定位方法
- 基于字符串存储长度分组匹配的装载基址定位方法
感兴趣的可以直接去看论文~,这里仅对手工定位的方法进行介绍
定位方法
通过手工定位的一些实践,发现主要是下面的两个方法比较好用:
- 猜:利用默认的加载基址
0
来打开固件,在固件的头部或是其他地方存在着一些绝对地址(例如将某个地址加载到pc寄存器),这些地址一般和正确的加载基址相差不大,尝试一下可能就会得到正确的基址 - 算:先将固件的基址设置为0,接着找到固件中的switch语句,利用switch语句跳转表中
default
分支的绝对地址减去当基址为0的时候的default
语句的所在的地址即可得到固件的加载基址
对第二种方法的理解:一般switch语句
跳转表中保存了多个case
的绝对地址,其中的default
语句地址记为real_addr
,当设置程序加载基址为0时default
语句地址记为fake_addr
。
那么会有:1
2
3
4real_addr = true_base_addr + relative_default_addr
fake_addr = 0 + relative_default_addr
// 相减就可以得到正确的加载基址
true_base_addr = real_addr-fake_addr
其中true_base_addr
是程序正确的加载基址,relative_default_addr
为default分支的相对偏移,这与基址多少无关,是固定的。
实践
使用默认基址打开固件,发现固件最开始处存在下列可疑的地址:
这里猜测与0x40040
最相近的0x40000
是加载基址,在IDA中rebase
一下,发现基址确实是0x40000
:
错误的基址:
正确的基址:
对于该固件来说,除了猜基址之外,还可以算出来正确的基址,首先将基址设置为0,然后Alt+T
全局搜索jump table
字符串,在找到的跳转表处利用跳转表中的绝对地址0x21d804
减去0x1dd804
即可得到加载基址0x40000
看到这里就会有疑问了,为什么就知道是用0x21d804-0x1dd804
,而不是跳转表里面的其它值呢?
首先,可以看到跳转表里面的地址从低到高依次是:
0x21D7C8–>0x21D804–>0x21D814–>0x21D844
他们之间的差值依次为:
———-0x3c———-0x10———-0x30——–
论文中的说法是:
仔细检查每个
case
的距离,如果某个case的距离不同于其他case的距离,就可以分析出这个case的绝对地址和偏移量的关系
个人感觉这应该是在假定每一个case语句中的代码长度是一样的情况下才能成立,例如每个case语句的长度占用8个字节,而default语句占用4个字节,这种情况下可以用来区分default语句。然而,并不是所有的case语句长度都一样,有些case语句长有些case语句短,甚至default语句的长度也可以比case语句长。
观察到IDA已经识别到跳转表的default分支的fake_addr=0x1dd804
,跟进该地址
可以看到default分支的开始到结束(0x1dd804–>0x1dd814)之间的差值为0x10,刚好对应跳转表中的(0x21d804–>0x21d814)之间的差值,所以就可以猜测default分支对应的real_addr
为0x21d804
,接着就可以算出来true_base_addr = real_addr-fake_addr=0x21d804-0x1dd804=0x40000
,即可得到程序的加载基址
固件中还存着许多其它的跳转表,可以发现其他的跳转表也可以用同样的方法来计算加载基址
上图中跳转表的地址从低到高排列依次是:
0x142C64–>0x142C74–>0x142C7C–>0x142C8C
对应的差值依次为:
0x10–>0x8–>0x10
观察到IDA识别出来的default分支0x102c7c
的长度刚好为8个字节,对应0x142C74-->0x142C7C
的差值,所以可以计算0x142C74-0x102C74=0x40000
总结
通过上面介绍的两种方法,虽然不能处理所有的arm固件的加载基址,但也可以对一些固件的加载基址进行手工定位,能够对固件的进一步分析提供一些帮助。