F1C100S-使用IDA分析可执行文件
前言
这里从内存镜像文件(elf文件objcopy后的bin文件)出发,主要从汇编的角度分析F1C100S(ARM926ejs)启动的早期阶段,建立startup.S和最终生成的二进制文件之间的关系。
下面是本文使用到的软件环境。
软件 | 介绍 |
---|---|
系统 | linux mint20 |
工具链 | gcc version 10.3.1 20210621 |
构建工具 | SCons: v4.2.0 |
文件分析 | IDA_Pro_v7.0,EmEditor,windows计算器 |
源文件正向分析
由于我们已知目标处理器的架构和指令集,也有对应的源文件,因此可以先从源文件入手。
start.S代码片段
/*
***************************************
* Interrupt vector table
***************************************
*/
.section .vectors
.code 32
b system_vectors
.long 0xaa55aa55
.long 0
.long image_size
.global system_vectors
system_vectors:
ldr pc, _vector_reset
ldr pc, _vector_undef
ldr pc, _vector_swi
ldr pc, _vector_pabt
ldr pc, _vector_dabt
ldr pc, _vector_resv
ldr pc, _vector_irq
ldr pc, _vector_fiq
_vector_reset:
.word reset
_vector_undef:
.word vector_undef
_vector_swi:
.word SVC_Handler
_vector_pabt:
.word vector_pabt
_vector_dabt:
.word vector_dabt
_vector_resv:
.word vector_resv
_vector_irq:
.word vector_irq
_vector_fiq:
.word vector_fiq
.balignl 16,0xdeadbeef
/*
***************************************
* Stack and Heap Definitions
***************************************
*/
.section .data
.space UND_STACK_SIZE
.align 3
.global und_stack_start
und_stack_start:
.space ABT_STACK_SIZE
.align 3
.global abt_stack_start
abt_stack_start:
.space FIQ_STACK_SIZE
.align 3
.global fiq_stack_start
fiq_stack_start:
.space IRQ_STACK_SIZE
.align 3
.global irq_stack_start
irq_stack_start:
.skip SYS_STACK_SIZE
.align 3
.global sys_stack_start
sys_stack_start:
.space SVC_STACK_SIZE
.align 3
.global svc_stack_start
svc_stack_start:
/*
***************************************
* Startup Code
***************************************
*/
.section .text
.global reset
reset:
/* Enter svc mode and mask interrupts */
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r0, r0, #MODE_SVC|NOINT
msr cpsr_cxsf, r0
ldscript脚本片段
. = 0x80000000;
__image_start = .;
无关文本省略......
__data_start = .;
. = ALIGN(4);
.data :
{
无关文本省略......
. = ALIGN(4);
__image_end = .;
}
__data_end = .;
PROVIDE(image_size = __image_end - __image_start);
".section .vectors" 定义了一个名为"vectors"的数据段,".code 32"选择ARM指令集,每个指令定长32位(4字节);
"b system_vectors" 生成无条件跳转到名为"system_vectors"节处的汇编指令,
".long 0xaa55aa55" 定义了一个长整型,分配空间并指定值为0xaa55aa55,long的定义为大于等于int,通常long占用的空间和int一样(int大小和平台相关),这里为4字节,"0xaa55aa55"在这里主要作为SPL程序加载用户程序时,检查读取到的二进制文件是否合法,与文件头的作用相同。
".long 0" 这里主要起补齐作用,由于中断向量表末尾有".balignl 16, 0xdeadbeef",表明进行16字节对齐,因此这里手动补4个字节避免自动填充"0xdeadbeef"
".long image_size" 可以从名字推断是镜像文件大小,这个编译时将留空,等到连接完成后再填充;从下面的链接脚本可以看到,导出了"image_size"符号,因此"image_size"就等于.text段 + .data段(.rodata也计算进入)的大小。
"ldr pc, XXX" 将处理器各个异常(中断)模式下的R15(PC)指向对应的中断入口点,随后跳转到"reset"标签处执行。
"Stack and Heap"由"reset"标签的代码处理,这里声明各个模式下的Stack空间,编译时预留RAM地址空间,随后的"reset"代码将"xxx_stack_start"赋值到R13(SP)栈顶。
IDA逆向分析
由于篇幅限制,这里就不介绍IDA的基本操作了,下面给出将二进制文件导入时的配置参数。
经过反汇编处理后的代码如下,可以说和上面的源文件一模一样了。
RAM:80000000 ; Processor : ARM
RAM:80000000 ; ARM architecture: ARMv5TEJ
RAM:80000000 ; Target assembler: Generic assembler for ARM
RAM:80000000 ; Byte sex : Little endian
RAM:80000000
RAM:80000000 ; ===========================================================================
RAM:80000000
RAM:80000000 ; Segment type: Regular
RAM:80000000 AREA RAM, DATA, ALIGN=0
RAM:80000000 ; ORG 0x80000000
RAM:80000000 B loc_80000010
RAM:80000000 ; ---------------------------------------------------------------------------
RAM:80000004 DCB 0x55 ; U
RAM:80000005 DCB 0xAA
RAM:80000006 DCB 0x55 ; U
RAM:80000007 DCB 0xAA
RAM:80000008 DCB 0
RAM:80000009 DCB 0
RAM:8000000A DCB 0
RAM:8000000B DCB 0
RAM:8000000C DCB 0xB4
RAM:8000000D DCB 0x93
RAM:8000000E DCB 7
RAM:8000000F DCB 0
RAM:80000010 ; ---------------------------------------------------------------------------
RAM:80000010
RAM:80000010 loc_80000010 ; CODE XREF: RAM:80000000↑j
RAM:80000010 ; DATA XREF: RAM:80000074↓o ...
RAM:80000010 LDR PC, =loc_80000060
RAM:80000014 ; ---------------------------------------------------------------------------
RAM:80000014 LDR PC, =loc_80000280
RAM:80000018 ; ---------------------------------------------------------------------------
RAM:80000018 LDR PC, =loc_80000240
RAM:8000001C ; ---------------------------------------------------------------------------
RAM:8000001C LDR PC, =loc_800002C0
RAM:80000020 ; ---------------------------------------------------------------------------
RAM:80000020 LDR PC, =loc_80000300
RAM:80000024 ; ---------------------------------------------------------------------------
RAM:80000024 LDR PC, =loc_80000340
RAM:80000028 ; ---------------------------------------------------------------------------
RAM:80000028 LDR PC, =loc_800001C0
RAM:8000002C ; ---------------------------------------------------------------------------
RAM:8000002C LDR PC, =loc_800001A0
RAM:8000002C ; ---------------------------------------------------------------------------
RAM:80000030 off_80000030 DCD loc_80000060 ; DATA XREF: RAM:loc_80000010↑r
RAM:80000030 ; RAM:80000090↓o
RAM:80000034 off_80000034 DCD loc_80000280 ; DATA XREF: RAM:80000014↑r
RAM:80000038 off_80000038 DCD loc_80000240 ; DATA XREF: RAM:80000018↑r
RAM:8000003C off_8000003C DCD loc_800002C0 ; DATA XREF: RAM:8000001C↑r
RAM:80000040 off_80000040 DCD loc_80000300 ; DATA XREF: RAM:80000020↑r
RAM:80000044 off_80000044 DCD loc_80000340 ; DATA XREF: RAM:80000024↑r
RAM:80000048 off_80000048 DCD loc_800001C0 ; DATA XREF: RAM:80000028↑r
RAM:8000004C off_8000004C DCD loc_800001A0 ; DATA XREF: RAM:8000002C↑r
RAM:80000050 DCB 0
RAM:80000051 DCB 0
RAM:80000052 DCB 0
RAM:80000053 DCB 0
RAM:80000054 DCB 0
RAM:80000055 DCB 0
RAM:80000056 DCB 0
RAM:80000057 DCB 0
RAM:80000058 DCB 0
RAM:80000059 DCB 0
RAM:8000005A DCB 0
RAM:8000005B DCB 0
RAM:8000005C DCB 0
RAM:8000005D DCB 0
RAM:8000005E DCB 0
RAM:8000005F DCB 0
RAM:80000060 ; ---------------------------------------------------------------------------
RAM:80000060
RAM:80000060 loc_80000060 ; CODE XREF: RAM:loc_80000010↑j
RAM:80000060 ; DATA XREF: RAM:loc_80000010↑o ...
RAM:80000060 MRS R0, CPSR
RAM:80000064 BIC R0, R0, #0x1F
RAM:80000068 ORR R0, R0, #0xD3
RAM:8000006C MSR CPSR_cxsf, R0
RAM:80000070 BL sub_8000010C
RAM:80000074 LDR R0, =loc_80000010
以下代码省略...
经过上面的操作,使用EmEditor打开生成的bin文件,手动将开始的一些字节数据分成6大类型。
标签1:
对应的反汇编代码:
这是SPL程序将用户程序文件载入DRAM后第一条执行的指令,作用是跳转到"system_vectors"也就是"标签5"处,将其输入windows计算器,查看二进制格式数据。
这是一条分支指令,无条件跳转到PC值+偏移量的地址处
最高4bit为条件标记位,其值为1110,表明忽略CPSR寄存器中的C, N, Z 和 V 标签位,Link Bit值为0,表明没有返回地址。
offset域的值为"0x02",如何得到IDA反汇编的结果"RAM:80000000 B loc_80000010"呢?
首先,这里看到的offset域的值需要左移2bit才能还原出原始偏移量,这是因为硬件上地址总线的低2BIT是没有接的(访问总线送出的地址强制被4字节对齐了);
Branch instructions contain a signed 2’s complement 24 bit offset. This is shifted left two bits, sign extended to 32 bits
这也就解释了为何24BIT的offset域能表示"+/- 32Mbytes"的偏移量了, 2^24 * 2^2 = 2^(24+2) = 67108864 = +/- 32M,经过还原后知道了offset的实际值= 0x2 << 2 = 0x8,试着将0x8 + 0x80000000 = 0x80000008,与想要跳转的地址还有差距。
查看F1C100S的简要手册,可以得知ARM926ejs为5级流水线结构
下图是5级流水线操作顺序
- PC初始值为0x8000 0000, 第一次取指令"02 00 00 EA"后,PC自动 + 4,因此PC = 0x8000 0004
- 译码第1步取到的指令,同时去PC的地址取第二条指令,译码与第二次取指同时进行,完成后PC自动+4,因此PC = 0x8000 0008
- 执行第2步译码后的即第1步取到的指令,此使PC还未改变,执行前预取了2次指令,因此PC=当前执行指令的地址 + 8。(到这一步已经能说明问题,后面的操作就省略...)
Branch instructions contain a signed 2’s complement 24 bit offset. This is shifted left two bits, sign extended to 32 bits, and added to the PC.
由于无条件跳转指令,跳转的目的地址 = 偏移量值 + 当前PC值,因此在跳转指令中需要对偏移量减8,以弥补流水线预取造成的PC向后增加了8。
总结来说:0x80000000地址处的无条件跳转指令想要跳转到0x80000010地址地址处,首先对地址减8得到0x80000008,然后右移两位,得到0x80000002,当前PC值=0x80000000,而无条件跳转指令存储的是偏移量,那么offset域 = 0x80000000 - 0x80000002 = 0x02。
标签2,标签3,标签4:
这里存放了二进制文件文件头部数据=文件头+补齐字节+文件大小,引导程序用来判断二进制文件是否合法,将他放在第一条指令后面有个好处,当引导程序初始化完DDR后,将用户程序一部分读入DDR首地址后进行格式检查并获取大小,若无错误直接读取后面的文件内容到DDR,由于第一条是ARM指令,直接从DDR首地址运行即可。如果将文件头部放在最前面,那么就要先读一部分到DDR首地址并进行解析,由于第一条不是ARM指令,解析通过后还需要根据文件头大小计算偏移量,从二进制文件第一条ARM指令处再次读入DDR首地址或者在内存内部搬移,这样产生了2次IO和内存操作,比较浪费。
标签4的值就是二进制文件的大小=.text+.data(包含.rodata)
标签5:
对应的反汇编代码:
这里使用寄存器传送指令设置各种异常模式向量地址,由于使用了基于PC(R15)的相对寻址方式并且存放各个异常地址的内存地址顺序排列,导致最终的汇编代码都是一样的,使用计算器查看二进制值
寄存器传送指令,加载目标地址上的数据到指定的寄存器,IDA比较智能,直接一步到位LDR PC=loc_80000060,但中间还有一些过程。
从上面的解析可以看出来,与第一条指令一样都是忽略CPSR中的4个标志位,offset域值为偏移量立即数;基地址寄存器和目地寄存器都是PC(R15), 所以这是基于PC值,在传输前将PC+偏移量得到目标内存地址,将目标内存地址的值载入R15(PC)。
offset域偏移量值为0x18, 从反汇编代码截图可以看到取这条LDR指令时PC=0x80000010,结合上面的知识,由于5级流水线且执行步骤在流水线第三步,经过取值和译码的两次预取,到执行到这条指令时PC已经向后偏移了8,因此目标内存地址=0x80000010+0x18+0x8 = 0x80000030;而这个内存地址存放的值为0x80000060,就能得到IDA最终显示的LDR PC, =loc_80000060
未完待续。。。
设置完各个异常模式的跳转地址后,按照顺序就执行到了0x80000060处的reset
标签处,这里将把处理器切换成SVC模式,关中断,关MMU,关看门狗等,将中断向量表(上文的system_vectors
)复制到0x0地址处,