ELF64文件逆向分析知识—[1]64位逆向基础知识
来源:互联网 发布:淘宝五金店铺名字大全 编辑:程序博客网 时间:2024/05/22 02:11
第一次用IDA打开ELF64可执行文件进行分析,有种刚学逆向的错觉,各种之前不认识的寄存器,函数调用完全找不到函数参数是怎么传递的。看来有必要恶补一下x64位CPU的和逆向分析相关的知识。
首先先来科普一下64位CPU的混乱的术语:
接下来就让我们一起看下x64中新增或变更的知识点(这里只介绍和逆向分析相关的,更详细内容可以参考Intel用户手册)。
64位
内存地址变成64位,当然程序使用的指针也相应的编程64位的指针。所以含有绝对地址(VA)的指令大小比原来增加了4字节。同样,寄存器的大小和栈的基本单元也变成64位。
内存
虚拟内存的实际大小为16TB(内核和用户空间各占8TB)。
通用寄存器
大小扩大到64(8字节),个数增加到18个(新增了R8~R15寄存器)。x64系统下的所有通用寄存器的名称均以字母”R”开头(x86以字母”E”开头),为了向下兼容,支持访问寄存器的8位、16位、32位(eg:AL、AX、EAX)。
**Note:**64位本地模式中不使用段寄存器:CS、DS、ES、SS、FS、GS,他们仅用于向下兼容32位程序。
CALL/JMP指令
仍延续x86相同的指令,eg,CALL XXXXXXXX –> FF15XXXXXXXX,其中XXXXXXXX“绝对地址”指向IAT区域的某个位置。但对地址解析方法不同了。具体解析方式暂时先不深入。
函数调用约定
Windows平台
整数和浮点数参数
32位:cdecl、stdcall、fastcall等几种,但64位统一为一种变形的fastcall。64位的fastcall中最多可以把函数的4个参数存储到寄存器中传递:
超过4个参数,使用栈来传递,传递顺序依照“从右向左”。此外,函数返回时传递的参数过程中所用的栈由调用者清理。看上去64位的fastcall就像32位下的cdcel和fastcall的结合。函数的前4个参数虽然使用寄存器传递,但在栈中仍为这4个参数预留了空间(32个字节)。
Note:当整数和浮点数参数混合出现时,eg:
void func(float a, int b, double c, int d);
a放入XMM0中,b放入RDX,c放入XMM2,d放入R9。
这个存放的顺序很怪异,其实这是严格按照表2.1中整数和浮点数的4个参数一一对应,需要为没用的参数预留空间,eg,c参数没有放到XMM1中,XMM1被预留位置了。
指针参数
指针参数的传递遵循整数参数传递的方式。
结构体参数
结构体参数比较特殊,如果结构体长度小于64bit,则使用整数参数的传递规则。但如果是一个很大的结构体,那么应该还是要在堆栈中申请临时空间的(但ddk没有明说这一点,参考x86的规则应该如此)。
未声明函数调用
func1();func2(){ func1(2, 1.0, 7);}
在这种情况下,func1()的参数表其实不明确,那么参数的传递要怎样进行?这里采用了一个比较保守的规则,就是:整数参数还是按照寄存器映射关系放入对应的寄存器中,浮点数在按照映射关系放入XMM寄存器后,还需要按照整数参数的寄存器映射关系放入整数寄存器中一次,这就是“比较保守的规则”的意思。就现在这个例子而言,结果如下:
2在RCX中,1.0在RDX和XMM1中,7在R8中。
Linux平台
和Windows平台的编译器一样,在Linux下的GCC编译器编译的函数,默认也是采用fastcall调用约定,但参数传递的方式却和Windows平台下截然不同,最多会把8个参数存储到寄存器中传递:
这里当参数都是整数时大于6个参数时使用栈传递,浮点数型参数大于8个时,使用栈传递,传递顺序依照“从右向左”。
Note:
当整数和浮点数参数混合出现时,eg:
void func(float a, int b, double c, int d);
a放在XMM0,b放在RDI,c放在XMM1,d放在RSI。
可以看到Linux的GCC下正浮混合时没有为没用到得参数预留空间。RAX、RCX、RDX、RSI、RDI、R8、R9都是易失寄存器(它们的值经常被改变),所以被调用函数不必恢复它们的值,可以看到上述几个寄存器大多被用于函数传参,值被修改了也无妨。其他的寄存器是非易失寄存器(RBX、RBP、RSP、R10~R15),通常在函数中使用时需要保存原值,并在函数返回时恢复它们的值。
从Windows和Linux的64位函数调用约定不同可以总结出两点:
- 随着x64位引入更多的寄存器后,传递参数也相应的更多使用寄存器。也可以说有钱更任性。
- 两个平台的代码移植问题很不乐观。想了一下好像和逆向分析无关(偷着乐!!)。
栈&栈帧
Windows平台
64位的操作系统中使用的栈与栈帧方式也发生了变化。简言之,栈的大小比函数实际需要的大小要大很多。调用子函数时不在使用PUSH命令来传递参数,而是通过MOV指令操作寄存器与预定的栈(fastcall)来传递。使用VC++创建的x64程序代码几乎看不到PUSH/POP指令(替换成MOV了,我晕!!)。
创建栈帧时也不再使用RBP寄存器,而是直接使用RSP寄存器来实现(这点让我们分析栈帧更加困难,幸亏还有IDA)。之前可以在32位下的编译器选项中开启优化功能。
让我们看下具体的变化:
- 看不到以前的栈帧的身影,现在上来就:
sub rsp,48h...add rsp,48hRet
- 在函数中基本上看不到PUSH/POP指令了,即使是调用子函数时超出4个参数部分也是使用MOV向栈中存入数据(好别扭!)
- 多余4个参数的函数,超出部分的参数使用栈传递,但设置顺序那叫一个乱啊,根本不是按顺序来的。且从第五个参数发现并不是存在当前的栈顶,而是[RSP+20h],也就是前面说的预留的32个字节。
Linux平台
由于Linux平台逆向分析很少,能总结的也不多。由于Linux平台下的GCC比Windows平台的VC使用更多的寄存器(达到6~8个),所以很少看到使用栈传递的情况。这里以一个玩具代码为例(GCC没有开启优化):
int func(int a , int b , int c , int d , int e , int f , int g , int h , int i){ return a + b + c + d + e + f + g + h + i;}int main(int argc, char* argv[]){ int ret; ret = func(1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9); printf("ret : %d" , ret); return 0;}
我们再来看下,反汇编后:
可以看到多余6个参数使用MOV指令向栈内存[RSP+XXXh]位置压入参数,并且看到参数的传递顺序,和Windows一样木有PUSH指令压入参数了。接下来再来看一下栈帧:
这里我编译时使用了“-g”选项,同时保留了符号表和重定位信息(没有使用“-s”),这里很尴尬IDA显示的func函数的调用约定是cdecl,可能64位的调用约定很像32位的cdecl和fastcall的结合体,所以IDA显示为cdecl。也可以看到清晰的栈帧结构:
但有时,可以看到就不那么幸运的看到清晰的栈帧了,比如下面的反汇编代码:
这里我去掉“-g”选项并使用“-s”选项,编译后:
已经找不到func的身影,我们使用插件反编译一下:
现,反编译插件显示的是fastcall调用约定。这可能是IDA还没能跟上编译器的变化,无法做出正确的调用约定匹配。
恶补完x64位的知识后,现在回过头再看下IDA中的汇编代码,顺眼很多了。
接下来,利用IDA的静态库SIG更进一步增加反汇编的可阅读行。
- ELF64文件逆向分析知识—[1]64位逆向基础知识
- ELF64文件逆向分析知识—[0]搭建动态调试环境
- ELF64文件逆向分析知识—[2]制作静态库SIG
- Android逆向分析--so文件
- 【看雪】第一课 逆向分析基础知识
- 逆向分析
- 逆向分析
- 易语言逆向分析1
- 逆向
- 逆向
- 逆向
- 170715 逆向-APK基础知识
- 解密分析基础 第一,二课 逆向分析基础知识【转】
- linux逆向分析之ELF文件详解
- zImage文件提取及逆向分析
- zImage文件提取及逆向分析
- 实验吧 逆向工程 smali文件分析
- 逆向随笔 - 位运算
- oracle set运算符
- H5之12__触摸与单击:基本的事件处理
- Mobile phones POJ1195(二维树状数组模板题)
- X86计算机启动流程分析之BIOS
- 基本概念
- ELF64文件逆向分析知识—[1]64位逆向基础知识
- redis持久化之AOF(Append Only File)及其总结
- 设置双坐标轴(twinx & twiny)
- C语言简单运算
- 数字图像处理matlab基本命令
- C++ vector 迭代器失效原因
- Error D8016 '/ZI' and '/Gy-' command-line options are incompatible
- 找出丑数
- 水仙花数_99乘法表