最近在学习汇编做MOOC和练习题的时候碰到了几道比较难的题,觉得可以总结一下。
栈溢出和大小端
有如下的C代码以及对应的反汇编出来的汇编代码(x86-32体系结构):
- 当strcpy调用完成返回到foo过程时,buf[0]、buf[1]、buf[2]的值分别是多少?
- 在执行0x0804850d的ret指令前(popl后),ebp的值是多少?
-
上述ret指令执行后,eip的值是多少?用32位16进制表示,注意大小端。e.g. 0x00000000
字符的十六进制转换表已给出
通过分析可以画出调用strcpy之前完整的栈(具体分析过程略)如下表(每个格子代表4个字节,address向下递减)
内容 | 指向该位置的指针 |
---|---|
callfoo过程保存的%ebp | |
empty | |
empty | |
empty | |
empty | |
empty | |
string address (0x0804859c) | |
foo过程的返回地址 (0x08048523) | |
foo过程保存的%ebp | %ebp |
buf | |
empty | |
empty | |
empty | |
empty | |
empty | |
empty | |
empty | |
string address (strcpy的第二个参数) | |
%ebp - 4 (buf) |
可以看出,传递给strcpy的buf指针就是%ebp-4,在复制了字符串"abcdefghi"之后,会发生溢出,破坏栈中保存的%ebp和返回地址。但现在的问题是,在考虑大小端之后,栈中的实际内容到底应该是什么样子的呢?
由于X86的字节序为小端(“低对低,高对高”),可以画出从buf指针开始向上到string address位置中实际的保存内容(一个格子代表一个字节,地址从上往下递减):
描述 | 内容 |
---|---|
string address (高) | 08 |
04 | |
85 | |
string address (低) | 9c |
foo的返回地址 (高) | 08 |
04 | |
85 | |
foo的返回地址 (低) | 23 |
保存的%ebp (高) | ?? |
?? | |
?? | |
保存的%ebp (低) | ?? |
buf[0] (高) | ?? |
?? | |
?? | |
buf[0] (低) | ?? |
在向以buf开头的地址中写入字符串"abcdefghi\0"(转换成16进制,就是0x61626364656667686900)时,由于char类型的大小只有一个字节,大小端对它来说是无所谓的,只要从低地址向高地址覆写就可以。于是,我们得到了修改过的栈帧:
描述 | 内容 |
---|---|
string address (高) | 08 |
04 | |
85 | |
string address (低) | 9c |
foo的返回地址 (buf[2]) (高) | 08 |
04 | |
|
|
foo的返回地址 (buf[2]) (低) |
|
保存的%ebp (buf[1]) (高) |
|
|
|
|
|
保存的%ebp (buf[1]) (低) |
|
buf[0] (高) |
|
|
|
|
|
buf[0] (低) |
|
而寄存器会以小端模式来解释内存中的内容,因此可得,buf[0] = 0x64636261,buf[1] = 0x68676665,buf[2] = 0x08040069;popl后得到的%ebp为0x68676665,执行ret后%eip的值(也就是要返回到什么地址)为0x08040069。
本题来自学堂在线“汇编语言程序设计”,参考了关于x86系统中“大小端”在C++中的应用实例分析和函数调用的地址问题。其实,做bufbomb的时候我对char和寄存器之间字节顺序的问题想得还挺明白的,不知怎的现在就忘了……
网友评论