From 7cd82b0077d143682dff709ffa8d4e38cb06e5bf Mon Sep 17 00:00:00 2001 From: Ben Gardiner Date: Thu, 22 Sep 2016 22:38:52 -0400 Subject: [PATCH 1/3] import changes fromhttps://patch-diff.githubusercontent.com/raw/Nightbringer21/fridump/pull/1.patch --- dumper.py | 78 ++++++++-------- fridump.py | 264 ++++++++++++++++++++++++++--------------------------- 2 files changed, 171 insertions(+), 171 deletions(-) diff --git a/dumper.py b/dumper.py index 8f4801c..1922905 100644 --- a/dumper.py +++ b/dumper.py @@ -1,39 +1,39 @@ -import os -import logging - -# Reading bytes from session and saving it to a file - -def dump_to_file(session,base,size,error,directory): - try: - filename = str(hex(base))+'_dump.data' - dump = session.read_bytes(base, size) - f = open(os.path.join(directory,filename), 'wb') - f.write(dump) - f.close() - return error - except: - print "Oops, memory access violation!" - - return error - -#Read bytes that are bigger than the max_size value, split them into chunks and save them to a file - -def splitter(session,base,size,max_size,error,directory): - times = size/max_size - diff = size % max_size - if diff is 0: - logging.debug("Number of chunks:"+str(times+1)) - else: - logging.debug("Number of chunks:"+str(times)) - global cur_base - cur_base = base - - for time in range(times): - logging.debug("Save bytes: "+str(hex(cur_base))+" till "+str(hex(cur_base+max_size))) - dump_to_file(session, cur_base, max_size, error, directory) - cur_base = cur_base + max_size - - if diff is not 0: - logging.debug("Save bytes: "+str(hex(cur_base))+" till "+str(hex(cur_base+diff))) - dump_to_file(session, cur_base, diff, error, directory) - +import os +import logging + +# Reading bytes from session and saving it to a file + +def dump_to_file(session,base,size,error,directory): + try: + filename = str(hex(base))+'_dump.data' + dump = session.read_bytes(base, size) + f = open(os.path.join(directory,filename), 'wb') + f.write(dump) + f.close() + return error + except: + print("Oops, memory access violation!") + + return error + +#Read bytes that are bigger than the max_size value, split them into chunks and save them to a file + +def splitter(session,base,size,max_size,error,directory): + times = size/max_size + diff = size % max_size + if diff is 0: + logging.debug("Number of chunks:"+str(times+1)) + else: + logging.debug("Number of chunks:"+str(times)) + global cur_base + cur_base = base + + for time in range(times): + logging.debug("Save bytes: "+str(hex(cur_base))+" till "+str(hex(cur_base+max_size))) + dump_to_file(session, cur_base, max_size, error, directory) + cur_base = cur_base + max_size + + if diff is not 0: + logging.debug("Save bytes: "+str(hex(cur_base))+" till "+str(hex(cur_base+diff))) + dump_to_file(session, cur_base, diff, error, directory) + diff --git a/fridump.py b/fridump.py index f6f661d..d68a07c 100644 --- a/fridump.py +++ b/fridump.py @@ -1,132 +1,132 @@ -import textwrap -import frida -import os -import sys -import frida.core -import dumper -import utils -import argparse -import logging - -logo = """ - ______ _ _ - | ___| (_) | | - | |_ _ __ _ __| |_ _ _ __ ___ _ __ - | _| '__| |/ _` | | | | '_ ` _ \| '_ \\ - | | | | | | (_| | |_| | | | | | | |_) | - \_| |_| |_|\__,_|\__,_|_| |_| |_| .__/ - | | - |_| - """ - - -# Main Menu -def MENU(): - parser = argparse.ArgumentParser( - prog='fridump', - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent("")) - - parser.add_argument('process', help='the process that you will be injecting to') - parser.add_argument('-o', '--out', type=str, help='provide full output directory path. (def: \'dump\')', - metavar="dir") - parser.add_argument('-u', '--usb', action='store_true', help='device connected over usb') - parser.add_argument('-v', '--verbose', action='store_true', help='verbose') - parser.add_argument('-r','--read-only',action='store_true', help="dump read-only parts of memory. More data, more errors") - parser.add_argument('-s', '--strings', action='store_true', - help='run strings on all dump files. Saved in output dir.') - parser.add_argument('--max-size', type=int, help='maximum size of dump file in bytes (def: 20971520)', - metavar="bytes") - args = parser.parse_args() - return args - - -print logo -arguments = MENU() - -# Define Configurations -APP_NAME = arguments.process -DIRECTORY = "" -USB = arguments.usb -DEBUG_LEVEL = logging.INFO -STRINGS = arguments.strings -MAX_SIZE = 20971520 -PERMS = 'rw-' - -if arguments.read_only: - PERMS = 'r--' - -if arguments.verbose: - DEBUG_LEVEL = logging.DEBUG -logging.basicConfig(format='%(levelname)s:%(message)s', level=DEBUG_LEVEL) - -# Start a new Session -session = None -try: - if USB: - session = frida.get_usb_device().attach(APP_NAME) - else: - session = frida.attach(APP_NAME) -except: - print "Can't connect to App. Have you connected the device?" - sys.exit(0) - - -# Selecting Output directory -if arguments.out is not None: - DIRECTORY = arguments.out - if os.path.isdir(DIRECTORY): - print "Output directory is set to: " + DIRECTORY - else: - print "The selected output directory does not exist!" - sys.exit(1) - -else: - print "Current Directory: " + str(os.getcwd()) - DIRECTORY = os.path.join(os.getcwd(), "dump") - print "Output directory is set to: " + DIRECTORY - if not os.path.exists(DIRECTORY): - print "Creating directory..." - os.makedirs(DIRECTORY) - -mem_access_viol = "" - -print "Starting Memory dump..." - -Memories = session.enumerate_ranges(PERMS) - -if arguments.max_size is not None: - MAX_SIZE = arguments.max_size - -i = 0 -l = len(Memories) - -# Performing the memory dump -for memory in Memories: - base = memory.base_address - logging.debug("Base Address: " + str(hex(base))) - logging.debug("") - size = memory.size - logging.debug("Size: " + str(size)) - if size > MAX_SIZE: - logging.debug("Too big, splitting the dump into chunks") - mem_access_viol = dumper.splitter(session, base, size, MAX_SIZE, mem_access_viol, DIRECTORY) - continue - mem_access_viol = dumper.dump_to_file(session, base, size, mem_access_viol, DIRECTORY) - i += 1 - utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) -print - -# Run Strings if selected - -if STRINGS: - files = os.listdir(DIRECTORY) - i = 0 - l = len(files) - print "Running strings on all files:" - for f1 in files: - utils.strings(f1, DIRECTORY) - i += 1 - utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) -print "Finished!" -raw_input('Press Enter to exit...') +import textwrap +import frida +import os +import sys +import frida.core +import dumper +import utils +import argparse +import logging + +logo = """ + ______ _ _ + | ___| (_) | | + | |_ _ __ _ __| |_ _ _ __ ___ _ __ + | _| '__| |/ _` | | | | '_ ` _ \| '_ \\ + | | | | | | (_| | |_| | | | | | | |_) | + \_| |_| |_|\__,_|\__,_|_| |_| |_| .__/ + | | + |_| + """ + + +# Main Menu +def MENU(): + parser = argparse.ArgumentParser( + prog='fridump', + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent("")) + + parser.add_argument('process', help='the process that you will be injecting to') + parser.add_argument('-o', '--out', type=str, help='provide full output directory path. (def: \'dump\')', + metavar="dir") + parser.add_argument('-u', '--usb', action='store_true', help='device connected over usb') + parser.add_argument('-v', '--verbose', action='store_true', help='verbose') + parser.add_argument('-r','--read-only',action='store_true', help="dump read-only parts of memory. More data, more errors") + parser.add_argument('-s', '--strings', action='store_true', + help='run strings on all dump files. Saved in output dir.') + parser.add_argument('--max-size', type=int, help='maximum size of dump file in bytes (def: 20971520)', + metavar="bytes") + args = parser.parse_args() + return args + + +print(logo) +arguments = MENU() + +# Define Configurations +APP_NAME = arguments.process +DIRECTORY = "" +USB = arguments.usb +DEBUG_LEVEL = logging.INFO +STRINGS = arguments.strings +MAX_SIZE = 20971520 +PERMS = 'rw-' + +if arguments.read_only: + PERMS = 'r--' + +if arguments.verbose: + DEBUG_LEVEL = logging.DEBUG +logging.basicConfig(format='%(levelname)s:%(message)s', level=DEBUG_LEVEL) + +# Start a new Session +session = None +try: + if USB: + session = frida.get_usb_device().attach(APP_NAME) + else: + session = frida.attach(APP_NAME) +except: + print("Can't connect to App. Have you connected the device?") + sys.exit(0) + + +# Selecting Output directory +if arguments.out is not None: + DIRECTORY = arguments.out + if os.path.isdir(DIRECTORY): + print(("Output directory is set to: " + DIRECTORY)) + else: + print("The selected output directory does not exist!") + sys.exit(1) + +else: + print(("Current Directory: " + str(os.getcwd()))) + DIRECTORY = os.path.join(os.getcwd(), "dump") + print(("Output directory is set to: " + DIRECTORY)) + if not os.path.exists(DIRECTORY): + print("Creating directory...") + os.makedirs(DIRECTORY) + +mem_access_viol = "" + +print("Starting Memory dump...") + +Memories = session.enumerate_ranges(PERMS) + +if arguments.max_size is not None: + MAX_SIZE = arguments.max_size + +i = 0 +l = len(Memories) + +# Performing the memory dump +for memory in Memories: + base = memory.base_address + logging.debug("Base Address: " + str(hex(base))) + logging.debug("") + size = memory.size + logging.debug("Size: " + str(size)) + if size > MAX_SIZE: + logging.debug("Too big, splitting the dump into chunks") + mem_access_viol = dumper.splitter(session, base, size, MAX_SIZE, mem_access_viol, DIRECTORY) + continue + mem_access_viol = dumper.dump_to_file(session, base, size, mem_access_viol, DIRECTORY) + i += 1 + utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) +print() + +# Run Strings if selected + +if STRINGS: + files = os.listdir(DIRECTORY) + i = 0 + l = len(files) + print("Running strings on all files:") + for f1 in files: + utils.strings(f1, DIRECTORY) + i += 1 + utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) +print("Finished!") +eval(input('Press Enter to exit...')) From 162c8eb5271ff86118af1e2717f65795f74751af Mon Sep 17 00:00:00 2001 From: Ben Gardiner Date: Fri, 3 Mar 2017 21:52:03 -0500 Subject: [PATCH 2/3] fix corner cases where 'times' is not a number --- dumper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dumper.py b/dumper.py index 1922905..07094eb 100644 --- a/dumper.py +++ b/dumper.py @@ -19,7 +19,7 @@ def dump_to_file(session,base,size,error,directory): #Read bytes that are bigger than the max_size value, split them into chunks and save them to a file def splitter(session,base,size,max_size,error,directory): - times = size/max_size + times = int(size/max_size) diff = size % max_size if diff is 0: logging.debug("Number of chunks:"+str(times+1)) From 909cf0518a236dfdccb13895b79a5efbda5845e0 Mon Sep 17 00:00:00 2001 From: Ben Gardiner Date: Mon, 6 Mar 2017 12:32:31 -0500 Subject: [PATCH 3/3] add function-hooking memory dumps and a a maximum number of dumps --- README.md | 5 +- fridump.py | 196 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 169 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index eb99286..1908d1b 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ The following are the main flags that can be used with fridump: -r, --read-only dump read-only parts of memory. More data, more errors -s, --strings run strings on all dump files. Saved in output dir. --max-size bytes maximum size of dump file in bytes (def: 20971520) + --hook pattern ApiResolver pattern specifying functions to hook with memory dumping action + -n, --count maximum number of dumps to take To find the name of a local process, you can use: @@ -34,7 +36,8 @@ Examples: fridump -u Safari - Dump the memory of an iOS device associated with the Safari app fridump -u -s com.example.WebApp - Dump the memory of an Android device and run strings on all dump files fridump -r -o [full_path] - Dump the memory of a local application and save it to the specified directory - + fridump -n 3 --hook 'imports:*!write' - dump memory whenever an imported 'write' function is executed, limit to 3 times. + More examples can be found [here](http://pentestcorner.com/introduction-to-fridump/) Installation diff --git a/fridump.py b/fridump.py index d68a07c..360ef08 100644 --- a/fridump.py +++ b/fridump.py @@ -7,6 +7,11 @@ import utils import argparse import logging +from frida.application import Reactor +import threading +import unicodedata +import re +from itertools import chain logo = """ ______ _ _ @@ -37,6 +42,8 @@ def MENU(): help='run strings on all dump files. Saved in output dir.') parser.add_argument('--max-size', type=int, help='maximum size of dump file in bytes (def: 20971520)', metavar="bytes") + parser.add_argument('--hook', type=str, action='append', help='ApiResolver statements specifying hooks where dumps will be performed') + parser.add_argument('-n', '--count', type=int, help='maximum number of dumps to take.', default=1) args = parser.parse_args() return args @@ -89,44 +96,171 @@ def MENU(): print("Creating directory...") os.makedirs(DIRECTORY) -mem_access_viol = "" +def safe_filename(value): + import unicodedata + value = unicodedata.normalize('NFKD', value) + value = re.sub('[^\w\s-]', '', value).strip().lower() + value = re.sub('[-\s]+', '-', value) + return value -print("Starting Memory dump...") +mem_access_viol = "" -Memories = session.enumerate_ranges(PERMS) +done = threading.Event() +reactor = Reactor(lambda reactor: done.wait()) -if arguments.max_size is not None: - MAX_SIZE = arguments.max_size +dump_count = 0 -i = 0 -l = len(Memories) +def do_dump(trigger_name): + global MAX_SIZE + global mem_access_viol + global dump_count -# Performing the memory dump -for memory in Memories: - base = memory.base_address - logging.debug("Base Address: " + str(hex(base))) - logging.debug("") - size = memory.size - logging.debug("Size: " + str(size)) - if size > MAX_SIZE: - logging.debug("Too big, splitting the dump into chunks") - mem_access_viol = dumper.splitter(session, base, size, MAX_SIZE, mem_access_viol, DIRECTORY) - continue - mem_access_viol = dumper.dump_to_file(session, base, size, mem_access_viol, DIRECTORY) - i += 1 - utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) -print() + print("Starting Memory dump...") + Memories = session.enumerate_ranges(PERMS) -# Run Strings if selected + if arguments.max_size is not None: + MAX_SIZE = arguments.max_size -if STRINGS: - files = os.listdir(DIRECTORY) i = 0 - l = len(files) - print("Running strings on all files:") - for f1 in files: - utils.strings(f1, DIRECTORY) + l = len(Memories) + + trigger_name = trigger_name.decode('utf-8') + dump_count = dump_count + 1 + dump_dir = os.path.join(DIRECTORY, safe_filename("%d-%s" % (dump_count, trigger_name))) + + if arguments.count == 1: + dump_dir = DIRECTORY + + if not os.path.exists(dump_dir): + print("Creating directory %s..." % dump_dir) + os.makedirs(dump_dir) + + # Performing the memory dump + for memory in Memories: + base = memory.base_address + logging.debug("Base Address: " + str(hex(base))) + logging.debug("") + size = memory.size + logging.debug("Size: " + str(size)) + if size > MAX_SIZE: + logging.debug("Too big, splitting the dump into chunks") + mem_access_viol = dumper.splitter(session, base, size, MAX_SIZE, mem_access_viol, dump_dir) + continue + mem_access_viol = dumper.dump_to_file(session, base, size, mem_access_viol, dump_dir) i += 1 utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) -print("Finished!") -eval(input('Press Enter to exit...')) + print() + + # Run Strings if selected + + if STRINGS: + files = os.listdir(dump_dir) + i = 0 + l = len(files) + print("Running strings on all files:") + for f1 in files: + utils.strings(f1, dump_dir) + i += 1 + utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) + + if dump_count >= arguments.count: + reactor.stop() + done.set() + print("Finished!") + +jscode = """ +var donotpassgo = function() { + var waiting = true; + var op = recv('go', function(dummy) {}); + op.wait(); +} + +function unicodeStringToTypedArray(str) { + var binary_str = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { + return String.fromCharCode('0x' + p1); + }); + var buf = new Uint8Array(binary_str.length); + for (var i=0, strLen=binary_str.length; i 1: + print("WARNING: ignoring --count=%d, only one capture supported when there are no --hooks" % arguments.count) + arguments.count = 1 + reactor.schedule(initiate_dump); + +reactor.run() + +session.detach() +sys.exit(0)