RCE Endeavors 😅

January 23, 2011

Steganography with Magic Squares

Filed under: Cryptography — admin @ 11:36 AM

Steganography is “the art or practice of concealing a message, image, or file within another message, image, or file.” The general premise is that your message is in plain sight, but obscured by the fact that no one, except who you want, knows to look for it. An image file or a block of text looks more unsuspecting than something like 03b062766c06092b6926f84ef0c41ad434fdfb327b6ee80c8fff87cefa09590f2212bb82b6b5

aa027a17529deadb99b206e580a3625f8784726d308bb9d7afa3e8cd97d83fb8f6ed1111c2ce

c4b64a60a5deca3bbaeba1b3241bb13718779ddaf01cd511f74c5ca59d1a51f11171cb9221cea9

ed6aad68fa73d22568899d328e

which is a 1024-bit RSA ciphertext. Steganography can be performed in countless ways — from modifying bits in files to using invisible ink to write a message on a physical canvas. Steganography also has quite a history, going back to the times of ancient Greece. Herodotus wrote about Histiaeus shaving the head of his most trusted slave and tattooing a message on the slave’s bald head. When the slave’s hair regrew, the message would be concealed and the slave could be sent off without having to conceal any physical evidence of a message. The receiving end  of the message (Aristagoras) would either know of the messages existence, or the slave would be trusted to reveal it to him upon arrival. With the advent of the digital age, Steganography can take advantage of technology and become more widespread. Images, text documents, music files, or any seemingly common and innocent looking object can be taken advantage of to conceal a message. This can then be transmitted over the internet to knowing parties without anyone knowing — unless they are specifically looking for it. The sheer size of the internet makes steganographic messages very difficult (almost impossible?) to spot. Knowing that there are messages buried within the billions of images, or files in general, on the internet doesn’t really provide any sort of start on how to get those images. Also, once a steganographic message is found, it is very difficult to extract. If the message was not encoded naively, there is not much of a starting point. The message can be encoded in a way to still pass a chi-square test for randomness of bits so extracting a key or plaintext message would be difficult. The steganographic message itself can be ciphertext which yields even further complications. As a result, an analogue to cryptanalysis called steganalysis has been (and is being) developed to find and extract steganographic codes. Most of the techniques rely on statistical analysis to find unusual features in a message — which may go on to yield how it was encoded and allow for the original message to be deciphered.

I recently decided to make a program that would hide one image in another. This works by doing a simple least significant bit substitution. The four least significant bits (LSBs) of every RGB value in the source image would be substituted with the four most significant bits (MSBs) of the hidden image. This takes advantage of the fact that the the four LSB values do not encode a large range of colors — their range is 0 to 15, as opposed to 16 to 255 of the remaining four bits. The theory is then that you can take two pixels, line them up, and make a new pixel. This new pixel has the four MSBs of the original image, but has its four LSBs replaced by the four MSBs of the hidden image. This preserves most of the color range of the source image, and encodes the important part of the hidden image. Thus,

+  = 

Visually, the difference is very small, even at the pixel level. On a large scale, with the colors blending together, a normal image and one that contains a message is very difficult to spot. The program that I originally wrote to do the simple LSB substitution technique produced these two images.

Which one has the hidden message embedded inside?

The difference in color is extremely difficult to spot on this scope. No image artifacts are present unless someone is specifically looking for them. At first glance, no one would know that a message is hidden in one of those two images. Performing the bit extraction on the following two images produces these two resulting images:

Quite a surprising difference. The results go back to the wide range that the four MSBs can encode.

A problem is that extraction is quite simple. An obfuscation technique that I decided to incorporate into this was the use of magic squares. A magic square is an n x n matrix containing integers whose sum across the rows, columns, and diagonals all equal the same value. For example, the following magic square sums up to 65:

These squares are useful in that for odd numbered sizes there is a general formula to generate a number m at row i and column j. This provides for a linear time algorithm as a function of size and squares of any odd order can be quickly generated. Unfortunately, no formula exists for generating an even numbered square, so I avoided using them for this project. Sidenote: there are techniques to generate even squares in special forms. One of the interesting things about magic squares is the number of unique squares. Excluding rotations and reflections of a square, the number of unique square configurations is: 1, 0, 1, 880, 275305224, for n = 1 .. 5. For n >= 6 there is no known number of configurations. This provides an ample key space to choose from.

There are several ways that magic squares can be useful in steganography. Using a formula to generate a base magic square provides a unique encryption key. Alternatively, a magic square unique from the base square can be found and used as a key. This key can then be used to add a layer of encryption to the hidden data. For example, let the tile below be a 3×3 subsquare of a 9×9 square. A magic square key can then be applied by continually tiling squares over box pixel regions.

So the 9×9 square would be broken up into nine 3×3 squares and the exclusive-or operation applied to the MSB of the hidden image. The same square can then be used when extracting the hidden image. Alternatively, the square can be treated as a one dimensional array and each element will act as an exclusive-or value for each pixel in the hidden image. Taking it even further, the square can be continually rotated or reflected about axes so the key doesn’t show as much repetition, although these steps would also have to be encoded in the image unless the same type of rotation/reflection takes place at each step. There are no shortage of possibilities for unique usage of magic squares. One question may be that since only four bits per color are stored, why use any sized square with values > 15 (0b1111). The answer is that these squares can be extended to be used with any form of data — not just images. Larger sized squares might be useful when more data can be stored — perhaps at some block level instead of a byte level.

For simplicity, I chose to implement was a simple algorithm that treats a square as a one dimensional array and uses the four lowest bits of it as an XOR key against the MSBs of the hidden image. The result of extracting the four MSBs without knowing the key of the hidden image is shown below:

The result is significantly different without knowing the key. Extracting the four LSBs is not enough to give much information about the source image. The size of the square also affects how the resulting image will be viewed (which should be obvious). A longer key provides for more distortion across the image. A key of length is equal to or greater than the data to encrypt would provide the best results. Below are the result with magic squares of n = 7, 13, 17, and 51.

Overall, the technique is really simple to implement and there shouldn’t be stopping anyone from using a simple XOR-based encryption for extra security, at the very least.

A copy of this post is available as a downloadable PDF 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.

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.

The Beginning…

Filed under: Uncategorized — admin @ 9:02 AM

I’ve put this blog up to help me stay focused and organized on my reverse engineering projects. In the future, it will contain posts about any sort of low level analysis that I do and my progress on it. For the time being, upcoming posts will focus mainly on x86 and x86-64 reverse engineering of applications until I get comfortable and experienced enough to branch out into other architectures, or into the CLR and/or JVM. I’m continually learning more about the field, and as such future posts may contain slight errors in my analysis of disassembled programs/code snippets. I’ll try to minimize this as much as possible and may revise old posts as I get a better understanding of things. I’ll end this initial post with a quote:

If you think technology can solve your security problems, then you don’t understand the problems and you don’t understand the technology.  – Bruce Schneier

Powered by WordPress