├── README.md ├── bin ├── .gitignore ├── README.md ├── fetch.py └── requirements.txt ├── mal_unpack_lib ├── README.md ├── malunpack │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── malunpack.py │ └── requirements.txt └── test.py └── runner ├── .gitignore ├── README.md ├── mal_unpack_runner.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # mal_unpack_py 2 | 3 | Python wrappers for [mal_unpack](https://github.com/hasherezade/mal_unpack) 4 | 5 | # Getting started 6 | 7 | 1. Clone this repository: 8 | 9 | ```console 10 | git clone https://github.com/hasherezade/mal_unpack_py.git 11 | ``` 12 | 2. Fetch needed executables: go to [`bin`](bin), and follow the steps from the [README](bin/README.md). 13 | 3. Install [`runner`](runner) or [`malunpack_lib`](mal_unpack_lib) by folowing the steps described in the corresponding README 14 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.exe 3 | 4 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | ## Utils required by the Python wrappers (mal_unpack_lib, runner) 2 | 3 | To run correctly the library requires 3 executable files: 4 | - `mal_unpack.exe`: the lastest version available [here](https://github.com/hasherezade/mal_unpack/releases) 5 | - `dll_load32.exe`: the dll loader for 32 bit files* 6 | - `dll_load64.exe`: the dll loader for 64 bit files* 7 | 8 | _*from: https://github.com/hasherezade/pe_utils_ 9 | 10 | This script will automatically download them and copy to the appropriate locations. 11 | 12 | ### Usage: 13 | 14 | 1. Install requirements: 15 | ```console 16 | pip install -r requirements.txt 17 | ``` 18 | 19 | 2. Run using Python 3: 20 | ```console 21 | python fetch.py 22 | ``` 23 | 24 | It should fetch all the neccessary executables 25 | -------------------------------------------------------------------------------- /bin/fetch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """fetch.py: A helper downloading and installing dependencies required by mal_unpack Python wrappers""" 4 | 5 | __author__ = 'hasherezade (hasherezade.net)' 6 | __license__ = "MIT" 7 | __version__ = "1.0" 8 | 9 | import requests 10 | import zipfile 11 | import os 12 | from pathlib import Path 13 | 14 | url1 = 'https://github.com/hasherezade/pe_utils/releases/download/1.0/dll_load32.exe' 15 | url2 = 'https://github.com/hasherezade/pe_utils/releases/download/1.0/dll_load64.exe' 16 | url_mal_unp32 = "https://github.com/hasherezade/mal_unpack/releases/download/0.9.7/mal_unpack32.zip" 17 | url_mal_unp64 = "https://github.com/hasherezade/mal_unpack/releases/download/0.9.7/mal_unpack64.zip" 18 | 19 | def download_file(filename, url): 20 | try: 21 | r = requests.get(url, allow_redirects=True) 22 | except: 23 | return False 24 | 25 | if r.status_code != 200 or r.content is None: 26 | return False 27 | 28 | open(filename, 'wb').write(r.content) 29 | return True 30 | 31 | def is_windows_64bit(): 32 | return os.environ['PROCESSOR_ARCHITECTURE'].endswith('64') 33 | 34 | def main(): 35 | url_mal_unp = url_mal_unp32 36 | if (is_windows_64bit()): 37 | url_mal_unp = url_mal_unp64 38 | 39 | dirpath = ("UTIL_PATH=\""+ os.path.abspath(".") +"\\\"").encode('unicode-escape') 40 | open("../runner/util_path.py", 'wb').write(dirpath) 41 | open("../mal_unpack_lib/util_path.py", 'wb').write(dirpath) 42 | 43 | urls = {"dll_load32.exe" : url1, "dll_load64.exe" : url2, "mal_unpack.zip" : url_mal_unp} 44 | for fname in urls: 45 | if not download_file(fname, urls[fname]): 46 | print("[-] Could not download: " + fname) 47 | continue 48 | 49 | p = Path(fname) 50 | ext = p.suffix 51 | print("[+] Downloaded: " + fname) 52 | if ext == ".zip": 53 | with zipfile.ZipFile(fname,"r") as zip_ref: 54 | zip_ref.extractall(".") 55 | print("[+] Unpacked: " + fname) 56 | os.remove(fname) 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /bin/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /mal_unpack_lib/README.md: -------------------------------------------------------------------------------- 1 | # Mal Unpack Python Library 2 | 3 | This tool is a python library wrapper for Mal-unpack. Code has been adapted from the [python runner](../runner). 4 | 5 | # Getting started 6 | 7 | 1. Clone this repository: 8 | 9 | ```console 10 | git clone https://github.com/hasherezade/mal_unpack_py.git 11 | ``` 12 | 2. Fetch needed executables: go to [`bin`](../bin), and follow the steps from the [README](../bin/README.md). 13 | 3. Install `malunpack` library by following the [README](../mal_unpack_lib/malunpack/README.md) 14 | 4. Import `malunpack` library into your script, as in the below example: 15 | ```python 16 | import malunpack 17 | 18 | timeout = 3000 # default 2000 19 | dump_dir = "temp/dumps" 20 | 21 | #sample_unpack = malunpack.MalUnpack("../../sample.exe", timeout, dump_dir) 22 | 23 | sample_unpack = malunpack.MalUnpack("sample.exe") 24 | 25 | # Run Mal Unpack and get the JSON result from the dump folder specified 26 | scan, dump = sample_unpack.unpack_file() 27 | 28 | print(scan) 29 | print(dump) 30 | ``` 31 | 32 | # Authors 33 | 34 | - Mal_Unpack author: [@hasherezade](https://twitter.com/hasherezade) 35 | - Mal_Unpack Lib wrapper: [@fr0gger_](https://twitter.com/fr0gger_) 36 | 37 | # License 38 | 39 | See [here.](https://github.com/hasherezade/mal_unpack/blob/master/LICENSE) 40 | -------------------------------------------------------------------------------- /mal_unpack_lib/malunpack/.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.exe 3 | *log 4 | __pycache__/* 5 | dumps/* 6 | util_path.py 7 | -------------------------------------------------------------------------------- /mal_unpack_lib/malunpack/README.md: -------------------------------------------------------------------------------- 1 | Install requirements: 2 | ```console 3 | pip install -r requirements.txt 4 | ``` 5 | -------------------------------------------------------------------------------- /mal_unpack_lib/malunpack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasherezade/mal_unpack_py/01ecfe0a6c1f8ae9cbaff699ac3c01e2832e2f97/mal_unpack_lib/malunpack/__init__.py -------------------------------------------------------------------------------- /mal_unpack_lib/malunpack/malunpack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """This is a library wrapper for Mal-Unpack. 3 | Mal-Unpack author: @hasherazade 4 | Mal_Unpack_Lib wrapper author: @fr0gger_ """ 5 | 6 | import sys 7 | import os 8 | import subprocess 9 | from pathlib import Path 10 | import shutil 11 | import uuid 12 | import hashlib 13 | import json 14 | import pefile 15 | try: 16 | from util_path import UTIL_PATH 17 | except: 18 | UTIL_PATH="..\\..\\bin\\" 19 | 20 | MAL_UNPACK_EXE = "mal_unpack.exe" 21 | DLL_LOAD64 = "dll_load64.exe" 22 | DLL_LOAD32 = "dll_load32.exe" 23 | 24 | 25 | class MalUnpack: 26 | """ MalUnpack Python Class wrapper for Mal-Unpack exe. 27 | Unpacking malware samples using mal-unpack executable. 28 | 29 | Classes: 30 | malUnpack 31 | 32 | Functions: 33 | calc_sha(FILE) 34 | rename_sample(FILE, IS_DLL) 35 | run_and_dump(FILE, DUMP_DIR, TIMEOUT) 36 | unpack_file(FILE) - Main Runner 37 | """ 38 | def __init__(self, filename, timeout=2000, dump_dir="dumps"): 39 | # Load PE and check characteristics and architecture 40 | try: 41 | exe = pefile.PE(filename, fast_load=True) 42 | self.is_dll = False 43 | if exe.is_exe() is False: 44 | self.is_dll = True 45 | self.is_64b = None 46 | 47 | if exe.OPTIONAL_HEADER.Magic == 0x10b: 48 | self.is_64b = False 49 | 50 | elif exe.OPTIONAL_HEADER.Magic == 0x20b: 51 | self.is_64b = True 52 | exe.close() 53 | 54 | except OSError as error: 55 | print(error) 56 | sys.exit() 57 | 58 | except pefile.PEFormatError as error: 59 | print(error.value) 60 | sys.exit() 61 | 62 | self.filename = filename 63 | self.timeout = timeout 64 | self.task_name = str(uuid.uuid4()) 65 | self.dump_dir = dump_dir 66 | 67 | def calc_sha(self): 68 | """calculate sha256""" 69 | with open(self.filename, "rb") as sample_file: 70 | rbytes = sample_file.read() 71 | sha_digest = hashlib.sha256(rbytes).hexdigest() 72 | return sha_digest 73 | 74 | def rename_sample(self, is_dll): 75 | """renaming the sample""" 76 | pathfile = Path(self.task_name) 77 | new_ext = ".exe" 78 | if is_dll: 79 | new_ext = ".dll" 80 | new_name = pathfile.stem + new_ext 81 | directory = str(pathfile.parent) 82 | pathfile.rename(Path(pathfile.parent, new_name)) 83 | abs_name = directory + os.path.sep + new_name 84 | return abs_name 85 | 86 | def run_and_dump(self, dump_dir, timeout): 87 | """Main function that runs the mal-unpack executable.""" 88 | # check required executable from malunpack: 89 | mal_unpack_path = os.path.join(UTIL_PATH, MAL_UNPACK_EXE) 90 | dll_load32_path = os.path.join(UTIL_PATH, DLL_LOAD32) 91 | dll_load64_path = os.path.join(UTIL_PATH, DLL_LOAD64) 92 | 93 | if not os.path.isfile(mal_unpack_path): 94 | print("[ERROR] MalUnpack binary missing. Please copy \'" + MAL_UNPACK_EXE + "\' to the \'" + UTIL_PATH + "\' directory") 95 | sys.exit() 96 | if not os.path.isfile(dll_load64_path): 97 | print("[ERROR] DLL load binary missing. Please copy \'" + DLL_LOAD64 + "\' to the \'" + UTIL_PATH + "\' directory") 98 | sys.exit() 99 | if not os.path.isfile(dll_load32_path): 100 | print("[ERROR] DLL load binary missing. Please copy \'" + DLL_LOAD32 + "\' to the \'" + UTIL_PATH + "\' directory") 101 | sys.exit() 102 | 103 | sample = self.rename_sample(self.is_dll) 104 | 105 | cmd = [mal_unpack_path, 106 | '/timeout', str(timeout), 107 | '/dir', dump_dir, 108 | '/img', sample, 109 | '/hooks', '1', 110 | '/shellc', '1', 111 | '/trigger', 'T'] 112 | 113 | if self.is_dll: 114 | cmd.append('/cmd') 115 | 116 | # run the first exported function 117 | cmd.append(sample + ' #1') 118 | if self.is_64b: 119 | sample = dll_load64_path 120 | else: 121 | sample = dll_load32_path 122 | 123 | cmd.append('/exe') 124 | cmd.append(sample) 125 | result = subprocess.run(cmd, check=False, capture_output=True) 126 | os.remove(sample) 127 | 128 | returncodedic = {-1: "ERROR", 0: "INFO", 1: "NOT_DETECTED", 2: "DETECTED"} 129 | 130 | if result.returncode is None: 131 | print("[!] mal_unpack failed to run") 132 | sys.exit() 133 | 134 | for code, value in returncodedic.items(): 135 | if result.returncode == code: 136 | print("[INFO] mal_unpack result: " + value) 137 | 138 | print("\n[INFO] Mal Unpack exe output:") 139 | print((result.stdout).decode('utf-8')) 140 | 141 | if result.stdout is None: 142 | print("[!] mal_unpack failed to run") 143 | if not os.path.exists(self.dump_dir): 144 | os.makedirs(self.dump_dir) 145 | 146 | filepath = self.dump_dir + os.path.sep + "mal_unp.stdout.txt" 147 | with open(filepath, "ab") as handle: 148 | handle.write(result.stdout) 149 | 150 | def unpack_file(self): 151 | """Unpacking the sample and copy in the right directory.""" 152 | sample_hash = self.calc_sha() 153 | dir_name = self.dump_dir + os.path.sep + sample_hash 154 | print("[+] Dump Directory: " + dir_name) 155 | shutil.copy(self.filename, self.task_name) 156 | 157 | self.run_and_dump(dir_name, self.timeout) 158 | 159 | # check result JSON 160 | resultpath = self.dump_dir + os.path.sep + self.calc_sha() 161 | 162 | dump_json = None 163 | scan_json = None 164 | # store json file to variable and return to caller 165 | for root, dirs, files in os.walk(resultpath): 166 | for name in files: 167 | if name.endswith("scan_report.json"): 168 | jsonpath = root + os.path.sep + name 169 | # print("[INFO] Scan json: " + jsonpath) 170 | scanjson = open(jsonpath) 171 | scan_json = json.load(scanjson) 172 | 173 | if name.endswith("dump_report.json"): 174 | jsonpath = root + os.path.sep + name 175 | # print("[INFO] Dump json: " + jsonpath) 176 | dumpjson = open(jsonpath) 177 | dump_json = json.load(dumpjson) 178 | 179 | return(scan_json, dump_json) 180 | -------------------------------------------------------------------------------- /mal_unpack_lib/malunpack/requirements.txt: -------------------------------------------------------------------------------- 1 | pefile 2 | uuid 3 | -------------------------------------------------------------------------------- /mal_unpack_lib/test.py: -------------------------------------------------------------------------------- 1 | from malunpack import malunpack # malunpack library 2 | import requests # for downloading the example 3 | 4 | def download_file(filename, url): 5 | try: 6 | r = requests.get(url, allow_redirects=True) 7 | except: 8 | return False 9 | 10 | if r.status_code != 200 or r.content is None: 11 | return False 12 | 13 | open(filename, 'wb').write(r.content) 14 | return True 15 | 16 | def main(): 17 | sample_name = "RunPE.exe" 18 | sample_url = "https://github.com/hasherezade/pesieve_tests/blob/main/32b/runpe/RunPE.exe?raw=true" 19 | if not download_file(sample_name,sample_url): 20 | print("[-] Downloading of the example failed!") 21 | exit(-1) 22 | 23 | #sample_unpack = malunpack.MalUnpack(sample_name, timeout, dump_dir) 24 | sample_unpack = malunpack.MalUnpack(sample_name) 25 | 26 | # Get some basic details about the sample 27 | #print(sample_unpack.filename) 28 | #print(sample_unpack.calc_sha()) 29 | #print(sample_unpack.is_dll) 30 | 31 | # Get the JSON result from MalUnpack 32 | scan, dump = sample_unpack.unpack_file() 33 | 34 | print("Scan report:") 35 | print(scan) 36 | 37 | print("Dump report:") 38 | print(dump) 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /runner/.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.pyc 3 | *.log 4 | dumps/* 5 | util_path.py 6 | -------------------------------------------------------------------------------- /runner/README.md: -------------------------------------------------------------------------------- 1 | # MalUnpack Runner 2 | 3 | A Python helper to deploy mal_unpack. 4 | Allows to: 5 | + autodetect file extension and unpack EXE as well as a DLL. 6 | + unpack a single file as well as full directory at once. 7 | 8 | ## Installation 9 | 10 | 1. Fetch needed executables: go to [`bin`](../bin), and follow the steps from the [README](../bin/README.md). 11 | 2. Install requirements: 12 | 13 | ```console 14 | pip install -r requirements.txt 15 | ``` 16 | 3. Run `mal_unpack_runner.py` using Python 3 17 | -------------------------------------------------------------------------------- /runner/mal_unpack_runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """mal_unpack_runner.py: A Python helper to deploy mal_unpack.""" 4 | 5 | __author__ = 'hasherezade (hasherezade.net)' 6 | __license__ = "MIT" 7 | __version__ = "1.0" 8 | 9 | import sys, os, subprocess 10 | from pathlib import Path 11 | import shutil 12 | import pefile 13 | import uuid 14 | import hashlib 15 | import argparse 16 | try: 17 | from util_path import UTIL_PATH 18 | except: 19 | UTIL_PATH="..\\bin\\" 20 | 21 | MAL_UNPACK_EXE = "mal_unpack.exe" 22 | DLL_LOAD64 = "dll_load64.exe" 23 | DLL_LOAD32 = "dll_load32.exe" 24 | DUMPS_DIR = "dumps" 25 | 26 | def mal_unp_res_to_str(returncode): 27 | if returncode == (-1): 28 | return "ERROR" 29 | if returncode == 0: 30 | return "INFO" 31 | if returncode == 1: 32 | return "NOT_DETECTED" 33 | if returncode == 2: 34 | return "DETECTED" 35 | return hex(returncode) 36 | 37 | def get_config(sample): 38 | null_config = (None, None) 39 | try: 40 | pe = pefile.PE(sample, fast_load=True) 41 | if pe is None: 42 | return null_config 43 | is_dll = False 44 | if (pe.is_exe() == False): 45 | is_dll = True 46 | is_64b = None 47 | if (pe.OPTIONAL_HEADER.Magic == 0x10b): 48 | is_64b = False 49 | elif (pe.OPTIONAL_HEADER.Magic == 0x20b): 50 | is_64b = True 51 | pe.close() 52 | if (is_64b == None): 53 | return null_config 54 | return (is_64b, is_dll) 55 | except: 56 | return null_config 57 | 58 | def rename_sample(sample, is_dll): 59 | p = Path(sample) 60 | new_ext = ".exe" 61 | if (is_dll): 62 | new_ext = ".dll" 63 | new_name = p.stem + new_ext 64 | directory = str(p.parent) 65 | p.rename(Path(p.parent, new_name)) 66 | abs_name = directory + os.path.sep + new_name 67 | return abs_name 68 | 69 | def log_mal_unp_out(outstr, dump_dir, filename): 70 | if outstr is None: 71 | return 72 | if not os.path.exists(dump_dir): 73 | os.makedirs(dump_dir) 74 | filepath = dump_dir + os.path.sep + filename 75 | with open(filepath, "ab") as handle: 76 | handle.write(outstr) 77 | 78 | def run_and_dump(sample, is_64b, is_dll, timeout, sample_out_dir, root_out_dir): 79 | mal_unpack_path = os.path.join(UTIL_PATH, MAL_UNPACK_EXE) 80 | dll_load32_path = os.path.join(UTIL_PATH, DLL_LOAD32) 81 | dll_load64_path = os.path.join(UTIL_PATH, DLL_LOAD64) 82 | 83 | print("Is 64b: " + str(is_64b)) 84 | print("Is DLL: " + str(is_dll)) 85 | 86 | orig_name = sample 87 | sample = rename_sample(sample, is_dll) 88 | print("Sample name: " + sample) 89 | 90 | cmd = [ mal_unpack_path, 91 | '/timeout' , str(timeout), 92 | '/dir', sample_out_dir, 93 | '/img', sample, 94 | '/hooks', '1', 95 | '/shellc' , '1', 96 | '/trigger', 'T' 97 | ] 98 | 99 | exe_name = sample 100 | if is_dll: 101 | cmd.append('/cmd') 102 | cmd.append(sample + ' #1') #run the first exported function 103 | if is_64b: 104 | exe_name = dll_load64_path 105 | else: 106 | exe_name = dll_load32_path 107 | 108 | cmd.append('/exe') 109 | cmd.append(exe_name) 110 | result = subprocess.run(cmd, check=False, capture_output=True) 111 | try: 112 | os.remove(sample) 113 | except FileNotFoundError: 114 | print("The sample autodeleted itself") 115 | 116 | if (result.returncode is None): 117 | print("mal_unpack failed to run") 118 | return 119 | print("mal_unpack result: " + mal_unp_res_to_str(result.returncode)) 120 | log_mal_unp_out(result.stdout, root_out_dir, "mal_unp.stdout.txt") 121 | log_mal_unp_out(result.stderr, root_out_dir, "mal_unp.stderr.txt") 122 | 123 | def calc_sha(filename): 124 | with open(filename, "rb") as f: 125 | rbytes = f.read() 126 | sha_digest = hashlib.sha256(rbytes).hexdigest() 127 | return sha_digest 128 | 129 | def unpack_file(orig_file, timeout, out_dir): 130 | 131 | sample_hash = calc_sha(orig_file) 132 | task_name = str(uuid.uuid4()) 133 | print("File: " + orig_file) 134 | print("sha256: " + sample_hash) 135 | print("task ID: " + task_name) 136 | 137 | print("starting...") 138 | sample_out_dir = out_dir + os.path.sep + sample_hash 139 | 140 | is_64b, is_dll = get_config(orig_file) 141 | if is_64b == None: 142 | print("[-] Not a valid PE") 143 | return 144 | 145 | shutil.copy(orig_file, task_name) 146 | run_and_dump(task_name, is_64b, is_dll, timeout, sample_out_dir, out_dir) 147 | 148 | def unpack_dir(rootdir, timeout, out_dir): 149 | for subdir, dirs, files in os.walk(rootdir): 150 | for file in files: 151 | filepath = str(os.path.join(subdir, file)) 152 | if not os.path.isfile(filepath): 153 | # it is a directory, walk recursively: 154 | unpack_dir(filepath, timeout, out_dir) 155 | continue 156 | print(filepath) 157 | unpack_file(filepath, timeout, out_dir) 158 | 159 | def main(): 160 | 161 | # check required elements: 162 | if not os.path.isfile(os.path.join(UTIL_PATH, MAL_UNPACK_EXE)): 163 | print("[ERROR] MalUnpack binary missing. Please copy \'" + MAL_UNPACK_EXE + "\' to the \'" + UTIL_PATH + "\' directory") 164 | return 165 | if not os.path.isfile(os.path.join(UTIL_PATH, DLL_LOAD64)): 166 | print("[ERROR] DLL load binary missing. Please copy \'" + DLL_LOAD64 + "\' to the \'" + UTIL_PATH + "\' directory") 167 | return 168 | if not os.path.isfile(os.path.join(UTIL_PATH, DLL_LOAD32)): 169 | print("[ERROR] DLL load binary missing. Please copy \'" + DLL_LOAD32 + "\' to the \'" + UTIL_PATH + "\' directory") 170 | return 171 | 172 | # parse input arguments: 173 | parser = argparse.ArgumentParser(description="MalUnpack Runner") 174 | parser.add_argument('--inpath', dest="inpath", default=None, help="Malware(s) to be unpacked: it can be a file or a directory", 175 | required=True) 176 | parser.add_argument('--outpath', dest="outpath", default=DUMPS_DIR, help="The directory where the output will be dumped") 177 | parser.add_argument('--timeout', dest="timeout", default=1000, help="Timeout, default = 1000", type=int) 178 | args = parser.parse_args() 179 | 180 | # run: 181 | isFile = False 182 | if os.path.isfile(args.inpath): 183 | isFile = True 184 | elif os.path.isdir(args.inpath): 185 | isFile = False 186 | else: 187 | print("[ERROR] The given path does not exist" + args.inpath) 188 | return 189 | 190 | timeout = args.timeout 191 | if isFile: 192 | unpack_file(args.inpath, timeout, args.outpath) 193 | else: 194 | unpack_dir(args.inpath, timeout, args.outpath) 195 | 196 | if __name__ == "__main__": 197 | main() 198 | 199 | -------------------------------------------------------------------------------- /runner/requirements.txt: -------------------------------------------------------------------------------- 1 | pefile 2 | uuid 3 | --------------------------------------------------------------------------------