ELF64文件逆向分析知识—[1]64位逆向基础知识

第一次用IDA打开ELF64可执行文件进行分析,有种刚学逆向的错觉,各种之前不认识的寄存器,函数调用完全找不到函数参数是怎么传递的。看来有必要恶补一下x64位CPU的和逆向分析相关的知识。

首先先来科普一下64位CPU的混乱的术语:

术语说明
AMD64AMD研制的64位CPU(直接向下兼容x86).
EM64TIntel研制的兼容AMD64的CPU.
Intel64EM64T的新名字.
IA-64Intel和HP合作开发的64位CPU(可以通过模拟器间接兼容x86).
x86Intel的IA-32、IA-16、IA-8系列的CPU.
x64AMD64&Intel64.

接下来就让我们一起看下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个参数存储到寄存器中传递:

参数整数型浮点数型
1stRCXXMM0
2stRDXXMM1
3stR8XMM2
4stR9XMM2

超过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中,7R8中。

Linux平台

和Windows平台的编译器一样,在Linux下的GCC编译器编译的函数,默认也是采用fastcall调用约定,但参数传递的方式却和Windows平台下截然不同,最多会把8个参数存储到寄存器中传递:

参数整数型浮点数型
1stRDIXMM0
2stRSIXMM1
3stRDXXMM2
4stRCXXMM3
5stR8XMM4
6stR9XMM5
7stXMM6
8stXMM7

这里当参数都是整数时大于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位下的编译器选项中开启优化功能。

让我们看下具体的变化:

  1. 看不到以前的栈帧的身影,现在上来就:
sub rsp,48h
...
add rsp,48h
Ret
  1. 在函数中基本上看不到PUSH/POP指令了,即使是调用子函数时超出4个参数部分也是使用MOV向栈中存入数据(好别扭!)
  2. 多余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更进一步增加反汇编的可阅读行。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值