Here's
one example (how to assemble my compiler with NASM into a Windows .exe) of doing that.
My
linker does that as well.
The following is a sample app for
my compiler (Smaller C) and linker that uses a few Win32 functions:
Code: Select all
// file: main2.c
//
// How to compile:
// smlrc -seg32 main2.c main2.asm
// nasm -f elf main2.asm -o main2.o
// smlrl -verbose -win -o main2.exe main2.o -map main2.map >main2.txt
typedef unsigned uint32;
typedef struct
{
union
{
uint32 Characteristics;
uint32 OrdinalFirstThunk;
} u;
uint32 TimeDateStamp;
uint32 ForwarderChain;
uint32 Name;
uint32 FirstThunk;
} tPeImageImportDescriptor;
static char hint_ExitProcess[] = "\0\0ExitProcess";
static char hint_GetStdHandle[] = "\0\0GetStdHandle";
static char hint_WriteFile[] = "\0\0WriteFile";
static void* hints[] =
{
hint_ExitProcess,
hint_GetStdHandle,
hint_WriteFile,
0
};
static void* iat[] =
{
hint_ExitProcess,
hint_GetStdHandle,
hint_WriteFile,
0
};
tPeImageImportDescriptor _dll_imports[] =
{
{
{ hints },
0,
0,
"kernel32.dll",
iat
},
{
{ 0 }
}
};
void ExitProcess(unsigned ExitCode)
{
asm("push dword [ebp+8]\n"
"call [_iat + 4*0]");
}
unsigned GetStdHandle(unsigned nStdHandle)
{
asm("push dword [ebp+8]\n"
"call [_iat + 4*1]");
}
int WriteFile(unsigned Handle,
void* Buffer,
unsigned NumberOfBytesToWrite,
unsigned* NumberOfBytesWritten,
void* Overlapped)
{
asm("push dword [ebp+24]\n"
"push dword [ebp+20]\n"
"push dword [ebp+16]\n"
"push dword [ebp+12]\n"
"push dword [ebp+8]\n"
"call [_iat + 4*2]");
}
void _start(void)
{
char hwmsg[] = "Hello, World!\r\n";
int h = GetStdHandle(-11);
WriteFile(h, hwmsg, sizeof hwmsg - 1, 0, 0);
ExitProcess(0);
}
My linker does little to nothing unusual w.r.t. dll import information, it simply looks up the symbol
__dll_imports (the _dll_imports[] array in the above code), writes its relative (to image base) address into the PE optional header and converts a few pointers in the relevant tables/arrays to relative as well.
The relevant parts in RelocateAndWriteAllSections() are these:
Code: Select all
peImportsStart = FindSymbolAddress("__dll_imports");
if (peImportsStart)
PeOptionalHeader.DataDirectory[1].VirtualAddress = peImportsStart - imageBase;
and
Code: Select all
// In PE, some importing-related addresses must be relative to the image base, so make them relative
if (OutputFormat == FormatWinPe32 && peImportsStart)
{
uint32 iofs;
for (iofs = peImportsStart; ; iofs += sizeof(tPeImageImportDescriptor))
{
tPeImageImportDescriptor id;
uint32 ofs, v;
Fseek(fout, iofs - imageBase, SEEK_SET);
Fread(&id, sizeof id, fout);
if (!id.u.OrdinalFirstThunk || !id.Name || !id.FirstThunk)
break;
id.u.OrdinalFirstThunk -= imageBase;
id.Name -= imageBase;
id.FirstThunk -= imageBase;
Fseek(fout, iofs - imageBase, SEEK_SET);
Fwrite(&id, sizeof id, fout);
for (ofs = id.u.OrdinalFirstThunk; ; ofs += sizeof v)
{
Fseek(fout, ofs, SEEK_SET);
Fread(&v, sizeof v, fout);
if (!v)
break;
v -= imageBase;
Fseek(fout, ofs, SEEK_SET);
Fwrite(&v, sizeof v, fout);
}
for (ofs = id.FirstThunk; ; ofs += sizeof v)
{
Fseek(fout, ofs, SEEK_SET);
Fread(&v, sizeof v, fout);
if (!v)
break;
v -= imageBase;
Fseek(fout, ofs, SEEK_SET);
Fwrite(&v, sizeof v, fout);
}
}
}
Also, there are articles about the format, e.g.
Peering Inside the PE: A Tour of the Win32 Portable Executable File Format.