ARM固件加载基址

定位arm固件基址

本文主要参考了ARM设备固件装载基址定位的研究这篇论文中提到的手工定位的arm固件基址的方法。同时,论文中还提出了其它几种自动定位固件基址的方法,主要是:

  • 基于函数入口表的装载基址定位方法
  • 基于字符串地址集合的装载基址定位方法
  • 基于文字池匹配的装载基址定位方法
  • 基于字符串存储长度分组匹配的装载基址定位方法

感兴趣的可以直接去看论文~,这里仅对手工定位的方法进行介绍

定位方法

通过手工定位的一些实践,发现主要是下面的两个方法比较好用:

  1. 猜:利用默认的加载基址0来打开固件,在固件的头部或是其他地方存在着一些绝对地址(例如将某个地址加载到pc寄存器),这些地址一般和正确的加载基址相差不大,尝试一下可能就会得到正确的基址
  2. 算:先将固件的基址设置为0,接着找到固件中的switch语句,利用switch语句跳转表中default分支的绝对地址减去当基址为0的时候的default语句的所在的地址即可得到固件的加载基址

对第二种方法的理解:一般switch语句跳转表中保存了多个case的绝对地址,其中的default语句地址记为real_addr,当设置程序加载基址为0时default语句地址记为fake_addr

那么会有:

1
2
3
4
real_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_addr0x21d804,接着就可以算出来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固件的加载基址,但也可以对一些固件的加载基址进行手工定位,能够对固件的进一步分析提供一些帮助。