Skip to content

Instantly share code, notes, and snippets.

@Theldus
Created November 15, 2021 02:22
Show Gist options
  • Save Theldus/4c6c9770ee30909d49a240da9a29fa24 to your computer and use it in GitHub Desktop.
Save Theldus/4c6c9770ee30909d49a240da9a29fa24 to your computer and use it in GitHub Desktop.
Recursively finds dependencies in Mach-O binaries, output a tree-like structure or a graph file via Graphviz
# RecOTool.py -- Recursively finds dependencies in Mach-O binaries
#
# This is free and unencumbered software released into the public domain.
#
# Run with Python2... I don't know why do not works with 3....
#
# Usage
# python RecOTool.py /usr/bin/something -o deps.png (if Graphviz installed)
# python RecOTool.py /usr/bin/something -v (output only text, verbose mode
# enabled)
# python RecOTool.py /usr/bin/something -v -o deps.png
# (outputs a text output & a graph file)
import sys
import subprocess
import argparse
done = set()
dotfile = None
args = None
id = 0
def printid(s):
print(' '*id + s)
def add_dot(n1, n2):
s1 = n1.split("/")[-1]
s2 = n2.split("/")[-1]
dotfile.write("\"{}\" -> \"{}\"\n".format(s1, s2))
def otool(s):
if args.verbose:
printid('otooling {}'.format(s))
printid('--------')
libs = set()
o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE)
for l in o.stdout:
if l[0] == '\t':
libs.add(l.split(' ', 1)[0][1:])
return libs
def search(targ):
global id
global done
libs = otool(targ)
libs.difference_update(done)
for l in libs:
if args.verbose:
printid(l)
done.add(l)
add_dot(targ, l)
id=id+args.indent
search(l)
id=id-args.indent
def parse_args():
p = argparse.ArgumentParser(
description='Recursively finds dependencies in a Mach-O binary')
p.add_argument("binary", type=str, help="Binary to be analyzed")
p.add_argument('-v', '--verbose', default=False, action="store_true",
help='Enable verbose output')
p.add_argument('-i', '--indent', help='Set indent level (default=4)',
type=int, default=4)
p.add_argument('-d', '--dot', help='Set output dot file', type=str,
default='out.dot')
p.add_argument('-o', '--output',
help='Enable and set output image filename (PNG)', type=str,
default='none')
return p.parse_args()
def main():
global dotfile
global args
args = parse_args()
# Open dot file and search recursively
dotfile = open(args.dot, "w+")
dotfile.write("digraph deps {\n")
search(args.binary)
dotfile.write("}\n")
dotfile.close()
# Output PNG file
if args.output != 'none':
try:
subprocess.call(['dot', '-Tpng', args.dot, '-o', args.output])
except:
sys.stderr.write(
"Unable to create graph, please check if Graphviz is "
"properly installed!\n")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment