NAME
Game::Hack::Live - Perl script to ease playing games
SYNOPSIS
To start the script:
perl -MGame::Hack::Live -e0 <name of executable>
Commands for the script:
dumpall [name] find <value> cleanup keepvalueat <address> <value> ["textual name"] killwrites <address> ["textual 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.
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.
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>
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.
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> <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"
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>"
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 write command so that the modified value doesn't reach its memory location.
keepvalueat 0xafad1208 "Money"
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. Even a modified binary might be written.
"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?
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.
- More types for searching
-
The searching is currently done only for integer values, although I'm working on supporting floating point values, too.
The problem is that a floating point value can't be matched exactly - if you're shown
200
on the display, it could be anything from200.0
to200.999
-- and they have different binary representations. So for floating point values a range must be given (or deduced, or assumed).Probably it would be best to take a numeric value as any of
double
,float
andint
(orlong
) and use a regex for searching, instead ofindex()
as now. - 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
-
Patching the write statements out of the code is currently valid for 32bit x86 only; via a
jmp short
(0xeb
).Hardware breakpoints (for the "keepvalueat" and "killwrites" commands) are available on the higher x86 (Pentium and above, I believe).
The number of available hardware breakpoints is not checked.
- 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
-
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!) - 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
Game::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>
COPYRIGHT AND LICENSE
Copyright (C) 2007 by Ph. Marek; licensed under the GPLv3.