#!/usr/bin/env python3
#=============================================================================#
# This script can detect hash collisions between exported API functions in
# multiple modules by either scanning a directory tree or just a single module.
# This script can also just output the correct hash value for any single API
# function for use with the 'api_call' function in 'block_api.asm'.
#
# Example: Detect fatal collisions against all modules in the C drive:
#     >hash.py /dir c:\
#
# Example: List the hashes for all exports from kernel32.dll (As found in 'c:\windows\system32\')
#     >hash.py /mod c:\windows\system32\ kernel32.dll
#
# Example: Simply print the correct hash value for the function kernel32.dll!WinExec
#     >hash.py kernel32.dll WinExec
#
# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
#=============================================================================#
import pefile
from sys import path
import os
import time
import sys

# Modify this path to pefile to suit your machine...
pefile_path = 'D:\\Development\\Frameworks\\pefile\\'

path.append(pefile_path)
collisions = [(0x006B8029, 'ws2_32.dll!WSAStartup'),
              (0xE0DF0FEA, 'ws2_32.dll!WSASocketA'),
              (0x6737DBC2, 'ws2_32.dll!bind'),
              (0xFF38E9B7, 'ws2_32.dll!listen'),
              (0xE13BEC74, 'ws2_32.dll!accept'),
              (0x614D6E75, 'ws2_32.dll!closesocket'),
              (0x6174A599, 'ws2_32.dll!connect'),
              (0x5FC8D902, 'ws2_32.dll!recv'),
              (0x5F38EBC2, 'ws2_32.dll!send'),

              (0x5BAE572D, 'kernel32.dll!WriteFile'),
              (0x4FDAF6DA, 'kernel32.dll!CreateFileA'),
              (0x13DD2ED7, 'kernel32.dll!DeleteFileA'),
              (0xE449F330, 'kernel32.dll!GetTempPathA'),
              (0x528796C6, 'kernel32.dll!CloseHandle'),
              (0x863FCC79, 'kernel32.dll!CreateProcessA'),
              (0xE553A458, 'kernel32.dll!VirtualAlloc'),
              (0x300F2F0B, 'kernel32.dll!VirtualFree'),
              (0x0726774C, 'kernel32.dll!LoadLibraryA'),
              (0x7802F749, 'kernel32.dll!GetProcAddress'),
              (0x601D8708, 'kernel32.dll!WaitForSingleObject'),
              (0x876F8B31, 'kernel32.dll!WinExec'),
              (0x9DBD95A6, 'kernel32.dll!GetVersion'),
              (0xEA320EFE, 'kernel32.dll!SetUnhandledExceptionFilter'),
              (0x56A2B5F0, 'kernel32.dll!ExitProcess'),
              (0x0A2A1DE0, 'kernel32.dll!ExitThread'),

              (0x6F721347, 'ntdll.dll!RtlExitUserThread'),

              (0x23E38427, 'advapi32.dll!RevertToSelf')
              ]

collisions_detected = {}
modules_scanned = 0
functions_scanned = 0

def ror(dword, bits):
    return (dword >> bits | dword << (32 - bits)) & 0xFFFFFFFF

def unicode(string, uppercase=True):
    result = ''
    if uppercase:
        string = string.upper()
    for c in string:
        result += c + '\x00'
    return result

def hash(module, function, bits=13, print_hash=True):
    module_hash = 0
    function_hash = 0
    for c in unicode(module + '\x00'):
        module_hash = ror(module_hash, bits)
        module_hash += ord(c)
    for c in str(function + b'\x00'):
        function_hash = ror(function_hash, bits)
        function_hash += ord(c)
    h = module_hash + function_hash & 0xFFFFFFFF
    if print_hash:
        print('[+] 0x%08X = %s!%s' % (h, module.lower(), function))
    return h

def scan(dll_path, dll_name, print_hashes=False, print_collisions=True):
    global modules_scanned
    global functions_scanned
    dll_name = dll_name.lower()
    modules_scanned += 1
    pe = pefile.PE(os.path.join(dll_path, dll_name))
    for export in pe.DIRECTORY_ENTRY_EXPORT.symbols:
        if export.name is None:
            continue
        h = hash(dll_name, export.name, print_hash=print_hashes)
        for (col_hash, col_name) in collisions:
            if col_hash == h and col_name != '%s!%s' % (dll_name, export.name):
                if h not in collisions_detected.keys():
                    collisions_detected[h] = []
                collisions_detected[h].append(
                    (dll_path, dll_name, export.name))
                break
        functions_scanned += 1

def scan_directory(dir):
    for dot, dirs, files in os.walk(dir):
        for file_name in files:
            if file_name[-4:] == '.dll':  # or file_name[-4:] == ".exe":
                scan(dot, file_name)
    print('\n[+] Found %d Collisions.\n' % (len(collisions_detected)))
    for h in collisions_detected.keys():
        for (col_hash, col_name) in collisions:
            if h == col_hash:
                detected_name = col_name
                break
        print('[!] Collision detected for 0x%08X (%s):' % (h, detected_name))
        for (collided_dll_path, collided_dll_name, collided_export_name) in collisions_detected[h]:
            print('\t%s!%s (%s)' %
                  (collided_dll_name, collided_export_name, collided_dll_path))
    print('\n[+] Scanned %d exported functions via %d modules.\n' %
          (functions_scanned, modules_scanned))

def usage():
    print(
        'Usage: hash.py [/dir <path>] | [/mod <path> <module.dll>] | [<module.dll> <function>]')


def main(argv=None):
    if not argv:
        argv = sys.argv
    if len(argv) == 1:
        usage()
    else:
        print('[+] Ran on %s\n' % (time.asctime(time.localtime())))
        if argv[1] == '/dir':
            print("[+] Scanning directory '%s' for collisions..." % argv[2])
            scan_directory(argv[2])
        elif argv[1] == '/mod':
            print("[+] Scanning module '%s' in directory '%s'..." %
                  (argv[3], argv[2]))
            scan(argv[2], argv[3], print_hashes=True)
        elif len(argv) < 3:
            usage()
        else:
            hash(argv[1], argv[2])

if __name__ == '__main__':
    main()
