arm 反汇编

来源:互联网 发布:win7仿mac os x主题包 编辑:程序博客网 时间:2024/05/24 23:13
****************************************linux 下动态库函数调用反汇编问题。author: hjjdebugdate:   2016年 09月 07日 星期三 14:41:49 CST****************************************
#include <stdio.h>#include <string.h>void test_so(){    char buffer[256];    printf("---------- hello Android ------------\n");    FILE * fp=fopen("1.txt","rt");    if(fp)    {        memset(buffer,0,sizeof(buffer));        fread(buffer,sizeof(buffer),1,fp);        printf("%s\n",buffer);        fclose(fp);    }        else    {        printf("error open 1.txt\n");    }} cat Makefile #CC=gcc -march=i386 -m32CC=arm-linux-androideabi-gcc -mthumbCFLAG= -c -fPIC -g LDFLAG= -sharedall: hellohello: main.o libtest.so    $(CC) -g -o $@ main.o -L . -ltest main.o: main.c    $(CC) $(CFLAG) -o $@ $<libtest.so : testso.c    $(CC) $(CFLAG) $(LDFLAG) -o $@ $<dump:    arm-linux-androideabi-objdump -S  -M force-thumb hello |tee 1.asmclean:    rm hello main.o libtest.so 1.asm *~

以下分析代码为arm 指令, 由arm-Linux-androideabi-gcc 编译

1. c 函数框架。

c 函数会使用堆栈,故而会使用sp寄存器, 但函数执行完后要恢复sp寄存器,
所以框架通常会保留原来的寄存器,我们把它叫old-sp寄存器。 arm 用fp寄存器
充当这个角色。 下面是它的框架示意: frame pointer 在函数生命期内其值是不变的。

 2b0:    e92d4800     push    {fp, lr} 2b4:    e28db004     add    fp, sp, #4 2b8:    e24ddf42     sub    sp, sp, #264    ; 0x108...... 34c:    e24bd004     sub    sp, fp, #4 350:    e8bd8800     pop    {fp, pc}

2.如何调用其它函数, 参数传递方法。

arm 的传参,小于4个的用寄存器, r0,r1,r2,r3. 大于4个用堆栈。 返回值用r0

3. 如何访问常量数据?例如: “hello android”

  printf("hello android\n"); 2bc:    e59f3090     ldr    r3, [pc, #144]    ; 354 <test_so+0xa4> 2c0:    e08f3003     add    r3, pc, r3 2c4:    e1a00003     mov    r0, r3 2c8:    ebffffe9     bl    274 <test_so-0x3c>...... 354:    0000009c     .word    0x0000009c 358:    000000b4     .word    0x000000b4 35c:    000000b0     .word    0x000000b0 360:    00000050     .word    0x00000050

在程序区造一个表,把它叫look-aside表吧, 就是手边的表。

表中存放数据地址,这个地址与当前pc值相加才是真实数据地址。
pc值2c0+9c = 36c 正好是”hello android”的的地址, 36c 位于rodata区

Contents of section .rodata: 0364 2d2d2d2d 2d2d2d2d 2d2d2068 656c6c6f  ---------- hello 0374 20616e64 726f6964 202d2d2d 2d2d2d2d   android ------- 0384 2d2d2d2d 2d000000 312e7478 74000000  -----...1.txt... 0394 72740000 6572726f 72206f70 656e2031  rt..error open 1 03a4 2e747874 00000000                    .txt....        

4. 如何访问局部变量?

局部变量是保存在堆栈中的,函数退出即丢弃。

例如:        memset(buffer,0,sizeof(buffer)); 2f8:    e24b3f42     sub    r3, fp, #264    ; 0x108 2fc:    e1a00003     mov    r0, r3 300:    e3a01000     mov    r1, #0 304:    e3a02c01     mov    r2, #256    ; 0x100 308:    ebffffdf     bl    28c <test_so-0x24>

通过frame pointer 可以获得局部变量的地址。

5. 如何调用动态链接函数, 找到函数入口地址。

还以printf 为例, printf 是c 库函数, 编译期并不知道其地址。

 看下面代码,关注bl 274 2bc:    e59f3090     ldr    r3, [pc, #144]    ; 354 <test_so+0xa4> 2c0:    e08f3003     add    r3, pc, r3 2c4:    e1a00003     mov    r0, r3 2c8:    ebffffe9     bl    274 <test_so-0x3c>....

274 处是另外一个表项,该表名称叫plt (procedure leakage table)
翻译为过程连接表

00000260 <.plt>: 260:    e52de004     .word    0xe52de004 264:    e59fe004     .word    0xe59fe004 268:    e08fe00e     .word    0xe08fe00e 26c:    e5bef008     .word    0xe5bef008 270:    000011d4     .word    0x000011d4 274:    e28fc600     .word    0xe28fc600 278:    e28cca01     .word    0xe28cca01 27c:    e5bcf1d4     .word    0xe5bcf1d4 280:    e28fc600     .word    0xe28fc600 284:    e28cca01     .word    0xe28cca01 288:    e5bcf1cc     .word    0xe5bcf1cc

这是小微代码区,用以完成向库函数跳转过程。 看274 对应的代码。(ida 中显示如下:)

.plt:00000274 ; =============== S U B R O U T I N E =======================================.plt:00000274.plt:00000274 ; Attributes: thunk.plt:00000274.plt:00000274 ; int puts(const char *s).plt:00000274 puts                                    ; CODE XREF: test_so+18p.plt:00000274                                         ; test_so+7Cp ....plt:00000274                 ADR             R12, 0x27C.plt:00000278                 ADD             R12, R12, #0x1000.plt:0000027C                 LDR             PC, [R12,#(puts_ptr - 0x127C)]! ; __imp_puts.plt:0000027C ; End of function puts

274处是一个三条指令组成的小代码区。
它首先取到一个立即数,以它为基址从一个内存中取数据, 转去执行。

这个内存地址在.got 表中.(global offset table). 显然,等价于pe 格式文件的导入地址表。
可以感受到,取到的地址就是动态连接库的函数地址。这个地址要由加载器把数值填充好。

extern:00001468 ; Segment type: Externsextern:00001468 ; int puts(const char *s)extern:00001468                 IMPORT __imp_puts       ; CODE XREF: puts+8jextern:00001468                                         ; DATA XREF: .got:puts_ptro

6.总结一下,动态跳转的过程是:

  1. 加载器把外部so文件的调用函数地址都填充好了, 这个导入地址表叫.got 全局偏移表。
  2. 有一个plt过程连接表,属于微代码区,用于从.got表中取得数据,并跳转执行。

7.扩展知识:

可重定位代码(windows->dll) 和 位置无关代码(linux->so)

1.可重定位代码:(windows)

生成动态库时假定它被加载在地址 0 处。加载时它会被加载到一个地址(base),
这时要根据代码重定位(relocation)信息,对代码进行定址,so 才能正确寻址。

缺点: 不同的进程会把so加载到不同的地址, 而这些地址是不同的,重定位后的代码也是不同的, 所以这些代码没有办法共享。内存中有多份。 这样失掉了共享库的优势,跟不共享没多少差别。
除非进程能把共享库加载到同一个地址(但这个要求是过分的).

2.位置无关代码:(linux)

linux so文件使用 -fPIC 来生成位置无关代码。这些代码可以被加载到内存的任何位置都可以运行。

怎样做到?

不管是程序地址还是数据地址,都是通过pc值加上一个偏移量来获得,就实现了位置无关。

例如访问外部函数,将外部函数地址全部放入.got table, 通过 [pc+offset] 获取。

优点:
虽然不同的进程会把so 映射到不同的地址空间,但操作系统将把它们映射到相同的物理地址, 节省了代码空间。

缺点:
代码执行效率上有一点损失。 但是,没有了重定位,加载也变快了。

8. 补充

1.arm thumb 编译及反汇编

arm thumb gcc 编译: 加上-mthumb 选项, 你可以用-S 生成汇编码arm thumb 反汇编objdump -d -M force-thumb xxx.so

2.b.n 是什么意思?

n 是正数, N 是负数, 在标志寄存器中CPSR 中占一位。运算结果是正数数跳转

要用ida-pro objdump 仅供参考,后者往往分不清arm,thumb 指令,只能强制一种。

3. 流水线与pc

add pc 指令由于有流水线需要额外添加2条指令。例如:4020D856 LDR             R4, =(26284)4020D858 ADD             R4, PC         ;4020d858+26284+4=40233ae04020D85A LRD             r3, [R4,4]

本来:
4020d858+26284 = 40233adc, 还需要再加两条指令(流水线)才构成40233ae0 赋值给r4

4. cmp 指令与 tst 指令,

ld r3, #1cmp r3, #1(status = src - dst)

两者相等,置位z标志
ld r3, #1
tst r3, #1
(status = src & dst)
相与不为0, 不置位z标志,即判定结果将沿着not-equ方向前进.
对应于c语言的相等判定和相与操作不为0判定

9. 添加几个例子

1.流程控制例:

  1. 能够分辨if-elseif-else 及其嵌套结构。
  2. 能够分辨switch-case 分支
  3. 能够分辨while循环及for循环
  while (customer->id != 0)    87fc:    ea000004     b    8814 <main+0x48>    {        print_customer(customer);    8800:    e51b0014     ldr    r0, [fp, #-20]    8804:    ebffff39     bl    84f0 <print_customer>        customer++;    8808:    e51b3014     ldr    r3, [fp, #-20]    880c:    e2833028     add    r3, r3, #40    ; 0x28    8810:    e50b3014     str    r3, [fp, #-20]    // customer->id != 0    8814:    e51b3014     ldr    r3, [fp, #-20]    8818:    e5933000     ldr    r3, [r3]    881c:    e3530000     cmp    r3, #0    8820:    1afffff6     bne    8800 <main+0x34>    }

2.结构变量例子

能够分辨结构变量,结构变量数组。 结构变量的成员变量

// insert our productsproducts[0].id = 1;8834:    e51b3018     ldr    r3, [fp, #-24]8838:    e3a02001     mov    r2, #1883c:    e5832000     str    r2, [r3]products[0].category = 0;8840:    e51b3018     ldr    r3, [fp, #-24]8844:    e3a02000     mov    r2, #08848:    e5832004     str    r2, [r3, #4]//注释: r3 是一个指针, 也许我们一开始翻译为 p->d0=1, p->d1=0//指针也可以用解引用翻译为引用. (*p).d0=1,(*p).d1=0;//...???products[0].p.book = ida_book;884c:    e51b2018     ldr    r2, [fp, #-24]8850:    e59f3234     ldr    r3, [pc, #564]    ; 8a8c <main+0x2c0>8854:    e7943003     ldr    r3, [r4, r3]8858:    e2821008     add    r1, r2, #8885c:    e1a02003     mov    r2, r38860:    e3a03080     mov    r3, #128    ; 0x808864:    e1a00001     mov    r0, r18868:    e1a01002     mov    r1, r2886c:    e1a02003     mov    r2, r38870:    ebfffeb3     bl    8344 <_start-0x1c>  ; 这是微码区copy, 因为ida_book 是256个字符数组// 注释:struct book_t ida_book = { "IDA QuickStart Guide" };struct book_t {    char title[128]; // an ASCII string};products[1].id = 2;8874:    e51b3018     ldr    r3, [fp, #-24]8878:    e2833088     add    r3, r3, #136    ; 0x88887c:    e3a02002     mov    r2, #28880:    e5832000     str    r2, [r3]products[1].category = SOFTWARE;8884:    e51b3018     ldr    r3, [fp, #-24]8888:    e2833088     add    r3, r3, #136    ; 0x88888c:    e3a02001     mov    r2, #18890:    e5832004     str    r2, [r3, #4]products[1].p.software = softwares.softs[0]; // we insert softwares from our variable length structure8894:    e51b3018     ldr    r3, [fp, #-24]8898:    e2832088     add    r2, r3, #136    ; 0x88889c:    e59f31ec     ldr    r3, [pc, #492]    ; 8a90 <main+0x2c4>88a0:    e7943003     ldr    r3, [r4, r3]88a4:    e282c008     add    ip, r2, #888a8:    e283e004     add    lr, r3, #488ac:    e8be000f     ldm    lr!, {r0, r1, r2, r3}88b0:    e8ac000f     stmia    ip!, {r0, r1, r2, r3}88b4:    e8be000f     ldm    lr!, {r0, r1, r2, r3}88b8:    e8ac000f     stmia    ip!, {r0, r1, r2, r3}88bc:    e59e3000     ldr    r3, [lr]88c0:    e58c3000     str    r3, [ip]

3.指针例子

能够分辨数据与指针,有很多函数调用用的就是指针
用指针访问结构中的成员变量
指针解耦合成为变量

看一下product->category 的汇编代码
product 是一个指针,
category 对应一个偏移量
取值对应解耦合。

bool print_product(struct product_t *product)....`if (!check_product(product->category))8734:    e51b3008     ldr    r3, [fp, #-8]8738:    e5933004     ldr    r3, [r3, #4]873c:    e1a00003     mov    r0, r38740:    ebffff56     bl    84a0 <check_product>

4.全局与局部变量例子

能够分辨全局变量和局部变量, 全局变量是PIC的。

struct customer_t *customer = gCustomers;87f0:    e59f3290     ldr    r3, [pc, #656]    ; 8a88 <main+0x2bc> // 从look-aside 表中取到偏移87f4:    e7943003     ldr    r3, [r4, r3]      // r4 是全局变量表参考地址, 从里面拿到gCustomers87f8:    e50b3014     str    r3, [fp, #-20]  // 存储到customer 局部变量......8a80:    00001648     .word    0x000016488a84:    0000037c     .word    0x0000037c8a88:    fffffff4     .word    0xfffffff48a8c:    fffffff8     .word    0xfffffff88a90:    fffffffc     .word    0xfffffffc8a94:    00000204     .word    0x0000020j