findSymbols: added option to colorize output.

This commit is contained in:
Mariusz B. / mgeeky 2021-10-23 15:08:21 +02:00
parent ad18cf327a
commit c9681b2ae7
2 changed files with 87 additions and 10 deletions

View File

@ -31,6 +31,7 @@ optional arguments:
Extensions of files to scan. By default will scan all files. Can be repeated: -E exe -E dll Extensions of files to scan. By default will scan all files. Can be repeated: -E exe -E dll
-o PATH, --output PATH -o PATH, --output PATH
Write output to file. Write output to file.
-C, --color Add colors to text output. May uglify table text output
Output sorting: Output sorting:
-u, --unique Return unique symbols only. The first symbol with a name that occurs in results, will be returned. -u, --unique Return unique symbols only. The first symbol with a name that occurs in results, will be returned.
@ -42,7 +43,7 @@ Output sorting:
Output filtering: Output filtering:
-i, --imports Filter only Imports. -i, --imports Filter only Imports.
-e, --exports Filter only Exports. -e, --exports Filter only Exports.
-s NAME, --name NAME Search for symbols with name matching this regular expression. Can be repeated, case insensitive, constructs: ".+VALUE.+" -s NAME, --name NAME Search for symbols with name matching this regular expression. Can be repeated, case insensitive
-S NOT_NAME, --not-name NOT_NAME -S NOT_NAME, --not-name NOT_NAME
Search for symbols with name NOT matching this regular expression. Search for symbols with name NOT matching this regular expression.
-m MODULE, --module MODULE -m MODULE, --module MODULE

View File

@ -48,6 +48,41 @@ headers = [
symbol_idx = headers.index('symbol') symbol_idx = headers.index('symbol')
class Logger:
colors_map = {
'red': 31,
'green': 32,
'yellow': 33,
'blue': 34,
'magenta': 35,
'cyan': 36,
'white': 37,
'grey': 38,
}
colors_dict = {
'error': colors_map['red'],
'trace': colors_map['magenta'],
'info ': colors_map['green'],
'debug': colors_map['grey'],
'other': colors_map['grey'],
}
@staticmethod
def with_color(c, s):
return "\x1b[%dm%s\x1b[0m" % (c, s)
@staticmethod
def end_color(s):
return "%s\x1b[0m" % (s)
@staticmethod
def colored(args, txt, col):
if not args.color:
return txt
return Logger.with_color(Logger.colors_map[col], txt)
def out(x): def out(x):
sys.stderr.write(x + '\n') sys.stderr.write(x + '\n')
@ -107,18 +142,35 @@ def verifyCriterias(args, regexes, infos, uniqueSymbols):
regexesVerified = sum([len(v) for k, v in regexes.items()]) regexesVerified = sum([len(v) for k, v in regexes.items()])
regexes_name = len(regexes['name'])
regexes_not_name = len(regexes['not-name'])
regexes_module = len(regexes['module'])
regexes_not_module = len(regexes['not-module'])
for name, rex in regexes['not-name']: for name, rex in regexes['not-name']:
match = rex.search(infos['symbol']) match = rex.search(infos['symbol'])
if match: if match:
matched = match.group(1)
infos['symbol'] = infos['symbol'].replace(matched, Logger.colored(args, matched, 'red'))
verbose(args, f'(-) Skipping symbol {infos["module"]}.{infos["symbol"]} as it DID satisfy not-name ({name}) regex.') verbose(args, f'(-) Skipping symbol {infos["module"]}.{infos["symbol"]} as it DID satisfy not-name ({name}) regex.')
return False return False
if regexes_not_module+regexes_module+regexes_name == 0:
verbose(args, f'(+) Symbol {infos["module"]}.{infos["symbol"]} satisfied all criterias.')
return True
for name, rex in regexes['not-module']: for name, rex in regexes['not-module']:
match = rex.search(infos['module']) match = rex.search(infos['module'])
if match: if match:
matched = match.group(1)
infos['module'] = infos['module'].replace(matched, Logger.colored(args, matched, 'red'))
verbose(args, f'(-) Skipping symbol\'s module {infos["module"]}.{infos["symbol"]} as it DID satisfy not-module ({name}) regex.') verbose(args, f'(-) Skipping symbol\'s module {infos["module"]}.{infos["symbol"]} as it DID satisfy not-module ({name}) regex.')
return False return False
if regexes_module+regexes_name == 0:
verbose(args, f'(+) Symbol {infos["module"]}.{infos["symbol"]} satisfied all criterias.')
return True
satisifed = False satisifed = False
carryOn = False carryOn = False
@ -126,16 +178,24 @@ def verifyCriterias(args, regexes, infos, uniqueSymbols):
for name, rex in regexes['module']: for name, rex in regexes['module']:
match = rex.search(infos['module']) match = rex.search(infos['module'])
if match: if match:
matched = match.group(1)
infos['module'] = infos['module'].replace(matched, Logger.colored(args, matched, 'green'))
verbose(args, f'(+) Symbol\'s module {infos["module"]}.{infos["symbol"]} satisfied module ({name}) regex.') verbose(args, f'(+) Symbol\'s module {infos["module"]}.{infos["symbol"]} satisfied module ({name}) regex.')
carryOn = True carryOn = True
break break
else: else:
carryOn = True carryOn = True
if regexes_name == 0:
verbose(args, f'(+) Symbol {infos["module"]}.{infos["symbol"]} satisfied all criterias.')
return True
if carryOn: if carryOn:
for name, rex in regexes['name']: for name, rex in regexes['name']:
match = rex.search(infos['symbol']) match = rex.search(infos['symbol'])
if match: if match:
matched = match.group(1)
infos['symbol'] = infos['symbol'].replace(matched, Logger.colored(args, matched, 'green'))
verbose(args, f'(+) Symbol {infos["module"]}.{infos["symbol"]} satisfied name ({name}) regex.') verbose(args, f'(+) Symbol {infos["module"]}.{infos["symbol"]} satisfied name ({name}) regex.')
satisifed = True satisifed = True
break break
@ -195,9 +255,15 @@ def processFile(args, regexes, path, results, uniqueSymbols, filesProcessed, sym
if args.format == 'text': if args.format == 'text':
appendRow = verifyCriterias(args, regexes, infos, uniqueSymbols) appendRow = verifyCriterias(args, regexes, infos, uniqueSymbols)
if args.color:
if infos['symbol type'] == 'import':
infos['symbol type'] = Logger.colored(args, infos['symbol type'], 'cyan')
else:
infos['symbol type'] = Logger.colored(args, infos['symbol type'], 'yellow')
if appendRow: if appendRow:
row = [] row = []
MaxWidth = 80 MaxWidth = 40
for h in headers: for h in headers:
obj = None obj = None
@ -208,6 +274,9 @@ def processFile(args, regexes, path, results, uniqueSymbols, filesProcessed, sym
obj = infos[h] obj = infos[h]
if type(obj) == str and len(obj) > MaxWidth: if type(obj) == str and len(obj) > MaxWidth:
if h == 'path':
obj = '\n'.join(textwrap.wrap(obj, width = 2 * MaxWidth))
else:
obj = '\n'.join(textwrap.wrap(obj, width = MaxWidth)) obj = '\n'.join(textwrap.wrap(obj, width = MaxWidth))
row.append(obj) row.append(obj)
@ -223,6 +292,12 @@ def processFile(args, regexes, path, results, uniqueSymbols, filesProcessed, sym
elif args.format == 'json': elif args.format == 'json':
appendRow = verifyCriterias(args, regexes, infos, uniqueSymbols) appendRow = verifyCriterias(args, regexes, infos, uniqueSymbols)
if args.color:
if infos['symbol type'] == 'import':
infos['symbol type'] = Logger.colored(args, infos['symbol type'], 'cyan')
else:
infos['symbol type'] = Logger.colored(args, infos['symbol type'], 'yellow')
if appendRow: if appendRow:
results.append(infos) results.append(infos)
uniqueSymbols.append(symbolName) uniqueSymbols.append(symbolName)
@ -236,7 +311,7 @@ def processFile(args, regexes, path, results, uniqueSymbols, filesProcessed, sym
symbolsProcessed.value += len(symbols) symbolsProcessed.value += len(symbols)
def trap_handler(signum, frame): def trap_handler(signum, frame):
out('[-] CTRL-C pressed. Wait a minute until all processes wrap up.') out('[-] CTRL-C pressed. Wait a minute until all processes wrap up or manually terminate python\'s child processes tree.')
def init_worker(): def init_worker():
signal.signal(signal.SIGINT, trap_handler) signal.signal(signal.SIGINT, trap_handler)
@ -300,6 +375,7 @@ def opts(argv):
params.add_argument('-f', '--format', choices=['text', 'json'], default='text', help='Output format. Text or JSON.') params.add_argument('-f', '--format', choices=['text', 'json'], default='text', help='Output format. Text or JSON.')
params.add_argument('-E', '--extension', default=[], action='append', help='Extensions of files to scan. By default will scan all files. Can be repeated: -E exe -E dll') params.add_argument('-E', '--extension', default=[], action='append', help='Extensions of files to scan. By default will scan all files. Can be repeated: -E exe -E dll')
params.add_argument('-o', '--output', metavar='PATH', help='Write output to file.') params.add_argument('-o', '--output', metavar='PATH', help='Write output to file.')
params.add_argument('-C', '--color', default=False, action='store_true', help='Add colors to text output. May uglify table text output')
sorting = params.add_argument_group('Output sorting') sorting = params.add_argument_group('Output sorting')
sorting.add_argument('-u', '--unique', action='store_true', help = 'Return unique symbols only. The first symbol with a name that occurs in results, will be returned.') sorting.add_argument('-u', '--unique', action='store_true', help = 'Return unique symbols only. The first symbol with a name that occurs in results, will be returned.')
@ -310,7 +386,7 @@ def opts(argv):
filters = params.add_argument_group('Output filtering') filters = params.add_argument_group('Output filtering')
filters.add_argument('-i', '--imports', action='store_true', help = 'Filter only Imports.') filters.add_argument('-i', '--imports', action='store_true', help = 'Filter only Imports.')
filters.add_argument('-e', '--exports', action='store_true', help = 'Filter only Exports.') filters.add_argument('-e', '--exports', action='store_true', help = 'Filter only Exports.')
filters.add_argument('-s', '--name', action='append', default=[], help = 'Search for symbols with name matching this regular expression. Can be repeated, case insensitive, constructs: ".+VALUE.+"') filters.add_argument('-s', '--name', action='append', default=[], help = 'Search for symbols with name matching this regular expression. Can be repeated, case insensitive')
filters.add_argument('-S', '--not-name', action='append', default=[], help = 'Search for symbols with name NOT matching this regular expression.') filters.add_argument('-S', '--not-name', action='append', default=[], help = 'Search for symbols with name NOT matching this regular expression.')
filters.add_argument('-m', '--module', action='append', default=[], help = 'Search for symbols exported in/imported from this module matching regular expression.') filters.add_argument('-m', '--module', action='append', default=[], help = 'Search for symbols exported in/imported from this module matching regular expression.')
filters.add_argument('-M', '--not-module', action='append', default=[], help = 'Search for symbols NOT exported in/NOT imported from this module matching regular expression.') filters.add_argument('-M', '--not-module', action='append', default=[], help = 'Search for symbols NOT exported in/NOT imported from this module matching regular expression.')
@ -321,7 +397,7 @@ def opts(argv):
out('[!] --imports and --exports are mutually exclusive. Pick only one of them!') out('[!] --imports and --exports are mutually exclusive. Pick only one of them!')
sys.exit(1) sys.exit(1)
accomodate_rex = lambda x: x accomodate_rex = lambda x: f'({x})'
regexes = { regexes = {
'name': [], 'name': [],
@ -418,15 +494,15 @@ def main():
print(table) print(table)
if args.first > 0: if args.first > 0:
out(f'\n[+] Found {len(resultsList)} symbols meeting all the criterias (but shown only first {args.first} ones).\n') out(f'\n[+] Found {Logger.colored(args, len(resultsList), "green")} symbols meeting all the criterias (but shown only first {Logger.colored(args, args.first, "magenta")} ones).\n')
else: else:
out(f'\n[+] Found {len(resultsList)} symbols meeting all the criterias.\n') out(f'\n[+] Found {Logger.colored(args, len(resultsList), "green")} symbols meeting all the criterias.\n')
else: else:
out(f'[-] Did not find symbols meeting specified criterias.') out(f'[-] Did not find symbols meeting specified criterias.')
out(f'[.] Processed {filesProcessed.value} files and {symbolsProcessed.value} symbols.') out(f'[.] Processed {Logger.colored(args, filesProcessed.value, "green")} files and {Logger.colored(args, symbolsProcessed.value, "green")} symbols.')
out('[.] Time elapsed: {}'.format(time_elapsed)) out('[.] Time elapsed: {}'.format(Logger.colored(args, time_elapsed, "magenta")))
if __name__ == '__main__': if __name__ == '__main__':
freeze_support() freeze_support()