forked from qilingframework/qiling
-
Notifications
You must be signed in to change notification settings - Fork 0
/
qltool
executable file
·286 lines (230 loc) · 13 KB
/
qltool
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# Built on top of Unicorn emulator (www.unicorn-engine.org)
import argparse, os, string, sys, ast
from binascii import unhexlify
from qiling import *
from qiling.const import QL_ARCH
from qiling.__version__ import __version__ as ql_version
from qiling.extensions.coverage import utils as cov_utils
def parse_args(parser, commands):
# Divide argv by commands
split_argv = [[]]
for c in sys.argv[1:]:
if c in commands.choices:
split_argv.append([c])
else:
split_argv[-1].append(c)
# Initialize namespace
args = argparse.Namespace()
for c in commands.choices:
setattr(args, c, None)
# Parse each command
parser.parse_args(split_argv[0], namespace=args) # Without command
for argv in split_argv[1:]: # Commands
n = argparse.Namespace()
setattr(args, argv[0], n)
parser.parse_args(argv, namespace=n)
return args
# read shellcode from file
def read_file(fname):
with open(fname,"rb") as f:
content = f.read()
f.close
return content
def run_shellcode(options):
if not options.os in ("linux", "windows", "freebsd", "macos"):
print("ERROR: -os required: either linux, windows, freebsd, macos")
exit(1)
if not options.arch in ("arm", "arm64", "x86", "x8664", "mips"):
print("ERROR: -arch required: either arm, arm64, x86, x8664, mips")
exit(1)
if type(options.bigendian) is not bool:
print("ERROR: -bigendian only takes in boolean, True or False")
exit(1)
elif options.hex == True:
if options.input is not None:
print ("[+] Load HEX from ARGV")
shellcoder = str(options.input).strip("\\\\x").split("x")
shellcoder = "".join(shellcoder).strip()
shellcoder = bytes.fromhex(shellcoder)
elif options.filename is not None:
print ("[+] Load HEX from FILE")
shellcoder = str(read_file(options.filename)).strip('b\'').strip('\\n')
shellcoder = shellcoder.strip('x').split("\\\\x")
shellcoder = "".join(shellcoder).strip()
shellcoder = bytes.fromhex(shellcoder)
else:
print("ERROR: File not found")
exit(1)
elif options.asm == True:
print ("[+] Load ASM from FILE")
shellcoder = "asmfile"
else:
print ("[+] Load BIN from FILE")
if options.filename is None:
print("ERROR: File not found")
exit(1)
shellcoder = read_file(options.filename)
return Qiling(shellcoder = shellcoder, archtype = options.arch, bigendian = options.bigendian, ostype = options.os, rootfs = options.rootfs, output = options.output, profile = options.profile)
def usage():
print("qltool for Qiling v%s" %ql_version)
print("\nUsage: ./qltool [run|shellcode] OPTIONS")
print("\n\nWith shellcode:")
print("\t ./qltool shellcode --os linux --arch arm --hex -f examples/shellcodes/linarm32_tcp_reverse_shell.hex")
print("\t ./qltool shellcode --os linux --arch x86 --asm -f examples/shellcodes/lin32_execve.asm")
print("\t ./qltool shellcode --os linux --arch arm --hex -f examples/shellcodes/linarm32_tcp_reverse_shell.hex --strace")
print("\n\nWith binary file:")
print("\t ./qltool run -f examples/rootfs/x8664_linux/bin/x8664_hello --rootfs examples/rootfs/x8664_linux/")
print("\t ./qltool run -f examples/rootfs/mips32el_linux/bin/mips32el_hello --rootfs examples/rootfs/mips32el_linux")
print("\n\nWith binary file and Qdb:")
print("\t ./qltool run -f examples/rootfs/mips32el_linux/bin/mips32el_hello --rootfs examples/rootfs/mips32el_linux --qdb")
print("\n\nWith binary file and gdbserver:")
print("\t ./qltool run -f examples/rootfs/x8664_linux/bin/x8664_hello --gdb 127.0.0.1:9999 --rootfs examples/rootfs/x8664_linux")
print("\n\nWith binary file and additional argv:")
print("\t ./qltool run -f examples/rootfs/x8664_linux/bin/x8664_args --rootfs examples/rootfs/x8664_linux --args test1 test2 test3")
print("\n\nwith binary file and various output format:")
print("\t ./qltool run -f examples/rootfs/mips32el_linux/bin/mips32el_hello --rootfs examples/rootfs/mips32el_linux --output=disasm")
print("\t ./qltool run -f examples/rootfs/mips32el_linux/bin/mips32el_hello --rootfs examples/rootfs/mips32el_linux --strace -e open")
print("\t ./qltool run -f examples/rootfs/mips32el_linux/bin/mips32el_hello --rootfs examples/rootfs/mips32el_linux --strace -e open --log-dir=qlog")
print("\n\nWith UEFI file:")
print("\t ./qltool run -f examples/rootfs/x8664_efi/bin/TcgPlatformSetupPolicy --rootfs examples/rootfs/x8664_efi --env examples/rootfs/x8664_efi/rom2_nvar.pickel")
exit(1)
if __name__ == '__main__':
# argparse setup
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(title='subcommands', description='valid subcommands', help='additional help', dest='subparser_name')
run_parser = commands.add_parser('run', help = 'run')
run_parser.add_argument('-f', '--filename', required=False, default=None, metavar="FILE", dest="filename", help="filename")
run_parser.add_argument('--rootfs', required=True, help='emulated rootfs')
run_parser.add_argument('--args', required=False, default=[], nargs=argparse.REMAINDER, dest="args", help="args")
run_parser.add_argument('run_args', default=[], nargs=argparse.REMAINDER)
shellcode_parser = commands.add_parser('shellcode', help = 'shellcode')
shellcode_parser.add_argument('-f', '--filename', required=False, metavar="FILE", dest="filename", help="filename")
shellcode_parser.add_argument('-i', '--input', required=False, metavar="INPUT", dest="input", help='input hex value')
shellcode_parser.add_argument('--arch', required=True, help='option are x86, x8664, arm, arm64, mips')
shellcode_parser.add_argument('--bigendian', required=False, default=False, help='Type: Bool True or False')
shellcode_parser.add_argument('--os', required=True, help='option are windows, linux, freebsd and macos')
shellcode_parser.add_argument('--rootfs', required=False, help='emulated rootfs, that is where all the so or dll sits')
shellcode_parser.add_argument('--asm', action='store_true', default=False, dest='asm', help='input file format, -asm')
shellcode_parser.add_argument('--hex', action='store_true', default=False, dest='hex', help='input file format, -hex')
shellcode_parser.add_argument('--bin', action='store_true', default=True, dest='bin', help='input file format, -bin')
if len(sys.argv) <= 1:
usage()
if (sys.argv[1] == 'run'):
comm_parser = run_parser
elif (sys.argv[1] == 'shellcode'):
comm_parser = shellcode_parser
else:
usage()
comm_parser.add_argument('-o', '--output', required=False, default='default',
help='output mode, options are off, debug , disasm, dump')
comm_parser.add_argument('-v', '--verbose', required=False, default=1, dest='verbose',
help='verbose mode, must be int and use with --debug or --dump')
comm_parser.add_argument('--env', required=False, default='', metavar="FILE", dest="env", help="Pickle file containing an environment dictionary")
comm_parser.add_argument('-g', '--gdb', required=False, help='enable gdb server')
comm_parser.add_argument('--qdb', action='store_true', required=False, help='attach Qdb at entry point, it\'s MIPS, ARM(THUMB) supported on for now')
comm_parser.add_argument('--profile', required=False, dest='profile', help="Define customized profile")
comm_parser.add_argument('--strace', action='store_true', default=False, dest='strace', help='Run in strace mode')
comm_parser.add_argument('--dump', action='store_true', default=False, dest='dump', help='Enable Debug + Diassembler mode')
comm_parser.add_argument('--debug', action='store_true', default=False, dest='debug', help='Enable Debug mode')
comm_parser.add_argument('--disasm', action='store_true', default=False, dest='disasm', help='Run in disassemble mode')
comm_parser.add_argument('--console', required=False, default=True, dest='console', help='display in console')
comm_parser.add_argument('-e', '--filter', metavar="FUNCTION NAME", required=False, dest="filter", default=None,
help="Only work with strace mode, you can choose what to be printout, for multiple function calls should be separated by comma")
comm_parser.add_argument('--log-dir', required=False, metavar="DIRECTORY NAME", dest='log_dir', default=None, help='the destinartion you want to store you log')
comm_parser.add_argument('--trace', action='store_true', default=False, dest='trace', help='Run in strace mode')
comm_parser.add_argument('--root', action='store_true', default=False, dest='root', help='Enable sudo required mode')
comm_parser.add_argument('--debug_stop', action='store_true', default=False, dest='debug_stop',
help='Stop running while encounter any error (only use it with debug mode)')
comm_parser.add_argument('-m','--multithread', action='store_true', default=False, dest='multithread', help='Run in multithread mode')
comm_parser.add_argument('--timeout', required=False, help='Emulation timeout')
comm_parser.add_argument('-c', '--coverage-file', required=False, default=None, dest='coverage_file', help='Code coverage file name')
comm_parser.add_argument('--coverage-format', required=False, default='drcov', dest='coverage_format',
choices=cov_utils.factory.formats, help='Code coverage file format')
options = parser.parse_args()
# var check
if options.strace:
options.output = "default"
elif options.trace:
options.output = "disasm"
elif options.dump:
options.output = "dump"
elif options.debug:
options.output = "debug"
elif options.disasm:
options.output = "disasm"
else:
options.output = "default"
if options.profile:
options.profile = str(options.profile)
if options.console == 'False':
options.console = False
else:
options.console = True
if options.env != '':
if os.path.exists(options.env):
import pickle
with open(options.env, 'rb') as f:
env = pickle.load(f)
else:
env = ast.literal_eval(options.env)
else:
env = {}
if type(options.verbose) != int and options.output not in ("debug", "dump"):
print("ERROR: verbose mode, must be int and use with --debug or --dump")
usage()
if options.debug_stop and not (options.dump or options.debug):
print("ERROR: debug_stop must use with either dump or debug mode")
usage()
# ql file setup
if (options.subparser_name == 'run'):
# with argv
if options.filename is not None and options.run_args == []:
ql = Qiling(filename=[options.filename] + options.args, rootfs=options.rootfs, profile=options.profile, output=options.output, console=options.console, log_dir=options.log_dir, env=env)
# Without argv
elif options.filename is None and options.args == [] and options.run_args != []:
ql = Qiling(filename=options.run_args, rootfs=options.rootfs, profile=options.profile, output=options.output, console=options.console, log_dir=options.log_dir, env=env)
else:
print("ERROR: Command error!")
usage()
# attach Qdb at entry point
if options.qdb == True:
from qiling.debugger.qdb import Qdb
entry_point = ql.loader.entry_point
if ql.archtype in (QL_ARCH.ARM, QL_ARCH.ARM_THUMB) and entry_point & 1:
entry_point -= 1
ql.hook_address(Qdb.attach, entry_point)
# ql shellcode setup
elif (options.subparser_name == 'shellcode'):
ql = run_shellcode(options)
if options.asm == True:
assembly = read_file(options.filename)
ql.shellcoder = ql.compile(ql.archtype, assembly)
ql.mem.write(ql.os.entry_point, ql.shellcoder)
else:
print("ERROR: Unknown command")
usage()
# ql execute additional options
if options.gdb:
ql.debugger = options.gdb
if options.debug_stop and (options.dump or options.debug):
ql.debug_stop = True
if options.root:
ql.root = True
if options.multithread:
ql.multithread = True
if options.verbose:
ql.verbose = options.verbose
else:
ql.verbose = 1
if options.filter:
ql.filter = options.filter
timeout = 0
if options.timeout != None:
timeout = int(options.timeout)
# ql run
with cov_utils.collect_coverage(ql, options.coverage_format, options.coverage_file):
ql.run(timeout=timeout)
exit(ql.os.exit_code)