├── cybertestlab ├── __init__.py ├── tests │ └── test_Analysis.py ├── Scoring.py ├── Analysis.py └── CyberTestLab.py ├── .gitmodules ├── docker └── Dockerfile ├── score.py ├── demo_scan.py ├── prep_fedora_host.yml ├── afCc_pdf.py ├── cleanup.sh ├── plot_centos.py ├── compare.py ├── plot_rhel.py ├── plot_fedora.py ├── .gitignore ├── rhel.py ├── fedora.py ├── README.md ├── scoring.yml └── LICENSE /cybertestlab/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ctl-results"] 2 | path = ctl-results 3 | url = https://github.com/redteam-project/ctl-results 4 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora 2 | 3 | RUN dnf install -y python-pip git vim gcc make patch ipython yum-utils hardening-check cpio findutils 4 | 5 | RUN pip install redteam && \ 6 | pip install python-magic 7 | 8 | RUN cd /root && git clone https://github.com/radare/radare2.git 9 | 10 | RUN cd /root/radare2 && git checkout 2.0.1 && ./sys/install.sh 11 | 12 | RUN pip install r2pipe timeout-decorator 13 | 14 | RUN mkdir /fedora_swap 15 | -------------------------------------------------------------------------------- /cybertestlab/tests/test_Analysis.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cybertestlab import Analysis 4 | 5 | def test_find_elfs(): 6 | a = Analysis.Analysis('/usr/bin') 7 | 8 | elfs = a.find_elfs() 9 | assert len(elfs) > 0 10 | 11 | def test_scan_elfs(): 12 | a = Analysis.Analysis('/usr/bin') 13 | 14 | b = a.scan_elfs(['/usr/bin/yes']) 15 | assert b.has_key('yes') 16 | 17 | def test_get_complexity(): 18 | 19 | a = Analysis.Analysis('/usr/bin') 20 | b = a.get_complexity('/usr/bin/yes') 21 | assert b.has_key('r2aa') 22 | -------------------------------------------------------------------------------- /score.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import json 5 | 6 | sys.path.append('.') 7 | from cybertestlab import Scoring 8 | 9 | __author__ = 'Jason Callaway' 10 | __email__ = 'jasoncallaway@fedoraproject.org' 11 | __license__ = 'GNU Public License v2' 12 | __version__ = '0.3' 13 | __status__ = 'beta' 14 | 15 | 16 | def main(argv): 17 | s = Scoring.Scoring(debug=True) 18 | 19 | scores = s.score(path=sys.argv[1]) 20 | 21 | with open(sys.argv[2], 'w') as f: 22 | json.dump(scores, f, indent=4, sort_keys=True) 23 | 24 | 25 | if __name__ == "__main__": 26 | main(sys.argv) 27 | -------------------------------------------------------------------------------- /demo_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cybertestlab import Analysis 4 | import json 5 | import sys 6 | from elasticsearch import Elasticsearch 7 | 8 | es = Elasticsearch(['http://localhost:9200']) 9 | 10 | if len(sys.argv) > 1: 11 | a = Analysis.Analysis(sys.argv[1], debug=True) 12 | else: 13 | a = Analysis.Analysis('/usr/bin', debug=True) 14 | 15 | elfs = a.find_elfs() 16 | 17 | # Let's loop this so we don't chew up all the RAM 18 | for one_elf in elfs: 19 | 20 | b = a.scan_elfs([one_elf]) 21 | 22 | for i in b.keys(): 23 | try: 24 | es.update(id=i, index="ctl", doc_type='doc', body={'doc' :b[i], 'doc_as_upsert': True}) 25 | except: 26 | # We'll figure this out someday 27 | pass 28 | 29 | -------------------------------------------------------------------------------- /prep_fedora_host.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | become: true 5 | tasks: 6 | - name: install pre requisite packages 7 | dnf: 8 | name: "{{ item }}" 9 | state: installed 10 | with_items: 11 | - git 12 | - vim 13 | - gcc 14 | - make 15 | - patch 16 | - hardening-check 17 | - python-pip 18 | - python-virtualenv 19 | - capstone-python 20 | - dnf-utils 21 | - cpio 22 | # Hm, this part seems to be broken 23 | # - stat: 24 | # path: /root/radare2 25 | # register: radare2 26 | # - name: download and install radare2 27 | # shell: > 28 | # cd /root; 29 | # git clone https://github.com/radare/radare2.git; 30 | # cd radare2; 31 | # git checkout tags/2.0.1; 32 | # sys/install.sh 33 | # when: radare2.stat.isdir is not defined and not radare2.stat.isdir 34 | - name: create repo directories 35 | file: 36 | path: "{{ item }}" 37 | state: directory 38 | with_items: 39 | - /repo 40 | - /fedora_swap -------------------------------------------------------------------------------- /afCc_pdf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | import scipy.stats as stats 5 | import sys 6 | import pylab as pl 7 | 8 | # to make afCc.txt 9 | # find ctl-results/fedora/27 -type f -exec grep afCc {} \; | grep -v null | awk '{print $2}' | sed -e 's/,$//' > afCc.txt 10 | 11 | complexities = [] 12 | with open(sys.argv[1], 'r') as f: 13 | for line in f: 14 | if int(line) > 0: 15 | complexities.append(int(line)) 16 | 17 | textbox = 'cyclomatic complexity pdf for fedora 27 (incomplete)\n' + \ 18 | 'total: ' + str(len(complexities)) + '\n' + \ 19 | 'minimum: ' + str(min(complexities)) + '\n' + \ 20 | 'maximum: ' + str(max(complexities)) + '\n' + \ 21 | 'mean: ' + str(np.mean(complexities)) + '\n' + \ 22 | 'median: ' + str(np.median(complexities)) + '\n' + \ 23 | 'mode: ' + str(stats.mode(complexities)) + '\n' + \ 24 | 'stdev: ' + str(np.std(complexities)) 25 | 26 | h = sorted(complexities) 27 | fit = stats.norm.pdf(h, np.mean(h), np.std(h)) 28 | pl.plot(h,fit,'o') 29 | pl.hist(h,normed=True) 30 | pl.text(150, 0.0065, textbox, fontsize=9) 31 | pl.show() 32 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ls *\,* >/dev/null 2>&1 4 | if [ "${?}" == "0" ]; then 5 | ls *\,* | xargs rm 6 | fi 7 | 8 | ls .*\,* >/dev/null 2>&1 9 | if [ "${?}" == "0" ]; then 10 | ls .*\,* | xargs rm 11 | fi 12 | 13 | ls *::* >/dev/null 2>&1 14 | if [ "${?}" == "0" ]; then 15 | ls *::* | xargs rm 16 | fi 17 | 18 | ls .*::* >/dev/null 2>&1 19 | if [ "${?}" == "0" ]; then 20 | ls .*::* | xargs rm 21 | fi 22 | 23 | ls *\(* >/dev/null 2>&1 24 | if [ "${?}" == "0" ]; then 25 | ls *\(* | xargs rm 26 | fi 27 | 28 | ls .*\(* >/dev/null 2>&1 29 | if [ "${?}" == "0" ]; then 30 | ls .*\(* | xargs rm 31 | fi 32 | 33 | ls *\)* >/dev/null 2>&1 34 | if [ "${?}" == "0" ]; then 35 | ls *\)* | xargs rm 36 | fi 37 | 38 | ls .*\)* >/dev/null 2>&1 39 | if [ "${?}" == "0" ]; then 40 | ls .*\)* | xargs rm 41 | fi 42 | 43 | ls *\<* >/dev/null 2>&1 44 | if [ "${?}" == "0" ]; then 45 | ls *\<* | xargs rm 46 | fi 47 | 48 | ls .*\<* >/dev/null 2>&1 49 | if [ "${?}" == "0" ]; then 50 | ls .*\<* | xargs rm 51 | fi 52 | 53 | ls *\>* >/dev/null 2>&1 54 | if [ "${?}" == "0" ]; then 55 | ls *\>* | xargs rm 56 | fi 57 | 58 | ls .*\>* >/dev/null 2>&1 59 | if [ "${?}" == "0" ]; then 60 | ls .*\>* | xargs rm 61 | fi 62 | 63 | -------------------------------------------------------------------------------- /plot_centos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | import scipy.stats as stats 5 | import sys 6 | import pylab as pl 7 | import json 8 | 9 | with open(sys.argv[1], 'r') as f: 10 | jsondata = json.load(f) 11 | 12 | package_scores = [] 13 | binary_scores = [] 14 | for package in jsondata.keys(): 15 | package_scores.append(jsondata[package]['package_score']) 16 | for binary in jsondata[package]['binary_scores']: 17 | binary_scores.append(jsondata[package]['binary_scores'][binary]) 18 | 19 | p = sorted(package_scores) 20 | b = sorted(binary_scores) 21 | 22 | by_package = '' + \ 23 | 'CentOS 7 CTL scores by package:\n' + \ 24 | ' n: ' + str(len(p)) + '\n' + \ 25 | ' max: ' + str(max(p)) + '\n' + \ 26 | ' min: ' + str(min(p)) + '\n' + \ 27 | ' mean: ' + str(np.mean(p)) + '\n' + \ 28 | ' median: ' + str(np.median(p)) + '\n' + \ 29 | ' stdev: ' + str(np.std(p)) 30 | 31 | by_binary = '' + \ 32 | 'CentOS 7 CTL scores by binary:\n' + \ 33 | ' n: ' + str(len(b)) + '\n' + \ 34 | ' max: ' + str(max(b)) + '\n' + \ 35 | ' min: ' + str(min(b)) + '\n' + \ 36 | ' mean: ' + str(np.mean(b)) + '\n' + \ 37 | ' median: ' + str(np.median(b)) + '\n' + \ 38 | ' stdev: ' + str(np.std(b)) 39 | 40 | print(by_package) 41 | print(by_binary) 42 | 43 | pfit = stats.norm.pdf(p, np.mean(p), np.std(p)) 44 | pl.plot(p, pfit, 'o') 45 | pl.hist(p, normed=True) 46 | pl.text(-200, 0.0125, by_package, fontsize=9) 47 | pl.show() 48 | 49 | -------------------------------------------------------------------------------- /compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | import scipy.stats as stats 5 | import sys 6 | import pylab as pl 7 | import json 8 | 9 | with open(sys.argv[1], 'r') as f: 10 | jsondata1 = json.load(f) 11 | with open(sys.argv[2], 'r') as f: 12 | jsondata2 = json.load(f) 13 | 14 | package_scores1 = [] 15 | binary_scores1 = [] 16 | for package in jsondata1.keys(): 17 | package_scores1.append(jsondata1[package]['package_score']) 18 | for binary in jsondata1[package]['binary_scores']: 19 | binary_scores1.append(jsondata1[package]['binary_scores'][binary]) 20 | 21 | package_scores2 = [] 22 | binary_scores2 = [] 23 | for package in jsondata2.keys(): 24 | package_scores2.append(jsondata2[package]['package_score']) 25 | for binary in jsondata2[package]['binary_scores']: 26 | binary_scores2.append(jsondata2[package]['binary_scores'][binary]) 27 | 28 | p1 = sorted(package_scores1) 29 | b1 = sorted(binary_scores1) 30 | 31 | p2 = sorted(package_scores2) 32 | b2 = sorted(binary_scores2) 33 | 34 | pfit1 = stats.norm.pdf(p1, np.mean(p1), np.std(p1)) 35 | pfit2 = stats.norm.pdf(p2, np.mean(p2), np.std(p2)) 36 | 37 | bfit1 = stats.norm.pdf(b1, np.mean(b1), np.std(b1)) 38 | bfit2 = stats.norm.pdf(b2, np.mean(b2), np.std(b2)) 39 | 40 | pl.figure(figsize=(15,5), dpi=150) 41 | 42 | pl.subplot(1, 2, 1) 43 | pl.hist(p1, normed=True, range=(-100,100), color='cornflowerblue', label='Fedora 27') 44 | pl.legend(loc='upper left') 45 | 46 | pl.subplot(1, 2, 2) 47 | pl.hist(p2, normed=True, range=(-100,100), color='crimson', label='RHEL 7') 48 | pl.legend(loc='upper left') 49 | 50 | pl.show() 51 | -------------------------------------------------------------------------------- /plot_rhel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | import scipy.stats as stats 5 | import sys 6 | import pylab as pl 7 | import json 8 | 9 | with open(sys.argv[1], 'r') as f: 10 | jsondata = json.load(f) 11 | 12 | package_scores = [] 13 | binary_scores = [] 14 | for package in jsondata.keys(): 15 | package_scores.append(jsondata[package]['package_score']) 16 | for binary in jsondata[package]['binary_scores']: 17 | binary_scores.append(jsondata[package]['binary_scores'][binary]) 18 | 19 | p = sorted(package_scores) 20 | b = sorted(binary_scores) 21 | 22 | by_package = '' + \ 23 | 'RHEL 7 CTL scores by package:\n' + \ 24 | ' n: ' + str(len(p)) + '\n' + \ 25 | ' max: ' + str(max(p)) + '\n' + \ 26 | ' min: ' + str(min(p)) + '\n' + \ 27 | ' mean: ' + str(np.mean(p)) + '\n' + \ 28 | ' median: ' + str(np.median(p)) + '\n' + \ 29 | ' stdev: ' + str(np.std(p)) 30 | 31 | by_binary = '' + \ 32 | 'RHEL 7 CTL scores by binary:\n' + \ 33 | ' n: ' + str(len(b)) + '\n' + \ 34 | ' max: ' + str(max(b)) + '\n' + \ 35 | ' min: ' + str(min(b)) + '\n' + \ 36 | ' mean: ' + str(np.mean(b)) + '\n' + \ 37 | ' median: ' + str(np.median(b)) + '\n' + \ 38 | ' stdev: ' + str(np.std(b)) 39 | 40 | print(by_package) 41 | print(by_binary) 42 | 43 | pfit = stats.norm.pdf(p, np.mean(p), np.std(p)) 44 | pl.plot(p, pfit, 'o') 45 | pl.hist(p, normed=True) 46 | pl.text(-200, 0.0125, by_package, fontsize=9) 47 | pl.show() 48 | 49 | # bfit = stats.norm.pdf(b, np.mean(b), np.std(b)) 50 | # pl.plot(b, bfit, 'o') 51 | # pl.hist(b, normed=True) 52 | # pl.show() 53 | 54 | -------------------------------------------------------------------------------- /plot_fedora.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | import scipy.stats as stats 5 | import sys 6 | import pylab as pl 7 | import json 8 | 9 | with open(sys.argv[1], 'r') as f: 10 | jsondata = json.load(f) 11 | 12 | package_scores = [] 13 | binary_scores = [] 14 | for package in jsondata.keys(): 15 | package_scores.append(jsondata[package]['package_score']) 16 | for binary in jsondata[package]['binary_scores']: 17 | binary_scores.append(jsondata[package]['binary_scores'][binary]) 18 | 19 | p = sorted(package_scores) 20 | b = sorted(binary_scores) 21 | 22 | by_package = '' + \ 23 | 'Fedora 27 CTL scores by package:\n' + \ 24 | ' n: ' + str(len(p)) + '\n' + \ 25 | ' max: ' + str(max(p)) + '\n' + \ 26 | ' min: ' + str(min(p)) + '\n' + \ 27 | ' mean: ' + str(np.mean(p)) + '\n' + \ 28 | ' median: ' + str(np.median(p)) + '\n' + \ 29 | ' stdev: ' + str(np.std(p)) 30 | 31 | by_binary = '' + \ 32 | 'Fedora 27 CTL scores by binary:\n' + \ 33 | ' n: ' + str(len(b)) + '\n' + \ 34 | ' max: ' + str(max(b)) + '\n' + \ 35 | ' min: ' + str(min(b)) + '\n' + \ 36 | ' mean: ' + str(np.mean(b)) + '\n' + \ 37 | ' median: ' + str(np.median(b)) + '\n' + \ 38 | ' stdev: ' + str(np.std(b)) 39 | 40 | print(by_package) 41 | print(by_binary) 42 | 43 | pfit = stats.norm.pdf(p, np.mean(p), np.std(p)) 44 | pl.plot(p, pfit, 'o') 45 | pl.hist(p, normed=True) 46 | pl.text(-134, 0.0125, by_package, fontsize=9) 47 | pl.show() 48 | 49 | # bfit = stats.norm.pdf(b, np.mean(b), np.std(b)) 50 | # pl.plot(b, bfit, 'o') 51 | # pl.hist(b, normed=True) 52 | # pl.show() 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .idea 104 | afC.txt 105 | afC_pdf.py 106 | afC_raw 107 | fedora27_scores.json 108 | rhel7_scores.json 109 | -------------------------------------------------------------------------------- /rhel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import os 5 | import sys 6 | import traceback 7 | 8 | from datetime import datetime 9 | 10 | sys.path.append('.') 11 | from cybertestlab import CyberTestLab 12 | 13 | __author__ = 'Jason Callaway' 14 | __email__ = 'jasoncallaway@fedoraproject.org' 15 | __license__ = 'GNU Public License v2' 16 | __version__ = '0.3' 17 | __status__ = 'beta' 18 | 19 | 20 | def main(argv): 21 | debug = True 22 | now = datetime.now() 23 | output_dir = sys.argv[1] 24 | repo_dir = sys.argv[2] 25 | swap_path = sys.argv[3] 26 | repos = ['.'] 27 | ctl = CyberTestLab.CyberTestLab(repo_dir=repo_dir, 28 | swap_path=swap_path, 29 | repo_list=repos, 30 | debug=True) 31 | ctl.redteam.funcs.mkdir_p(repo_dir) 32 | ctl.redteam.funcs.mkdir_p(swap_path) 33 | 34 | # if debug: 35 | # print('+ syncing repos') 36 | # ctl.repo_sync('reposync') 37 | 38 | for repo in ctl.repo_list: 39 | if debug: 40 | print('+ ' + repo) 41 | for root, dirs, files in os.walk(repo_dir + '/' + repo): 42 | for filename in files: 43 | if debug: 44 | print('+ ' + filename) 45 | results_dir = output_dir + '/' + filename[0] 46 | results_file = results_dir + '/' + filename + '.json' 47 | if not os.path.isfile(results_file): 48 | if debug: 49 | print('++ analyzing ' + filename) 50 | ctl.prep_swap() 51 | try: 52 | analyze(ctl, repo, filename, results_dir, results_file) 53 | except Exception as e: 54 | print('analysis failed on ' + filename) 55 | traceback.print_exc() 56 | continue 57 | 58 | 59 | def analyze(ctl, repo, filename, results_dir, results_file): 60 | ctl.prep_rpm(repo, filename) 61 | metadata = ctl.get_metadata(filename) 62 | elfs = ctl.find_elfs() 63 | if elfs: 64 | results = ctl.scan_elfs(filename, elfs) 65 | ctl.redteam.funcs.mkdir_p(results_dir) 66 | with open(results_file, 'w') as f: 67 | json.dump({'metadata': metadata, 68 | 'results': results}, f, indent=4) 69 | else: 70 | with open(results_file, 'w') as f: 71 | json.dump({'metadata': metadata, 72 | 'results': 'no elfs found'}, f, indent=4) 73 | 74 | 75 | if __name__ == "__main__": 76 | main(sys.argv) 77 | -------------------------------------------------------------------------------- /fedora.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import os 5 | import sys 6 | import traceback 7 | 8 | from datetime import datetime 9 | 10 | sys.path.append('.') 11 | from cybertestlab import CyberTestLab 12 | 13 | __author__ = 'Jason Callaway' 14 | __email__ = 'jasoncallaway@fedoraproject.org' 15 | __license__ = 'GNU Public License v2' 16 | __version__ = '0.3' 17 | __status__ = 'beta' 18 | 19 | 20 | def main(argv): 21 | debug = True 22 | now = datetime.now() 23 | output_dir = sys.argv[1] 24 | repo_dir = sys.argv[2] 25 | swap_path = sys.argv[3] 26 | repos = ['fedora','google-chrome','rpmfusion-free','rpmfusion-free-updates','rpmfusion-nonfree','rpmfusion-nonfree-updates','spideroak-one-stable','updates'] 27 | ctl = CyberTestLab.CyberTestLab(repo_dir=repo_dir, 28 | swap_path=swap_path, 29 | repos=repos, 30 | debug=True) 31 | ctl.redteam.funcs.mkdir_p(repo_dir) 32 | ctl.redteam.funcs.mkdir_p(swap_path) 33 | 34 | # if debug: 35 | # print('+ syncing repos') 36 | # ctl.repo_sync('reposync') 37 | 38 | for repo in ctl.repo_list: 39 | for root, dirs, files in os.walk(repo_dir + '/' + repo): 40 | for filename in files: 41 | if debug: 42 | print('+ ' + filename) 43 | results_dir = output_dir + '/' + filename[0] 44 | results_file = results_dir + '/' + filename + '.json' 45 | if not os.path.isfile(results_file): 46 | if debug: 47 | print('++ analyzing ' + filename) 48 | ctl.prep_swap() 49 | try: 50 | analyze(ctl, repo, filename, results_dir, results_file) 51 | except Exception as e: 52 | print('fedora analysis failed on ' + filename) 53 | traceback.print_exc() 54 | continue 55 | 56 | 57 | def analyze(ctl, repo, filename, results_dir, results_file): 58 | ctl.prep_rpm(repo, filename) 59 | metadata = ctl.get_metadata(filename) 60 | elfs = ctl.find_elfs() 61 | if elfs: 62 | results = ctl.scan_elfs(filename, elfs) 63 | ctl.redteam.funcs.mkdir_p(results_dir) 64 | with open(results_file, 'w') as f: 65 | json.dump({'metadata': metadata, 66 | 'results': results}, f, indent=4) 67 | else: 68 | with open(results_file, 'w') as f: 69 | json.dump({'metadata': metadata, 70 | 'results': 'no elfs found'}, f, indent=4) 71 | 72 | 73 | if __name__ == "__main__": 74 | main(sys.argv) 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cyber-test-lab 2 | 3 | **Note that as of May 2022 this project is no longer active.** 4 | 5 | Cyber Test Lab offers quantitative static and dynamic risk analysis of binaries 6 | 7 | This project is in beta. It can still be hard to get working. Contact [jasoncallaway@google.com](matilto:jasoncallaway@google.com) for help. 8 | 9 | If you're looking for a place to contribute, we need help with documentation! For development contributions, read on. 10 | 11 | ## Warning - this is broken 12 | We're still in the process of migrating this tool from the [old project](https://github.com/fedoraredteam/cyber-test-lab), but we're actively working on fixing it. Check back soon for updates. 13 | 14 | ## How to run CTL 15 | 16 | The CTL code can be executed from within a docker container, making cross-platform development much easier. These instructions assume [PyCharm](https://www.jetbrains.com/pycharm/) is your development environment, but others will work fine too. 17 | 18 | First, set up [PyCharm](https://www.jetbrains.com/pycharm/download/#section=linux) and [Docker](https://docs.docker.com/get-started/) on your system. 19 | 20 | Next, configure PyCharm's Docker plugin. Here's a [tutorial](https://blog.jetbrains.com/pycharm/2015/12/using-docker-in-pycharm/). 21 | 22 | Now it's time to build your CTL container. 23 | 24 | ```bash 25 | git clone https://github.com/redteam-project/cyber-test-lab 26 | cd cyber-test-lab/docker 27 | docker built -t fctl . 28 | ``` 29 | 30 | Then you can configure your [remote interpreter](https://www.jetbrains.com/help/pycharm/configuring-remote-interpreters-via-docker.html) in PyCharm. 31 | 32 | You've got one step left before you can run the CTL, which is downloading some packages. Since docker containers are ephemeral, you want to mount a local directory into the fctl container before syncing any repos. 33 | 34 | On Docker 17.06 or later: 35 | ```bash 36 | docker run --rm -ti \ 37 | --mount type=bind,source="~/fctl/fedora27",target=/repo \ 38 | fctl \ 39 | timeout 600 reposync -p /repo 40 | ``` 41 | 42 | On earlier versions: 43 | ```bash 44 | docker run --rm -ti -v /home/jason/fctl/fedora27:/repo fctl \ 45 | timeout 600 reposync -p /repo 46 | ``` 47 | 48 | Note that if you're using Fedora, RHEL, or a variant, you'll need to add a ```:z``` to the bind mount for SELinux relabeling. I.E., ```-v /home/jason/fctl/fedora27:/repo:z```. 49 | 50 | Now you should have some rpms with binaries to analyze. Note that we're using ```timeout``` to sync for 10 minutes to limit disk usage. Remove ```timeout 600``` if you want the whole shebang. 51 | 52 | The last step is to create a new run/debug configuration. But there are two tricky parts: 53 | 54 | 1. Be sure to pick the remote docker interpreter under "Python Interpreter" 55 | 2. Mount the repo using Docker Container Image Settings > Volume Bindings. Be sure to use the same mapping as above, i.e., ```/home/jason/fctl/fedora27``` to ```/repo```. 56 | -------------------------------------------------------------------------------- /scoring.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 3 | date: 20171210 4 | ctl_version: 0.2 5 | starting_score: 100 6 | complexity: 7 | cyclomatic_complexity: 8 | mean: 35 9 | stdev: 59 10 | stdev_coefficient: -10 11 | cycle_cost: 12 | mean: 500 13 | stdev: 790 14 | stdev_coefficient: -10 15 | hardening-check: 16 | Read-only relocations: 17 | 'yes': 0 18 | 'no': -20 19 | Position Independent Executable: 20 | 'yes': 0 21 | 'no': -20 22 | Stack protected: 23 | 'yes': 0 24 | 'no': -20 25 | Fortify Source functions: 26 | 'yes (some protected functions found)': 5 27 | 'yes': 10 28 | 'no': -5 29 | Immediate binding: 30 | 'yes': 0 31 | 'no': -20 32 | bad_functions: 33 | functions: 34 | # From MSDN SDL banned function calls article, CWE-242, FreeBSD Developers' Handbook 35 | - ChangeWindowMessageFilter 36 | - CharToOem 37 | - CharToOemA 38 | - CharToOemBuffA 39 | - CharToOemBuffW 40 | - CharToOemW 41 | - CopyMemory 42 | - IsBadCodePtr 43 | - IsBadHugeReadPtr 44 | - IsBadHugeWritePtr 45 | - IsBadReadPtr 46 | - IsBadStringPtr 47 | - IsBadWritePtr 48 | - OemToChar 49 | - OemToCharA 50 | - OemToCharW 51 | - Recommended:wnsprintf 52 | - RtlCopyMemory 53 | - StrCat 54 | - StrCatA 55 | - StrCatBuff 56 | - StrCatBuffA 57 | - StrCatBuffW 58 | - StrCatChainW 59 | - StrCatN 60 | - StrCatNA 61 | - StrCatNW 62 | - StrCatW 63 | - StrCpy 64 | - StrCpyA 65 | - StrCpyN 66 | - StrCpyNA 67 | - StrCpyNW 68 | - StrCpyW 69 | - StrLen 70 | - StrNCat 71 | - StrNCatA 72 | - StrNCatW 73 | - StrNCpy 74 | - StrNCpyA 75 | - StrNCpyW 76 | - _alloca 77 | - _fstrncat 78 | - _fstrncpy 79 | - _ftcscat 80 | - _ftcscpy 81 | - _getts 82 | - _gettws 83 | - _i64toa 84 | - _i64tow 85 | - _itoa 86 | - _itow 87 | - _makepath 88 | - _mbccat 89 | - _mbccpy 90 | - _mbscat 91 | - _mbscpy 92 | - _mbslen 93 | - _mbsnbcat 94 | - _mbsnbcpy 95 | - _mbsncat 96 | - _mbsncpy 97 | - _mbstok 98 | - _mbstrlen 99 | - _snprintf 100 | - _sntprintf 101 | - _sntscanf 102 | - _snwprintf 103 | - _splitpath 104 | - _stprintf 105 | - _stscanf 106 | - _tccat 107 | - _tccpy 108 | - _tcscat 109 | - _tcscpy 110 | - _tcsncat 111 | - _tcsncpy 112 | - _tcstok 113 | - _tmakepath 114 | - _tscanf 115 | - _tsplitpath 116 | - _ui64toa 117 | - _ui64tot 118 | - _ui64tow 119 | - _ultoa 120 | - _ultot 121 | - _ultow 122 | - _vsnprintf 123 | - _vsntprintf 124 | - _vsnwprintf 125 | - _vstprintf 126 | - _wmakepath 127 | - _wsplitpath 128 | - alloca 129 | - gets 130 | - lstrcat 131 | - lstrcatA 132 | - lstrcatW 133 | - lstrcatn 134 | - lstrcatnA 135 | - lstrcatnW 136 | - lstrcpy 137 | - lstrcpyA 138 | - lstrcpyW 139 | - lstrcpyn 140 | - lstrcpynA 141 | - lstrcpynW 142 | - lstrlen 143 | - lstrncat 144 | - makepath 145 | - memcpy 146 | - nsprintf 147 | - scanf 148 | - snprintf 149 | - snscanf 150 | - sntprintf _vsnprintf 151 | - snwscanf 152 | - sprintf 153 | - sprintfA 154 | - sprintfW 155 | - sscanf 156 | - strcat 157 | - strcatA 158 | - strcatW 159 | - strcpy 160 | - strcpyA 161 | - strcpyW 162 | - strcpynA 163 | - strlen 164 | - strncat 165 | - strncpy 166 | - strtok 167 | - swprintf 168 | - swscanf 169 | - vsnprintf 170 | - vsprintf 171 | - vswprintf 172 | - wcscat 173 | - wcscpy 174 | - wcslen 175 | - wcsncat 176 | - wcsncpy 177 | - wcstok 178 | - wmemcpy 179 | - wnsprintfA 180 | - wnsprintfW 181 | - wscanf 182 | - wsprintf 183 | - wsprintfA 184 | - wsprintfW 185 | - wvnsprintf 186 | - wvnsprintfA 187 | - wvnsprintfW 188 | - wvsprintf 189 | - wvsprintfA 190 | - wvsprintfW 191 | - gets 192 | - strcpy 193 | - strcat 194 | - getwd 195 | - gets 196 | - scanf 197 | - vfscanf 198 | - realpath 199 | - sprintf 200 | - vsprintf 201 | addend: -25 202 | 203 | 204 | -------------------------------------------------------------------------------- /cybertestlab/Scoring.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import json 5 | import yaml 6 | 7 | import numpy as np 8 | 9 | __author__ = 'Jason Callaway' 10 | __email__ = 'jasoncallaway@fedoraproject.org' 11 | __license__ = 'GNU Public License v2' 12 | __version__ = '0.3' 13 | __status__ = 'beta' 14 | 15 | 16 | class Scoring(object): 17 | def __init__(self, debug=False, schema='./scoring.yml'): 18 | self.debug = debug 19 | with open(schema, 'r') as f: 20 | self.score_schema = yaml.load(f) 21 | 22 | def score(self, path): 23 | scores = {} 24 | for root, dirs, files in os.walk(path): 25 | for directory in dirs: 26 | if self.debug: 27 | print('+ walking ' + path + '/' + directory) 28 | for r, d, f in os.walk(path + '/' + directory): 29 | for filename in f: 30 | abspath = path + '/' + directory + '/' + filename 31 | 32 | if self.debug: 33 | print('++ scoring ' + abspath) 34 | binary_scores = self.score_json(abspath) 35 | if binary_scores is None or len(binary_scores.keys()) == 0: 36 | continue 37 | 38 | bscores = [] 39 | for binary in binary_scores.keys(): 40 | bscores.append(binary_scores[binary]) 41 | package_score = np.mean(bscores) 42 | 43 | scores[filename] = {"package_score": package_score, 44 | "binary_scores": binary_scores} 45 | return scores 46 | 47 | def score_json(self, filename): 48 | try: 49 | with open(filename, 'r') as f: 50 | jsondata = json.load(f) 51 | except Exception as e: 52 | return None 53 | 54 | if type(jsondata['results']) is unicode: 55 | return None 56 | 57 | binary_scores = {} 58 | binaries = jsondata['results'].keys() 59 | for binary in binaries: 60 | if self.debug: 61 | print('+++ scoring binary ' + binary) 62 | score = self.score_binary(jsondata['results'][binary]) 63 | if score is None: 64 | continue 65 | binary_scores[binary] = score 66 | 67 | return binary_scores 68 | 69 | def score_binary(self, binary_json): 70 | score = self.score_schema['starting_score'] 71 | 72 | hardening = binary_json['hardening-check'] 73 | # quick sanity checks 74 | if len(hardening.keys()) == 0 or '/bin/sh' in hardening.keys(): 75 | return None 76 | if binary_json.get('find-libc-functions'): 77 | libc_functions = set(binary_json['find-libc-functions']) 78 | else: 79 | libc_functions = set() 80 | functions = set(binary_json['report-functions']) 81 | complexity = binary_json['complexity']['r2aa'] 82 | complexity_schema = self.score_schema['complexity'] 83 | hardening_schema = self.score_schema['hardening-check'] 84 | 85 | # handle cyclomatic complexity 86 | if type(complexity) is not unicode: 87 | if complexity['afCc'] is not None: 88 | c = int(complexity['afCc']) 89 | m = int(complexity_schema['cyclomatic_complexity']['mean']) 90 | s = int(complexity_schema['cyclomatic_complexity']['stdev']) 91 | o = int(complexity_schema['cyclomatic_complexity']['stdev_coefficient']) 92 | a = (c - m) / s 93 | score += a * o 94 | 95 | # handle cycle cost 96 | if complexity['afC'] is not None: 97 | c = int(complexity['afC']) 98 | m = int(complexity_schema['cycle_cost']['mean']) 99 | s = int(complexity_schema['cycle_cost']['stdev']) 100 | o = int(complexity_schema['cycle_cost']['stdev_coefficient']) 101 | a = (c - m) / s 102 | score += a * o 103 | 104 | # handle hardening check values 105 | for check in hardening_schema.keys(): 106 | for value in hardening_schema[check].keys(): 107 | if value in hardening[' ' + check]: 108 | score += hardening_schema[check][value] 109 | 110 | # check for bad functions 111 | bad_functions = set(self.score_schema['bad_functions']['functions']) 112 | addend = self.score_schema['bad_functions']['addend'] 113 | if len(bad_functions.union(libc_functions)) > 0: 114 | score += addend 115 | if len(bad_functions.union(functions)) > 0: 116 | score += addend 117 | 118 | return score 119 | -------------------------------------------------------------------------------- /cybertestlab/Analysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import subprocess 6 | import magic 7 | 8 | import r2pipe 9 | 10 | __author__ = 'Jason Callaway' 11 | __email__ = 'jasoncallaway@fedoraproject.org' 12 | __license__ = 'GNU Public License v2' 13 | __version__ = '0.3' 14 | __status__ = 'beta' 15 | 16 | 17 | class Analysis(object): 18 | def __init__(self, path=None, debug=False): 19 | 20 | self.debug = debug 21 | self.path = path 22 | self.hardening_check = '/usr/bin/hardening-check' 23 | 24 | def find_elfs(self, **kwargs): 25 | path = self.path 26 | if kwargs.get('path'): 27 | path = kwargs['path'] 28 | 29 | find_results = [] 30 | 31 | rootDir = path 32 | for dirName, subdirList, fileList in os.walk(path): 33 | for fname in fileList: 34 | the_file = os.path.join(path, fname) 35 | try: 36 | file_type = magic.from_file(the_file) 37 | if 'ELF' in file_type: 38 | find_results.append(the_file) 39 | except: 40 | # Sometimes we fail to read the file for various 41 | # reasons 42 | pass 43 | 44 | elfs = [] 45 | for result in filter(None, find_results): 46 | elfs.append(result.split(':')[0]) 47 | 48 | if len(elfs) == 0: 49 | return None 50 | else: 51 | return filter(None, elfs) 52 | 53 | def scan_elfs(self, elfs): 54 | if not elfs: 55 | raise Exception('scan_elfs: you gave me an empty list of elfs you dope') 56 | scan_results = {} 57 | 58 | for elf in elfs: 59 | if self.debug: 60 | print('++ elf: ' + elf.replace(self.path + '/', '')) 61 | binary = elf 62 | relative_binary = \ 63 | binary.replace(self.path + '/', '').replace('.', '_') 64 | 65 | scan_results[relative_binary] = {} 66 | scan_results[relative_binary]['filename'] = binary.replace( 67 | self.path + '/', '') 68 | 69 | # get hardening-check results 70 | cmd = self.hardening_check + ' ' + binary 71 | hardening_results = \ 72 | self.run_command(cmd) 73 | 74 | # turn the hardening-check results into a dict 75 | pretty_results = {} 76 | for hr in hardening_results.split('\n'): 77 | if self.path in hr: 78 | continue 79 | if hr == '': 80 | continue 81 | hrlist = hr.split(':') 82 | test = hrlist[0] 83 | finding = hrlist[1] 84 | pretty_results[test.rstrip()] = finding.rstrip().lstrip() 85 | scan_results[relative_binary]['hardening-check'] = pretty_results 86 | 87 | # get function report 88 | cmd = self.hardening_check + ' -R ' + binary 89 | hardening_results = \ 90 | self.run_command(cmd) 91 | # relevant stuff starts at 9th line 92 | scan_results[relative_binary]['report-functions'] = \ 93 | filter(None, hardening_results.split('\n')[8:]) 94 | 95 | # get libc functions 96 | cmd = self.hardening_check + ' -F ' + binary 97 | hcdashf = filter( 98 | None, 99 | self.run_command(cmd).split('\n') 100 | ) 101 | hcdashf_clean = [] 102 | for lib in hcdashf: 103 | if len(lib.split("'")) > 1: 104 | hcdashf_clean.append(lib.split("'")[1]) 105 | scan_results[relative_binary]['find-libc-functions'] = \ 106 | hcdashf_clean 107 | else: 108 | if self.debug: 109 | print('+++ ' + elf.replace(self.path + '/', '') + 110 | ' had no `hardening-check -F` output') 111 | 112 | scan_results[relative_binary]['complexity'] = self.get_complexity(binary) 113 | 114 | return scan_results 115 | 116 | def get_complexity(self, elf): 117 | if self.debug: 118 | print('++ get_complexity getting cyclomatic complexity via r2 for: ' + elf) 119 | complexity = 0 120 | cycles_cost = 0 121 | try: 122 | r2 = r2pipe.open(elf) 123 | if self.debug: 124 | print('++ starting aa') 125 | r2.cmd("aa") 126 | if elf.endswith('.so'): 127 | functions = r2.cmdj('afl') 128 | entry = 'entry' 129 | for f in functions: 130 | if f.get('name'): 131 | if f['name'] == 'entry0': 132 | entry = 'entry0' 133 | cycles_cost = r2.cmdj('afC @' + entry) 134 | complexity = r2.cmdj('afCc @' + entry) 135 | elif elf.endswith('.a'): 136 | complexity = r2.cmdj('afCc') 137 | else: 138 | cycles_cost = r2.cmdj('afC @main') 139 | complexity = r2.cmdj('afCc @main') 140 | except Exception as e: 141 | if self.debug: 142 | print('+ get_complexity caught exception: ' + str(e)) 143 | r2.quit() 144 | return {'r2aa': 'failed: ' + str(e)} 145 | 146 | r2.quit() 147 | return {'r2aa': 148 | {'afCc': complexity, 149 | 'afC': cycles_cost 150 | } 151 | } 152 | 153 | def run_command(self, cmd): 154 | try: 155 | p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, 156 | stderr=subprocess.STDOUT) 157 | results = p.communicate()[0] 158 | except Exception as e: 159 | raise Exception(cmd + ' failed: ' + str(e)) 160 | return results 161 | -------------------------------------------------------------------------------- /cybertestlab/CyberTestLab.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | import r2pipe 7 | import timeout_decorator 8 | 9 | from redteam import redteam 10 | 11 | __author__ = 'Jason Callaway' 12 | __email__ = 'jasoncallaway@fedoraproject.org' 13 | __license__ = 'GNU Public License v2' 14 | __version__ = '0.3' 15 | __status__ = 'beta' 16 | 17 | 18 | class CyberTestLab(object): 19 | def __init__(self, **kwargs): 20 | self.redteam = redteam.RedTeam(debug=True, 21 | init_trello=False, 22 | init_sapi=False, 23 | init_edb=False, 24 | connect_to_trello=False, 25 | cache_dir='~/.redteam') 26 | self.sapi = redteam.SAPI.SAPI() 27 | 28 | self.repo_dir = '/repo' 29 | if kwargs.get('repo_dir'): 30 | self.repo_dir = kwargs['repo_dir'] 31 | 32 | self.swap_path = '/fedora_swap' 33 | if kwargs.get('swap_path'): 34 | self.swap_path = kwargs['swap_path'] 35 | 36 | self.repo_list = ['fedora', 'updates'] 37 | if kwargs.get('repo_list'): 38 | self.repo_list = kwargs['repo_list'] 39 | 40 | self.hardening_check = self.redteam.funcs.which('hardening-check') 41 | if kwargs.get('hardening_check'): 42 | self.hardening_check = kwargs['hardening_check'] 43 | if not self.hardening_check: 44 | raise Exception('CyberTestLab: cannot find hardening-check') 45 | 46 | self.debug = False 47 | if kwargs.get('debug'): 48 | self.debug = kwargs['debug'] 49 | 50 | def repo_sync(self, command): 51 | args = '' 52 | if 'reposync' in command: 53 | args = ' -p ' + self.repo_dir 54 | else: 55 | raise Exception('CyberTestLab: unsupported repo type: ' + command) 56 | sync_cmd = self.redteam.funcs.which(command) + args 57 | r = self.redteam.funcs.run_command(sync_cmd, 'syncing repos') 58 | 59 | def prep_swap(self): 60 | rm = self.redteam.funcs.which('rm') 61 | cmd = rm + ' -Rf ' + self.swap_path + '/*' 62 | r = self.redteam.funcs.run_command(cmd, 'clean up swap path') 63 | 64 | def prep_rpm(self, repo, rpm): 65 | cp = self.redteam.funcs.which('cp') 66 | cmd = cp + ' ' + self.repo_dir + '/' + \ 67 | repo + '/' + rpm + ' ' + \ 68 | self.swap_path 69 | r = self.redteam.funcs.run_command(cmd, 'cp rpm to swap_path') 70 | 71 | # crack the rpm open 72 | # cd = self.redteam.funcs.which('cd') 73 | rpm2cpio = self.redteam.funcs.which('rpm2cpio') 74 | cpio = self.redteam.funcs.which('cpio') 75 | cmd = '(cd ' + self.swap_path + ' && ' + rpm2cpio + ' ' + \ 76 | rpm + ' | ' + cpio + ' -idm 2>&1 >/dev/null)' 77 | r = self.redteam.funcs.run_command(cmd, 'rpm2cpio pipe to cpio') 78 | 79 | def get_metadata(self, rpm): 80 | rpm_data = {} 81 | cmd = 'rpm -qip ' + self.swap_path + '/' + rpm 82 | # this is a list 83 | rpm_qip = self.redteam.funcs.run_command(cmd, 'rpm -qip') 84 | 85 | if len(rpm_qip.split('Description :')) > 1: 86 | not_description, description = \ 87 | rpm_qip.split('Description :') 88 | raw_metadata = not_description.split('\n') 89 | metadata = {} 90 | for line in raw_metadata: 91 | if line == '': 92 | continue 93 | k, v = line.split(':', 1) 94 | metadata[k.rstrip()] = v 95 | metadata['Description'] = description 96 | rpm_data['spec_data'] = metadata 97 | else: 98 | rpm_data['Description'] = 'unable to parse `rpm -qip` output' 99 | 100 | return rpm_data 101 | 102 | def find_elfs(self, **kwargs): 103 | swap_path = self.swap_path 104 | if kwargs.get('swap_path'): 105 | swap_path = kwargs['swap_path'] 106 | 107 | find_results = [] 108 | find = self.redteam.funcs.which('find') 109 | grep = self.redteam.funcs.which('grep') 110 | cmd = find + ' ' + swap_path + \ 111 | ' -type f -exec file {} \; | ' + grep + ' -i elf' 112 | find_results = self.redteam.funcs.run_command(cmd, 'find elfs') 113 | 114 | elfs = [] 115 | for result in filter(None, find_results.split('\n')): 116 | elfs.append(result.split(':')[0]) 117 | 118 | if len(elfs) == 0: 119 | return None 120 | else: 121 | return filter(None, elfs) 122 | 123 | def scan_elfs(self, rpm, elfs): 124 | if not elfs: 125 | raise Exception('scan_elfs: you gave me an empty list of elfs you dope') 126 | scan_results = {} 127 | 128 | for elf in elfs: 129 | if self.debug: 130 | print('++ elf: ' + elf.replace(self.swap_path + '/', '')) 131 | binary = elf 132 | relative_binary = \ 133 | binary.replace(self.swap_path + '/', '').replace('.', '_') 134 | 135 | scan_results[relative_binary] = {} 136 | scan_results[relative_binary]['rpm'] = rpm 137 | scan_results[relative_binary]['filename'] = binary.replace( 138 | self.swap_path + '/', '') 139 | 140 | # get hardening-check results 141 | cmd = self.hardening_check + ' ' + binary 142 | hardening_results = \ 143 | self.redteam.funcs.run_command(cmd, 'hardening-check') 144 | 145 | # turn the hardening-check results into a dict 146 | pretty_results = {} 147 | for hr in hardening_results.split('\n'): 148 | if self.swap_path in hr: 149 | continue 150 | if hr == '': 151 | continue 152 | hrlist = hr.split(':') 153 | test = hrlist[0] 154 | finding = hrlist[1] 155 | pretty_results[test.rstrip()] = finding.rstrip().lstrip() 156 | scan_results[relative_binary]['hardening-check'] = pretty_results 157 | 158 | # get function report 159 | cmd = self.hardening_check + ' -R ' + binary 160 | hardening_results = \ 161 | self.redteam.funcs.run_command(cmd, 'hardening-check -R') 162 | # relevant stuff starts at 9th line 163 | scan_results[relative_binary]['report-functions'] = \ 164 | filter(None, hardening_results.split('\n')[8:]) 165 | 166 | # get libc functions 167 | cmd = self.hardening_check + ' -F ' + binary 168 | hcdashf = filter( 169 | None, 170 | self.redteam.funcs.run_command(cmd, 171 | 'hardening-check -F').split('\n') 172 | ) 173 | hcdashf_clean = [] 174 | for lib in hcdashf: 175 | if len(lib.split("'")) > 1: 176 | hcdashf_clean.append(lib.split("'")[1]) 177 | scan_results[relative_binary]['find-libc-functions'] = \ 178 | hcdashf_clean 179 | else: 180 | if self.debug: 181 | print('+++ ' + elf.replace(self.swap_path + '/', '') + 182 | ' had no `hardening-check -F` output') 183 | 184 | scan_results[relative_binary]['complexity'] = self.get_complexity(binary) 185 | 186 | return scan_results 187 | 188 | @timeout_decorator.timeout(600) 189 | def get_complexity(self, elf): 190 | complexity = 0 191 | cycles_cost = 0 192 | try: 193 | r2 = r2pipe.open(elf) 194 | if self.debug: 195 | print('++ starting aa') 196 | r2.cmd("aa") 197 | if '.so.' in elf or elf.endswith('.so'): 198 | ccomplexities = [] 199 | complexities = [] 200 | functions = r2.cmdj('aflj') 201 | for f in functions: 202 | if f.get('name'): 203 | ccomplexities.append(r2.cmd('afCc @' + f['name'])) 204 | complexities.append(r2.cmd('afC @' + f['name'])) 205 | cycles_cost = max(complexities) 206 | complexity = max(ccomplexities) 207 | elif '.a.' in elf or elf.endswith('.a'): 208 | complexity = r2.cmdj('afCc') 209 | else: 210 | cycles_cost = r2.cmdj('afC @main') 211 | complexity = r2.cmdj('afCc @main') 212 | except Exception as e: 213 | if self.debug: 214 | print('++ get_complexity caught exception: ' + str(e)) 215 | r2.quit() 216 | return {'r2aa': 'failed: ' + str(e)} 217 | 218 | r2.quit() 219 | return {'r2aa': 220 | {'afCc': complexity, 221 | 'afC': cycles_cost 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------