Воробьёвы

(-:

It was shown in the article «Win32 Appy by Hand» how to «assemble» the simplest Win32 application with the only MessageBox using debug.exe. This time let’s create the simplest dll in a similar manner; this is the continuation and further elaboration of the previous work and the necessary foundation for my future articles, because I am going to describe how to create COM components in binary and it is quite impossible to manage without dll’s there.

I hope that a reader has thoroughly studied the last topic and now can easily create PE headers, import tables and code and data sections on the fly :) It is supposed, too, that because of Svin’s work followed by epidemic enthusiasm with Intel’s manuals everybody will be able to understand hex’es or even binary codes. So we will concentrate on more useful things – namely, what is the difference between dll’s and usual exe-files. There are two main points here: first, exported functions appear hence export tables, too, and second, we will have to investigate relocations because it is not guaranteed that our dll will be loaded to the preferred base address of 10000000h :(

This time let’s create 4 sections in our PE-file: for code, data, import with export and relocations. We will name these sections .code, .data, .rdata and .reloc respectively. The sections will be located in memory at offsets 1000h, 2000h, 3000h and 4000h, and in a file at offsets 200h, 400h, 600h and 800h correspondingly. The content will be the same as in the last article, i.e. we will export the only function and the only work it does will be dispalying of MessageBox. Thus there are only two strings of data; create the file data.txt:

n data.bin
r cx
200
f 0 l 200 0
e 0 "Dll"
e 10 "Exported function "
m 0 l 200 100
w
q

I hope it is clear that these are commands for debug.exe written in the file. This time we decided to automate the work somewhat :)

Using debug.exe is not mandatory though. If you wish you can use any hex-editor e.g. HIEW or QView or something else. Then you can build dll as a single file not divided into separate auxiliary files as in our case. In any case the .data section must look like this in memory:

2000: 44 6C 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 | Dll.............
2010: 45 78 70 6F 72 74 65 64 20 66 75 6E 63 74 69 6F | Exported functio
2020: 6E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | n...............

So the title string is located in the beginning of the data section at offset 2000h and the memory address will be at offset 10002000h correspondingly. The values for the message string will be 2010h and 10002010h accordingly. Do not forget that the .data section begins in the dll file at offset 400h.

And now the code! We will have to import MessageBoxA from User32.dll again; this time IAT will be located in its own section – .rdata – in the very beginning as usual, i.e. at offset 3000h; the memory address will be 10003000h. There are no other imported functions there. The .code section will look like this:

1000: 6A 00 68 00 20 00 10 68 10 20 00 10 6A 00 FF 15
1010: 00 30 00 10 C3 00 00 00 00 00 00 00 00 00 00 00

To use debug.exe fill in the file code.txt:

n code.bin
r cx
200
f 0 l 200 0
a 0
; parameters of MessageBox
db 6a 0
db 68  0 20 0 10
db 68 10 20 0 10
db 6a 0
; call MessageBox
db ff 15 0 30 0 10
; return
db c3

m 0 l 200 100
w
q

Do not remove the empty string after ‘db c3’! Otherwise debug will complain and you will get nonsense instead of a cool binary block :)

So we did it – the half of the work is already done! It’s time to proceed to the new subject now. Let’s look at our code and mark unsafe places:

1000: 6a 00 68 00 | 20 00 10 68 | 10 20 00 10 | 6a 00 ff 15
1010: 00 30 00 10 | c3

I marked out in bold the memory addresses that are parts of instructions. The system easily can load our dll in a quite different location – then we will get our addresses broken and the code will push garbage in the stack and send it to unknown place instead of our imported function. It is the three 32-bit values that must be fixed up; we must specify them in fix-up table for that purpose.

Each fixup is represented with only two bytes (16-bit values), the 4 high bits being the fixup type. The fixup type nearly always is 3 for Win32, meaning the system must fix up the 32-bit address at the given offset. The offset is represented with the remaining 12 bits of the fixup. As you can see this is enough for offsets within the single memory page (4 Kb) only. So the fixups for the given memory page are combined in fixup block. The first 4 bytes at the beginning of the fixup block contain the offset of the memory page relative to the base load address and the next 4 bytes contain the size of the fixup block (including the first 8 bytes). The remaining of the fixup block is a collection of fixups for the memory page (see picture).

When loading a so called delta is calculated as the difference between the preferred base address (indicated in the PE-header), and the base where the image is actually loaded. If the dll is loaded at its preferred base, the delta would be 0, and thus the fixups would not have to be applied. Otherwise the delta is added to each 32-bit value that has the corresponding fixup. And that’s all.

In our case there must be 3 fixups; their offsets are 3, 8 and 10h relative to the page beginning. Including the fixup type (3) we will get values 3003h, 3008h, and 3010h. A fixup block must be aligned on DWORD boundary, so we add one more «empty» fixup at the end (it has even its own type – of course, 0). Finally we get: page RVA is the offset of the relocated page, that is, the code page – 1000h; the block size is 8 bytes (block header) + 3 fixups, 2 bytes each + 1 «empty» fuxup (2 bytes) – 10h in sum. The relocation section is ready! It looks like this:

4000: 00 10 00 00 10 00 00 00 03 30 08 30 10 30 00 00

Fill in the file reloc.txt:

n reloc.bin
r cx
200
f 0 l 200 0
a 0
; Page RVA
dw 1000 0 
; Block size
dw 10 0 
; fixups
dw 3003
dw 3008
dw 3010

m 0 l 200 100
w
q

Now only export remains. Since my readers are great specialists on import no word will be said about it more :) If anybody does not understand how to deal with import refer to my previous article.

The main table combining all the other information is the export directory table. It has the following fields:

Offset Size Description
0 4 Reserved (0)
4 4 Time and date the export data was created.
8 2 Major version number.
0Ah 2 Minor version number.
0Ch 4 Offset (RVA) of the ASCII string containing the name of the DLL.
10h 4 Starting ordinal number for exports.
14h 4 Number of entries in the export address table.
18h 4 Number of entries in the name pointer table.
1Ch 4 Offset (RVA) of the export address table.
20h 4 Offset (RVA) of the export name pointer table.
24h 4 Offset (RVA) of the ordinal table.

See the following picture for interrelation of various tables:

As you can see export data contains mainly various offsets and indices. When exporting using ordinals only we need only two tables: the main export directory table and the export address table. The latter is a simple array of 32-bit offsets of the exported symbols, that is the implementations of the functions we export relative to the base load address. Thus each exported function has its own index to this array; adding the ordinal base to that index we get the function’s ordinal.

If functions are exported by names, there must be three additional tables: the ordinal table, the export name pointer table and the export name table. It is clear with the export name table: it contains the names to be exported. The number of names can be less than the number of entries in the export address table. The export name pointer table contains offsets (RVAs) into the export name table. In fact the export name table is simply a list of strings containing names for exported functions; and the export name pointer table contains offsets to those strings. But the pointers to the strings must be ordered lexically to allow binary searches between function names. Note: it is the name pointers that must be ordered not the names (that is, strings) itself. The export ordinal table is another array, but it contains 16-bit (not 32-bit!) indices into the export address table. The ordinal table and the name pointer table are joint in that the index of each of these arrays corresponds to the same function. So if we have a function name (a string in the name table) and a pointer to that name in the name pointer table, the location of that pointer in the name pointer table is determined by the lexical order of the name. And we will find the index into the export address table at the same relative position (that is with the same index as in the name pointer table) in the ordinal table. After we get index into the export address table we can get the function address itself in the address table.

Well, it is time to proceed to the last section – ‘.rdata’. First we will try on a layout (see picture)

Here is the .rdata section itself:

3000: 20 30 00 00 00 00 00 00 20 30 00 00 00 00 00 00    0...... 0......
3010: 55 73 65 72 33 32 2E 64 6C 6C 00 00 00 00 00 00   User32.dll......
3020: 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00   ..MessageBoxA...
3030: 08 30 00 00 00 00 00 00 00 00 00 00 10 30 00 00   .0...........0..
3040: 00 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00   .0..............
3050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
3060: 00 00 00 00 00 00 00 00 00 00 00 00 94 30 00 00   .............0..
3070: 01 00 00 00 01 00 00 00 01 00 00 00 88 30 00 00   .............0..
3080: 90 30 00 00 8C 30 00 00 00 10 00 00 00 00 00 00   .0...0..........
3090: A0 30 00 00 44 6C 6C 2E 64 6C 6C 00 00 00 00 00   .0..Dll.dll.....
30A0: 46 75 6E 63 74 69 6F 6E 31 00 00 00 00 00 00 00   Function1.......

And now the file rdata.txt:

n rdata.bin
r cx
200
f 0 l 200 0
a 0
; Import
; IAT
dw 3020 0 0 0
; Lookup table
dw 3020 0 0 0
; Dll name
db "User32.dll" 0

a 20
; Imported function with hint
db 0 0 "MessageBoxA" 0

a 30
; Import directory table:
; lookup table RVA
dw 3008 0
; 2 empty fields
dw 0 0 0 0
; Dll name RVA
dw 3010 0
; RVA of IAT
dw 3000 0

a 60
; Export
; Export directory table:
; 3 empty fields
dw 0 0 0 0 0 0
; Dll name RVA
dw 3094 0
; Ordinal base
dw 1 0
; Address table entries
dw 1 0
; Number of name pointers
dw 1 0
; Export address table RVA 
dw 3088 0
; Name pointer RVA
dw 3090 0
; Ordinal table RVA
dw 308C 0
; Export address table
dw 1000 0
; Ordinal table
dw 0 0
; Name pointer table
dw 30a0 0
; Dll name
db "Dll.dll" 0

a a0
; Exported function name
db "Function1" 0

m 0 l 200 100
w
q

Do not forget to keep the empty strings! Now we will have to only slightly modify our PE-header (compared to our first article). Here it is in full:

0000: 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00
0040: 50 45 00 00 4C 01 04 00 00 00 00 00 00 00 00 00
0050: 00 00 00 00 E0 00 0E 21 0B 01 00 00 00 00 00 00
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0070: 00 00 00 00 00 00 00 10 00 10 00 00 00 02 00 00
0080: 04 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
0090: 00 50 00 00 00 02 00 00 00 00 00 00 02 00 00 00
00A0: 00 00 10 00 00 10 00 00 00 00 10 00 00 10 00 00
00B0: 00 00 00 00 10 00 00 00 60 30 00 00 4A 00 00 00
00C0: 30 30 00 00 28 00 00 00 00 00 00 00 00 00 00 00
00D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00E0: 00 40 00 00 10 00 00 00 00 00 00 00 00 00 00 00
00F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0130: 00 00 00 00 00 00 00 00 2E 63 6F 64 65 00 00 00
0140: 00 02 00 00 00 10 00 00 00 02 00 00 00 02 00 00
0150: 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60
0160: 2E 64 61 74 61 00 00 00 00 02 00 00 00 20 00 00
0170: 00 02 00 00 00 04 00 00 00 00 00 00 00 00 00 00
0180: 00 00 00 00 40 00 00 C0 2E 72 64 61 74 61 00 00
0190: 00 02 00 00 00 30 00 00 00 02 00 00 00 06 00 00
01A0: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40
01B0: 2E 72 65 6C 6F 63 00 00 00 02 00 00 00 40 00 00
01C0: 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00
01D0: 00 00 00 00 40 00 00 42 00 00 00 00 00 00 00 00
01E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

And here is how to create it using debug.exe (file header.txt):

n Header.bin
r cx
200
f 0 l 200 0
e 0 'MZ'
e 3C 40
e 40 'PE'
e 44 4C 01
a 46
; Number of sections 
db 04 

a 54
; Size of the optional header
db e0 00
; Flags: set dll flag and flag that permits to 
; load image at address different from the preferred
; base address indicated in PE-header
db 0E 21
; Magic number
db 0B 01

a 74
; Image base
db 00 00 00 10
; Section alignment
db 00 10 00 00 
; File alignment
db 00 02 00 00
; Major Windows version
db 04

a 88
; Major subsystem version
db 04

a 90
; Size of image
db 00 50 00 00
; Size of headers
db 00 02

a 9C
; Subsystem
db 02 00 

a A0
; Size of stack to reserve
db 00 00 10 00
; Size of stack to commit
db 00 10 00 00
; Size of heap to reserve
db 00 00 10 00
; Size of heap to commit
db 00 10 00 00

a B4
; Number of data directory entries
db 10 00 00 00
;
; Data directories:
; Export directory table RVA 
dw 3060 0
; Export directory table size
dw 4a 0
; Import directory table RVA
dw 3030 0
; Import directory table size
dw 28 0
; skip 24 bytes (3 entries)
dw 0 0 0 0 0 0 0 0 0 0 0 0
; Base relocation table RVA
dw 4000 0
; Base relocation table size
dw 10

a 138
; Start of section table
;
; name of the first section
db '.code' 0 0 0
; size of the section in memory
dw 200 0
; section RVA
dw 1000 0
; size of the section on disk
dw 200 0
; offset of section data in file
dw 200 0
; skip 12 bytes
dw 0 0 0 0 0 0
; first section flags
db 20 00 00 60
; second section
db '.data' 0 0 0
dw 200 0
dw 2000 0
dw 200 0
dw 400 0
dw 0 0 0 0 0 0
db 40 0 0 c0
; third section
db '.rdata' 0 0
dw 200 0
dw 3000 0
dw 200 0
dw 600 0
dw 0 0 0 0 0 0
db 40 0 0 40
; fourth section
db '.reloc' 0 0
dw 200 0
dw 4000 0
dw 200 0
dw 800 0
dw 0 0 0 0 0 0
db 40 0 0 42

m 0 l 200 100
w
q

That’s all! So we have five fragments (4 sections and 1 header) and 5 corresponding files if you used debug.exe. The next table summarizes our data:

RVA in
memory
Section File Offset
in file
0 (Header) header.bin 0
1000 .code code.bin 200
2000 .data data.bin 400
3000 .rdata rdata.bin 600
4000 .reloc reloc.bin 800

If you have used hex-editor, your binary file is ready. If you have used debug.exe you will have to combine the five binary files into one dll. Put all together in the file make.bat:

@echo off
debug < header.txt > report.lst
debug < code.txt >> report.lst
debug < data.txt >> report.lst
debug < rdata.txt >> report.lst
debug < reloc.txt >> report.lst
copy /b header.bin+code.bin+data.bin+rdata.bin+reloc.bin dll.dll

Launch this file and – hurrah! – we get out dll. Necessarily look at created report file – report.lst – to check for errors that may be reported by debug. Carefully look for errors. You perfectly know: cut&paste technique does not protect against the most stupid errors!

Well; but we will need some exe-application to test our dll, won’t we? I have absolutely no doubt that now your level will permit you to easily create such a test program using debug.exe :)

Well, well... I see some confused faces. You have famously worked for a while today (if you have read this up to here) so I decided to grant you a «lazy» test application in MASM32 as a bonus. Here is it:

.386
.model flat,stdcall
option casemap:none

include masm32includewindows.inc
include masm32includekernel32.inc
include masm32includeuser32.inc
includelib masm32libkernel32.lib
includelib masm32libuser32.lib

.data

hM1	dword 0
hM2	dword 0
app	db "Test dll",0
dll	db "dll.dll",0
dll2	db "dll2.dll",0
fname	db "Function1",0
err1	db "LoadLibrary (dll1) failed",0
err1_1	db "LoadLibrary (dll2) failed",0
err2	db "GetProcAddress (first) failed",0
err2_1	db "GetProcAddress (second) failed",0

.code
start:
invoke LoadLibrary,offset dll
.if eax==0
	invoke MessageBox,0,offset err1,offset app,MB_ICONERROR
	ret
.endif
mov hM1,eax
invoke LoadLibrary,offset dll2
.if eax==0
	invoke MessageBox,0,offset err1_1,offset app,MB_ICONERROR
	ret
.endif
mov hM2,eax
invoke GetProcAddress,hM1,offset fname
.if eax==0
	invoke MessageBox,0,offset err2,offset app,MB_ICONERROR
	ret
.endif
call eax
invoke GetProcAddress,hM2,offset fname
.if eax==0
	invoke MessageBox,0,offset err2_1,offset app,MB_ICONERROR
	ret
.endif
call eax
invoke FreeLibrary,hM1
invoke FreeLibrary,hM2
ret
end start

Copy created dll.dll with new name ‘dll2.dll’ in the same folder. The point is that we need to check for correctness of our fixups and for that system can work with them. We need at least two dll’s with the same preferred base load address for that purpose. The most lazy approach one could imagine is to use the second renamed instance of the same image. But you can experiment with them of course, at least to change text strings for output messages and create another dll with different module and function names.

See you later!

  [C] Roustem