2.Initial
approach (Introduction)
|
Let's
see what we have on our hands first... hmm a PE-structure based reverseme.
That means we have to know the structure of a PE file (windows exe
file) very well (at least that's what they tell us). Now, we know
that a PE file has some crappy info we're not concerned about at its
beggining, then the PE header, than a certain number of sections that
contain our data/ code/ resources. The PE header is divided into the
Image File Header and the Image Optional Header (not really optional,
but that's what they called it... :) ) Let's take a look at the two
header definitions from WINNT.H:
WINNT.H
typedef struct _IMAGE_FILE_HEADER {
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
USHORT Magic;
UCHAR MajorLinkerVersion;
UCHAR MinorLinkerVersion;
ULONG SizeOfCode;
ULONG SizeOfInitializedData;
ULONG SizeOfUninitializedData;
ULONG AddressOfEntryPoint;
ULONG BaseOfCode;
ULONG BaseOfData;
//
// NT additional fields.
//
ULONG ImageBase;
ULONG SectionAlignment;
ULONG FileAlignment;
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
USHORT MajorImageVersion;
USHORT MinorImageVersion;
USHORT MajorSubsystemVersion;
USHORT MinorSubsystemVersion;
ULONG Reserved1;
ULONG SizeOfImage;
ULONG SizeOfHeaders;
ULONG CheckSum;
USHORT Subsystem;
USHORT DllCharacteristics;
ULONG SizeOfStackReserve;
ULONG SizeOfStackCommit;
ULONG SizeOfHeapReserve;
ULONG SizeOfHeapCommit;
ULONG LoaderFlags;
ULONG NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
Next, since we are going to deal with the import table, let's say
a few words about it also. The import table is usually situated towards
the beggining of a section (usually .rdata), and its structure is
based on the following structure definition:
typedef struct tagImportDirectory {
DWORD dwRVAFunctionNameList;
DWORD dwUseless1;
DWORD dwUseless2;
DWORD dwRVAModuleName;
DWORD dwRVAFunctionAddressList;
} IMAGE_IMPORT_MODULE_DIRECTORY, * PIMAGE_IMPORT_MODULE_DIRECTORY;
Also take a look at the exe (with your hex editor let's say). Notice
that the only imported function is Kernel32!ExitProcess. That means
we'll have to alter the import table somehow.
And, the api definition for the function we need to import, and then
call:
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
That's about it for the info we need to start, the rest I'll tell
you 'on the road'. |
3.Essay
(steps)
|
Let's
see what our goals are then for this reverseme: add a new section,
modify the import table and add a little extra code that displays
a messagebox (but all this 'manually'). The essay will be
divided in three parts accordingly.
PART I
Take a look at the exe in PEBuild. See that it has 3 sections.
We will add our own, called '.tank'. Therefore, we will modify at
first the number of sections in the image file header (first locate
the beggining of the PE header, there's a pointer to at at offset
0x3C). Go to 0xB6 to see the desired number(the number of sections
in the exe). We see it is 0300=3. We will increment the value to
0400=4. Every single modify I make, make it using your favourite
hex-editor. See the original and changed exe in fig #1 and fig #2.
Fig. #1: The original no.
of sections

Fig. #2: The modified no. of sections
We have now changed the number of sections, let's add the actual section
definition now. The sections table starts around 0x1A0 and ends at
0x21F. Therefore, we will add our section at 0x220. Now, a typical
section looks like this: name(8 bytes), virtual size(4 bytes), virtual
RVA(4 bytes), physical size(4 bytes), physical RVA(4 bytes), never
mind(12 bytes), characteristics(4 bytes). A grand total of 40 bytes.
There
you have it, so our new section will look something like this:
section .tank{
name='.tank';//8 bytes
VirtualSize=100;//4 bytes
VirtualRVA=4000;//4 bytes
PhysicalSize=200;//4 bytes
PhysicalRVA=A00;//4 bytes
Unused=0;//12 bytes
Characteristics=E0000020;//4 bytes
}
Now, why THOSE values? Here's why:
Virtual Size is how much of the exe our section occupies (not counting
the ending zeros). I used 100
just to be sure, it's actually B0 in size.
Virtual RVA is where the section will be loaded into memory (Location
of section in memory=Image Base+Virtual RVA). My Virtual RVA was determined
by adding 1000 to the Virtual RVA of the previous section.
Physical Size. That is how much is allocated in the exe for our section
(including ending zeos). 200 is fair enough.
Physical RVA. The address in the exe where our section begins. The
original exe ended at 9FF, so our new section will start at A00.
Add those values in your hex editor as seen below (figure #4). Now,
using your hex-editor, or whatever, add 200 bytes at the end of the
exe.
Now we have finished adding a new section and part one of the essay
also. :)
Fig. #3: The original sections table

Fig. #4: The modified sections table
PART II
Here's
part two of our essay: we will try (and succeed :P) to add some
imports to the import table (we need the MessageBoxA function, remember?),
but without changing the original import table. At offset 0x130
we find the pointer to the original import table (fig. #5). Remeber
it, then change it to 4000 (Virtual RVA of our section)(fig. #6).
Now, take a look at 2008(virtual offset, actually 608 as physical
offset)(the original import table), we will have to copy the first
20 bytes in our section, then add another 20 bytes for our import
(User32!MessageBoxA) and finally 20 empty bytes to let the loader
know the import table has finished. That means we will write the
module/api information strting at 0xA3C=0x403C(virtual offset).
Since we only import one api, it's gonna be 4 bytes in length (4
bytes=1 api), then 4 empty bytes, and again the same information
(again 4 bytes the pointer to MessageBoxA, and 4 empty bytes). After
these 32 bytes, we can write the name of our desired DLL (user 32.dll,
followed by a zero byte - name of modules are zero terminated) at
0x404C=0x64C. That means our section (.tank) will start like this:
20 bytes for the original import table (copy them from 0x608), then
our 20 bytes and final 20 bytes, all equal to 0. Now, our 20 bytes
will look like this (respecting the tagImportDirectory structure
defined earlier):
3C40 0000=0x403C --> pointer to information about which apis
to load
0000 0000=0x0 --> useless
0000
0000=0x0 --> useless
4C40 0000=0x404C --> pointer to module name (user32.dll)
4440 0000=0x4044 -->again pointer to information about which
apis to load, their addresses during runtime will be found starting
at this address+imagebase (404044).
So the first api at 404044, the second (if it would have existed)
at 404048 and so on... We only have one api, that means that call
dword ptr [404044] is actually call MessageBoxA after the import
table is properly loaded at startup. Now we need to know what to
write at 403C(0xA3C) and 404C(0xA4C). We need to write there a pointer
to the name of the api(s) we need to load. Therefore, after the
name of the dll (user32.dll), write two zeros and then MessageBoxA
(the two zeros are used as hints, don't ask). Do that at 0xA57=0x4057.
Now, at 403C and 404C write 5740(=4057). Now if everything is ok,
when you run the exe you should have a brand new api loaded at startup,
simply waiting for you to call it.
Fig. #5: The original import table
pointer
Fig. #6: The modified import
table pointer
PART
III
Fig. #7: The new section
Here's the final (and easiest) part of the essay, adding the extra
code. Now, we need to call MessageBoxA with a text and a caption as
main parameters. So, we need them somewhere in the exe (at A70 write'em).
Now, we will add the code starting with AA0, which is 40A0 in memory,
so we will need to modify the entry point RVA from 1000 (original)
to 40A0 (our code start). Do that at 0x00D8, write A040(=40A0). Now
we only need to add the code, as said at 0xAA0:
push 0 ;unused
push 0 ;unused
push 404090 ;caption
push 404070 ;text
push 0 ;unused
call d,[404044] ;Call MessageBoxA
jmp .401000 ;jmp to original entry point (Use OpGen to get the opcodes)
Assemble this in your favourite asm editor, and run it. Voila...
it w0rks... kewl right!
See fig #7 for the new section of the exe.
As sad I am to go, my essay must end here, but remember... I'LL BE
BACK! |