中级栈溢出

中级栈溢出

1. ret2csu

1.1 目的

64位程序下,函数的前6个参数是通过寄存器来传递的,从第7个参数开始才从栈开始传递。但是,我们很难找到这6个寄存器对应的gadget。
    此时,我们可以利用程序内部的_libc_csu_init()函数内部的gadget来实现这样的功能。
    这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在(64位程序)。

1.2 步骤

    1.  首先我们来检查一下程序的安全机制。

img

    查看上述内容,该程序打开了栈不可执行,没有打开canary和地址随机化。
    2.  我们使用ida来反编译该程序,得到该程序的源代码。

img
img

    通过检查上述的源代码,我们可以发现:
        1.  溢出存在于vulnerable_function()函数中的read函数。
        2.  因为局部变量buf距离rbp0x80H,但是我们却可以通过read函数往buf写入0x200H的数据。
    3.  我们可以查看该程序的gadget,看看有没有可以利用的。

img

    我们可以发现:该程序所可以利用的gadget极少。因此,我们无法通过这种方式来进行传参。
    4.  我们可以通过ida来查看一下这个程序有没有后门函数?

img

    遗憾的说,并没有。因此,我们无法通过ret2text的做法来进行解决。
    5.  那么,我们只能利用_libc_csu_init()函数的gadget来解决此问题。接下来,我们来查看一下该函数的汇编代码。
.text:00000000004005A0 ; =============== S U B R O U T I N E =======================================
.text:00000000004005A0
.text:00000000004005A0
.text:00000000004005A0 ; void _libc_csu_init(void)
.text:00000000004005A0                 public __libc_csu_init
.text:00000000004005A0 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:00000000004005A0
.text:00000000004005A0 var_30          = qword ptr -30h
.text:00000000004005A0 var_28          = qword ptr -28h
.text:00000000004005A0 var_20          = qword ptr -20h
.text:00000000004005A0 var_18          = qword ptr -18h
.text:00000000004005A0 var_10          = qword ptr -10h
.text:00000000004005A0 var_8           = qword ptr -8
.text:00000000004005A0
.text:00000000004005A0 ; __unwind {
.text:00000000004005A0                 mov     [rsp+var_28], rbp
.text:00000000004005A5                 mov     [rsp+var_20], r12
.text:00000000004005AA                 lea     rbp, cs:600E24h
.text:00000000004005B1                 lea     r12, cs:600E24h
.text:00000000004005B8                 mov     [rsp+var_18], r13
.text:00000000004005BD                 mov     [rsp+var_10], r14
.text:00000000004005C2                 mov     [rsp+var_8], r15
.text:00000000004005C7                 mov     [rsp+var_30], rbx
.text:00000000004005CC                 sub     rsp, 38h
.text:00000000004005D0                 sub     rbp, r12
.text:00000000004005D3                 mov     r13d, edi
.text:00000000004005D6                 mov     r14, rsi
.text:00000000004005D9                 sar     rbp, 3
.text:00000000004005DD                 mov     r15, rdx
.text:00000000004005E0                 call    _init_proc
.text:00000000004005E5                 test    rbp, rbp
.text:00000000004005E8                 jz      short loc_400606
.text:00000000004005EA                 xor     ebx, ebx
.text:00000000004005EC                 nop     dword ptr [rax+00h]
.text:00000000004005F0
.text:00000000004005F0 loc_4005F0:                             ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]
.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn
.text:0000000000400628 ; } // starts at 4005A0
.text:0000000000400628 __libc_csu_init endp
    我们可以分析上述的汇编代码:
        1.  从0x0000000000400606开始到0x000000000040061F结束,我们分别可以控制rbx,rbp,r12,r13,r14,r15寄存器。
        2.  从0x00000000004005F0开始到0x00000000004005F6结束,我们可以通过r13的低32位,r14,r15来分别控制edi(rdi寄存器的低32位,高32位都为0),rsirdx寄存器来实现传参。需要注意的是:我们只能控制edi,但是对于大多数情况,已经足够了。
        3.  我们可以通过0x00000000004005F9call指令来实现函数调用。该指令会跳转到[r12+rbx*8](代表r12+rbx*8地址的值)这就意味着:我们可以将r12设置为某一个函数的got表项,rbx设置为0。此时,call指令就会找到该函数的真实地址进行跳转(实现调用库函数的效果)。
        4.  从0x00000000004005FD0x0000000000400604,我们可以使用rbxrbp来避免进行函数的循环,使其执行流可以继续往下执行。rbx设置为0rbp设置为1jnz代表不相等进行跳转。
        5.  从0x0000000000400606开始到0x000000000040061F结束,rbx,rbp,r12,r13,r14,r15寄存器都是通过相对寻址来进行赋值的。例如:[rsp+38h+var_30]效果就相当于rsp+8H地址的值来赋给rbx(意味着:我们还需要多填充8个字节的垃圾数据)。其余的话依次类推即可。
    6.  我们可以通过gdb来实现动态调试,计算地址。

img

    需要填充的垃圾数据:0xdfc0 - 0xdf40 = 128 + 8 = 136
    7.  计算完地址后,我们来讲述一下这道题的攻击思路:
        1.  首先,我们通过填充垃圾数据到返回地址,返回地址处需要填入0x0000000000400606,之后需要填入8字节的垃圾数据(原因上述已经说明),再给寄存器进行传值(依次为:rbx=0,rbp=1,r12=write_got,r13=1,r14=write_got,r15=8)。
        2.  其次,再通过ret来到达0x00000000004005F0,给write函数传参,并调用该write函数(将write函数的真实地址泄露出来,打印到屏幕上),填充0x38个垃圾数据后,再次返回main函数进行第二次溢出。
        3.  在第二次溢出时,我们可以通过write的真实地址来计算出system的真实地址。我们可以调用read函数来读取system的真实地址和/bin/sh字符串到bss区(默认可执行)
        4.  我们可以重复上述过程,再次返回main函数进行第三次溢出(唯一变化的是寄存器的传值)(rbx = 0,rbp = 1,r12=read_got,r13 = 0,r14=bss_address,r15=16)。
        5.  在第三次溢出时,我们可以通过bss的起始地址来调用system函数并进行传参进而获得shell。我们可以重复上述过程。唯一变化的是寄存器的传值(rbx = 0,rbp = 1,r12=bss_address,r13 = bss_address+8,r14=0,r15=0)
    我们可以根据上述的攻击思路,来构造三个payload,进而获得shell(具体看EXP即可)。
    注意:当我们获得了system的真实地址后,为什么不直接进行call?因为,在上述汇编代码中,call是通过相对寻址进行跳转的。如果我们将r12设置了system的真实地址,在实际运行时,是对该真实地址的内容进行跳转的(但是该真实地址的内容并不是一个有效的地址(是一个指令))。
    8.  根据上述的攻击思路,我们来写一下EXP

img

    9.  执行EXP,发现可以获得shell,本题结束。

img

posted @   夏目^_^  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示