堆栈分析:
---------------------------------------------------------------------------------------------------------------------------------------------------
父函数
[stack]:BFB2951C dd 8048477h ; 调用sum后的返回地址,call指令添加的
[stack]:BFB29520 dd 0Ah ; ESP main栈顶
[stack]:BFB29524 dd 14h ; "__libc_start_main"
[stack]:BFB29528 dd offset initial
[stack]:BFB2952C dd offset off_D58FF4
[stack]:BFB29530 dd offset __libc_csu_init
[stack]:BFB29534 dd offset _start
[stack]:BFB29538 dd 14h
[stack]:BFB2953C dd 0Ah
[stack]:BFB29540 dd offset __libc_csu_init
[stack]:BFB29544 dd 0
[stack]:BFB29548 db 0C8h ; ;EBP
调用函数栈
+-------------------------+--------------------------------------父函数的EBP
|…… locals |
+-------------------------+
| arg3 | arg1, arg2, arg3
+-------------------------+
| arg2 |
+-------------------------+
| arg1 |
+-------------------------+--------------------------------------- 父函数的ESP
| 返回地址 | call指令添加,添加之后ESP向栈增长方向增长,该值在子函数调用ret后被弹出,并给EIP
+-------------------------+
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
子函数
call时, 参数被从右到左依次压入栈, 然后压入返回地址(ESP会向栈增长的方向增长)
然后进入被调用函数,一般新函数都会先执行下面两条汇编
push ebp ;保存父函数ebp(栈底)
mov ebp, esp ;设置新栈的栈起始地址为当前esp的地址
实际上push ebp,ebp被保存在了子函数栈中,之后执行 sub esp, 10h类似的操作,开辟了16个字节的空间,但是保存的父函数的ebp占据了最开始的4个字节
[stack]:BFB29508 dd offset off_BFB29518 ; 保存main函数的ebp,新栈的栈底
[stack]:BFB2950C dd 80482A4h
[stack]:BFB29510 dd 16h
[stack]:BFB29514 dd offset _GLOBAL_OFFSET_TABLE_
[stack]:BFB29518 off_BFB29518 dd offset asc_BFB29548
被调用函数栈
+-------------------------+--------------------------------------子函数的EBP
| 保存的父函数EBP | 调用leave后被弹出
+-------------------------+
| 第一个local | locals
+-------------------------+
| 第二个local |
+-------------------------+
| …… |
+-------------------------+--------------------------------------- 子函数的ESP
执行leave后,ebp变为父函数的ebp,移动到该位置,esp指向返回指令的地址处(非父函数的esp)
执行ret后,返回执行指令的地址从栈上被弹出给EIP,esp指向父函数的esp,移动到该位置(ret弹出靠近栈顶的一个地址给EIP)
!!!!!!!!!!!!!!!!!!!
Linux栈是从高地址向低地址增长的,也就是说EBP在ESP的上面,被传人的参数是在父函数的栈中,如下:
#argN
#argN-1
#.
#.
#.
#arg3
#arg2
#arg1
#返回地址
-----子函数栈
#保存父函数EBP
#.locals
这样如果在传递参数时,如果未校验参数长度,参数长度超过缓存区长度,参数就会向栈的栈的增长方向增长,
覆盖返回地址,就产生了漏洞。
如果参数长度恰好等于缓冲区长度,并向缓冲区执行字符串拷贝操作,这样多余的NULL字节就可能覆盖调用者的EBP的最低有效位
(LSB),导致漏洞(调用者EBP恰巧在该缓冲区之上,off-by-one,任意代码执行)。