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级流水线操作顺序

  1. PC初始值为0x8000 0000, 第一次取指令"02 00 00 EA"后,PC自动 + 4,因此PC = 0x8000 0004
  2. 译码第1步取到的指令,同时去PC的地址取第二条指令,译码与第二次取指同时进行,完成后PC自动+4,因此PC = 0x8000 0008
  3. 执行第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地址处,

posted @ 2022-01-17 22:01  Yanye  阅读(624)  评论(0编辑  收藏  举报