出于好奇学习一下,根据实践分析结果,并未根据gcc 源码分析,可能不正确
本案例在 ubuntu 20.04 x86_64 上测试
源码
int oo(char*passwd)
{
int auth = 8;
char buf[8];
strcpy(buf, passwd);
printf("auth=%d\n", auth);
return 0;
}
int overlow(char *passwd)
{
char buf[6];
int auth = 5;
char d[6];
strcpy(buf, passwd);
strcpy(d, passwd);
printf("auth=%d\n", auth);
return 0;
}
int main()
{
overlow("passwordab");
return 0;
}
这是基础测试的最初代码,很明显示这个代码会产生数组溢出,结果就是 auth
的值被修改了。
并且这样的溢出会导致 auth 的值是固定的
分析
先对 oo
函数分析,提取出汇编代码
int oo(char*passwd)
{
1169: f3 0f 1e fa endbr64
116d: 55 push %rbp
116e: 48 89 e5 mov %rsp,%rbp
1171: 48 83 ec 20 sub $0x20,%rsp
1175: 48 89 7d e8 mov %rdi,-0x18(%rbp)
int auth = 8;
1179: c7 45 fc 08 00 00 00 movl $0x8,-0x4(%rbp)
char buf[8];
strcpy(buf, passwd);
1180: 48 8b 55 e8 mov -0x18(%rbp),%rdx
1184: 48 8d 45 f4 lea -0xc(%rbp),%rax
1188: 48 89 d6 mov %rdx,%rsi
118b: 48 89 c7 mov %rax,%rdi
118e: e8 cd fe ff ff callq 1060 <strcpy@plt>
printf("auth=%d\n", auth);
1193: 8b 45 fc mov -0x4(%rbp),%eax
1196: 89 c6 mov %eax,%esi
1198: 48 8d 3d 65 0e 00 00 lea 0xe65(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
119f: b8 00 00 00 00 mov $0x0,%eax
11a4: e8 c7 fe ff ff callq 1070 <printf@plt>
return 0;
11a9: b8 00 00 00 00 mov $0x0,%eax
}
1171 行 rsp-0x20 说明整个栈分配了32字节的空间
1175 将rdi 放到了rbp-0x18 的位置,rdi 是调用之前设置的地址,值是参数内容,而放到的位置是 rbp-24 个字节的位置
1179 将数值8放到 rbp-4的位置,也就是 auth 的位置
1180 1184 是从 rbp-0x18(参数的位置) 取数据放到 rbp-0xc(buf,要存储到的位置)
其中的 rsi rdi 用于传参时的参数位置指定
这里的重点就是这些位置是如何确定的。
大概布局
//
// +--------+
// | | - rbp
// | auth | - rbp-4
// | |
// | ... | - rbp-0xc
// | |
// | | - rbp-0x18
// | |
// +--------+ - rbp-0x20 (rsp)
rbp 在栈顶的位置, rsp 会被设置成当前所指向的栈帧位置,根据资料,rsp 最后要存储返回地址,
rbp-4 是auth的地址,下面 rbp-0xc 是 buf 的位置 (c-4) = 8 是 buf 的长度,这样总结下来 ,栈空间的总长度应该是 (返回地址8+auth4+buf8+参数8=28) 但是 rbp-0x20 是 32字节空间是哪来的?
经过测试,这个栈空间应该是按 16 的整数倍来分配 (这个只是实验得来,并非通过 gcc源码获得,不一定正确)
进一步实验
将 overlow
的汇编放出来
int overlow(char *passwd)
{
11b0: f3 0f 1e fa endbr64
11b4: 55 push %rbp
11b5: 48 89 e5 mov %rsp,%rbp
11b8: 48 83 ec 20 sub $0x20,%rsp
11bc: 48 89 7d e8 mov %rdi,-0x18(%rbp)
char buf[6];
int auth = 5;
11c0: c7 45 fc 05 00 00 00 movl $0x5,-0x4(%rbp)
char d[6];
strcpy(buf, passwd);
11c7: 48 8b 55 e8 mov -0x18(%rbp),%rdx
11cb: 48 8d 45 f6 lea -0xa(%rbp),%rax
11cf: 48 89 d6 mov %rdx,%rsi
11d2: 48 89 c7 mov %rax,%rdi
11d5: e8 86 fe ff ff callq 1060 <strcpy@plt>
strcpy(d, passwd);
11da: 48 8b 55 e8 mov -0x18(%rbp),%rdx
11de: 48 8d 45 f0 lea -0x10(%rbp),%rax
11e2: 48 89 d6 mov %rdx,%rsi
11e5: 48 89 c7 mov %rax,%rdi
11e8: e8 73 fe ff ff callq 1060 <strcpy@plt>
printf("auth=%d\n", auth);
11ed: 8b 45 fc mov -0x4(%rbp),%eax
11f0: 89 c6 mov %eax,%esi
11f2: 48 8d 3d 0b 0e 00 00 lea 0xe0b(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
11f9: b8 00 00 00 00 mov $0x0,%eax
11fe: e8 6d fe ff ff callq 1070 <printf@plt>
return 0;
120a: b8 00 00 00 00 mov $0x0,%eax
进一步测试,故意对参数大小做测试,如上面的代码,8+8+buf6+auth4+d6 = 32 正好是 rsp-0x20,而此时把 buf[6] ⇒ buf[7] ,此时的大小是 33 ,而汇编之后是 rsp-0x30 ,直接加大大小到 48。
同时根据这个现象看到 int 和 char 在一起的时候,int 始终在位置较高处,这个规则还不懂,有机会看看源码学习一下。
arm下的现象
void overlow(char *passwd, char *next)
{
104e4: e92d4800 push {fp, lr}
104e8: e28db004 add fp, sp, #4
104ec: e24dd020 sub sp, sp, #32
104f0: e50b0020 str r0, [fp, #-32] ; 0xffffffe0
104f4: e50b1024 str r1, [fp, #-36] ; 0xffffffdc
int t = 9;
104f8: e3a03009 mov r3, #9
104fc: e50b3008 str r3, [fp, #-8]
char buf[6];
int auth = 5;
10500: e3a03005 mov r3, #5
10504: e50b300c str r3, [fp, #-12]
char d[6];
strcpy(buf, passwd);
10508: e24b3014 sub r3, fp, #20
1050c: e51b1020 ldr r1, [fp, #-32] ; 0xffffffe0
10510: e1a00003 mov r0, r3
10514: ebffff97 bl 10378 <strcpy@plt>
strcpy(d, next);
10518: e24b301c sub r3, fp, #28
1051c: e51b1024 ldr r1, [fp, #-36] ; 0xffffffdc
10520: e1a00003 mov r0, r3
10524: ebffff93 bl 10378 <strcpy@plt>
printf("auth=%d\n", auth);
10528: e51b100c ldr r1, [fp, #-12]
1052c: e3000674 movw r0, #1652 ; 0x674
10530: e3400001 movt r0, #1
10534: ebffff8c bl 1036c <printf@plt>
}
10538: e320f000 nop {0}
1053c: e24bd004 sub sp, fp, #4
10540: e8bd8800 pop {fp, pc}
在 arm 下以 fp 做为栈基址,直接将 fp+4 存储为返回地址,相当于返回地址在高地址处,而x86_64 的返回地址在低地址处
总结
- 没事看看汇编有助于理解代码的原因,对找寻一些困难问题的方法,思路有点帮助
- 栈的入参从右到左,这个很多地方都解释,不通解释规则是不一样的
- int 会始终放在前面
- 栈大小会按 16 的倍数在增加
- 局部变量同类型的按上到下从高地址到低地址逐步增加
- 通过实验进一步理解理论