├── README.md ├── dumper.py ├── fridump3.py └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | # fridump3 2 | Fridump is an open source memory dumping tool, primarily aimed to penetration testers and developers. Fridump is using the Frida framework to dump accessible memory addresses from any platform supported. It can be used from a Windows, Linux or Mac OS X system to dump the memory of an iOS, Android or Windows application. 3 | 4 | This project is based on the following project: [https://github.com/Nightbringer21/fridump](https://github.com/Nightbringer21/fridump) and the pending PR concerning the python3 support (especially from [georgepetz](https://github.com/georgepetz) . Additionally I added the network support in addition to the USB support. 5 | 6 | FYI: I will destroy this repo is the Fridump author will integrate the pending PR concerning Python3 support. 7 | 8 | Usage 9 | --- 10 | 11 | ``` 12 | usage: fridump [-h] [-o dir] [-u] [-H HOST] [-v] [-r] [-s] [--max-size bytes] process 13 | 14 | positional arguments: 15 | process the process that you will be injecting to 16 | 17 | optional arguments: 18 | -h, --help show this help message and exit 19 | -o dir, --out dir provide full output directory path. (def: 'dump') 20 | -u, --usb device connected over usb 21 | -H HOST, --host HOST device connected over IP 22 | -v, --verbose verbose 23 | -r, --read-only dump read-only parts of memory. More data, more errors 24 | -s, --strings run strings on all dump files. Saved in output dir. 25 | --max-size bytes maximum size of dump file in bytes (def: 20971520) 26 | ``` 27 | -------------------------------------------------------------------------------- /dumper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | # Reading bytes from session and saving it to a file 5 | 6 | 7 | def dump_to_file(agent, base, size, error, directory): 8 | try: 9 | filename = str(base) + '_dump.data' 10 | dump = agent.read_memory(base, size) 11 | f = open(os.path.join(directory, filename), 'wb') 12 | f.write(dump) 13 | f.close() 14 | return error 15 | except Exception as e: 16 | logging.debug(str(e)) 17 | print("Oops, memory access violation!") 18 | return error 19 | 20 | # Read bytes that are bigger than the max_size value, split them into chunks and save them to a file 21 | 22 | def splitter(agent,base,size,max_size,error,directory): 23 | times = size//max_size 24 | diff = size % max_size 25 | if diff == 0: 26 | logging.debug("Number of chunks:"+str(times+1)) 27 | else: 28 | logging.debug("Number of chunks:"+str(times)) 29 | global cur_base 30 | cur_base = int(base,0) 31 | 32 | for time in range(times): 33 | # logging.debug("Save bytes: "+str(cur_base)+" till "+str(hex(cur_base+max_size))) 34 | dump_to_file(agent, cur_base, max_size, error, directory) 35 | cur_base = cur_base + max_size 36 | 37 | if diff != 0: 38 | # logging.debug("Save bytes: "+str(hex(cur_base))+" till "+str(hex(cur_base+diff))) 39 | dump_to_file(agent, cur_base, diff, error, directory) 40 | -------------------------------------------------------------------------------- /fridump3.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | import frida 3 | import os 4 | import sys 5 | import frida.core 6 | import dumper 7 | import utils 8 | import argparse 9 | import logging 10 | 11 | logo = """ 12 | ______ _ _ 13 | | ___| (_) | | 14 | | |_ _ __ _ __| |_ _ _ __ ___ _ __ 15 | | _| '__| |/ _` | | | | '_ ` _ \| '_ \\ 16 | | | | | | | (_| | |_| | | | | | | |_) | 17 | \_| |_| |_|\__,_|\__,_|_| |_| |_| .__/ 18 | | | 19 | |_| 20 | """ 21 | 22 | 23 | # Main Menu 24 | def MENU(): 25 | parser = argparse.ArgumentParser( 26 | prog='fridump', 27 | formatter_class=argparse.RawDescriptionHelpFormatter, 28 | description=textwrap.dedent("")) 29 | 30 | parser.add_argument( 31 | 'process', help='the process that you will be injecting to') 32 | parser.add_argument('-o', '--out', type=str, help='provide full output directory path. (def: \'dump\')', 33 | metavar="dir") 34 | parser.add_argument('-u', '--usb', action='store_true', 35 | help='device connected over usb') 36 | parser.add_argument('-H', '--host', type=str, 37 | help='device connected over IP') 38 | parser.add_argument('-v', '--verbose', action='store_true', help='verbose') 39 | parser.add_argument('-r', '--read-only', action='store_true', 40 | help="dump read-only parts of memory. More data, more errors") 41 | parser.add_argument('-s', '--strings', action='store_true', 42 | help='run strings on all dump files. Saved in output dir.') 43 | parser.add_argument('--max-size', type=int, help='maximum size of dump file in bytes (def: 20971520)', 44 | metavar="bytes") 45 | args = parser.parse_args() 46 | return args 47 | 48 | 49 | print(logo) 50 | 51 | arguments = MENU() 52 | 53 | # Define Configurations 54 | APP_NAME = utils.normalize_app_name(appName=arguments.process) 55 | DIRECTORY = "" 56 | USB = arguments.usb 57 | NETWORK=False 58 | DEBUG_LEVEL = logging.INFO 59 | STRINGS = arguments.strings 60 | MAX_SIZE = 20971520 61 | PERMS = 'rw-' 62 | 63 | if arguments.host is not None: 64 | NETWORK=True 65 | IP=arguments.host 66 | 67 | if arguments.read_only: 68 | PERMS = 'r--' 69 | 70 | if arguments.verbose: 71 | DEBUG_LEVEL = logging.DEBUG 72 | logging.basicConfig(format='%(levelname)s:%(message)s', level=DEBUG_LEVEL) 73 | 74 | 75 | # Start a new Session 76 | session = None 77 | try: 78 | if USB: 79 | session = frida.get_usb_device().attach(APP_NAME) 80 | elif NETWORK: 81 | session = frida.get_device_manager().add_remote_device(IP).attach(APP_NAME) 82 | else: 83 | session = frida.attach(APP_NAME) 84 | except Exception as e: 85 | print(str(e)) 86 | sys.exit() 87 | 88 | 89 | # Selecting Output directory 90 | if arguments.out is not None: 91 | DIRECTORY = arguments.out 92 | if os.path.isdir(DIRECTORY): 93 | print("Output directory is set to: " + DIRECTORY) 94 | else: 95 | print("The selected output directory does not exist!") 96 | sys.exit(1) 97 | 98 | else: 99 | print("Current Directory: " + str(os.getcwd())) 100 | DIRECTORY = os.path.join(os.getcwd(), "dump") 101 | print("Output directory is set to: " + DIRECTORY) 102 | if not os.path.exists(DIRECTORY): 103 | print("Creating directory...") 104 | os.makedirs(DIRECTORY) 105 | 106 | mem_access_viol = "" 107 | 108 | print("Starting Memory dump...") 109 | 110 | def on_message(message, data): 111 | print("[on_message] message:", message, "data:", data) 112 | 113 | 114 | script = session.create_script("""'use strict'; 115 | 116 | rpc.exports = { 117 | enumerateRanges: function (prot) { 118 | return Process.enumerateRangesSync(prot); 119 | }, 120 | readMemory: function (address, size) { 121 | return Memory.readByteArray(ptr(address), size); 122 | } 123 | }; 124 | """) 125 | script.on("message", on_message) 126 | script.load() 127 | 128 | agent = script.exports_sync 129 | ranges = agent.enumerate_ranges(PERMS) 130 | 131 | if arguments.max_size is not None: 132 | MAX_SIZE = arguments.max_size 133 | 134 | i = 0 135 | l = len(ranges) 136 | 137 | # Performing the memory dump 138 | for range in ranges: 139 | logging.debug("Base Address: " + str(range["base"])) 140 | logging.debug("") 141 | logging.debug("Size: " + str(range["size"])) 142 | if range["size"] > MAX_SIZE: 143 | logging.debug("Too big, splitting the dump into chunks") 144 | mem_access_viol = dumper.splitter( 145 | agent, range["base"], range["size"], MAX_SIZE, mem_access_viol, DIRECTORY) 146 | continue 147 | mem_access_viol = dumper.dump_to_file( 148 | agent, range["base"], range["size"], mem_access_viol, DIRECTORY) 149 | i += 1 150 | utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50) 151 | 152 | # Run Strings if selected 153 | 154 | if STRINGS: 155 | files = os.listdir(DIRECTORY) 156 | i = 0 157 | l = len(files) 158 | print("Running strings on all files:") 159 | for f1 in files: 160 | utils.strings(f1, DIRECTORY) 161 | i += 1 162 | utils.printProgress(i, l, prefix='Progress:', 163 | suffix='Complete', bar=50) 164 | print("Finished!") 165 | #raw_input('Press Enter to exit...') 166 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import string 3 | import logging 4 | import os 5 | import re 6 | 7 | # Progress bar function 8 | 9 | 10 | def printProgress(times, total, prefix='', suffix='', decimals=2, bar=100): 11 | filled = int(round(bar * times / float(total))) 12 | percents = round(100.00 * (times / float(total)), decimals) 13 | bar = '#' * filled + '-' * (bar - filled) 14 | sys.stdout.write('%s [%s] %s%s %s\r' % 15 | (prefix, bar, percents, '%', suffix)), 16 | sys.stdout.flush() 17 | if times == total: 18 | print("\n") 19 | 20 | 21 | # A very basic implementations of Strings 22 | def strings(filename, directory, min=4): 23 | strings_file = os.path.join(directory, "strings.txt") 24 | path = os.path.join(directory, filename) 25 | with open(path, encoding='Latin-1') as infile: 26 | str_list = re.findall("[A-Za-z0-9/\-:;.,_$%'!()[\]<> \#]+", infile.read()) 27 | with open(strings_file, "a") as st: 28 | for string in str_list: 29 | if len(string) > min: 30 | logging.debug(string) 31 | st.write(string + "\n") 32 | 33 | # Normalize thw namo of application works better on frida 34 | def normalize_app_name(appName=str): 35 | try: 36 | appName = int(appName) 37 | except ValueError: 38 | pass 39 | return appName 40 | --------------------------------------------------------------------------------