For the people (and this article is dedicated for them), which have no clue about the thing and for the full coverage.
PE (Portable Executable) is format of executable files of all newer Window$.
In comparision with now nearly extinct NE species brings PE some substantial
advantages. The most important one is the 32-bit FLAT model support. In this
memory model the segments (as we know them from DOS or 16-bit protected mode)
practically did lost its sense. Every process has its own adress space, we do
not need to solve the old problem where to place some piece of code in order
not to fuck up (overwrite) another proggy and so on. Due to this feature,
can every program decide on its own, with practically no limitations, where
it will store whatever it want. If the program wants to have at adress
0x1234567 some wierd string, it can expect the string will be really there.
therefore it is not necessary for the program to have relacationa table
(but there are some reasons why this table is linked to the program by
default). As i mentioned before, segments in such a mode doesn't have any
special nor important reason. Program after the start has already set CS, DS,
and ES to the segments covering whole adress space (with 4GB size) and the
program can access any desired adress. Of course, if the system thinks it
it doesn't have access to this adress (if there is something important on
this adress [this is not the case under W95 or W98] or there is nothing on
this adress) attempt to access such a adress could generate exception. Such
exception could be of course intercepted and the lamer at the keyboard will
have no clue about it :-)... But this is far advanced for now...
As we want have the overview, how the adress space looks like, here it is:
0h- 3FFFFFh
reserved, should not be accessed 400000h-7FFFFFFFh
process private area -
here are all the sections form EXE mapped 80000000h-FFFFFFFFh
shared area -
various DLL, VxD and some other monsters
Well, as a kick start it should be enough, let go to the PE itself. PE is
format suspiciously similar to the COFF formats used on the Unix systems.
It consists of header and the data area which is directly mapped to the
adress space of the process. The headers itself are stored before start of code
so if you want to find them align address to 4kB and track back to find
page starting with 'MZ'. Of course don't forget to set exception handler for
page fault.
EXE files are mapped staring at adress 0x400000. This starting adress is
called ImageBase. It should be no suprise the ImageBase can be set during
linking process. When the file is loaded couple of things happens (shit is
none of them)
note: i don't guarantee the steps above are performed in given order ;-)
As for the DLL mapping this process is similar, but jump to the entry point is not " just jump" but AL holds value which holds information whether
DLL_PROCESS_ATTACH = 1
library is mapped to a new process and
should initialise its global dataDLL_THREAD_ATTACH = 2
some proces registred library again but
data have been already inicialised
(DLL_PROCESS_ATTACH already performed)DLL_THREAD_DETACH = 3
some thread freed libraryDLL_PROCESS_DETACH = 0
process terminates, library is being unmapped
and should terminate its activity
note: most likely calling of library entry point could be disabled by some way
note: naturally, library doen't have own stack
Due compatibility reasons every PE file starts with MZ header, where at
offset 0x3C is dword ptr to the PE header.
File is divided in somethink like sectors (with variable size, by default
0x200 - FileAlignment) and file headers (all together, not every one
separated) and sections are aligned to that size.
This creates some space for storing the virus body (but under 4000
bytes) because on every section we can gain approx. 100h bytes and
in the PE files do not use have to much sections. This strategy is
used by CIH.
As for the structure of the headers, i strongly recommend to see file WINNT.H File header has following structure:
struct PE_FILE_HEADERS
{
DWORD magic; // = 0x00004550 ("PE\0\0")
_IMAGE_FILE_HEADER primary_header;
_IMAGE_OPTIONAL_HEADER optional_header;
_IMAGE_SECTION_HEADER section_headers[primary_header.NumberOfSections];
BYTE dummy[aligned to optional_header.FileAlignment];
};
In the header i would like to point to some positions
_IMAGE_FILE_HEADER | |
Machine |
processor, on which the file is able to run I386 = 0x14c |
NumberOfSections |
name says it all, for dummies number of the sections in program |
_IMAGE_OPTIONAL_HEADER | |
SizeOfCode |
size of all pages alloceted for code |
SizeOfInitializedData |
size of all pages allocated for data |
AddressOfEntryPoint |
RVA adress of entry pointu (relative 2 ImageBase) |
ImageBase |
starting on this adress all the sections are mapped |
SectionAlignment |
alignment of the sections (I386=0x1000) |
FileAlignment |
alignment of the file |
SizeOfImage |
size in bytes including all headers, has 2 be multiple of object align (allocated for code and data) |
SizeOfHeaders |
size of headers (including all section headers) |
CheckSum |
checksum - the same as in DOS - ignored |
Subsystem |
this tells the OS wheather it is windowed or console aplication |
DataDirectory |
this is a field belonging to the structure containing RVAs and sizes of some important tables |
PE code and data are divided to sections. Each section has its description in header. Main purpose of this is to tell loader where in address space should be data stored, how big area should be allocated and what attributes should be set for them. For example code section should start at 4000000h and should be read only and executable. Special attribute for section is SHARED. If section is shared all instances of this program has this sections common. It means if one of them modifies something in section all instances can see it.
Some word of explanation to the RVA (RelativeVirtualAdress). All the complicated operations as e.g. relocation etc... is are performed AFTER the file is mapped to the memory. If we add to the RVA value the ImageBase, we get the pointer directly to the memory adress where the desired piece of information is mapped. Without mapping the problem is much harder, cos we need to search through all the sections and find the one RVA points to.
(VirtualAddress<RVA<VirtualAddress+SizeOfRawData)
and offset in the file could be calculated as:
file_off = PhysicalAddress + RVA-VirtualAddress
And now there are only three problems to solve
1. Where to store the virus body
Before we will go any further, we should notice, that none of us known
implementation of Window$ that checks if we execute the code in sections
which is declared as data (cool enough ... :-P). This is the fact all the
viruses (and packers as well) heavy relies on.
As for the storage of the body, i saw till now 3 different strategies.
Common strategy is based on the extending the last section. In such a case is "conditio sinne qua non" - basic condition the section should be writeable in the section attributes (Intel platform could check it). As for the implementation of this method, it easy as it could only be, but for resident viruses we can got in to the trouble.... Physical data in the section can be followed by uninitialized data, which can be changed by the program. This means our fine piece of code may become fucked up. Therefore whole viral body should be moved elsewhere.
Another method is to create new section in the file and copy virus body there. This approach has one major disadvantage (which is nearly impossible to solve) - nonstandard section name could arise a suspiction of something not very pleasant going on, not speaking of the situation header is to small for adding another section. Contrary, advantage is we have our section just for us, nobody can overwrite the virus. As for the implementation, trivial again in comparision with before mentioned method we have one aditional task - we need to know, where last section ends.
One of the non-standart method is the one used by CIH virus. This is all about the using the free space in sectors which are aligned to FileAlignment value (0x200 by default). This technique is very clever (invented by some as Germans use to say "Klugscheisser") as we get bonus in some cases there is no increase in file lenght plus the virus is harder to clean. This method will not be in the focus of this article as for the larges viruses in not suitable.
Main disadvantage of the first two methods is the code runs in the last section. And if some emulator get here, there are just and only 3 options left.
But solution is up to you - probably best way would be to use CIH approach and place entry point in the first sections.
2. we_need_to_be_first_on_the_draw
how to make our fine virus the supreme commander in the system
Most trivial solution is to modify AdressOfEntryPoint in the PE header
to point to start of added (viral) code. In plain words put there RVA
of virus entry point. Nothing more nothing less.
More rafined methods are e.g. to hook some import (let's say CreateFileA or whatevever is on 100% callled in every proggy) or even hook export in DLL's so every call to DLL goes through virus. This approach is not trivial as complicated search in structures is required. Some of the options will be covered in next section.
3. How to call API functions
This is the key problem of all the viruses. This problem could be
transformed in the question - what API calls i need to get pointer
to whatever API function?
Answer is GetModuleHandle and GetProcAdress. If we have pointers
to this functions we can get pointer to any function we want.
Both of this two API calls are exported from kernel32.dll. As i haven't
seen any application not importing something from this important system
module we can perform test if the file we want to infect imports from
kernel32.dll.
Then we have to search in import table and look for this two functions.
If we find them, the file is suitable target for the infection.
Imports in PE file work that way they point to some dword and in this
dword will be set address of imported function. All the requests in the
file for specific API function will look like
call dword ptr [dddd]
Advantage for us is there is no problem to hook function and at each call
to this function do "something".
To the import table we will get throug DataDirectory (i think index 1).
This should point to the array of structures IMAGE_IMPORT_DESCRIPTOR.
Last element of the array should have Characteristics set to 0. Name is
RVA pointer to the name of the module from which the import is performed.
OriginalFirstThunk points to array of type IMAGE_THUNK_DATA from which we
can get the name of the function. Let's saz the function has index i.
Then pointer to function f in imports will be i-th element of the dword
array, to which FirstThunk points. Last element of the array of type
IMAGE_THUNK_DATA is zero.
I recommend to see it all in HIEW and then in debugger search where the desired functions are.
//
// Import Format
//
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
typedef struct _IMAGE_THUNK_DATA {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA;
typedef IMAGE_THUNK_DATA * PIMAGE_THUNK_DATA;
#define IMAGE_ORDINAL_FLAG 0x80000000
#define IMAGE_SNAP_BY_ORDINAL(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG) != 0)
#define IMAGE_ORDINAL(Ordinal) (Ordinal & 0xffff)
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
PIMAGE_THUNK_DATA OriginalFirstThunk; // RVA to original unbound IAT
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
PIMAGE_THUNK_DATA FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
As I mentioned before, there is no need to do all fixups while file is being infected. It is either possible to do it after file is loaded. Just find old 'MZ' header and trace file structure. (What is nice, rva are almost valid pointers so you need just to add base address and you can follow any structure). After you find first export to Kernel32 use the same method to find start of kernel32 and then follow export table ...
As for the exports it's much easier, but i had no time for experiments and will let this problem open. If you want know more, look for it in some PE doxes (RTFM).
I would like to point to the fact the article has been written more or less
using just and only my memory (my degenerated gray cell mass), has not been
subject to verification and thus i can't guarantee any fact presented here
is true :))))))
If you are interested in the problem, you should see it again elsewhere,
in some more reliable refference.
And finally, some warm closing words. If the fucking proggy doesn't work and you poor guy did check it all over and over about 10 times, try to find that bug in the Windows loader.
Navrhar