├── LICENSE ├── README.md └── converthound.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Layer 8 Security LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Layer-8-Logo-Wide](https://user-images.githubusercontent.com/8293038/96061566-93d8af00-0e61-11eb-8b84-3fd207290be2.png) 2 | 3 | # ConvertHound 4 | Convert [BloodHound](https://github.com/BloodHoundAD/BloodHound) zip files to nmap xml for use in reporting software. Created by [Cory Wolff](https://github.com/cwolff411) from [Layer 8 Security](https://layer8security.com) 5 | 6 | ### Installation 7 | `git clone https://github.com/layer8secure/converthound` 8 | 9 | ### Usage 10 | `python3 converthound.py convert BLOODHOUND_ZIP_FILE` 11 | 12 | Should work without additional dependencies. 13 | 14 | Takes an BLOODHOUND ZIP FILE and outputs an nmap xml file of the discovered hosts and a csv file of discovered usernames. Outputs to the ./converthound directory. 15 | 16 | ![ConvertHound](https://user-images.githubusercontent.com/8293038/96060567-df3d8e00-0e5e-11eb-9587-60c5144e18bf.png) 17 | 18 | 19 | ### About 20 | 21 | Built as an easy way to convert BloodHound output files into data that can be imported into reporting software like Dradis and Plextrac. 22 | 23 | When a BloodHound zip file is converted two files are generated and placed in the `./converthound` folder: 24 | 25 | 1. **ORIGINAL_FILE_NAME**_computers.xml 26 | 2. **ORIGINAL_FILE_NAME**_users.csv 27 | 28 | The resulting xml file follows nmap DTD formatting and can be imported to any reporting software that supports nmap xml output. This file will include the hostname found during the SharpHound scan. No additional host info is included at this time. The second generated file is a csv listing all users that were found from SharpHound. This users file will include username, firstname, domain, email, title, and home directory if found. 29 | 30 | This is v1. Pull requests and collaboration welcomed. 31 | 32 | ### Author 33 | - [Cory Wolff](https://github.com/cwolff411) - Senior Security Consultant at [Layer 8 Security](https://layer8security.com) 34 | 35 | ### Roadmap 36 | - Add additional host info to xml output 37 | - Support additional output formats 38 | - Create csv of hosts with OS, open ports, etc 39 | 40 | For additional feature requests please submit an [issue](https://github.com/layer8secure/ConvertHound/issues/new) and add the `enhancement` tag. 41 | 42 | ### License 43 | [MIT License](https://opensource.org/licenses/MIT) -------------------------------------------------------------------------------- /converthound.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import os 5 | import argparse 6 | import json 7 | from zipfile import ZipFile 8 | 9 | dashline = "-" * 75 10 | 11 | def color(text): 12 | 13 | if text.startswith('[+]'): 14 | return "\033[%d;3%dm%s\033[0m" % (0, 2, text) 15 | if text.startswith('[-]'): 16 | return "\033[%d;3%dm%s\033[0m" % (0, 1, text) 17 | if text.startswith('[*]'): 18 | return "\033[%d;3%dm%s\033[0m" % (0, 3, text) 19 | 20 | def banner(): 21 | print(r""" 22 | _____ __ __ __ __ 23 | / ___/__ ___ _ _____ ____/ /_/ // /__ __ _____ ___/ / 24 | / /__/ _ \/ _ \ |/ / -_) __/ __/ _ / _ \/ // / _ \/ _ / 25 | \___/\___/_//_/___/\__/_/ \__/_//_/\___/\_,_/_//_/\_,_/ 26 | 27 | 28 | author: Cory Wolff 31 | company: Layer 8 32 | """) 33 | print(dashline + "\n") 34 | 35 | def python_check(): 36 | if sys.version_info.major != 3: 37 | print(color("[-] Please run with Python 3. Python 2 is ded.")) 38 | exit() 39 | 40 | def create_computers_xml(filename_prefix, raw_json): 41 | 42 | data = json.loads(raw_json) 43 | new_file_name = './converthound/' + filename_prefix + '_computers.xml' 44 | new_file = open(new_file_name, "w") 45 | 46 | xml_header = [''] 47 | new_file.writelines(xml_header) 48 | 49 | for c in data['computers']: 50 | hostname = c['Properties']['name'] 51 | os = c['Properties']['operatingsystem'] or "n/a" 52 | 53 | new_host = ['', 54 | '', 55 | '
', 56 | '', 57 | '', 58 | '', 59 | '', 60 | '', 61 | '', 62 | '', 63 | '', 64 | '', 65 | '', 66 | '', 67 | '', 68 | '', 69 | '', 70 | '', 71 | '', 72 | '', 73 | '', 74 | ''] 75 | 76 | new_file.writelines(new_host) 77 | 78 | xml_footer = [''] 79 | new_file.writelines(xml_footer) 80 | 81 | def create_users_file(filename_prefix, raw_json): 82 | 83 | data = json.loads(raw_json) 84 | new_file_name = './converthound/' + filename_prefix + '_users.csv' 85 | new_file = open(new_file_name, "w") 86 | 87 | new_file.writelines(['display_name, user_name, domain, email, title, home_directory\n']) 88 | 89 | for u in data['users']: 90 | new_user = str( u['Properties']['displayname'] or "none") + ',' + \ 91 | str( u['Properties']['name'] or "none" ) + ',' + \ 92 | str( u['Properties']['domain'] or "none" ) + ',' + \ 93 | str( u['Properties']['email'] or "none" ) + ',' + \ 94 | str( u['Properties']['title'] or "none" ) + ',' + \ 95 | str( u['Properties']['homedirectory'] or "none" ) + '\n' 96 | 97 | new_file.writelines(new_user) 98 | 99 | # 100 | # 101 | # Start of class 102 | # 103 | # 104 | class ConvertHound(object): 105 | 106 | def __init__(self): 107 | parser = argparse.ArgumentParser(usage='''converthound.py [] 108 | Possible Commands: 109 | // Convert a Bloodhound computers.json to nmap XML 110 | 111 | Try converthound.py -h for help with a particular command. 112 | ''') 113 | parser.add_argument('command', help='Command to run') 114 | 115 | args = parser.parse_args(sys.argv[1:2]) 116 | if not hasattr(self, args.command): 117 | print (color('[-] Unrecognized command')) 118 | parser.print_help() 119 | exit(1) 120 | # use dispatch pattern to invoke method with same name 121 | getattr(self, args.command)() 122 | 123 | 124 | def convert(self): 125 | parser = argparse.ArgumentParser(description='Convert a Bloodhound zip file to multiple output files.') 126 | parser.add_argument('zipfile', type=str) 127 | 128 | args = parser.parse_args(sys.argv[2:]) 129 | 130 | filename_prefix = args.zipfile.split("_")[0] 131 | 132 | if not (args.zipfile.endswith('.zip')): 133 | print(color('[-] You sure this is a zip file?')) 134 | exit() 135 | 136 | if not os.path.isdir('./converthound'): 137 | os.mkdir('converthound') 138 | 139 | try: 140 | print(color("[+] Reading zip file...")) 141 | 142 | with ZipFile(args.zipfile) as bloodzip: 143 | files = bloodzip.namelist() 144 | for f in files: 145 | if "computers" in f: 146 | print(color('[+] Creating computers file...')) 147 | computers_file = bloodzip.read(f) 148 | create_computers_xml(filename_prefix, computers_file) 149 | elif "users" in f: 150 | print(color('[+] Creating users file...')) 151 | users_file = bloodzip.read(f) 152 | create_users_file(filename_prefix, users_file) 153 | 154 | 155 | except IOError as e: 156 | errno, strerror = e.args 157 | print(color("[-] I/O error({0}): {1}".format(errno,strerror))) 158 | except: #handle other exceptions such as attribute errors 159 | print(color("[-] Unexpected error:" + sys.exc_info()[0])) 160 | pass 161 | 162 | 163 | 164 | if __name__ == '__main__': 165 | 166 | banner() 167 | python_check() 168 | 169 | ConvertHound() --------------------------------------------------------------------------------