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.
gdb
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.
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.
-p
--
CTRL-C
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.
c
help
This just shows the documentation of the Games::Hack::Live module, which you're just reading now.
Games::Hack::Live
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.
perldoc
%INC
dumpall
dumpall [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.
find
find <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
unsigned
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.
C
int
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?
long
Only for char the default is unsigned; all other integer types default to <signed>.
char
float
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.
cleanup
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.
keepvalueat
keepvalueat <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.
killwrites
killwrites <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.
killwrites 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.
ask
"killwrites" 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?
patch
patch <destination name>
With this command the program gets copied to the new name; the currently known locations are patched, as found by killwrites.
patch patched-prg
Currently after the script was ended with EOF (CTRL-D on the command line) it outputs the patching commands used.
EOF
CTRL-D
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.
info proc mappings
This is my first project using Expect, 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.
Simply patching the program is already possibly; another way would be to print a shell script, that took care of patching the binary (via gdb) itself - so this script would have to be started instead of the original executable. (Should check for the same executable - MD5/SHA-256 or whatever.)
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.
echo
dd
perl
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).
Lifes: %4d Energy: %3d
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.
Games::Hack::Offline
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?
heap
stack
Patches are welcome.
Ph. Marek <pmarek@cpan.org>
The homepage is at http://games-hack.berlios.de/.
Copyright (C) 2007 by Ph. Marek; licensed under the GPLv3.
To install Games::Hack::Live, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Games::Hack::Live
CPAN shell
perl -MCPAN -e shell install Games::Hack::Live
For more information on module installation, please visit the detailed CPAN module installation guide.