****************************************
linux 下动态库函数调用反汇编问题。
author: hjjdebug
date: 2016年 09月 07日 星期三 14:41:49 CST
****************************************
#include
#include
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 -m32
CC=arm-linux-androideabi-gcc -mthumb
CFLAG= -c -fPIC -g
LDFLAG= -shared
all: hello
hello: 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.asm
clean:
rm hello main.o libtest.so 1.asm *~
以下分析代码为arm 指令, 由arm-Linux-androideabi-gcc 编译
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}
arm 的传参,小于4个的用寄存器, r0,r1,r2,r3. 大于4个用堆栈。 返回值用r0
printf("hello android\n");
2bc: e59f3090 ldr r3, [pc, #144] ; 354
2c0: e08f3003 add r3, pc, r3
2c4: e1a00003 mov r0, r3
2c8: ebffffe9 bl 274
......
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....
局部变量是保存在堆栈中的,函数退出即丢弃。
例如: 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
通过frame pointer 可以获得局部变量的地址。
还以printf 为例, printf 是c 库函数, 编译期并不知道其地址。
看下面代码,关注bl 274
2bc: e59f3090 ldr r3, [pc, #144] ; 354
2c0: e08f3003 add r3, pc, r3
2c4: e1a00003 mov r0, r3
2c8: ebffffe9 bl 274
....
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
可重定位代码(windows->dll) 和 位置无关代码(linux->so)
生成动态库时假定它被加载在地址 0 处。加载时它会被加载到一个地址(base),
这时要根据代码重定位(relocation)信息,对代码进行定址,so 才能正确寻址。
缺点: 不同的进程会把so加载到不同的地址, 而这些地址是不同的,重定位后的代码也是不同的, 所以这些代码没有办法共享。内存中有多份。 这样失掉了共享库的优势,跟不共享没多少差别。
除非进程能把共享库加载到同一个地址(但这个要求是过分的).
linux so文件使用 -fPIC 来生成位置无关代码。这些代码可以被加载到内存的任何位置都可以运行。
怎样做到?
不管是程序地址还是数据地址,都是通过pc值加上一个偏移量来获得,就实现了位置无关。
例如访问外部函数,将外部函数地址全部放入.got table, 通过 [pc+offset] 获取。
优点:
虽然不同的进程会把so 映射到不同的地址空间,但操作系统将把它们映射到相同的物理地址, 节省了代码空间。
缺点:
代码执行效率上有一点损失。 但是,没有了重定位,加载也变快了。
arm thumb gcc 编译: 加上-mthumb 选项, 你可以用-S 生成汇编码
arm thumb 反汇编
objdump -d -M force-thumb xxx.so
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
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判定
while (customer->id != 0)
87fc: ea000004 b 8814
{
print_customer(customer);
8800: e51b0014 ldr r0, [fp, #-20]
8804: ebffff39 bl 84f0
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
}
能够分辨结构变量,结构变量数组。 结构变量的成员变量
// 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
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
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]
能够分辨数据与指针,有很多函数调用用的就是指针
用指针访问结构中的成员变量
指针解耦合成为变量
看一下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
能够分辨全局变量和局部变量, 全局变量是PIC的。
struct customer_t *customer = gCustomers;
87f0: e59f3290 ldr r3, [pc, #656] ; 8a88 // 从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