RCE Endeavors 😅

February 21, 2011

API Hooking Through Near Call Replacement

Filed under: General x86 — admin @ 3:00 AM

This post will focus on an interesting technique that I thought of this past week. I’ve noticed that a lot of API hooking libraries, or techniques in general, rely on replacing the prologue to the function with a jump into the hook (e.g. writing in E9 XXXXXXXX and jumping to a trampoline). This technique is quick, effective, and reliable in most scenarios. However, it is also easily detectable since all someone has to do is just check the first five bytes of a function to see whether there is a jump or not. The idea that I thought of this past weekend (although I’m sure I’m not the only one) was to replace the references to a function to that of the hook. For example, suppose I have a function at .text:00555440

.text:00555440                 push    esi
.text:00555441                 mov     si, [esp+4+arg_0]
.text:00555446                 test    si, si
.text:00555449                 jl      loc_5554FE
.text:0055544F                 mov     eax, [ecx+0A4h]
.text:00555455                 movsx   edx, si
...

The actual content is not important. Now again suppose that this function has five places that it’s being called from: 0045A9D5, 0045AA9E, 0045AAC3, 0045AAE1, 0045AB53.

...
.text:0045AA9C                 mov     ecx, esi
.text:0045AA9E                 call    sub_555440
.text:0045AAA3                 jmp     loc_45AB3A
...
.text:0045AADF                 mov     ecx, esi
.text:0045AAE1                 call    sub_555440
.text:0045AAE6                 jmp     short loc_45AB3A
...

and so on. My idea entails replacing all five call sub_555440 instructions to a call to the hooking function. Visually, it is something like this:

Where the green line denotes the new path of the calling functions. While easy to explain pictorially or in words, actually programming it is somewhat tedious. The main issue that is x86 has eight different ways on how a function can be called (reproduced below from here)

Opcode Mnemonic Description
E8 cw CALL rel16 Call near, relative, displacement relative to next instruction
E8 cd CALL rel32 Call near, relative, displacement relative to next instruction
FF /2 CALL r/m16 Call near, absolute indirect, address given in r/m16
FF /2 CALL r/m32 Call near, absolute indirect, address given in r/m32
9A cd CALL ptr16:16 Call far, absolute, address given in operand
9A cp CALL ptr16:32 Call far, absolute, address given in operand
FF /3 CALL m16:16 Call far, absolute indirect, address given in m16:16
FF /3 CALL m16:32 Call far, absolute indirect, address given in m16:32

The ones that I chose to focus on are the two most common ways that 32-bit programs call functions — E8 cw or FF /2, the 32-bit near relative and near absolute calls. Replacing them involves parsing through the entire file in memory, section by section, and replacing references as they are found. For the actual implementation I cheated a bit and parsed a user-supplied section for the references, but it can be extended to do through all without any problem, just more computation time. The actual implementation looks like this

void replace_references(DWORD_PTR image_base, const char* section_name, LPVOID original_addr, LPVOID hook_addr) {
    PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)image_base;
    PIMAGE_SECTION_HEADER section_header = get_section_addr_by_name(image_base, section_name);
    BYTE* section_start = (BYTE*)(image_base + section_header->VirtualAddress);
    BYTE* section_end = section_start + section_header->SizeOfRawData;
    BYTE call_near_relative = 0xE8;
    BYTE call_near_absolute[] = {0xFF, 0x15};
    DWORD new_protections = PAGE_EXECUTE_READWRITE;
    DWORD old_protections = 0;
    for(section_start; section_start < section_end; ++section_start) {
        VirtualProtect(section_start, PROTECT_SIZE, new_protections, &old_protections);
        if(*section_start == call_near_relative) {
            BYTE offset[] = {
                *(section_start + 0x4),
                *(section_start + 0x3),
                *(section_start + 0x2),
                *(section_start + 0x1)
            };
            DWORD_PTR relative_offset = (((offset[0] & 0xFF) << 24) | ((offset[1] & 0xFF) << 16) |
                ((offset[2] & 0xFF) << 8) | offset[3] & 0xFF) + NEAR_PATCH_SIZE;
            if((section_start + relative_offset) == original_addr) {
                DWORD_PTR hook_offset = (BYTE*)hook_addr - section_start - NEAR_PATCH_SIZE;
                patch_memory((section_start + 0x1), &hook_offset);
            }
        }
        else if(memcmp(section_start, call_near_absolute, sizeof(call_near_absolute)) == 0) {
            BYTE offset[] = {
                *(section_start + 0x5),
                *(section_start + 0x4),
                *(section_start + 0x3),
                *(section_start + 0x2)
            };
            PDWORD_PTR absolute_addr = (PDWORD_PTR)(((offset[0] & 0xFF) << 24) | ((offset[1] & 0xFF) << 16) |
                ((offset[2] & 0xFF) << 8) | offset[3] & 0xFF);
            __try {
                if(*absolute_addr == (DWORD_PTR)original_addr)
                    patch_memory(absolute_addr, &hook_addr);
            }
            __except(translate_exception(GetExceptionCode(), GetExceptionInformation())) {
                //Dereferenced bad pointer
            }
        }
        VirtualProtect(section_start, PROTECT_SIZE, old_protections, NULL);
    }
}

There is quite a bit to explain here. First off, the function takes four parameters — the base of the executable (obtained with GetModuleHandle(NULL)), a string to the section name to perform the replacements in, the desired address to be replaced, and lastly, the address of the hooking function that will replace it. The function then goes through the section byte by byte checking for either the E8 or FF 15 opcodes. Once these are found it is time to check and see if the call is to the correct place. For a relative near call this is pretty simple. The offset to the destination is the next four bytes – 0x5. All that needs to be done is to get those four bytes, change their endianness, and see whether the call leads to the address that is to be replaced. For an absolute near call, the process gets a bit tricky. The absolute address of the function to be called is stored in a 32-bit register. This means that the register needs to be read and dereferenced. The contents inside that register then need to be changed to that of the address of the hooking function. For example,

004010F1  |. FF15 C0204000  CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA

Here [004020C0] will contain 757CFEAE which is the address of USER32.MessageBoxA. To hook the call, [004020C0] should be replaced with the address of the hook. One issue the absolute address that this function finds may not in fact be an absolute address. Since the function simply reads a byte (or two) at a time for a match, the absolute address may not actually be an address. It may be two different instructions that coincidentally got interpreted as one by this function. Short of writing a disassembler, there is not much that can be done about this since instructions are variable length on the x86 architecture. It will simply suffice to catch the inevitable access violation error that will result from trying to dereference an invalid pointer. However, if a valid address if found (for a relative or absolute call), patching it is very simple

void patch_memory(LPVOID patch_addr, LPVOID replacement_addr) {
    DWORD old_protections = 0;
    VirtualProtect(patch_addr, sizeof(DWORD_PTR), PAGE_EXECUTE_READWRITE, &old_protections);
    memmove(patch_addr, replacement_addr, sizeof(DWORD_PTR));
    VirtualProtect(patch_addr, sizeof(DWORD_PTR), old_protections, NULL);
}

That is about it for how it works. The usage of it is pretty straightforward as well. It is easy to hook a function by an absolute address or by its name. Below is a code snippet that hooks MessageBoxA and also hooks a function at 00401000. The hook at 00401000 is more of a specialized exception to how hooking an unknown function by address should be done. Typically it would be done as an offset from an image or section instead of a full absolute address. The absolute version was shown simply to save lines of code and demonstrate the technique.

       DWORD_PTR image_base = (DWORD_PTR)GetModuleHandle(NULL);
        //Consider a new thread for actual use
        replace_references(image_base, ".text", &MessageBoxA, &MessageBoxA_hook);
        if(image_base == 0x00400000)
            replace_references(image_base, ".text", generate_number, &generate_number_hook);
        else
            MessageBox(NULL, L"Executable did not load at 0x00400000", L"Error", MB_ICONEXCLAMATION);

Here the base of the executable is retrieved and MessageBoxA and an “unknown” function in the executable are hooked. The executable is provided in the zip and should always load at 00400000. Again, this was done to simply demonstrate the technique and is not the way it should be done as programs can have randomized base addresses. The two hooking functions look pretty standard

typedef int (__cdecl *pgenerate_number)(int a, int b);
pgenerate_number generate_number = (pgenerate_number)(0x00401000); //Example compiled to load at a fixed base address
//Otherwise an offset from an image/section base would be needed
 
int __cdecl generate_number_hook(int a, int b) {
    return 666;
}
 
int WINAPI MessageBoxA_hook(HWND hwnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
    __asm pushad
    MessageBoxA(hwnd, "Hooked MessageBoxA called!", "MessageBoxA_hook", uType);
    __asm popad
    return MessageBoxA(hwnd, lpText, lpCaption, uType);
}

The hooks are designed for the sample application, but the technique extends to any application with minimal modifications (none in the case of MessageBoxA). Below is a screenshot of them in action:

The hook for MessageBoxA is called and the one for the “unknown” function is called too to always return “666”.
It should definitely be noted that this technique does have its downsides. The first one was already mentioned previously — the fact that you cannot really tell where you are exactly when you match what could be a call. This is a problem that cannot be fixed easily, it really would require writing a large part of a disassembler to fix. The second major problem is that it may be computationally expensive. There may be a lot of sections or the section sizes may be very large. The best fix would be to do a multithreaded replacement on the desired sections. The application will not freeze during the scan, but the functions may be called before the scanner arrives to replace the addresses. The third problem is that modules may be loaded after yours that call the functions, thus they will not be patched. This is more of a simple fix and can be remedied by hooking LoadLibrary/LoadLibraryEx and patching the addresses before returning to the application. The fourth, and what I think is the largest problem, is that this technique is extremely difficult to defeat. Doing something like

MOV EAX, [004020C0]
NOP
CALL DWORD PTR:[EAX]

completely defeats this method. This can be minimized by heuristically looking at the instructions being scanned, but the obfuscations can get much more complex to the point of it being impractical. Alternatively, any address that is purely calculated at runtime will not be spotted by this. While being an interesting technique, I can understand why I would not find any current existing implementations of it.
In the archive below is the full source code for the hook and sample programs as well as binaries for the DLL and sample EXE that can be hooked.
Download: Archive of source/binaries

A downloadable PDF of this post can be found here.

February 14, 2011

Running a (32-bit) Process in the Context of Another

Filed under: General x86 — admin @ 12:53 AM

I’ve recently finished reading Malware Analyst’s Cookbook, a very thorough and up-to-date book on malware analysis. Among one of the many useful things in the later chapters was a code injection technique in which a process runs in anothers’ context, which they called “process hollowing”. Googling the term yields no useful results, and the closest that I could find was a 2004 paper by Tan Chew Keong called, “Dynamic Forking of Win32 EXE” in which the technique is outlined in a similar manner as the book. I decided to give a shot at implementing this one afternoon, especially since code for the technique was partially provided in the book.

The overall technique is very simple. A malicious process executes, creates a benign looking process (svchost.exe, lsass.exe, …) in a suspended state. The malware then deallocates all of the memory of the benign process and replaces it with its own. Once this is done, the malware resumes the process and now the benign looking process is executing only malicious code. The main benefit that this technique provides over simply naming the malware (svchost.exe, lsass.exe, …) is that the processes’ PEB is untouched; meaning that it will preserve valid looking fields in important structures such as RTL_USER_PROCESS_PARAMETERS, which stores important information such as the path on file, command line parameters, working directories, etc. Therefore, to someone inspecting the process externally or statically (e.g. not attaching a debugger to a running instance), everything will appear normal. Below is a utility that I coded up in a few hours which runs one process in the context of another. The variation and introduction of an external application to do this is intentional, since just writing one process that runs in the context of another by itself is pretty useless (except for malware). The version shown here is about as stripped down as can get — it simply demonstrates the technique. Anyone using the code should definitely have error handling at each step. I also won’t take any undeserved credit for the code provided below except the fact that I physically typed it up and haven’t found code written in C++ showing this technique (excusing the above-mentioned link to the paper, which does it slightly differently). There are, of course, the obvious limitations to this technique as implemented (32-bit to 32-bit replacement only, the two processes must have the same subsystem, etc.)

#include <Windows.h>
#include <assert.h>
 
typedef NTSTATUS (__stdcall* pNtUnmapViewOfSection)(HANDLE ProcessHandle,
    PVOID BaseAddress);
 
typedef struct {
    PIMAGE_DOS_HEADER dos_header;
    PIMAGE_NT_HEADERS nt_headers;
    PIMAGE_SECTION_HEADER section_header;
    LPBYTE file_data;
} NEW_PROCESS_INFO, *PNEW_PROCESS_INFO;
 
void get_replacement_info(const char* full_file_path, PNEW_PROCESS_INFO new_process_info) {
    HANDLE hFile = CreateFileA(full_file_path, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    DWORD file_size = GetFileSize(hFile, NULL); //Note: High DWORD ignored, dangerous with >4GB files :-P
    new_process_info->file_data = (LPBYTE)malloc(file_size * sizeof(LPBYTE));
    DWORD bytes_read;
    ReadFile(hFile, new_process_info->file_data, file_size, &bytes_read, 0);
    assert(bytes_read == file_size);
    new_process_info->dos_header = 
        (PIMAGE_DOS_HEADER)(&new_process_info->file_data[0]);
    new_process_info->nt_headers = 
        (PIMAGE_NT_HEADERS)(&new_process_info->file_data[new_process_info->dos_header->e_lfanew]);
}
 
int main(int argc, char* argv[]) {
    NEW_PROCESS_INFO new_process_info;
    PROCESS_INFORMATION process_info;
    STARTUPINFOA startup_info;
    RtlZeroMemory(&startup_info, sizeof(STARTUPINFOA));
    pNtUnmapViewOfSection NtUnmapViewOfSection = NULL;
    CreateProcessA(NULL, argv[1], NULL, NULL, FALSE, CREATE_SUSPENDED,
        NULL, NULL, &startup_info, &process_info);
    get_replacement_info(argv[2], &new_process_info);
    NtUnmapViewOfSection = (pNtUnmapViewOfSection)(GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection"));
 
    //Remove target memory code
    NtUnmapViewOfSection(process_info.hProcess, (PVOID)new_process_info.nt_headers->OptionalHeader.ImageBase);
 
    //Allocate memory in target process starting at replacements image base
    VirtualAllocEx(process_info.hProcess, (PVOID)new_process_info.nt_headers->OptionalHeader.ImageBase,
        new_process_info.nt_headers->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
 
    //Copy in PE header of replacement process
    WriteProcessMemory(process_info.hProcess, (PVOID)new_process_info.nt_headers->OptionalHeader.ImageBase,
        &new_process_info.file_data[0], new_process_info.nt_headers->OptionalHeader.SizeOfHeaders, NULL);
 
    //Write in all sections of the replacement process
    for(int i = 0; i < new_process_info.nt_headers->FileHeader.NumberOfSections; i++) {
        //Get offset of section
        int section_offset = new_process_info.dos_header->e_lfanew + sizeof(IMAGE_NT_HEADERS) +
            (sizeof(IMAGE_SECTION_HEADER) * i);
        new_process_info.section_header = (PIMAGE_SECTION_HEADER)(&new_process_info.file_data[section_offset]);
        //Write in section
        WriteProcessMemory(process_info.hProcess, (PVOID)(new_process_info.nt_headers->OptionalHeader.ImageBase +
            new_process_info.section_header->VirtualAddress),
            &new_process_info.file_data[new_process_info.section_header->PointerToRawData],
            new_process_info.section_header->SizeOfRawData, NULL);
    }
 
    //Get CONTEXT of main thread of suspended process, fix up EAX to point to new entry point
    LPCONTEXT thread_context = (LPCONTEXT)_aligned_malloc(sizeof(CONTEXT), sizeof(DWORD));
    thread_context->ContextFlags = CONTEXT_FULL;
    GetThreadContext(process_info.hThread, thread_context);
    thread_context->Eax = new_process_info.nt_headers->OptionalHeader.ImageBase +
        new_process_info.nt_headers->OptionalHeader.AddressOfEntryPoint;
    SetThreadContext(process_info.hThread, thread_context);
 
    //Resume the main thread, now holding the replacement processes code
    ResumeThread(process_info.hThread);
 
    free(new_process_info.file_data);
    _aligned_free(thread_context);
    return 0;
}

The usage is as follows:

runasprocess [process to replace] [replacement process]

Here is a program that outputs a MessageBox running as an instance of VMWare:

The 64-bit analogue to this isn’t so nice — you can’t simply use PIMAGE_NT_HEADERS64 and a 64-bit _CONTEXT structure and modify RAX since process loading is done differently under 64-bit systems. I haven’t read much about 64-bit loading so it’s definitely something for the future.

A downloadable PDF of this post can be found here.

January 13, 2011

Analyzing An Application Challenge

Filed under: General x86,Reverse Engineering — admin @ 8:30 AM

I recently stumbled upon Hack This Site, a site that offers “missions” relating to web security, application reversing, programming, and general hacking topics. I quickly completed all of their application challenges, with the exception of the last two. Along the way, I found an interesting one — application challenge #7. I chose to post about this one since there was an interesting twist in the application which is probably why it was rated as a medium level challenge instead of easy.

The Analysis

This challenge comes with two files, the executable and a file called “encrypted.enc” that the executable uses. From opening up encrypted.enc in a hex editor, it is obvious to see that the contents of encrypted.enc are encrypted or obfuscated in some way. As a result, analyzing it as a standalone file won’t really provide any information. The executable will have to be reverse engineered to see just how it manipulates this file and how it derives the password.

Upon startup, the application simply asks for a password. Providing the wrong password simply causes it to display “Invalid Password” and terminate. Taking a look at how this occurs reveals a lot of information about how the password is stored and derived.

.text:0040118C                 cmp     [ebp+var_18], 0DCAh
.text:00401193                 jnz     short loc_4011A8
.text:00401195                 lea     ecx, [ebp+Dst]
.text:00401198                 push    ecx
.text:00401199                 push    offset Format   ; "Congratulations, The password is '%s'"
.text:0040119E                 call    _printf
.text:004011A3                 add     esp, 8
.text:004011A6                 jmp     short loc_4011B5
.text:004011A8 ; ---------------------------------------------------------------------------
.text:004011A8
.text:004011A8 loc_4011A8:                             ; CODE XREF: _main+193j
.text:004011A8                 push    offset aInvalidPasswor ; "Invalid Password"
.text:004011AD                 call    _puts

The application jumps to the “Invalid Password” location at loc_4011A8 if the value stored in [ebp+var_18] does not equal 0DCAh (3530d). Otherwise it continues on to the congratulations message. At first instinct, it might be tempting to simply NOP out the jump or to switch it to whatever [ebp+var_18] is during runtime so the congratulations message is hit. However, this causes some problems as shown below. The comparison was replaced with a NOP instruction and the image below shows what happened as a result.

The password becomes unintelligible and the website where you eventually submit the solution rejects it. It is interesting to look at why this occurs. Thus, begins the actual analysis of how this works.

The program flow is simple enough. A few loops and conditionals are the main parts of how this functions.

Identifying the Variables

IDA identifies the following variables used by the application

.text:00401000 File            = dword ptr -2Ch
.text:00401000 var_28          = dword ptr -28h
.text:00401000 var_24          = dword ptr -24h
.text:00401000 DstBuf          = dword ptr -20h
.text:00401000 var_1C          = dword ptr -1Ch
.text:00401000 var_18          = dword ptr -18h
.text:00401000 Dst             = byte ptr -14h
.text:00401000 var_4           = byte ptr -4

Some were identified in the initial autoanalysis phase by IDA so only five remain to be manually identified. The first ones to occur are [ebp+var_4] and [ebp+var1C]:

.text:0040103F loc_40103F:                             ; CODE XREF: _main+62j
.text:0040103F                 call    j___fgetchar
.text:00401044                 mov     [ebp+var_4], al
.text:00401047                 movsx   ecx, [ebp+var_4]
.text:0040104B                 mov     edx, [ebp+var_1C]
.text:0040104E                 add     edx, ecx
.text:00401050                 mov     [ebp+var_1C], edx
.text:00401053                 movsx   eax, [ebp+var_4]
.text:00401057                 cmp     eax, 0Ah
.text:0040105A                 jz      short loc_401064
.text:0040105C                 movsx   ecx, [ebp+var_4]
.text:00401060                 test    ecx, ecx
.text:00401062                 jnz     short loc_40103F

Looking at this block, it is easy to see that it is a loop, as evidenced by the conditional jnz instruction back to the top at .text:00401062. [ebp+var_4] stores the value returned from fgetchar, which is identified as

The next character from the input stream pointed to by stdin. If the stream is at end-of-file, the end-of-file indicator is set, and the function returns EOF. If a read error occurs, the error indicator is set, and the function returns EOF.

[ebp+var_1C] then stores the sum of all of the characters that have been read (including the 0Ah line feed character when the enter key is pressed). It is obvious at this point to see that this loop is responsible for reading in the user supplied password. It terminates when the 0Ah line feed character is read from the stream. From here on, [ebp+var_4] will be referenced as [ebp+input_char] and [ebp+var_1C] will be references as [ebp+input_sum]. The application then continues on to open the encrypted.enc file and check for a valid FILE*. Then begins the bulk of the program. The first block does something interesting with the local variable [ebp+var_24]:

.text:00401093 loc_401093:                             ; CODE XREF: _main+7Dj
.text:00401093                                         ; _main+17Bj
.text:00401093                 mov     edx, [ebp+var_24]
.text:00401096                 and     edx, 4
.text:00401099                 test    edx, edx
.text:0040109B                 jz      short loc_4010AB
.text:0040109D                 mov     eax, [ebp+var_24]
.text:004010A0                 and     eax, 1
.text:004010A3                 test    eax, eax
.text:004010A5                 jnz     loc_401180

[ebp+var_24] has an “and” operation performed with it and checked to see if the zero flag is set. If it is, the program jumps to loc_4010AB to continue execution; otherwise, [ebp+var_24] again has an “and” operation against 1 and jumps out to loc_401180 if the result is 0. IDA gives the helpful hint that this is a loop (as evidenced  by the arrows), but it is easy to see without help that this is true by looking a bit further down.

.text:00401175                 add     edx, 1
.text:00401178                 mov     [ebp+var_24], edx
.text:0040117B                 jmp     loc_401093

[ebp+var_24] is incremented by 1 and a jump is made back to the beginning of the loop. Therefore, it is easy to deduce that the block with the “and” operations is the conditional part of the loop. Studying the structure, it is two statements connected by an “or” operation. The body of the loop will execute as long as (([ebp+var_24] & 4) == 0) || (([ebp+var_24] & 1) == 0). Looking at for what values this works for reveals that the values 0-4 satisfy this conditional. Since [ebp+var_24] is initialized to 0 at the start of the program, this is a loop counter that runs from 0 to 4. From here on, [ebp+var_24] will be referred to as [ebp+dst_index] (shown why later). Assuming normal execution, the program then continues by calling fread at

.text:004010B7                 call    sub_401279

This is deduced because of the comments noted in IDA, or by simply following the call into until it hits _fread. That block was responsible for reading a character from the encrypted.enc file and storing the character in the [ebp+DstBuf] array. The block that follows at

.text:004010D8 loc_4010D8:                             ; CODE XREF: _main+C2j
.text:004010D8                 mov     eax, [ebp+DstBuf]
.text:004010DB                 and     eax, 0FFh
.text:004010E0                 xor     eax, [ebp+input_sum]
.text:004010E3                 mov     ecx, [ebp+var_18]
.text:004010E6                 add     ecx, eax
.text:004010E8                 mov     [ebp+var_18], ecx
.text:004010EB                 mov     edx, [ebp+DstBuf]
.text:004010EE                 and     edx, 0FFh
.text:004010F4                 xor     edx, [ebp+input_sum]
.text:004010F7                 mov     eax, [ebp+dst_index]
.text:004010FA                 mov     [ebp+eax+Dst], dl
.text:004010FE                 mov     [ebp+var_28], 0
.text:00401105                 jmp     short loc_401110

is probably the most important block of the entire application. Remembering from earlier, [ebp+var_18] is compared against 0DCAh to see whether the correct password was supplied or not. Looking at what happens in this block, it is shown that a character at [ebp+DstBuf] is moved into eax and has an xor performed against the input sum of the user supplied password. Then its sum is stored in [ebp+var_18] so [ebp+var_18] will be referred to as [ebp+xor_sum] from here on. At this point it is actually possible to deduce how the program works and what steps are required to get a working password. This is because [ebp+xor_sum] is not written to anywhere else for the remainder of the program so anything that happens has no effect on the outcome of the comparison with 0DCAh. Also, if analyzed closely, the number of characters of the password is known (discussed later). This would allow an easy brute-force approach since the way to get the compared sum is known and the number of characters in the password (a very low amount) is known. However, for practice, it is interesting to see how the actual password decoding algorithm works. The analysis won’t be as detailed as the required parts, but still provides an overview of how the program behaves. The rest of this block shows that the character as [ebp+eax+Dst] is set to the xor of the input sum and the character that was read. Then a jump is taken to loc_401110. Here [ebp+var_28] makes its appearance in usage (it was set to 0 prior to the jump into loc_401110). It is not obvious at first sight what [ebp+var_28] is used for, just that in this jump it is compared against the the input sum. Ignoring the jump instruction that leaves this block, the code continues on and performs an if-else comparison of [ebp+eax+Dst] “and” 1. Both of these blocks have the same exit point, their last instruction always jumps back in the code to

jmp     short loc_401107

so this is the beginning of another loop, more specifically, a for loop because of how the instructions are organized (the entry into the loop jumped past the increment instructions at loc_401107). Using this knowledge, it is possible to conclude that [ebp+var_28] is actually a counter for a for loop and will be referred to as [ebp+counter] from here on. Going back to the if-else discovered earlier, the code in the “if” body does an arithmetic shift of the value of the array index to the right by 1 and performs an or with 80h. In the else block, the same thing occurs, except there is no or with 80h. Once this for loop exists, the value in the current index has 3h added to it and the index is increased for the next iteration of the topmost loop. Knowing all this, it is possible to reproduce how this program works. The below programs behaves just like the application. It doesn’t exactly match the diassembly of the application (I took the liberty of making few stylistic changes for readability), but it captures the functionality:

Putting Everything Together

#include <stdio.h>
 
#define CHARS_TO_READ 5
 
int main(int argc, char* argv[])
{
	unsigned char dst[16] = {0};
	unsigned char enc_char;
	int input_char = 0;
	int input_sum = 0;
	int xor_sum = 0;
	int index = 0;
	printf("Please enter the password:\n");
	while(input_char != 0xA && input_char != EOF)
	{
		input_char = fgetchar();
		input_sum += input_char;
	}
	FILE* enc_file = fopen("encrypted.enc", "rb");
	if(enc_file)
	{
		while(index < CHARS_TO_READ)
		{
			if(fread(&enc_char, sizeof(unsigned char), 1, enc_file) != 1)
			{
				printf("An error occured.\n");
				return 0;
			}
			xor_sum += (input_sum ^ enc_char);
			dst[index] = (input_sum ^ enc_char);
			for(int i = 0; i < input_sum; ++i)
			{
				if(dst[index] & 1)
					dst[index] = ((dst[index] >> 1) | 0x80);
				else
					dst[index] = (dst[index] >> 1);
			}
			dst[index] += 3;
			index++;
		}
		if(xor_sum == 0xDCA)
			printf("Congratulations, the password is %s\n", dst);
		else
			printf("Invalid password.\n");
	}
	else
		printf("Failed to open encrypted.enc\n");
	return 0;
}

The xor_sum serves as an xor key for the first five characters in the encrypted.enc file. As the algorithm runs through, these five characters are decoded to form the five letter password that solves the solution. In the spirit of the application challenge, the password won’t be disclosed; however, this post is more than enough information to know how to solve the challenge (arithmetically or brute force).

A copy of this post is available as a downloadable PDF here

January 6, 2011

Reversing (Undocumented) Windows API Functions

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

Note: The functions that were analyzed are not found in Windows XP or lower.

This post will show my analysis of some undocumented APIs in the Windows kernel. The functions that were found relate to Locale IDs.

LCIDs are identifiers used to specify localizable information. They are also known as culture identifiers in the Microsoft® .NET Framework environment.

The name of a culture consists of its [ISO-639] language code, its [ISO-3166] country/region code, and an optional [ISO-15924] script tag for the written language. For example, the name of the culture in which the language is Bosnian (as written in Latin script and used in the Bosnia and Herzegovina region) is bs-Latn-BA.

More info: [MS-LCID] (PDF warning)

The functions that I will be analyzing are RtlLCIDToCultureName, RtlLcidToLocaleName, RtlCultureNameToLCID, RtlLocaleNameToLcid, and RtlConvertLCIDToString. The names alone give away a hint of what they probably do. The first two will be analyzed in depth and then sample code utilizing all five will be provided. There are a few additional functions with Rtlp* pertaining to LCIDs, but the generally Rtlp* functions are private functions and are meant only to be used within the native API, not to be exported and used by external programs.

RtlLCIDToCultureName (disassembly reproduced below)

.text:7DEB4C38                 public RtlLCIDToCultureName
.text:7DEB4C38 RtlLCIDToCultureName proc near          ; CODE XREF: sub_7DEB4C06+24p
.text:7DEB4C38                                         ; sub_7DEB908D-17p ...
.text:7DEB4C38
.text:7DEB4C38 var_8           = word ptr -8
.text:7DEB4C38 var_4           = dword ptr -4
.text:7DEB4C38 arg_0           = dword ptr  8
.text:7DEB4C38 arg_4           = dword ptr  0Ch
.text:7DEB4C38
.text:7DEB4C38 ; FUNCTION CHUNK AT .text:7DEBB21D SIZE 0000006D BYTES
.text:7DEB4C38
.text:7DEB4C38                 mov     edi, edi
.text:7DEB4C3A                 push    ebp
.text:7DEB4C3B                 mov     ebp, esp
.text:7DEB4C3D                 push    ecx
.text:7DEB4C3E                 push    ecx
.text:7DEB4C3F                 push    ebx
.text:7DEB4C40                 push    esi
.text:7DEB4C41                 push    edi
.text:7DEB4C42                 mov     edi, [ebp+arg_0]
.text:7DEB4C45                 xor     ebx, ebx
.text:7DEB4C47                 cmp     edi, ebx
.text:7DEB4C49                 jz      short loc_7DEB4C88
.text:7DEB4C4B                 mov     esi, [ebp+arg_4]
.text:7DEB4C4E                 cmp     esi, ebx
.text:7DEB4C50                 jz      short loc_7DEB4C88
.text:7DEB4C52                 cmp     edi, 1000h
.text:7DEB4C58                 jz      short loc_7DEB4C88
.text:7DEB4C5A                 mov     eax, dword_7DF7208C
.text:7DEB4C5F                 cmp     eax, ebx
.text:7DEB4C61                 jz      short loc_7DEB4C77
.text:7DEB4C63                 lea     ecx, [ebp+arg_0]
.text:7DEB4C66                 push    ecx
.text:7DEB4C67                 push    ebx
.text:7DEB4C68                 push    edi
.text:7DEB4C69                 push    eax
.text:7DEB4C6A                 call    sub_7DEB4C96
.text:7DEB4C6F                 test    eax, eax
.text:7DEB4C71                 jge     loc_7DEBB21D
.text:7DEB4C77
.text:7DEB4C77 loc_7DEB4C77:                           ; CODE XREF: RtlLCIDToCultureName+29j
.text:7DEB4C77                                         ; RtlLCIDToCultureName+65FFj
.text:7DEB4C77                 push    0
.text:7DEB4C79                 push    2
.text:7DEB4C7B                 push    esi
.text:7DEB4C7C                 push    edi
.text:7DEB4C7D                 call    RtlLcidToLocaleName
.text:7DEB4C82                 test    eax, eax
.text:7DEB4C84                 jl      short loc_7DEB4C88
.text:7DEB4C86
.text:7DEB4C86 loc_7DEB4C86:                           ; CODE XREF: RtlLCIDToCultureName+6646j
.text:7DEB4C86                 mov     bl, 1
.text:7DEB4C88
.text:7DEB4C88 loc_7DEB4C88:                           ; CODE XREF: RtlLCIDToCultureName+11j
.text:7DEB4C88                                         ; RtlLCIDToCultureName+18j ...
.text:7DEB4C88                 pop     edi
.text:7DEB4C89                 pop     esi
.text:7DEB4C8A                 mov     al, bl
.text:7DEB4C8C                 pop     ebx
.text:7DEB4C8D                 leave
.text:7DEB4C8E                 retn    8
.text:7DEB4C8E RtlLCIDToCultureName endp

Quite a function at first, but relatively easy to analyze. I started the analysis by looking at all of the cross references to this function (click to enlarge).

This might lead to some hints as to what the types of the two parameters this function takes are.  I found an interesting hint at

.text:7DEBBA51                 call    RtlLCIDToCultureName

By scrolling up and looking at the call in context, I found

.text:7DEBBA3C                 push    eax
.text:7DEBBA3D                 push    esi
.text:7DEBBA3E                 call    RtlInitUnicodeString
.text:7DEBBA43
.text:7DEBBA43 loc_7DEBBA43:                           ; CODE XREF: sub_7DEBB9E0+78j
.text:7DEBBA43                                         ; sub_7DEBB9E0+40E8Dj
.text:7DEBBA43                 mov     eax, edi
.text:7DEBBA45
.text:7DEBBA45 loc_7DEBBA45:                           ; CODE XREF: sub_7DEBB9E0+84j
.text:7DEBBA45                 pop     edi
.text:7DEBBA46                 pop     esi
.text:7DEBBA47                 pop     ebp
.text:7DEBBA48                 retn    0Ch
.text:7DEBBA4B ; ---------------------------------------------------------------------------
.text:7DEBBA4B
.text:7DEBBA4B loc_7DEBBA4B:                           ; CODE XREF: sub_7DEBB9E0+22j
.text:7DEBBA4B                 movsx   eax, word ptr [eax+4]
.text:7DEBBA4F
.text:7DEBBA4F loc_7DEBBA4F:                           ; CODE XREF: sub_7DEBB9E0+40E83j
.text:7DEBBA4F                 push    esi
.text:7DEBBA50                 push    eax
.text:7DEBBA51                 call    RtlLCIDToCultureName

Looking at how ESI is used in this function (the entirety is not reproduced here, it begins at .text:7DEBB9E0) shows that its value is set to the second parameter. There is a test for 0 and a branch to a function that moves an error code into EAX. Thus, since ESI was not reused anywhere in this function, it can safely be determined that it will have the same type as an argument to RtlLCIDToCultureName as it does to RtlInitUnicodeString.

VOID WINAPI RtlInitUnicodeString(
__inout PUNICODE_STRING DestinationString,
__in_opt PCWSTR SourceString
);

ESI is passed as the first argument into RtlInitUnicodeString and as the second argument of RtlLCIDToCultureName concluding that ESI is a PUNICODE_STRING structure. Also, looking at .text:7DEBBA4B shows that the first argument passed into RtlLCIDToCultureName appears to be a 16-bit integer that got sign extended into EAX. Now the two arguments are known and the RtlLCIDToCultureName function itself can be analyzed. It appears that loc_7DEB4C88 is where the function jumps to if an error occured where it subsequently will return 0. If dword_7DF7208C is 0 then the function branches a bit further down, otherwise it jumps into a huge function chunk. The assumption is that short branches are good and branches to function chunks result from an unwanted condition. Following this assumption, a good branch would go to .text:7DEB4C77 where two constants, ESI, and EDI are pushed followed by RtlLcidToLocaleName being called. The return value is then tested and if all goes well, RtlLCIDToCultureName returns a 1 (otherwise 0). Given all of this information, the following can be inferred

NTSTATUS RtlLCIDToCultureName(ULONG unknown1, PUNICODE_STRING unknown2);

With some logic and testing, it can be guessed and tested that unknown1 is the numerical LCID which will be converted and stored in unknown2. However, analyzing RtlLcidToLocaleName in depth will confirm this.

RtlLcidToLocaleName (disassembly reproduced below)

.text:7DEB454F                 public RtlLcidToLocaleName
.text:7DEB454F RtlLcidToLocaleName proc near           ; CODE XREF: RtlLCIDToCultureName+45p
.text:7DEB454F                                         ; sub_7DECC39F+58p ...
.text:7DEB454F
.text:7DEB454F var_BC          = word ptr -0BCh
.text:7DEB454F var_BA          = word ptr -0BAh
.text:7DEB454F var_B8          = dword ptr -0B8h
.text:7DEB454F var_B4          = dword ptr -0B4h
.text:7DEB454F var_B0          = byte ptr -0B0h
.text:7DEB454F var_4           = dword ptr -4
.text:7DEB454F arg_0           = dword ptr  8
.text:7DEB454F arg_4           = dword ptr  0Ch
.text:7DEB454F arg_8           = dword ptr  10h
.text:7DEB454F arg_C           = dword ptr  14h
.text:7DEB454F
.text:7DEB454F ; FUNCTION CHUNK AT .text:7DEFFB8C SIZE 000000CC BYTES
.text:7DEB454F
.text:7DEB454F                 mov     edi, edi
.text:7DEB4551                 push    ebp
.text:7DEB4552                 mov     ebp, esp
.text:7DEB4554                 sub     esp, 0BCh
.text:7DEB455A                 mov     eax, dword_7DF72088
.text:7DEB455F                 xor     eax, ebp
.text:7DEB4561                 mov     [ebp+var_4], eax
.text:7DEB4564                 push    ebx
.text:7DEB4565                 mov     ebx, [ebp+arg_4]
.text:7DEB4568                 push    esi
.text:7DEB4569                 push    edi
.text:7DEB456A                 mov     edi, [ebp+arg_0]
.text:7DEB456D                 mov     [ebp+var_B4], 55h
.text:7DEB4577                 test    edi, edi
.text:7DEB4579                 jz      loc_7DEB4649
.text:7DEB457F                 cmp     edi, 1000h
.text:7DEB4585                 jz      loc_7DEB4649
.text:7DEB458B                 test    ebx, ebx
.text:7DEB458D                 jz      loc_7DEFFB8C
.text:7DEB4593                 test    [ebp+arg_8], 0FFFFFFFDh
.text:7DEB459A                 jnz     loc_7DEFFB96
.text:7DEB45A0                 cmp     byte ptr [ebp+arg_C], 0
.text:7DEB45A4                 jnz     short loc_7DEB45B0
.text:7DEB45A6                 cmp     dword ptr [ebx+4], 0
.text:7DEB45AA                 jz      loc_7DEFFB8C
.text:7DEB45B0
.text:7DEB45B0 loc_7DEB45B0:                           ; CODE XREF: RtlLcidToLocaleName+55j
.text:7DEB45B0                 cmp     edi, 1400h
.text:7DEB45B6                 jz      loc_7DEFFBA0
.text:7DEB45BC                 cmp     edi, 0C00h
.text:7DEB45C2                 jz      loc_7DEFFC16
.text:7DEB45C8                 cmp     edi, 400h
.text:7DEB45CE                 jz      loc_7DEFFC16
.text:7DEB45D4                 mov     esi, dword_7DF72028
.text:7DEB45DA                 test    esi, esi
.text:7DEB45DC                 jz      loc_7DEFFBD6
.text:7DEB45E2
.text:7DEB45E2 loc_7DEB45E2:                           ; CODE XREF: RtlLcidToLocaleName+4B696j
.text:7DEB45E2                 cmp     edi, 800h
.text:7DEB45E8                 jz      loc_7DEFFBEA
.text:7DEB45EE
.text:7DEB45EE loc_7DEB45EE:                           ; CODE XREF: RtlLcidToLocaleName+4B6A1j
.text:7DEB45EE                 push    edi
.text:7DEB45EF                 call    sub_7DEB4510
.text:7DEB45F4                 test    eax, eax
.text:7DEB45F6                 jl      short loc_7DEB4649
.text:7DEB45F8                 test    byte ptr [ebp+arg_8], 2
.text:7DEB45FC                 jz      loc_7DEFFBF5
.text:7DEB4602
.text:7DEB4602 loc_7DEB4602:                           ; CODE XREF: RtlLcidToLocaleName+4B6BCj
.text:7DEB4602                 mov     ecx, [esi+14h]
.text:7DEB4605                 movzx   eax, word ptr [ecx+eax*8+6]
.text:7DEB460A                 mov     ecx, [esi+1Ch]
.text:7DEB460D                 lea     esi, [ecx+eax*2+2]
.text:7DEB4611                 lea     eax, [ebp+var_B4]
.text:7DEB4617                 push    eax
.text:7DEB4618                 push    54h
.text:7DEB461A                 push    esi
.text:7DEB461B                 call    sub_7DEB44CC
.text:7DEB4620                 test    eax, eax
.text:7DEB4622                 jl      loc_7DEFFBB9
.text:7DEB4628                 push    ebx
.text:7DEB4629                 push    [ebp+var_B4]
.text:7DEB462F                 push    esi
.text:7DEB4630
.text:7DEB4630 loc_7DEB4630:                           ; CODE XREF: RtlLcidToLocaleName+4B682j
.text:7DEB4630                                         ; RtlLcidToLocaleName+4B704j
.text:7DEB4630                 push    [ebp+arg_C]
.text:7DEB4633                 call    sub_7DEB4655
.text:7DEB4638
.text:7DEB4638 loc_7DEB4638:                           ; CODE XREF: RtlLcidToLocaleName+FFj
.text:7DEB4638                                         ; RtlLcidToLocaleName+4B642j ...
.text:7DEB4638                 mov     ecx, [ebp+var_4]
.text:7DEB463B                 pop     edi
.text:7DEB463C                 pop     esi
.text:7DEB463D                 xor     ecx, ebp
.text:7DEB463F                 pop     ebx
.text:7DEB4640                 call    sub_7DE9DF74
.text:7DEB4645                 leave
.text:7DEB4646                 retn    10h
.text:7DEB4649 ; ---------------------------------------------------------------------------
.text:7DEB4649
.text:7DEB4649 loc_7DEB4649:                           ; CODE XREF: RtlLcidToLocaleName+2Aj
.text:7DEB4649                                         ; RtlLcidToLocaleName+36j ...
.text:7DEB4649                 mov     eax, 0C00000EFh
.text:7DEB464E                 jmp     short loc_7DEB4638
.text:7DEB464E RtlLcidToLocaleName endp

Although slightly larger, the analysis is relatively straightforward. The flow graph shows a ton of error or value checking, but a stepwise path to the return point. It will be taken as an assumption for now that the branches result from error conditions from invalid or uninitialized values. The main interesting parts are

.text:7DEB4593                 test    [ebp+arg_8], 0FFFFFFFDh
.text:7DEB459A                 jnz     loc_7DEFFB96
.text:7DEB45A0                 cmp     byte ptr [ebp+arg_C], 0
.text:7DEB45A4                 jnz     short loc_7DEB45B0
.text:7DEB45A6                 cmp     dword ptr [ebx+4], 0
.text:7DEB45AA                 jz      loc_7DEFFB8C

The third argument, originally passed in as 2d, is compared against 0FFFFFFFDh (11111111111111111111111111111101b). The two values where these functions do not branch are 00b or 10b, or 0d and 2d. The many branches make the function annoying to analyze, but working through it and following a few branches (sub_7DEB4510 specifically) shows that dword_7DF72028 plays an important role. As a static analysis, not much can be determined. A lot of arithmetic follows loading dword_7DF72028 into a register, so it can be guessed that it retrieves a value based on the given key. Cross references to it also seem to suggest the same thing. Since the key to dword_7DF72028 depends on the EDI register, which is in turn, the first argument from RtlLCIDToCultureName, the guess that the first argument is the LCID has much more credibility. Further analysis also shows that the third parameter, passed in as a 2, is a flag to determine what the LCID is if additional flags are present. Using all of the knowledge gathered, it is now possible to call these functions from a normal program.

RtlCultureNameToLCID and RtlLocaleNameToLcid are more or less the same functions, except in reverse. Their analysis isn’t going to be shown here. The only significant difference is that RtlLocaleNameToLcid takes a PWSTR as its first parameter instead of a PUNICODE_STRING. There is also an ambiguity in the third parameter of RtlLocaleNameToLcid. All cross references call it with a 3h as the third argument, but the function only compares the third argument against a value of 2h or 0FFFFFFFCh. The condition for when the third argument matches 2h branches to code that manipulates another static address (dword_7DF72028). Since this was done as a static analysis, not too much can be inferred from what this code means. It is possible to gain more knowledge by reversing the program below and tracing through what those static addresses hold and what happens when the “magic” value is set to 2h. All of the functions and how to invoke them are shown in the sample program below.

#include <Windows.h>
#include <assert.h>
#include <stdio.h>
 
#define DECLARE_UNICODE_STRING(_var, _string) \
const WCHAR _var ## _buffer[] = _string; \
UNICODE_STRING _var = { sizeof(_string) - sizeof(WCHAR), sizeof(_string), (PWCH) _var ## _buffer }
 
typedef struct _UNICODE_STRING {
  USHORT  Length;
  USHORT  MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
 
typedef NTSTATUS (__stdcall* pRtlLCIDToCultureName)(ULONG lcid, PUNICODE_STRING name);
typedef NTSTATUS (__stdcall* pRtlLcidToLocaleName)(ULONG lcid, PUNICODE_STRING name,
	DWORD reserved, BYTE encoded);
typedef NTSTATUS (__stdcall* pRtlCultureNameToLCID)(PUNICODE_STRING name, PULONG lcid);
typedef NTSTATUS (__stdcall* pRtlLocaleNameToLcid)(PWSTR name, PULONG lcid, BYTE magic);
typedef NTSTATUS (__stdcall* pRtlConvertLCIDToString)(ULONG lcid, ULONG value, ULONG precision,
	PWSTR wstr_lcid, ULONG length);
 
int main(int argc, char* argv[])
{
	HMODULE hmodule = LoadLibrary(L"ntdll.dll");
	pRtlLCIDToCultureName LCIDToCulture =
		(pRtlLCIDToCultureName)GetProcAddress(hmodule, "RtlLCIDToCultureName");
	pRtlLcidToLocaleName LcidToLocale =
		(pRtlLcidToLocaleName)GetProcAddress(hmodule, "RtlLcidToLocaleName");
	pRtlCultureNameToLCID CultureToLCID =
		(pRtlCultureNameToLCID)GetProcAddress(hmodule, "RtlCultureNameToLCID");
	pRtlLocaleNameToLcid LocaleToLcid =
		(pRtlLocaleNameToLcid)GetProcAddress(hmodule, "RtlLocaleNameToLcid");
	pRtlConvertLCIDToString LCIDToString =
		(pRtlConvertLCIDToString)GetProcAddress(hmodule, "RtlConvertLCIDToString");
	NTSTATUS ret = 0;
	DECLARE_UNICODE_STRING(name, L"dummystringinitializer");
 
	if(LCIDToCulture && LcidToLocale && CultureToLCID && LocaleToLcid && LCIDToString)
	{
		//Test RtlLCIDToCultureName and LCIDCultureNameToLCID
		ULONG lcid_enus = 0x0409;
		ULONG lcid_ruru = 0x0419;
		ret = LCIDToCulture(lcid_enus, &name);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, lcid_enus, name.Buffer);
		ret = LCIDToCulture(lcid_ruru, &name);
		wprintf(L"[%i] LCID: %i  Culture name: %s\n", ret, lcid_ruru, name.Buffer);
 
		ULONG ret_enus = 0x0;
		ULONG ret_ruru = 0x0;
		DECLARE_UNICODE_STRING(wstr_enus, L"en-US");
		DECLARE_UNICODE_STRING(wstr_ruru, L"ru-RU");
		ret = CultureToLCID(&wstr_enus, &ret_enus);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, lcid_enus, wstr_enus.Buffer);
		ret = CultureToLCID(&wstr_ruru, &ret_ruru);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n\n", ret, lcid_ruru, wstr_ruru.Buffer);
 
		assert(ret_enus == lcid_enus);
		assert(ret_ruru == lcid_ruru);
		//End RtlLCIDToCultureName and LCIDCultureNameToLCID
 
		//Test RtlLcidToLocaleName and RtlLocaleNameToLcid
		ULONG lcid_dedephoneb = 0x10407; //Phone book sorting flag added
		ULONG lcid_esve = 0x200A;
		ret = LcidToLocale(lcid_dedephoneb, &name, 0x00, 0x02);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, lcid_dedephoneb, name.Buffer);
		ret = LcidToLocale(lcid_esve, &name, 0x00, 0x00);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, lcid_esve, name.Buffer);
 
		ULONG ret_dedephoneb = 0x0;
		ULONG ret_esve = 0x0;
		DECLARE_UNICODE_STRING(wstr_uzlatnuz, L"de-DE_phoneb");
		DECLARE_UNICODE_STRING(wstr_esve, L"es-VE");
		ret = LocaleToLcid(wstr_uzlatnuz.Buffer, &ret_dedephoneb, 3);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, ret_dedephoneb, wstr_uzlatnuz.Buffer);
		ret = LocaleToLcid(wstr_esve.Buffer, &ret_esve, 3);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n\n", ret, ret_esve, wstr_esve.Buffer);
 
		assert(lcid_dedephoneb == ret_dedephoneb);
		assert(lcid_esve == ret_esve);
		//End RtlLcidToLocaleName and RtlLocaleNameToLcid
 
		//Test ConvertLCIDToString
		ULONG lcid_is = 0x040F;
		ret = LCIDToString(lcid_is, 0x10, 0x4, name.Buffer, name.MaximumLength);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, lcid_is, name.Buffer);
		ret = LCIDToString(lcid_is, 0x10, 0x8, name.Buffer, name.MaximumLength);
		wprintf(L"[%i] LCID: %i  Culture/Locale name: %s\n", ret, lcid_is, name.Buffer);
		//End ConvertLCIDToString
	}
	else
		wprintf(L"One or more functions could not be exported.\n");
 
	return 0;
}

RtlConvertLCIDToString (usage shown above) is the last function to be discussed. It is a rather mysterious function which is only cross referenced once (shown below)

.text:7DF298E6                 push    20h
.text:7DF298E8                 push    esi
.text:7DF298E9                 push    4
.text:7DF298EB                 push    10h
.text:7DF298ED                 push    [ebp+ebx*4+var_24]
.text:7DF298F1                 call    RtlConvertLCIDToString

It seems to take three known integer values as its second, third, and fifth arguments. It calls the RtlIntegerToUnicodeString function passing in it’s first two arguments. Looking this up on MSDN yields

NTSTATUS
RtlIntegerToUnicodeString(
IN ULONG Value,
IN ULONG Base OPTIONAL,
IN OUT PUNICODE_STRING String
);

So the first parameter into RtlConvertLCIDToString is a ULONG value to convert — the LCID; the second one is the base of the value. This seems to make sense when looking back at the function call. The value stored in [ebp+ebx*4+var_24] will be converted to a string representation of a base 16 number. The fourth parameter can easily be deduced as the output string. The code handles it as a PWSTR instead of a UNICODE_STRING. The fifth parameter is compared against 200h (512d) and an error code is returned if it is greater or equal to. If the value is fine, the code proceeds normally and a UNICODE_STRING struct is initialized in EAX for use in RtlIntegerToUnicodeString. Looking at how the UNICODE_STRING struct is initialized, it is possible to see that the Length, and MaximumLength members are initialized depending on the value of the fifth argument. Therefore, the fifth argument to RtlConvertLCIDToString has to be the length of the PWSTR passed in the fourth argument (since Length <= MaximumLength). This just leaves the third argument to be examined. The first (and only) time that the third argument is used is at

.text:7DF29439                 cmp     eax, [ebp+arg_8]

What happens afterwards is pretty interesting. Depending on certain conditions, the code will either continue regularly, or is will loop and hit

.text:7DF2942F                 push    30h
.text:7DF29431                 pop     edx
.text:7DF29432                 mov     [ecx], dx

This block is interesting because 30h is put into [ecx], which in that context holds the PWSTR to receive the LCID as a string. The 30h is interesting because converted it is the value of a “0”. What is happening is that the PWSTR is being padded with zeroes while this loop is ongoing and there is nothing left to copy. This means that the third argument is the precision of the number.

That concludes everything for this post. A downloadable PDF is available here.

« Newer Posts

Powered by WordPress