게임/소스정보
PrintScreenTut
SGhacker
2011. 10. 17. 14:49
Introduction:
I always categorized hacks into two categories: hacks that
exploit game glitches and security flaws, and hacks that simply
give you information. For the latter, finding a convenient way to
display this information is quite tricky at times. In Starcraft, a
simple alternative is to display the information as a chat
message, but sometimes this isn't enough. One common hack that
needs a good way to display information is the "stats hack".
I'm sure all of you know what a stats hack for Starcraft is, it
displays the minerals/gas/supply of each player on the screen.
Since this is constantly being updated, it is impractical to
display this information through chat messages. There isn't a
pre-defined space for you to write the data in either, besides
the leaderboard, which is a pain in the ass to get set up (so says
Drakken and Jakor). So our last (decent) alternative is to find
out how Starcraft displays any text on to the screen, and use that
to display our data.
Unlike my last tutorial, I'm not going to go into great detail
about coding in mASM, but I am going to explain most of the
debugger's output.
=================================================================
In this tutorial, you will learn:
� How Starcraft displays text onto the screen
� How to call the function ourselves (for our own evil purposes)
=================================================================
Tools Needed:
� A debugger (I'm going to use OllyDbg in this tutorial)
� Basic ASM knowledge
� Memory Searcher
=================================================================
Thinking about our hack:
The first thing I did when I started this was think about how
Starcraft would display regular text when I send a chat message.
First I thought there would be a function similar to
PrintTextToScreen(X,Y,[Duration])
But after looking for it for a while, I couldn't find it and
gave up =p. Later, I thought about what this function would
actually do. All this function would be doing is setting up the
text to be written in a variable somewhere, and another function
would read from this variable, and print it on the screen multiple
times per second. So, I attempted to find this second function
in hopes of being able ot draw anywhere on the screen.
=================================================================
Stage 1: Finding WHERE Starcraft holds data to be printed
Well, as I said above, we are looking for a function that reads
the text that another function has set up, and prints it to the
screen. So think about this: When you send a chat message, or use
Starcraft뭩 ChatOut() function, all you are doing is writing the
text into an offset somewhere. The function we are looking for
MUST read from this offset to print on to the screen, or else
it wouldn't know what text to print.
So the first thing we are going to do is find where Starcraft
stores text. This is fairly straightforward, and already
described in my previous tutorial, but what the hell, lets do it
again =p.
Start a multiplayer game in Starcraft so you can send chat
messages, and start up OllyDbg. Now type in any message you want,
start up a memory searcher, and search for "YourName: Message".
Of course, replace YourName and Message with...your name and
the message you typed in =p.
Write the address down somewhere.
If you wait for the text to disappear off the screen, you will
notice that a 00 byte is written on the first letter of your
name. This basically tells the function that prints the text to
stop printing it. This is bad, because we need to catch the
function reading the data and actually printing it.
=================================================================
Stage 2: Finding HOW Starcraft reads this data and prints it
So now that we have one of the places where Starcraft stores its
chat messages (there are like 12 in total or something, never
really bothered to count), we have to see which function reads
from it. This will be the function we're looking for :).
So, fill up the chat buffer by sending repeated messages until
they start scrolling off the screen. Make sure that none of them
disappear, then ALT+TAB into ollydbg and quickly set an
ACCESS breakpoint anywhere in your message (as long as its not
the beginning of it).
To set an access breakpoint:
� Locate the "Hex Dump" in the lower left corner of ollyDbg
� Go to the location of your message by hitting CTRL+G
and typing in the address you wrote down earlier.
� If you don't see your message, try repeating the previous step
one more time
� Once you see the message (in the ASCII Column), highlight any
one letter of the message (except the first one, because
another function might be reading from this one)
� Right click on this letter and go to
Breakpoint > Memory, On Access
Now quickly ALT+TAB back into Starcraft. If you took too long,
your text might have disappeared already. Wait a little bit
to see if ollydbg pops. If it doesn't after 10 seconds or so,
remove your breakpoint, re-fill the chat buffer, and try again.
Ollydbg should land somewhere like this:
|INC EDI
|MOV DWORD PTR DS:[6BEA24],ESI
|MOV CL,BYTE PTR DS:[EDI] ;Olly lands here
|TEST CL,CL
\JNZ SHORT Starcraf.004DAACF
MOVSX ECX,WORD PTR DS:[6BEA64]
Well, I really don't care what this is doing, but if you insist
on knowing, its basically a loop that moves every letter of your
message to somewhere else =p. At least it looks like it =\.
A good thing to do right now is to remove your memory breakpoint,
so we won't get bugged by other functions reading from the
same place.
Now we are going to go one function level up
To go one function level up:
� Look at all those blue buttons at the top of OllYDbg
� Find the one that says "Execute Till Return"
� Hit the button that says "Step Into", or press F8.
We are now staring into the function we were just in. As you can
see, we're still in a loop (so says ollydbg), and this doesn't
look like its a higher-level function.
So go one more function level up by using execute till return and
step into.
Ok, now we're not in a loop, but think about this. The function
we want should have at least 3 parameters, X coordinate, Y
coordinate, and a pointer to our text. Now Jakor being the smart
guy that he is once told me that Starcraft uses something called
"fast-call"...or something like that. This basically means that
when Starcraft calls a function, only ECX and EDX are actually
parameters. The rest of the parameters are PUSHed into the stack.
So if we have 3 parameters (X, Y, and Text), we have to have at
least 1 PUSH before the call, which I don't see. So that made me
think I'm still too low. This, and for the reason that when I
tried to NOP the call out, I got crashed =p.
So again, one more level up. You should be looking at this:
|PUSH EBP
|AND EDX,0FFFF
|MOV BYTE PTR DS:[6BEA31],11
|MOV WORD PTR DS:[6BEA60],CX
|MOV WORD PTR DS:[6BEA64],276
|MOV WORD PTR DS:[6BEA62],DI
|CALL Starcraf.004DAB60 ; this is the function we were in
|MOV ECX,DWORD PTR DS:[659176]
|LEA EAX,DWORD PTR DS:[ESI+1]
Will you look at that, there is a PUSH in there, just like good
ol' Yondy said =). Now lets check EDX and ECX before this
function gets called to see if they are X and Y coordinates, and
check the push to see what we are PUSHing.
Press the blue "Play" button at the top of Ollydbg. Next,
select the line of code with the "PUSH EBP" on it. Then press F2.
This toggles a breakpoint, so whenever this line of code is ran,
OllyDbg will stop it and we will be able to check the registers
and run through the code line by line. After you set your
breakpoint, switch back into SC. Ollydbg should pop immediately.
If it doesn't, then try chatting some text.
Now look in the upper-right corner at the Registers. Check out
EBP. Its a pointer to our message! Now press step into. This runs
one line of code. Press Step into again to run the "AND EDX,..."
EDX is now something like C0, and ECX is something like 0A.
This looks like the function we are looking for!
Remove your code breakpoint and press Play. We are now going to try
to NOP it out (stop it from being executed). If we NOP it out, and
no text is written to the screen, we know that this is the function
responsible for writing the text.
To NOP out the function:
� Right click on PUSH EBP
� Select Binary > Fill with NOPS.
� Right click on the CALL, and do the same.
Go back into the game and check. Whoopee, no text!
Also, we can check if this is the function being used by ALL text
in the game.
To do this, we will try to NOP out the whole entire function; not
only this one call to it. If it is called multiple times by
different places, we need to either NOP out the whole function,
or NOP out all the calls to it.
To NOP out the whole function:
� First un-NOP the call by right-clicking and selecting
"Undo Selection"
� Press ENTER on the call to jump inside it
� Click on the line of code that was selected and drag the
selection all the way down until you see a RETN 4 and a
couple NOPs, but don't select the RETN 4
� NOP all of this code out
Switch back into the game to see if there is any text. You will
notice that there is no text ANYWHERE in the game :).
Again, "undo selection" so we can continue with the rest of the
tutorial.
=================================================================
Stage 3: Figuring out how to call this function
The thing about writing to the screen is, if you write at the
wrong time, something else might draw over your text, and the
text will flicker. This is the same with this function, if we
just make it draw every millisecond or so, it will still flicker.
So, what better time to draw our text, than right after Starcraft
draws its text?
Well we already know where Starcraft makes its call to draw regular
chat text. So we'll stick with making our call after this one.
The only problem is that its only written when there is actually
chat text on the screen. Look at the code again.
0046E746 MOV BYTE PTR DS:[6BEA31],11
0046E74D MOV WORD PTR DS:[6BEA60],CX
0046E754 >MOV WORD PTR DS:[6BEA64],276
0046E75D |MOV WORD PTR DS:[6BEA62],DI
0046E764 |CALL Starcraf.004DAB60
0046E769 |MOV ECX,DWORD PTR DS:[659176]
0046E76F |LEA EAX,DWORD PTR DS:[ESI+1]
0046E772 |CDQ
0046E773 |ADD EDI,ECX
0046E775 |MOV ECX,0B
0046E77A |IDIV ECX
0046E77C |DEC DWORD PTR SS:[ESP+C]
0046E780 |MOV ESI,EDX
0046E782 \JNZ SHORT Starcraf.0046E708
0046E784 MOV AL,BYTE PTR DS:[659064]
0046E789 TEST AL,AL
Well, if you try setting a breakpoint (F2) on the actual call,
you will see that it only if called when there is chat text on
the screen (makes sense). But we want our text to appear whenever
we want it to. Try setting a breakpoint farther down.
I set it at MOV AL,BYTE PTR DS:[659064].
This is being called constantly, whether or not there is chat text
on the screen, so we can use this as our patch. Write the address
of this line of code down (0046E784).
So lets sum up:
Call 004DAB60
ECX, EDX - X and Y coordinates
Pointer to Text - PUSHed
Patch at 0046E784
=================================================================
Stage 4: Problems :(
Well, I won't go through coding this, but if you need any help look
at my Unit Alert tutorial. I discuss how to make patches, and
call SC's function's in that tutorial.
So one thing you will notice with this is that the text doesn't appear
on the screen right away, only until the screen refreshes =(. Another
thing you will notice (maybe not, but I noticed it) is that if the
chat buffer's full, your text will disappear!
Took me a while to figure this one out myself. I tried setting my
patch at several locations, and noticed that every time it takes
the formatting of the place I set my patch to.
Well, SC is always calling the same function...and the parameters
pretty much look the same. The only other thing that could be happening
is that SC writes some extra formatting to some static locations.
Well, look around the call that we found.
0046E746 MOV BYTE PTR DS:[6BEA31],11
0046E74D MOV WORD PTR DS:[6BEA60],CX
0046E754 >MOV WORD PTR DS:[6BEA64],276
0046E75D |MOV WORD PTR DS:[6BEA62],DI
0046E764 |CALL Starcraf.004DAB60
0046E769 |MOV ECX,DWORD PTR DS:[659176]
0046E76F |LEA EAX,DWORD PTR DS:[ESI+1]
See all those MOV's before the call? That's right, STATIC offsets.
Try playing with the numbers to see what they do. They are some kind of
formatting string.
I won't go into great depth with this, but you need to mess around with
these to see which one will make your text stop disappearing. You can
also use this for centering text and things like that.
So before your call, you need to BACKUP the previously formatted strings,
and write your own, and then replace them with the backups after you're
done. Otherwise, some text will get messed up.
There are many static locations like these that hold things like text color
and many other things (you can have ANY colored text you want =D). Also,
you can make text any size you want, but I won't go into great detail with
these.
=================================================================
Stage 5: Refreshing the screen
This only leaves us with one problem, refreshing our text location
so it will be updated immediately.
Now i know some people that would be happy with just this *cough*
agent *cough*, but I like things nice and neat, and I don't rest
until my stuff is perfect :). This is the difference between a good
hack, and a crappy one.
Now, I spent a lot of time thinking about how to find the refresh
function. Well, Starcraft has to refresh the screen when chat text
is first written to the screen, so I searched around here for a long
time.
After having Starcraft crash on me several times because of NOPing
random calls, I finally gave up on searching. I gave this project
a couple day's break, but still thought about it often. I finally
remembered "Starcraft has to refresh when its text DISAPPEARS, too!"
So I decided to search around there.
To find how Starcraft gets rid of text:
� send out some chat text
� Search for it
� before it disappears, set a breakpoint on the FIRST character
of your message.
Ollydbg should land here:
0046E62F JS Starcraf.0046E6CE
0046E635 CMP ESI,0C
0046E638 >MOV BYTE PTR DS:[EAX*2+65862C],0 ; lands here
0046E640 JNZ SHORT Starcraf.0046E661
0046E642 MOV EDX,DWORD PTR DS:[659178]
0046E648 AND EDX,0FFFF
That basically puts a 0 in the first character to notify that
the text shouldn't be written anymore.
Now scroll around and look for some calls...
This one caught my eye (*ouch*):
0046E6BB PUSH ECX
0046E6BC PUSH 276
0046E6C1 LEA EDX,DWORD PTR DS:[EAX+70]
0046E6C4 MOV ECX,0A
0046E6C9 CALL Starcraf.004D8590
Well, I've messed with the X and Y coordinates so much I noticed
the 276 and 0A in there. 0A is the X coordinate that chat text
is written at, and 276 is the X coordinate of the right edge of the
screen!
Try NOPping this function out (remember to NOP out the pushes too)
After typing in some text, you will notice that it doesn't refresh
when its supposed to disappear! This is our function!
Now when refreshing the screen, you have to refresh a RECTANGLE.
So this means you need 2 sets of X and Y coordinates. Look back
at our function. We have ECX and EDX, and 2 PUSHed parameters.
Take a wild guess what these could be ;).
004D8590 - refresh screen
ECX: X1
EDX: Y1
PUSH Y2
PUSH X2
This should be all you need :).
=================================================================
Conclusion:
Well, that's it. I hope you guys have fun with this. Try
experimenting on your own too, look for format strings,
try to get your text in different colors, sizes, fonts.
There's a lot of stuff you can do with this, and be a little
respectful and give me a little props if you ever decide to use
it in one of your hacks :).
=================================================================
Shouts!:
Thanks to Jakor for teaching me so much about hacking and for
always helping me out with stuff =).
Also thanks to TheTempest for always wanting to help.
Besides that, thanks to everyone and anyone who's ever helped me
Nickolay, Fish Beans, Bulk_4me, NAATYE, Dt, Palomino, and
everyone else I forgot.
Also thanks to Palomino for figuring out how to write text in
different sizes and fonts..
Thanks to Microsoft Word for correcting me every time I spelled
"disappear" wrong.
=================================================================