[Tutorial] Reverse engineering Diablo (v1.09)
#1
Hello.

This is a tutorial regarding how to go about reverse engineering a game. In this instance I've chosen a classic: Diablo!

In order to reverse engineer something: We must have a clear-cut objective. Without a clear objective (goal): We'll wander aimlessly.
We need to stay focused on our objective. It's O.K. to put down mile-stone markers, so you can revert back to interesting ideas, but stay focused.

The tool we'll be using to reverse engineer Diablo with today, will be: x32dbg ( https://x64dbg.com/#start ). This is an excellent, free and powerful tool.
Diablo's a 32-bit program. If you try to load it with x64dbg: The program should load up with x32dbg for you. If not: Load it up with x32dbg manually.

We'll start by defining our objective for the day. We will start off by finding more information about how Diablo's items in the inventory work.
We can uncover all sorts of interesting and fun information! Perhaps we'll learn how to make all items indestructible or increase stats.

OBJECTIVE: Learn about Diablo items (in the inventory).

Where should we attack this daunting task, you ask? I suggest we play Diablo for a little bit and gather some items up. In fact: Fill your inventory!
After you've had your fun and have filled up your inventory with items and equipped what items you can (weapon, shield, armor, helm, etc): Pause.

Just return to town and leave the game running. We're only beginning! Smile

Open x32dbg and click on "File" then "Attach" (or press ALT + F2 as a hotkey). Find the "DIABLO" process and attach our debugger to it.
After it's finished loading: Find the "Memory Map" tab and click on it. Scroll down until you find "DIABLO.EXE" then double-click on ".text" (executable).

Right click anywhere in the memory map window pane (where all the assembly code is shown) -> Search For -> Current Module -> String References.
Find something of interest that may lead you to your items in your inventory.

The only thing I saw was a reference to Griswold (NPC), asking if you want to sell an item. This means that Diablo must look at your item.
This will hopefully give us something good to look at. If at first you fail: Try, try again!

You can double click on the string reference and it'll jump you to where it's at in the memory map. Then you can press F2 to set a break-point.
Alternatively: You can just select the string (click once) and press F2 to set a break-point.

A break-point means that when the program executes that instruction: The entire program (Diablo) will freeze. Allowing you to go instruction-by-instruction through the program.
This will expose all the registers, memory dump, stack and other fancy things. This will allow us to gather more sensitive information.

You should probably talk to Griswold and attempt to sell an item at this point (after your break-point is set).
For me (v1.09 patch of Diablo) it popped on this particular address (yours should be the same, but maybe you're on another patch):
Code:
004597F9 | 68 78 32 4A 00           | push    diablo.4A3278                        | 4A3278:"Are you sure you want to sell this item?"

You'll notice at Griswold: He has a few menus after talking to him initially. The tree reads:
Talk to Griswold -> Sell items (your unused items in your inventory were loaded here?) -> "Selected item" -> Yes/No option

Our break-point appears to have popped when the static text of "Are you sure you want to sell this item?" is pushed into the text-rendering function.
We will have to walk our way through and put notes all over the place and explore! This is the fun part.

To put a comment on an instruction: You can use the hotkey of a semi-colon ( ; ). You can also place break-points along the way so you don't get lost.
You could also use CTRL + C and a notepad (or x32dbg's internal "NOTES" tab which is associated with this process (DIABLO.EXE)).

You can use the hotkey F8 to step over (meaning: The debugger will execute all Nth deep calls until you "step over" the function call) instructions.
If you'd like to step into a function: Press F7 (you can also hover the mouse over a function call to see a "preview pane window" pop up).

When we press F8: We're placed on a JMP command. That leads us here:
Code:
0045982C | 68 B0 31 4A 00           | push    diablo.4A31B0

We also see that it pushes the contents of the ESI register (which is another memory address).
You can right click on the ESI register (far upper right pane) and click on "Follow in dump" if you'd like to see what is being pushed onto the stack (lower right pane).

If you press F7 on the next call: You will enter into that function call. It is more than likely just a text-rendering function of sorts (top level perhaps?).
Code:
00459832 | E8 49 05 01 00           | call    <diablo.renderText()>

Upon entering the function, we can immediately see that a string reference to our item is moved into the EDI register (copied from the stack-segment).
Code:
00469D81 | 8B 7C 24 08              | mov     edi,dword ptr ss:[esp+0x8]

If we press F8 to step over some instructions: We'll find ourselves in a loop! This loop appears to be going through the string(s) to be displayed.
We exit out of the function, only to see that we're back at where we started! Frustrating, almost. It feels like we've made no progress.

The next instruction we've landed on is here:
Code:
00459831 | 56                       | push    esi                                       |
Pressed F7 here -> 00459832 | E8 49 05 01 00           | call    <diablo_copy.renderText()>                |
Landed here -> 00459837 | 59                       | pop     ecx                                       | ecx:"Say goodbye"

It's okay though. Don't give up hope and believe that it is "too hard" or "too complex." This is actually quite exciting to me.
We're making progress. We're learning something different and new. All roads lead to Rome. This is just the one I've chosen to take.

If we press F8 and walk down some more: We can see the "Yes/No" question posed by Griswold!
Code:
0045984E | 68 AC 31 4A 00           | push    diablo.4A31AC                        | 4A31AC:"Yes"

This is loading the menu screen (which is NOT our objective, but is quite interesting; Maybe we could put a marker?).
This is also quite important. The game (DIABLO.EXE) must delete our game item and exchange it for gold (also in our inventory).

I would step into the function where he asks if you would like to sell the item. We can repeat the same procedure.
We will find where the text "Yes" is displayed and place a break-point on it (by pressing F2).

You could place the break-point outside of the function (where it is called at least once):
Code:
00459857 | E8 89 E7 FF FF           | call    <diablo.sub_457FE5>                  |

However: This doesn't sound like a good idea. This was called when we first talked to Griswold and asked to sell items.
I believe it would be better to enter the function first (press F7) and place a break-point on the entry (press F2).

This is where I ended up. If you ever get lost: You can click on the memory map pane then use the hotkey CTRL + G and enter "00457FE5" as the address.
Code:
00457FE5 | 56                       | push    esi                                       |

Now that we have our new break-point set on (what we hopefully assume is Griswold's "Yes" option): We return to our game and try to sell our item!

Success! The debugger has popped on our break-point for displaying text. We can press "F9" to tell the debugger to run, until it hits a break-point.
If you have more than one item for sale: It should pop again and again and again.

What I am seeing is my "Leather Armor" constantly showing. That is the text being rendered that you see in Griswold's menu screen.
It must've come from my inventory. 'But how do I find that out?' You ask. Have patience! This is an incredibly odd way of solving the issue.

If you glance up at the registers (upper right window pane): You will see the text being displayed under the EAX register.
The EAX register is holding a memory address (more than likely our culprit). You can right click on the EAX register -> "Follow in dump"
Then you can right click on the beginning address where the item is being loaded from (in the dump, on the lower left) -> Breakpoint -> Hardware, Write -> Byte

For me, the address I am setting a hardware break-point on in the data segment (DS) or "dump" is:
Code:
006A0A2D

What will this do, you wonder? That's okay. Questions are good to ask! Never be afraid to ask questions.
Setting a break-point in the .data or data-segment or DS or "dump" will do different things based on your break-point's conditions.

A "hardware, write -> byte" means: It will freeze the program when the program attempts to modify that variable (by writing to it).
Why would we want to put a break-point there of all places, you ask?!! Because: Diablo must've loaded my item from the inventory.
It either copied my item from it's location and placed it at this new address OR it directly modifies my item from Griswold's menu.

We'll find out soon! Press F9 to continue. Wink

Oh wait! Even after putting our clever hardware break-point in the dump: It keeps popping on our text-rendering function.
Press F2 to disable the text-rendering function OR navigate to the "Breakpoints" tab at the top and left click on your active break-point and press "SPACE-BAR" (toggles disabled/enabled).

To reiterate: We want to remove (disable preferably) the break-point set in the .text (executable code; "Memory map"). The text-rendering one for Griswold.
Code:
00457FE5 | 56                       | push    esi                                       |

Now press F9 in the debugger, return to the game and attempt to sell an item to Griswold in exchange for gold.

Our debugger appears to have popped (as our game has frozen) yet again! Success! That is our data-segment break-point!!!
This is VERY exciting and GREAT news! This means something has attempted to WRITE to the memory address of our item string.

If we're lucky: This will be our item in the game and it will be directly modified. If not: It should be near-by anyway.

My debugger popped on this particular instruction (which is quite Greek to me):
Code:
00458AD5 | F3 A5                    | repe movsd                                        |

After a quick Google search on the mnemonics (Assembly instructions): "repe movsd"
ECX holds the number of bytes to copy; It copies FROM the address listed in ESI TO the address listed at EDI.

Quantity to copy: ECX
(Copy from) ESI -> (copy to) EDI

You can right click on the ESI or EDI registers and follow in the .data dump. This should be your inventory!
I would look at the ESI register first (as it's likely our inventory; since it is being copied from that location).

Voila! After following the address located in the ESI register and scrolling up/down the data segment: I can see other items!!!

I have "gold" I have the item I'm about to sell and scrolls and potions. You should see yours too!

After you've completed this tutorial: Now you have quite a few options opened in front of you (without the anxiety and stress).
You have the ability of easily finding out how Diablo manipulates menu text from NPCs, displaying text in general and items!

You can modify values in your items (that are in your inventory) and see what changes happen in the game (trial and error).
You could try dropping an item and seeing what values are changed in the inventory. This will tell you how the game really operates on the data.

In case you're not too comfortable with this: I'll give one freebie out for you (to encourage you to play with it).
If an item is NOT currently in the inventory: The value will be read as: 0xFF 0xFF 0xFF 0xFF

For my game: My shield's address (in the data-segment) is listed at:
Code:
00686EFC

You can right click in the "Dump" -> Go To -> Expression (or press CTRL + G)
Enter the address: 00686EFC then press enter

If you have a shield equipped: You will not see the first 4 bytes listed as 0xFF 0xFF 0xFF 0xFF
Instead: You will see some numerical value that the game interprets and uses to modify the graphics displayed.

Hopefully this tutorial has inspired you to learn more and try more things in general. Smile
Reply
#2
Hello.

This tutorial is more focused towards using multiple tools to aid in reverse engineering our selected target (Diablo! Patch v1.09).

Today: We'll be using both x32dbg and CheatEngine 6.7 (http://www.cheatengine.org/downloads.php).

What is "CheatEngine?" You may be wondering. That's a fairly good question to ask. I'll answer it.
"CheatEngine" is a memory searching ('memory scanner'), debugger, reverse engineering tool all rolled into one!

Why would we want to use both x32dbg and CheatEngine when CheatEngine can do it all on it's own?
The only answer I can provide: Because although x32dbg does provide the ability of searching for (constant or static) string references:
It cannot actively scan the process memory for a particular string! Where-as CheatEngine can.

Would you only eat steak with a fork or with a fork and knife? There are times when multiple tools will simplify your work.

Our objective for the day: Finding out where Diablo stores the monster kill count tallies.
Our first step is as usual: Basic research! We know from playing Diablo, that all kills are universal.
Your kills on any given number of single player characters will carry over to your multiplayer characters (no matter what server/connection type).

To simplify what I'm saying: If you kill 50 Fallen on your Sorcerer, then go kill 50 Zombies on your Rogue, then go kill 50 Scavenger's on Single Player and then log on to Battle.net and search around: You'll see that those monster kills are all still there (the counter is never reset).

This leads me to believe initially that the data is stored locally (in the DS or .data segment or "heap" (these are all aliases and are synonymous)).
This is why I've trumped up the idea of using CheatEngine! It sounds like a perfect match for the task at hand.

To reiterate our objective:
OBJECTIVE: Find out where Diablo stores the monster's total kill counters!

TOOLS USED: CheatEngine 6.7 and x32dbg (to create a hack "patch" to reset counters; Left as a user exercise)

First: Let's load up Diablo and have some fun! Kill those monsters! Play until you feel you've gotten ready to reverse engineer.
Second: Open CheatEngine. Click on the computer with a magnifying glass ("Open Process list"), click on "DIABLO," then click on "Attach debugger to process." Then click, "Yes."

Let's choose Fallen as our target to identify. We'll assume we have 3 kills on the Fallen. Under "value" in CheatEngine: Enter "3."
Click on "First Scan." This will pull open a list of all values in Diablo's process memory that contain "3." There might be hundreds of addresses listed!

We need to narrow down that list, so we can find what we're searching for. You will need to change the value (which is the deaths of the particular monster or game unit). Kill another Fallen. Now it should be at "4."

Open CheatEngine again, enter "4" in the "value" and press on "Next scan." If you're lucky, like I was: You'll be going...
WOW! Only ONE address left! It may or may not be what you're looking for, but you can quickly test it out!

Double click on the address. It'll create a quick-copy on the bottom pane window in CheatEngine.
Double left-click on the "VALUE" (which should be listed as "4"). This will open a dialog to modify the value.

Enter any value you can think of. I'm going to go with... 35!
If we're right: This should make our Fallen's kill counter set to 35.

In my game: I immediately noticed that "Fallen One" is not just one unit type. There are actually TWO types!
Those that carry Spears and those that carry Swords. Diablo identifies them as two different types with two different counters.

The address I have listed is:
Code:
Diablo.exe + 0x24CD18 (Monster kill counter for: Fallen)
---> 0x0064CD18 (the actual address you can reference in: x32dbg)
Diablo.exe = 0x400000 (base of the module)

Now we can close CheatEngine (do not worry about it saying it'll have to deattach the debugger).
Open x32dbg up, attach the debugger to DIABLO.exe and click on the heap (bottom left pane).
Use the hotkey: CTRL + G and enter your address (I will enter: 0x0064CD18)!

You can set a hardware break-point on this memory address. Right click on the value (0x24 is what I see) -> Breakpoint -> Hardware, Write -> Byte
Then resume Diablo and slay another Fallen monster (or whatever one you have selected as a target).

My x32dbg debugger popped on this memory address in the memory map (.text):
Code:
00433C61 | E8 B2 38 FE FF           | call    <diablo_copy.sub_417518>                  |

This is the instruction following where our break-point actually popped at. You can look at the immediate preceding instruction:
Code:
00433C5F | FF 00                    | inc     dword ptr ds:[eax]                        |

We can read this instruction out loud:
Increment a double-word pointer to the data-segment at the address of list at EAX

This means we should turn our attention towards the EAX register (in the far upper right pane window).
Code:
EAX = 0x0064CD18

This is the memory address in the Diablo.exe process under the .data segment. This particular value is our Fallen monster's kill count!
We can right click on the EAX value and click on: "Follow in dump (data segment)."
I interchangeably use DS, .data, dump, data segment and heap to illustrate that they are all just aliases.

Now you can visually see your kill counter in x32dbg! Smile

To review the actual set of instructions that are (partially) involved in loading the kill counter:
Code:
00433C43 | 8B 86 0C D4 64 00        | mov     eax,dword ptr ds:[esi+0x64D40C]           | [esi+64D40C]:sub_657D21+FE7
00433C49 | 8B 8E E0 D3 64 00        | mov     ecx,dword ptr ds:[esi+0x64D3E0]           |
00433C4F | 0F B6 00                 | movzx   eax,byte ptr ds:[eax]                     |
00433C52 | 89 AE C4 D3 64 00        | mov     dword ptr ds:[esi+0x64D3C4],ebp           |
00433C58 | 8D 04 85 E8 CC 64 00     | lea     eax,dword ptr ds:[eax*4+0x64CCE8]         |
00433C5F | FF 00                    | inc     dword ptr ds:[eax]                        |
00433C61 | E8 B2 38 FE FF           | call    <diablo_copy.sub_417518>                  |

I've decided to remove my hardware break point on the .data kill count directly and attach a breakpoint to the .text segment (00433C43).
In case you're a little confused: Try clicking on the large window with all the instructions, then pressing CTRL + G and entering "00433C43."

You should land here:
Code:
00433C43 | 8B 86 0C D4 64 00        | mov     eax,dword ptr ds:[esi+0x64D40C]           | [esi+64D40C]:sub_657D21+FE7

Then press F2 to set a breakpoint. Then open Diablo and kill another monster (any monster in this instance).

I will leave it as a user exercise to map out exactly which memory addresses are attached to which monsters.

There are a few ways you could tackle this problem, but I presume as a person new to reverse engineering: You will create a notepad document and draft up something like this...

(NOTE: These are real addresses; but do not represent the actual monster kill counters)
Code:
0064CD18 = Fallen (Spear)
0064CD28 = Fallen (Sword)
0064CD48 = Skeleton Captain
0064CCE8 = Skeleton

Perhaps a better way would be to find out how the game is deciding which offset to write to...
HINT: Monster index table is a possibility.

On a secondary note: We can also use this address to learn more about how the game renders text! Or how the game knows when to display monster's text based on the mouse's position (think: collision detection with the mouse).

Last freebie to encourage a happy reverse engineering experience:
Code:
00433C72 | 8B 86 08 D4 64 00        | mov     eax,dword ptr ds:[esi+0x64D408]           | [esi+64D408]:"Fallen One"

Go to this expression in the memory map module for Diablo.exe in the .text segment.

Tune in next time, for another fun reverse engineering tutorial objective! Smile
Reply
#3
Dear readers:

I apologize for the slight delay in the preferred once-a-day reverse engineering tutorial series of Diablo.

I am taking a little bit of time off to write a side-project (which directly pertains to this tutorial).

I will edit this post when it is finished (hopefully by this week's end)!

Thank you for your patience and understanding! Smile
Reply
#4
Hey if you want to mske money on this put it on youtube

Keep this up i enjoy it.

If you make a yt videos on this Ill sub and tell my friends about it.
Reply
#5
This thread is very interesting, keep up the good work!
Reply
#6
Thanks for the tutorial. Will try these soon.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)