标 题:
【原创】原生程序初次逆向之ARM与X86相关知识对比
作 者: Xbalien
时 间: 2013-07-29,15:52:11
链 接: http://bbs.pediy.com/showthread.php?t=176283
小弟近来开始学习原生逆向,并对其中一些基础知识做下总结(大神勿喷,出错请指正)...
Android的原生程序运行于Linux内核上,并且处理器基于ARM,因此对于NDK的逆向,一些设计汇编知识的安全技术(注入,HOOK,劫持)等就需要了解有关知识。因此有必要学习一些与ARM相关的知识,下面主要介绍我们必须要了解的知识,具体指令等更加详细的知识参考相关文档,总结目录如下:
寄存器比较之ARM VS 80X86
1.80X86
寄存器有以下规则:
(1)数据寄存器(EAX、EBX、ECX、EDX):数据寄存器主要用来保存操作数和运算结果等信息。但其也有一些约定俗成的特别作用。EAX用于存放函数返回值;EBX常用作计算存储器地址时的基址寄存器;ECX用作计数器使用,EDX一些计算中的默认参与运算寄存器。
(2)变址寄存器(ESI、EDI):它们主要用于存放存储单元在段内的偏移量。
(3)指针寄存器(EBP、ESP):ESP常用作堆栈指针,指向栈顶维护堆栈变化;EBP用作栈基地址,主要用于维护该函数栈帧,可直接存取堆栈中的数据。
(4)指令指针(EIP):存放下次将要执行的指令在代码段的偏移量。
2.ARM(用户模式)
寄存器的使用必须满足下面的规则:
(1)函数间通过寄存器R0~R3来传递参数,低于32位的函数返回值存于R0。
(2)在函数中,使用寄存器R4~R11来保存局部变量。函数进入时必须保存所用到的局部变量寄存器的值,在返回前必须恢复这些寄存器的值;对于函数中没有用到的寄存器则不必进行这些操作。在Thumb中,通常只能使用寄存器 R4~R7来保存局部变量。
(3)寄存器R12 用作函数间的scratch 寄存器,记作IP
(4)寄存器R13 用作数据栈指针,记作SP。函数调用前后必须保持堆栈平衡。
(5)寄存器R14 称为连接寄存器,记作LR。它用作保存函数的返回地址。如果在函数中保存了返回地址,寄存器R14 则可以用作其他用途。
(6)寄存器R15是程序计数器,记作PC。
函数调用比较之ARM VS 80X86
函数调用样例:
1.80X86
函数调用主要是使用CALL指令触发,函数结束并由RET/RETN返回到调用该函数时的下一条指令。在函数调用执行过程中,大部分通过栈来完成,包括参数的传递、局部变量存储、函数返回地址等。函数调用函数栈变化如下(常见调用方式):
|…… |<-------- [ESP](分配完局部变量后)
|…… |
|局部变量2 |
|局部变量1 |
|上层[EBP] |<-------- [EBP](栈帧)
|返回地址 |
|参数1 |
|参数2 |
|参数3 |
+|栈底方向,高地址|+<-------- [ESP](调用前)
整个函数执行过程中,有[EBP]来操作栈中的数据,函数调用结束后,根据栈中的返回地址返回上级函数。
IDA载入,call函数即将被调用的代码如下:
函数参数压栈,并调用call函数
call函数里边的代码:
进入函数后,
push ebp ;先保存上层函数栈帧
sub esp,54h ;栈中开辟空间用于函数过程使用(局部变量等),
mov eax, 0CCCCCCCCh
rep stosd ;将预留空间填充为CC,以便越界中断
之后的mov操作即可看出是在给局部变量赋值。
最后运算结果存储于eax 。
完成函数功能后,函数恢复之前寄存器值,retn返回。
2.ARM
一般采用B系列指令执行函数的跳转执行,其中BL指令将返回地址存储于LR寄存器,BLX指令除了地址存储于LR寄存器,还对要执行的指令进行判断(ARM or Thumb)。
在参数传递时,将所有参数看作是存放在连续的内存单元中的字数据。然后,依次将各字数据传送到寄存器R0~R3中,如果参数多于4个,则通过栈进行存储,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。
局部变量可以存储在寄存器中,也可以有栈来分配。
函数执行完后,若结果32位的整数时,可以通过寄存器返回(一般使用R0)。 64位整数时,可以通过寄存器R0和R1返回,依次类推(不含浮点运算结果)。最后将之前保存的函数地址赋予到PC(可以是LR,也可以是其他寄存器)。函数调用结构图可如下(采用BL跳转):
寄存器:
R0 = 参数1
R1 = 参数2
R2 = 参数3
R3 = 参数4
LR = 返回地址
栈:
|参数5 |
|参数6 |
|参数7 |
+|栈底方向,高地址|+
IDA载入,call函数即将被调用的代码如下:
通过BL进行函数调用,参数分别存放在R0~R3寄存器中。
call函数里边的代码:
结果存在R0中,并通过BX LR跳转会函数被调用的下一条指令,并判断指令类型。
Linux系统调用比较之ARM VS 80X86
1.80X86
在该系列体系中,系统调用号将放入%eax,它的参数则依次放入%ebx,%ecx,%edx,%esi和%edi。然后调用软中断0x80,程序将参数和系统调用号交给内核,内核来完成系统调用的执行比如,在以下的调用:
Write(2,“Hello”,5)的汇编形式大概是这样的
movl $4, %eax
movl $2, %ebx
movl $hello, %ecx
movl $5, %edx
int $0×80
在进行linux进行注入过程中,常采用ptrace函数,对于系统调用的查看,只需要根据函数调用时刻寄存器的值,即可判断当前程序执行的函数功能以及所附带的参数。
2.ARM
在ARM架构上,所有的系统调用都是通过SWI软中断来实现的,指令如下:
SWI{cond} immed_24,具体的调用号存放在寄存器R7中,调用函数的参数和之前提到的传参方式相同。例如:exit(0)
MOV R0,#0
MOV R7,#7
SWI #0
以上为最近学习总结,有错误的地方请大家指正!*转载请注明来自看雪论坛@PEdiy.com
作 者: Xbalien
时 间: 2013-07-29,15:52:11
链 接: http://bbs.pediy.com/showthread.php?t=176283
小弟近来开始学习原生逆向,并对其中一些基础知识做下总结(大神勿喷,出错请指正)...
Android的原生程序运行于Linux内核上,并且处理器基于ARM,因此对于NDK的逆向,一些设计汇编知识的安全技术(注入,HOOK,劫持)等就需要了解有关知识。因此有必要学习一些与ARM相关的知识,下面主要介绍我们必须要了解的知识,具体指令等更加详细的知识参考相关文档,总结目录如下:
- 寄存器比较之ARM VS 80X86
- 函数调用比较之ARM VS 80X86
- Linux系统调用比较之ARM VS 80X86
寄存器比较之ARM VS 80X86
1.80X86
寄存器有以下规则:
(1)数据寄存器(EAX、EBX、ECX、EDX):数据寄存器主要用来保存操作数和运算结果等信息。但其也有一些约定俗成的特别作用。EAX用于存放函数返回值;EBX常用作计算存储器地址时的基址寄存器;ECX用作计数器使用,EDX一些计算中的默认参与运算寄存器。
(2)变址寄存器(ESI、EDI):它们主要用于存放存储单元在段内的偏移量。
(3)指针寄存器(EBP、ESP):ESP常用作堆栈指针,指向栈顶维护堆栈变化;EBP用作栈基地址,主要用于维护该函数栈帧,可直接存取堆栈中的数据。
(4)指令指针(EIP):存放下次将要执行的指令在代码段的偏移量。
2.ARM(用户模式)
寄存器的使用必须满足下面的规则:
(1)函数间通过寄存器R0~R3来传递参数,低于32位的函数返回值存于R0。
(2)在函数中,使用寄存器R4~R11来保存局部变量。函数进入时必须保存所用到的局部变量寄存器的值,在返回前必须恢复这些寄存器的值;对于函数中没有用到的寄存器则不必进行这些操作。在Thumb中,通常只能使用寄存器 R4~R7来保存局部变量。
(3)寄存器R12 用作函数间的scratch 寄存器,记作IP
(4)寄存器R13 用作数据栈指针,记作SP。函数调用前后必须保持堆栈平衡。
(5)寄存器R14 称为连接寄存器,记作LR。它用作保存函数的返回地址。如果在函数中保存了返回地址,寄存器R14 则可以用作其他用途。
(6)寄存器R15是程序计数器,记作PC。
函数调用比较之ARM VS 80X86
函数调用样例:
代码:
#include <stdio.h> int call(int a,int b,int c,int d,int e,int f){ int sum =0; int v1 =1,v2 =2,v3 =3,v4 =4; sum = a+b+c+d+e+f+v1+v2+v3+v4; return sum; } int main(void){ int sum =0; sum =call(1,2,3,4,5,6); printf("sum = %d\n",sum); return 0; }
函数调用主要是使用CALL指令触发,函数结束并由RET/RETN返回到调用该函数时的下一条指令。在函数调用执行过程中,大部分通过栈来完成,包括参数的传递、局部变量存储、函数返回地址等。函数调用函数栈变化如下(常见调用方式):
|…… |<-------- [ESP](分配完局部变量后)
|…… |
|局部变量2 |
|局部变量1 |
|上层[EBP] |<-------- [EBP](栈帧)
|返回地址 |
|参数1 |
|参数2 |
|参数3 |
+|栈底方向,高地址|+<-------- [ESP](调用前)
整个函数执行过程中,有[EBP]来操作栈中的数据,函数调用结束后,根据栈中的返回地址返回上级函数。
IDA载入,call函数即将被调用的代码如下:
代码:
.text:004010BF push 6 .text:004010C1 push 5 .text:004010C3 push 4 .text:004010C5 push 3 .text:004010C7 push 2 .text:004010C9 push 1 .text:004010CB call j_?call@@YAHHHHHHH@Z ;
call函数里边的代码:
代码:
var_14 = dword ptr -14h var_10 = dword ptr -10h var_C = dword ptr -0Ch var_8 = dword ptr -8 var_4 = dword ptr -4 .text:00401020 push ebp .text:00401021 mov ebp, esp .text:00401023 sub esp, 54h .text:00401026 push ebx .text:00401027 push esi .text:00401028 push edi .text:00401029 lea edi, [ebp+var_54] .text:0040102C mov ecx, 15h .text:00401031 mov eax, 0CCCCCCCCh .text:00401036 rep stosd .text:00401038 mov [ebp+var_4], 0 .text:0040103F mov [ebp+var_8], 1 .text:00401046 mov [ebp+var_C], 2 .text:0040104D mov [ebp+var_10], 3 .text:00401054 mov [ebp+var_14], 4 .text:0040105B mov eax, [ebp+arg_0] .text:0040105E add eax, [ebp+arg_4] .text:00401061 add eax, [ebp+arg_8] .text:00401064 add eax, [ebp+arg_C] .text:00401067 add eax, [ebp+arg_10] .text:0040106A add eax, [ebp+arg_14] .text:0040106D add eax, [ebp+var_8] .text:00401070 add eax, [ebp+var_C] .text:00401073 add eax, [ebp+var_10] .text:00401076 add eax, [ebp+var_14] .text:00401079 mov [ebp+var_4], eax .text:0040107C mov eax, [ebp+var_4] .text:0040107F pop edi .text:00401080 pop esi .text:00401081 pop ebx .text:00401082 mov esp, ebp .text:00401084 pop ebp .text:00401085 retn
push ebp ;先保存上层函数栈帧
sub esp,54h ;栈中开辟空间用于函数过程使用(局部变量等),
mov eax, 0CCCCCCCCh
rep stosd ;将预留空间填充为CC,以便越界中断
之后的mov操作即可看出是在给局部变量赋值。
最后运算结果存储于eax 。
完成函数功能后,函数恢复之前寄存器值,retn返回。
2.ARM
一般采用B系列指令执行函数的跳转执行,其中BL指令将返回地址存储于LR寄存器,BLX指令除了地址存储于LR寄存器,还对要执行的指令进行判断(ARM or Thumb)。
在参数传递时,将所有参数看作是存放在连续的内存单元中的字数据。然后,依次将各字数据传送到寄存器R0~R3中,如果参数多于4个,则通过栈进行存储,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。
局部变量可以存储在寄存器中,也可以有栈来分配。
函数执行完后,若结果32位的整数时,可以通过寄存器返回(一般使用R0)。 64位整数时,可以通过寄存器R0和R1返回,依次类推(不含浮点运算结果)。最后将之前保存的函数地址赋予到PC(可以是LR,也可以是其他寄存器)。函数调用结构图可如下(采用BL跳转):
寄存器:
R0 = 参数1
R1 = 参数2
R2 = 参数3
R3 = 参数4
LR = 返回地址
栈:
|参数5 |
|参数6 |
|参数7 |
+|栈底方向,高地址|+
IDA载入,call函数即将被调用的代码如下:
代码:
var_14 = -0x14 var_10 = -0x10 var_8 = -8 .text:0000851C STMFD SP!, {R11,LR} .text:00008520 ADD R11, SP, #4 .text:00008524 SUB SP, SP, #0x10 .text:00008528 MOV R3, #0 .text:0000852C STR R3, [R11,#var_8] .text:00008530 MOV R3, #5 .text:00008534 STR R3, [SP,#0x14+var_14] .text:00008538 MOV R3, #6 .text:0000853C STR R3, [SP,#0x14+var_10] .text:00008540 MOV R0, #1 .text:00008544 MOV R1, #2 .text:00008548 MOV R2, #3 .text:0000854C MOV R3, #4 .text:00008550 BL call
call函数里边的代码:
代码:
…… .text:0000850C MOV R0, R3 .text:00008510 MOV SP, R11 .text:00008514 LDMFD SP!, {R11} .text:00008518 BX LR
Linux系统调用比较之ARM VS 80X86
1.80X86
在该系列体系中,系统调用号将放入%eax,它的参数则依次放入%ebx,%ecx,%edx,%esi和%edi。然后调用软中断0x80,程序将参数和系统调用号交给内核,内核来完成系统调用的执行比如,在以下的调用:
Write(2,“Hello”,5)的汇编形式大概是这样的
movl $4, %eax
movl $2, %ebx
movl $hello, %ecx
movl $5, %edx
int $0×80
在进行linux进行注入过程中,常采用ptrace函数,对于系统调用的查看,只需要根据函数调用时刻寄存器的值,即可判断当前程序执行的函数功能以及所附带的参数。
2.ARM
在ARM架构上,所有的系统调用都是通过SWI软中断来实现的,指令如下:
SWI{cond} immed_24,具体的调用号存放在寄存器R7中,调用函数的参数和之前提到的传参方式相同。例如:exit(0)
MOV R0,#0
MOV R7,#7
SWI #0
以上为最近学习总结,有错误的地方请大家指正!*转载请注明来自看雪论坛@PEdiy.com