A computer’s main component is the CPU (Central Processing Unit) or microprocessor. The microprocessor executes instructions.
The microprocessor also contains registers. Each register is a temporary storage place inside the microprocessor. Some registers are general purpose storage places, while other registers are used to store specific kinds of data.
In the following, we will be dealing with registers that are 4 bytes long.
One byte contains 8 bits. A bit (binary digit) is either 0 or 1.
A computer also has RAM (Random Access Memory) memory which is a temporary storage for data that the microprocessor acts upon.
The memory is a long list of bytes. Each byte has an address. The first memory byte has an address of 0, the second memory byte has an address of 1, the third memory byte has an address of 2 and so on.
Addresses correspond to bytes, not to individual bits. An address corresponds to one byte in memory, not to a bit, not to a double byte, not to a quadruple byte, not to anything else for that matter. The next address (the address plus one) corresponds to to the next byte in memory and so on.
The hexadecimal system is used to denote addresses. The hexadecimal system is also used to denote the contents of memory. Each hexadecimal digit corresponds to 4 binary digits. Thus, a byte is represented by 2 hexadecimal digits.
Addresses are 4 bytes long. The lowest address is hexadecimal 00000000 (or decimal 0). The highest address is hexadecimal FFFFFFFF (or decimal 4,294,967,295 or (2^32)-1). Thus a program can access (including the first zero)
4,294,967,296 or 2^32 bytes of memory.
As mentioned, addresses are 4 bytes long and we will be dealing with registers that are 4 bytes long. Thus one register can hold an address.
THE HARD DISK
A computer also has one or more hard disks which are a permanent storage for data.
In the following, we will only be dealing with the microprocessor and the memory.
Every program is a series of instructions that the microprocessor executes. Every program uses memory to temporarily store data that the instructions use and manipulate.
Every program uses areas in memory. On such area is the stack.
LIFO AND FIFO STRUCTURES
The stack is a LIFO (Last In First Out) structure and it resides in memory. It is thus an area in memory. Every program uses a stack.
The stack is a LIFO structure because of the way that the microprocessor and the programs use it. Data is put in (pushed) at the top of the stack and data is also taken out (poped) at the top of the stack. Thus, the last data that is pushed is the first data that is poped.
We can compare a stack to a queue. A queue is a FIFO (First In First Out) structure. Data is put in at the start of the queue and taken out at the end of the queue. In a queue, the first data that is put in is the first data that is taken out.
In the following, we will be dealing with the stack.
The top of the stack is towards lower addresses and the bottom (beginning) of the stack is towards higher addresses.
The stack grows towards lower addresses and shrinks towards higher addresses.
The stack is aligned at a 4-byte boundary and grows and shrinks in increments of 4 bytes.
The ESP register points at the top of the stack.
When we push a register in the stack, the stack grows 4 bytes down and the ESP register is decremented by 4. Thus, decrementing the ESP register makes the stack grow.
When we pop a register from the stack, the stack shrinks 4 bytes up and the ESP register is incremented by 4. Thus, incrementing the ESP register makes the stack shrink.
WINDOWS INTERNAL ARCHITECTURE
Each process is able to access a 32-bit (4 GB) address space. But only the lower 2 GB is actually available, because the Windows operating system reserves for itself the upper 2GB.
The lower virtual addresses are from 00000000 to 7FFFFFFF and the upper virtual addresses are from 80000000 to FFFFFFFF. The 00000000 to 0000FFFF region, the 7FFF0000 to 7FFFFFFF region and the 80000000 to FFFFFFFF are reserved by Windows. Thus each and every process has the 00010000 to 7FFEFFFF for itself.
00000000 --------------- --------------- . Reserved . by . Windows 0000FFFF --------------- 00010000 --------------- . Available Lower . to the 2 GB . application 7FFEFFFF --------------- 7FFF0000 --------------- . . . 7FFF0000 Reserved --------------- 80000000 by --------------- . Windows Upper . 2 GB . FFFFFFFF --------------- ---------------
The upper 2 GB are used by Windows to load all system DLLs. Thus, KERNEL32.DLL will always be at the same upper virtual address (for a specific version of the Windows operating system).
The lower 2 GB are used to load the process in them and to also load all DLLs (except system DLLs that are loaded in the upper 2 GB) that the process depends upon.
A process will always be loaded in the same lower virtual address (depended only on the version of the Windows operating system) as all other processes, because each process is treated and acts as it is the only one running.
Since Windows is a multitasking operating system, more than one process can run simultaneously. But each process is written in a way that ignores this fact. Each process can access all the lower 2 GB address space. (There is an exception to that. The 00000000 to 0000FFFF region and the 7FFF0000 to 7FFFFFFF region are also reserved by Windows).
Each process acts like it is the only one running. This is made possible by the VMM (Virtual Memory Manager) in Windows.
VMM is a process that maps virtual addresses to physical RAM. A process accesses and manipulates virtual addresses. VMM translates these addresses to actual physical memory (RAM) addresses, in a way that is completely transparent to each and all applications that are running.
We can have ten processes running simultaneously, each one accessing (reading or writing) for example to the 008D34FA address. The VMM will keep tract of all the processes addresses and provide each one of the processes their own “version” of the virtual memory addresses.
When the VMM maps the virtual memory address to physical RAM, it will map it to a physical memory addresses that will change from process to process and also from time to time for the same process.
Thus VMM creates the illusion for each process that this process is the only one running and that this process can address all 2 GB of the lower address region (or, rather, from 00010000 to 7FFEFFFF to be absolutely correct).
In conclusion, a process will always be loaded in the same lower 2 GB virtual addresses, each and every system DLL will be loaded in the same upper 2 GB virtual addresses and the non-system DLLs that the process needs will be loaded in the lower 2 GB virtual addresses. The latter DLLs will be loaded to the lower 2 GB virtual addresses according to the sequence that the process loads them. Thus a non-system DLL can be loaded to any lower 2 GB range of addresses after the process. The first DLL that the process loads will go to the virtual addresses above the process, the second DLL will go above the first and so on.
PE (PORTABLE EXECUTABLE) FORMAT
PE is the format (i.e. the structure) .EXE and .DLL files have when on disk and also when loaded on memory (for execution). This format dictates how the .EXE or the .DLL (or other types of system files as well) are structured. For example, it dictates that there will be headers defining the type of the file, also where on memory it will be loaded, etc. It also dictates at what place in the file the actual program code will reside, where the import and export tables will be in the file, how these tables will be structured and so on.
Thus, a .EXE or a .DLL file do not just contain the program instructions (i.e. the microprocessor opcodes) but a lot of other information.
Of this information, special interest has the address at which the file will be loaded on memory (the “base memory address” as it is called), and the use of import and export tables.
When we have a .EXE file, the address at which it will be loaded is 0x00400000. For a .DLL that is a system .DLL, this address will be in the upper 2 GB region and for a non-system .DLL this address will be in the lower 2 GB region.
As far as the import and export table are concerned, these are actually placeholder tables that reside somewhere in the executable file and in the memory region it occupies (when loaded in memory for execution).
An .EXE or .DLL file usually imports and/or exports functions. It imports functions from other .EXEs or .DLLs in order to use them (i.e. to call them). It exports functions if it wants these functions to be used by other .EXEs or .DLLs.
When we say that a .EXE or .DLL imports functions, we mean that in the PE file format we declare that the program will use such and such functions from such and such files and that the program code in the PE file format will call these functions at some place.
When we say that a .EXE or .DLL exports functions, we mean that in the PE file format we declare that the program provides the code (i.e. the implementation) of such and such functions and that other programs can use these functions. The code for these functions is, of course, in the file in a way that is dictated by the PE file format.
When a .EXE or .DLL file imports functions, it knows the names of the files it will import them from and it knows the names of the functions it will import. But it does not know the location of these functions in memory. This is how the import tables help by acting as placeholders.
When a .EXE or a .DLL is loaded in memory, it is loaded by an operating system process called “the loader”. The loader reads the file from disk and maps it into memory at the address the file’s PE format specifies. The loader then checks the import table and finds and loads all .EXE or .DLL files that the program needs at the addresses their PE files dictate. It then looks in their export tables to find the specific address that each export function has in memory. For each export function it fills its address in the file’s import table.
Thus the file’s import table is a placeholder of each imported function’s memory address. The code in the file is written in a way that to call an imported function, it looks for its address in the import table. The loader makes sure to load the called file that exports this function, to find the function’s address in memory and to write that address in the calling file’s import table. When the function is called, the address in the import table is where program execution is redirected.
As a final note, the loader finds a function’s memory address by adding the base memory address of the file that exports the function to the offset the function address has from the beginning of its file.