Evo. G Tech Team Forum
Welcome to Evo. G Tech Team Forum. We have moved to a new website : www.evogtechteam.com

Thanks you.

by Evo. G Tech Team Management.

【转】缓冲区溢出攻击初学者手册

Go down

【转】缓冲区溢出攻击初学者手册 Empty 【转】缓冲区溢出攻击初学者手册

Post by cyjian on December 12th 2014, 09:58

缓冲区溢出出现在用户输入的相关缓冲区内,在一般情况下,这是现在的计算机和网络上的最大的安全隐患之一。这是因为在编程的层次上很容易出现这中问题,这 对于不明白或是无法获得源代码的使用者来说是不可见的,很多的这中问题就会被利用。本文就是企图教会新手-C程序员,证明怎么利用一个溢出环境。
一、内存
注:我这里的描述方式是在大多数计算机上内存是进程的组织者,但是它是依赖处理器体系结构的类型。这是一个x86的例子,同时也可以大致应用在sparc。
缓冲区溢出的攻击原理是不应该是重写随机输入和在进程中执行代码的内存的重写。要看在什么地方和怎么发生的溢出,让我们来看下内存是如何组织的。页面是使 用自己相关地址的内存的一个部分,这就意味着内核的进程的初始化,这就没有必要知道在RAM中存储的物理地址。进程内存由下面三个部分组成:
代码段,在这一段代码中你的数据是通过汇编指令在处理器中执行的。该代码执行是非线性的,它可以跳过代码,跳跃,在某种条件下调用函数。以此,我们使用EIP指针,或是指针指令。其中EIP指向的地址总是包含下一个执行代码。
数据段,变量空间和动态缓冲器。
堆栈段,这是用来给函数传递变量的和和作为函数变量的空间。在栈的底部位于每一页的虚拟内存的尽头,同时向下增长。汇编命令PUSHL会增加到栈的顶部,POPL会从栈的顶部移除项目并且把它们放到寄存器中。要直接访问栈寄存器,在栈的顶部有栈顶指针ESP。
二、函数
函数是一段代码段的代码,当被调用执行一个任务,之后返回执行的前一个主题。或是,把参数传递给函数,在汇编语言中,通常看起来是这样的。
memory address         code
0x8054321               pushl $0x0
0x8054322               call $0x80543a0
0x8054327              ret
0x8054328              leave
...
0x80543a0              popl %eax
0x80543a1              addl $0x1337,%eax
0x80543a4              ret
这会发生什么?主函数调用了function(0);
变量是0,主要把它压入栈中,同时调用该函数。该函数使用popl来获取栈中的变量。完成后,返回0×8054327。通常,主函数要把EBP寄存器压入 栈中,主要是储存和在结束后在储存。这是帧指针的概念,即允许函数使用自己的偏移地址,在对付攻击时就变的很无趣了。因为函数将不会返回到原有的执行线 程。
我们只需要知道栈。在顶部,我们有函数的内部缓冲区和变量。在此之后,有保存的EBP寄存器(32位,4个字节),然后返回地址,是另外的4个字节。再往下,还有要传递给函数的参数,这对我们没有用。
在这种情况下,我们返回的地址是0×8054327。在函数被调用时,它就会自动的存储到栈中。如果代码中存在溢出的地方,这个返回值会被覆盖,并且指针指向下内存中的下一个位置。
三、一个可以利用的程序实例
让我们假设我们要利用的函数为:
void lame (void) { char small[30]; gets (small); printf("%s\n", small); }
main() { lame (); return 0; }
编译并反汇编以上代码:
# cc -ggdb blah.c -o blah

/tmp/cca017401.o: In function `lame':

/root/blah.c:1: the `gets' function is dangerous and should not be used.

# gdb blah

/* short explanation: gdb, the GNU debugger is used here to read the

   binary file and disassemble it (translate bytes to assembler code) */

(gdb) disas main
反汇编main函数的结果:
0x80484c8 :    pushl  %ebp

0x80484c9 :     movl   %esp,%ebp

0x80484cb :     call   0x80484a0

0x80484d0 :     leave

0x80484d1 :     ret

(gdb) disas lame
反汇编lame函数的结果:
/* saving the frame pointer onto the stack right before the ret address */
0x80484a0 :       pushl  %ebp
0x80484a1 :     movl   %esp,%ebp

/* enlarge the stack by 0x20 or 32. our buffer is 30 characters, but the
   memory is allocated 4byte-wise (because the processor uses 32bit words)
   this is the equivalent to: char small[30]; */
0x80484a3 :     subl   $0x20,%esp

/* load a pointer to small[30] (the space on the stack, which is located
   at virtual address 0xffffffe0(%ebp)) on the stack, and call
   the gets function: gets(small); */

0x80484a6 :     leal   0xffffffe0(%ebp),%eax
0x80484a9 :     pushl  %eax
0x80484aa :    call   0x80483ec
0x80484af :    addl   $0x4,%esp

/* load the address of small and the address of "%s\n" string on stack
   and call the print function: printf("%s\n", small); */

0x80484b2 :    leal   0xffffffe0(%ebp),%eax
0x80484b5 :    pushl  %eax
0x80484b6 :    pushl  $0x804852c
0x80484bb :    call   0x80483dc
0x80484c0 :    addl   $0x8,%esp

/* get the return address, 0x80484d0, from stack and return to that address.
you don't see that explicitly here because it is done by the CPU as "ret"*/
0x80484c3 :    leave
0x80484c4 :    ret
反汇编结束。
3.1 程序溢出
# ./blah
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx    <- user input
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ./blah
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx <- user input
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Segmentation fault (core dumped)
# gdb blah core
(gdb) info registers
     eax:       0x24          36
     ecx:  0x804852f   134513967
     edx:        0x1           1
     ebx:   0x11a3c8     1156040
     esp: 0xbffffdb8 -1073742408
     ebp:   0x787878     7895160
EBP是0×787878,这就意味我们已经写入了超出缓冲区输入可以控制的范围。0×78的x是十六进制的标志。该过程有32个字节的最大的缓冲器。我 们已经在内存中写入了比用户输入更多的数据,因此重写EBP和返回值的地址是'xxxx',这个过程会尝试在地址0×787878处重复执行,这就会导致 段的错误。
3.2 改变返回值地址
让我们尝试利用这个程序来返回lame(),我们要改变返回值的地址从0x80484d0到0x80484cb,在内存中,我们有32字节的缓冲区空间|4个字节保存EBP|4个字节的RET。下面是一个很简单的程序,把4个字节的返回地址变成一个1个字节字符缓冲区:
main()
{
int i=0; char buf[44];
for (i=0;i<=40;i+=4)
*(long *) &buf = 0x80484cb;
puts(buf);
}

# ret
ËËËËËËËËËËË,
# (ret;cat)|./blah
test         <- user input
ËËËËËËËËËËË,test
test         <- user input
test
我们在这里使用这个程序通过了函数两次。如果有溢出存在,函数的返回值地址是可以变的,从而改变程序的执行线程
四、Shellcode
为了简单,Shellcode使用简单的汇编指令,我们写在栈上,然后更改返回地址,使它返回到栈内。使用这个方法,我们可以我们可以把代码插入到一个脆 弱的进程中,然后在栈中正确的执行它。所以,让我们通过插入的汇编代码来运行一个Shell。一个常见的调用命令是execve(),它加载和运行任意的 二进制代码,终止执行当前的进程。联机界面给我的应用:

反汇编execve函数如下:


结束汇编转存。
4.1 使代码可移植
我们必须应用一个策略使没有参数的Shellcode在内存中的传统方式,通过在它们的页存储上的精确位置,在编译中完成。
一旦我们估计shellcode的大小,我们能够使用指令jmp和call来得到指定的字节在执行线程向前或是向后。为什么使用call?我们有机会使用 CALL来自动的在栈内存储返回地址,这个返回地址是在下一个CALL指令后的4个字节。通过放置一个正确的变量通过使用call,我们间接的把地址压进 了栈中,没有必要了解它。

(注:如果你要写的代码比一个简单的shell还要复杂,可以多次使用上面的代码。字符串放在代码的后面。你知道这些字符串的大小,因此可以计算他们的相对位置,一旦你知道第一个字符串的位置。)
4.2 Shellcode


(相对偏移了0×17和-0x1c通过放在0×0,编译,反汇编和看看shell代码的大小。)
这是一个正在工作着的shellcode,虽然很小。你至少使用exit()来调用和依附它(在调用之前)。Shellcode的正真的艺术还包括避免任 何二进制0代码和修改它为例,二进制代码不包含控制和小写字符,这将会过滤掉一些问题程序。大多数的东西是通过自己修改代码来完成的,就是我们想的使用 mov %eax,0×7(%esi)指令。我们用来取代X,但是在shellcode初始化中没有。
让我们测试下这些代码,保存上面的代码为code.S和下面的文件为
code.c:

现在你可以把shellcode转变成16进制字符缓冲区。要做到这的最好的方法就是打印:

通过使用aconv –h或bin2c.pl来解析它,可以在[You must be registered and logged in to see this link.]或是[You must be registered and logged in to see this link.]上找到工具。
五、编写EXP
让我们看看如何改变返回地址指向的shellcode进行压栈,写一个攻击的例子。我们将要采用zgv,因为这是可以利用的一个最简单的事情。

那么,这是在栈顶的故障时间,安全的假设是我们能够使用这作为我们shellcode的返回地址。
现在我们要在我们的缓冲区前增加一些NOP指令,所以我们没有必要对于我们内存中的shellcode的精确开始的预测100%的正确。这个函数将会返回 到栈在我们的shellcode之前,通过这个方式使用NOPs的头文字JMP命令,跳转到CALL,在转回popl,在栈中运行我们的代码。
记住,栈是这样的。在最低级的内存地址,ESP指向栈的顶部,初始变量被储存,即时缓冲器中的zgv储存了HOME环境变量。在那之后,我们保存了EBP和前一个函数的返回地址。我们必须要写8个字节或是更多在缓冲区后面,用栈中的新的地址来覆盖返回地址。
Zgv缓冲器有1024个字节。你可以通过扫视代码来发现,或是通过在脆弱的函数中搜索初始化的subl $0×400,%esp (=1024)。我们可以把这些放在一起来利用。
5.1 zgv攻击实例




现在我可以看到类似于以下的字符串:
[ ... NOP NOP NOP NOP NOP JMP SHELLCODE CALL /bin/sh RET RET RET RET RET RET ]
此时zgv的栈空间分布如下:
v-- 0xbffff574 is here
[     S   M   A   L   L   B   U   F   F   E   R   ] [SAVED EBP] [ORIGINAL RET]
接下来zgv的执行顺序是:
main ... -> function() -> strcpy(smallbuffer,getenv("HOME"));
此时,zgv做不到边界检查,写入超出了smallbuffer,返回到main的地址被栈中的返回地址覆盖。function()离不开/ ret和栈中EIP的指向。

现在让我们测试下EXP:

5.2 编写EXP的进一步提示
有很都可以被利用的程序,但还是很脆弱。但是这有很多的技巧,你可以通过过滤等方式得到。还有其他的溢出技术,这并不一定要包括改变返回地址或是只是放回 地址。有指针溢出,函数分配的指针能够被覆盖通过一个数据流,改变程序执行的流程。攻击的返回地址指向shell环境指针,shellcode为与那里, 而不是在栈上。
对于一个熟练掌握shellcode的人是在根本上的自己修改代码,最初包含可以打印的,非白色的大写字母,然后修改自己它,把shellcode函数放在要执行的栈上。
你应该永远不会有任何二进制0在你的shell代码里,因为如果它包含任何都可能无法正常的工作。但是本文讨论了怎么升华某种汇编指令与其他的命令超出了范围。我也建议读其他大的数据流怎么超出的,通过aleph1,Taeoh Oh和mudge来写的。
5.3 重要注意事项
你将不能在Windows 或是 Macintosh上使用这个教程,不要和我要cc.exe和gdb.exe。
六、结论
我们已经知道,一旦用户依赖存在的的溢出,在90%的时间了是可以利用的,即使利用起来和困难,同时要一些技能。为什么写这个攻击很重要呢?因为软件企业 是无知的。在软件缓冲区溢出方面的漏洞的报告已经有了,虽然这些软件没有更新,或是大多数用户没有更新,因为这个漏洞很难被利用,没有人认为这会成为一个 安全隐患。然后,漏洞出现了,证明和实践是程序能够利用,而且这就要急于更新了。
作为程序员,写一个安全的程序是一个艰巨的任务,但是要认真的对待。在写入服务器时就变的更加值得关注,任何类型的安全程序,或是suid root的程序,或是设计使用root来运行,如特别的账户或是系统本身。使用范围检查,更喜欢分配动态缓冲器,输入的依赖性,大小,小心/while /etc。收集数据和填充缓冲区,以及一般处理用户很关心的输入的循环是我建议的主要原则。
目前在安全行业取得了显著的成绩,使用非可执行的栈,suid包,防卫程序来核对返回值,边界核查编辑器等技术来阻止溢出问题。你应该在可以使用的情况下 使用这些技术,但是不要完全依赖他们。如果你运行vanilla的UNIX的发行版时,不要假设安全,但是有溢出保护或是防火墙/IDS。它不能保证安 全,如果你继续使用不安全的程序,因为_all_安全程序是_software_和包含自身漏洞的,至少他们不是完美的。如果你频繁的使用updates _和_ security measures,你仍然不能渴望安全,_but_你可以希望。
原文地址:[You must be registered and logged in to see this link.] ... /buff-overflow.html

cyjian
Spammer
Spammer

Posts : 211
Points : 38915
Reputation : 0
Join date : 2014-06-18

View user profile

Back to top Go down

Back to top


 
Permissions in this forum:
You cannot reply to topics in this forum