中级栈溢出
1. ret2csu
1.1 目的
在64位程序下,函数的前6个参数是通过寄存器来传递的,从第7个参数开始才从栈开始传递。但是,我们很难找到这6个寄存器对应的gadget。
此时,我们可以利用程序内部的_libc_csu_init()函数内部的gadget来实现这样的功能。
这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在(64位程序)。
1.2 步骤
1. 首先我们来检查一下程序的安全机制。
查看上述内容,该程序打开了栈不可执行,没有打开canary和地址随机化。
2. 我们使用ida来反编译该程序,得到该程序的源代码。
通过检查上述的源代码,我们可以发现:
1. 溢出存在于vulnerable_function()函数中的read函数。
2. 因为局部变量buf距离rbp为0x80H,但是我们却可以通过read函数往buf写入0x200H的数据。
3. 我们可以查看该程序的gadget,看看有没有可以利用的。
我们可以发现:该程序所可以利用的gadget极少。因此,我们无法通过这种方式来进行传参。
4. 我们可以通过ida来查看一下这个程序有没有后门函数?
遗憾的说,并没有。因此,我们无法通过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),rsi,rdx寄存器来实现传参。需要注意的是:我们只能控制edi,但是对于大多数情况,已经足够了。
3. 我们可以通过0x00000000004005F9的call指令来实现函数调用。该指令会跳转到[r12+rbx*8](代表r12+rbx*8地址的值)这就意味着:我们可以将r12设置为某一个函数的got表项,rbx设置为0。此时,call指令就会找到该函数的真实地址进行跳转(实现调用库函数的效果)。
4. 从0x00000000004005FD到0x0000000000400604,我们可以使用rbx和rbp来避免进行函数的循环,使其执行流可以继续往下执行。rbx设置为0,rbp设置为1。jnz代表不相等进行跳转。
5. 从0x0000000000400606开始到0x000000000040061F结束,rbx,rbp,r12,r13,r14,r15寄存器都是通过相对寻址来进行赋值的。例如:[rsp+38h+var_30]效果就相当于rsp+8H地址的值来赋给rbx(意味着:我们还需要多填充8个字节的垃圾数据)。其余的话依次类推即可。
6. 我们可以通过gdb来实现动态调试,计算地址。
需要填充的垃圾数据: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
9. 执行EXP,发现可以获得shell,本题结束。
标签:中级,rbx,text,mov,00000000004005A0,var,rsp,溢出
From: https://www.cnblogs.com/gao79135/p/17826359.html