Hacking Diablo 2
by: Jan Miller
Requirements
-C Language basic knowledge
-x88 intel ASM basics
-Windows API / Windows programming basic knowledge
-OllyDbg (You can get it here:
http://www.ollydbg.de/ - but if you don't have it yet, you might aswell just
skip this paper for the moment *g*)
.. and most importantly: Diablo II: Lord of Destruction with v1.11 Patch
General Introduction
This paper is going to be the first of a series of papers I plan to publish in
the future. The series of papers has been designed for "regular coders" that are
at interest to enhance their knowledge in the field of game-hacking. That means:
IF you're already an experienced game-hacker, this paper might be quite a bore
for you ;-). I plan on increasing the difficulty with the upcoming papers, so
essentially my readers can grow and increase their qualities while we walk the
road. I will also do my best at keeping the language as clear and
straightforward as possible.
--
OK, now that we've got that stuff out of the way, let's get things 'rollin.
Today we will take a deeper look at the red life-ball that displays your life
when hovering the mouse above it. We will essentially try to reverse engineer
Diablo II functions along the way - figure out how everything works - and to
finish it off, write a hack that will ALWAYS display the life above the red life
ball! :-) This may seem like alot of work at first, but hopefully you will get
the catch sooner or later. "Reverse engineering" isn't that hard, if you take
your time and make sensable assumptions. In the end, you're basically
re-constructing a crime, sort-of like Sherlock holmes :-).
Chapter I - The Theory
Why do we need to find an entrypoint? Well, Diablo II has a few million lines of
code, so we should try to imagine how the code-flow in Diablo II will probably
be looking like, that draws the "Life: Min/Max" text above the red ball. In the
end, all Diablo II functions will boil down to the basic system functions, so
they will be our entry-point.
First, let's use our well-functional brain and gather some basic information,
before starting (note: Using a moment of silence to think about the problem
before tackling it, will be referred to as the "zen approach").
What do we know about text-drawing in Diablo II?
1) Diablo II is an international game that is sold in asian countries aswell. We
assume that they handle strings in UNICODE format, to support countries such as
"Korea"
2) In order for Diablo II to draw a text at a specific location, it needs to
gather infos about the text it is drawing and where it is drawing. The infos
gathered will be: "String length", "Screen Dimensions", "Text Color", etc.
What do we know about the red ball?
The most important fact about the life being drawn above the red ball is that
the text is centered. The life of a player is variable, so the string length of
the "Life: Min/Max" string will be variable aswell. So, in order for Diablo II
to draw that string, it needs to somehow calculate its string-length - that is
definate!
OK, so in this case I will use the fact that Diablo II needs to calculate the
string length as our approach to get an entrypoint into a code-location that is
near the "Draw Life to screen" function. This is the code-flow that I am
assuming:
-> Diablo II gets mouse info
-> Diablo II checks if mouse is hovering the life ball
-> Diablo II calculates the players life
-> Diablo II sets up the output string (we will probably see a referrence
to d2lang.dll here, as the Prefix may be "Life", "Leben" or any other
language-dependent output)
-> Diablo II will print the string to the screen
---> Diablo II will calculate the string length in here, somewhere (THIS
IS OUR ENTRYPOINT!!) |
So basically, we will be breaking into Diablo II's code very deeply and use the
stack to "walk back" to the callee functions. More to that later!
Chapter II - The Approach
So, fire up Diablo II and join a singleplayer game. That's my "preferred"
environment for Diablo II reversing, as you will not "timeout" from a game if
it's paused for too long (unlike battle.net, where you will be booted from the
server for missing ping responses).
Alright, after you joined a singleplayer game and successfully attached OllyDbg
to Diablo II, let your mouse hover over the life-bar so Diablo II's code passes
the "IsMouseHoveringBall" check and actually draws the life. As soon as your
mouse is hovered above the life-ball, use alt-tab to tab back to olly (be sure
to not move your mouse before tabbing out of Diablo II). This is necessary,
because we want Diablo II to save our mouse-position above the ball. The next
time we maximize Diablo II the "IsMouseHoveringBall" check will pass and the
code location's we breakpointed will be executed.
We will now try to find the "string-length" function as suggested in Chapter I
and set an execution breakpoint there. You should have Diablo II minimized with
your mouse hovering over the life-ball with ollydbg attached to the process
before following these steps.
1) Press ALT-E to open the module list of Diablo II (lists all .dll files that
Diablo II is using)
2) Find d2lang.dll and select it. Now Press CTRL-N to get a list of
imported/exported function names
3) Find the mangled "strlen(UNICODE)" function and set an execution breakpoint
(F2)
Image Example
4) Now we are ready to maximize Diablo II, do so. You should be breaking at the
strlen function now. Obviously, this function is being called by many different
functions in Diablo, so you should press F9 (run) so many times until you
actually see the "Life: Current/Max" string in the ECX register (used as
parameter, if you take a close look at the function). You should eventually end
up looking at something like this:
Image Example
Note: It displays "Leben" instead of "Life" in the screenshot, because I am
using the german d2lang.dll
5) Now take a look at the stack, it should be looking like this:
Image Example
As we can see, the d2lang.strlen(UNICODE) function is being called by
D2Win.6F8EDFB9, which is probably inside the "TextDraw" function (we are making
this assumption, based on the code-flow we created in Chapter I). We can also
see, that the *supposed* "TextDraw" function is being called by a function in
D2CLIENT (the second return address in the stack, see picture above).
6) Right-click the second return address in the stack and select the "Follow in
Disassembler" option in the drop-down menu. You should now be at this code
location:
Image Example
As you see, I've commented it abit. At this point (now that we know where the "TextDraw"
for the life-ball hover-text is called) you should clear the breakpoint on the
string function and set a new one abit futher above the new code location we
found and repeat the process of maximizing Diablo II and breaking at the new
location you chose. Do that until you understand what is going on and can make
judgements about what the code is actually doing.
On a side note: We can derrive from the analysis of the d2lang.#10005 function
call, that the function has this syntax:
typedef wchar_t* (__stdcall* fnGetLangStringByID) (DWORD dwID) |
7) Now that we know what function actually draws the text, let's investigate how
Diablo II actually gets the players life. Clear all your current execution
breakpoints and make a new one at d2client.6FADD6C0 (the first instruction of
the "DrawLifeAboveLifeBall" function). Restart the entire process (press F9 to
let d2 execute itself, maximize d2 and then move your mouse over the life ball
so olly breaks) so we can get a fresh start.
8) Now that we breaked at d2client.6FADD6C0 let's follow the code-flow and learn
how Diablo II gets the players life. To do this, it'd be a good idea to get the
hex-value of your current (and max) life. In this case, the hex value is: 0x2C
(45 decimal). IF you really don't know what you're doing, you just follow the
code flow by single-stepping (with F7) until the EAX register (which holds the
return value of a function) returns our life, which is 0x2D (all values in
ollydbg are displayed in hexadecimal). The first referrence of our current life
is found here:
Image Example
As you can see in the image, EAX holds the value 0x2D00 and is (a few
instructions later) shifted to the right by 8 bits, which would result in 0x2D.
IF you remember, our current life is 0x2D! BINGO!
We can now derrive from the analysis the following functions:
typedef DWORD (__fastcall* fnGetOwnPlayerStat) (DWORD StatID)
//d2client.6FADCCC0 param passed in EAX! |
typedef DWORD (__stdcall* fnGetMaxLifeFromUnit) (unit* ptrToUnit)
//d2common.#10907 |
As you know, __stdcall passes the parameter on stack and __fastcall passes the
first two parameters in ECX and EDX, the rest on stack. ODDLY enough though, the
"GetOwnPlayerStat" function passes the parameter in the EAX register, which is
very uncommon. This is the case, because Blizzard changed their compiler
settings to call functions in a very optimized way (MSVC++ 7.0 might've been the
cause). The only way we can actually call the function is to build a "wrapper"
that adjusts the parameters for us. You'd call "GetOwnPlayerStat" like this:
DWORD __declspec(naked) __fastcall GetOwnPlayerStatWrapper(DWORD StatID) {
__asm { mov eax, ecx //first parameter is in ECX, so we move it to EAX, as
the D2 function requires it
call fnGeOwnPlayerStat
ret
}
} |
9) Now that we have completed the analysis, let's move on to our actual goal:
Making a hack that always enables the life display above the life-bar,
irregardless if the mouse is hovering. We're taking the same approach as in step
5) - we will breakpoint at the first instruction of the "DrawLifeAboveLifeBall"
function, let olly break at it, and check out the stack as to who is calling us.
Then we will investigate at -what condition- we are being called. It turns out,
that actually the function is called *no matter where our mouse is located* - so
the conditional check has to be somewhere between the first instruction
(d2client.6FADD6C0) and the call that gets our current life (d2client.6FADD742).
A little "guessing work" will make us come up with these results:
Image Example
10) Now that we know where the actual check happens, we can circumvent it
easily. A possibility to do so is to add an unconditional jump at
d2client.6FADD710, as shown in the image:
Image Example
Chapter III - Writing the hack
Basically, to make the hack that displays the life above the life-bar at all
times, we have to only change 2 bytes in Diablo II's code. We need to write
0x2BEB at d2client.6FADD6C0. You could achieve that by changing the memory
protection of that memory page to EXECUTE_READ_WRITE and calling
WriteProcessMemory - or just simply overwriting the location. Your .dll file
could look like this (very dirty code):
BOOL WINAPI DllMain(HINSTANCE hDll,DWORD dwReason,LPVOID lpReserved)
{
switch (dwReason) {
case DLL_PROCESS_ATTACH:
*(WORD*)&0x6FADD6C0 = 0x2BEB;
//enable life display above life-bar at all times
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
default:
break;
}
return TRUE;
}
This would actually do the job. Detectability is another issue, but that is not
the aim of this essay. We might address that in another future edition.
|
|