-
-
Save RedTeams/0366ec9219d3c1b8ea9ddaca6b549c2b to your computer and use it in GitHub Desktop.
This script do most of the heavy lifting for generating C headers for Cobalt Strike BOF, but this is not 100% accurate.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
try: | |
import pefile | |
import argparse | |
import requests | |
from bs4 import BeautifulSoup | |
except ImportError as e: | |
print("[-] One of the following module is not installed:") | |
print("\t- bs4") | |
print("\t- pefile") | |
print("\t- argparse") | |
print("\t- requests") | |
sys.exit(1) | |
verbose = False | |
headers = set() | |
# C-Style structure because why not | |
class FunctionInfo: | |
Function: str = None | |
Link: str = None | |
Description: str = None | |
Module: str = None | |
Header: str = None | |
Library: str = None | |
Prototype: str = None | |
def get_exported_funtions(module: str): | |
if (os.path.exists(module) == False): | |
print("[-] Invalid module provided") | |
return | |
image = pefile.PE(module, fast_load=True) | |
image.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"]]) | |
functions = list() | |
for function in image.DIRECTORY_ENTRY_EXPORT.symbols: | |
functions.append(function.name) | |
return functions | |
def get_function_info(info: FunctionInfo): | |
if (info.Function is None or info.Function == ""): | |
print("[-] Invalid function provided") | |
return False | |
# Get the first info from the Microsoft API | |
# The next line is from @dtmsecurity????? Definitively yes. | |
url = "https://docs.microsoft.com/api/search?search={}&locale=en-us&scoringprofile=search_for_en_us_a_b_test&facet=category&%24top=1".format(info.Function) | |
if (verbose == True): | |
print("[>] Searching: {}".format(info.Function)) | |
resp = requests.get(url) | |
# Ensure that there is a valid value returned | |
blob = resp.json() | |
if (len(blob["results"]) < 1): | |
if (verbose == True): | |
print("[-] Unable to find documentation for {} ".format(info.Function)) | |
return False | |
# Get the URL of the documentation + description of the function | |
blob = blob["results"][0] | |
info.Link = blob["url"] | |
info.Description = blob["description"] | |
resp = requests.get(info.Link) | |
soup = BeautifulSoup(resp.text, "lxml") | |
# Get the Library | |
lib = soup.find("meta", {"name": "req.lib"}) | |
if (lib is None): | |
return False | |
info.Library = lib.get("content") | |
# Get the module | |
dll = soup.find("meta", {"name": "req.dll"}) | |
if (dll is None): | |
return False | |
info.Module = dll.get("content") | |
# Get the header | |
header = soup.find("meta", {"name": "req.header"}) | |
if (header is None): | |
return False | |
info.Header = header.get("content") | |
headers.add(info.Header.lower()) | |
# Get prototype of the function | |
prototype = soup.find_all("code", {"class": "lang-cpp"}) | |
if (len(prototype) == 0): | |
prototype = soup.find_all("code", {"class": "lang-c++"}) | |
if (len(prototype) != 0): | |
info.Prototype = prototype[0].text | |
else: | |
return False | |
return True | |
def add_documentation(info: FunctionInfo): | |
content = "" | |
content += "/// <summary>\n" | |
content += "/// {}\n".format(info.Description) | |
content += "/// Link: {}\n".format(info.Link) | |
content += "/// Header: {}\n".format(info.Header) | |
content += "/// Module: {}\n".format(info.Module) | |
content += "/// Library: {}\n".format(info.Library) | |
content += "/// </summary>\n" | |
return content | |
def add_prototype(info: FunctionInfo): | |
prefix = (info.Module.split(".")[0]).lower() | |
info.Prototype = info.Prototype.replace(info.Function, "{}${}".format(prefix, info.Function)) | |
return "__declspec(dllimport) {}\n".format(info.Prototype) | |
def main(): | |
print("") | |
print("Build Windows headers for Cobalt Strike BOF") | |
print("Copyright (C) 2020 Paul Laine (@am0nsec)") | |
print("https://ntamonsec.blogspot.com/\n") | |
print("disclaimer: ") | |
print("This script do most of the heavy lifting but this is not 100% accurate.") | |
print("If one function is missing, you can Google for the documentation.") | |
print("Also, you will probably have to fix few bits and piece prior to compilation.\n") | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-m", "--module", help="Module in which symbols will be extracted.", required=True) | |
parser.add_argument("-o", "--outfile", help="Name of the file in which the functions' prototype will be written.", required=False) | |
parser.add_argument("-v", "--verbose", help="Increase script verbosity.", required=False, action='store_true', default=False) | |
args = parser.parse_args() | |
if (args.verbose == True): | |
verbose = True | |
(head, tail) = os.path.split(args.module) | |
module_name = tail | |
module_path = args.module | |
if (args.outfile is None): | |
args.outfile = tail.split(".")[0] + ".h" | |
# Get all the exported functions | |
functions = get_exported_funtions(module_path) | |
# Start building the header | |
content = "" | |
failed = 0 | |
success = 0; | |
print("[>] Exported functions: {}".format(len(functions))) | |
for x in functions: | |
if (x is None or "_" in x.decode()): | |
failed += 1 | |
continue | |
info = FunctionInfo() | |
info.Function = x.decode() | |
if (get_function_info(info) == False): | |
failed += 1 | |
continue | |
if (info.Module.lower() != module_name): | |
failed += 1 | |
continue | |
content += add_documentation(info) | |
content += add_prototype(info) | |
success += 1 | |
# Write the header to disk | |
with open(args.outfile, "w") as file: | |
file.write("#include <windows.h>\n") | |
for header in headers: | |
file.write("#include <{}>\n".format(header)) | |
macro = "_" + args.outfile.replace(".", "_").upper() | |
file.write("#ifndef {}\n".format(macro)) | |
file.write("#define {}\n\n".format(macro)) | |
file.write(content) | |
file.write("#endif // !{}".format(macro)) | |
print("[+] Functions defined: {}".format(success)) | |
print("[-] Functions skipped: {}\n".format(failed)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment