Skip to content

Instantly share code, notes, and snippets.

@schwartz1375
Created March 3, 2024 12:39
Show Gist options
  • Save schwartz1375/545ebfcb0a43a107d2e2f7d00c240830 to your computer and use it in GitHub Desktop.
Save schwartz1375/545ebfcb0a43a107d2e2f7d00c240830 to your computer and use it in GitHub Desktop.
This script estimates the cyclomatic complexity of a binary file by analyzing the assembly code extracted using objdump.
__author__ = 'Matthew Schwartz'
import argparse
import subprocess
def get_assembly_code(binary_file):
try:
output = subprocess.check_output(["objdump", "-d", binary_file], text=True)
return output
except Exception as e:
print(f"Error during objdump execution: {e}")
return None
def estimate_cyclomatic_complexity(assembly_code):
conditional_jumps = [
'ja', 'jae', 'jb', 'jbe', 'jc', 'jcxz', 'jecxz',
'jrcxz', 'je', 'jg', 'jge', 'jl', 'jle', 'jna',
'jnae', 'jnb', 'jnbe', 'jnc', 'jne', 'jng', 'jnge',
'jnl', 'jnle', 'jno', 'jnp', 'jns', 'jnz', 'jo',
'jp', 'jpe', 'jpo', 'js', 'jz'
]
count = sum(1 for line in assembly_code.splitlines() if any(jump in line for jump in conditional_jumps))
return count + 1 # Adding 1 to account for the initial path
def main():
parser = argparse.ArgumentParser(description="Estimate the cyclomatic complexity of a binary file.")
parser.add_argument("file", help="The path to the binary file to analyze.")
args = parser.parse_args()
assembly_code = get_assembly_code(args.file)
if assembly_code:
complexity = estimate_cyclomatic_complexity(assembly_code)
print(f"Cyclomatic Complexity Estimate: {complexity}")
else:
print("Failed to estimate cyclomatic complexity.")
if __name__ == "__main__":
main()
'''
The list of conditional jump instructions in the estimate_cyclomatic_complexity function is a
basic set and might not be exhaustive for all x86 and x86_64 binaries.
objdump -M intel -d file | grep -E -c 'ja|jae|jb|jbe|jc|jcxz|jecxz|jrcxz|je|jg|jge|jl|jle|jna|jnae|jnb|jnbe|jnc|jne|jng|jnge|jnl|jnle|jno|jnp|jns|jnz|jo|jp|jpe|jpo|js|jz'
ja/jnbe: Jump if above/not below or equal (unsigned)
jae/jnb/jnc: Jump if above or equal/not below/no carry
jb/jnae: Jump if below/not above or equal (unsigned)
jbe/jna: Jump if below or equal/not above (unsigned)
jc: Jump if carry
jcxz/jecxz/jrcxz: Jump if CX/ECX/RCX register is zero
je/jz: Jump if equal/zero
jg/jnle: Jump if greater/not less or equal (signed)
jge/jnl: Jump if greater or equal/not less (signed)
jl/jnge: Jump if less/not greater or equal (signed)
jle/jng: Jump if less or equal/not greater (signed)
jna: Jump if not above (unsigned)
jnae: Jump if not above or equal (unsigned)
jnb: Jump if not below (unsigned)
jnbe: Jump if not below or equal (unsigned)
jnc: Jump if no carry
jne/jnz: Jump if not equal/not zero
jng: Jump if not greater (signed)
jnge: Jump if not greater or equal (signed)
jnl: Jump if not less (signed)
jnle: Jump if not less or equal (signed)
jno: Jump if not overflow
jnp/jpo: Jump if not parity/parity odd
jns: Jump if not sign (non-negative)
jo: Jump if overflow
jp/jpe: Jump if parity/parity even
js: Jump if sign (negative)
jz: Jump if zero
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment