NAME
Games::Hack::Live - Perl script to ease playing games
SYNOPSIS
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.
DESCRIPTION
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.
SOME DETAILS
Controlling the run-state - 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.
help
This 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.
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:
- Integer types, with an optional
signed
orunsigned
prefix. -
These are
- char
-
A character; should be 8 bits long.
- short
-
Always 16bit.
- long
-
Always 32bit.
- long long
-
Always 64bit.
- int
-
The
C
typeint
, 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 along
- which violates C standard! --- should possibly be changed?Only for
char
the default isunsigned
; all other integer types default to <signed>. - Floating point types -
float
anddouble
-
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 likefind 200 - 200.9999 find 200 .. 200.9999
If you don't do that, a range of values is assumed:
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
- String type
-
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
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.
Discussion about keepvalueat
and killwrites
"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
Final output
Currently after the script was ended with EOF
(CTRL-D
on the command line) it outputs the patching commands used.
SEE ALSO
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).
BUGS/CAVEATS/TODO/IDEAS/WISHLIST
- Operating system - Linux only
-
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. - Stability
-
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.
- Search intelligence
-
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 (in)dependence
-
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.
- Binary matching
-
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.
- Binary patching, program start
-
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. - Updates
-
The region around the patched location could be stored as a disassembly dump, to possibly find the same program code again after an update.
- More difficult - finding locations by output
-
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. viaprintf
).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. - Interface
-
Some kind of graphical interface would be nice (eg. Tk) - on another screen, X server, or some serial console?
- Other points
-
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.
AUTHOR
Ph. Marek <pmarek@cpan.org>
HOMEPAGE
The homepage is at http://games-hack.berlios.de/.
COPYRIGHT AND LICENSE
Copyright (C) 2007 by Ph. Marek; licensed under the GPLv3.