好的,这将是一个很长的问题。我试图理解“缓冲区溢出”是如何工作的。我正在阅读aleph 1的Smashing the stack for fun and profit,并刚刚得到了以下代码的反汇编:
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
使用GCC的-S
标志进行反汇编,可以得到:
.file "example1.c"
.text
.globl function
.type function, @function
function:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $48, %rsp
movl %edi, -36(%rbp)
movl %esi, -40(%rbp)
movl %edx, -44(%rbp)
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movq -8(%rbp), %rax
xorq %fs:40, %rax
je .L2
call __stack_chk_fail
.L2:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size function, .-function
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $3, %edx
movl $2, %esi
movl $1, %edi
call function
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
X1 m1n1x指令没有出现在Aleph 1的论文中,我猜当时没有使用它们。我读过this question on SO,我知道GCC使用它们进行异常处理。我也读过another question on SO,我知道.LFB0、.LFE0、.LFE1和.LFB1是标签,但我有以下疑问:
1.我知道.cfi指令是用于异常处理的,但是我不明白它们的意思。我在这里看到了一些定义,如:
.cfi_def_cfa寄存器,偏移量
.cfi_def_cfa将计算CFA的规则定义为:从寄存器中获取地址并向其添加偏移量。
然而,如果你看一下我上面放的反汇编 *,你不会发现任何寄存器名称 *(如EAX、EBX等),您会在那里找到一个数字(我通常会找到“6”),但我不知道这怎么会是一个寄存器。特别是,有人能解释一下.cfi_def_cfa_offset 16
、.cfi_offset 6, -16
、.cfi_def_cfa_register 6
和.cfi_def_cfa 7, 8
的含义吗?还有,CFA
是什么意思?我问这个问题是因为大多数书籍/论文中的过程序言都是这样的:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
但是,现在我认为现代计算机中的程序prolog如下:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $48, %rsp
最初我认为使用CFI指令而不是sub
助记符来设置偏移量,但事实并非如此; sub
命令仍在使用。
1.我知道每个过程都有标签。但是,为什么在一个过程中有多个嵌套标签?在我的例子中,main有.LFB1和.LFE2标签。为什么需要多个标签?类似地,function
过程有.LFB0、.L2和.LFE0标签
1.这两个过程的最后3行似乎是用于一些内务处理功能(告诉程序的大小,也许?),但我不知道他们是什么意思。谁能解释一下他们的意思和他们的用途?
编辑:
(再增加一个问题)
- CFI指令是否占用空间?因为在过程“function”中,每个int参数占用4个字节,而参数个数为3,所以所有参数占用内存12个字节。接下来,第一个
char
数组占用8个字节(将5个字节向上舍入为8个字节),下一个char
数组占用12个字节(将10个字节四舍五入为12个字节),因此整个char
数组需要20个字节。所有这些加起来,参数和局部变量只需要12+20=32个字节。
但是在函数过程中,编译器要减去48个字节来存储值,为什么?
3条答案
按热度按时间vhipe2zx1#
CFI代表 call frame information。它是编译器描述函数中发生的事情的方式。它可以被调试器用来呈现调用栈,被链接器用来合成异常表,用于栈深度分析和其他类似的事情。
实际上,它描述了资源(如处理器寄存器)的存储位置以及返回地址。
CFA代表 call frame address,意思是调用函数的堆栈指针位置的地址。这是获取堆栈上下一个帧的信息所必需的。
mzillmmw2#
Lindy Dancer回答了什么
cfi and cfa means
(call frame information
)和(call frame address
).L<num>
表示按照Google中各种花絮的标签,以x64 GCC命名,所有标签均以.L
开始,以a numeral
结束,因此.L1 , .L2 , .L....infinity
为标签根据Google和一些早期的
SO
答案,BF<num>
表示函数开始,EF<num>
表示FUNCTION-END
所以
.LBF0 , .LBF1 . LBF.....infinity
和.LFE0 ,......., .LFE....infinity
表示每个函数中的函数开始和函数结束,编译器可能需要这些函数来处理一些内部需求,因此,除非非常需要深入研究编译器内部,否则此时应该忽略它们
另一个标签
.L2
用于处理函数中的分支指令je而且每个编译器都将对参数和局部变量的访问对齐并填充到某个边界
我不能确定,但是我认为GCC的x64默认对齐是16字节,所以如果您请求一个奇怪的保留,如
char foo[5]或
字节空白[10]
即使对于
x86
,索引5 and 10
也不对齐对于5个
x86 compiler will assign
8个字节s and for 10 16 bytes
像wise
x64 gcc might assign 16 bytes
一样为您的每个请求您实际上不应该担心编译器为什么要这样做
当你试图理解汇编的逻辑时,只关注地址
如果编译器决定它是
will put x at rbp +/- X
,它将在该变量的作用域或生命周期内是also access it at the same location
gab6jxml3#
48是跳过参数和局部变量的。5字节数组在8字节边界上对齐,10字节数组在16字节边界上对齐。每个参数占用8字节,因此3*8的参数加上8 + 16的局部变量得到24+24或48。你可以在gdb中查看它,只需询问每个参数的地址即可。