Skip to content

Commit da0a706

Browse files
authored
Merge pull request #8 from s0lst1c3/user-interface-sanity-checks
Added reasonably robust sanity checks to user interface
2 parents 6a8307e + b64e7a3 commit da0a706

File tree

8 files changed

+189
-40
lines changed

8 files changed

+189
-40
lines changed

core/dispatcher_cli.py

Lines changed: 143 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
1+
import os
22
import json
33
import sys
44

55
#from core.loader import Loader
66
#from base64 import b64decode as b64
77
from argparse import ArgumentParser
88
from core.loader import Loader
9+
from core.helpers.error import pcompat, picompat, perror, pnoselect, pexit
910

1011
class Dispatcher:
1112

@@ -192,8 +193,8 @@ def list_compatible(self, mod):
192193
print()
193194
print(' Listing compatible interfaces:')
194195
print()
195-
for omodule in mod.compatible_interfaces:
196-
print(' ', omodule)
196+
for iface in mod.compatible_interfaces:
197+
print(' ', iface)
197198
print()
198199

199200
print()
@@ -203,7 +204,7 @@ def list_compatible_interfaces(self, mods):
203204
print()
204205
for mod in mods:
205206
if self.master_args.interface in mod.compatible_interfaces:
206-
print(' ', mod)
207+
print(' ', mod.name)
207208
print()
208209

209210

@@ -334,72 +335,67 @@ def parse_args(self):
334335
print('Listing interfaces:')
335336
print()
336337
for _ in self.interfaces:
337-
print(f' {_}')
338+
print(f' {_.name}')
338339
print()
339340

340341
if list_all or 'ekeys' in self.master_args.list:
341342
print('Listing ekeys:')
342343
print()
343344
for _ in self.ekeys:
344-
print(f' {_}')
345+
print(f' {_.name}')
345346
print()
346347
if list_all or 'dkeys' in self.master_args.list:
347348
print('Listing dkeys:')
348349
print()
349350
for _ in self.dkeys:
350-
print(f' {_}')
351+
print(f' {_.name}')
351352
print()
352353
if list_all or 'executors' in self.master_args.list:
353354
print('Listing executors:')
354355
print()
355356
for _ in self.executors:
356-
print(f' {_}')
357+
print(f' {_.name}')
357358
print()
358359
if list_all or 'crypters' in self.master_args.list:
359360
print('Listing crypters:')
360361
print()
361362
for _ in self.crypters:
362-
print(f' {_}')
363+
print(f' {_.name}')
363364
print()
364365
if list_all or 'decrypters' in self.master_args.list:
365366
print('Listing decrypters:')
366367
print()
367368
for _ in self.decrypters:
368-
print(f' {_}')
369+
print(f' {_.name}')
369370
print()
370371
if list_all or 'mutators' in self.master_args.list:
371372
print('Listing mutators:')
372373
print()
373374
for _ in self.mutators:
374-
print(f' {_}')
375+
print(f' {_.name}')
375376
print()
376377
if list_all or 'runners' in self.master_args.list:
377378
print('Listing runners:')
378379
print()
379380
for _ in self.runners:
380-
print(f' {_}')
381-
print()
382-
if list_all or 'interfaces' in self.master_args.list:
383-
print('Listing interfaces:')
384-
print()
385-
for _ in self.interfaces:
386-
print(f' {_}')
381+
print(f' {_.name}')
387382
print()
388383
if list_all or 'premodules' in self.master_args.list:
389384
print('Listing premodles:')
390385
print()
391386
for _ in self.premodules:
392-
print(f' {_}')
387+
print(f' {_.name}')
393388
print()
394389
if list_all or 'postmodules' in self.master_args.list:
395390
print('Listing postmodules:')
396391
print()
397392
for _ in self.postmodules:
398-
print(f' {_}')
393+
print(f' {_.name}')
399394
print()
400395
sys.exit()
401396

402-
elif self.at_least_one_module_type_is_selected(self.master_args):
397+
#elif self.at_least_one_module_type_is_selected(self.master_args):
398+
elif self.master_args.build:
403399

404400
#if self.master_args.ekey is not None:
405401

@@ -477,6 +473,66 @@ def parse_args(self):
477473
unknown = self.dkeys[dkey].parse_args(unknown)
478474
self.options['dkeys'].append(self.dkeys[dkey].get_options())
479475

476+
self.validate_build_args()
477+
self.validate_module_compatibility()
478+
479+
480+
else:
481+
482+
self.print_help()
483+
sys.exit()
484+
485+
def validate_build_args(self):
486+
487+
interface = self.dispatch['interface']
488+
runner = self.dispatch['runner']
489+
crypter = self.dispatch['crypter']
490+
decrypter = self.dispatch['decrypter']
491+
ekeys = self.dispatch['ekeys']
492+
dkeys = self.dispatch['dkeys']
493+
executor = self.dispatch['executor']
494+
postmodules = self.dispatch['postmodules']
495+
premodules = self.dispatch['premodules']
496+
mutator = self.dispatch['mutator']
497+
498+
shellcode = self.master_args.shellcode
499+
500+
if interface is None:
501+
pnoselect('interface')
502+
503+
if runner is None:
504+
pnoselect('runner')
505+
506+
if crypter is None:
507+
pnoselect('crypter')
508+
509+
if decrypter is None:
510+
pnoselect('decrypter')
511+
512+
if ekeys == []:
513+
pnoselect('ekeys')
514+
515+
if dkeys == []:
516+
pnoselect('dkeys')
517+
518+
if executor is None:
519+
pnoselect('executor')
520+
521+
if mutator is None:
522+
pnoselect('mutator')
523+
524+
if shellcode is None:
525+
perror('No shellcode was provided to DropEngine.')
526+
perror('You must specify the path to your shellcode using the --input-file flag.')
527+
pexit('Aborting.')
528+
529+
if not os.path.exists(shellcode):
530+
perror('The shellcode path you provided was invalid.')
531+
perror('You must specify a valid path to your shellcode using the --input-file flag.')
532+
pexit('Aborting.')
533+
534+
535+
480536
def print_args(self):
481537

482538
print(json.dumps(self.options, indent=4, sort_keys=True))
@@ -485,6 +541,68 @@ def print_dispatch(self):
485541

486542
print(self.dispatch)
487543

544+
def vmc_iface(self, mod, iface, mtype):
545+
546+
if iface.name not in mod.compatible_interfaces:
547+
picompat(mod.name, iface.name, mtype)
548+
549+
def vmc_imodule(self, imodule, omodule, iface, mtype):
550+
551+
if imodule.name not in omodule.compatible_imodules:
552+
pcompat(imodule.name, omodule.name, mtype)
553+
554+
self.vmc_iface(imodule, iface, mtype)
555+
556+
def vmc_omodule(self, omodule, imodule, iface, mtype):
557+
558+
if omodule.name not in imodule.compatible_omodules:
559+
pcompat(omodule.name, imodule.name, mtype)
560+
561+
self.vmc_iface(omodule, iface, mtype)
562+
563+
def validate_module_compatibility(self):
564+
565+
#print(json.dumps(self.options, indent=4, sort_keys=True))
566+
567+
interface = self.dispatch['interface']
568+
runner = self.dispatch['runner']
569+
crypter = self.dispatch['crypter']
570+
decrypter = self.dispatch['decrypter']
571+
ekeys = self.dispatch['ekeys']
572+
dkeys = self.dispatch['dkeys']
573+
executor = self.dispatch['executor']
574+
postmodules = self.dispatch['postmodules']
575+
premodules = self.dispatch['premodules']
576+
577+
vmc_iface = self.vmc_iface
578+
vmc_imodule = self.vmc_imodule
579+
vmc_omodule = self.vmc_omodule
580+
581+
# validate crypter
582+
vmc_imodule(crypter, decrypter, interface, 'crypter')
583+
584+
# validate decrypter
585+
vmc_omodule(decrypter, crypter, interface, 'decrypter')
586+
587+
# validate ekeys and dkeys
588+
for ekey,dkey in zip(ekeys, dkeys):
589+
vmc_imodule(ekey, dkey, interface, 'ekeys')
590+
vmc_omodule(dkey, ekey, interface, 'dkeys')
591+
592+
# validate executor
593+
vmc_iface(executor, interface, 'executor')
594+
595+
# validate postmodules
596+
for post in postmodules:
597+
vmc_iface(post, interface, 'postmodules')
598+
599+
# validate premodules
600+
for pre in premodules:
601+
vmc_iface(pre, interface, 'premodules')
602+
603+
# validate runner
604+
vmc_iface(runner, interface, 'runner')
605+
488606
@staticmethod
489607
def add_hidden_help_method(parser):
490608

@@ -519,6 +637,7 @@ def add_arguments(parser):
519637
type=str,
520638
required=False,
521639
default=None,
640+
choices=Dispatcher.get_choices('./modules/interfaces', 'MRunnerInterface'),
522641
help='Select interface')
523642

524643
#modules_group.add_argument('--ekey',
@@ -655,16 +774,16 @@ def add_arguments(parser):
655774
modes_group.add_argument('--validate-modules',
656775
dest='validate_modules',
657776
action='store_true',
658-
help='Build a payload')
777+
help='Validate the sanity of a custom module')
659778

660779
build_group = parser.add_argument_group('Build')
661780

662-
build_group.add_argument('--shellcode',
781+
build_group.add_argument('--input-file', '-i',
663782
dest='shellcode',
664783
type=str,
665784
required=False,
666785
default=None,
667-
help='Select shellcode')
786+
help='Specify path to input file containing shellcode')
668787

669788
if __name__ == '__main__':
670789

core/helpers/error.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,46 @@ def pexit(msg):
1616
def pattr_missing(name, attr):
1717
pexit(f'Error: module {name} must have attribute: self.{attr}')
1818

19+
def pnoselect(mtype):
20+
21+
perror(f'No {mtype} selected.')
22+
print()
23+
perror(f'You must specify a module of type {mtype} using the --{mtype} flag.')
24+
print()
25+
perror(f'Run the following command for a list of available {mtype} modules:')
26+
print()
27+
if mtype in ['premodules', 'postmodules', 'ekeys', 'dkeys']:
28+
print(f' python dropengine.py --list {mtype}')
29+
else:
30+
print(f' python dropengine.py --list {mtype}s')
31+
print()
32+
pexit('Aborting.')
33+
34+
def picompat(mod1, mod2, mtype):
35+
print()
36+
perror(f'[!] Error: module "{mod1}" incompatible with interface "{mod2}"')
37+
print()
38+
perror(f'Run the following command for a list of modules compatible with interface "{mod2}":')
39+
print()
40+
print(f' python dropengine.py --list --compatible --interface {mod2}')
41+
print()
42+
43+
perror(f'Run the following command for a list of interfaces compatible with module "{mod1}":')
44+
print()
45+
print(f' python dropengine.py --list interfaces --compatible --{mtype} {mod1}')
46+
print()
47+
pexit('Aborting.')
48+
49+
def pcompat(mod1, mod2, mtype):
50+
print()
51+
perror(f'[!] Error: module "{mod2}" incompatible with module "{mod1}"')
52+
print()
53+
perror(f'Run the following command for a list of modules compatible with module "{mod1}":')
54+
print()
55+
print(f' python dropengine.py --list --compatible --{mtype} {mod1}')
56+
print()
57+
pexit('Aborting.')
58+
1959
def pmeth_missing(name, meth):
2060
pexit(f'Error: module {name} must have method: self.{meth}')
2161

dropengine.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
dispatcher = Dispatcher()
88

99
dispatcher.parse_args()
10+
dispatcher.validate_module_compatibility()
1011

1112
if dispatcher.options['master']['debug']:
1213
dispatcher.print_args()

modules/input/ekeys/ekey_env_ad_domain_name.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@ def __init__(self):
1919
self.mtype = 'ekey'
2020
self.author = '@s0lst1c3'
2121
self.description = 'Environmental key derived from AD domain name'
22-
self.compatible_omodule = [
23-
]
2422

2523
self.compatible_interfaces = [
2624

2725
'csharp_runner_interface',
2826
]
2927

30-
self.compatible_dkeys = [
28+
self.compatible_omodules = [
3129
'dkey_csharp_env_ad_domain_name',
3230
]
3331

modules/input/ekeys/ekey_env_ext_fqdn.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,12 @@ def __init__(self):
1818
self.mtype = 'ekey'
1919
self.author = '@s0lst1c3'
2020
self.description = 'Environmental key derived from external FQDN'
21-
self.compatible_omodule = [
22-
]
2321

2422
self.compatible_interfaces = [
2523

2624
'csharp_runner_interface',
2725
]
28-
self.compatible_dkeys = [
29-
26+
self.compatible_omodules = [
3027

3128
'dkey_env_csharp_ext_fqdn',
3229
]

modules/input/ekeys/ekey_env_ext_ip.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@ def __init__(self):
1818
self.mtype = 'ekey'
1919
self.author = '@s0lst1c3'
2020
self.description = 'Environmental key derived from external IP address'
21-
self.compatible_omodule = [
22-
]
2321

2422

25-
self.compatible_dkeys = [
23+
self.compatible_omodules = [
2624

2725
'dkey_env_csharp_ext_ip',
2826
]

0 commit comments

Comments
 (0)