一、前言
前面一章节主要介绍了逆向方面的基础知识点,并且介绍的都是32位的逆向。
但如今的程序大部分都已经过渡到了64位,所以只学习32位肯定是不够的。
而本章要介绍的便是64位逆向基础内容,当然,这是在前文基础之上的,已经讲解过的知识点不会再重复赘述。
二、寄存器
首先一个不同之处就是寄存器的不同,64位程序的寄存器与32位程序的寄存器相差很大。
前面我们一直看到过:EAX
、EBX
等寄存器的名字,前面的E
实际上就是扩展的意思(extend
),在16位上,就是AX
、BX
等寄存器名字,因为扩展到了32位,所以前面添加了一个E
。
而64位则是将这个E
改为了R
,并将其大小扩展到了64位,此时寄存器的名字就为RAX
、RBX
等等
并且数量还增加了8个(R8-R15
),并且还扩展了8个128位的XMM寄存器。
64位程序中,XMM寄存器经常被用来优化代码。
而64位程序是可以与32位寄存器兼容的:
- RAX:占64位
- EAX:使用RAX的低32位
- AX:使用EAX的低32位
- AL、AH:分别为AX的低8位与高八位
这就是一个寄存器的分布情况,就目前来说,我们一般用到的只有RAX
与EAX
了。
比如当你使用x64的64位调试器时,就可以从右边的寄存器窗口看到所有可用的寄存器,非常之多:
三、函数
对于函数的内容,上一章节已经介绍的非常详细了,所以这里只介绍一些存在的不同之处。
首先是传递参数问题。
注意:此时要将vs项目更改为x64模式,并在属性中关闭优化,具体步骤不再赘述。
由于64位程序的寄存器比32位要多很多,而寄存器的速度又比内存快,所以此时,它只有一种约定:寄存器快速调用约定:
此时我给函数传递了5个参数,但可以看到,前面4个参数都直接使用的寄存器来传递:ecx
、edx
、r8d
、r9d
,只有第五个参数才使用的栈来传递。
传递方向仍然是从右向左,所以最下面的ecx是第一个参数。
之所以这里使用的ecx
而不是rcx
,是由于我们这里是int
类型,只占32位,所以只需要rcx寄存器低32位就能存放了,如果为64位的long long
,就是rcx
了。
这是整数的情况,如果是小数:
那么其用的是xmm0
、xmm1
、xmm2
、xmm3
这四个寄存器,如果还有更多的参数,就使用栈传递。
总结如下:
虽然此时使用的是寄存器传参,但这会导致可用寄存器数量减少,在某些复杂的计算中,可能会存在寄存器数量不够用的情况。
所以一般,调用者依旧会预留出相应的栈空间:
比如我这里,虽然已经使用的是4个寄存器来传递参数,但它依旧预留出了28h
大小的栈空间交给这个函数内部去使用,即40字节,5个8字节,即5个数据的大小(4个参数+1个call指令压入的地址值):
如果进入函数后,你还会看到,它又把这四个参数复制到栈空间来使用,看起来有点多此一举了,但在没有优化的情况下,确实会出现这种情况。
并且还可以看到,即使传递参数的时候只是32位的int
类型,但这里依旧用的每个参数占8位来传递。
如果你将这个生成的64位可执行文件放到IDA中,还可以直接找到入口点函数:
也就是这个start
函数,它就是启动函数,并在这个函数中调用了我们的main
函数:
它基本就把我们整个程序还原的差不多了,是不是非常的强大!
函数参数除了普通参数外,有时候还可能直接传递结构体:
它是直接将结构体t
当作了两个int
给其赋值,也就是第一个红框中的内容,每个数据占4位,即dword
。
而传递参数时,它仅仅只传递了第一个参数的值进入,因此此时它是qword
,即8位,它一次性把两个参数通过一个rcx
寄存器给传递到函数内部去了!