看Clang的文章前觉得是啥啊,看完后热血沸腾。这么好的文章不该埋没
计算机最讲道理。凭啥计算机知道加载地址,而我不知道?答:裸机程序中不必要包含自己的加载地址,如果没有加载地址,就无法对绝对地址的引用有正确的解析。所以分析的固件如果是裸机层面的代码,就需要知道其加载地址。
本文章相关PPT:加载地址相关.pptx
原因分析
平日在做pwn题时,从来没有分析过程序的加载地址因为:
-
如果没有开启PIE编译选项,程序的加载基址是写在ELF文件中的
-
如果开启PIE编译选项,那么程序的加载基址是加载器随机决定的
二种程序都可以被IDA正常的分析,其中的地址解析也不会出现什么问题,因为绝对地址的引用和ELF中保存的程序基址是匹配的。所以我们也从来就没有在分析代码时琢磨过加载地址啥的,那么为啥研究IoT固件的时候就需要知道固件的加载地址呢?让我们来看一下你刚刚按下电源键那一刻,计算机内部的盘古开天地吧!
CPU相关
不过在按下电源之前,先回忆一下CPU这么多年的发展吧:
-
芯片维基百科(非常推荐!!!)
-
CPU的历史
-
时间简史——扒一扒那些近代经典CPU(上)
-
时间简史——扒一扒那些近代经典CPU(下)
-
CPU 历史上著名的破解有哪些?
-
硅谷历史 Intel的东进与ARM的西征
-
Arm公司再次重拳反制RISC-V架构,中国芯片厂商们该何去何从
x86
先说我们一般PC机的CPU架构:x86,其模式包括,实模式,保护模式,虚拟8086模式,IA-32e模式以及系统管理模式:
历史
仍然来看一下x86这么多年来的历史吧:
-
x86的历史和未来
-
x86计算机的架构演进
-
x86 架构发展至今,是不是可以抛弃 8086 的历史包袱,重新设计新的架构?
-
既然有说 x86 架构是过时架构,为什么不采用新架构替换它?
想要明白x86,看英特尔官方手册是最好的,不过我也没明白总共有几卷,官网注释说总共5卷,下面这就8卷了,还有一个2卷的集合,也不知道咋回事,乱七八糟的。卷123内容分别是:基本架构,指令集参考,系统编程指南
-
英特尔® 64 位和 IA-32 架构开发人员手册:卷 1
-
英特尔® 64 位和 IA-32 架构开发人员手册:卷 2A
-
英特尔® 64 位和 IA-32 架构开发人员手册:卷 2B
-
英特尔® 64 位和 IA-32 架构开发人员手册:卷 2C
-
英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A
-
英特尔® 64 位和 IA-32 架构开发人员手册:卷 3B
-
英特尔® 64 位和 IA-32 架构开发人员手册,卷 3C
-
英特尔® 64 位和 IA-32 架构开发人员手册,卷 3D
-
英特尔® 64 位和 IA-32 软件开发人员手册:文档变更
一个单独的卷2:
-
英特尔® 64 位和 IA-32 架构软件开发人员手册第 2 卷
可以看到x86架构的CPU实体一般是Intel和AMD两家生产,这两家既是x86架构的设计者,又是CPU的设计者。
启动分析
以《一个64位操作系统的设计与实现》为例,最近正好在跟着这本书学习操作系统相关知识,x86 CPU的启动,当然这个启动过程的例子是古老的:
-
CPU上电,CS:IP复位到0xffff0
-
此时天地初开,内存没有开始工作,CPU访问的0xffff0实际上是BIOS的ROM,这个实现的原理应该是在线路上设计好的
-
BIOS在0xffff0的指令一般是一个长跳转指令,不过仍然是跳转到BIOS中的代码去执行,初始化各种硬件
-
去读取磁盘上的0磁头0磁道1扇区(引导扇区)内容到内存中0x7c00,然后并跳转
-
引导扇区其实怎么设计都可以,直接放文件系统的第一个扇区格式也可以,总之是存放着boot相关代码
-
在《一个64位操作系统的设计与实现》中,第一个扇区是带FAT12文件系统的boot,整个磁盘被格式化为FAT12文件系统
-
Boot去文件系统中搜索loader.bin并加载到0x10000,并跳转过去
-
loader去文件系统中搜索kernel.bin并加载到0x100000,并跳过去
代码名称 | 代码基址 | 静态时代码保存位置 | 控制流 | 基址决定因素 |
---|---|---|---|---|
BIOS | 0xf0000 | BIOS ROM | 开机加电跳转到0xffff0 | 主板线路BIOS基址 |
boot | 0x7c00 | 硬盘第一个扇区 | BIOS执行完毕后跳转到0x7c00 | BIOS决定boot基址 |
loader | 0x10000 | 硬盘中的文件系统(扇区随意) | boot执行完毕后跳转到0x10000 | boot决定loader基址 |
kernel | 0x100000 | 硬盘中的文件系统(扇区随意) | loader执行完毕后跳转到0x100000 | loader决定kernel基址 |
-
所以BIOS可以不知道自己的加载地址,因为线路以及决定好了映射,CPU加电就跳过去
-
同理boot可以不知道自己的加载地址,因为BIOS加载的他,并跳过去
-
同理loader也可以不知道自己的加载地址,因为boot加载的他,并跳过去
-
同理kernel也可以不知道自己的加载地址,因为loader加载的他,并跳过去
之后我们一般将boot.bin,loader.bin,kernel.bin,一起打包成一个镜像os.img,也就是我们常见的系统镜像:
所以没法直接用IDA分析融合了三个文件的os.img,需要拆出来分别分析。故拆的方法就是打包的逆方法,镜像打包的方法可能不同,所以拆的方式也不同,本书中是os.img是一个FAT12的文件系统,boot.bin作为FAT12文件系统的第一个扇区,loader.bin与kernel.bin是直接放入文件系统中的文件:
-
IDA可以直接识别BIOS固件的加载基址
-
要用IDA分析boot需要设置加载地址0x7c00
-
要用IDA分析loader需要设置加载地址0x10000
-
要用IDA分析kernel需要设置加载地址0x100000
更多关于BIOS与UEFI的知识:
-
不知道哪来的BIOS文档
-
MBR与GPT
-
UEFI背后的历史
-
UEFI架构
-
UEFI与硬件初始化
-
ACPI与UEFI
-
UEFI和UEFI论坛
-
UEFI安全启动
-
FAT文件系统与UEFI
-
知乎专栏:UEFI和BIOS探秘
ARM
IoT设备的CPU大多是ARM架构,所以ARM才是我们关注的重点,还是从ARM的历史说起把!
历史
-
一文带你分分钟掌握手机ARM处理器的前世今生
-
干货丨ARM、MCU、DSP、FPGA、SOC各是什么?有什么区别?
可以看出ARM公司不直接设计CPU,和Intel不太一样。所以我们常见的ARM架构的手机的CPU的那些牌子,比如苹果的A系列,高通骁龙,华为海思的麒麟,都是ARM授权才允许制作的。还看到了任天堂,索性查了一下常见的一些设备:
发现的确都是ARM架构的CPU,而且也都是不是ARM公司自己去做的,可以发现一张芯片从指令集到整体设计再到制作,都不是一家完成的。所以虽然说CPU的指令集都是ARM,可是这些CPU却千差万别,比如,以下分析了一些ARM架构的CPU的启动过程:
-
ARM芯片上电取第一条指令流程
-
ARM上电启动及Uboot代码分析
-
平台/代码从上电到运行(ARM/Cortex-M)(一)
一张芯片上只有CPU是不合适移动设备如此普及的今天,于是来到了SoC的时代:
SoC的定义多种多样,由于其内涵丰富、应用范围广,很难给出准确定义。一般说来,SoC称为系统级芯片,也有称片上系统,意指它是一个产品,是一个有专用目标的集成电路,其中包含完整系统并有嵌入软件的全部内容。同时它又是一种技术,用以实现从确定系统功能开始,到软/硬件划分,并完成设计的整个过程。
-
什么是SoC
-
知乎话题:Soc
-
为什么知乎上有很多人认为苹果A系列芯片不是SOC
-
为什么树莓派等嵌入式 ARM 平台没有用高通或者 MTK 等 SoC?
-
关于Exynos4412 SoC的启动分析
要问到底啥是SoC:
-
知乎回答:soc≈一台电脑的主机,而不是=cpu
-
我的回答:集成了其他部件的CPU就可以算SoC了
移动设备上,寸土寸金,既要考虑成本,又要考虑工艺,还要考虑节能等等。所以集成电路(IC)发展到今天是可以做到把各种东西(CPU,RAM,ROM,GPU,NPU等等)封装到一张芯片里的。这个芯片本身,加上能控制这一套东西正常运行的软件,都应该是SoC的一部分。
启动分析
以一般android手机启动为例:
-
CPU上电,位于CPU内部的onChipRom开始执行
-
此时天地初开,外部内存SDRAM没有真正的开始工作,onChipRom进行一些列芯片内部的初始化工作
-
onChipRom将flash中的xloader加载到CPU内部的sram中,然后跳转过去执行
-
xloader对系统时钟以及外部SDRAM进行初始化,然后将flash芯片中的u-boot(bootloader)加载到外部内存中,然后跳转
-
u-boot(bootloader)在进行一系列初始化,并将flash中的kernel加载到外部内存中然后跳转
代码名称 | 代码运行位置 | 静态时代码保存位置 | 控制流 | 运行位置决定因素 |
---|---|---|---|---|
onchiprom | SRAM | CPU chip | CPU加电 | CPU设计 |
xloader | SRAM | flash | onChipRom执行后跳转 | onChipRom决定 |
u-boot | SDRAM | flash | xloader执行后跳转 | xloader决定 |
kernel | SDRAM | flash | u-boot执行后跳转 | uboot决定 |
注:onChipRom,boot rom,rom code,一个意思,都是指SoC内部的ROM,即整个CPU芯片里面的ROM
-
on-chip ROM boot的原理分析
-
xloader概念
-
关于xloader和uboot的几个初级问题
-
uboot如何启动内核
-
Uboot和内核到底是什么(从系统启动角度看)
-
U-Boot移植——链接地址、运行地址、加载地址、存储地址
-
真假vmlinux–由vmlinux.bin揭开的秘密
-
vmlinux,vmlinuz,bzimage,zimage,initrd.img的区别与联系
当然现在的手机启动没有这么简单,在uboot这个层面不仅仅是简单的启动一个linux的kernel,而且有着更复杂的过程,即ARM公司提出的Trust Zone技术,linux kernel已经不再是启动的唯一目标。
-
一篇了解TrustZone
-
ARMv8 arm trust firmware信任的固件—-安全相关
影响
还是以以《一个64位操作系统的设计与实现》最开始的一个引导程序为例:
可以看出没有正确的加载地址,将导致分析绝对地址使用时分析出错,如:控制流转移jmp,call,以及数据的访问。我们在分析内存破坏漏洞的时候关心的就是内存,唯一标识一块内存的信息就是内存地址,但是在不同的寻址模式下,内存地址的意义也是不同的,所以分析一段代码,也要关心执行这段代码时,CPU的模式,寻址的模式。
对比平日的用户程序和裸机的二进制:
加载地址 | 加载者 | 地址分类 | |
---|---|---|---|
用户程序 | 程序文件自身包含 | 加载器:如linux的ld | 虚拟地址 |
裸机程序 | 程序文件不必要包含 | 自举:只要能run就可以 | 物理地址 |
总结
所以我们研究的固件研究加载地址是为了:分析那些需要确定加载地址的代码
这句圈话类似于,于丹讲论语,《论语·子罕》:”子曰:知者不惑,仁者不忧,勇者不惧”,知者不惑意为:聪明的人就不困惑。有人抨击于丹,这不是废话么,聪明的就不困惑,困惑的就不聪明。认为此处,知,应该翻译成求知。
哪些代码需要确定加载地址呢?
-
裸机程序:bootloader 这类(物理地址)
-
内核程序:vmlinux 这类(逻辑地址)
比如一个路由器固件(全部存储在flash中)包括:bootloader,linux kernel,以及文件系统。如果我们关注点在文件系统中的可执行程序,那么就完全不用关心什么固件加载地址。
但是如果我们关注一个PLC固件,这个东西实现不是bootloader起linux kernel这套。而是类似裸机程序,即关注的功能实现就是在bootloader和kernel的层面实现的,就需要关注加载地址了。如:工控漏洞挖掘方法之固件逆向分析。
确定地址
发现一篇文章介绍了一个工具可以帮助定位固件的加载地址:
-
https://github.com/MagpieRYL/arm32_base_loc
这个工具参考的论文:
-
ARM设备固件装载基址定位的研究
这篇文章解决的问题就是:在只有这个二进制文件本身时,利用其自身的一些性质,分析出其加载地址。利用的性质如下:
-
利用序言来识别函数
-
利用了代码开发时的函数表的特征
-
利用了函数指针与函数的关系对应
-
利用了字符串的引用,长度等
其中的第一种方法是:基于函数入口表的装载基址定位,也是刚才的工具的原理方法。在分析大型应用程序的源代码时,经常可以发现这种形式的代码:
//GT-I9500_JB_OpensourceKernelnetbluetoothmgmt.c
struct mgmt_handler {
int (*func) (struct sock *sk, struct hci_dev *hdev, void *data, u16 data_len);
bool var_len;
size_t data_len;
}
mgmt_handlers[] = {
{ NULL }, /* 0x0000 (no command) */
{ read_version,false, MGMT_READ_VERSION_SIZE },
{ read_commands, false, MGMT_READ_COMMANDS_SIZE },
......
{ block_device,false, MGMT_BLOCK_DEVICE_SIZE },
{ unblock_device,false, MGMT_UNBLOCK_DEVICE_SIZE },
};
该代码来自三星手机内核源代码,这类代码的特点是定义了一个结构体数组,并且结构体的成员之一为函数指针。作者定义这些函数指针组成的表为函数入口表。
-
所以想办法分析出二进制中的函数指针后
-
对内存中所有可能的装载基址位置进行枚举,在每一个可能位置根据函数入口表查找每个函数序言
-
如果一个基址使得较多的函数指针指针匹配上了函数
-
那么估计这个地址为装载基址。
剩下三种办法就是根据字符串在代码中的一些使用性质,如引用偏移,长度啥的,进行的分析:
-
基于字符串地址集合的装载基址定位
-
基于文字池匹配的装载基址定位
-
基于字符串存储长度分组匹配的装载基址定位
这个哥们还在Kcon2018做了关于此内容的报告,不过没有找到完整的PPT:
-
KCon 2018 黑客大会第二个演讲日精彩内容回顾
-
KCon 2018 如何优雅地获得ARM固件装载地址
-
KCon PPT 公开下载
固件练习
IOT 安全实战资料收集整合
IDA加载地址设置
IDA如果ARM,MIPS等处理器,则可以在选择完CPU后设置如下界面,x86只能设置基址偏移:
提取练习
-
物联网硬件安全分析基础-固件提取
-
内有十种固件提取方法和首次公开uboot提取固件方法
分析练习
哪些binwalk出来就可以直接分析文件系统中的代码的?
哪些是本身加密的,需要解密后才能binwalk的?
哪些不是要研究文件系统的,而是直接研究无文件结构的代码的?
-
工控漏洞挖掘方法之固件逆向分析
-
工控安全入门(五)—— plc逆向初探
-
施耐德NOE77101以太网模块固件逆向及后门挖掘
-
VXWorks 固件 NOE-711 后门账号漏洞分析
-
VxWorks固件分析方法总结
-
D-Link DIR-882 路由器加密固件的解密
原文始发于微信公众号(qingjiegong):为啥要分析固件的加载地址?
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论