Quake2 wallhack tutorial - cppdude This fairly advanced tutorial is going to teach you how to make a wallhack for Quake2. To use it you will need TSearch and W32Dasm. We are going to be hacking the final version of Quake2 (3.20); and we are going to be using OpenGL apis. Dont worry if you dont know opengl, ill explain what you need to know. You will also need a basic knowledge of asm. Read sheeps tuts if you dont(www.gamehacking.com). Knowing C is also helpful but not required. Ok lets go. Look in you Quake2 dir. Youll find a ref_gl.dll dll file. This file is used by Quake2 to use opengl, and its this file we are gonna hack. So disasemble it with dasm. If you can, save it to a text file and open it with notepad, cos we are gonna be doing a lot of copy pastin. Our wallhack will have 2 modes. The first will be a wireframe mode, where we will force quake2 to draw all walls in wireframe. To do this we must first find the function that draws the world, and replace it with a call to our code. By world i mean not things like guns, monsters etc. Here is the pseudocode of our code section: DrawMode(Lines) Drawworld() DrawMode(Fill) return //to game code Pretty easy that. DrawMode will force all drawwing after that call to be done with lines, then we drawworld in lines, then we restore DrawMode to fill to make sure everything else is drawn properly; cos we dont want guns, players etc to be drawn in lines. Ok now you understand the theory of our first wallhack mode, lets put it into practice. We will first need to find out the Quake2 function to drawworld. In your asm view of Quake2 that you just made do a search for r_drawworld. r_drawworld is a Quake2 cvar that determines wether Quake2 will draw the world. Without this it would be very difficult to find the function that draws the world, and we would have to draw everything in wireframe mode (not nice). You will find a string ref to r_drawworld: * Possible StringData Ref from Data Obj ->"r_drawworld" | :1000914F 68BCE40210 push 1002E4BC The address of the string ref is being pushed as a parameter to a function. What does this function do? It takes a string pointer as one of its parameters("r_drawworld" in this case) and adds a cvar to the console, so that you can use that cvar in the console for example by typing: r_drawworld 0 The function then returns a pointer to a variable. Functions return values by saving to the eax register, so the r_drawworld varibale is saved like this: mov dword ptr [10056908], eax So now at location 10056908, we have a pointer to the r_drawworld variable. We now need to do a search for that address and find out where that pointer is dereferenced. So do a search for 10056908 and you should find this: * Referenced by a CALL at Address: |:10008EC6 | :1000C080 A108690510 mov eax, dword ptr [10056908] :1000C085 83EC48 sub esp, 00000048 :1000C088 D94014 fld dword ptr [eax+14] :1000C08B D81D74730210 fcomp dword ptr [10027374] :1000C091 DFE0 fstsw ax :1000C093 F6C440 test ah, 40 :1000C096 0F8543010000 jne 1000C1DF :1000C09C F6050467051002 test byte ptr [10056704], 02 :1000C0A3 0F8536010000 jne 1000C1DF ... Great. Youve now found the function that draws the world. You can see that the pointer is being dereferenced (mov eax, dword ptr [10056908]). W32Dasm tells you that the function is called at 10008EC6. Go there by doing a search for 10008EC6. Youll come to a code section like this: :10008EB2 E889F9FFFF call 10008840 :10008EB7 E874F8FFFF call 10008730 :10008EBC E80FFCFFFF call 10008AD0 :10008EC1 E82A330000 call 1000C1F0 :10008EC6 E8B5310000 call 1000C080 //call to drawworld :10008ECB E8A0F2FFFF call 10008170 :10008ED0 E83BAFFFFF call 10003E10 :10008ED5 E8D6F5FFFF call 100084B0 :10008EDA E811250000 call 1000B3F0 :10008EDF E82CFFFFFF call 10008E10 We have now found out where the drawworld function is called. Hooray! We can now replace that call with a call to our code section. At the end of files there is usually a large area of unused memory that you can use for a code section. So go to the end of the asm of the file. Youll find a large area of 0x00s after 5 0x90s. We can start our code section at the 0x90s; at 1002644B. This is where we start using TSearch; to make asm scripts. Read the TSearch help file if you dont know how to make TSearch asm scripts. Load up TSearch and open up the easywrite interpreter. In a new script type: offset 1002644B ;define the start of our code section We now need to make a call to the DrawMode function. The OpenGL api we will use for this is glPolygonMode. In C if you wanted to do make following polygons drawn with lines you would do: glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); So now we need to make a call to the function glPolygonMode, and we need to find its the address of the pointer to it. In W32Dasm do a search for glPolygonMode. Youll find a string ref address being pushed to a function like this: * Possible StringData Ref from Data Obj ->"glPolygonMode" | :10011108 6830000310 push 10030030 :1001110D 50 push eax :1001110E FFD6 call esi ;call GetProcAddress :10011110 8B0DD4600510 mov ecx, dword ptr [100560D4] * Possible StringData Ref from Data Obj ->"glPolygonOffset" | :10011116 6820000310 push 10030020 :1001111B 51 push ecx :1001111C A334510510 mov dword ptr [10055134], eax ;glPolygonMode address is saved :10011121 A3C45C0510 mov dword ptr [10055CC4], eax ... Here the address of the string ref "glPolygonMode" is being pushed as a function parameter and the called is then called(call esi). In C this function is GetProcAddress. It is used to get a pointer to a function imported from a module such as a dll file. GetProcAddress returns a pointer to the function(glPolygonMode in this case). So now we need to look for where the address of glPolygonMode is saved. Like all functions, the address is stored in the eax register so we need to look after call esi to where the eax register is saved to. Ive commented above where that is in case you cant figure it out. The address is saved in two places. Do a search for one of these addresses and youll find that the api is called like this: call dword ptr [10055CC4] You now know how to call glPolygonMode. Soon you can add it to your TSearch asm script. But first you need to push the parameters to the function. In asm we push parameters in reverse order becuase pushing puts the parameters onto a stack which is a first in last out data structure. So we first push GL_LINE. This is where the gl.h header file comes in. It will tell what GL_LINE is defined as. If you dont already have the gl.h header file, download it from www.opengl.org. Ill tell you what value to use anyway, its 0x1B01. So in your asm script in TSearch add a push for that value. Your script now looks like this; //define the start of our code section offset 1002644B //push GL_LINE push 1B01 Now we need to push GL_FRONT_AND_BACK which is defined as 0x0408. Then you call glPolygonMode. So now your asm script looks like this: //define the start of our code section offset 1002644B //push GL_LINE push 1B01 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] Now that all following polygons are drawn in line mode, we can call drawworld. We can then restore drawing to GL_FILL and then return to the game code. So your asm script now looks like this: //define the start of our code section offset 1002644B //push GL_LINE push 1B01 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] //call drawworld call 1000C080 //push GL_FILL push 1B02 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] //return to game code ret Great. We now have our code section defined. We now need to make a call to it, by replacing the existing call to the drawworld function. We already found that the existing call to drawworld was at 10008EC6. So we add a call to our code section in our asm script so that it now looks like this: //define the start of our code section offset 1002644B //push GL_LINE push 1B01 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] //call drawworld call 1000C080 //push GL_FILL push 1B02 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] //return to game code ret //make call to our code offset 10008EC6 //by replacing existing call to drawworld function call 1002644b Save this script and run it on quake2. Disaster!!! Quake2 now looks a mess. Every frame that Quake2 draws is being drawn on the previous frame. When the world was drawn in fill mode this wasnt a problem but now that we are drawing the world in lines we need to clear the screen everytime Quake2 draws a new frame. We could do this by using an opengl api, like this: glClear(GL_COLOR_BUFFER_BIT); but Quake2 already has a cvar to do this so i would rather hack that. The Quake2 cvar to do this is gl_clear. Do a search for gl_clear in W32Dasm and youll find, like r_drawworld, its the address of the string being pushed to the add cvar function, and a pointer returned. Search for where that pointer is being referenced like you did before and youll come to a code section like this: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:10008D43(C) | :10008DA6 8B1574620510 mov edx, dword ptr [10056274] :10008DAC D94214 fld dword ptr [edx+14] :10008DAF D81DD0720210 fcomp dword ptr [100272D0] :10008DB5 DFE0 fstsw ax :10008DB7 F6C440 test ah, 40 :10008DBA 7507 jne 10008DC3 :10008DBC 6800410000 push 00004100 :10008DC1 EB05 jmp 10008DC8 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:10008DBA(C) | :10008DC3 6800010000 push 00000100 You can see that 0x4100 is being pushed. If this is used as the paramater to glClear(the opengl api, not to be confused with gl_clear, the Quake2 cvar), then the screen buffer and the depth buffer(not important) will be cleared. But there is a jump above that push that will cause only 0x100 to be pushed to glClear. This will only clear the depth buffer, and that mess our wallhack was making will still be there. So all we need to do is(you guessed it) nop the jne. So now your TSearch asm script will now look like this: //define the start of our code section offset 1002644B //push GL_LINE push 1B01 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] //call drawworld call 1000C080 //push GL_FILL push 1B02 //push GL_FRONT_AND_BACK push 408 //call glPolygonMode call dword ptr [10055CC4] //return to game code ret //make call to our code offset 10008EC6 //by replacing existing call to drawworld function call 1002644b //nop jne to make sure GL_COLOR_BUFFER_BIT is also passed to glClear offset 10008DBA hex 9090 If you run this you will have a fully working wallhack for Quake2. If you want to toggle it off you use the bottom half of the easywrite interpreter. In this you can just replace the call to our code section with a call to the original drawworld function. The rest of the code section you can leave intact. Note: If you are implementing this wallhack method on other games you may have to nop any original calls to glPolygonMode. You are probably thinking: how can i remove that gay pink as the clear color? John Carmack seems to like this color because its exactly the same for Quake3. You simply call glClearColor like this: glClearColor(0.0f, 0.5f, 0.75f, 1.0f); for a nice calm blue. The first 3 params are RGB, and the 4th is an alpha value which you dont need to know unless you decide to get into the wonderful world of OpenGL programming. Use the TSearch converter to get the hex values from the floating point values i just specified. Remember params are pushed in reverse order. Very important that. Stick a call to glClearColor in your code section before the drawworld call. I know its silly and a waste of processor power to call that api everytime but it works and youve gained lots of power by only drawing lines anyway. I cant be bothered to explain exactly how i do the second wallhack mode, because the methods are pretty much the same. The second wallhack mode will be a depth test hack. By this i mean that we will disable depth testing while drawing entities so that they are always drawn onto the screen nomatter if they are behind a wall. Depth testing is OpenGL jargon for testing wether an object to be drawn on the screen is behind another object, and if it is then it is not drawn. Here is the C code for our code section for this method: glDisable(GL_DEPTH_TEST); drawentities(); glEnable(GL_DEPTH_TEST); return; GL_DEPTH_TEST is defined as 0x0B71. One other thing you should know, you must drawentities last if you intend them to be always on screen. We found that code section that originally calls drawworld: :10008EB2 E889F9FFFF call 10008840 :10008EB7 E874F8FFFF call 10008730 :10008EBC E80FFCFFFF call 10008AD0 :10008EC1 E82A330000 call 1000C1F0 :10008EC6 E8B5310000 call 1000C080//drawworld :10008ECB E8A0F2FFFF call 10008170//drawentities :10008ED0 E83BAFFFFF call 10003E10 :10008ED5 E8D6F5FFFF call 100084B0 :10008EDA E811250000 call 1000B3F0 :10008EDF E82CFFFFFF call 10008E10 Drawentities is also called there. When i made my depth test hack I made that drawenities call last in that list of calls. Works perfectly. That sums up my Quake2 wallhack tutorial. The methods here can be applied to any OpenGL game, but if you cant find the specific function that draws the world, then youll just have to make everything wireframe mode, which is bad cos you cant see your ammo health etc. You may be wondering why i made this tutorial using Quake2 instead of Quake3. Well i couldnt for the life of me find the block of code that draws the world/entities. I found out the functions that dereference the cvar pointer, but the drawing code isnt actually there. If you figure it out please write a tutorial and post it on www.gamehacking.com. Also, i like to get the bytes generated by TSearch and put them into a C++ program so that I have a nice small executable to use while playing Quake. I made a C++ class to do this. If you are going to do this remember that you should set the process's class priority to idle while patching, else the asm code you inject can easily get executed while it is still being injected(not nice). Have fun and go rage some servers =) cppdude