《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.7)

1.7. 带有参数的printf()

示例代码:

#include <stdio.h>
int main()
{
    printf(“a=%d; b=%d; c=%d”, 1, 2, 3);
    return 0;
}

1.7.1. x86

x86: 3个参数

MSVC

参数是以3,2,1的顺序入栈的。因为参数类型是int,是32bits大小,所以一个参数占4byte。除了参数,还要入栈字符串的地址,所以是一共4个阐述,在栈中占据了16byte。所以,我们看看函数调用之后的ADD ESP, X指令,用X除以4就可以得到参数的个数。以上针对cdecl调用约定和32位环境。

注意有的时候编译器会在好几个函数调用完毕之后一起恢复栈。也就是很多个CALL之后才有ADD ESP, X指令,X的大小是此前所有调用函数参数大小总和。

MSVC和OllyDbg

发现printf()执行后ECX和EDX也有变化,说明函数内部机制用了这两个寄存器。如果不执行下一步,我们发现ESP和栈中内容都没有改变。这是因为cdecl调用约定,被调函数不恢复ESP,调用函数需要恢复ESP。继续执行下一条ADD ESP, 16指令,会发现ESP改变了,但栈中内容依然没变。

GCC

用GCC编译后,我们可以看到与MSVC的不同之处。GCC编译出的汇编代码,并没有使用PUSH/POP来将参数入栈,而是先设置好esp的值(一个基址),再使用mov [esp+10h+var_4], 3这样的指令,将参数放在esp加偏移指示的内存地址,实际上就是把值放在了栈中。

GCC和GDB

在printf()处下断点,然后查看esp指向的地址,可以证明该处放的是返回地址。

xchg %ax, %ax等同于nop。

紧跟着返回地址之后存放的就是要打印的字符串。(地址递增)再后面放的就是1,2,3三个参数值。

然后执行到结束。查看寄存器值,printf()返回值放在EAX里,为13(打印字符串长度)。

x64:8个参数

输出1-8这8个数字。加上字符串一共是9个参数。

MSVC

在Win64中,前4个参数放在RCX, RDX, R8, R9这4个寄存器中,其他的放在栈中。入栈的指令并不是PUSH,而是MOV。

在32位中,4byte是一个基本单位。而在64位中,8byte是一个基本单位。

GCC

前6个参数用RDI,RSI,RDX,RCX,R8,R9传递,其他的用栈传递。字符串指针用EDI存储而不是RDI。在printf()调用前会把EAX清零。因为返回值即打印字符个数要放在EAX中。

GCC+GDB

在printf()处断点。可以看到rdi的值就是字符串的地址。栈中第一个元素就是返回地址。然后余下的3个参数放在栈中。

1.7.2. ARM

ARM: 3个参数

前4个参数用R0-R3传递。余下的用栈传递。这种方式与fastcall调用约定以及win64相似。

32位ARM
Non-optimizing Keil (ARM模式)

字符串放在R0中,1,2,3放在R1-R3中。调用printf函数后将R0中的值变为0。

Optimizing Keil (Thumb模式)

与ARM模式的不同就是把开头的STMFD指令换成PUSH,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值