|
Yonderknight's Unit Alert Tutorial! Introduction: I am writing this tutorial not only for the unit alert competition at bwhacks.com, but also to help people learn. I think this will help the people who started game-hacking and know how to do things like mineral hacks and changing numbers around, but can't really go any farther than that. This will help (hopefully) you guys getting started on some more complicated hacks.I want this tutorial to cover all aspects of making this hack from debugging it to actually writing the hack. I hope to explain everything very clearly and explain what each line of code does. I also want to give many thanks to In this tutorial you will learn how to: • Write a DLL to be injected• Find functions in StarCraft • Excecute a piece of code when SC runs a certain routine More Specifically how to: • Make a unit alert hack• Find starcraft's texting function • Find how SC keeps track of units • Run a piece of code when a specific unit is made Tools Needed: • Simple memory searcher (TSearch, Artmoney, Winhack...)• Debugger (SoftICE, I will use Ollydbg because it's free and easy to use) • [Optional] if you use OllyDbg, you'll either have to use a program to restore system resolution (Powerstrip) or run Starcraft in a window. I use DxWnd to run SC in a window because it is much easier and faster than hacking to restore your resolution every time SC pops. • mASM32 (This tutorial will cover how to make a DLL in mASM32 only, although making it in other languages should be simiilar) • [Highly Recommended] radASM. This is a tool that will help you write mASM32 programs and stuff. Also comes with a bunch of utilities and support for other ASM languages. The beggining: THINKING about our hack Well, the first thing I like to do when starting a hack is actuallly thinking about it. It might be a little more complicated than you think at first, and starting the wrong way can take a lot of your time away.Starcraft keeps count of every kind of unit in different addresses. This means that it keeps count of how many SCV's player 1 has, player 2 has, player 3...how many Drones player 1 has....and so on. Lets say we build a unit, starcraft has to edit this value. If we can find the specific line of code that edit's these values, we can make a JMP to our own code (stored in our DLL) that then finds which unit was built and alerts us if it matches our specifications. We will have to find a lin eof code that is ran every time a building is made too, because in the contest specifications, we need to make a warning if a Nexus/Command Center/Hatchery is being created. We also need some way to find the number of units. Then we need to use Text_out to make an alert in the following format: <playername> has (<unitNumber>) of <UnitType> Finding WHERE Starcraft stores unit counts The first thing we need to do is find where Starcraft stores each unit count. This part is pretty simple.Start a challenger game with a computer. You should start with 4 drones/SCVs/probes. Open up your memory searcher (I used TSearch) and start a new search for the number 4 (four bytes long). The reason that it is 4 bytes long is because starcraft stores information like this in four bytes (00 00 00 04). Once your search is done, ALT+TAB back into starcraft and make another unit, search within results/filter/seive/whatever your memory searcher calls it for the number 5 (four bytes long again), and keep doing this until you narrow your results down to about 8 offsets. Finding HOW Starcraft edits unit counts Starcraft may use a single line of code to edit more than one unit count. This is a good thing. We are going to have to make a JMP from wherever starcraft edits the units, and if the line we make a JMP from is used when ANY unit is built, then we only have to make ONE jmp. This will jmp to our own code which will then find which unit was built and alert us if necessary.So basically we're looking for a line of code that is ran when A) Buildings are built B) Units are built C) Enemy units and buildings are built So lets start. In the previous section, you should have narrowed your search down to about 8 values or so. Select the first one and copy the address. Next, open up your debugger (I will show you how to use OllyDbg), and attatch it to the Starcraft process. This is done by going to File>Attatch, then choosing Brood War. After it is done loading, SC should be frozen, so press the PLAY button in olly (looks like the play on a VCR). In the bottom left corner you should see a "Hex Dump." You can see SC's memory from here. Right click anywhere in here, and click on Goto>Expression. In the box type one of the addresses you found in your memory searcher, then press OK. If you don't see the Hex Dump change, do it again. The upper left corner of the Hex Dump should now contain the number of SCVs/Drones/Probes you had. Right click on this, and click Breakpoint>Memory, on write. Now, any time starcraft WRITES to this address (i.e. increasing/decreasing the unit count) OllyDbg will freeze SC and pop up telling you what line of code edited this address. It is important that SC is running in a window or you have Powerstrip running because if SC freezes, you'll have no way of going back to OllyDbg. ALT+Tab back to SC and try creating an SCV/Probe/Drone. OllyDbg should now pop up. In the main code section, a line of code should be highlighted. It is not important what this does at this moment, so just ignore it. Click on the play button. Now we have to check if this line of code deals with more than one unit counter (and building counter). Remove your memory breakpoint by going back to the Hex-Dump and clicking Breakpoint>Remove memory breakpoint. Now, instead of setting a breakpoing on the SCV/Probe/Drone count, we will set a breakpoint on the line of code that dealt with them. Do this by selecting the line of code that Olly highlighted and press F2. This will make OllyDbg pop every time this LINE OF CODE is ran. Remember that the CODE could be used for more than one kind of unit. Alt+Tab back into SC and try making ANOTHER type of unit. If OllyDbg pops, that's a good thing. If not, start the same process with the next address from your memory searcher (remember to remove all of your breakpoints). If Olly did pop, click on play and ALT+Tab back into SC. Next, look into your enemies territory (maphack or blacksheepwall) and check if Olly pops if THEY build a unit. Next, check if olly pops if you build a building, or if they build a building. This is the address I found (1.11b offset): 0040E143 Keep repeating this for all of your addresses until you find one that matches ALL of the criteria (buildings, units, enemy buildings and units). Once you find one, we are ready to move on to the next section! ANALYZING the line of code you found: Now we are almost ready to make a JMP to our own DLL, but first we must find a way to determine what kind of building/unit was made, and WHO made the building/unit.Start by setting a bp on whatever address you found (0040E143). Next, make ANY unit/building. SC should break. Now, look into the register window. This is located on the top-right section of Olly and is titled "Registers". You should see this: EAX 12345678 ECX: 1234578 EDX: 12345678 EBX: 12345678 ESP: 12345678 EBP: 12345678 ESI: 12345678 EDI: 12345678 These are where all of our information will be held (Unit Type, Person who built it, etc...) Now all we have to do is look for it. Most of these should be red (this signifies that they changed from the last BP) so build another unit of the same type to check which have changed, and which have stayed the same. Try to find which values contain what in them. Find your player number (If you're in a challenger map, it is 0 if you are on top, and 1 if you are on the bottom) in one of the register's. Now watch when your enemy build something. Try to find what has changed from 0 to 1 or from 1 to 0. This is proably the player number. I found this to be stored in EAX. Now, we need to find something that changes to which unit was being created. It would be helpful if you either kill your enemy or stop it from creating units so Olly doesn't pop every second. Try creating different kinds of units and seeing which values change. I found that ESI changes to which unit was created (EBX does too, but we will use that for something else). Make of list of all the units we need and their according ESI numbers: 48 = Carrier
C = Battlecruiser
67 = Lurker
3D = Dark Templar
53 = Reaver
2C = Guardian
1E = Siege Tank (Siege Mode)
5 = Siege Tank (Tank mode)
E = Nuke
27 = Ultralisk
9A = Nexus
6A = Command Center
83 = Hatchery
84 = Lair
85 = Hive
82 = Infested Command Center
One more thing you should note is that Olly breaks if a unit is killed, too.
ECX is 1 if a unit is built, and something else if it is killed. Now we need to find the number of the unit being built. This isn't in the registers, so we have a couple of choices. Either we can make a list of all the unit counters, with tSearch, or you can do it the smart way (which I will teach you). Look at the line of code we set a breakpoint on. 0040E143 ADD DWORD PTR DS:[EBP*4+503194],ECXThis basically says add ECX to the offset EBP*4+503194. Now think about this for a second. Remember the very first memory offset we set a memory BP on that landed here. This line of code HAS to modify that memory address, or else olly wouldn't have popped. This means that EBP*4+503194 MUST equal the memory offset that our SCV/Probe/Drone where in. Even try it. Make another SCV/Drone/Probe, and write down EBP. Then multiply this number by 4 and add 503194 to it (make sure your calculator is in HEX mode). When you get the final address, goto it in the Hex dump in Olly. The number in this address is the number of units that were created! Lets get a summary on where our information is stored: EAX holds the player number ESI holds the type of unit created [EBP*4+503194] +1 holds the amount of the unit created. ECX Contains 1 when a unit is created Finding Starcraft's Text Function Now it's time to get away from all this number stuff and get to the text! This part is very important because this is the best way to display our information on screen. If you read Drakken's tutorial about writing to SC's screen, it might've sounded a bit complicated, but it is MUCH easier (and better) if you find the function that SC uses to display messeges.The first thing we need to do is find WHERE our text is located. I would use ArtMoney for this because TSearch has problems with stuff like this... Start a multiplayer game (LAN or Bnet) Once the game has started, press <enter> My Message!<enter>. At this point, "<Name>: My Message!" should appear on the screen. Now alt-tab over to TSearch. TSearch provides a screen to view a hex dump of memory(called Hex Editor). Run this. You will see that there are two more magnifying glasses on a toolbar for the Hex Editor. Click the one to start the search. This time type in "My Message!" exactly (case sensitive). Make sure that Value is Ascii, and Case is Match. Now click Find Next. You should see the hex dump change. If you scroll up in the Hex Editor window, you should see "Name>: My Message!" This is because Starcraft writes a null byte to the beggining of the message in order to stop it from appearing on the screen.(That part always seems to confuse people looking for their chats). Because there are 12 or so places where SC displays text, if you type in something else, Olly won't break yet. Wait until all of the chats dissappear until you start checking for breaks, because we don't want to catch starcraft writing the 00 into the first character, we want to find it writing the actual text. Keep sending chats until Olly breaks. If olly doesn't break, then you picked the wrong address. You should now be at this location (1.11b addresses): 15030988 OR ESI,ECX 1503098A AND EDI,81010101 15030990 JNZ SHORT Storm.150309B8 15030992 ADD EAX,4 15030995 MOV DWORD PTR DS:[EAX+EBP-4],ECX << olly landed here 15030999 JNS Storm.15030A2E 1503099F MOV ECX,DWORD PTR DS:[EDX] 150309A1 SUB ESI,ECX 150309A3 LEA EDI,DWORD PTR DS:[ECX+7EFEFEFF] 150309A9 XOR EDI,ESIIt is not really that important what all this stuff means, but it is important to understand this is NOT the text function. If we were to draw a map of SC's text function, it would look soemthing like this: Original Text Function{
Do some stuff
Another Text Function{
do some stuff
Yet Another Text Function{
find where to display text
another Function{
Mov DWORD PTR DS: [EAX+EBP-4],ECX << this is where we are
}
more stuff
}
do more stuff
}
do some more stuff
maybe some sound stuff too
}
Basically, we are deep inside the original text function, which is the
one we want. We are in some code located god know's where (actually in
Storm.dll). We need to go up a couple of "levels" to finally reach the
original one. This one should not have any parameters besides the Text
to be printed.So how can we go UP a couple of levels? I mean, to go DOWN one, we can always step inside a function, but how are we supposed to find which function called our function? It is actually very simple! Ollydbg has something called "Run till Return". This will run Starcraft's code until it finds a Return. If we actually return, we will be a level higher than we were. So now to start. Set your breakpoint again on the same address and make Olly break by typing in stuff. When it breaks, remove your memory breakpoint, but DO NOT press the Play button. This is because Olly will continue running SC's code. We only want it to run the code until we find a Return. So instead of pressing the Play button, press the button Execute Till Return. It is the 7th button right from the play button (it is blue). After you press this, the code Olly highlighted should now move to a code that says RETN. Now we want to actually Return, so press the button called "Step Into." This basically runs one line of code, and if the next line of code is a function or call, it will Step Into the call. You should now be somewhere like this: 00469AA6 PUSH EBX 00469AA7 ADD EDX,starcraf.0064CDD8 ; ASCII "Henry: asdf" 00469AAD PUSH EDX 00469AAE CALL <JMP.&Storm.#501> <This is where we used to be located in! 00469AB3 MOV AL,BYTE PTR DS:[ESI] 00469AB5 XOR EDI,EDI 00469AB7 TEST AL,ALWell, we know that this IS NOT the main text function because this is located in Storm.dll. We need the text function located in Starcraft.exe. So Execute till return again, step into, and you should be somewhere like this: 00469DDA ADD BL,2 00469DDD CALL DWORD PTR DS:[<&KERNEL32.GetTickCou>; kernel32.GetTickCount 00469DE3 ADD EAX,1B58 00469DE8 PUSH EAX 00469DE9 MOV DL,BL 00469DEB LEA ECX,DWORD PTR SS:[ESP+14] 00469DEF CALL starcraf.004699B0 << This is where we used to be 00469DF4 POP ESI 00469DF5 POP EDI 00469DF6 POP EBX 00469DF7 ADD ESP,100Now we know this IS NOT the main text function because you can see GetTickCount in there. GetTickCount is used for starcraft to set the time for how long the message to stay up. This means that we are still too far deep. Execute till return yet again, step into, and you should be here: 00475B22 TEST EAX,EAX 00475B24 JNZ SHORT starcraf.00475B3B 00475B26 MOV ECX,EDI 00475B28 CALL starcraf.00469C90 << This is where we used to be 00475B2D POP EDI 00475B2E MOV EAX,ESI 00475B30 POP ESIHmm. We have no way of telling if this is the main text function or not, so press PLAY. Now put a breakpoint on this CALL, and type in something in SC. Olly should pop. Now look into the registers: ECX is a pointer to the address which contains our text EDI is also a pointer to an address which contains our text EDX holds our player number. I found this out later, and was definitely confused, but it is our player number. Try sending another message. The only things that have changed are ECX and EDI, which are pointers to our text. This is good, because there are no other numbers like timers and things in the registers. This is probably our text function! Press PLAY and remove your breakpoint. Now Try NOPping (right click and click binary>fill with NOPs). This will basically erase the call. Also NOP out MOV ECX, EDI. Now try sending some text. Yay! Nothing happened. If this was NOT the text very first text function, some other things might have happened (Like the sound playing), but now we know for sure this is the right one. Write this down in your notes (Which you SHOULD be keeping ;) ) TextFunction : 00469C90 ECX and EDI hold pointers to Text And that's that! Finding Player Names We also need to find the names of each player. This will be fairly simple, because by now, you should be noticing that mostly everything in starcraft is stored in an array with a base address (Player 0's name in this case).Well, this is pretty straightforward, start a game with a single player account whose name is not in any Starcraft folders, or in any user names for windows (don't pick "asdf" if your user name for windows is "asdf"). While in the game, open up TSearch/whatever memory editor you like, and search for your name in the Hex Dump. You should see your name surrounded by 00's (dots) and then the computer's name (which is "Computer"). Write down the first one you found, this would be player 0's name. I found this offset: 00640C60 Also record how many bytes until the next player's name, I found it to be 72 bytes long. Finding the Current Player Number: We need to find the address that stores which player you currently are in the game. This is because we don't want the Unit Alert alerting us if WE are making the unit, we only need it for other player's.This too, is pretty simple. Start a challenger game with a computer. If you start off on the top, then your player ID is 0, if otherwise, your player ID is 1. Search for 0 or 1 in your memory searcher. Start a new game, search for your new player number. This could take a while, and there are a bunch of addresses that store the same value, so just pick any. I used this one: 004F1A6C Final Chapter! Making our DLL Well, in this section we're going to actually write the DLL in mASM32. This may seem a little complicated for those new to programming, so I'll try to explain as best I can.When I first wrote the DLL, I used Drakken's DLL template, just because I'm new to DLL's, and his is set up in an organized way. If you don't have Drakken's DLL template, that's fine, because I'll show you what you need anyways. First of all, make sure mASM32 is installed on your PC, and I would highly recommend using radASM (just because it color coats everything). Now to get to the juicy part. Open up the file called "sample.asm". I'm really sorry I couldn't color code this, but it was taking way too much time in HTML. ; Masm32 Dll Plugin Example for Damnation by Drakken .386 .Model Flat, StdCall OPTION CASEMAP :NONE include \masm32\include\windows.inc include \masm32\include\masm32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\debug.inc includelib \masm32\lib\masm32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\debug.lib include Sample.inc include Events.inc .Data JmpByte db 0EBh .Data? ThreadID dd ? hThread dd ? .code DllEntryPoint proc hInstDLL:DWORD, reason:DWORD, unused:DWORD mov eax,reason .if eax == DLL_PROCESS_ATTACH ; Called when our dll loaded call DLLStartup ; Memory patches and jmp patches invoke CreateThread, NULL, 0, addr DLLProc, 0, 0, addr ThreadID ; Create a thread for hotkeys and events mov hThread, eax ; Store thread handle .endif ret DllEntryPoint endp DLLStartup proc ; Here you can put the patches you want to use when the dll loads ; Also you should put all your jump patches here ; Examples: ; WriteMem works similar to WriteProcessMemory. ; WriteMem, offset, data pointer, number of bytes to write invoke WriteMem, 004528D7h, addr JmpByte, 1 ; Start a game without an opponent ; JmpPatch works like code injection only easier. :) ; It dynamically patches a jmp to your function. ; You must have it execute any code you've overwritten ; and have it jump back or return to the appropriate memory location. ; Use "jmp dword ptr [variablename]" if you need to have it jump back instead of ret ; It will write 5 bytes at the offset you specify for the jump. ; JmpPatch, from offset, to offset invoke JmpPatch, 004ADA68h, addr StartupMsg ; Game startup message ret DLLStartup endp End DllEntryPointNow you must be saying 'What is all this crap!," but its really not that complicated once you break it down. .386 .Model Flat, StdCall OPTION CASEMAP :NONE include \masm32\include\windows.inc include \masm32\include\masm32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\debug.inc includelib \masm32\lib\masm32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\debug.lib include Sample.inc include Events.incThis is just all the basic header stuff, all those "Include"s just load all the libraries so we can use the functions that are stored in them. As you can see, we also included Sample.inc and Events.inc, because this is where all of OUR functions are stored! .Data JmpByte db 0EBh .Data? ThreadID dd ? hThread dd ?This is all of our data, ThreadID and hTHread are in ."Data?" because we don't know what they are going to be (they change every time we load SC up) Finally, our .code section! .code DllEntryPoint proc hInstDLL:DWORD, reason:DWORD, unused:DWORD mov eax,reason .if eax == DLL_PROCESS_ATTACH ; Called when our dll loaded call DLLStartup ; Memory patches and jmp patches invoke CreateThread, NULL, 0, addr DLLProc, 0, 0, addr ThreadID ; Create a thread for hotkeys and events mov hThread, eax ; Store thread handle .endif ret DllEntryPoint endpWell, DLLEntryPoint is called by Damnation or any DLL loader when we inject it into starcraft. I'm not exactly sure about the first couple of lines, but apparantly eax = DLL_PROCESS_ATTACH when it is being attached to a program, like StarCraft. The first thing we do is call DLLStartup, which we will discuss what it does in the next paragraph or so Next, it creates a Thread (a process related thingy....) for our hotkey stuff to run in (I will also explain this later). Notice the addr DLLProc This is a function declared in Events.inc, so we will discuss what this does later. Right now, all you need to know is that the new process is running the DLLProc function. the CreateThread API returns a handle (an ID) to the thread we created in eax, so we store this in hThread by using mov hThread, eax Next is DLLStartup, which we previously called in DLLEntryPoint (using call DLLStartup) DLLStartup proc ; Here you can put the patches you want to use when the dll loads ; Also you should put all your jump patches here ; Examples: ; WriteMem works similar to WriteProcessMemory. ; WriteMem, offset, data pointer, number of bytes to write invoke WriteMem, 004528D7h, addr JmpByte, 1 ; Start a game without an opponent ; JmpPatch works like code injection only easier. :) ; It dynamically patches a jmp to your function. ; You must have it execute any code you've overwritten ; and have it jump back or return to the appropriate memory location. ; Use "jmp dword ptr [variablename]" if you need to have it jump back instead of ret ; It will write 5 bytes at the offset you specify for the jump. ; JmpPatch, from offset, to offset invoke JmpPatch, 004ADA68h, addr StartupMsg ; Game startup message ret DLLStartup endpAs Drakken so nicely describes, this is basically ran first, so we can patch our functions in here. We're not realy going to do that right now since we haven't written any functions to patch! We are going to replace his patches with our own later, so you can delete them. Well hopefully that wasn't so painful. There's not really much I can explain, but hopefully after this it won't be as hard. Open up the file called Events.Inc: ResourceHack PROTO :DWORD
.Data
.Data?
HndHook dd ?
.Code
HotKeys proc nCode:DWORD, wParam:DWORD, lParam:DWORD; Hotkey handling function
.if nCode != HC_ACTION ; Keystroke message
jmp HotKeys_End
.endif
mov ebx, lParam
or ebx, 00FFFFFFh
.if ebx == 0C0FFFFFFh; WM_KEYUP
jmp HotKeys_End
.endif
.if wParam == VK_F12 ; Hotkey Examples:
invoke ResourceHack, 00501370h
invoke BWTextDisplay, CTEXT ("Minerals H4x0r3d!")
.elseif wParam == VK_F11
invoke ResourceHack, 005013A0h
invoke BWTextDisplay, CTEXT ("Gas H4x0r3d!")
.endif
HotKeys_End:
invoke CallNextHookEx, HndHook, nCode, wParam, lParam ; Call next hook in chain
ret
HotKeys endp
DLLProc proc
invoke FindWindow, CTEXT ("SWarClass"), 0 ; Get Starcraft window handle
invoke GetWindowThreadProcessId, eax, 0 ; Get Starcraft's Thread ID
invoke SetWindowsHookEx, WH_KEYBOARD, addr HotKeys, NULL, eax ; Hook our hotkey function
mov HndHook, eax ; Save the handle to our hook
Events: ; Events - Here we keep our thread in a loop.
xor eax, eax ; Use this function for events checking. (ally alert..etc)
jmp Events ; If we terminate this thread our hook is unset. :(
DLLProc endp
ResourceHack proc uses eax ebx ecx Resource:DWORD
mov ebx, 004F5944h
xor eax, eax
mov al, byte ptr [ebx]
mov ebx, Resource
mov ecx, dword ptr [eax*4+ebx]
add ecx, 00002710h
mov dword ptr [eax*4+ebx], ecx
ret
ResourceHack endp
StartupMsg proc
pushad
invoke BWTextDisplay, CTEXT ("Sample.dll by Drakken") ; Print text
popad
ret
StartupMsg endp
Well, as you can see this has a lot of stuff that we are not going to use, so I'm going to go ahead and delete most of this:ResourceHack PROTO :DWORDThis just declares the function ResourceHack to be a function, we can delete this since we're not going to use it. Next are the data sections which I don't need to explain again, we don't need to delete anything out of here. Next comes the HotKeys stuff. We're just going to leave a lot of this exactly the same, but change the hotkey stuff to call our functions. HotKeys proc nCode:DWORD, wParam:DWORD, lParam:DWORD ; Hotkey handling function
.if nCode != HC_ACTION ; Keystroke message
jmp HotKeys_End
.endif
mov ebx, lParam
or ebx, 00FFFFFFh
.if ebx == 0C0FFFFFFh ; WM_KEYUP
jmp HotKeys_End
.endif
.if wParam == VK_F12 ; Hotkey Examples:
invoke ResourceHack, 00501370h
invoke BWTextDisplay, CTEXT ("Minerals H4x0r3d!")
.elseif wParam == VK_F11
invoke ResourceHack, 005013A0h
invoke BWTextDisplay, CTEXT ("Gas H4x0r3d!")
.endif
HotKeys_End:
invoke CallNextHookEx, HndHook, nCode, wParam, lParam ; Call next hook in chain
ret
HotKeys endp
Well, first of all this checks to see if a key is being pressed,
then it checks which key was pressed. We are going to update this part
so we only need to press 1 key to toggle off and on our hack. Replace
the ".if wParam" stuff with this (this is different from what Jakor
used because I like to use .if's:.if wParam == VK_F9 .if hackon == 0 ; if the hack is off invoke TxtOut, addr OnText ; display "Unit Alert Hack On" mov hackon, 1 ; put 1 into Hackon .else ; if the hack is already on invoke TxtOut, addr OffText ; display "Unit Alert Hack off" mov hackon, 0 ; And put a 0 into hackon .endif .endifWhen F9 is pressed, we display "Unit Alert Hack On" or "Unit Alert Hack Off" according to whether the hack was already on or not. Hackon stores a 1 when the hack is on, and a 0 when the hack is off. And put this in the .data section: .data hackon db 0 OnText db 03h,"Unit Alert Hack",07h," On",0 OffText db 03h,"Unit Alert Hack",06h," Off",0The reason why OnText and OffText have some numbers in front, is because those are the color numbers SC uses. 3 is yellow, 7 is green, and 6 is red. We need to finish all of our strings with a 0 at the end. So now your Hotkeys proc should look like this: HotKeys proc nCode:DWORD, wParam:DWORD, lParam:DWORD ; Hotkey handling function .if nCode != HC_ACTION ; Keystroke message jmp HotKeys_End .endif mov ebx, lParam or ebx, 00FFFFFFh .if ebx == 0C0FFFFFFh ; WM_KEYUP jmp HotKeys_End .endif .if wParam == VK_F9 .if hackon==0 mov hackon, 1 invoke TxtOut, addr OnText .else mov hackon, 0 invoke TxtOut, addr OffText .endif .endif HotKeys_End: invoke CallNextHookEx, HndHook, nCode, wParam, lParam ; Call next hook in chain ret HotKeys endpNotice that we are using a function called TxtOut. We will use this to send text to SC, but we haven't made the function yet. This tut is a little out of order, so we will cover the Txt function later. The next thing is DLLProc. Maybe I should've done this first because it was the first thing being executed when we created our process (Remember the "CreateProcess..."stuff in sample.asm?) DLLProc proc
invoke FindWindow, CTEXT ("SWarClass"), 0 ; Get Starcraft window handle
invoke GetWindowThreadProcessId, eax, 0 ; Get Starcraft's Thread ID
invoke SetWindowsHookEx, WH_KEYBOARD, addr HotKeys, NULL, eax ; Hook our hotkey function
mov HndHook, eax ; Save the handle to our hook
Events: ; Events - Here we keep our thread in a loop.
xor eax, eax ; Use this function for events checking. (ally alert..etc)
jmp Events ; If we terminate this thread our hook is unset. :(
DLLProc endp
Well, this just finds the necessary information about starcraft to be
able to set our windows hook. A windows hook basically monitors
information windows deals with, like keystrokes, so we will use this
for our hotkeying. We made a windows hook to the function we previously
covered called "HotKeys". Notice we get into a loope. This is because: We have nothing else to do with our process so we must close it, if we close it, our windows hook gets unset, if our windows hook gets unset, then no more hotkeys will work. The only problem with this is that the loop takes up some CPU ussage and could even cause SC to lag. We can fix this by not going into a loop at all. We can just add "Invoke Sleep,-1" to keep our program sleeping forever without closing! DLLProc proc
invoke FindWindow, CTEXT ("SWarClass"), 0 ; Get Starcraft window handle
invoke GetWindowThreadProcessId, eax, 0 ; Get Starcraft's Thread ID
invoke SetWindowsHookEx, WH_KEYBOARD, addr HotKeys, NULL, eax ; Hook our hotkey function
mov HndHook, eax ; Save the handle to our hook
invoke Sleep,-1 ; If we terminate this thread our hook is unset. :(
DLLProc endp
The rest of the stuff is basically resource hack stuff and a startup
message which we haven't gone into, so just delete all of it.Not we're going to actually write the coding for our Unit Alert! We need to make a function that will get called every time a unit is made. We'll do this by making a patch in Sample.asm to the function we are about to create. But first, we need to make the function. Now recall where we are going to make our patch from: 0040E143 ADD DWORD PTR DS:[EBP*4+503194],ECXEAX holds the player number ESI holds the type of unit created [EBP*4+503194] +1 holds the amount of the unit created. ECX Contains 1 when a unit is created The register's hold all of the information we need, so we will have to be messing with them a lot in our function. If we mess with them in our function, and then return back to starcraft without changing them back the way they were, SC might not run properly, or even crash! In order to fix this, we will save all the registers before doing anything, and then restore them afterwords. Luckily, this isn't so hard. We will use "pushad" to save all the registerers, then "popad" to restore them when we're done. So go ahead and make a function called "Unit Creation", and put pushad/popad in it. UnitCreation proc pushad popad UnitCreation endpNext, we are going to run all of our checks and things in this order 1. Check if our hack is turned on. 2. Check if a unit was created (not destroyed) 3. Check if the unit that was created is on our list 4. Check if we are the ones who made this unit We can do this by putting ".if aregister==something". We will compare values to the register's that we recorded (eax, ecx, ebx...) UnitCreation proc pushad .if hackon==1 ; check if our hack is on .if ecx==1 ; check if a unit was created, not destroyed .if esi==48h || esi== 0Ch || esi==67h || esi==3Dh || esi==53h || esi==2Ch || esi==5h || esi== 0Eh || esi==27h || esi==9Ah || esi==6Ah || esi==83h || esi==84h || esi==85h || esi==82h ; check if the unit that was created is on our list .if eax!= DWORD PTR DS:[004F1A6Ch] ; check if we created this unit. Remember that 4F1A6C was the address we ; found that contains the current player number? .endif .endif .endif .endif popad ret UnitCreation endp Notice that the really long .if comparing esi is on seperate lines. If you are copying and pasting this code, be sure to put all the lines back together. Now, we need to record all the values we need into data sections. We will need to make space for them, so at the very beginning of your document, add these to the .data? section: .Data? Pname dd ? UnitNum dd ? UnitType dd ? UnitText db 80 dup (?) We will use UnitText to store the very last string, which will be "<Pname> has (<UnitNum>) <UnitType>". db 80 dup (?) basically means, put 80 ?? that are 1 byte each. A question mark could be the equivilant of a zero, so it would store our space like this: 00 00 00 00 00 00 00 .... 80 times Each 00 is a letter is ASCII, and the reason why we only have 80 of them is because Starcraft cuts off chats longer than 80 bytes. Pname will store a pointer to our player name, UnitNum will store the unit number, and UnitType will store a pointer to the Unit Type. To find the player name, we will use the pointer we found earlier: 00640C60+72*Playernumber imul eax, 72d ;name's are 15bytes long, but 72 bytes apart add eax, 00640C60h ;Player0 name address mov Pname, eax Remember that eax was the person creating the unit. This basically multiplies the player number by 72, then adds it to our base address, then moves it into Pname. Next, we need to move the name of the unit created into UnitType. We will just make a big set of .if's and compare them to esi. .if esi==05h
mov UnitType, CTEXT("Seige Tank")
.elseif esi==0Ch
mov UnitType, CTEXT("Battle Cruiser")
.elseif esi==0Eh
mov UnitType, CTEXT("Nuke")
.elseif esi==27h
mov UnitType, CTEXT("Ultralisk")
.elseif esi==2Ch
mov UnitType, CTEXT("Guardian")
.elseif esi==3Dh
mov UnitType, CTEXT("Dark Templar")
.elseif esi==48h
mov UnitType, CTEXT("Carrier")
.elseif esi==53h
mov UnitType, CTEXT("Reaver")
.elseif esi==67h
mov UnitType, CTEXT("Lurker")
.elseif esi==6Ah
mov UnitType, CTEXT("Command Center")
.elseif esi==82h
mov UnitType, CTEXT("Infested Command Center")
.elseif esi==83h
mov UnitType, CTEXT("Hatchery")
.elseif esi==84h
mov UnitType, CTEXT("Lair")
.elseif esi==85h
mov UnitType, CTEXT("Hive")
.elseif esi==9Ah
mov UnitType, CTEXT("Nexus")
.endif
CTEXT passes the address of the memory location that stores that certain string. This is easier than putting all the strings in the data section. And finally, the last thing we need to do is get the number of units. We will do this by using our pointer [EBP*4+503194] +1. Although we don't need the +1 because I will explain later. imul ebp, 4 add ebp, 00503194hd! mov UnitNum, dword ptr [ebp]Now we need to format our string to contain "<pname> has (<UnitNum>) <unitType>" We will use the wsprintf API to do this for us. The wsprintf api is kind of confusing, so I think it would be easier to explain if I show it to you, and not tell it to you. First you need to add this into your .data section: UnitFrmtString db "%s has (%d) %s",0This tells wsprintf that the first pointer we will be passing to it will print fo a string (%s, s for string). Then we tell it that the next one is a number (%d, d for decimal). Then we tell it that the last one is a string again. And just to add in some color, I used this: UnitFrmtString db 03,"%s ","has (",07,"%d",03,") ","%s",0
I used 03 for yellow, and 07 for green. Now we have to actually call wsprintf. We do it like this:
invoke wsprintf,addr UnitText, addr UnitFrmtString,Pname,addr UnitNum,UnitTypeWsprintf only takes pointers, this means it only takes addresses to the actual values. That's why we use addr, this basically means, return the address of the certain value. The reason why we don't use addr for Pname and UnitType is because we used CTEXT. Remember I told you that CTEXT just returns addresses? This means that all Pname is storing is some numbers like 01234567. It is inside the address at these numbers where our text is located, so Pname and UnitType are already pointers, which is what wsprintf uses. And finally, we need to display our Alert! invoke TxtOut,addr UnitTextSo the final UnitCreation proc should look like this: UnitCreation proc
pushad
.if hackon==1 ; check if our hack is on
.if ecx==1 ; check if a unit was created, not destroyed
.if esi==48h || esi== 0Ch || esi==67h || esi==3Dh || esi==53h
|| esi==2Ch || esi==5h || esi== 0Eh || esi==27h || esi==9Ah || esi==6Ah ||
esi==83h || esi==84h || esi==85h ||
esi==82h ; check if the unit that was created is on our list
.if eax!= DWORD PTR DS:[004F1A6Ch]
; check if we created this unit. Remember that 4F1A6C was the address we
; found that contains the current player number?
imul eax, 72d ;name's are 15bytes long, but 72 bytes apart
add eax, 00640C60h ;Player0 name address
mov Pname, eax
.if esi==05h
mov UnitType, CTEXT("Seige Tank")
.elseif esi==0Ch
mov UnitType, CTEXT("Battle Cruiser")
.elseif esi==0Eh
mov UnitType, CTEXT("Nuke")
.elseif esi==27h
mov UnitType, CTEXT("Ultralisk")
.elseif esi==2Ch
mov UnitType, CTEXT("Guardian")
.elseif esi==3Dh
mov UnitType, CTEXT("Dark Templar")
.elseif esi==48h
mov UnitType, CTEXT("Carrier")
.elseif esi==53h
mov UnitType, CTEXT("Reaver")
.elseif esi==67h
mov UnitType, CTEXT("Lurker")
.elseif esi==6Ah
mov UnitType, CTEXT("Command Center")
.elseif esi==82h
mov UnitType, CTEXT("Infested Command Center")
.elseif esi==83h
mov UnitType, CTEXT("Hatchery")
.elseif esi==84h
mov UnitType, CTEXT("Lair")
.elseif esi==85h
mov UnitType, CTEXT("Hive")
.elseif esi==9Ah
mov UnitType, CTEXT("Nexus")
.endif
imul ebp, 4
add ebp, 00503194hd!
mov UnitNum, dword ptr [ebp]
invoke wsprintf,addr UnitText, addr UnitFrmtString,Pname,addr UnitNum,UnitType
invoke TxtOut,addr UnitText
.endif
.endif
.endif
.endif
popad
ret
UnitCreation endp
Yay, we finally are done with events.inc. Lets look at the completed code:
.data
hackon db 0
OnText db 03h,"Unit Alert Hack",07h," On",0
OffText db 03h,"Unit Alert Hack",06h," Off",0
UnitFrmtString db 03,"%s ","has (",07,"%d",03,") ","%s",0
.Data?
HndHook dd ?
Pname dd ?
UnitNum dd ?
UnitType dd ?
UnitText db 80 dup (?)
HotKeys proc nCode:DWORD, wParam:DWORD, lParam:DWORD ; Hotkey handling function
.if nCode != HC_ACTION ; Keystroke message
jmp HotKeys_End
.endif
mov ebx, lParam
or ebx, 00FFFFFFh
.if ebx == 0C0FFFFFFh ; WM_KEYUP
jmp HotKeys_End
.endif
.if wParam == VK_F9
.if hackon==0
mov hackon, 1
invoke TxtOut, addr OnText
.else
mov hackon, 0
invoke TxtOut, addr OffText
.endif
.endif
HotKeys_End:
invoke CallNextHookEx, HndHook, nCode, wParam, lParam ; Call next hook in chain
ret
HotKeys endp
DLLProc proc
invoke FindWindow, CTEXT ("SWarClass"), 0 ; Get Starcraft window handle
invoke GetWindowThreadProcessId, eax, 0 ; Get Starcraft's Thread ID
invoke SetWindowsHookEx, WH_KEYBOARD, addr HotKeys, NULL, eax ; Hook our hotkey function
mov HndHook, eax ; Save the handle to our hook
invoke Sleep,-1 ; If we terminate this thread our hook is unset. :(
DLLProc endp
UnitCreation proc
pushad
.if hackon==1 ; check if our hack is on
.if ecx==1 ; check if a unit was created, not destroyed
.if esi==48h || esi== 0Ch || esi==67h || esi==3Dh || esi==53h
|| esi==2Ch || esi==5h || esi== 0Eh || esi==27h || esi==9Ah || esi==6Ah ||
esi==83h || esi==84h || esi==85h ||
esi==82h ; check if the unit that was created is on our list
.if eax!= DWORD PTR DS:[004F1A6Ch]
; check if we created this unit. Remember that 4F1A6C was the address we
; found that contains the current player number?
imul eax, 72d ;name's are 15bytes long, but 72 bytes apart
add eax, 00640C60h ;Player0 name address
mov Pname, eax
.if esi==05h
mov UnitType, CTEXT("Seige Tank")
.elseif esi==0Ch
mov UnitType, CTEXT("Battle Cruiser")
.elseif esi==0Eh
mov UnitType, CTEXT("Nuke")
.elseif esi==27h
mov UnitType, CTEXT("Ultralisk")
.elseif esi==2Ch
mov UnitType, CTEXT("Guardian")
.elseif esi==3Dh
mov UnitType, CTEXT("Dark Templar")
.elseif esi==48h
mov UnitType, CTEXT("Carrier")
.elseif esi==53h
mov UnitType, CTEXT("Reaver")
.elseif esi==67h
mov UnitType, CTEXT("Lurker")
.elseif esi==6Ah
mov UnitType, CTEXT("Command Center")
.elseif esi==82h
mov UnitType, CTEXT("Infested Command Center")
.elseif esi==83h
mov UnitType, CTEXT("Hatchery")
.elseif esi==84h
mov UnitType, CTEXT("Lair")
.elseif esi==85h
mov UnitType, CTEXT("Hive")
.elseif esi==9Ah
mov UnitType, CTEXT("Nexus")
.endif
imul ebp, 4
add ebp, 00503194hd!
mov UnitNum, dword ptr [ebp]
invoke wsprintf,addr UnitText, addr UnitFrmtString,Pname,addr UnitNum,UnitType
invoke TxtOut,addr UnitText
.endif
.endif
.endif
.endif
popad
ret
UnitCreation endp
Actually, we are not really done, but there are only a couple of minor
things we need to do. But for now, lets get on to making our patch!
Switch back to Sample.asm DLLstartup should be empty and this is where we are going to make our patch. First of all, we need to find the code we are going to make our patch from: 0040E143 |. 010CAD 94315000 ADD DWORD PTR DS:[EBP*4+503194],ECXNow see the stuff in between the address and the code? These are the OP codes. These are the codes that go directly into the processor. The function we are going to use to set our patch (JmpPatch) writes 5 bytes to wherever we set it to. So split ADD DWORD PTR DS....into bytes, like this: 01 0C AD 94 31 50 00Now because JmpPatch uses 5 of them, the resulting will be this: 01 0C AD 94 31 50 00 ^ ^ ^ ^ ^ 1 2 3 4 5 <all of these will be usedNow what about the 50 00? Well, these are just left sitting there, and they are invalid OP codes that the processor can't understand. So we must replace these with NOPs (or 90's). First of all, we need to find the address where to write the NOP's to. Because our original address was 0040E143, we need to write the NOPs 5 bytes later, so we need to write them at 0040E148. Add the following to your .data section: doubleNOP db 9090hAnd add the following into the DLLStartup: DLLStartup proc invoke JmpPatch, 0040E143h, addr UnitCreation invoke WriteMem, 0040E148h, addr doubleNOP,2 ret DLLStartup endpAnd that's it for the Sample.asm! Sample.asm should look like this now: ; Masm32 Dll Plugin Example for Damnation by Drakken .386 .Model Flat, StdCall OPTION CASEMAP :NONE include \masm32\include\windows.inc include \masm32\include\masm32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\debug.inc includelib \masm32\lib\masm32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\debug.lib include Unit.inc include Events.inc .Data doubleNOP dw 9090h .Data? ThreadID dd ? hThread dd ? .code DllEntryPoint proc hInstDLL:DWORD, reason:DWORD, unused:DWORD mov eax,reason .if eax == DLL_PROCESS_ATTACH ; Called when our dll loaded call DLLStartup ; Memory patches and jmp patches invoke CreateThread, NULL, 0, addr DLLProc, 0, 0, addr ThreadID ; Create a thread for hotkeys and events mov hThread, eax ; Store thread handle .endif ret DllEntryPoint endp DLLStartup proc invoke JmpPatch, 0040E143h, addr UnitCreation invoke WriteMem, 0040E148h, addr doubleNOP,2 ret DLLStartup endp End DllEntryPointAnd because we overwrote the instruction "ADD DWORD PTR [blahblahblah..." we need to run it from within UnitCreation proc. Otherwise, SC won't ever run it and bad things will happen. Go back to UnitCreation proc in Events.inc and add this to the very beginning: UnitCreation proc ADD DWORD PTR DS:[EBP*4+503194h],ECX ;we gotta do the overwritten code too! pushadAlso, we need to make a Jmp back to SC, so put this at the end of your code: popad jmp dword ptr [UnitJmpBack] ret UnitCreation endpAlso, put this into your data section: UnitJmpBack dd 0040E14AhFinally, lets go to Sample.inc. .Data BWFXN_PrintText dd 0046B7D0h .Data? lgJmp db 5 dup(?) .Code BWTextDisplay proc uses ecx edx text:DWORD xor edx, edx mov ecx, text call dword ptr [BWFXN_PrintText] ret BWTextDisplay endp WriteMem proc MemOffset:DWORD, DataPtr:DWORD, dataLen:DWORD LOCAL OldProt:DWORD invoke VirtualProtect, MemOffset, dataLen, PAGE_EXECUTE_READWRITE, addr OldProt invoke RtlMoveMemory, MemOffset, DataPtr, dataLen invoke VirtualProtect, MemOffset, dataLen, OldProt, addr OldProt ret WriteMem endp JmpPatch proc uses ecx ebx from:DWORD, to:DWORD mov ebx, to mov ecx, from add ecx, 05h sub ebx, ecx lea ecx, lgJmp mov byte ptr [ecx], 0E9h mov dword ptr [ecx+1], ebx invoke WriteMem, from, addr lgJmp, 5 ret JmpPatch endpWell, there's not really much here. You don't really need to understand JmpPatch and WriteMem right now, so just ignore it. Notice that drakken already has a BWTextDisplay. Only problem is that his is different from ours, and we named ours differently, so replace that, with this: TxtOut proc uses ecx edi ebx text:DWORD mov ebx, 10h mov ecx, text mov edi, ecx call dword ptr [BWFXN_PrintText] ret TxtOut endpThis is all very very simple. Remember the values we recorded for this function? Edi and ecx store the text, and ebx stores the player number. Because we don't want "c00lDood: blahblahblah has (10) blah" to appear, I put 10 into player number because there is no player 10, and so there won't be anything coming up at the beginning. Jakor told me today that if you put -1 into there, then it will come up in the white space in the middle, but I haven't tried that yet. And that's pretty much it! You can compile it by making a .bat file in the same directory and putting this into it: if exist Sample.obj del Sample.obj if exist Sample.dll del Sample.dll \masm32\bin\ml /c /coff Sample.asm \masm32\bin\Link /SUBSYSTEM:WINDOWS /DLL Sample.obj PauseThen run the .bat file. If all goes well, you should see your DLL appear! Now just load it into damnation or inhale and congratulations, you're done! Shoutz! Special thanks to Jakor who was my partner during this and fixed all of my syntax errors and many other things =pI also want to thank TheTempest for always helping me and everybody else! Thanks to Drakken for making the DLL template and for many many other things, and everybody who ever helped and everyone bwhacks: FishBeans, Nickolay, Overflow, Titan, Dt, Indulgence....and other that I forgot! |