memwatch
                                
                                 memwatch copied to clipboard
                                
                                    memwatch copied to clipboard
                            
                            
                            
                        macOS memory editor / universal game trainer
memwatch
memwatch provides an interactive interface to find and modify variables in the memory of a running process. This is done by repeatedly searching the process' address space for values that follow the rules given by the user, narrowing down the result set until it includes only the desired variable. After finding this variable, memwatch can monitor it, change its value, "freeze" its value, and more. This functionality is similar to that provided by scanmem(1).
I often use memwatch to cheat in games, but have also used it in debugging to complement the features of a generic debugger such as gdb.
Building
- Build and install phosg (https://github.com/fuzziqersoftware/phosg)
- Build and install libamd64 (https://github.com/fuzziqersoftware/libamd64)
- make
- sudo make install
If it doesn't work on your system, let me know. It should build and run on all recent OS X versions.
Running
Run sudo memwatch <target_name_or_pid>. If you give a name, memwatch will search the list of running processes to find one that matches this name (case-insensitive). If multiple processes match the name, it will ask you which one to operate on. See the man page (man memwatch after building and installing) for more options.
Upon running memwatch, you'll see a debugger-like prompt like memwatch:48536/VirtualBoxVM 0s/0f # . This prompt shows the pid of the attached process, the process name, the number of open searches, and the number of frozen memory regions.
Commands
Most commands can be abbreviated intuitively (e.g. read, rd, and r are all the same command). There are a few unintuitive abbreviations, which are noted below.
General commands
- attach [pid_or_name]: Attaches to a new process by PID or by name. If no argument is given, attaches to a process with the same name as the currently-attached process.
- data <data_string>: Parses the data string and displays the raw values returned from the parser. You can use this to test complex data strings before writing them to be sure the correct data will be written.
- state [field_name value]: With no arguments, displays simple variables from memwatch's internal state. This includes variables from command-line options, like use_color and pause_target. With arguments, sets the internal variable- field_nameto- value.
- watch <command> [args...]or- ! <command> [args...]: Repeat the given command every second until Ctrl+C is pressed. Not all commands may be repeated; if the command doesn't support watching, it will execute only once.
- quitor- exit: Exits memwatch.
Memory access commands
memwatch's interactive interface implements the following commands for reading, writing, and otherwise manipulating virtual memory:
- access <address> <mode>: Changes the virtual memory protection mode on the region containing- address. The access mode is a string consisting of the r, w, or x characters, or some combination thereof. To remove all access to a memory region, use - for the access mode.
- list: Lists memory regions allocated in the target process's address space.
- dump [filename_prefix]: If a prefix is given, dumps all readable memory in the target process to files named- <prefix>.address1.address2.bin, along with an index file. If no prefix is given, only determines which regions are readable.
- read <address> <size> [filename] [+format]: Reads a block of- sizebytes from- addressin the target process's memory.- addressand- sizeare specified in hexadecimal. If a filename is given, writes the data to the given file instead of printing to the terminal.- formatis a short string specifying which additional interpretations of the data should be printed. It starts with '+' and includes any of the characters 'a' (text), 'f' (float), 'd' (double), and 'r' (reverse-endian); the default is '+a'.
- write <address-or-result-id> <data>: Writes- datato- addressin the target process's memory.- addressmay be preceded by- sto read the address from the current search result set, or by- tto read the address from the results of the previous invocation of the- findcommand. For example, specifying- s0refers to the first result address in the current search;- s1refers to the second result, etc. Results from other searches may also be used by specifying the search name explicitly, as in- s1@search-name. See the Data Format section for information on how to specify the data string.
Memory search commands
memwatch's interactive interface implements the following commands for searching a process's memory for variables:
- find <data>or- t <data>: Finds all occurrences of- datain readable regions of the target's memory. See the Data Format section for more information on how to specify the search string. Searches done with this command do not affect the current saved search results.
- open <type> [name]: Opens a search for a variable of the given type in writable memory regions.- typemay be suffixed with a ! to search all readable memory instead of only writable memory. See the Search Types section for valid search types. If- nameis not given, the search will be unnamed and cannot be resumed after another search is opened. If- predicateis given, perform the initial search immediately.
- open [name]: If- nameis given, reopens a previous search. If- nameis not given, lists all open searches.
- close [name]: If- nameis given, closes the specified search. If- nameis not given, closes the current search.
- fork <name> [name2]: If- name2is given, makes a copy of the search named- nameas- name2. Otherwise, makes a copy of the current search as- name.
- search [search_name] <operator> [value]: Reads the values of variables in the current list of results (or the named search's results, if a name is given), and filters out the results for which- new_value operator prev_valueis false. If- valueis not given, uses the value of the variable during the previous search. Valid operators are- <(less than),- >(greater than),- <=(less-or-equal),- >=(greater-or-equal),- =(equal),- !=(not equal), and- $(flag search - true if the two arguments differ in only one bit). The- $operator cannot be used in a search for a floating-point variable.
- search [search_name] .: Begins a search for a variable with an unknown initial value. Once this is done, future searches can be done using the above operators.
- results [search_name]or- x: Displays the current list of results. If- search_nameis given, displays the results for that search.
- delete <spec> [spec ...]: Deletes specific search results.- specmay be the address of a specific result to delete, or a range of addresses to delete, which is inclusive on both ends. Ranges are specified as a pair of addresses separated by a dash with no spaces. Result references like- s1are acceptable for this command as well.
- iterations [search_name]: Displays the list of stored iterations for the current search, or the named search if a name is given.
- truncate [search_name] <count>: Deletes all iterations except the- countmost recent from the current search, or the named search if a name is given.
- undo [search_name]or- cz: Undoes the latest iteration of the current search, or the named search if a name is given.
- set <value>: Writes- valueto all addresses in the current result set.
- set <result-id> <value>: Writes- valueto one address in the current result set.- result-idshould be of the form- s0,- s1, etc. (as for the- writecommand).
Memory freeze commands
memwatch implements a memory freezer, which repeatedly writes values to the target's memory at a very short interval, effectively fixing the variable's value in the target process. The following commands allow manipulation of frozen variables:
- freeze [+n<name>] <address-or-result-id> <data> [+d]: Sets a freeze on- addresswith the given data.- addressmay refer to a search or find result, using the same syntax as for the- writecommand. The given data is written in the background approximately every 10 milliseconds. Sets the freeze name to- nameif given; otherwise, sets the freeze name to the current search name (if any). If- +dis given, the freeze is initially disabled and won't take effect until enabled with the- enablecommand.
- freeze [+n<name>] <address-or-result-id> +s<size> [+d]: Identical to the above command, but uses the data already present in the process's memory. Size is specified in hexadecimal.
- freeze [+n<name>] <address-or-result-id> +m<max-entries> <data> [+N<null-data>] [+d]: Sets a freeze on an array of- max-entriesitems starting at- addresswith the given data. If- datais not present in the array, the first null entry in the array is overwritten with- data. Null entries are those whose contents are entirely zeroes, or whose contents match- null-dataif- null-datais given. The size of each array element is assumed to be the size of- data.- dataand- null-datamust have equal sizes.
- unfreeze [id]: If- idis not given, displays the list of currently-frozen regions. Otherwise,- idmay be the index, address, or name of the region to unfreeze. If a name is given and multiple regions have the same name, unfreezes all of them. If- *is given, unfreezes all regions.
- enable <id>or- + <id>: Enables the given frozen regions, so their values will be written. Values for- idare specified in the same way as for the- unfreezecommand.
- disable <id>or- - <id>: Disables the given frozen regions, so their values will not be written. Values for- idare specified in the same way as for the- unfreezecommand.
- frozen [data | commands]: Displays the list of currently-frozen regions. If run as- frozen data, displays the data associated with each region as well. If run as- frozen commands, displays for each frozen region a command to freeze that region (this is generally a more concise way to view frozen regions with their data).
Execution state management commands
memwatch implements experimental support for viewing and modifying execution state in the target process, implemented by the following commands:
- pause: Pauses the target process.
- unpauseor- resume: Unpauses the target process.
- signal <signum>: Sends the Unix signal- signumto the target process. See the signal(3) manual page for a list of signals.
Search types
memwatch supports searching for the following types of variables. Any type except 'str' may be prefixed by the letter 'r' to perform reverse-endian searches (that is, to search for big-endian values on a little-endian architecture, or vice versa).
- s,- str,- string: Search for any string. Values are specified in immediate data format (see the Data Format section for more information).
- f,- flt,- float: Search for a 32-bit floating-point value.
- d,- dbl,- double: Search for a 64-bit floating-point value.
- u8,- u16,- u32,- u64: Search for an unsigned 8-bit, 16-bit, 32-bit, or 64-bit value.
- s8,- s16,- s32,- s64: Search for a signed 8-bit, 16-bit, 32-bit, or 64-bit value.
Data format
Input data for raw data searches and the find, write, and freeze commands is specified in a custom format, described here. You can try using this format with the data command (see above). Every pair of hexadecimal digits represents one byte, with special control sequences as follows:
- Decimal integers: A decimal integer may be specified by preceding it with #signs (#for a single byte,##for a 16-bit int,###for a 32-bit int, or####for a 64-bit int).
- Floating-point numbers: A floating-point number may be specified by preceding it with %signs (%for single-precision,%%for double-precision).
- String literals: ASCII strings must be enclosed in double quotes, and unicode strings in single quotes. Within a string, the escape sequences \n,\r,\t, and\\will be replaced with a newline, a carriage return, a tab character, and a single backslash respectively.
- File contents: A string enclosed in < >will be treated as a filename, and will be replaced with the contents of the file in the output data.
- Change of endianness: A dollar sign ($) inverts the endianness of the data following it. This applies to unicode string literals, integers specified with#signs, and floating-point numbers.
- Wildcard: Any data between question marks (?) will match any byte when searching with thefindcommand or freezing array entries with thefreezecommand. This is not yet implemented for thesearchcommand.
- Comments: Comments are formatted in C-style blocks; anything between /*and*/will be omitted from the output string, as well as anything between//and a newline (though this format is rarely used since commands are delimited by newlines). Comments cannot be nested.
Any non-recognized characters are ignored. The initial endian-ness of the output depends on the endian-ness of the host machine: on an Intel machine, the resulting data would be little-endian.
Example data string:
/* omit 01 02 */ 03 ?04? $ ##30 $ ##127 ?"dark"? ###-1 'cold'
Resulting data (Intel):
03 04 00 1E 7F 00 64 61 72 6B FF FF FF FF 63 00 6F 00 6C 00 64 00
Resulting mask:
FF 00 FF FF FF FF 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF
Usage examples
Known-value search
We're playing Supaplex in DOSBox and we want to have infinite bombs. The number of bombs that we have is displayed on the screen at all times, so we can always know how many the game thinks we have. We start memwatch and open an unsigned, small integer search:
fuzziqersoftware@pointy:~$ sudo memwatch dosbox
memwatch:90732/DOSBox 0s/0f # open u8 bombs
opened new u8 search named bombs
Now we start playing a level on which there are a lot of bombs. We collect a few of them and search for the number we collected:
memwatch:90732/DOSBox 1s/0f bombs:u8 # search = 3
memwatch:90732/DOSBox 1s/0f bombs:u8(378052) #
Now we use one of the bombs, and narrow down the result set to variables that had the value 3 during the initial search but have the value 2 now:
memwatch:90732/DOSBox 1s/0f bombs:u8(378052) # s = 2
memwatch:90732/DOSBox 1s/0f bombs:u8(167) #
We use one more bomb and search again:
memwatch:90732/DOSBox 1s/0f bombs:u8(167) # s = 1
(0) 000000000C34E37C 1 (0x01)
memwatch:90732/DOSBox 1s/0f bombs:u8(1) #
This single result must be the variable that represents the number of bombs we have. Now we freeze that address at a nonzero value (s0 refers to the first result in the current search):
memwatch:90732/DOSBox 1s/0f bombs:u8(1) # freeze s0 01
region frozen
Now we have infinite bombs as long as memwatch is running, and we don't unfreeze that variable.
Unknown-value search
Paper Mario for the Nintendo 64 has a glitch whereby we can replay earlier chapters of the game, but keep all of our items and upgrades from later chapters. To trigger the glitch, we need to fall under the floor in a specific room. This is possible to do without memory editing, but very difficult to pull off without spending a lot of time setting up the necessary glitches. Instead, we can use memwatch to directly change our Y coordinate within the emulator.
In this example we're using sixtyforce, but this should work for any emulator. The Nintendo 64 uses 32-bit registers, so we assume the type we need is float. We start memwatch, open a search, and do an initial-value search:
fuzziqersoftware@pointy:~$ sudo memwatch sixtyforce
memwatch:91372/sixtyforce 0s/0f # open float y-coord
memwatch:91372/sixtyforce 1s/0f y-coord:f # s .
memwatch:91372/sixtyforce 1s/0f y-coord:f(+) #
This creates a reference point for the next search, but doesn't generate any results yet - (+) indicates that a reference point exists for the current search. Now we jump up onto a ledge (which should increase our Y coordinate within the game) and search for increased values:
memwatch:91372/sixtyforce 1s/0f y-coord:f(+) # s >
memwatch:91372/sixtyforce 1s/0f y-coord:f(192462) #
Unknown-value searches tend to take longer to converge to the variables we want. We jump onto ledges, jump down, jump up again, etc., repeating until we get a manageable number of search results:
memwatch:91372/sixtyforce 1s/0f y-coord:f(192462) # s <
memwatch:91372/sixtyforce 1s/0f y-coord:f(55813) # s >
memwatch:91372/sixtyforce 1s/0f y-coord:f(24824) # s >
memwatch:91372/sixtyforce 1s/0f y-coord:f(5594) # s >
memwatch:91372/sixtyforce 1s/0f y-coord:f(1336) # s >
memwatch:91372/sixtyforce 1s/0f y-coord:f(327) # s >
memwatch:91372/sixtyforce 1s/0f y-coord:f(82) # s <
memwatch:91372/sixtyforce 1s/0f y-coord:f(72) # s =
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # s >
(0) 00000001070177A0 50.000000 (0x42480000)
(1) 00000001082B60C0 50.000000 (0x42480000)
(2) 00000001082F3DC0 166.129028 (0x43262108)
(3) 00000001082F3DE4 50.000000 (0x42480000)
(4) 00000001082F4214 50.000000 (0x42480000)
(5) 00000001082F4228 50.000000 (0x42480000)
(6) 0000000108350FF4 50.000000 (0x42480000)
(7) 000000010839C580 50.000000 (0x42480000)
(8) 00000001085534E4 50.000000 (0x42480000)
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) #
Now we can try changing each of these individually to see if they have any effect in the game:
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s0 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s1 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s2 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s3 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s4 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s5 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s6 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s7 100
wrote 4 (0x4) bytes
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s8 100
wrote 4 (0x4) bytes
When we wrote to s6, we suddenly jumped up into the air within the game - this must be our Y coordinate. Now that we know this, we go to the place where we want to fall under the floor, check the value at that place, and set it to a smaller value:
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # x
(0) 00000001070177A0 0.000000 (0x00000000)
(1) 00000001082B60C0 0.000000 (0x00000000)
(2) 00000001082F3DC0 116.129028 (0x42E84210)
(3) 00000001082F3DE4 0.000000 (0x00000000)
(4) 00000001082F4214 0.000000 (0x00000000)
(5) 00000001082F4228 0.000000 (0x00000000)
(6) 0000000108350FF4 0.000000 (0x00000000)
(7) 000000010839C580 0.000000 (0x00000000)
(8) 00000001085534E4 0.000000 (0x00000000)
memwatch:91372/sixtyforce 1s/0f y-coord:f(9) # set s6 -20
wrote 4 (0x4) bytes
This causes us to fall under the floor and trigger the beginning of the story again.