CTRL-C and SIGINT, "cont"helpdumpallfindcleanupkeepvalueatkillwriteskeepvalueat and killwritespatch
Games::Hack::Live - Perl script to ease playing games
To start the script:
hack-live {<name of executable>|-p pid}
Commands for the script:
help dumpall [name] find <value> find cleanup keepvalueat <address> <value> ["textual name"] killwrites <address> ["textual name"] [ask] patch <destination name>
All other strings are passed through to GDB.
This script helps you patch your favourite programs, to keep them from incrementing error counters or decrementing life values.
It does this by starting your program, and attaching gdb (the GNU
debugger) to it; with its help it can examine memory, change it, and find
locations in the program that try to modify it.
In order to use that script, you need a machine-dependent perl library for
patching the programs; for 32bit x86 machines, it would be
Games::Hack::Patch::i686.
You can also attach gdb to already running processes, via the -p
switch; please do not forget to put the double dash -- in front of that,
otherwise the perl interpreter will take that option for itself.
CTRL-C and SIGINT, "cont"To control whether the debuggee should run or not you can simply press CTRL-C; the resulting signal gets caught by the script, and it will try to stop the debuggee, so that its state can be examined.
Use any abbreviation of cont (like eg. c) to continue its execution.
helpThis just shows the documentation of the Games::Hack::Live module, which
you're just reading now.
If perldoc is not available, it tries to show the synopsis by using
%INC; if that doesn't work, too, the user is out of luck.
dumpalldumpall [name]
This command writes all writeable mappings of the program into files like /tmp/$PID-$DUMP_NUMBER-$NAME/$start-$end.
These could be used to compare the data at different times.
findfind <value> find (<format>)<value> find (<format>)<value> .. <value> find (<format>)<value> - <value> find
The most important step is to find the memory location where the debuggee stores the precious values. If you know that you have 982 money points left, you can simply say
find 982
and you'll see a list of some memory locations where this value was found. If you buy something and see the number 922, use
find 922
to see an updated list; especially the most wanted list, where the number of
matches is counted. If you typed find 7 times, and one memory location
was found every time, it's very likely that this is the address you want.
Normally 2 or 3 searches suffice to determine a location.
find without an argument just prints the last output list again.
The default search only looks for an integer value; you can change that by the format specification:
signed or unsigned prefix.
These are
A character; should be 8 bits long.
Always 16bit.
Always 32bit.
Always 64bit.
The C type int, which can (machine-dependent) be anything from 16 to
64 bits.
Please note that (because of the perl conventions) an int can here be
bigger than a long - which violates C standard!
--- should possibly be changed?
Only for char the default is unsigned; all other integer types
default to <signed>.
float and double
These are native representations, which on most machine will be conforming to the IEEE-standard anyway.
As most floating point values cannot be represented exactly, and they
surely won't be displayed with full precision, some range has to be
allowed; for the .. and - specifications you can give start and end value
like
find 200 - 200.9999 find 200 .. 200.9999
If you don't do that, a range of values is assumed:
[ int(X-1)+0.5, int(X+1) ]
The second case tries to account for the fact that a visible value of 94 might be anything from 93.5 to 94.9.
Note that if you want to use the auto-range feature, you'll need to either prepend the correct type, or use an explicit decimal point:
find 55.0 find (float)54
This consists of single or double quotes, and the string therein:
find "Player 1"
This should be used sometime to get relative positioning of the patch addresses; currently it's nearly useless.
If you give a single value, int is taken as default type; for two
values, float.
cleanupcleanup
If you found an interesting memory location (and used it with the commands keepvalueat or killwrites, or wrote it down), you might want to start a new search.
Use the cleanup command for that; it cleans the search history.
keepvalueatkeepvalueat <address> [(type)]<value> ["textual name"]
If you found out where your money, life or wizard points are stored, you might want to keep that at a value that eases the game a bit. Simply tell which memory location should be kept at which value, and an optional name (which is used for Final output), and you get a watchpoint registered that resets the value after it has been changed.
keepvalueat 0xafad1208 20000 "Money" keepvalueat 0xafad120c (float)120 "Energy" keepvalueat 0xafad1218 10.0 "Power"
Please note that this might cause a (very slight) runtime overhead, in that
every write to this location causes a break into gdb, which
overwrites the value again, and has to return to the debuggee.
Depending on the frequency of changes you might be able to notice that.
killwriteskillwrites <address> ["textual name"] [ask]
This command has a purpose similar to keepvalueat, but achieves that by patching the program.
It registers a watchpoint, too; but on a write to the given address the script takes momentarily control, deassembles a bit of the program, and patches the assembler code so that the modified value doesn't reach its memory location.
keepvalueat 0xafad1208 "Money"
If you specify the optional flag ask, you get asked for a description on
every such event; this is handy if you want to differentiate between
good and bad events later.
keepvalueat and killwriteskillwrites has to be done only for a single run; the patch commands might then simply be loaded without runtime-overhead.
If a modified binary was written (see patch), this can simply be started; not even gdb has to be invoked.
keepvalueat gives a better starting point - instead of having to do some steps to get enough money you simply have the money needed.
Possibly both could be done - patching writes out of the binary, and change the initial value that gets loaded. Volunteers?
patchpatch <destination name>
With this command the program gets copied to the new name; the currently known locations are patched, as found by the killwrites manpage.
patch patched-prg
Currently after the script was ended with EOF (CTRL-D on the command
line) it outputs the patching commands used.
The gdb documentation for other useful commands, and Star Trek -
TODO about ethical considerations (Kirk patches a simulation, and so is
the only one that ever made it).
I found no way to determine in gdb which memory regions are mapped
read-write (info proc mappings doesn't show the mode), so I had to
read /proc/*/maps directly - which limits this script to Linux only
currently.
This is my first project using the Expect manpage, which was recommended to me by Gabor Szabo (CPAN id SZABGAB) during the YAPC::Vienna 2007 -- instead of writing my own loop.
So there might be bugs; the script might break the connection, but the debuggee will run along.
You're welcome to help.
For some things it might be good (or even necessary) to avoid giving distinct values to look for - eg. because they're simply not known. If you have just some kind of barchart showing energy left, you might know when it changes, but no value. (Similar if the display differs from the internal representation).
So storing/comparing memory dumps might prove helpful for such cases. First attempts done in dumpall - we'd have to ask for two (or more) dumps with the interesting value unchanged, and a few with it changed - to compare the dumps and find the location. (Which is the fastest way - simple use the dumps as bitvectors, XOR them, and look for 0/!0 values?)
Hardware breakpoints (for the keepvalueat and killwrites commands) are available on the higher x86 (Pentium and above, I believe) - don't know about other platforms.
The number of available hardware breakpoints is not checked.
More patch libraries are needed.
The commands given by killwrites are meaningful only for a single executable; if it gets only recompiled, they might be off.
So this should maybe get bound to a MD5 of the binary or some such.
Instead of simply outputting commands for gdb to patch the program, the
binary itself could be patched (to a new name); then this binary could
simply be started instead of the other one. (Would avoid needing to check
the MD5 of the binary.)
Or a shell script should be printed, that would take care of patching the
binary (via gdb) itself - so only the shell script would
have to be started. (Could check for the MD5 of the executable, too!)
A further idea might be to export a shell script that uses
echo/dd/perl or suchlike to patch the binary in the filesystem.
This would avoid permission problems (users normally can't write to the
binaries) and easily allows to transmit the changes via email.
The region around the patched location could be stored as a disassembly dump, to possibly find the same program code again after an update.
As in the good old times (C64 and similar) sometimes the easiest way is to
look for the output code - eg. search for Lifes: %4d Energy: %3d in the
program data, do a cross-reference where it's used, and resolve back to
the memory locations used for the output (eg. via printf).
Would need some kind of intelligent disassembler - to (reversely) follow the
data-stream; but should be doable, at least for easier things (like
printf output - simply look for argument N on the stack, where it
comes from).
Should be Games::Hack::Offline, or some such.
Some kind of graphical interface would be nice (eg. Tk) - on another screen, X server, or some serial console?
As linux is getting address space randomizations, the data addresses reported might not be worth anything in the long run; if the executable sections get moved, too, not even the patch commands given by killwrites will help.
There should be some way to describe the relative positioning of the memory
segments - easy for heap, stack or executable segments, but other
anonymous ranges?
Patches are welcome.
Ph. Marek <pmarek@cpan.org>
Copyright (C) 2007 by Ph. Marek; licensed under the GPLv3.