├── README.md ├── frida-memory-dumper.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # frida-scripts 2 | 3 | ## frida-memory-dumper.py 4 | 5 | Tool for memory dump and search data in process memory. 6 | 7 | Worked on Linux and Windows. 8 | 9 | Has 2 modes: **spawn** and **attach**. 10 | 11 | **When spawn (app not running yet) may run from user.** 12 | 13 | **When attach (app is already running) need to be run in privileged mode.** 14 | 15 | ## Install 16 | 17 | Works only with **Python >=3.7** and need a **pip3** package. 18 | 19 | `sudo pip3 intall -r requirements.txt` 20 | 21 | ### Note: new frida 16 need updated version of pip, wheel and setuptools 22 | 23 | `sudo pip3 install --upgrade pip setuptools wheel` 24 | 25 | ## Usage 26 | 27 | ### Dump all process memory 28 | 29 | `python3 frida-memory-dumper.py --dump ` 30 | 31 | ### Dump process memory from address and size 32 | 33 | `python3 frida-memory-dumper.py --dump --addr --size ` 34 | 35 | ### Scan process memory and search bytes pattern 36 | 37 | `python3 frida-memory-dumper.py --scan --pattern ` 38 | 39 | ### Scan process memory and search string 40 | 41 | `python3 frida-memory-dumper.py --scan --string ` 42 | 43 | ### Interactive Dump all process memory 44 | 45 | `python3 frida-memory-dumper.py --dump --interactive ` 46 | 47 | Press Enter to dump memory when you want. 48 | 49 | ## Examples 50 | 51 | Dump process with 1234 id 52 | 53 | `python3 frida-memory-dumper.py --dump 1234` 54 | 55 | Dump firefox process in interactive mode 56 | 57 | `python3 frida-memory-dumper.py --dump --interactive firefox` 58 | 59 | Scan firefox memory and search bytes 60 | 61 | `python3 frida-memory-dumper.py --scan --pattern "30 ?? 32 33 34 35 36 37 38" firefox` 62 | 63 | Scan firefox memory and search string 64 | 65 | `python3 frida-memory-dumper.py --scan --string "12345678" firefox` 66 | -------------------------------------------------------------------------------- /frida-memory-dumper.py: -------------------------------------------------------------------------------- 1 | import frida 2 | import argparse 3 | import sys 4 | import os 5 | import subprocess 6 | import time 7 | import shutil 8 | 9 | version = '1.2' 10 | PROTECTION = 'r--' 11 | 12 | 13 | def on_message(message, data): 14 | print("[%s] => %s" % (message, data)) 15 | 16 | 17 | def get_bytes(string): 18 | result = '' 19 | q = 0 20 | for c in string: 21 | if q != 1: 22 | result += c.encode("utf-8").hex() 23 | if q != len(string)-1: 24 | result += ' ' 25 | else: 26 | result += '?? ' 27 | q += 1 28 | return result 29 | 30 | 31 | def check_proc(process): 32 | proc1 = subprocess.Popen(['frida-ps'], stdout=subprocess.PIPE) 33 | proc2 = subprocess.Popen(['grep', '-i', process], stdin=proc1.stdout, 34 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 35 | proc1.stdout.close() 36 | result, err = proc2.communicate() 37 | return result 38 | 39 | 40 | def attach(process): 41 | try: 42 | session = frida.attach(process) 43 | except frida.ProcessNotFoundError: 44 | print('[!] No app running. Trying to spawn.') 45 | pid = spawn_app(process) 46 | session = frida.attach(pid) 47 | return session 48 | 49 | 50 | def spawn_app(process): 51 | print('Spawning %s app.' % process) 52 | pid = frida.spawn(shutil.which(process)) 53 | frida.resume(pid) 54 | time.sleep(5) 55 | print('Spawned.') 56 | return pid 57 | 58 | 59 | def scan_memory(process, pattern, interactive=False, protection=PROTECTION): 60 | print('Scanning %s as \'%s\' pattern.' % (process, pattern)) 61 | session = attach(process) 62 | if interactive: 63 | print('[Interactive] Press Enter to scan...') 64 | input() 65 | script = session.create_script(""" 66 | var ranges = Process.enumerateRangesSync({protection: '%s', coalesce: true}); 67 | var range; 68 | function processNext(){ 69 | range = ranges.pop(); 70 | if (!range){ 71 | // we are done 72 | return; 73 | } 74 | // due to the lack of blacklisting in Frida, there will be 75 | // always an extra match of the given pattern (if found) because 76 | // the search is done also in the memory owned by Frida. 77 | Memory.scan(range.base, range.size, '%s', { 78 | onMatch: function(address, size){ 79 | console.log('[+] Pattern found at: ' + address.toString()); 80 | console.log(hexdump(address)); 81 | }, 82 | onError: function(reason){ 83 | console.log('[!] There was an error scanning memory'); 84 | }, 85 | onComplete: function(){ 86 | processNext(); 87 | } 88 | }); 89 | } 90 | processNext(); 91 | """ % (protection, pattern)) 92 | script.on('message', on_message) 93 | script.load() 94 | input('[!] Press at any time to detach from instrumented program.\n\n') 95 | session.detach() 96 | 97 | 98 | def dump_memory(process, output, interactive=False, protection=PROTECTION): 99 | print('Dumping %s memory.' % process) 100 | try: 101 | proc = int(process) 102 | except ValueError: 103 | proc = process 104 | if not os.path.isabs(output): 105 | output = os.path.join(os.path.dirname(os.path.realpath(__file__)), output) 106 | try: 107 | os.mkdir(output) 108 | except OSError as error: 109 | pass 110 | 111 | if sys.platform == "win32": 112 | output = output.replace('\\', '/') 113 | 114 | print (output) 115 | session = attach(proc) 116 | if interactive: 117 | print('[Interactive] Press Enter to dump...') 118 | input() 119 | script = session.create_script(""" 120 | function storeArrayBuffer(filename, buffer) { 121 | console.log(filename); 122 | var destFileName = new File(filename, "wb"); 123 | destFileName.write(buffer); 124 | destFileName.flush(); 125 | destFileName.close(); 126 | } 127 | 128 | var ranges = Process.enumerateRangesSync({protection: '%s', coalesce: true}); 129 | var totalRanges = ranges.length; 130 | var failedDumps = 0; 131 | console.log('[BEGIN] Located ' + totalRanges + ' memory ranges matching [' + '%s' + ']'); 132 | ranges.forEach(function (range) { 133 | var destFileName = '%s/'.concat(range.base, "_dump"); 134 | var arrayBuf; 135 | try { 136 | arrayBuf = range.base.readByteArray(range.size); 137 | } catch (e) { 138 | failedDumps += 1; 139 | return; 140 | } 141 | if (arrayBuf) { 142 | storeArrayBuffer(destFileName, arrayBuf); 143 | } 144 | }); 145 | var successfulDumps = totalRanges - failedDumps; 146 | console.log("[FINISH] Successfully dumped ".concat(successfulDumps, "/").concat(totalRanges, " ranges.")); 147 | """ % (protection, protection, output)) 148 | script.on('message', on_message) 149 | script.load() 150 | session.detach() 151 | 152 | 153 | def read_memory(process, addr, size): 154 | print('Reading %s memory.' % process) 155 | try: 156 | proc = int(process) 157 | except ValueError: 158 | proc = process 159 | session = attach(proc) 160 | script = session.create_script(""" 161 | var buf = Memory.readByteArray(ptr('0x%x'), %d); 162 | console.log(hexdump(buf, { 163 | offset: 0, 164 | length: %d, 165 | header: true, 166 | ansi: false 167 | })); 168 | console.log("[FINISH] Successfully read memory"); 169 | """ % (addr, size, size)) 170 | script.on('message', on_message) 171 | script.load() 172 | session.detach() 173 | 174 | 175 | def get_parser() -> argparse.ArgumentParser: 176 | parser = argparse.ArgumentParser(description="frida-memory-dumper.py") 177 | parser.add_argument('process', help='Process name or pid') 178 | 179 | # Spawn arguments 180 | parser.add_argument('--spawn', action='store_true', help='Spawn app path') 181 | parser.add_argument('--spawntime', type=int, default=5, help='Spawn time') 182 | 183 | # Modes 184 | parser.add_argument('--scan', action='store_true', help='Scan memory with pattern') 185 | parser.add_argument('--dump', action='store_true', help='Dump memory') 186 | 187 | parser.add_argument('--interactive', action='store_true', help='Dump and Scan memory when user want') 188 | parser.add_argument('-p', '--protection', type=str, default=PROTECTION, help='Protection of the memory that we scan or dump') 189 | 190 | # Scan arguments 191 | parser.add_argument('--pattern', type=str, help='Scan Pattern Bytes in form "42 2c ?? 00 4a" ') 192 | parser.add_argument('--string', type=str, help='Search string as pattern in memory') 193 | 194 | # Partial memory dump arguments 195 | parser.add_argument('--addr', type=str, help='Memory initial address in hex form without 0x') 196 | parser.add_argument('--size', type=int, help='Memory size') 197 | 198 | parser.add_argument('-o', '--output', type=str, default='out', help='Folder for output files') 199 | return parser 200 | 201 | 202 | def main(parser) -> None: 203 | print('---------------------------') 204 | print("%s v%s" % (__file__, version)) 205 | print('---------------------------') 206 | args = parser.parse_args() 207 | 208 | if not args.scan and not args.dump: 209 | print('No mode selected') 210 | parser.print_help(sys.stderr) 211 | sys.exit(1) 212 | 213 | if args.spawn: 214 | if check_proc(args.process): 215 | frida.kill(args.process) 216 | spawn_app(args.process) 217 | 218 | if args.dump: 219 | if args.addr and args.size: 220 | read_memory(args.process, int(args.addr, 16), args.size) 221 | else: 222 | dump_memory(args.process, args.output, args.interactive, args.protection) 223 | elif args.scan: 224 | if not args.pattern and not args.string: 225 | print('No scan pattern') 226 | parser.print_help(sys.stderr) 227 | sys.exit(1) 228 | elif args.pattern: 229 | scan_memory(args.process, args.pattern, args.interactive, args.protection) 230 | elif args.string: 231 | string = get_bytes(args.string) 232 | print('Pattern is "%s"' % string) 233 | scan_memory(args.process, string, args.interactive, args.protection) 234 | 235 | 236 | if __name__ == '__main__': 237 | main(get_parser()) 238 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | frida 2 | --------------------------------------------------------------------------------