There is not exactly about the zoom, and not quite about mhook. The fact is that I did a little wrap over mhook (to help my work), I would like to show what happened, how I use it, and get some constructive criticism. And in order not to use synthetic examples, I will go over the thumb and make fun of zuma. I will tell in the following sequence: first a couple of words (really few) about how the mhook intercepts, then a little about how I used it, then I will describe what I did, and end up implanting a pair of electrodes into my favorite frog. So you can decide for yourself what is interesting to you, and accordingly, where to start reading.
In the beginning, as I said, a couple of words about Mhook . Mhook is a library for embedding your code into the code flow of another application. On Habré, the question of code injection has been repeatedly raised, so if anyone is interested in a detailed consideration of the issue, you can search about it on the site. In short, to intercept the code, 5 bytes are removed at the address that will be intercepted and the unconditional branching code (jmp #addr) is recorded in the interception function. And those 5 bytes, which were removed, are transferred to a specially designated place, which is also called a springboard. When the interception function runs, it can make an unconditional transition to the springboard, the 5 most saved bytes will be executed there and the transition to the intercepted code will occur. So it will look like a schematic:
It looks great, but sometimes, especially when you need to intercept code execution somewhere, for example, in the middle of a procedure, do something interesting (write the state of some memory cells to the file, change registers, something else like that) you need to insert another intermediate function between the intercepted and the interception handler. This function is declared as _naked and is needed in order not to spoil the registers. I used to do it like this (I’m ashamed to show it, so I’ll use the hidden text):
')
Horrible horror
// intercept data handler function
void onProc ( DWORD backTrace, DWORD arg1 ) {
printf ( "% d% d" , backTrace, arg1 ) ;
}
// intermediate interception function
__declspec ( naked ) void hookProc ( ) {
// save registers
pushad
// collect data that I need
__asm {
push [ esp + 0x20 ]
pop backTrace
push [ esp + 0x24 ]
pop arg1
} ;
// call handler
onProc ( backTrace, player ) ;
__asm {
// restore registers and go to the springboard to the intercepted function
As you can see, it does not look very. It’s also so-so to use, all the time it’s lazy to make a new interception, because you have to write all these ties. I decided to throw out this very intermediate function (hookProc). And in the handler function, transfer the value of registers in the form of a c ++ class. I called this class Context. It contains all general purpose registers, and provides access to them. For complete happiness, I need the following features:
Context context ;
// can read the value from the register
DWORD var = context. EAX ;
// can be written to the register
context. EAX = var
// you can read the value from the memory.
// The index can be a constant
var = context [ 0xBEDABEDA ]
// So is the register
var = context [ EAX ]
// and it should also be possible to write to memory
context [ 0xBEDABEDA ] = var
context [ EAX ] = var
// and so that register arithmetic can be performed
context. EAX = context. EAX + context. EBX * 4
// And so that the index can be this arithmetic expression
context [ context. EAX + context. EBX * 4 ] = var
// and not quite obvious, but for some reason I wanted.
// so that the offset from the register can be specified in square brackets
var = context. ESI [ 0x20 ]
The function from the previous example takes the following form:
void onProc ( Context context ) {
DWORD backTrace = context [ esp ]
DWORD arg1 = context [ esp + 4 ]
printf ( "% d% d" , backTrace, arg1 ) ;
}
RegisterHook ( realProc, hookProc ) ;
To make all this joy possible, we had to create a Register class and overload the type conversion, index and arithmetic operators in it. I am not a great expert in this, but if someone really wants to, a link to the code at the end of the article).
How does all this get into the program? I have already described above how mhook works. I added another step:
; save registers
pushad
; pass a pointer to them in the handler function
push esp
; call handler function
call hook
; clear stack
add esp , 4
; restore registers
popad
; go to the springboard for the real function
jmp trampoline
This code is stored in the string "x60x54xE8x00x00x00x00x83xC4x04x61xE9x00x00x00x00" . When you need to install a hook, memory is allocated, a string is copied there, the displacement of the handler function is substituted into the call operator, and the springboard offset is substituted into the jmp operator. pushad, in addition to saving registers, prepares data for the handler function, so to speak. If the handler function changes this data, the changes are applied by the popad command. Not sure what is so clear. I'll try on the pictures to show.
Once again I will note that after the execution of the esp command, the stack pointer points to all the saved registers, so it is passed to the handler function as a pointer to the execution context.
And now - dessert! Using this all in real terms, on the game about toads and balls. I will take the task easier. For example, to make the reward for balls increased 10 times.
To begin with, I try to find where the account is kept. To make it more interesting, I do not use a debugger and utilities such as Art Money.
In Zuma, as in many casual games, the score is a multiple of ten or a hundred. As far as I know, this is done because for some reason the score with a zero at the end is perceived by players as more “pleasant” and increases the pleasure from the game. And I am making a strange conclusion, just like in the joke about Vanka-Kosoy. If the score is a multiple of ten, then somewhere it is divided. And, in theory, should apply for this integer division, since the score is an integer and will always be divided completely into ten. You can intercept all these division operations, write down the dividend and the address where this division occurs, and then compare with the score. This is simplified by the fact that the compiler optimizes integer division by ten, replacing by multiplying by the constant 0x66666667 and then shifting it to the right (if interested, you can read about it in the book "Algorithmic Tricks for Programmers" chapter 10.3). Like that:
mov eax , 66666667h ; magic number to divide by 2.5
imul ecx ; EDX: EAX ← EAX (in edx, the result of dividing by 2.5)
sar edx , 2 ; the result is divided by 4. it turns out edx = ecx / 10
Another luck is that mov eax, 66666667 takes exactly five bytes and is easily replaced by jmp or call. I bypass the TEXT section in the loop, look for the opcodes of the operation (0xB8, 0x67, 0x66, 0x66, 0x66) and replace them with the call of the trap. Since the division into ten is a rather popular operation, showing all operations that have fallen into a trap is absolutely not informative, and it has an extremely negative effect on the speed of the game. It is necessary to filter so that one of the operands is equal to the current account. And how to find out the bill, if I'm just looking for it? It is necessary to work out a series of rules according to which the testing will take place, so that you can predict what the score will be at a certain point in time.
1) The game starts with a score of zero.
2) Every mouse click - shot
3) Every shot is effective
4) Each shot brings thirty points
5) Corollary to the second rule: since you have to do a few mouse clicks before the game, the program of interception should start counting from the moment only after the game has started and you can already collect sequences of balls.
After the rules are formulated, it becomes clear that you need to put a trap on the window procedure of the game. Then it will be known about all clicks on the mouse button, and it will be possible to signal the beginning of the game by pressing the button on the keyboard.
Finding the address of the window procedure is not difficult, for this you need to look at the arguments of the RegisterClass function in IDA. Here is what the window procedure trap will look like with all the requirements:
void onMainProc ( Context * context ) {
// button 0 on the numpad switches the state: does the scoring go or not
// If one of the possible divisibles is equal to the intended account, then the address at which the division occurs and the values of the registers are sent to the debug output.
// Instead, the code was inserted jmp, so you need to run it
context - > EAX = 0x66666667 ;
}
The video shows how I'm trying to find the address (a rather interesting modification of the rules turned out. "Zuma in the giveaway").
After the address is found, I study the function to which it belongs.
The part where the division takes place performs the calculation of digits in the sum of points that the player scored from the beginning of the level. In general, the function is apparently responsible for updating the point counter after the sequence of balls has been destroyed. Two arguments are passed to it. The first argument is a pointer to some structure (maybe the counter itself). At offset 0x18C is the current number of points, and at offset 0x190 is the number of points at the beginning of the level. The second argument is the current number of points. To find out where it comes from, I go up the stack. Immediately I discover that the current account is transferred to a function from a certain structure, where it is located at the offset 0x104. Slightly higher, I detect the operation add [esi + 0x104], edi. Obviously, this is what I’m looking for, and if you set a trap here, you can have some influence on the number of points earned by the combination. The trap code is extremely simple. The EDI register is increased tenfold, giving us an increased number of points for a successful combination.
void onAddScore ( Context * context ) {
context - > EDI = context - > EDI * 10 ;
}
Look like that's it. It remains to apologize for the large interval between posts and horse illustrations (if someone tells you how best to place them, thank you very much).
Oh, and the code , of course. But this is a big fan, for review and for the consequences I am not responsible.