The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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 or unsigned prefix.

These are

char

A character; should be 8 bits long.

short

Always 16bit.

long

Always 32bit.

long long

Always 64bit.

int

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>.

Floating point types - 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:

+-1% of the given value, or
the range of [ 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
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. 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.

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.