栈溢出主要是通过覆盖栈内保存的函数返回地址eip,然后在EIP处写入shellcode地址做的。
对于栈溢出的保护有很多
1 使用栈保护
比如linux上的 -fstack-protector windows上的 GS开关。
这种处理的方式是。
进入函数时将一个特定的DATA保存在stack 的高位置
[buffer][DATA][ebp][eip]
低位置 高位置
执行代码
退出时将DATA取出来和原来的进行比较。
如果想要覆盖高位置的EIP同时也会将栈内的DATA数据修改。
如果DATA被修改,表示溢出了。此时就会执行退出指令。
(简单的是采用保存一个数据,如果这个数据被猜测出来。还是可以通过填充来绕过这个检测
所以有加强版的。就是使用esp和这个值进行xor.由于esp可能每次都不同。所以不容易被猜解。
)
这种方式的确能够很好保护,避免出现堆栈溢出的状况。
2 ASLR 加载地址随机化
Address Space Layout Randomization
由于前一种需要将固定的地址写入EIP中(如果是跳转到其他模块。要求其他模块必须加载地址固定。如果是执行栈内的代码。必须要求栈的起始地址固定)
这样就造成了不方便。
于是就需要使用ret2reg的技术。主要是指的查看有没有寄存器是和栈地址相关的。
比如执行的时候 寻找是否有 某处的代码 是 call reg jmp reg (这个地址应该是固定的(比如某个模块的代码))比如call esp call eax之类的。
而这个reg又指向栈内的地址。将这个固定的地址代码写入eip。于是根据call或者jmp的形式又跳转回到了栈内(即使栈地址是不固定的)
3数据执行保护
比如栈内的数据是不可执行的。例如windows开启了DEP。
这样shellcode放在栈内也无法执行。
可以通过 返回到系统函数的方式。
比如执行system("/bin/bash")。
这样只要 在bin里面找到/bin/bash等字符串。或者写入自定义字符串。
有时候system的地址可能是以0结束的。比如0x7fffff00。遇到00就不拷贝了。
这样的话就需要用到 plt或者got了。
什么是plt 和got
#include <stdio.h>
void showmsg(char *szMsg)
{
printf("%s\n", szMsg);
}
int main(int argc, char **argv)
{
char szMsg[] = "Hello, world!";
showmsg(szMsg);
return 0;
}
对printf的调用被编译器改成了puts@plt,位于0×08048350,这是一个PLT(Procedure Linkage Table)条目,往上翻查看这个地址的代码:
void showmsg(char *szMsg)
{
8048434: 55 push %ebp
8048435: 89 e5 mov %esp,%ebp
8048437: 83 ec 18 sub $0x18,%esp
printf("%s\n", szMsg);
804843a: 8b 45 08 mov 0x8(%ebp),%eax
804843d: 89 04 24 mov %eax,(%esp)
8048440: e8 0b ff ff ff call 8048350 <puts@plt>
}
8048445: c9 leave
8048446: c3 ret
08048350 <puts@plt>:
8048350: ff 25 04 a0 04 08 jmp *0x804a004
8048356: 68 08 00 00 00 push $0x8
804835b: e9 d0 ff ff ff jmp 8048330 <_init+0x38>
a.out: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ff0 R_386_GLOB_DAT __gmon_start__
0804a000 R_386_JUMP_SLOT __stack_chk_fail
0804a004 R_386_JUMP_SLOT puts
0804a008 R_386_JUMP_SLOT __gmon_start__
0804a00c R_386_JUMP_SLOT __libc_start_main
所以需要使用strcpy 将地址进行一个个地拷贝。
具体参看http://blog.csdn.net/linyt/article/details/47429823
pop pop ret 第一次会将strcpy的首次plt 运行修复got 时push的值给pop出来。后续的
strcpy是会拷贝末尾的0的。