Difference between revisions of "Signature Scanning"
m (→How do I scan for signatures at runtime?) |
m (→How do I find the signature of a function?) |
||
Line 270: | Line 270: | ||
This will find the signature of a function at runtime, however it is useless unless you already have or know how to find signatures for which it can scan. | This will find the signature of a function at runtime, however it is useless unless you already have or know how to find signatures for which it can scan. | ||
− | == | + | == Finding the Signature of a Function == |
To find a signature for a function, we need a Disassembler to disassemble the server module. Here are links to free Disassemblers that seem to work well: | To find a signature for a function, we need a Disassembler to disassemble the server module. Here are links to free Disassemblers that seem to work well: |
Revision as of 04:32, 15 January 2006
Contents
Introduction
Hello, and welcome to today's episode of Function Signature Scanning (sigscanning for short). This guide is oriented for those of you who don’t know what the hell is going on and can’t cope anymore. I will be covering the finer details of the vague art for extracting function signatures from memory and then re-scanning for them at program runtime. But first, let’s go into some background information.
What is Sigscanning and how is it useful?
Sigscanning is a method of looking for the “signature” of a function at program runtime. A signature for a function is a set of bytes which are the instructions a machine uses to operate. These set of bytes are what make up functions (or subroutines as they are called in Assembly). When we have enough of these bytes, we can then obviously match them again to the function in memory. Why is this useful for plugins? Well each time Valve releases an update for their mods, they may change classes such as CBaseEntity used by many plugin coders to change a player’s entity. Due to the fact that we do not have the code for each of these changes, we may be using virtual functions that now rely at different addresses. This would cause a crash at runtime. To avoid this, our only solution is to use (a) offsets, (b) dumby virtual function insertion, or (c) sigscanning. Each have their pros and cons, but it turns out that although sigscanning is the most tedious out of the three, it is the most reliable.
So what is this Assembly machine code and runtime memory crap?
To understand all of this, one must first understand how the code of a C++ program is compiled into a binary file. When you hit “Build Solution” on your IDE or run gcc, your compiler does a multitude of operations. First it takes the code and runs it through a Preprocessor. The Preprocessor takes all the #directives (e.g. #define, #include, etc.) and substitutes them with normal C++ code. It also takes the code of all the inline functions and replaces them with the code that called them (substitution again). After the Preprocessor has done its job, the code is passed on to the Parser. In simplified terms, the Parser basically takes C++ code and turns it into Assembly (asm) code. After the Parser has parsed the code into Assembly code, the Assembler “assembles” the code into mostly machine (byte) code which is put into Object files that carry the .obj extension. Then the Linker gathers all the Object files where it looks for function calls and sets the “links” to them (static linking) as well as external calls to functions outside the scope of the assembled code (dynamic linking) and finally maps any runtime calls (runtime linking) and finally outputs the finished product (a binary file, i.e. an executable or library). So when a server loads your plugin, it uses runtime calls to call functions from within your plugin, and likewise your plugin calls functions from the server. Now where this all comes into play in sigscanning is where you will be taking the server module (.dll or .so file) from memory and scanning it in its machine-code form for those functions that you need. This is done by first disassembling the server module back into Assembly form and finding the instructions that make up that function you want. Assembly is the “lowest-level” language in programming terms. It is easy to flip-flop between Assembly and machine code because they are basically the same, except Assembly uses something called “Mnemonics” before every instruction. These are the keywords “push, pop, mov, call,” etc. that define what the instruction does. As C++ code is translated into Assembly, you can imagine the very structured syntax of C++ flattened out into primitive simple Assembly instructions. I won’t go into further detail on Assembly due to its complex nature. The best way to learn more about it (which is recommended) is to read one of the links provided at the bottom of this writing.
Before we go any further
Read BAILOPAN’s three DevLogs on sigscanning to gain an understanding of the proceeding sections (i.e., how I obtained the code used for the sigscanner).
- Oh, you’ve not hacked yet? – http://www.sourcemod.net/devlog/?p=55
- Finding Functions, Part 2 – http://www.sourcemod.net/devlog/?p=56
- Finding Functions, Part 3 – http://www.sourcemod.net/devlog/?p=57
Scanning for Signatures at Run-time
Since BAILOPAN explained the functions used here in his DevLogs, I won’t go into much detail about them. This is the complete code for a basic sigscanner. All functions and variables are described with comments.
sigscan.h
#ifndef SIGSCAN_H #define SIGSCAN_H class CSigScan { private: /* Private Variables */ /* Base Address of the server module in memory */ static unsigned char *base_addr; /* The length to the module's ending address */ static size_t base_len; /* The signature to scan for */ unsigned char *sig_str; /* A mask to ignore certain bytes in the signature such as addresses The mask should be as long as all the bytes in the signature string Use '?' to ignore a byte and 'x' to check it Example"xxx????xx" - The first 3 bytes are checked, then the next 4 are ignored, then the last 2 are checked */ char *sig_mask; /* The length of sig_str and sig_mask (not including a terminating null for sig_mask) */ size_t sig_len; /* Private Functions */ void* FindSignature(void); public: /* Public Variables */ /* sigscan_dllfunc should be set to the function "gameServerFactory" in Load() */ static void *(*sigscan_dllfunc)(const char *pName, int *pReturnCode); /* If the scan was successful or not */ char is_set; /* Starting address of the found function */ void *sig_addr; /* Public Functions */ CSigScan(void)sig_str(NULL), sig_mask(NULL), sig_len(0), sig_addr(NULL) {} ~CSigScan(void); static bool GetDllMemInfo(void); void Init(unsigned char *sig, char *mask, size_t len); }; void InitSigs(void); /* Sig Functions */ class CBaseAnimating; void CBaseAnimating_Ignite(CBaseAnimating *cba, float flFlameLifetime); #endif
sigscan.cpp
#include <stdio.h> #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> #else #include <dlfcn.h> #include <sys/types.h> #include <sys/stat.h> #endif #include "sigscan.h" /* There is no ANSI ustrncpy */ unsigned char* ustrncpy(unsigned char *dest, const unsigned char *src, int len) { while(len--) dest[len] = src[len]; return dest; } /* ////////////////////////////////////// CSigScan Class ////////////////////////////////////// */ unsigned char* CSigScan::base_addr; size_t CSigScan::base_len; void *(*CSigScan::sigscan_dllfunc)(const char *pName, int *pReturnCode); /* Initialize the Signature Object */ void CSigScan::Init(unsigned char *sig, char *mask, size_t len) { is_set = 0; sig_len = len; sig_str = new unsigned char[sig_len]; ustrncpy(sig_str, sig, sig_len); sig_mask = new char[sig_len+1]; strncpy(sig_mask, mask, sig_len); sig_mask[sig_len+1] = 0; if(!base_addr) return ; // GetDllMemInfo() Failed if((sig_addr = FindSignature()) == NULL) return ; // FindSignature() Failed is_set = 1; // SigScan Successful! } /* Destructor frees sig-string allocated memory */ CSigScan::~CSigScan(void) { delete[] sig_str; delete[] sig_mask; } /* Get base address of the server module (base_addr) and get its ending offset (base_len) */ bool CSigScan::GetDllMemInfo(void) { void *pAddr = (void*)sigscan_dllfunc; base_addr = 0; base_len = 0; #ifdef WIN32 MEMORY_BASIC_INFORMATION mem; if(!pAddr) return false; // GetDllMemInfo failed!pAddr if(!VirtualQuery(pAddr, &mem, sizeof(mem))) return false; base_addr = (unsigned char*)mem.AllocationBase; IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER*)mem.AllocationBase; IMAGE_NT_HEADERS *pe = (IMAGE_NT_HEADERS*)((unsigned long)dos+(unsigned long)dos->e_lfanew); if(pe->Signature != IMAGE_NT_SIGNATURE) { base_addr = 0; return false; // GetDllMemInfo failedpe points to a bad location } base_len = (size_t)pe->OptionalHeader.SizeOfImage; #else Dl_info info; struct stat buf; if(!dladdr(pAddr, &info)) return false; if(!info.dli_fbase || !info.dli_fname) return false; if(stat(info.dli_fname, &buf) != 0) return false; base_addr = (unsigned char*)info.dli_fbase; base_len = buf.st_size; #endif return true; } /* Scan for the signature in memory then return the starting position's address */ void* CSigScan::FindSignature(void) { unsigned char *pBasePtr = base_addr; unsigned char *pEndPtr = base_addr+base_len; size_t i; while(pBasePtr < pEndPtr) { for(i = 0;i < sig_len;i++) { if((sig_mask[i] != '?') && (sig_str[i] != pBasePtr[i])) break; } // If 'i' reached the end, we know we have a match! if(i == sig_len) return (void*)pBasePtr; pBasePtr++; } return NULL; } /* Signature Objects */ CSigScan CBaseAnimating_Ignite_Sig; /* Set the static base_addr and base_len variables then initialize all Signature Objects */ void InitSigs(void) { CSigScan::GetDllMemInfo(); /* void CBaseAnimating::Ignite(float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner); Last Address0x220BC7A0 Signature56 8B F1 8B? 86? BC? 00? 00? 00? C1? E8? 1B? A8? 01? 0F? 85? 9A? 00? 00? 00? 8B 16 FF 92? F0? 00? 00? 00? 80? 7C? 24? 0C? 00? 74? 08? 84 C0 0F? 84? 83? 00? 00? 00? 3C 01 75? 20? 80 7C 24 14 00 75? 19? 8B CE E8 83? 1A? 01? 00? 85? C0? 74? 0E? 8B 10 8B C8 FF 92? 08? 05? 00? 00? 84 C0 74? 5F? 57 6A 01 56 E8 48? EA? 07? 00? 8B F8 83 C4 08 85 FF 74? 3D? 8B 44 24 0C 50 8B CF E8 83? E5? 07? 00? 68 00 00 00 08 8B CE */ CBaseAnimating_Ignite_Sig.Init((unsigned char*)"\x56\x8B\xF1\x8B\x86\xBC\x00\x00\x00\xC1\xE8\x1B\xA8\x01\x0F\x85\x9A \x00\x00\x00\x8B\x16\xFF\x92\xF0\x00\x00\x00\x80\x7C\x24\x0C\x00\x74\x08\x84 \xC0\x0F\x84\x83\x00\x00\x00\x3C\x01\x75\x20\x80\x7C\x24\x14\x00\x75\x19\x8B \xCE\xE8\x83\x1A\x01\x00\x85\xC0\x74\x0E\x8B\x10\x8B\xC8\xFF\x92\x08\x05\x00 \x00\x84\xC0\x74\x5F\x57\x6A\x01\x56\xE8\x48\xEA\x07\x00\x8B\xF8\x83\xC4\x08 \x85\xFF\x74\x3D\x8B\x44\x24\x0C\x50\x8B\xCF\xE8\x83\xE5\x07\x00\x68\x00\x00 \x00\x08\x8B\xCE", "xxx?????????????????xxx????????????xx??????xx??xxxxx??xxx????????xxxx x?????xx??xxxxx????xxxxxxx??xxxxxxxx????xxxxxxx", 116); return ; } /* Example of a sig-scanned method function */ void CBaseAnimating_Ignite(CBaseAnimating *cba, float flFlameLifetime) { int bNPCOnly = false, bCalledByLevelDesigner = false; float flSize = 0.0f; if(!CBaseAnimating_Ignite_Sig.is_set) return ; // sigscan failed #ifdef WIN32 /* The first parameter takes a pointer to the object, the second is just a (required) dummy parameter */ typedef void (__fastcall *func)(CBaseAnimating*, void*, float, bool, float, bool); func thisfunc = (func)CBaseAnimating_Ignite_Sig.sig_addr; // Pointer to the function scanned from memory /* Pass "this" for the first parameter, and 0 for the second parameter */ thisfunc(cba, 0, flFlameLifetime, (bool)bNPCOnly, flSize, (bool)bCalledByLevelDesigner); /* NOTE: If you are trying to call a member function with variable arguments, you must call it just like the linux member functions below */ #else typedef void (*func)(CBaseAnimating*, float, bool, float, bool); func thisfunc = (func)CBaseAnimating_Ignite_Sig.sig_addr; // Pointer to the function scanned from memory // GCC pushes the CBaseAnimating object for us automaticly thisfunc(cba, flFlameLifetime, (bool)bNPCOnly, flSize, (bool)bCalledByLevelDesigner); #endif return ; }
This will find the signature of a function at runtime, however it is useless unless you already have or know how to find signatures for which it can scan.
Finding the Signature of a Function
To find a signature for a function, we need a Disassembler to disassemble the server module. Here are links to free Disassemblers that seem to work well:
- IDA Pro Freeware Version (Windows)
- PEBrowse Professional (Windows)
- Proview “PVDasm” Disassembler (Windows)
- GNU objdump (Linux)
- Data Display Debugger (Linux)
Linux
Showdax pointed out a very helpful way that makes finding signatures under Linux much easier than under Windows. The GNU tool “objdump” can be used against the server module to disassemble the binary into AT&T Assembly code with each subroutine (and variable?) given its name from the source code. Here, as an example from showdax’s post: ~/usr/srcds/dod/bin$ objdump -d server_i486.so > server_i486.so.objdump This dumps the disassembled code into the file “server_i486.so.objdump”. You can then simply open up that file and search for the function name that corresponds to its subroutine then extract its bytes to create a signature (discussed later in How do I create a searchable signature?).
Windows
Under Windows, finding a signature becomes much more complicated as PE-format binaries do not include symbol names for each subroutine. I suggest using the IDA Pro Freeware Disassembler since I will be using a method I have found to find signatures that I only know how to do in IDA Pro (although you may be able to find a way to do my method in the other disassemblers).
The rest of this section will assume you are running Windows, have Microsoft Visual Studio C++ and using the IDA Pro Freeware Disassembler. First what you need to do is compile the HL2:DM server module. So navigate your way to the "hl2src/dlls" directory and open up "hl_sdk.vcproj". Once open inside MSVC++, go to the Build menu and select Configuration Manager. Set the configuration to "Release" and press OK. Go to the Build menu again and select Build. This may take a bit as everything is compiled together. Once finished, you should have a new directory named "Release_hl2." Inside, there will be the compiled server.dll file and a server.pdb file. The PDB file is key to finding the subroutines in Assembly for the function you want to use.
Now open IDA Pro and go to the File menu and open the server.dll file you just compiled. When you open it, a dialog will pop up. Just press OK and everything will start to load. Eventually IDA Pro will say something akin to "There is a PDB file located in the same directory for this binary." Click Yes and it will use that PDB file to name the subroutines which will make our life much easier. As things load, IDA Pro will go through the DLL, disassembling it and naming everything to their corresponding function or variable name. This may take a while and may even appear that IDA has frozen, but it has not so don't close it or you will have to start over.
After some time, everything will have finished. Before we continue, however, we have to open up yet another IDA Pro again (don't close the other IDA Pro) and load the server.dll for the mod we will be sigscanning (in this case, Counter-Strike: Source). This file is located in "cstrike/bin" in your game installation directory. Again, do the same procedure, except this time we won't have a PDB file for it. This should take a shorter time than last time since there is no PDB file. After both IDA Pro processes have finished disassembling, we are ready to start finding signatures.
To start looking for a signature, switch over to the HL2:DM server module IDA Pro window. Let's try to find the signature for Teleporting an entity. This is the prototype for its method from the source code:
virtual void CBaseEntity::Teleport(const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity);
Now in IDA Pro look for CBaseEntity::Teleport in the Names list then double click it. You should be brought to the location of CBaseEntity::Teleport in the disassembly, however, its internal name is given as CBaseEntity_Teleport. As you can see, IDA Pro lists all the local and parameter variables near the top (lines starting with var_ and arg_), and then the actual code is listed below with its corresponding byte representation. (Tip: I had to actually set IDA Pro to list the opcode bytes from the Options menu, in "General" then under the tab "Disassembly"). At first, it seems you can just use the bytes here for the signature, but the Valve decided to change almost all the CBase* methods (that I've seen) for Counter-Strike: Source from the Half-Life 2: Deathmatch code. Due to this fact, we will have to use the HL2:DM disassembly as clues for finding the same function/method in the CS:S disassembly. To find our Teleport function, we will have to find a unique set of instructions that wouldn't be repeated in another subroutine. I had to do some trial and error with this function since Valve had changed or moved various pieces of code. If you try looking over the subroutine for something unique, you may see a group of repeated mov instructions:
.text:220C96CF 89 44 24 18 mov [esp+30h+var_18], eax .text:220C96D3 89 74 24 1C mov [esp+30h+var_14], esi .text:220C96D7 89 74 24 20 mov [esp+30h+var_10], esi .text:220C96DB 89 74 24 24 mov [esp+30h+var_C], esi .text:220C96DF 89 74 24 28 mov [esp+30h+var_8], esi .text:220C96E3 89 74 24 2C mov [esp+30h+var_4], esi
Try searching for part of this in the CS:S disassembly. If you get results, search for the next item to make sure there isn't another set of the same instructions. Then to verify this is the correct subroutine for the function Teleport(), compare the two subroutines to see if they have similar instructions in the same places. If they do then it is most likely correct and now you can use some of the instructions from your find in machine-code form to create a signature, which is discussed in the next section.
How do I create a searchable signature?
Once we have found the subroutine for the function we want, we can start extracting the bytes for use in a signature. Here is part of the Assembly for CBaseEntity::Teleport(): [quote]220D9940 CBaseEntity_Teleport proc near ; CODE XREFsub_220B6330+14 p 220D9940 ; sub_22143900+A p ... 220D9940 220D9940 83 EC 18 sub esp, 18h 220D9943 53 push ebx 220D9944 56 push esi 220D9945 8B D9 mov ebx, ecx 220D9947 8B 0D 78 B2 46 22 mov ecx, dword_2246B278 220D994D 33 F6 xor esi, esi 220D994F 33 C0 xor eax, eax 220D9951 3B CE cmp ecx, esi 220D9953 7E 21 jle short loc_220D9976 220D9955 8B 15 6C B2 46 22 mov edx, dword_2246B26C 220D995B EB 03 jmp short loc_220D9960 220D995B ; --------------------------------------------------------------------------- 220D995D 8D 49 00 align 10h 220D9960 220D9960 loc_220D9960 ; CODE XREFCBaseEntity_Teleport+1B j 220D9960 ; CBaseEntity_Teleport+2A j 220D9960 39 1C 82 cmp [edx+eax*4], ebx 220D9963 74 09 jz short loc_220D996E 220D9965 83 C0 01 add eax, 1 220D9968 3B C1 cmp eax, ecx 220D996A 7C F4 jl short loc_220D9960 220D996C EB 08 jmp short loc_220D9976 220D996E ; --------------------------------------------------------------------------- 220D996E 220D996E loc_220D996E ; CODE XREFCBaseEntity_Teleport+23 j 220D996E 3B C6 cmp eax, esi 220D9970 0F 8D 17 01 00 00 jge loc_220D9A8D 220D9976 220D9976 loc_220D9976 ; CODE XREFCBaseEntity_Teleport+13 j 220D9976 ; CBaseEntity_Teleport+2C j 220D9976 55 push ebp 220D9977 57 push edi 220D9978 8D 44 24 10 lea eax, [esp+28h+var_18] 220D997C 50 push eax 220D997D 51 push ecx 220D997E B9 6C B2 46 22 mov ecx, offset dword_2246B26C 220D9983 89 5C 24 18 mov [esp+30h+var_18], ebx 220D9987 E8 B4 88 F9 FF call sub_22072240 220D998C 8D 4C 24 14 lea ecx, [esp+28h+var_14] 220D9990 51 push ecx 220D9991 53 push ebx 220D9992 89 44 24 18 mov [esp+30h+var_18], eax 220D9996 89 74 24 1C mov [esp+30h+var_14], esi 220D999A 89 74 24 20 mov [esp+30h+var_10], esi …[/quote] I have written a small C program for which I use to create a signature string and signature mask from. It is not necessary to use, but it can help to reduce the amount of tedious work you have to do to create a signature.
sigcreator.c
#include <stdio.h>
- include <string.h>
- include <stdlib.h>
- include <ctype.h>
int main() {
int i, len, out_i = 0, mask_i = 0; char *sig, *out, *mask; sig = (char*)calloc(1024, sizeof(char)); printf("?"); fgets(sig, 1022, stdin); len = strlen(sig); out = (char*)calloc(len*2, sizeof(char)); mask = (char*)calloc(len*2, sizeof(char)); for(i = 0;i < len;i++) { if(isalnum(sig[i]) && isalnum(sig[i+1])) { out[out_i++] = '\\'; out[out_i++] = 'x'; out[out_i++] = sig[i]; out[out_i++] = sig[i+1]; if(sig[i+2] == '?') { mask[mask_i++] = '?'; i += 2; } else { mask[mask_i++] = 'x'; i++; } } } printf("\n\nSize%d\n\nSig:\n%s\n\nMask:\n%s", strlen(mask), out, mask); getchar(); free(sig); free(out); free(mask); return 0;
} It takes an input string of hexadecimal characters just as they appear as bold in the Assembly above. For each byte that should be ignored, a question mark after a hexadecimal byte will tell the program where a “?” should be in the signature mask. I will give an example of what the input and output should look like near the end of this section.
When creating a signature, you must also create a signature mask as well. A signature mask tells the sigscanner which bytes to ignore, such as addresses which may change at runtime. The signature mask is the same length as the signature. A question mark (“?”) in the mask signifies the position of the byte in the signature string to be ignored. Any other character corresponding to a byte in the signature string will be checked (usually we use “x” to represent these).
For the Teleport() function we found in the last section, we will build our signature from the bold hexadecimal characters in the Assembly above. I will be using the format of my “Sig Creator” for developing a signature string and mask for the sigscanner. The first three instructions in the subroutine are called the “function prologue.” These instructions will most likely never change as long as the variable declarations are not changed. So to start off, input “83 EC 18 53 56” into the sigcreator. Next is a mov instruction that probably won’t change“8B D9”. After that is another mov instruction, but this mov instruction contains an address in it, so add this to the input“8B? 0D? 78? B2? 46? 22?”. This will appear as question marks in the signature mask. The next set of xor instructions don’t reference an address so they’re safe to add normally to the input“33 F6 33 C0”. From here on, continue to input each segment of bytes into the sigcreator, where any segment referencing an address should have question marks follow each hexadecimal character pairs (like I did for the mov instruction). Eventually you’ll end at those last mov instructions. This isn’t the end of the subroutine, but it’s enough to develop a worthy signature. So press enter in the sigcreator, and it should output something like this: ?83 EC 18 53 56 8B D9 8B? 0D? 78? B2? 46? 22? 33 F6 33 C0 3B CE 7E? 21? 8B? 15? 6C? B2? 46? 22? EB? 03? 8D? 49? 00? 39 1C 82 74? 09? 83? C0? 01? 3B C1 7C? F4? EB? 08? 3B C6 0F? 8D? 17? 01? 00? 00? 55 57? 8D? 44? 24? 10? 50 51 B9? 6C? B2? 46? 22? 89 5C 24 18 E8? B4? 88? F9? FF? 8D? 4C? 24? 14? 51 53 89 44 24 18 89 74 24 1C 89 74 24 20 89 74 24 24 89 74 24 28 89 74 24 2C
Size106
Sig: \x83\xEC\x18\x53\x56\x8B\xD9\x8B\x0D\x78\xB2\x46\x22\x33\xF6\x33\xC0\x3B\xCE\x7E \x21\x8B\x15\x6C\xB2\x46\x22\xEB\x03\x8D\x49\x00\x39\x1C\x82\x74\x09\x83\xC0\x01 \x3B\xC1\x7C\xF4\xEB\x08\x3B\xC6\x0F\x8D\x17\x01\x00\x00\x55\x57\x8D\x44\x24\x10 \x50\x51\xB9\x6C\xB2\x46\x22\x89\x5C\x24\x18\xE8\xB4\x88\xF9\xFF\x8D\x4C\x24\x14 \x51\x53\x89\x44\x24\x18\x89\x74\x24\x1C\x89\x74\x24\x20\x89\x74\x24\x24\x89\x74 \x24\x28\x89\x74\x24\x2C
Mask: xxxxxxx??????xxxxxx?????????????xxx?????xx????xx??????x?????xx?????xxxx????????? xxxxxxxxxxxxxxxxxxxxxxxxxx Now you have a nice signature and mask you can use for your sigscanner and it also gives you the length of the signature as well. Cheers!
Contibuters
Thank you to those who contributed to this document directly and/or indirectly: Lance Vorgin – Forefather of sigscanners, posted his sigscanner on the sourcemod forums somewhere BAILOPAN – Created his DevLogs on how to create a sigscanner, also gave tips as well as other useful information Showdax – Suggested the objdump command for disassembly on Linux CyberMind – Examples of how to use a different method of calling sig functions Jacob Lojo – Posted a thread on how to develop a signature from disassembly (I was having trouble finding sigs with my scanner before his post). Also contributed a link to another disassembler
References
Oh, you’ve not hacked yet? – http://www.sourcemod.net/devlog/?p=55 Finding Functions, Part 2 – http://www.sourcemod.net/devlog/?p=56 Finding Functions, Part 3 – http://www.sourcemod.net/devlog/?p=57 Introduction to Reverse Engineering Software - http://www.acm.uiuc.edu/sigmil/RevEng/index.html The Art of Assembly Language Programming - http://maven.smith.edu/~thiebaut/ArtOfAssembly/artofasm.html