arm 反汇编

来源:互联网 发布:多多返利淘宝客 编辑:程序博客网 时间:2024/05/18 22:42
****************************************
linux 下动态库函数调用反汇编问题。
author: hjjdebug
date:   2016年 09月 07日 星期三 14:41:49 CST
****************************************
----------------------------------------
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: Externs
extern:00001468 ; int puts(const char *s)
extern:00001468                 IMPORT __imp_puts       ; CODE XREF: puts+8j
extern:00001468                                         ; DATA XREF: .got:puts_ptro


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


----------------------------------------
扩展知识:
----------------------------------------
可重定位代码(windows->dll) 和 位置无关代码(linux->so)
可重定位代码:(windows)
生成动态库时假定它被加载在地址 0 处。加载时它会被加载到一个地址(base),
这时要根据代码重定位(relocation)信息,对代码进行定址,so 才能正确寻址。
缺点: 不同的进程会把so加载到不同的地址, 而这些地址是不同的,重定位后的代码也是不同的,
所以这些代码没有办法共享。内存中有多份。 这样失掉了共享库的优势,跟不共享没多少差别。
除非进程能把共享库加载到同一个地址(但这个要求是过分的).

位置无关代码:(linux)
linux so文件使用 -fPIC 来生成位置无关代码。这些代码可以被加载到内存的任何位置都可以运行。
怎样做到?
不管是程序地址还是数据地址,都是通过pc值加上一个偏移量来获得,就实现了位置无关。
例如访问外部函数,将外部函数地址全部放入.got table, 通过 [pc+offset] 获取。
优点: 虽然不同的进程会把so 映射到不同的地址空间,但操作系统将把它们映射到相同的物理地址,
节省了代码空间。
缺点: 代码执行效率上有一点损失。 但是,没有了重定位,加载也变快了。

****************************************

arm thumb gcc 编译: 加上-mthumb 选项, 你可以用-S 生成汇编码

arm thumb 反汇编
objdump -d -M force-thumb xxx.so
****************************************
b.n 是什么意思?
n 是正数, N 是负数, 在标志寄存器中CPSR 中占一位。
运算结果是正数数跳转
要用ida-pro objdump 仅供参考,后者往往分不清arm,thumb 指令,只能强制一种。
----------------------------------------
add pc 指令由于有流水线需要额外添加2条指令。
----------------------------------------
例如:
4020D856 LDR             R4, =(26284)
4020D858 ADD             R4, PC         ;4020d858+26284+4=40233ae0
4020D85A LRD             r3, [R4,4]
本来:
4020d858+26284 = 40233adc, 还需要再加两条指令(流水线)才构成40233ae0 赋值给r4

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


----------------------------------------
a.流程控制:
----------------------------------------
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>
    }

----------------------------------------
b.结构变量
----------------------------------------
能够分辨结构变量,结构变量数组。
结构变量的成员变量
    // insert our products
    products[0].id = 1;
    8834:    e51b3018     ldr    r3, [fp, #-24]
    8838:    e3a02001     mov    r2, #1
    883c:    e5832000     str    r2, [r3]
    products[0].category = 0;
    8840:    e51b3018     ldr    r3, [fp, #-24]
    8844:    e3a02000     mov    r2, #0
    8848:    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, #8
    885c:    e1a02003     mov    r2, r3
    8860:    e3a03080     mov    r3, #128    ; 0x80
    8864:    e1a00001     mov    r0, r1
    8868:    e1a01002     mov    r1, r2
    886c:    e1a02003     mov    r2, r3
    8870:    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    ; 0x88
    887c:    e3a02002     mov    r2, #2
    8880:    e5832000     str    r2, [r3]
    products[1].category = SOFTWARE;
    8884:    e51b3018     ldr    r3, [fp, #-24]
    8888:    e2833088     add    r3, r3, #136    ; 0x88
    888c:    e3a02001     mov    r2, #1
    8890:    e5832004     str    r2, [r3, #4]
    products[1].p.software = softwares.softs[0]; // we insert softwares from our variable length structure
    8894:    e51b3018     ldr    r3, [fp, #-24]
    8898:    e2832088     add    r2, r3, #136    ; 0x88
    889c:    e59f31ec     ldr    r3, [pc, #492]    ; 8a90 <main+0x2c4>
    88a0:    e7943003     ldr    r3, [r4, r3]
    88a4:    e282c008     add    ip, r2, #8
    88a8:    e283e004     add    lr, r3, #4
    88ac:    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]

----------------------------------------
c.指针
----------------------------------------
能够分辨数据与指针,有很多函数调用需要的就是指针
用指针访问结构中的成员变量
指针解耦合成为变量
    
看一下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, r3
8740:    ebffff56     bl    84a0 <check_product>

d.全局与局部变量
能够分辨全局变量和局部变量, 全局变量是PIC的。
struct customer_t *customer = gCustomers;
87f0:    e59f3290     ldr    r3, [pc, #656]    ; 8a88 <main+0x2bc> // 从look-aside 表中取到偏移
87f4:    e7943003     ldr    r3, [r4, r3]      // r4 是全局变量表参考地址, 从里面拿到gCustomers
87f8:    e50b3014     str    r3, [fp, #-20]  // 存储到customer 局部变量
......
8a80:    00001648     .word    0x00001648
8a84:    0000037c     .word    0x0000037c
8a88:    fffffff4     .word    0xfffffff4
8a8c:    fffffff8     .word    0xfffffff8
8a90:    fffffffc     .word    0xfffffffc
8a94:    00000204     .word    0x0000020j
0 0
原创粉丝点击