"A little bit about Relocations And Quite A bit about NE Files" -------------------------------------------------------------- OR "The Case of the missing pixel" - Gij here, this time i'm going to finish off the little pet project i started in my last article. Minesweeper. What Is there left to do? ------------------------- I'm glad you asked. in the last article, we found the key sequence that triggers the cheat. we also discovered, that the code had a bug in it, we're here to fix it. Required Tools -------------- The NE Filespec ( at least i needed it ), availible on wotsit's. A good Hex Editor ( i use Hiew ). an NE file dumper (availible on mammon's). a knowledge of c will help. SoftIce to test things out. Pinpointing the problem ------------------------- now what's wrong with this code? if you activate the cheat, and bpx the setpixel function we looked at earlier, you'll see that the cheat code does get executed, so the problem is not in the key sequence, but an actual bug in the code. i am not a GDI guru, but fortunately, i had a chance to explore a GDI a bit in lately ( hunting for a sparkle.... ). apparently m$ made some (subtle) changes in the way graphics API Functions are used, between win 3.1 and win95. at this point you should take the time to cuddle up with an API ref, and read up on Device contexts, window handles, etc'. the code uses GetDesktopWindow to get the handle of the desktop window, it then feeds it to GetDC, which should return a DC (device context) for that specific window, it then feeds the DC to setpixel to paint a pixel inside that window. since the desktop fills your entire screen, it actually sets the pixel at the top-left. GetDesktopWindow, gives returns normally, giving us the handle of the window in eax ( all api functions return the first return value in eax) ,u can see this by using SoftIce, type: "hwnd value", where value is the handle value returned in eax. you'll get back something suggesting you got the handle for the desktop. The problem lies in GetDC, it returns without an error, giving us a value. but this value is not appropriate for use with setpixel, because we're not dealing with just any window, but with the desktop (that's my theory anyway). the solution is simple, we need to use another api function, namely GetWIndowDC. This function is very similar to GetDC, only it allows us to write to the entire window, if you want to understand this better, read the API ref. GetDC <-> GetWindowDC --------------------- changing an api call to another one, how hard could it be? depends. if your going to do it by reading this, it should be very easy in this case. however, I had to spend a couple of hours, chasing down specs ( NE is not a popular format these days ), hexing the file, and dumping over and over again, until i got it right. now we need to tackle the subject of relocations, get a LARGE mug of herbal tea or vodka, whichever sets you in the mood for learning. Relocations ----------- Relocations ( sometimes known as fixups , although a bit different ), are not new to win 3.1, they have been around since dos. their purpose was mainly to overcome the problem of multi-segment code. if you load a win32 program into SoftIce, you'll see that the segment is always the same, every win32 program loads at 137:xxxxxxx ( ignore code rings, they don't exist for noobies ). however, 16 bit code is loaded at a different segment most times. this has to do with the amount of memory already used by other programs. a 16 bit segment is 0ffffh bytes long, if you want to jump to code outside this segment, you'll have a hard time. why? because a 16 offset ( like the one used in jumps and some calls ) can only go 0ffffh bytes away from it's location. actually, this is only partly true, since a jump/call can go backwards or forward, it can only go about half that but in either direction. so, you can't use a 16 bit jump/call to get to another segment, what do u do? use a direct jump/call. something like: jmp 1234:5768 or call 1678:90ab so by specifying a segment:offset pair, you can get your code to go wherever u want. but here's the problem, how do u know the segment:offset pair to jump to? u can't know the segment the code will be in when you program the damn thing, because it only gets decided when you run the program, and may change every single time. that's what relocations are for. every time the os loads a program, it runs through the relocations in the file, and fixes-up the values in the jumps and calls so your code goes to the right place. a similar thing happends when a 32bit program uses external functions in dll's, when the program loads, the os fixes up the address of the external functions in the dll into the code, and that way you can call code outside your own program. to summarize, relocations are fields of information inside the executable file, that tell the os the location inside the code that need to be adjusted at load-time. be that segment:offset pairs, or 32bit locations of dll functions. i know that may not be very clear, try reading it again, that might help. Why are you telling me this? --------------------------- we want to replace one api call with another, inside an NE (New Executable) file. this call is fixed-up by the os according to relocations in the files. so if we want to change the call, we need to change the relocation fields to make the os fix-up the call to our desired api function. let's begin. Ordinary Ordinals --------------------------- run Winmine.exe through nedump with the /R switch. now look at the output. you should see the relocations table looking like this: Segment relocation record Segment 01 relocations offset: 000025B2 CODE: 00000260-000025B2 Attr: Movable Preload Relocations Discardable NN type flag offset target 1: PTR Import by ordinal 01E6 USER.113 2: PTR Import by ordinal 01F0 USER.114 3: BASE Internal reference 0397 01:0000 4: PTR Import by ordinal 1FDB KERNEL.127 5: PTR Import by ordinal 206A KERNEL.128 the fields are: 1) Target Address Type (byte) 2) Import Type (byte) 3) target address/offset (word) 4) segment:offset or dll.ordinal (each part is a word) so what does part 4, USER.113, mean? it's made up of 2 parts: "USER": this refers to the dll in which the function resides. it is actually referenced as a word value in the actual file. if you look at the dump again, you can see the list of imported modules. Module reference table offset: 000001DF Module 1: KERNEL Module 2: USER Module 3: GDI Module 4: SOUND Module 5: SHELL you can see that USER.exe ( dll really ), is module number 2, so in the files it's actually referenced by a word containing the value "0002". the dumper just replaces it with the name to make it more readable. "113" this is just the ordinal number. wait, don't panic, i'll explain. What's an ordinal ----------------- you all know that api functions are usually called by name: MessageboxA, ExitProcess, BozoLivesHere (my fave). this functions are all contained inside a dll living somewhere in your \system dir. when a program wants to use a function from a dll it can just tell the os i want the function called "foo" inside the dll called "BAR", and it'll work. for some reason or other, there's another way to specify a function inside a dll. that is the ordinal, an ordinal is just a number, each function inside a dll get an ordinal number, and so when the program wants to use a function inside a dll, it can specify it by name, or use the ordinal number instead. you can use a PE dumper on any dll file to see an example of this. it should look something like this: the Ord. field is the ordinal. so the program could either say "get me USER32.xxx", or get me "USER32.xxx", and it will get to the same function eventually. Back to reality..... ----------------- so what is an ordinal doing in a relocation field? if u remember what i wrote about relocations, this should make perfect sense. the relocation field containing an ordinal simply means: "fix-up this address in my code to contain the address of dll function". the next step is to find out what ordinal/function our cheat code is using, and what ordinal/function to we want to use instead. if you look at the code activated by the cheat key sequence, you will see that the GETDC call is at cs:728 cs:0722 loc_0_722: ; CODE XREF: cs:071Dj cs:0722 call GETDESKTOPWINDOW cs:0727 push ax cs:0728 call GETDC cs:072D mov [bp-2], ax the opcode looks like: 9A (dword target address). so the target address is contained at cs:728+1 = cs:729. look at the relocation table for 0729, you should find it at the line belonging to USER.66 (if you can't find it, re-run nedump with the /R switch ). let's check this shall we? use nedump on user.exe , which should be in your \system directory. search for "GETDC", what entry number is listed right beside it? 66 of course. good. we want to replace the GETDC call with GETWINDOWDC, we we'll do this by adding another relocation entry, and for that we need the ordinal for GETWINDOWDC. look at the user.exe dump again. what's the ordinal number? 67? yes that's it. good. now we're ready for the hard part. Nitty Gritty ----------------- this is the part that took me a little time, since the first spec i got wasn't very detailed. things we need to do: understand the format of the relocation table, it's entries, and it's relation to the rest of the file and code. And then Modify The file By: 1) increasing the number of relocations by 1. 2) making a new relocation entry for with the right ordinal. 3) removing cs:729 from the relocation list for USER.66. look at the NE spec, and see if you can figure all these out by yourself. don't worry, i'll wait. got it? good. Step 1: incrementing the Relocation Entry Counter ------------------------------------------------- let's start with the easiest, number 4. you should now know that the first word right after the the end of the code segment contains the number of relocations in the table. the NE dump for winmine.exe shows us that the number of different ordinals referenced by the code is 87. this number is in decimal, the exe file has it in Hex. quick jump to s-ice: "? +87" gives us 0057 in hex. ( + before a number tells s-ice it's a decimal ). the dump also tells us that the code segment ends at 25B2h. let's increment the relocation counter by one, for the one will add later. load winmine.exe into HIEW, go to address 25B2 ( that's "F5 25b2" ). you should land on a bye containing 57h. to the right is one byte containing 00, the relocation counter is a word value, so "57 00" equal "0057", just like "12 34" equals "34 12". ( read a "little endian" section later in the article ). just change 57 to 58, and we're done with this step. Step 2: adding another relocation entry --------------------------------------- look at the last relocation shown in the NE file dump. the file shows: NN type flag offset target 87: PTR Import by ordinal 007A USER.111 this is the NE relocation entry format ( stolen from the WINE source tree ): /* * Relocation table entry */ struct relocation_entry_s { BYTE address_type; /* Relocation address type */ BYTE relocation_type; /* Relocation type */ WORD offset; /* Offset in segment to fixup */ WORD target1; /* Target specification */ WORD target2; /* Target specification */ }; And these are the defines: /* * Relocation address types */ #define NE_RADDR_LOWBYTE 0 #define NE_RADDR_SELECTOR 2 #define NE_RADDR_POINTER32 3 #define NE_RADDR_OFFSET16 5 #define NE_RADDR_POINTER48 11 #define NE_RADDR_OFFSET32 13 /* * Relocation types */ #define NE_RELTYPE_INTERNAL 0 #define NE_RELTYPE_ORDINAL 1 #define NE_RELTYPE_NAME 2 #define NE_RELTYPE_OSFIXUP 3 #define NE_RELFLAG_ADDITIVE 4 after a little cut&paste, putting it all together, the first few Hex bytes corresponding to the last relocation entry should be: "03 01 7A 00" which translates to: Ptr, By Ordinal, 007A Load up winmine.exe into your hex editor, and look for this, you land at 2864h. we see that the next segment begins at 2880, which gives us plenty of room to insert a relocation entry to the table ( 8 bytes each ). move ahead 8 bytes to 286ch, so you don't overwrite the last entry and enter the new hex bytes for the relocation entry we want: Address Type: Ptr "03" Relocation Type: Ord. "01" Fixup Target Address: 0279 "29 07" Module Entry Number: 2 "02 00" Ordinal: +67/43h "43 00" which gives us: "03 01 29 08 02 00 43 00" save the file, another step done. step 3: snipping the relocation chain --------------------------------------- sometimes the program uses an api function more then once, instead of having to add relocation entry for each call, the folks at m$ have found a little more efficient (if cumbersome) way to have multiple fix-ups from just one ordinal. there is only one relocation entry for each ordinal. M$ used a cute trick to save space. when u have a call that gets fixed up, the whole opcode and operands are stored in memory. assuming you have code like: call api_func that code takes up 5 bytes. the address you call takes 4, and since that address get's fixed-up by the os at load-time, you have 4 bytes that are there as part of the code, but get overwritten when the program is loaded. so instead of just letting those bytes go unused, they are used to construct a relocation chain. let's look at that dump one more time: NN type flag offset target 61: PTR Import by ordinal 1C45 USER.66 > 1CA7 1E75 1A34 1AFF 1BBB 1797 191A 1651 0729 FFFF see the first address? 1c45h. look at that address in the file. you will see the call opcode followed by 4 bytes: "09 xx xx xx xx" now, if we take the 2 first bytes and turn them into a little-endian word, we get: "1ca7". if you look back at the NE dump, you'll see that the next relocation target address is also "1ca7". neat huh?. this goes on for all the the relocation targets, each target contains the address of the next one. if the address contained in the 2 bytes is "0ffffh", that signles the end of the chain for this ordinal. * Update: turns out that this is an option of tlink called "relocation chaining" look at address "0729" inside winmine.exe, which is the entry we want to remove. it contains 0ffffh, and that is the last target in the chain of relocation of ordinal 66. so how do we got our code address out of this relocation chain? we just flag the end of the chain, one target earlier. look at the NE dump, the target address before "0729" is "1651", look at that address in the hex editor. it contains the call opcode followed by the address of the next target, just like i showed you. we want to make this the last item in the chain, so we replace "29 07 " with "ff 0f ", and that's it! our address is no longer relocated. that concludes the bug fix, the program should now work, try typing the cheat sequence and then running the mouse over the game area, you should see a white/black pixel flickering at the top left. Little/big endian ----------------- this is what's called byte-ordering. different machines use different byte order. all memory is broken up into bytes. a WORD is 2 bytes, a DWORD is 4 bytes. byte-order means the way a word or larger piece of memory is ordered when you look at the memory byte by byte. intel uses LITTLE ENDIAN. so if we have a word "1234" when u look at the memory in byte mode, you will see "34 12". on BIG ENDIAN machines when you have a word "1234" it's actually stored in memory as "12 34", which seems more logical, but i guess to intel it's not (i'm sure they have good reason). what about dwords? a dword is best thought of as 2 words. the same way little endian flips bytes to make words, it flips around words to make dwords. so just like "1234" becomes "34 12", a dword like "12345678" becomes 2 words like "5678 1234", when u look at the memory in word mode. and the words in turn flip again, when u look at it in byte mode. so "5678" becomes "78 56", and "1234" becomes "34 12", and when you put it all together you get "78 56 34 12". DWORD: "12345678" WORD : "5678 1234" BYTE " "78 56 34 12" hope you got it. Closing Arguments ----------------- although i originally meant this to be a newbie article, i realize now that it's doesn't qualify as one. if you don't understand major parts of the article i suggest you go about coding and cracking for a few months and then come back and try again. nothing teaches like time. If You wish to reach me, i can often be found on EFNET's #cracking4newbies. Greets to all the ppl on the channel. until next time.... Gij.