Skip to content

Commit

Permalink
fix #8; save auto-analysis, IOCTLs, dumb IOCTLs, pooltags
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidSec committed Dec 10, 2021
1 parent 5017dfb commit 63ec844
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 64 deletions.
99 changes: 68 additions & 31 deletions DriverBuddyReloaded.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
DriverBuddyReloaded.py: Entry point for IDA python plugin used in Windows driver vulnerability research.
Updated in 2021 by Paolo Stagno aka VoidSec: https://voidsec.com - https://twitter.com/Void_Sec
"""
# needed GLOBALs
driver_name = idaapi.get_root_filename()
path = "{}{}".format(os.getcwd(), os.sep)
ioctl_file_name = "{}-{}-{}-IOCTLs.txt".format(driver_name, utils.today(), utils.timestamp())
analysis_file_name = "{}-{}-{}-DriverBuddyReloaded_autoanalysis.txt".format(driver_name, utils.today(),
utils.timestamp())
pool_file_name = "{}-{}-{}-pooltags.txt".format(driver_name, utils.today(), utils.timestamp())


class UiAction(idaapi.action_handler_t):
Expand Down Expand Up @@ -147,8 +154,6 @@ def print_table(self, ioctls):
:param ioctls: IOCTL to decode
:return:
"""
driver_name = idaapi.get_root_filename()
ioctl_file_name = "{}-{}-IOCTLs.txt".format(driver_name, utils.today())
try:
with open(ioctl_file_name, "w") as IOCTL_file:
print("\nDriver Buddy Reloaded - IOCTLs\n"
Expand All @@ -169,9 +174,10 @@ def print_table(self, ioctls):
access_code)
print("0x%-8X | 0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)" % all_vars)
IOCTL_file.write("0x%-8X | 0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)\n" % all_vars)
print("\n[>] Saved decoded IOCTLs to \"{}{}{}\"".format(os.getcwd(), os.sep, ioctl_file_name))
print("\n[>] Saved decoded IOCTLs to \"{}{}\"".format(path, ioctl_file_name))
except IOError as e:
print("ERROR #{}: Can't write to {}; {}".format(e.errno, ioctl_file_name, e.strerror))
print("ERROR #{}: {}\nCan't save decoded IOCTLs to \"{}{}\"".format(e.errno, e.strerror, path,
ioctl_file_name))
print("\nDriver Buddy Reloaded - IOCTLs\n"
"-----------------------------------------------")
print("%-10s | %-10s | %-42s | %-10s | %-22s | %s" % (
Expand All @@ -195,7 +201,7 @@ def find_all_ioctls():
"""

ioctls = []
# Find the currently selected function and get a list of all of it's basic blocks
# Find the currently selected function and get a list of all of its basic blocks
addr = idc.get_screen_ea()
f = idaapi.get_func(addr)
fc = idaapi.FlowChart(f, flags=idaapi.FC_PREDS)
Expand Down Expand Up @@ -422,33 +428,64 @@ def run(self, args):
:param args:
:return:
"""
print("\nDriver Buddy Reloaded Auto-analysis\n"
"-----------------------------------------------")
idc.auto_wait() # Wait for IDA analysis to complete
file_type = idaapi.get_file_type_name()
if "portable executable" not in file_type.lower():
print("[!] ERR: Loaded file is not a valid PE")
else:
driver_entry_addr = utils.is_driver()
if driver_entry_addr is False:
print("[!] ERR: Loaded file is not a Driver")
else:
print("[+] `DriverEntry` found at: 0x{addr:08x}".format(addr=driver_entry_addr))
print("[>] Searching for `DeviceNames`...")
device_name_finder.search()
print("[>] Searching for `Pooltags`... ")
pool = dump_pool_tags.get_all_pooltags()
if pool:
print(pool)
if utils.populate_data_structures() is True:
driver_type = utils.get_driver_id(driver_entry_addr)
print(("[+] Driver type detected: {}".format(driver_type)))
if ioctl_decoder.find_ioctls_dumb() is False:
print("[!] Unable to automatically find any IOCTLs")
try:
with open(analysis_file_name, "w") as log_file:
print("\nDriver Buddy Reloaded Auto-analysis\n"
"-----------------------------------------------")
log_file.write("\nDriver Buddy Reloaded Auto-analysis\n"
"-----------------------------------------------\n")
idc.auto_wait() # Wait for IDA analysis to complete
file_type = idaapi.get_file_type_name()
if "portable executable" not in file_type.lower():
print("[!] ERR: Loaded file is not a valid PE")
log_file.write("[!] ERR: Loaded file is not a valid PE\n")
else:
print("[!] ERR: Unable to enumerate functions")
print("[+] Analysis Completed!\n"
"-----------------------------------------------")
driver_entry_addr = utils.is_driver()
if driver_entry_addr is False:
print("[!] ERR: Loaded file is not a Driver")
log_file.write("[!] ERR: Loaded file is not a Driver\n")
else:
print("[+] `DriverEntry` found at: 0x{addr:08x}".format(addr=driver_entry_addr))
log_file.write("[+] `DriverEntry` found at: 0x{addr:08x}\n".format(addr=driver_entry_addr))
print("[>] Searching for `DeviceNames`...")
log_file.write("[>] Searching for `DeviceNames`...\n")
device_name_finder.search(log_file)
print("[>] Searching for `Pooltags`...")
log_file.write("[>] Searching for `Pooltags`...\n")
pool = dump_pool_tags.get_all_pooltags()
if pool:
print(pool)
try:
with open(pool_file_name, "w") as pool_file:
pool_file.write(pool)
except IOError as e:
print(
"ERROR #{}: {}\nCan't write pool file to \"{}{}\"".format(e.errno, e.strerror, path,
pool_file_name))
if utils.populate_data_structures(log_file) is True:
driver_type = utils.get_driver_id(driver_entry_addr, log_file)
print("[+] Driver type detected: {}".format(driver_type))
log_file.write("[+] Driver type detected: {}\n".format(driver_type))
if ioctl_decoder.find_ioctls_dumb(log_file, ioctl_file_name) is False:
print("[!] Unable to automatically find any IOCTLs")
log_file.write("[!] Unable to automatically find any IOCTLs\n")
else:
print("\n[>] Saved decoded IOCTLs log file to \"{}{}_dumb.txt\"".format(path,
ioctl_file_name))
else:
print("[!] ERR: Unable to enumerate functions")
log_file.write("[!] ERR: Unable to enumerate functions\n")
print("[+] Analysis Completed!\n"
"-----------------------------------------------")
log_file.write("[+] Analysis Completed!\n"
"-----------------------------------------------")
print("\n[>] Saved Autoanalysis log file to \"{}{}\"".format(path, analysis_file_name))
if pool:
print("[>] Saved Pooltags file to \"{}{}\"".format(path, pool_file_name))
except IOError as e:
print("ERROR #{}: {}\nAutoanalysis aborted, can't write log file to \"{}{}\"".format(e.errno, e.strerror,
path,
analysis_file_name))
return

def term(self):
Expand Down
21 changes: 17 additions & 4 deletions DriverBuddyReloaded/device_name_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import ida_nalt

ASCII_BYTE = b" !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t"
ASCII_BYTE = b" !\"#\\$%&\'\\(\\)\\*\\+,-\\./0123456789:;<=>\\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[\\]\\^_`abcdefghijklmnopqrstuvwxyz\\{\\|\\}\\\\~\t"
UNICODE_RE_4 = re.compile(b"((?:[%s]\x00){%d,})" % (ASCII_BYTE, 4))
REPEATS = ["A", "\x00", "\xfe", "\xff"]
SLICE_SIZE = 4096
Expand Down Expand Up @@ -73,9 +73,10 @@ def get_unicode_device_names():
return possible_names


def find_unicode_device_name():
def find_unicode_device_name(log_file):
"""
Attempts to find and output potential DeviceNames - returning False if none are found so further analysis can be done
:param log_file: log file handler
"""

possible_names = get_unicode_device_names()
Expand All @@ -84,39 +85,51 @@ def find_unicode_device_name():
if len(possible_names) == 1:
print(
"[!] The Device prefix was found but no full Device Paths; the DeviceName is likely obfuscated or created on the stack.")
log_file.write(
"[!] The Device prefix was found but no full Device Paths; the DeviceName is likely obfuscated or created on the stack.\n")
return False
elif '\\Device\\' in possible_names and '\\DosDevices\\' in possible_names:
print(
"[!] The Device prefix was found but no full Device Paths; the DeviceName is likely obfuscated or created on the stack.")
log_file.write(
"[!] The Device prefix was found but no full Device Paths; the DeviceName is likely obfuscated or created on the stack.\n")
return False
else:
# print("Potential DeviceName: ")
for i in possible_names:
if i != '\\Device\\' and i != '\\DosDevices\\':
print("\t- {}".format(i))
log_file.write("\t- {}\n".format(i))
return True
else:
# print("Potential DeviceNames: ")
for i in possible_names:
print("\t- {}".format(i))
log_file.write("\t- {}\n".format(i))
return True
elif len(possible_names) > 2:
# print("Possible devices names found:")
for i in possible_names:
print("\t- {}".format(i))
log_file.write("\t- {}\n".format(i))
return True
else:
print("[!] No potential DeviceNames found; it may be obfuscated or created on the stack in some way.")
log_file.write(
"[!] No potential DeviceNames found; it may be obfuscated or created on the stack in some way.\n")
return False


def search():
def search(log_file):
"""
Attempts to find potential DeviceNames in the currently opened binary.
It starts by searching for Unicode DeviceNames, if this fails then it suggests the analyst to use FLOSS
in order to search for stack based and obfuscated strings.
:param log_file: log file handler
"""

if not find_unicode_device_name():
if not find_unicode_device_name(log_file):
print(
"[!] Unicode DeviceName not found; try using FLOSS in order to recover obfuscated and stack based strings.")
log_file.write(
"[!] Unicode DeviceName not found; try using FLOSS in order to recover obfuscated and stack based strings.\n")
21 changes: 13 additions & 8 deletions DriverBuddyReloaded/find_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def FindInstructions(instr, asm_where=None):
seg = ida_segment.get_first_seg()
asm_where = seg.start_ea if seg else ida_idaapi.BADADDR
if asm_where == ida_idaapi.BADADDR:
return (False, "No segments defined")
return False, "No segments defined"

# regular expression to distinguish between opcodes and instructions
re_opcode = re.compile('^[0-9a-f]{2} *', re.I)
Expand All @@ -71,7 +71,7 @@ def FindInstructions(instr, asm_where=None):
# assemble the instruction
ret, buf = idautils.Assemble(asm_where, line)
if not ret:
return (False, "Failed to assemble:" + line)
return False, "Failed to assemble: {}".format(line)
# add the assembled buffer
bufs.append(buf)

Expand All @@ -96,9 +96,9 @@ def FindInstructions(instr, asm_where=None):
# ida_kernwin.msg(".")
ea += tlen
if not ret:
return (False, "Could not match {} - [{}]".format(instr, bin_str))
return False, "Could not match {} - [{}]".format(instr, bin_str)
# ida_kernwin.msg("\n")
return (True, ret)
return True, ret


# Chooser class
Expand Down Expand Up @@ -129,7 +129,7 @@ def OnSelectLine(self, n):

# class to represent the results
class SearchResult:
def __init__(self, ea):
def __init__(self, ea, log_file):
self.ea = ea
self.funcname_or_segname = ""
self.text = ""
Expand All @@ -151,13 +151,18 @@ def __init__(self, ea):
if opcode in self.text:
print(
"\t- Found {} in {} at 0x{addr:08x}".format(self.text, self.funcname_or_segname, addr=self.ea))
log_file.write("\t- Found {} in {} at 0x{addr:08x}\n".format(self.text, self.funcname_or_segname,
addr=self.ea))
else:
print("\t- Found {} in {} at 0x{addr:08x}".format(self.text, self.funcname_or_segname, addr=self.ea))
log_file.write(
"\t- Found {} in {} at 0x{addr:08x}\n".format(self.text, self.funcname_or_segname, addr=self.ea))


def find(s=None, x=False, asm_where=None):
def find(log_file, s=None, x=False, asm_where=None):
"""
Search for opcode/instruction
:param log_file: log file handler
:param s: opcode/instruction
:param x: if true search for executable code segments only
:param asm_where: where to start searching
Expand All @@ -173,9 +178,9 @@ def find(s=None, x=False, asm_where=None):
seg = ida_segment.getseg(ea)
if (not seg) or (seg.perm & ida_segment.SEGPERM_EXEC) == 0:
continue
results.append(SearchResult(ea))
results.append(SearchResult(ea, log_file))
else:
results = [SearchResult(ea) for ea in ret]
results = [SearchResult(ea, log_file) for ea in ret]
"""title = "Search result for: [%s]" % s
ida_kernwin.close_chooser(title)
c = SearchResultChoose(title, results)
Expand Down
40 changes: 37 additions & 3 deletions DriverBuddyReloaded/ioctl_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,19 @@ def get_define(ioctl_code):
return "#define %s CTL_CODE(0x%X, 0x%X, %s, %s)" % (name, device_code, function, method_name, access_name)


def find_ioctls_dumb():
def find_ioctls_dumb(log_file, ioctl_file_name):
"""
Attempts to locate any IOCTLs in driver automatically.
:return boolean: True if any IOCTLs found, False otherwise
:param log_file: log file handler
:param ioctl_file_name: IOCTL log file name
"""
ioctl_file_name = ioctl_file_name + "_dumb.txt"
result = False
cur = idc.ida_ida.inf_get_min_ea()
max = idc.ida_ida.inf_get_max_ea()
print("[>] Searching for IOCTLs found by IDA...")
log_file.write("[>] Searching for IOCTLs found by IDA...\n")
while cur < max:
# idc.find_text(ea, flag, y, x, searchstr, from_bc695=False)
# cur = find_text(cur, SEARCH_DOWN, 0, 0, "IoControlCode")
Expand All @@ -182,11 +186,41 @@ def find_ioctls_dumb():
else:
if idc.get_operand_type(cur, 0) == 5:
idc.op_dec(cur, 0)
get_ioctl_code(int(idc.print_operand(cur, 0)))
ioctl_code = int(idc.print_operand(cur, 0))
function = get_function(ioctl_code)
device_name, device_code = get_ioctl_code(ioctl_code)
method_name, method_code = get_method(ioctl_code)
access_name, access_code = get_access(ioctl_code)
all_vars = (
ioctl_code, device_name, device_code, function, method_name, method_code, access_name,
access_code)
try:
with open(ioctl_file_name, "w") as IOCTL_file:
IOCTL_file.write("0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)\n" % all_vars)
except IOError as e:
print("ERROR #{}: {}\nCan't save decoded IOCTLs to \"{}\"".format(e.errno, e.strerror,
ioctl_file_name))
print("0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)" % all_vars)
log_file.write("0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)\n" % all_vars)
result = True
elif idc.get_operand_type(cur, 1) == 5:
idc.op_dec(cur, 1)
get_ioctl_code(int(idc.print_operand(cur, 1)))
ioctl_code = int(idc.print_operand(cur, 1))
function = get_function(ioctl_code)
device_name, device_code = get_ioctl_code(ioctl_code)
method_name, method_code = get_method(ioctl_code)
access_name, access_code = get_access(ioctl_code)
all_vars = (
ioctl_code, device_name, device_code, function, method_name, method_code, access_name,
access_code)
try:
with open(ioctl_file_name, "w") as IOCTL_file:
IOCTL_file.write("0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)\n" % all_vars)
except IOError as e:
print("ERROR #{}: {}\nCan't save decoded IOCTLs to \"{}\"".format(e.errno, e.strerror,
ioctl_file_name))
print("0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)" % all_vars)
log_file.write("0x%-8X | %-31s 0x%-8X | 0x%-8X | %-17s %-4d | %s (%d)\n" % all_vars)
result = True
# else:
# print("[!] Cannot get IOCTL from {} at {} ".format(idc.GetDisasm(cur), hex(cur)))
Expand Down
Loading

0 comments on commit 63ec844

Please sign in to comment.