逆向一个最简单的函数,返回预定常亮应该够简单了。
C/C++代码:
int f()
{
return 123;
};
0x01 x86
开启优化功能后,GCC编译器的汇编代码如下:
f:
mov eax,123
ret
这个函数只是两条指令构成:第一条指令把数值123存放到eax寄存器中;再根据函数调用约定,后面一条指令会把eax的值当做返回值传递给调用者主函数,调用者(Caller)函数会从eax寄存器中取值把它当做返回结果。
0x02 ARM
ARM模式的情况:
f PROC
MOV r0,#0x7b;123
BX lr
ENDP
ARM程序使用r0寄存器传递函数的返回值,所以指令把数值123赋值给r0。
ARM程序使用LR寄存器存储函数结束后的返回地址(Return Address);x86程序使用栈结构存放返回地址。可见,BX LR指令的作用是跳转到返回地址,返回到调用者函数,然后继续执行调用体caller的后续指令。
x86和ARM指令集中的mov是复制的意思,而不是移动。
0x03 MIPS
在mips指令中,寄存器有两种命名方式,一种是以数字命名($0-V0-$A0),在GCC生成的汇编指令中,寄存器都采用数字方式命名。
j $31
li $2,123
IDA会显示寄存器的伪名称
jr $ra
li $v0,0x7B
根据伪名称和寄存器数字编号可以知道,存储函数返回值的寄存器都是$V0),此处LI指令的意思是Load Immediate。
J和JR指令都是跳转指令,他们把执行流程递交给调用者函数,转到¥31寄存器中的地址上,这个寄存器相当于ARM平台上的LR寄存器。
为什么赋值指令LI和跳转指令J/JR的位置会反过来,这属于精简指令集的特性之一,转移指令延迟槽现象。简单来说就是,不管跳转发生与否,位于跳转指令后面的一条指令总是被先于跳转指令执行。
总之,转移指令后面的这条赋值指令是先于转移指令执行的。