Gcc中的编译器堆栈保护技术浅析

最近在看ios下的arm汇编代码的时候看到有个stack_chkguard 和stack_chk_fail,记录下,以下代码均来自linux64下gcc编译器编译的二进制文件,代码已经备注的很详细了。

gcc中的编译器选项可以设置是否开启栈保护:-fstack-protector -fstack-protector -fno-stack-protector gcc 4版本中默认已经启用的stack-protector

c源代码如下:

#include <stdio.h>
int main(int argc, char* argv[]){
	int i;
	char a[64];
	i = 0;
	a[0] = 'a';
	return 0;
}
1_nosp 为强制关掉stack_protector的反汇编代码。
(gdb) file 1_nosp
Reading symbols from 1_nosp...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004d6 <+0>:     push   %rbp // 保存栈基址
   0x00000000004004d7 <+1>:     mov    %rsp,%rbp // 设置新的栈指针
   0x00000000004004da <+4>:     mov    %edi,-0x54(%rbp) // argc
   0x00000000004004dd <+7>:     mov    %rsi,-0x60(%rbp) // argv
   0x00000000004004e1 <+11>:    movl   $0x0,-0x4(%rbp) // 设置i的值
   0x00000000004004e8 <+18>:    movb   $0x61,-0x50(%rbp) // 设置a[0] = 'a'
   0x00000000004004ec <+22>:    mov    $0x0,%eax // 设置返回值
   0x00000000004004f1 <+27>:    pop    %rbp  // 弹出栈基址
   0x00000000004004f2 <+28>:    retq
End of assembler dump.
1_sp 为默认开启stack_protector的反汇编代码。
(gdb) file 1_sp
Reading symbols from 1_sp...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x0000000000400546 <+0>:     push   %rbp // 保存栈基址
   0x0000000000400547 <+1>:     mov    %rsp,%rbp // 设置新的栈指针
   0x000000000040054a <+4>:     sub    $0x70,%rsp // 开辟新的栈空间
   0x000000000040054e <+8>:     mov    %edi,-0x64(%rbp) // argc
   0x0000000000400551 <+11>:    mov    %rsi,-0x70(%rbp) // argv
   0x0000000000400555 <+15>:    mov    %fs:0x28,%rax // canary 值
   0x000000000040055e <+24>:    mov    %rax,-0x8(%rbp) // 这里是canary
   0x0000000000400562 <+28>:    xor    %eax,%eax // 清空eax
   0x0000000000400564 <+30>:    movl   $0x0,-0x54(%rbp) // 设置i的值
   0x000000000040056b <+37>:    movb   $0x61,-0x50(%rbp) // 设置a[0] = 'a'
   0x000000000040056f <+41>:    mov    $0x0,%eax // 设置返回值
   0x0000000000400574 <+46>:    mov    -0x8(%rbp),%rdx //
   0x0000000000400578 <+50>:    xor    %fs:0x28,%rdx // 检测canary值
   0x0000000000400581 <+59>:    je     0x400588 <main+66> // 如果为0则直接返回 否则调到保护代码区。
   0x0000000000400583 <+61>:    callq  0x400420 <__stack_chk_fail@plt>
   0x0000000000400588 <+66>:    leaveq
   0x0000000000400589 <+67>:    retq
End of assembler dump.
(gdb) disas __stack_chk_fail
Dump of assembler code for function __stack_chk_fail@plt:
   0x0000000000400420 <+0>:     jmpq   *0x200bf2(%rip)        # 0x601018
   0x0000000000400426 <+6>:     pushq  $0x0
   0x000000000040042b <+11>:    jmpq   0x400410
End of assembler dump.

等于保存了两份一份在%fs:0x28处,另外一份在-0x8(%rbp)处,最终检测canary值是否被修改

当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。xor %fs:0x28,%rdx,如果没有被修改直接返回,如果被修改了就跳到_stack_chk_fail中去。

linux64位汇编参数传递方式:参数个数大于 7 个的时候 H(a, b, c, d, e, f, g, h); a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9 h->8(%esp) g->(%esp) call H

最后的最后,欢迎大家与我多多交流,vx号:nicholas_mcc

多谢大神分享,啥时候分享一下iPhone arm_64汇编?

其实都差不多。以后写的话我还会分享出来的,只不过最近时间比较紧。杂七杂八事情多。

理论上一样的,但是在看汇编的时候,细节还是有很多不一样的,所以期待你arm_64汇编的精彩分享。不需要将的很大,只需要把一个问题用简单的例子将清楚,就了不起了。 :grin: