(* BSD 2-Clause License Copyright (c) 2018-2020, Anton Krotov All rights reserved. *) MODULE PE32; IMPORT BIN, LISTS, UTILS, WR := WRITER, CHL := CHUNKLISTS; CONST SIZE_OF_DWORD = 4; SIZE_OF_WORD = 2; SIZE_OF_IMAGE_EXPORT_DIRECTORY = 40; IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; IMAGE_SIZEOF_SHORT_NAME = 8; SIZE_OF_IMAGE_FILE_HEADER* = 20; SIZE_OF_IMAGE_SECTION_HEADER* = 40; (* SectionHeader.Characteristics *) SHC_text = 060000020H; SHC_data = 040000040H; SHC_bss = 0C0000080H; SectionAlignment = 1000H; FileAlignment = 200H; TYPE WORD = WCHAR; DWORD = INTEGER; NAME* = ARRAY IMAGE_SIZEOF_SHORT_NAME OF CHAR; IMAGE_DATA_DIRECTORY = RECORD VirtualAddress: DWORD; Size: DWORD END; IMAGE_OPTIONAL_HEADER = RECORD Magic: WORD; MajorLinkerVersion: BYTE; MinorLinkerVersion: BYTE; SizeOfCode: DWORD; SizeOfInitializedData: DWORD; SizeOfUninitializedData: DWORD; AddressOfEntryPoint: DWORD; BaseOfCode: DWORD; BaseOfData: DWORD; ImageBase: DWORD; SectionAlignment: DWORD; FileAlignment: DWORD; MajorOperatingSystemVersion: WORD; MinorOperatingSystemVersion: WORD; MajorImageVersion: WORD; MinorImageVersion: WORD; MajorSubsystemVersion: WORD; MinorSubsystemVersion: WORD; Win32VersionValue: DWORD; SizeOfImage: DWORD; SizeOfHeaders: DWORD; CheckSum: DWORD; Subsystem: WORD; DllCharacteristics: WORD; SizeOfStackReserve: DWORD; SizeOfStackCommit: DWORD; SizeOfHeapReserve: DWORD; SizeOfHeapCommit: DWORD; LoaderFlags: DWORD; NumberOfRvaAndSizes: DWORD; DataDirectory: ARRAY IMAGE_NUMBEROF_DIRECTORY_ENTRIES OF IMAGE_DATA_DIRECTORY END; IMAGE_FILE_HEADER* = RECORD Machine*: WORD; NumberOfSections*: WORD; TimeDateStamp*: DWORD; PointerToSymbolTable*: DWORD; NumberOfSymbols*: DWORD; SizeOfOptionalHeader*: WORD; Characteristics*: WORD END; IMAGE_SECTION_HEADER* = RECORD Name*: NAME; VirtualSize*, VirtualAddress*, SizeOfRawData*, PointerToRawData*, PointerToRelocations*, PointerToLinenumbers*: DWORD; NumberOfRelocations*, NumberOfLinenumbers*: WORD; Characteristics*: DWORD END; IMAGE_EXPORT_DIRECTORY = RECORD Characteristics: DWORD; TimeDateStamp: DWORD; MajorVersion: WORD; MinorVersion: WORD; Name, Base, NumberOfFunctions, NumberOfNames, AddressOfFunctions, AddressOfNames, AddressOfNameOrdinals: DWORD END; VIRTUAL_ADDR* = RECORD Code*, Data*, Bss*, Import*: INTEGER END; VAR Signature: ARRAY 4 OF BYTE; FileHeader: IMAGE_FILE_HEADER; OptionalHeader: IMAGE_OPTIONAL_HEADER; msdos: ARRAY 128 OF BYTE; SectionHeaders: ARRAY 16 OF IMAGE_SECTION_HEADER; libcnt: INTEGER; SizeOfWord: INTEGER; PROCEDURE Export (program: BIN.PROGRAM; name: INTEGER; VAR ExportDir: IMAGE_EXPORT_DIRECTORY): INTEGER; BEGIN ExportDir.Characteristics := 0; ExportDir.TimeDateStamp := FileHeader.TimeDateStamp; ExportDir.MajorVersion := 0X; ExportDir.MinorVersion := 0X; ExportDir.Name := name; ExportDir.Base := 0; ExportDir.NumberOfFunctions := LISTS.count(program.exp_list); ExportDir.NumberOfNames := ExportDir.NumberOfFunctions; ExportDir.AddressOfFunctions := SIZE_OF_IMAGE_EXPORT_DIRECTORY; ExportDir.AddressOfNames := ExportDir.AddressOfFunctions + ExportDir.NumberOfFunctions * SIZE_OF_DWORD; ExportDir.AddressOfNameOrdinals := ExportDir.AddressOfNames + ExportDir.NumberOfFunctions * SIZE_OF_DWORD RETURN SIZE_OF_IMAGE_EXPORT_DIRECTORY + ExportDir.NumberOfFunctions * (2 * SIZE_OF_DWORD + SIZE_OF_WORD) END Export; PROCEDURE GetProcCount (lib: BIN.IMPRT): INTEGER; VAR imp: BIN.IMPRT; res: INTEGER; BEGIN res := 0; imp := lib.next(BIN.IMPRT); WHILE (imp # NIL) & (imp.label # 0) DO INC(res); imp := imp.next(BIN.IMPRT) END RETURN res END GetProcCount; PROCEDURE GetImportSize (imp_list: LISTS.LIST): INTEGER; VAR imp: BIN.IMPRT; proccnt: INTEGER; procoffs: INTEGER; OriginalCurrentThunk, CurrentThunk: INTEGER; BEGIN libcnt := 0; proccnt := 0; imp := imp_list.first(BIN.IMPRT); WHILE imp # NIL DO IF imp.label = 0 THEN INC(libcnt) ELSE INC(proccnt) END; imp := imp.next(BIN.IMPRT) END; procoffs := 0; imp := imp_list.first(BIN.IMPRT); WHILE imp # NIL DO IF imp.label = 0 THEN imp.OriginalFirstThunk := procoffs; imp.FirstThunk := procoffs + (GetProcCount(imp) + 1); OriginalCurrentThunk := imp.OriginalFirstThunk; CurrentThunk := imp.FirstThunk; INC(procoffs, (GetProcCount(imp) + 1) * 2) ELSE imp.OriginalFirstThunk := OriginalCurrentThunk; imp.FirstThunk := CurrentThunk; INC(OriginalCurrentThunk); INC(CurrentThunk) END; imp := imp.next(BIN.IMPRT) END RETURN (libcnt + 1) * 5 * SIZE_OF_DWORD + (proccnt + libcnt) * 2 * SizeOfWord END GetImportSize; PROCEDURE fixup* (program: BIN.PROGRAM; Address: VIRTUAL_ADDR; amd64: BOOLEAN); VAR reloc: BIN.RELOC; iproc: BIN.IMPRT; code: CHL.BYTELIST; L, delta, delta0, AdrImp, offset: INTEGER; BEGIN AdrImp := Address.Import + (libcnt + 1) * 5 * SIZE_OF_DWORD; code := program.code; reloc := program.rel_list.first(BIN.RELOC); delta0 := 3 - 7 * ORD(amd64) - Address.Code; WHILE reloc # NIL DO offset := reloc.offset; L := BIN.get32le(code, offset); delta := delta0 - offset; CASE reloc.opcode OF |BIN.PICDATA: INC(delta, L + Address.Data) |BIN.PICCODE: INC(delta, BIN.GetLabel(program, L) + Address.Code) |BIN.PICBSS: INC(delta, L + Address.Bss) |BIN.PICIMP: iproc := BIN.GetIProc(program, L); INC(delta, iproc.FirstThunk * SizeOfWord + AdrImp) END; BIN.put32le(code, offset, delta); reloc := reloc.next(BIN.RELOC) END END fixup; PROCEDURE WriteWord (w: WORD); BEGIN WR.Write16LE(ORD(w)) END WriteWord; PROCEDURE WriteName* (name: NAME); VAR i, nameLen: INTEGER; BEGIN nameLen := LENGTH(name); FOR i := 0 TO nameLen - 1 DO WR.WriteByte(ORD(name[i])) END; i := LEN(name) - nameLen; WHILE i > 0 DO WR.WriteByte(0); DEC(i) END END WriteName; PROCEDURE WriteSectionHeader* (h: IMAGE_SECTION_HEADER); VAR i, nameLen: INTEGER; BEGIN nameLen := LENGTH(h.Name); FOR i := 0 TO nameLen - 1 DO WR.WriteByte(ORD(h.Name[i])) END; i := LEN(h.Name) - nameLen; WHILE i > 0 DO WR.WriteByte(0); DEC(i) END; WR.Write32LE(h.VirtualSize); WR.Write32LE(h.VirtualAddress); WR.Write32LE(h.SizeOfRawData); WR.Write32LE(h.PointerToRawData); WR.Write32LE(h.PointerToRelocations); WR.Write32LE(h.PointerToLinenumbers); WriteWord(h.NumberOfRelocations); WriteWord(h.NumberOfLinenumbers); WR.Write32LE(h.Characteristics) END WriteSectionHeader; PROCEDURE WriteFileHeader* (h: IMAGE_FILE_HEADER); BEGIN WriteWord(h.Machine); WriteWord(h.NumberOfSections); WR.Write32LE(h.TimeDateStamp); WR.Write32LE(h.PointerToSymbolTable); WR.Write32LE(h.NumberOfSymbols); WriteWord(h.SizeOfOptionalHeader); WriteWord(h.Characteristics) END WriteFileHeader; PROCEDURE write* (program: BIN.PROGRAM; FileName: ARRAY OF CHAR; console, dll, amd64: BOOLEAN); VAR i, n, temp: INTEGER; Size: RECORD Code, Data, Bss, Import, Reloc, Export: INTEGER END; BaseAddress: INTEGER; Address: VIRTUAL_ADDR; _import: BIN.IMPRT; ImportTable: CHL.INTLIST; ExportDir: IMAGE_EXPORT_DIRECTORY; export: BIN.EXPRT; PROCEDURE WriteExportDir (e: IMAGE_EXPORT_DIRECTORY); BEGIN WR.Write32LE(e.Characteristics); WR.Write32LE(e.TimeDateStamp); WriteWord(e.MajorVersion); WriteWord(e.MinorVersion); WR.Write32LE(e.Name); WR.Write32LE(e.Base); WR.Write32LE(e.NumberOfFunctions); WR.Write32LE(e.NumberOfNames); WR.Write32LE(e.AddressOfFunctions); WR.Write32LE(e.AddressOfNames); WR.Write32LE(e.AddressOfNameOrdinals) END WriteExportDir; PROCEDURE WriteOptHeader (h: IMAGE_OPTIONAL_HEADER; amd64: BOOLEAN); VAR i: INTEGER; BEGIN WriteWord(h.Magic); WR.WriteByte(h.MajorLinkerVersion); WR.WriteByte(h.MinorLinkerVersion); WR.Write32LE(h.SizeOfCode); WR.Write32LE(h.SizeOfInitializedData); WR.Write32LE(h.SizeOfUninitializedData); WR.Write32LE(h.AddressOfEntryPoint); WR.Write32LE(h.BaseOfCode); IF amd64 THEN WR.Write64LE(h.ImageBase) ELSE WR.Write32LE(h.BaseOfData); WR.Write32LE(h.ImageBase) END; WR.Write32LE(h.SectionAlignment); WR.Write32LE(h.FileAlignment); WriteWord(h.MajorOperatingSystemVersion); WriteWord(h.MinorOperatingSystemVersion); WriteWord(h.MajorImageVersion); WriteWord(h.MinorImageVersion); WriteWord(h.MajorSubsystemVersion); WriteWord(h.MinorSubsystemVersion); WR.Write32LE(h.Win32VersionValue); WR.Write32LE(h.SizeOfImage); WR.Write32LE(h.SizeOfHeaders); WR.Write32LE(h.CheckSum); WriteWord(h.Subsystem); WriteWord(h.DllCharacteristics); IF amd64 THEN WR.Write64LE(h.SizeOfStackReserve); WR.Write64LE(h.SizeOfStackCommit); WR.Write64LE(h.SizeOfHeapReserve); WR.Write64LE(h.SizeOfHeapCommit) ELSE WR.Write32LE(h.SizeOfStackReserve); WR.Write32LE(h.SizeOfStackCommit); WR.Write32LE(h.SizeOfHeapReserve); WR.Write32LE(h.SizeOfHeapCommit) END; WR.Write32LE(h.LoaderFlags); WR.Write32LE(h.NumberOfRvaAndSizes); FOR i := 0 TO LEN(h.DataDirectory) - 1 DO WR.Write32LE(h.DataDirectory[i].VirtualAddress); WR.Write32LE(h.DataDirectory[i].Size) END END WriteOptHeader; PROCEDURE InitSection (VAR section: IMAGE_SECTION_HEADER; Name: NAME; VirtualSize: INTEGER; Characteristics: DWORD); BEGIN section.Name := Name; section.VirtualSize := VirtualSize; section.SizeOfRawData := WR.align(VirtualSize, FileAlignment); section.PointerToRelocations := 0; section.PointerToLinenumbers := 0; section.NumberOfRelocations := 0X; section.NumberOfLinenumbers := 0X; section.Characteristics := Characteristics END InitSection; BEGIN SizeOfWord := SIZE_OF_DWORD * (ORD(amd64) + 1); Size.Code := CHL.Length(program.code); Size.Data := CHL.Length(program.data); Size.Bss := program.bss; IF dll THEN BaseAddress := 10000000H ELSE BaseAddress := 400000H END; Signature[0] := 50H; Signature[1] := 45H; Signature[2] := 0; Signature[3] := 0; IF amd64 THEN FileHeader.Machine := 08664X ELSE FileHeader.Machine := 014CX END; FileHeader.NumberOfSections := WCHR(4 + ORD(dll)); FileHeader.TimeDateStamp := UTILS.UnixTime(); FileHeader.PointerToSymbolTable := 0H; FileHeader.NumberOfSymbols := 0H; FileHeader.SizeOfOptionalHeader := WCHR(0E0H + 10H * ORD(amd64)); FileHeader.Characteristics := WCHR(010EH + (20H - 100H) * ORD(amd64) + 2000H * ORD(dll)); OptionalHeader.Magic := WCHR(010BH + 100H * ORD(amd64)); OptionalHeader.MajorLinkerVersion := UTILS.vMajor; OptionalHeader.MinorLinkerVersion := UTILS.vMinor; OptionalHeader.SizeOfCode := WR.align(Size.Code, FileAlignment); OptionalHeader.SizeOfInitializedData := 0; OptionalHeader.SizeOfUninitializedData := 0; OptionalHeader.AddressOfEntryPoint := SectionAlignment; OptionalHeader.BaseOfCode := SectionAlignment; OptionalHeader.BaseOfData := OptionalHeader.BaseOfCode + WR.align(Size.Code, SectionAlignment); OptionalHeader.ImageBase := BaseAddress; OptionalHeader.SectionAlignment := SectionAlignment; OptionalHeader.FileAlignment := FileAlignment; OptionalHeader.MajorOperatingSystemVersion := 1X; OptionalHeader.MinorOperatingSystemVersion := 0X; OptionalHeader.MajorImageVersion := 0X; OptionalHeader.MinorImageVersion := 0X; OptionalHeader.MajorSubsystemVersion := 4X; OptionalHeader.MinorSubsystemVersion := 0X; OptionalHeader.Win32VersionValue := 0H; OptionalHeader.SizeOfImage := SectionAlignment; OptionalHeader.SizeOfHeaders := 400H; OptionalHeader.CheckSum := 0; OptionalHeader.Subsystem := WCHR((2 + ORD(console)) * ORD(~dll)); OptionalHeader.DllCharacteristics := 0040X; OptionalHeader.SizeOfStackReserve := 100000H; OptionalHeader.SizeOfStackCommit := 10000H; OptionalHeader.SizeOfHeapReserve := 100000H; OptionalHeader.SizeOfHeapCommit := 10000H; OptionalHeader.LoaderFlags := 0; OptionalHeader.NumberOfRvaAndSizes := IMAGE_NUMBEROF_DIRECTORY_ENTRIES; FOR i := 0 TO IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1 DO OptionalHeader.DataDirectory[i].VirtualAddress := 0; OptionalHeader.DataDirectory[i].Size := 0 END; InitSection(SectionHeaders[0], ".text", Size.Code, SHC_text); SectionHeaders[0].VirtualAddress := SectionAlignment; SectionHeaders[0].PointerToRawData := OptionalHeader.SizeOfHeaders; InitSection(SectionHeaders[1], ".data", Size.Data, SHC_data); SectionHeaders[1].VirtualAddress := WR.align(SectionHeaders[0].VirtualAddress + SectionHeaders[0].VirtualSize, SectionAlignment); SectionHeaders[1].PointerToRawData := SectionHeaders[0].PointerToRawData + SectionHeaders[0].SizeOfRawData; InitSection(SectionHeaders[2], ".bss", Size.Bss, SHC_bss); SectionHeaders[2].VirtualAddress := WR.align(SectionHeaders[1].VirtualAddress + SectionHeaders[1].VirtualSize, SectionAlignment); SectionHeaders[2].PointerToRawData := SectionHeaders[1].PointerToRawData + SectionHeaders[1].SizeOfRawData; SectionHeaders[2].SizeOfRawData := 0; Size.Import := GetImportSize(program.imp_list); InitSection(SectionHeaders[3], ".idata", Size.Import + CHL.Length(program._import), SHC_data); SectionHeaders[3].VirtualAddress := WR.align(SectionHeaders[2].VirtualAddress + SectionHeaders[2].VirtualSize, SectionAlignment); SectionHeaders[3].PointerToRawData := SectionHeaders[2].PointerToRawData + SectionHeaders[2].SizeOfRawData; Address.Code := SectionHeaders[0].VirtualAddress + OptionalHeader.ImageBase; Address.Data := SectionHeaders[1].VirtualAddress + OptionalHeader.ImageBase; Address.Bss := SectionHeaders[2].VirtualAddress + OptionalHeader.ImageBase; Address.Import := SectionHeaders[3].VirtualAddress + OptionalHeader.ImageBase; fixup(program, Address, amd64); IF dll THEN Size.Export := Export(program, SectionHeaders[1].VirtualAddress + program.modname, ExportDir); InitSection(SectionHeaders[4], ".edata", Size.Export + CHL.Length(program.export), SHC_data); SectionHeaders[4].VirtualAddress := WR.align(SectionHeaders[3].VirtualAddress + SectionHeaders[3].VirtualSize, SectionAlignment); SectionHeaders[4].PointerToRawData := SectionHeaders[3].PointerToRawData + SectionHeaders[3].SizeOfRawData; OptionalHeader.DataDirectory[0].VirtualAddress := SectionHeaders[4].VirtualAddress; OptionalHeader.DataDirectory[0].Size := SectionHeaders[4].VirtualSize END; OptionalHeader.DataDirectory[1].VirtualAddress := SectionHeaders[3].VirtualAddress; OptionalHeader.DataDirectory[1].Size := SectionHeaders[3].VirtualSize; FOR i := 1 TO ORD(FileHeader.NumberOfSections) - 1 DO INC(OptionalHeader.SizeOfInitializedData, SectionHeaders[i].SizeOfRawData) END; OptionalHeader.SizeOfUninitializedData := WR.align(SectionHeaders[2].VirtualSize, FileAlignment); FOR i := 0 TO ORD(FileHeader.NumberOfSections) - 1 DO INC(OptionalHeader.SizeOfImage, WR.align(SectionHeaders[i].VirtualSize, SectionAlignment)) END; n := 0; BIN.InitArray(msdos, n, "4D5A80000100000004001000FFFF000040010000000000004000000000000000"); BIN.InitArray(msdos, n, "0000000000000000000000000000000000000000000000000000000080000000"); BIN.InitArray(msdos, n, "0E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F"); BIN.InitArray(msdos, n, "742062652072756E20696E20444F53206D6F64652E0D0A240000000000000000"); WR.Create(FileName); WR.Write(msdos, LEN(msdos)); WR.Write(Signature, LEN(Signature)); WriteFileHeader(FileHeader); WriteOptHeader(OptionalHeader, amd64); FOR i := 0 TO ORD(FileHeader.NumberOfSections) - 1 DO WriteSectionHeader(SectionHeaders[i]) END; WR.Padding(FileAlignment); CHL.WriteToFile(program.code); WR.Padding(FileAlignment); CHL.WriteToFile(program.data); WR.Padding(FileAlignment); n := (libcnt + 1) * 5; ImportTable := CHL.CreateIntList(); FOR i := 0 TO (Size.Import - n * SIZE_OF_DWORD) DIV SizeOfWord + n - 1 DO CHL.PushInt(ImportTable, 0) END; i := 0; _import := program.imp_list.first(BIN.IMPRT); WHILE _import # NIL DO IF _import.label = 0 THEN CHL.SetInt(ImportTable, i + 0, _import.OriginalFirstThunk * SizeOfWord + SectionHeaders[3].VirtualAddress + n * SIZE_OF_DWORD); CHL.SetInt(ImportTable, i + 1, 0); CHL.SetInt(ImportTable, i + 2, 0); CHL.SetInt(ImportTable, i + 3, _import.nameoffs + Size.Import + SectionHeaders[3].VirtualAddress); CHL.SetInt(ImportTable, i + 4, _import.FirstThunk * SizeOfWord + SectionHeaders[3].VirtualAddress + n * SIZE_OF_DWORD); INC(i, 5) END; _import := _import.next(BIN.IMPRT) END; CHL.SetInt(ImportTable, i + 0, 0); CHL.SetInt(ImportTable, i + 1, 0); CHL.SetInt(ImportTable, i + 2, 0); CHL.SetInt(ImportTable, i + 3, 0); CHL.SetInt(ImportTable, i + 4, 0); _import := program.imp_list.first(BIN.IMPRT); WHILE _import # NIL DO IF _import.label # 0 THEN temp := _import.nameoffs + Size.Import + SectionHeaders[3].VirtualAddress - 2; CHL.SetInt(ImportTable, _import.OriginalFirstThunk + n, temp); CHL.SetInt(ImportTable, _import.FirstThunk + n, temp) END; _import := _import.next(BIN.IMPRT) END; FOR i := 0 TO n - 1 DO WR.Write32LE(CHL.GetInt(ImportTable, i)) END; FOR i := n TO CHL.Length(ImportTable) - 1 DO IF amd64 THEN WR.Write64LE(CHL.GetInt(ImportTable, i)) ELSE WR.Write32LE(CHL.GetInt(ImportTable, i)) END END; CHL.WriteToFile(program._import); WR.Padding(FileAlignment); IF dll THEN INC(ExportDir.AddressOfFunctions, SectionHeaders[4].VirtualAddress); INC(ExportDir.AddressOfNames, SectionHeaders[4].VirtualAddress); INC(ExportDir.AddressOfNameOrdinals, SectionHeaders[4].VirtualAddress); WriteExportDir(ExportDir); export := program.exp_list.first(BIN.EXPRT); WHILE export # NIL DO WR.Write32LE(export.label + SectionHeaders[0].VirtualAddress); export := export.next(BIN.EXPRT) END; export := program.exp_list.first(BIN.EXPRT); WHILE export # NIL DO WR.Write32LE(export.nameoffs + Size.Export + SectionHeaders[4].VirtualAddress); export := export.next(BIN.EXPRT) END; FOR i := 0 TO ExportDir.NumberOfFunctions - 1 DO WriteWord(WCHR(i)) END; CHL.WriteToFile(program.export); WR.Padding(FileAlignment) END; WR.Close END write; END PE32.