-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrepl.py
More file actions
107 lines (81 loc) · 3.86 KB
/
repl.py
File metadata and controls
107 lines (81 loc) · 3.86 KB
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
106
import signal
import subprocess
import os
import hashlib
import re
import time
from pathlib import Path
from pexpect import spawn
def execute(cmd):
''' run any command and get stdout result as utf-8 string. '''
# execute command
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = p.communicate()
# check status code is ok
# if it's not, will raise RuntimeError exception
if p.returncode != 0:
raise RuntimeError('"{0}" run fails, err={1}'.format(
cmd, stderr.decode('utf-8', errors='replace')))
# return stdout utf-8 string
return stdout.decode('utf-8').replace('\r\n', '').replace('\n', '')
class GhidraJythonRepl:
def __init__(self, ghidra_home=None):
# those paths come from "$GHIDRA_INSTALL_DIR/support/launch.sh"
# User must define "GHIDRA_INSTALL_DIR" for Ghidra's installation directory
# i.e. GHIDRA_INSTALL_DIR=/path/to/ghidra_9.1_PUBLIC
self.INSTALL_DIR = Path(ghidra_home or os.environ['GHIDRA_INSTALL_DIR'])
self._java_home = None
self._java_vmargs = None
# build pythonRun commandline
run_cmd = '{java_home}/bin/java {java_vmargs} -showversion -cp "{utility_jar}" \
ghidra.GhidraLauncher "ghidra.python.PythonRun"'.format(
java_home=self.java_home,
java_vmargs=self.java_vmargs,
utility_jar=self.INSTALL_DIR / 'Ghidra/Framework/Utility/lib/Utility.jar'
)
# spawn Ghidra's Jython Interpreter (ghidra.python.PythonRun)
# this is exactly same as running "pythonRun" script
self.child = spawn(run_cmd, echo=False, encoding='utf-8')
self.prompt1 = r'>>> '
self.prompt2 = r'... '
# wait for first prompt
self.child.expect('>>> ')
self.inital_msg = self.child.before
@property
def java_home(self):
if self._java_home is None:
self._java_home = execute('java -cp "{0}" LaunchSupport "{1}" -jdk_home -save'.format(
self.INSTALL_DIR / 'support/LaunchSupport.jar', self.INSTALL_DIR))
return self._java_home
@property
def java_vmargs(self):
if self._java_vmargs is None:
self._java_vmargs = execute('java -cp "{0}" LaunchSupport "{1}" -vmargs'.format(
self.INSTALL_DIR / 'support/LaunchSupport.jar', self.INSTALL_DIR))
return self._java_vmargs
def repl(self, code):
''' Ghidra's Jython Interpreter REPL function. '''
# We could escape only key chars for efficiency, but brute force is safer and easier
# e.g., "do_code()" => exec('\\x64\\x6f\\x5f\\x63\\x6f\\x64\\x65\\x28\\x29')
hex_escaped_code = "exec('{}')".format(''.join(['\\x{:02x}'.format(ord(c)) for c in code]))
# Insert some unique line to signify completion, this should run
# eventually, even in any exceptional cases.
flag = hashlib.md5(str(time.time()).encode("ascii")).hexdigest()
completed_cmd = "print('# comp'+'lete {}')".format(flag) # plus sign injected so terminal echo wont match expect pattern
# Run command
self.child.sendline(hex_escaped_code + "\n" + completed_cmd)
# Wait for completion
exp = re.compile("# complete {}".format(flag))
self.child.expect([exp], timeout=1000*1000*1000)
result = self.child.before
# filter all control chars except newline and tab
ccfiltered = re.sub(r'[\x00-\x08\x0b-\x1F]+', '', result)
# filter our two exec/print lines
exp = re.compile('^(>>> )+(exec|print).*$', re.MULTILINE)
metafiltered = re.sub(exp, '', ccfiltered)
# filter out the completed flag
filtered = re.sub(r'# complete [0-9a-f]{32}\n','',metafiltered)
# Return everything that's fit to print
return filtered
def kill(self):
self.child.kill(signal.SIGKILL)