├── AllUnits.dpr ├── AllUnits.dproj ├── Examples ├── 00 Sample │ ├── SampleLib.dpr │ └── SampleLib.dproj ├── 01 Exports │ ├── ExportsAdd.dpr │ ├── ExportsAdd.dproj │ ├── ExportsRead.dpr │ └── ExportsRead.dproj ├── 02 Imports │ ├── ImportsAdd.dpr │ ├── ImportsAdd.dproj │ ├── ImportsDelayed.dpr │ ├── ImportsDelayed.dproj │ ├── ImportsEnumeration.dpr │ └── ImportsEnumeration.dproj ├── 03 Resources │ ├── ResourceBuild.dpr │ ├── ResourceBuild.dproj │ ├── ResourceExtract.dpr │ ├── ResourceExtract.dproj │ ├── ResourceStats.dpr │ ├── ResourceStats.dproj │ ├── ResourceVersionInfo.dpr │ └── ResourceVersionInfo.dproj ├── 04 Module Loader │ ├── ModuleLoader.dpr │ └── ModuleLoader.dproj ├── 05 FillMem │ ├── FillMem.dpr │ └── FillMem.dproj ├── 06 Relocs │ ├── BuildRelocs.dpr │ └── BuildRelocs.dproj ├── 07 Directory Save and Load │ ├── directory_save_and_load.dpr │ └── directory_save_and_load.dproj ├── 08 Build x86 executable graph │ ├── build_x86_graph.dpr │ └── build_x86_graph.dproj ├── 09 PEID Signatures │ ├── PeidSignatures.dpr │ └── PeidSignatures.dproj ├── 10 Load From Process │ ├── ProcessTest.dpr │ ├── ProcessTest.dproj │ └── readme.txt └── Examples.groupproj ├── LICENSE ├── NullStream.pas ├── PE.Build.Common.pas ├── PE.Build.Export.pas ├── PE.Build.Import.pas ├── PE.Build.Relocs.pas ├── PE.Build.Resource.pas ├── PE.Build.pas ├── PE.COFF.Types.pas ├── PE.COFF.pas ├── PE.Common.pas ├── PE.DataDirectories.pas ├── PE.ExecutableLoader.pas ├── PE.ExportSym.pas ├── PE.FileHeaderToStr.pas ├── PE.Headers.pas ├── PE.ID.pas ├── PE.Image.Defaults.pas ├── PE.Image.Saving.pas ├── PE.Image.pas ├── PE.Image.x86.pas ├── PE.Imports.Func.pas ├── PE.Imports.Lib.pas ├── PE.Imports.pas ├── PE.MemoryStream.pas ├── PE.Msg.pas ├── PE.Parser.Export.pas ├── PE.Parser.Headers.pas ├── PE.Parser.Import.pas ├── PE.Parser.ImportDelayed.pas ├── PE.Parser.PData.pas ├── PE.Parser.Relocs.pas ├── PE.Parser.Resources.pas ├── PE.Parser.TLS.pas ├── PE.ParserCallbacks.pas ├── PE.ProcessModuleStream.pas ├── PE.RTTI.pas ├── PE.Resources.Extract.pas ├── PE.Resources.VersionInfo.pas ├── PE.Resources.Windows.Bitmap.pas ├── PE.Resources.Windows.Strings.pas ├── PE.Resources.Windows.pas ├── PE.Resources.pas ├── PE.Search.pas ├── PE.Section.pas ├── PE.Sections.pas ├── PE.TLS.pas ├── PE.Types.DOSHeader.pas ├── PE.Types.Directories.pas ├── PE.Types.Export.pas ├── PE.Types.FileHeader.pas ├── PE.Types.Imports.pas ├── PE.Types.ImportsDelayed.pas ├── PE.Types.NTHeaders.pas ├── PE.Types.OptionalHeader.pas ├── PE.Types.Relocations.inc ├── PE.Types.Relocations.pas ├── PE.Types.Resources.pas ├── PE.Types.Sections.inc ├── PE.Types.Sections.pas ├── PE.Types.TLS.pas ├── PE.Types.pas ├── PE.Utils.pas ├── README.md ├── Test ├── test.dpr └── test.dproj ├── VerRsrc.inc ├── WinHelper.pas ├── bin └── examples │ ├── UserDB.TXT │ └── dummy.txt ├── gmap.pas └── grbtree.pas /AllUnits.dpr: -------------------------------------------------------------------------------- 1 | program AllUnits; 2 | 3 | uses 4 | PE.Build.Common in 'PE.Build.Common.pas', 5 | PE.Build.Export in 'PE.Build.Export.pas', 6 | PE.Build.Import in 'PE.Build.Import.pas', 7 | PE.Build in 'PE.Build.pas', 8 | PE.Build.Relocs in 'PE.Build.Relocs.pas', 9 | PE.Build.Resource in 'PE.Build.Resource.pas', 10 | PE.COFF in 'PE.COFF.pas', 11 | PE.COFF.Types in 'PE.COFF.Types.pas', 12 | PE.Common in 'PE.Common.pas', 13 | PE.DataDirectories in 'PE.DataDirectories.pas', 14 | PE.ExportSym in 'PE.ExportSym.pas', 15 | PE.FileHeaderToStr in 'PE.FileHeaderToStr.pas', 16 | PE.Headers in 'PE.Headers.pas', 17 | PE.Image.Defaults in 'PE.Image.Defaults.pas', 18 | PE.Image in 'PE.Image.pas', 19 | PE.Image.Saving in 'PE.Image.Saving.pas', 20 | PE.Image.x86 in 'PE.Image.x86.pas', 21 | PE.Imports in 'PE.Imports.pas', 22 | PE.MemoryStream in 'PE.MemoryStream.pas', 23 | PE.Msg in 'PE.Msg.pas', 24 | PE.Parser.Export in 'PE.Parser.Export.pas', 25 | PE.Parser.Headers in 'PE.Parser.Headers.pas', 26 | PE.Parser.Import in 'PE.Parser.Import.pas', 27 | PE.Parser.PData in 'PE.Parser.PData.pas', 28 | PE.Parser.Relocs in 'PE.Parser.Relocs.pas', 29 | PE.Parser.Resources in 'PE.Parser.Resources.pas', 30 | PE.Parser.TLS in 'PE.Parser.TLS.pas', 31 | PE.Resources.Extract in 'PE.Resources.Extract.pas', 32 | PE.Resources in 'PE.Resources.pas', 33 | PE.Resources.Windows in 'PE.Resources.Windows.pas', 34 | PE.RTTI in 'PE.RTTI.pas', 35 | PE.Section in 'PE.Section.pas', 36 | PE.Sections in 'PE.Sections.pas', 37 | PE.TLS in 'PE.TLS.pas', 38 | PE.Types.Directories in 'PE.Types.Directories.pas', 39 | PE.Types.DOSHeader in 'PE.Types.DOSHeader.pas', 40 | PE.Types.Export in 'PE.Types.Export.pas', 41 | PE.Types.FileHeader in 'PE.Types.FileHeader.pas', 42 | PE.Types.Imports in 'PE.Types.Imports.pas', 43 | PE.Types.NTHeaders in 'PE.Types.NTHeaders.pas', 44 | PE.Types.OptionalHeader in 'PE.Types.OptionalHeader.pas', 45 | PE.Types in 'PE.Types.pas', 46 | PE.Types.Relocations in 'PE.Types.Relocations.pas', 47 | PE.Types.Resources in 'PE.Types.Resources.pas', 48 | PE.Types.Sections in 'PE.Types.Sections.pas', 49 | PE.Types.TLS in 'PE.Types.TLS.pas', 50 | PE.Utils in 'PE.Utils.pas', 51 | PE.ExecutableLoader in 'PE.ExecutableLoader.pas', 52 | PE.ParserCallbacks in 'PE.ParserCallbacks.pas', 53 | PE.Types.ImportsDelayed in 'PE.Types.ImportsDelayed.pas', 54 | PE.Parser.ImportDelayed in 'PE.Parser.ImportDelayed.pas', 55 | NullStream in 'NullStream.pas', 56 | PE.Imports.Func in 'PE.Imports.Func.pas', 57 | PE.Imports.Lib in 'PE.Imports.Lib.pas'; 58 | 59 | begin 60 | end. 61 | -------------------------------------------------------------------------------- /Examples/00 Sample/SampleLib.dpr: -------------------------------------------------------------------------------- 1 | library SampleLib; 2 | 3 | uses 4 | WinApi.Windows; 5 | 6 | { Procedures for export } 7 | 8 | procedure p_ord_10; 9 | begin 10 | end; 11 | 12 | procedure p_ord_20; 13 | begin 14 | end; 15 | 16 | procedure p3; 17 | begin 18 | end; 19 | 20 | procedure p4; 21 | begin 22 | end; 23 | 24 | procedure p_ord_50; 25 | begin 26 | end; 27 | 28 | procedure p6; 29 | begin 30 | end; 31 | 32 | procedure p7; 33 | begin 34 | end; 35 | 36 | procedure p8; 37 | begin 38 | end; 39 | 40 | var 41 | ExpVar: integer; 42 | 43 | // Exported for delayed imports example. 44 | function delayed_01: integer; 45 | begin 46 | result := 1; 47 | end; 48 | 49 | function delayed_02: integer; 50 | begin 51 | result := 2; 52 | end; 53 | 54 | exports 55 | p_ord_10 index $10, 56 | p_ord_20 index $20, 57 | p3 name 'p3', 58 | p4 name 'p4', 59 | p_ord_50 index $50, 60 | p6, 61 | p7, 62 | 63 | p8 name 'p8_one', 64 | p8 name 'p8_two', 65 | p8 name 'p8_three', 66 | 67 | ExpVar, 68 | 69 | delayed_01 index 110, 70 | delayed_02 index 120; 71 | 72 | begin 73 | // Force MessageBox be imported. 74 | if ParamCount <> 0 then 75 | MessageBox(0, 'Text', 'Caption', 0); 76 | end. 77 | -------------------------------------------------------------------------------- /Examples/01 Exports/ExportsAdd.dpr: -------------------------------------------------------------------------------- 1 | { 2 | * Example of adding exports to image. 3 | * ExportsAdd will add new exports and rebuild image. 4 | } 5 | program ExportsAdd; 6 | 7 | uses 8 | PE.Common, 9 | PE.Image, 10 | PE.Section, 11 | PE.ExportSym, 12 | 13 | PE.Build; 14 | 15 | var 16 | img: TPEImage; 17 | sec: TPESection; 18 | 19 | begin 20 | img := TPEImage.Create; 21 | try 22 | 23 | img.LoadFromFile('SampleLib.dll'); 24 | 25 | // Create new 512-byte sized section. 26 | sec := img.Sections.AddNew('data', 512, 0, nil); 27 | 28 | // Add exports 29 | // by name 30 | img.ExportSyms.AddByName(sec.RVA + $40, 'exp40'); 31 | img.ExportSyms.AddByName(sec.RVA + $30, 'exp30'); 32 | img.ExportSyms.AddByName(sec.RVA + $20, 'exp20'); 33 | img.ExportSyms.AddByName(sec.RVA + $10, 'exp10'); 34 | 35 | // by ordinal 36 | img.ExportSyms.AddByOrdinal(sec.RVA + $40, 64); 37 | img.ExportSyms.AddByOrdinal(sec.RVA + $80, 128); 38 | 39 | // forwarder 40 | img.ExportSyms.AddForwarder('fwd5', 'external5'); 41 | 42 | // Set executable export name. 43 | img.ExportedName := 'my_export_dll'; 44 | 45 | // Rebuild exports. Try to overwrite old exports or append it in new 46 | // section if it's too large. 47 | sec := ReBuildDirData(img, DDIR_EXPORT, True); 48 | 49 | // Change name of export section. 50 | if sec <> nil then 51 | sec.Name := 'myexport'; 52 | 53 | // Save resulted image to file. 54 | img.SaveToFile('tmp\new_exports.dll'); 55 | finally 56 | img.Free; 57 | end; 58 | 59 | end. 60 | -------------------------------------------------------------------------------- /Examples/01 Exports/ExportsRead.dpr: -------------------------------------------------------------------------------- 1 | program ExportsRead; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | 6 | uses 7 | System.SysUtils, 8 | 9 | PE.Common, 10 | PE.Image, 11 | PE.ExportSym; 12 | 13 | const 14 | SOURCE_PATH = 'samplelib.dll'; 15 | 16 | var 17 | img: TPEImage; 18 | sym: TPEExportSym; 19 | 20 | begin 21 | img := TPEImage.Create; 22 | try 23 | // Read image and parse exports only. 24 | if not img.LoadFromFile(SOURCE_PATH, [PF_EXPORT]) then 25 | begin 26 | writeln('Failed to load image: ', SOURCE_PATH); 27 | exit; 28 | end; 29 | 30 | // Print exports. 31 | for sym in img.ExportSyms.Items do 32 | begin 33 | writeln(format('RVA: $%x; ord: $%x; name: "%s"; fwd: "%s"', 34 | [sym.RVA, sym.Ordinal, sym.Name, sym.ForwarderName])); 35 | end; 36 | 37 | finally 38 | img.Free; 39 | end; 40 | end. 41 | -------------------------------------------------------------------------------- /Examples/02 Imports/ImportsAdd.dpr: -------------------------------------------------------------------------------- 1 | program ImportsAdd; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | 6 | uses 7 | PE.Common, 8 | PE.Image, 9 | PE.Imports, 10 | PE.Imports.Func, 11 | PE.Build; 12 | 13 | var 14 | img: TPEImage; 15 | 16 | begin 17 | img := TPEImage.Create; 18 | try 19 | img.LoadFromFile('SampleLib.dll'); 20 | 21 | // Adding dll and func in one line. 22 | // To add few funcs, lib must be stored to temp. variable. 23 | img.Imports.NewLib('some.dll').NewFunction('somefunc'); 24 | 25 | if ReBuildDirData(img, DDIR_IMPORT, true) <> nil then 26 | img.SaveToFile('tmp\new_import.dll') 27 | else 28 | writeln('Failed to rebuild image'); 29 | finally 30 | img.Free; 31 | end; 32 | 33 | end. 34 | -------------------------------------------------------------------------------- /Examples/02 Imports/ImportsDelayed.dpr: -------------------------------------------------------------------------------- 1 | { 2 | First build SampleLib project. 3 | Then run this project. 4 | It should list imported delay-loaded functions. 5 | } 6 | program ImportsDelayed; 7 | 8 | {$APPTYPE CONSOLE} 9 | 10 | 11 | uses 12 | System.Generics.Collections, 13 | System.SysUtils, 14 | 15 | PE.Common, 16 | PE.Image, 17 | PE.Imports.Lib, 18 | PE.Imports.Func; 19 | 20 | // By ordinal only. 21 | function delayed_01: integer; external 'samplelib' delayed index 110; 22 | // By ordinal and hint/name. 23 | function delayed_02: integer; external 'samplelib' delayed index 120 name 'delayed_02'; 24 | 25 | var 26 | img: TPEImage; 27 | Lib: TPEImportLibrary; 28 | fn: TPEImportFunction; 29 | 30 | begin 31 | ReportMemoryLeaksOnShutdown := True; 32 | 33 | // Force linking of delay loaded imports into this exe. 34 | delayed_01; 35 | delayed_02; 36 | 37 | img := TPEImage.Create; 38 | try 39 | img.LoadFromFile(ParamStr(0), [PF_IMPORT_DELAYED]); 40 | 41 | for Lib in img.ImportsDelayed.Libs do 42 | begin 43 | writeln(Lib.Name); 44 | for fn in Lib.Functions do 45 | writeln(format(' "%s" ordinal %d', [fn.Name, fn.Ordinal])); 46 | end; 47 | 48 | readln; 49 | finally 50 | img.Free; 51 | end; 52 | 53 | end. 54 | -------------------------------------------------------------------------------- /Examples/02 Imports/ImportsEnumeration.dpr: -------------------------------------------------------------------------------- 1 | program ImportsEnumeration; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | 6 | uses 7 | System.SysUtils, 8 | System.Generics.Collections, // using TPair (in AnotherWayToEnumerate procedure) 9 | 10 | PE.Common, // using TRVA 11 | 12 | PE.Image, 13 | PE.Imports.Lib, // using TPEImportLibrary 14 | PE.Imports.Func; // using TPEImportFunction 15 | 16 | procedure Enumerate(Img: TPEImage; ShowRVAs: boolean); 17 | var 18 | Lib: TPEImportLibrary; 19 | Fn: TPEImportFunction; 20 | rva: TRVA; 21 | begin 22 | // Scan libraries. 23 | for Lib in Img.Imports.Libs do 24 | begin 25 | writeln(format('"%s"', [Lib.Name])); 26 | 27 | rva := Lib.IatRva; 28 | 29 | // Scan imported functions (sorted by RVA). 30 | // It's map of RVA->Func (key->value). 31 | for Fn in Lib.Functions do 32 | begin 33 | write(' '); // indent 34 | 35 | if ShowRVAs then 36 | write(format('rva: %-8x', [rva])); 37 | 38 | if Fn.Name <> '' then 39 | writeln(format('"%s"', [Fn.Name])) 40 | else 41 | writeln(format('"%s" ordinal: %d', [Fn.Name, Fn.Ordinal])); 42 | 43 | inc(rva, Img.ImageWordSize); 44 | end; 45 | inc(rva, Img.ImageWordSize); // null 46 | 47 | writeln; 48 | end; 49 | end; 50 | 51 | procedure AnotherWayToEnumerate(Img: TPEImage); 52 | var 53 | Lib: TPEImportLibrary; 54 | Func: TPEImportFunction; 55 | rva: TRVA; 56 | begin 57 | for Lib in Img.Imports.Libs do 58 | begin 59 | rva := Lib.IatRva; 60 | for Func in Lib.Functions do 61 | begin 62 | writeln(format('rva: %-8x %s "%s" %d', [rva, Lib.Name, Func.Name, Func.Ordinal])); 63 | inc(rva, Img.ImageWordSize); 64 | end; 65 | inc(rva, Img.ImageWordSize); // null 66 | end; 67 | end; 68 | 69 | procedure main; 70 | var 71 | Img: TPEImage; 72 | begin 73 | Img := TPEImage.Create; 74 | try 75 | Img.LoadFromFile('SampleLib.dll'); 76 | 77 | writeln(format('Enumerating imports of "%s"', [Img.FileName])); 78 | writeln; 79 | 80 | // Try either Enumerate or AnotherWayToEnumerate 81 | 82 | Enumerate(Img, True); 83 | 84 | // AnotherWayToEnumerate(Img); 85 | 86 | finally 87 | Img.Free; 88 | end; 89 | end; 90 | 91 | begin 92 | main; 93 | readln; 94 | 95 | end. 96 | -------------------------------------------------------------------------------- /Examples/03 Resources/ResourceBuild.dpr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/Examples/03 Resources/ResourceBuild.dpr -------------------------------------------------------------------------------- /Examples/03 Resources/ResourceExtract.dpr: -------------------------------------------------------------------------------- 1 | { 2 | See also PE.Resources.Extract for resource extraction. 3 | } 4 | program ResourceExtract; 5 | 6 | uses 7 | System.Classes, 8 | System.SysUtils, 9 | 10 | PE.Image, 11 | PE.Resources; 12 | 13 | type 14 | TMyImg = class(TPEImage) 15 | function MyTraverse(Node: TResourceTreeNode): boolean; 16 | end; 17 | 18 | var 19 | img: TMyImg; 20 | 21 | { TMyImg } 22 | 23 | function TMyImg.MyTraverse(Node: TResourceTreeNode): boolean; 24 | var 25 | Leaf: TResourceTreeLeafNode; 26 | FileName, ParentName: string; 27 | begin 28 | // Need only leaf nodes (data). 29 | if Node.IsLeaf then 30 | begin 31 | Leaf := Node as TResourceTreeLeafNode; 32 | 33 | // Leaf should always have parent (branch), though it's just example. 34 | if (Leaf.Parent <> nil) then 35 | ParentName := (Leaf.Parent as TResourceTreeBranchNode).GetSafeName 36 | else 37 | ParentName := ''; 38 | 39 | // Make file name. 40 | FileName := Format('tmp\rsrc_%s_%x_%x_%x', 41 | [ParentName, Leaf.DataRVA, Leaf.DataSize, Leaf.Codepage]); 42 | 43 | // Dump raw resource. 44 | self.SaveRegionToFile(FileName, Leaf.DataRVA, Leaf.DataSize); 45 | end; 46 | Result := True; // continue 47 | end; 48 | 49 | begin 50 | img := TMyImg.Create; 51 | try 52 | if img.LoadFromFile('SampleLib.dll') then 53 | begin 54 | // Traverse and dump all resources. 55 | img.ResourceTree.Root.Traverse(img.MyTraverse); 56 | end; 57 | finally 58 | img.Free; 59 | end; 60 | 61 | end. 62 | -------------------------------------------------------------------------------- /Examples/03 Resources/ResourceStats.dpr: -------------------------------------------------------------------------------- 1 | { 2 | List image resources sorted by size. 3 | } 4 | program ResourceStats; 5 | 6 | {$apptype console} 7 | 8 | 9 | uses 10 | System.Generics.Collections, 11 | System.Generics.Defaults, 12 | System.SysUtils, 13 | 14 | PE.Common, 15 | PE.Image, 16 | PE.Resources; 17 | 18 | type 19 | TSizePair = TPair; // name, size pair 20 | TSizeList = TList; 21 | 22 | TMyImg = class(TPEImage) 23 | public 24 | List: TSizeList; 25 | constructor Create; 26 | destructor Destroy; override; 27 | 28 | function MyTraverse(Node: TResourceTreeNode): boolean; 29 | end; 30 | 31 | var 32 | img: TMyImg; 33 | pair: TSizePair; 34 | 35 | { TMyImg } 36 | 37 | constructor TMyImg.Create; 38 | begin 39 | inherited Create; 40 | List := TSizeList.Create; 41 | end; 42 | 43 | destructor TMyImg.Destroy; 44 | begin 45 | List.Free; 46 | inherited; 47 | end; 48 | 49 | function TMyImg.MyTraverse(Node: TResourceTreeNode): boolean; 50 | var 51 | Leaf: TResourceTreeLeafNode; 52 | begin 53 | if Node.IsLeaf then 54 | begin 55 | Leaf := Node as TResourceTreeLeafNode; 56 | List.Add(TSizePair.Create(Leaf.GetPath, Leaf.Data.Size)); 57 | end; 58 | result := True; // continue 59 | end; 60 | 61 | begin 62 | img := TMyImg.Create; 63 | try 64 | if ParamStr(1) <> '' then 65 | if img.LoadFromFile(ParamStr(1), [PF_RESOURCES]) then 66 | begin 67 | img.ResourceTree.Root.Traverse(img.MyTraverse); 68 | 69 | img.List.Sort(TComparer.Construct( 70 | function(const a, b: TSizePair): integer 71 | begin 72 | if a.Value > b.Value then 73 | exit(1) 74 | else if a.Value < b.Value then 75 | exit(-1); 76 | exit(0); 77 | end 78 | )); 79 | 80 | for pair in img.List do 81 | writeln(format('%-16d %s', [pair.Value, pair.Key])); 82 | 83 | end; 84 | finally 85 | img.Free; 86 | end; 87 | 88 | readln; 89 | 90 | end. 91 | -------------------------------------------------------------------------------- /Examples/03 Resources/ResourceVersionInfo.dpr: -------------------------------------------------------------------------------- 1 | { 2 | Parse version info resource (RT_VERSION) 3 | } 4 | program ResourceVersionInfo; 5 | 6 | {$APPTYPE CONSOLE} 7 | 8 | {$R *.res} 9 | 10 | 11 | uses 12 | System.SysUtils, 13 | 14 | PE.Image, 15 | PE.Resources, 16 | PE.Resources.Windows, 17 | PE.Resources.VersionInfo; 18 | 19 | procedure DumpRtVersionNode(node: TResourceTreeLeafNode); 20 | var 21 | inf: TPEVersionInfo; 22 | begin 23 | writeln(format(' lang: %d', [node.id])); 24 | 25 | inf := TPEVersionInfo.Create; 26 | try 27 | inf.LoadFromStream(node.Data); 28 | inf.PrintTree( 29 | procedure(const Text: string) 30 | begin 31 | writeln(Text); 32 | end); 33 | finally 34 | inf.Free; 35 | end; 36 | end; 37 | 38 | procedure DumpRtVersionBranch(branch: TResourceTreeBranchNode); 39 | var 40 | n, leaf: TResourceTreeNode; 41 | n_br: TResourceTreeBranchNode; 42 | name: string; 43 | begin 44 | for n in branch.Children do 45 | begin 46 | if not n.IsBranch then 47 | raise Exception.Create('Expected branch node (ID)'); 48 | 49 | name := n.GetNameOrId; 50 | writeln(name); 51 | 52 | n_br := TResourceTreeBranchNode(n); 53 | if n_br.Children.Count = 0 then 54 | begin 55 | writeln(' no sub-nodes'); 56 | exit; 57 | end; 58 | 59 | for leaf in n_br.Children do 60 | if leaf.IsLeaf then 61 | DumpRtVersionNode(TResourceTreeLeafNode(leaf)) 62 | else 63 | raise Exception.Create('Expected leaf node'); 64 | end; 65 | end; 66 | 67 | procedure Parse(const FileName: string); 68 | var 69 | img: TPEImage; 70 | rt: TWindowsResourceTree; 71 | branch_rt_version: TResourceTreeBranchNode; 72 | begin 73 | img := TPEImage.Create; 74 | try 75 | if not img.LoadFromFile(FileName) then 76 | begin 77 | writeln('Failed to load image'); 78 | exit; 79 | end; 80 | 81 | // Find RT_VERSION branch. 82 | rt := TWindowsResourceTree.Create(img.ResourceTree); 83 | try 84 | branch_rt_version := rt.FindResource(PChar(RT_VERSION)); 85 | finally 86 | rt.Free; 87 | end; 88 | 89 | if branch_rt_version = nil then 90 | begin 91 | writeln('no RT_VERSION branch found'); 92 | exit; 93 | end; 94 | 95 | DumpRtVersionBranch(branch_rt_version); 96 | finally 97 | img.Free; 98 | end; 99 | end; 100 | 101 | begin 102 | ReportMemoryLeaksOnShutdown := True; 103 | try 104 | // verinfo1.dll doesn't exist 105 | // You have to create it first. 106 | Parse('verinfo1.dll'); 107 | readln; 108 | except 109 | on E: Exception do 110 | writeln(E.ClassName, ': ', E.Message); 111 | end; 112 | 113 | end. 114 | -------------------------------------------------------------------------------- /Examples/04 Module Loader/ModuleLoader.dpr: -------------------------------------------------------------------------------- 1 | program ModuleLoader; 2 | 3 | uses 4 | PE.Image, 5 | PE.ExecutableLoader; 6 | 7 | var 8 | Img: TPEImage; 9 | Module: TExecutableModule; 10 | 11 | begin 12 | Img := TPEImage.Create; 13 | Module := TExecutableModule.Create(Img); 14 | try 15 | if Img.LoadFromFile('SampleLib.dll') then 16 | begin 17 | if Module.Load() <> msOK then 18 | ; // error 19 | end; 20 | finally 21 | Module.Free; // free it before Img. 22 | Img.Free; 23 | end; 24 | 25 | end. 26 | -------------------------------------------------------------------------------- /Examples/05 FillMem/FillMem.dpr: -------------------------------------------------------------------------------- 1 | { 2 | Example of filling section memory. 3 | } 4 | program FillMem; 5 | 6 | uses 7 | PE.Common, 8 | PE.Image, 9 | PE.Section, 10 | PE.Types.Directories; 11 | 12 | var 13 | img: TPEImage; 14 | sec: TPESection; 15 | dir: TImageDataDirectory; 16 | 17 | begin 18 | img := TPEImage.Create; 19 | try 20 | // load image 21 | img.LoadFromFile('SampleLib.dll'); 22 | // try get import directory info 23 | if img.DataDirectories.Get(DDIR_IMPORT, @dir) then 24 | begin 25 | // find section where import directory is located 26 | if img.Sections.RVAToSec(dir.VirtualAddress, @sec) then 27 | begin 28 | // Fill directory data block with zeros. 29 | img.Sections.FillMemory(dir.VirtualAddress, dir.Size, 0); 30 | end; 31 | img.SaveToFile('SampleLib_with_import_directory_zeroed.dll') 32 | end; 33 | finally 34 | img.Free; 35 | end; 36 | 37 | end. 38 | -------------------------------------------------------------------------------- /Examples/06 Relocs/BuildRelocs.dpr: -------------------------------------------------------------------------------- 1 | program BuildRelocs; 2 | 3 | uses 4 | PE.Common, 5 | PE.Image, 6 | PE.Section, 7 | PE.Build; 8 | 9 | var 10 | img: TPEImage; 11 | sec: TPESection; 12 | 13 | begin 14 | img := TPEImage.Create; 15 | try 16 | sec := img.Sections.AddNew('.code', 32, $60000020, nil); 17 | 18 | // Create "code". 19 | sec.Mem[0] := $A1; 20 | PCardinal(@sec.Mem[1])^ := img.ImageBase + sec.RVA + 6; 21 | sec.Mem[5] := $C3; 22 | PCardinal(@sec.Mem[6])^ := $12345678; 23 | 24 | img.EntryPointRVA := sec.RVA; 25 | 26 | // Create reloc and export. 27 | img.Relocs.Put(sec.RVA + 1, 3); 28 | img.ExportSyms.AddByName(sec.RVA + 6, 'MyVariable'); 29 | 30 | // Rebuild. 31 | ReBuildDirData(img, DDIR_RELOCATION, False); 32 | ReBuildDirData(img, DDIR_EXPORT, False); 33 | 34 | // Save. 35 | img.SaveToFile('myreloc.exe'); 36 | finally 37 | img.Free; 38 | end; 39 | end. 40 | -------------------------------------------------------------------------------- /Examples/07 Directory Save and Load/directory_save_and_load.dpr: -------------------------------------------------------------------------------- 1 | { 2 | * This example shows how block of data specified in data directory can be 3 | * saved from image and loaded into image. 4 | * 5 | * Though this is not recommended way to export directory data unless you 6 | * know what you do. 7 | * 8 | * Normally you need to export parsed data (such as imports, exports and other) 9 | * and save it in your custom file format. 10 | * 11 | * Note that image directory size can be less than actual data. So real size 12 | * can be calcualted only by parsing directory. That's why avoid saving/loading 13 | * directory data directly. Use parsed data instead. 14 | } 15 | program directory_save_and_load; 16 | 17 | {$APPTYPE CONSOLE} 18 | 19 | {$R *.res} 20 | 21 | 22 | uses 23 | System.SysUtils, 24 | 25 | PE.Common, 26 | PE.Image; 27 | 28 | procedure main; 29 | var 30 | img: TPEImage; 31 | begin 32 | img := TPEImage.Create; 33 | try 34 | if img.LoadFromFile('SampleLib.dll', []) then 35 | begin 36 | img.DataDirectories.SaveToFile(DDIR_EXPORT, 'export_dir'); 37 | img.DataDirectories.LoadFromFile(DDIR_EXPORT, 'export_dir', 0); 38 | end; 39 | finally 40 | img.Free; 41 | end; 42 | end; 43 | 44 | begin 45 | try 46 | main; 47 | except 48 | on E: Exception do 49 | Writeln(E.ClassName, ': ', E.Message); 50 | end; 51 | end. 52 | -------------------------------------------------------------------------------- /Examples/08 Build x86 executable graph/build_x86_graph.dpr: -------------------------------------------------------------------------------- 1 | { 2 | Build exe with a huge graph of simple structure. 3 | It acutally needed for VDisAsm to test and optimize performance of graph 4 | rendering. 5 | } 6 | program build_x86_graph; 7 | 8 | {$APPTYPE CONSOLE} 9 | 10 | {$R *.res} 11 | 12 | 13 | uses 14 | System.Classes, 15 | System.SysUtils, 16 | PE.Image, 17 | PE.Section; 18 | 19 | const 20 | MAX_ITERATIONS = 10000; 21 | 22 | { 23 | Basic block looks like this: 24 | jmp next 25 | @next: ... 26 | } 27 | BASIC_BLOCK: array [0 .. 2] of byte = ($EB, $01, $00); 28 | 29 | { 30 | It ends with: 31 | ret 32 | } 33 | BASIC_BLOCK_LAST: array [0 .. 0] of byte = ($C3); 34 | 35 | procedure main; 36 | var 37 | i: integer; 38 | ms: TMemoryStream; 39 | sec: TPESection; 40 | var 41 | img: TPEImage; 42 | begin 43 | ms := TMemoryStream.Create; 44 | try 45 | // Prepare "code" 46 | for i := 1 to MAX_ITERATIONS do 47 | ms.Write(BASIC_BLOCK, Length(BASIC_BLOCK)); 48 | ms.Write(BASIC_BLOCK_LAST, Length(BASIC_BLOCK_LAST)); 49 | 50 | // Save image. 51 | img := TPEImage.Create; 52 | try 53 | sec := img.Sections.AddNew('.graph', ms.Size, $60000020, ms.Memory); 54 | img.EntryPointRVA := sec.RVA; 55 | img.SaveToFile('x86_graph.exe'); 56 | finally 57 | img.Free; 58 | end; 59 | finally 60 | ms.Free; 61 | end; 62 | end; 63 | 64 | begin 65 | ReportMemoryLeaksOnShutdown := True; 66 | try 67 | main; 68 | except 69 | on E: Exception do 70 | Writeln(E.ClassName, ': ', E.Message); 71 | end; 72 | 73 | end. 74 | -------------------------------------------------------------------------------- /Examples/09 PEID Signatures/PeidSignatures.dpr: -------------------------------------------------------------------------------- 1 | program PeidSignatures; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | 8 | uses 9 | System.Classes, 10 | System.Diagnostics, 11 | System.SysUtils, 12 | 13 | PE.ID, 14 | PE.Image; 15 | 16 | procedure main; 17 | var 18 | sig: TPEIDSignatures; 19 | img: TPEImage; 20 | FoundSigs: TStringList; 21 | s: string; 22 | sw: TStopwatch; 23 | begin 24 | // Try to load signatures. 25 | sig := PeidLoadSignatures('UserDB.txt'); 26 | try 27 | if assigned(sig) then 28 | begin 29 | img := TPEImage.Create; 30 | FoundSigs := TStringList.Create; 31 | try 32 | if img.LoadFromFile('SampleLib.dll', []) then 33 | begin 34 | sw := TStopwatch.StartNew; 35 | PeidScan(img, sig, FoundSigs); 36 | sw.Stop; 37 | writeln('Elapsed ', string(sw.Elapsed)); 38 | 39 | if FoundSigs.Count <> 0 then 40 | begin 41 | for s in FoundSigs do 42 | writeln(s); 43 | end 44 | else 45 | begin 46 | writeln('Nothing detected'); 47 | end; 48 | 49 | end; 50 | finally 51 | img.Free; 52 | FoundSigs.Free; 53 | end; 54 | end; 55 | finally 56 | sig.Free; 57 | end; 58 | end; 59 | 60 | begin 61 | ReportMemoryLeaksOnShutdown := True; 62 | try 63 | main; 64 | readln; 65 | except 66 | on E: Exception do 67 | writeln(E.ClassName, ': ', E.Message); 68 | end; 69 | 70 | end. 71 | -------------------------------------------------------------------------------- /Examples/10 Load From Process/ProcessTest.dpr: -------------------------------------------------------------------------------- 1 | program ProcessTest; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | 8 | uses 9 | System.SysUtils, 10 | 11 | WinHelper, 12 | 13 | PE.Common, 14 | PE.Image, 15 | PE.Imports.Lib, 16 | PE.ProcessModuleStream; 17 | 18 | procedure PrintAllProcesses; 19 | var 20 | processList: TProcessRecList; 21 | processRec: TProcessRec; 22 | begin 23 | processList := TProcessRecList.Create; 24 | try 25 | if not EnumProcessesToList(processList) then 26 | begin 27 | writeln('Failed to enum processes'); 28 | exit; 29 | end; 30 | 31 | // display processes 32 | for processRec in processList do 33 | writeln(format('%-6d %s', [processRec.PID, processRec.Name])); 34 | 35 | writeln; 36 | finally 37 | processList.Free; 38 | end; 39 | end; 40 | 41 | procedure PrintModules(PID: uint32); 42 | var 43 | list: TModuleRecList; 44 | rec: TModuleRec; 45 | begin 46 | list := TModuleRecList.Create; 47 | try 48 | if not EnumModulesToList(PID, list) then 49 | begin 50 | writeln('Failed to enum modules for PID ', PID); 51 | exit; 52 | end; 53 | 54 | for rec in list do 55 | writeln(format('%p "%s"', [rec.modBaseAddr, rec.szModule])); 56 | 57 | writeln; 58 | finally 59 | list.Free; 60 | end; 61 | end; 62 | 63 | procedure PrintImports(PID: uint32); 64 | var 65 | Stream: TProcessModuleStream; 66 | Img: TPEImage; 67 | Lib: TPEImportLibrary; 68 | begin 69 | Stream := TProcessModuleStream.CreateFromPid(PID); 70 | try 71 | Img := TPEImage.Create; 72 | try 73 | if not Img.LoadFromStream(Stream, [PF_IMPORT], PEIMAGE_KIND_MEMORY) then 74 | writeln('Failed to parse image from process'); 75 | 76 | for Lib in Img.Imports.Libs do 77 | writeln(' ', Lib.Name); 78 | 79 | writeln; 80 | finally 81 | Img.Free; 82 | end; 83 | finally 84 | Stream.Free; 85 | end; 86 | end; 87 | 88 | procedure PrintPidByName(const Name: string); 89 | var 90 | PID: uint32; 91 | begin 92 | write('Process "', name, '" has PID = '); 93 | if FindPIDByProcessName(name, PID, MATCH_STRING_START) then 94 | writeln(PID) 95 | else 96 | writeln('not found'); 97 | writeln; 98 | end; 99 | 100 | procedure main; 101 | var 102 | cmd: string; 103 | PID: uint32; 104 | error: integer; 105 | begin 106 | writeln('usage:'); 107 | writeln(' proc: show processes'); 108 | writeln(' mod 123: show modules of process 123'); 109 | writeln(' imp 123: show imports of process 123'); 110 | writeln(' pid name: get pid of process string with "name"'); 111 | writeln; 112 | 113 | while true do 114 | begin 115 | readln(cmd); 116 | cmd := cmd.ToLower; 117 | 118 | if cmd = 'proc' then 119 | begin 120 | PrintAllProcesses; 121 | end 122 | else if cmd.StartsWith('mod') then 123 | begin 124 | cmd := cmd.Substring(4); 125 | val(cmd, PID, error); 126 | if error <> 0 then 127 | writeln('wrong PID number') 128 | else 129 | PrintModules(PID); 130 | end 131 | else if cmd.StartsWith('imp') then 132 | begin 133 | cmd := cmd.Substring(4); 134 | val(cmd, PID, error); 135 | if error <> 0 then 136 | writeln('wrong PID number') 137 | else 138 | PrintImports(PID); 139 | end 140 | else if cmd.StartsWith('pid') then 141 | begin 142 | PrintPidByName(cmd.Substring(4)); 143 | end 144 | else 145 | writeln('unknown command: ', cmd); 146 | end; 147 | end; 148 | 149 | begin 150 | try 151 | main; 152 | except 153 | on E: Exception do 154 | begin 155 | writeln(E.ClassName, ': ', E.Message); 156 | writeln('Press Enter to quit'); 157 | readln; 158 | end; 159 | end; 160 | 161 | end. 162 | -------------------------------------------------------------------------------- /Examples/10 Load From Process/readme.txt: -------------------------------------------------------------------------------- 1 | This example shows: 2 | 3 | - use of WinHelper unit 4 | - how to enumerate processes 5 | - how to enumerate process modules 6 | - how to load image from running process 7 | - how to get process id from process name -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /NullStream.pas: -------------------------------------------------------------------------------- 1 | unit NullStream; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils; 8 | 9 | type 10 | TNullStream = class(TStream) 11 | private 12 | FPosition: int64; 13 | FSize: int64; 14 | protected 15 | procedure SetSize(NewSize: Integer); override; 16 | public 17 | function Seek(const Offset: int64; Origin: TSeekOrigin): int64; override; 18 | function Read(var Buffer; Count: Longint): Longint; override; 19 | function Write(const Buffer; Count: Longint): Longint; override; 20 | end; 21 | 22 | implementation 23 | 24 | { TNullStream } 25 | 26 | procedure TNullStream.SetSize(NewSize: Integer); 27 | begin 28 | FSize := NewSize; 29 | end; 30 | 31 | function TNullStream.Seek(const Offset: int64; Origin: TSeekOrigin): int64; 32 | begin 33 | case Origin of 34 | soBeginning: 35 | FPosition := Offset; 36 | soCurrent: 37 | Inc(FPosition, Offset); 38 | soEnd: 39 | FPosition := FSize + Offset; 40 | end; 41 | Result := FPosition; 42 | end; 43 | 44 | function TNullStream.Read(var Buffer; Count: Integer): Longint; 45 | begin 46 | raise Exception.Create('Null stream cannot read'); 47 | end; 48 | 49 | function TNullStream.Write(const Buffer; Count: Integer): Longint; 50 | var 51 | pos: int64; 52 | begin 53 | if (FPosition >= 0) and (Count >= 0) then 54 | begin 55 | pos := FPosition + Count; 56 | if pos > FSize then 57 | FSize := pos; 58 | FPosition := pos; 59 | exit(Count); 60 | end; 61 | exit(0); 62 | end; 63 | 64 | end. 65 | -------------------------------------------------------------------------------- /PE.Build.Common.pas: -------------------------------------------------------------------------------- 1 | unit PE.Build.Common; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | PE.Common, 8 | PE.Image, 9 | NullStream; 10 | 11 | type 12 | // Parent class for any directory builders. 13 | // Override Build procedure and fill Stream with new dir data. 14 | TDirectoryBuilder = class 15 | protected 16 | FPE: TPEImage; 17 | public 18 | constructor Create(PE: TPEImage); 19 | 20 | // Builds bogus directory and return size. 21 | // Override it if you have better implementation. 22 | function EstimateTheSize: uint32; virtual; 23 | 24 | // Build directory data and store it to stream. 25 | // * DirRVA: RVA of directory start. 26 | // * Stream: Stream to store data. 27 | procedure Build(DirRVA: TRVA; Stream: TStream); virtual; abstract; 28 | 29 | // If new section created, it's called to get the flags. 30 | class function GetDefaultSectionFlags: uint32; virtual; abstract; 31 | 32 | // If new section created, it's called to get the name. 33 | class function GetDefaultSectionName: string; virtual; abstract; 34 | 35 | // Return True if need to call Build each time when DirRVA changed. 36 | class function NeedRebuildingIfRVAChanged: boolean; virtual; abstract; 37 | end; 38 | 39 | TDirectoryBuilderClass = class of TDirectoryBuilder; 40 | 41 | implementation 42 | 43 | 44 | { TDirBuilder } 45 | 46 | constructor TDirectoryBuilder.Create(PE: TPEImage); 47 | begin 48 | FPE := PE; 49 | end; 50 | 51 | function TDirectoryBuilder.EstimateTheSize: uint32; 52 | var 53 | tmp: TNullStream; 54 | begin 55 | tmp := TNullStream.Create; 56 | try 57 | Build(0, tmp); 58 | Result := tmp.Size; 59 | finally 60 | tmp.Free; 61 | end; 62 | end; 63 | 64 | end. 65 | -------------------------------------------------------------------------------- /PE.Build.Export.pas: -------------------------------------------------------------------------------- 1 | { 2 | Building export table to stream. 3 | This stream can be later saved to section or replace old export table. 4 | 5 | todo: clear old export data 6 | } 7 | 8 | {$WARN COMBINING_SIGNED_UNSIGNED OFF} 9 | 10 | unit PE.Build.Export; 11 | 12 | interface 13 | 14 | uses 15 | System.Classes, 16 | 17 | PE.Build.Common, 18 | PE.Common, 19 | PE.Utils; 20 | 21 | type 22 | TExportBuilder = class(TDirectoryBuilder) 23 | public 24 | procedure Build(DirRVA: UInt64; Stream: TStream); override; 25 | class function GetDefaultSectionFlags: Cardinal; override; 26 | class function GetDefaultSectionName: string; override; 27 | class function NeedRebuildingIfRVAChanged: Boolean; override; 28 | end; 29 | 30 | implementation 31 | 32 | uses 33 | System.Generics.Defaults, 34 | System.Generics.Collections, 35 | System.SysUtils, 36 | PE.ExportSym, 37 | PE.Types.Export; 38 | 39 | type 40 | TSym = record 41 | sym: TPEExportSym; 42 | nameRVA: TRVA; 43 | end; 44 | 45 | TSyms = TList; 46 | 47 | { TExportBuilder } 48 | 49 | procedure TExportBuilder.Build(DirRVA: UInt64; Stream: TStream); 50 | var 51 | i: integer; 52 | ExpDir: TImageExportDirectory; 53 | ofs_SymRVAs: uint32; // sym rvas offsets 54 | ofs_NameRVAs: uint32; // name rva offsets 55 | ofs_NameOrds: uint32; // name ordinals 56 | ofs_LibName: uint32; // offset of address of names 57 | sym: TPEExportSym; 58 | rva32: uint32; 59 | RVAs: packed array of uint32; 60 | minIndex, maxIndex: word; 61 | var 62 | nSyms: TSyms; 63 | nSym: TSym; 64 | ordinal: word; 65 | begin 66 | nSyms := TSyms.Create; 67 | 68 | try 69 | // Collect named items. 70 | // Find min and max index. 71 | maxIndex := 0; 72 | if FPE.ExportSyms.Count = 0 then 73 | minIndex := 1 74 | else 75 | minIndex := $FFFF; 76 | 77 | for sym in FPE.ExportSyms.Items do 78 | begin 79 | nSym.sym := sym; 80 | nSym.nameRVA := 0; 81 | nSyms.Add(nSym); 82 | 83 | if sym.ordinal > maxIndex then 84 | maxIndex := sym.ordinal; 85 | if sym.ordinal < minIndex then 86 | minIndex := sym.ordinal; 87 | end; 88 | 89 | // Create RVAs. 90 | if maxIndex <> 0 then 91 | begin 92 | SetLength(RVAs, maxIndex); 93 | for i := 0 to FPE.ExportSyms.Count - 1 do 94 | begin 95 | sym := FPE.ExportSyms.Items[i]; 96 | if sym.ordinal <> 0 then 97 | RVAs[sym.ordinal - minIndex] := sym.RVA; 98 | end; 99 | end; 100 | 101 | // Calc offsets. 102 | ofs_SymRVAs := SizeOf(ExpDir); 103 | ofs_NameRVAs := ofs_SymRVAs + Length(RVAs) * SizeOf(rva32); 104 | ofs_NameOrds := ofs_NameRVAs + nSyms.Count * SizeOf(rva32); 105 | ofs_LibName := ofs_NameOrds + nSyms.Count * SizeOf(ordinal); 106 | 107 | // Initial seek. 108 | Stream.Size := ofs_LibName; 109 | Stream.Position := ofs_LibName; 110 | 111 | // Write exported name. 112 | if FPE.ExportedName <> '' then 113 | StreamWriteStringA(Stream, FPE.ExportedName); 114 | 115 | // Sort nSyms by names (lexicographical order) to allow binary searches. 116 | nSyms.Sort(TComparer.Construct( 117 | function(const a, b: TSym): integer 118 | begin 119 | Result := CompareStr(a.sym.Name, b.sym.Name) 120 | end 121 | )); 122 | 123 | // Write names. 124 | for i := 0 to nSyms.Count - 1 do 125 | begin 126 | nSym := nSyms[i]; 127 | nSym.nameRVA := DirRVA + Stream.Position; 128 | nSyms[i] := nSym; 129 | StreamWriteStringA(Stream, nSym.sym.Name); 130 | end; 131 | 132 | // Write forwarder names. 133 | for i := 0 to nSyms.Count - 1 do 134 | begin 135 | nSym := nSyms[i]; 136 | if nSym.sym.Forwarder then 137 | begin 138 | RVAs[nSym.sym.ordinal - minIndex] := DirRVA + Stream.Position; 139 | StreamWriteStringA(Stream, nSym.sym.ForwarderName); 140 | end; 141 | end; 142 | 143 | // Fill export dir. 144 | ExpDir.ExportFlags := 0; 145 | ExpDir.TimeDateStamp := 0; 146 | ExpDir.MajorVersion := 0; 147 | ExpDir.MinorVersion := 0; 148 | if FPE.ExportedName <> '' then 149 | ExpDir.nameRVA := DirRVA + ofs_LibName 150 | else 151 | ExpDir.nameRVA := 0; 152 | ExpDir.OrdinalBase := minIndex; 153 | ExpDir.AddressTableEntries := Length(RVAs); 154 | ExpDir.NumberOfNamePointers := nSyms.Count; 155 | ExpDir.ExportAddressTableRVA := DirRVA + ofs_SymRVAs; 156 | ExpDir.NamePointerRVA := DirRVA + ofs_NameRVAs; 157 | ExpDir.OrdinalTableRVA := DirRVA + ofs_NameOrds; 158 | 159 | // Seek start. 160 | Stream.Position := 0; 161 | 162 | // Write export dir. 163 | Stream.Write(ExpDir, SizeOf(ExpDir)); 164 | 165 | // Write RVAs of all symbols. 166 | StreamWrite(Stream, RVAs[0], Length(RVAs) * SizeOf(RVAs[0])); 167 | 168 | // Write name RVAs. 169 | for i := 0 to nSyms.Count - 1 do 170 | begin 171 | nSym := nSyms[i]; 172 | rva32 := nSym.nameRVA; 173 | StreamWrite(Stream, rva32, SizeOf(rva32)); 174 | end; 175 | 176 | // Write name ordinals. 177 | for i := 0 to nSyms.Count - 1 do 178 | begin 179 | nSym := nSyms[i]; 180 | ordinal := nSym.sym.ordinal - minIndex; 181 | StreamWrite(Stream, ordinal, SizeOf(ordinal)); 182 | end; 183 | 184 | finally 185 | nSyms.Free; 186 | end; 187 | end; 188 | 189 | class function TExportBuilder.GetDefaultSectionFlags: Cardinal; 190 | begin 191 | Result := $40000040; // readable, initialized data 192 | end; 193 | 194 | class function TExportBuilder.GetDefaultSectionName: string; 195 | begin 196 | Result := '.edata'; 197 | end; 198 | 199 | class function TExportBuilder.NeedRebuildingIfRVAChanged: Boolean; 200 | begin 201 | Result := True; 202 | end; 203 | 204 | end. 205 | -------------------------------------------------------------------------------- /PE.Build.Import.pas: -------------------------------------------------------------------------------- 1 | unit PE.Build.Import; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.Generics.Collections, 8 | System.SysUtils, 9 | 10 | PE.Common, 11 | PE.Section, 12 | PE.Build.Common, 13 | PE.Utils; 14 | 15 | type 16 | TImportBuilder = class(TDirectoryBuilder) 17 | public 18 | // Modified after Build called. 19 | BuiltIatRVA: TRVA; 20 | BuiltIatSize: uint32; 21 | procedure Build(DirRVA: TRVA; Stream: TStream); override; 22 | class function GetDefaultSectionFlags: Cardinal; override; 23 | class function GetDefaultSectionName: string; override; 24 | class function NeedRebuildingIfRVAChanged: Boolean; override; 25 | end; 26 | 27 | implementation 28 | 29 | uses 30 | // Expand 31 | PE.Image, 32 | PE.Types.FileHeader, 33 | // 34 | PE.Imports, 35 | PE.Imports.Lib, 36 | PE.Imports.Func, 37 | PE.Types.Imports; 38 | 39 | { 40 | * Import directory layout 41 | * 42 | * IDT. 43 | * For each library 44 | * Import Descriptor 45 | * Null Import Descriptor 46 | * 47 | * Name pointers. 48 | * For each library 49 | * For each function 50 | * Pointer to function hint/name or ordinal 51 | * Null pointer 52 | * 53 | * IAT. 54 | * For each library 55 | * For each function 56 | * Function address 57 | * Null address 58 | * 59 | * Names. 60 | * For each library 61 | * Library name 62 | * ** align 2 ** 63 | * For each function 64 | * Hint: uint16 65 | * Function name: variable length 66 | } 67 | procedure WriteStringAligned2(Stream: TStream; const s: string); 68 | const 69 | null: byte = 0; 70 | var 71 | bytes: TBytes; 72 | begin 73 | bytes := TEncoding.ANSI.GetBytes(s); 74 | StreamWrite(Stream, bytes[0], length(bytes)); 75 | StreamWrite(Stream, null, 1); 76 | if Stream.Position mod 2 <> 0 then 77 | StreamWrite(Stream, null, 1); 78 | end; 79 | 80 | procedure WriteIDT( 81 | Stream: TStream; 82 | var ofs_idt: uint32; 83 | const idt: TImportDirectoryTable); 84 | begin 85 | Stream.Position := ofs_idt; 86 | StreamWrite(Stream, idt, sizeof(idt)); 87 | inc(ofs_idt, sizeof(idt)); 88 | end; 89 | 90 | procedure WriteNullIDT(Stream: TStream; var ofs_idt: uint32); 91 | var 92 | idt: TImportDirectoryTable; 93 | begin 94 | idt.Clear; 95 | WriteIDT(Stream, ofs_idt, idt); 96 | end; 97 | 98 | // Write library name and set idt name pointer, update name pointer offset. 99 | procedure WriteLibraryName( 100 | Stream: TStream; 101 | Lib: TPEImportLibrary; 102 | DirRVA: TRVA; 103 | var ofs_names: uint32; 104 | var idt: TImportDirectoryTable); 105 | begin 106 | // library name 107 | idt.NameRVA := DirRVA + ofs_names; 108 | Stream.Position := ofs_names; 109 | WriteStringAligned2(Stream, Lib.Name); 110 | ofs_names := Stream.Position; 111 | end; 112 | 113 | function MakeOrdinalRVA(ordinal: uint16; wordsize: byte): TRVA; inline; 114 | begin 115 | if wordsize = 4 then 116 | result := $80000000 or ordinal 117 | else 118 | result := $8000000000000000 or ordinal; 119 | end; 120 | 121 | procedure WriteFunctionNamesOrOrdinalsAndIat( 122 | Stream: TStream; 123 | Lib: TPEImportLibrary; 124 | DirRVA: TRVA; 125 | var ofs_names: uint32; 126 | var ofs_name_pointers: uint32; 127 | var ofs_iat: uint32; 128 | var idt: TImportDirectoryTable; 129 | wordsize: byte); 130 | var 131 | hint: uint16; 132 | fn: TPEImportFunction; 133 | rva_hint_name: TRVA; 134 | begin 135 | if Lib.Functions.Count = 0 then 136 | exit; 137 | 138 | idt.ImportLookupTableRVA := DirRVA + ofs_name_pointers; 139 | 140 | if (not Lib.Original) then 141 | begin 142 | idt.ImportAddressTable := DirRVA + ofs_iat; 143 | 144 | // Update IAT in library. 145 | Lib.IatRva := idt.ImportAddressTable; 146 | end 147 | else 148 | begin 149 | idt.ImportAddressTable := Lib.IatRva; 150 | end; 151 | 152 | hint := 0; 153 | for fn in Lib.Functions do 154 | begin 155 | // Write name. 156 | if not fn.Name.IsEmpty then 157 | begin 158 | // If imported by name. 159 | rva_hint_name := DirRVA + ofs_names; 160 | Stream.Position := ofs_names; 161 | StreamWrite(Stream, hint, sizeof(hint)); 162 | WriteStringAligned2(Stream, fn.Name); 163 | ofs_names := Stream.Position; 164 | end 165 | else 166 | begin 167 | // If imported by ordinal. 168 | rva_hint_name := MakeOrdinalRVA(fn.ordinal, wordsize); 169 | end; 170 | 171 | // Write name pointer. 172 | Stream.Position := ofs_name_pointers; 173 | StreamWrite(Stream, rva_hint_name, wordsize); 174 | inc(ofs_name_pointers, wordsize); 175 | 176 | // Write IAT item. 177 | Stream.Position := ofs_iat; 178 | StreamWrite(Stream, rva_hint_name, wordsize); 179 | inc(ofs_iat, wordsize); 180 | end; 181 | 182 | rva_hint_name := 0; 183 | 184 | // Write null name pointer. 185 | Stream.Position := ofs_name_pointers; 186 | StreamWrite(Stream, rva_hint_name, wordsize); 187 | ofs_name_pointers := Stream.Position; 188 | 189 | // Write null IAT item. 190 | Stream.Position := ofs_iat; 191 | StreamWrite(Stream, rva_hint_name, wordsize); 192 | inc(ofs_iat, wordsize); 193 | end; 194 | 195 | procedure TImportBuilder.Build(DirRVA: TRVA; Stream: TStream); 196 | var 197 | idt: TImportDirectoryTable; 198 | Lib: TPEImportLibrary; 199 | elements: uint32; 200 | var 201 | ofs_idt: uint32; 202 | ofs_name_pointers: uint32; 203 | ofs_iat, ofs_iat_0: uint32; 204 | ofs_names: uint32; 205 | begin 206 | BuiltIatRVA := 0; 207 | BuiltIatSize := 0; 208 | 209 | if FPE.Imports.Libs.Count = 0 then 210 | exit; 211 | 212 | // Calculate initial offsets. 213 | elements := 0; 214 | for Lib in FPE.Imports.Libs do 215 | inc(elements, Lib.Functions.Count + 1); 216 | 217 | ofs_idt := 0; 218 | ofs_name_pointers := sizeof(idt) * (FPE.Imports.Libs.Count + 1); 219 | ofs_iat := ofs_name_pointers + elements * (FPE.ImageWordSize); 220 | ofs_iat_0 := ofs_iat; 221 | ofs_names := ofs_name_pointers + 2 * elements * (FPE.ImageWordSize); 222 | 223 | // Write. 224 | for Lib in FPE.Imports.Libs do 225 | begin 226 | idt.Clear; 227 | WriteLibraryName(Stream, Lib, DirRVA, ofs_names, idt); 228 | WriteFunctionNamesOrOrdinalsAndIat(Stream, Lib, DirRVA, 229 | ofs_names, ofs_name_pointers, ofs_iat, idt, FPE.ImageWordSize); 230 | WriteIDT(Stream, ofs_idt, idt); 231 | end; 232 | WriteNullIDT(Stream, ofs_idt); 233 | 234 | self.BuiltIatRVA := DirRVA + ofs_iat_0; 235 | self.BuiltIatSize := ofs_iat - ofs_iat_0; 236 | end; 237 | 238 | class function TImportBuilder.GetDefaultSectionFlags: Cardinal; 239 | begin 240 | result := $C0000040; 241 | end; 242 | 243 | class function TImportBuilder.GetDefaultSectionName: string; 244 | begin 245 | result := '.idata'; 246 | end; 247 | 248 | class function TImportBuilder.NeedRebuildingIfRVAChanged: Boolean; 249 | begin 250 | result := True; 251 | end; 252 | 253 | end. 254 | -------------------------------------------------------------------------------- /PE.Build.Relocs.pas: -------------------------------------------------------------------------------- 1 | unit PE.Build.Relocs; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | PE.Common, 8 | PE.Build.Common, 9 | PE.Types.Relocations, 10 | PE.Utils; 11 | 12 | type 13 | TRelocBuilder = class(TDirectoryBuilder) 14 | public 15 | procedure Build(DirRVA: TRVA; Stream: TStream); override; 16 | class function GetDefaultSectionFlags: uint32; override; 17 | class function GetDefaultSectionName: string; override; 18 | class function NeedRebuildingIfRVAChanged: boolean; override; 19 | end; 20 | 21 | implementation 22 | 23 | const 24 | RELOC_BLOCK_ALIGN = $1000; 25 | 26 | { TRelocBuilder } 27 | 28 | function CalcBaseRVA(RVA: TRVA): TRVA; inline; 29 | begin 30 | Result := AlignDown(RVA, RELOC_BLOCK_ALIGN); 31 | end; 32 | 33 | procedure TRelocBuilder.Build(DirRVA: TRVA; Stream: TStream); 34 | var 35 | Block: TBaseRelocationBlock; 36 | Cur: TRelocTree.TRBNodePtr; 37 | NextBlockRVA: TRVA; 38 | Pos0, Pos1: UInt64; 39 | Entry: TBaseRelocationEntry; 40 | begin 41 | if FPE.Relocs.Count = 0 then 42 | Exit; 43 | // Relocations are already sorted by RVA. 44 | Cur := FPE.Relocs.Items.First; 45 | while (Cur <> nil) do 46 | begin 47 | // New block. 48 | Pos0 := Stream.Position; 49 | Stream.Position := Pos0 + SizeOf(Block); 50 | Block.PageRVA := CalcBaseRVA(Cur^.K.RVA); 51 | NextBlockRVA := Block.PageRVA + RELOC_BLOCK_ALIGN; 52 | // Entries. 53 | while (Cur <> nil) and (Cur^.K.RVA < NextBlockRVA) do 54 | begin 55 | Entry.raw := (Cur^.K.RVA and $0FFF) or (Cur^.K.&Type shl 12); 56 | Stream.Write(Entry, SizeOf(Entry)); 57 | Cur := FPE.Relocs.Items.GetNext(Cur); 58 | end; 59 | // If not last block, check if need align for next block. 60 | if (Cur <> nil) then 61 | begin 62 | // Each block must start on a 32-bit boundary. 63 | Entry.raw := 0; 64 | while (Stream.Position mod 4) <> 0 do 65 | Stream.Write(Entry, SizeOf(Entry)); 66 | end; 67 | // Write block header. 68 | Pos1 := Stream.Position; 69 | Block.BlockSize := Pos1 - Pos0; 70 | // Write block record. 71 | Stream.Position := Pos0; 72 | Stream.Write(Block, SizeOf(Block)); 73 | Stream.Position := Pos1; 74 | end; 75 | end; 76 | 77 | class function TRelocBuilder.GetDefaultSectionFlags: uint32; 78 | begin 79 | Result := $42000040; // Readable, Discardable, Initialized data. 80 | end; 81 | 82 | class function TRelocBuilder.GetDefaultSectionName: string; 83 | begin 84 | Result := '.reloc'; 85 | end; 86 | 87 | class function TRelocBuilder.NeedRebuildingIfRVAChanged: boolean; 88 | begin 89 | Result := False; 90 | end; 91 | 92 | end. 93 | -------------------------------------------------------------------------------- /PE.Build.Resource.pas: -------------------------------------------------------------------------------- 1 | unit PE.Build.Resource; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | PE.Build.Common, 8 | PE.Common, 9 | PE.Resources, 10 | PE.Types.Resources; 11 | 12 | type 13 | TRsrcBuilder = class(TDirectoryBuilder) 14 | private 15 | // Sizes. 16 | FSizeOfResourceTables: UInt32; 17 | FSizeOfDataDesc: UInt32; 18 | FSizeOfNames: UInt32; 19 | FSizeOfData: UInt32; 20 | procedure ClearSizes; inline; 21 | function CalcSizesCallback(Node: TResourceTreeNode): boolean; 22 | procedure CalcSizes; inline; 23 | private 24 | // Offsets. 25 | FOfsTables: UInt32; 26 | FOfsDataDesc: UInt32; 27 | FOfsNames: UInt32; 28 | FOfsData: UInt32; 29 | FOfsEnd: UInt32; 30 | procedure CalcOffsets; 31 | private 32 | FBaseRVA: TRVA; 33 | FStream: TStream; 34 | // Write name. Result is position of written name. 35 | function WriteName(const Name: UnicodeString): UInt32; 36 | // Write table and update offsets. Result is offset where table was written. 37 | function WriteBranchNode(Root: TResourceTreeBranchNode): UInt32; 38 | // Write leaf data and return offset where it was written. 39 | function WriteLeafData(Node: TResourceTreeLeafNode): UInt32; 40 | // Write leaf node and return offset where it was written. 41 | function WriteLeafNode(Node: TResourceTreeLeafNode): UInt32; 42 | public 43 | procedure Build(DirRVA: TRVA; Stream: TStream); override; 44 | class function GetDefaultSectionFlags: Cardinal; override; 45 | class function GetDefaultSectionName: string; override; 46 | class function NeedRebuildingIfRVAChanged: boolean; override; 47 | end; 48 | 49 | implementation 50 | 51 | uses 52 | // Expand 53 | PE.Image, 54 | // 55 | PE.Utils; 56 | 57 | procedure TRsrcBuilder.Build(DirRVA: TRVA; Stream: TStream); 58 | var 59 | Root: TResourceTreeBranchNode; 60 | begin 61 | FBaseRVA := DirRVA; 62 | CalcSizes; 63 | CalcOffsets; 64 | 65 | Root := FPE.ResourceTree.Root; 66 | 67 | // If there's no items at root exit. 68 | if Root.Children.Count = 0 then 69 | exit; 70 | 71 | // Setup stream. 72 | FStream := Stream; 73 | FStream.Size := FOfsEnd; 74 | // Build nodes starting from root. 75 | WriteBranchNode(Root); 76 | end; 77 | 78 | procedure TRsrcBuilder.CalcSizes; 79 | begin 80 | ClearSizes; 81 | FPE.ResourceTree.Root.Traverse(CalcSizesCallback); 82 | end; 83 | 84 | function TRsrcBuilder.CalcSizesCallback(Node: TResourceTreeNode): boolean; 85 | var 86 | Leaf: TResourceTreeLeafNode; 87 | Branch: TResourceTreeBranchNode; 88 | begin 89 | if Node.IsLeaf then 90 | begin 91 | Leaf := Node as TResourceTreeLeafNode; 92 | inc(FSizeOfDataDesc, SizeOf(TResourceDataEntry)); 93 | inc(FSizeOfData, Leaf.DataSize); 94 | end 95 | else 96 | begin 97 | Branch := Node as TResourceTreeBranchNode; 98 | inc(FSizeOfResourceTables, SizeOf(TResourceDirectoryTable)); 99 | inc(FSizeOfResourceTables, SizeOf(TResourceDirectoryEntry) * Branch.Children.Count); 100 | if Branch.Name <> '' then 101 | begin 102 | inc(FSizeOfNames, 2 + SizeOf(WideChar) * Length(Branch.Name)); 103 | end; 104 | end; 105 | Result := True; 106 | end; 107 | 108 | procedure TRsrcBuilder.ClearSizes; 109 | begin 110 | FSizeOfResourceTables := 0; 111 | FSizeOfDataDesc := 0; 112 | FSizeOfNames := 0; 113 | FSizeOfData := 0; 114 | end; 115 | 116 | class function TRsrcBuilder.GetDefaultSectionFlags: Cardinal; 117 | begin 118 | Result := $40000040; // readable + initialized data 119 | end; 120 | 121 | class function TRsrcBuilder.GetDefaultSectionName: string; 122 | begin 123 | Result := '.rsrc'; 124 | end; 125 | 126 | class function TRsrcBuilder.NeedRebuildingIfRVAChanged: boolean; 127 | begin 128 | Result := True; 129 | end; 130 | 131 | function TRsrcBuilder.WriteLeafData(Node: TResourceTreeLeafNode): UInt32; 132 | var 133 | Pos: UInt32; 134 | begin 135 | Result := FOfsData; 136 | if Node.Data.Size <> 0 then 137 | begin 138 | Pos := FStream.Position; 139 | FStream.Position := FOfsData; 140 | Node.Data.Position := 0; 141 | FStream.CopyFrom(Node.Data, Node.Data.Size); 142 | inc(FOfsData, Node.Data.Size); 143 | FStream.Position := Pos; 144 | end; 145 | end; 146 | 147 | function TRsrcBuilder.WriteLeafNode(Node: TResourceTreeLeafNode): UInt32; 148 | var 149 | DataEntry: TResourceDataEntry; 150 | Pos: UInt32; 151 | begin 152 | Result := FOfsDataDesc; 153 | Pos := FStream.Position; 154 | FStream.Position := FOfsDataDesc; // store pos 155 | // Write data and make data desc. 156 | DataEntry.DataRVA := WriteLeafData(Node) + FBaseRVA; 157 | DataEntry.Size := Node.DataSize; 158 | DataEntry.Codepage := Node.Codepage; 159 | DataEntry.Reserved := 0; 160 | // Write data. 161 | FStream.Write(DataEntry, SizeOf(DataEntry)); 162 | inc(FOfsDataDesc, SizeOf(DataEntry)); 163 | FStream.Position := Pos; // restore pos 164 | end; 165 | 166 | function TRsrcBuilder.WriteBranchNode(Root: TResourceTreeBranchNode): UInt32; 167 | type 168 | TSimpleEntry = packed record 169 | IdOrNameOfs: UInt32; 170 | ChildOfs: UInt32; 171 | end; 172 | 173 | PSimpleEntry = ^TSimpleEntry; 174 | var 175 | Table: TResourceDirectoryTable; 176 | Node: TResourceTreeNode; 177 | Entries: array of TSimpleEntry; 178 | Entry: PSimpleEntry; 179 | Pos: UInt32; 180 | begin 181 | if Root.Children.Count = 0 then 182 | exit(0); 183 | 184 | Pos := FStream.Position; 185 | 186 | Result := FOfsTables; 187 | 188 | // Prepare table. 189 | Table.Characteristics := Root.Characteristics; 190 | Table.TimeDateStamp := Root.TimeDateStamp; 191 | Table.MajorVersion := Root.MajorVersion; 192 | Table.MinorVersion := Root.MinorVersion; 193 | 194 | Table.NumberOfNameEntries := 0; 195 | Table.NumberOfIDEntries := 0; 196 | 197 | for Node in Root.Children do 198 | if Node.IsNamed then 199 | inc(Table.NumberOfNameEntries) 200 | else 201 | inc(Table.NumberOfIDEntries); 202 | 203 | // Write table. 204 | FStream.Position := FOfsTables; 205 | FStream.Write(Table, SizeOf(Table)); 206 | // Update FOfsTables. 207 | inc(FOfsTables, SizeOf(TResourceDirectoryTable)); 208 | inc(FOfsTables, SizeOf(TResourceDirectoryEntry) * Root.Children.Count); 209 | // Entries. 210 | 211 | // Prepare entries. 212 | SetLength(Entries, Root.Children.Count); 213 | Entry := @Entries[0]; 214 | 215 | for Node in Root.Children do 216 | begin 217 | // Set Id or Name. 218 | if Node.IsNamed then 219 | Entry.IdOrNameOfs := WriteName(Node.Name) or $80000000 // Named 220 | else 221 | Entry.IdOrNameOfs := Node.Id; // ID 222 | // Set child offset. 223 | if Node.IsLeaf then 224 | Entry.ChildOfs := WriteLeafNode(TResourceTreeLeafNode(Node)) 225 | else 226 | Entry.ChildOfs := WriteBranchNode(TResourceTreeBranchNode(Node)) or $80000000; 227 | // Next entry. 228 | inc(Entry); 229 | end; 230 | // Write entries. 231 | FStream.Write(Entries[0], Length(Entries) * SizeOf(Entries[0])); 232 | FStream.Position := Pos; // resotre pos 233 | end; 234 | 235 | function TRsrcBuilder.WriteName(const Name: UnicodeString): UInt32; 236 | var 237 | Len: word; 238 | Pos: UInt32; 239 | begin 240 | // Align offset up 2 bytes. 241 | if (FOfsNames mod 2) <> 0 then 242 | inc(FOfsNames); 243 | Pos := FStream.Position; // store pos 244 | Result := FOfsNames; 245 | FStream.Position := FOfsNames; 246 | Len := Length(Name); 247 | FStream.Write(Len, 2); 248 | FStream.Write(Name[1], Length(Name) * SizeOf(Name[1])); 249 | FOfsNames := FStream.Position; 250 | FStream.Position := Pos; // restore pos 251 | end; 252 | 253 | procedure TRsrcBuilder.CalcOffsets; 254 | var 255 | MachineWord: Byte; 256 | begin 257 | MachineWord := FPE.GetImageBits div 8; 258 | FOfsTables := 0; 259 | FOfsDataDesc := FOfsTables + FSizeOfResourceTables; 260 | FOfsNames := FOfsDataDesc + FSizeOfDataDesc; 261 | FOfsData := AlignUp(FOfsNames + FSizeOfNames, MachineWord); 262 | FOfsEnd := FOfsData + FSizeOfData; 263 | end; 264 | 265 | end. 266 | -------------------------------------------------------------------------------- /PE.Build.pas: -------------------------------------------------------------------------------- 1 | unit PE.Build; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | PE.Common, 8 | PE.Section; 9 | 10 | { 11 | * Rebuild directory data. 12 | * 13 | * If TryToOverwritesection is True, it will try to put new section at 14 | * old section space (if new section is smaller). 15 | * 16 | * If new section is bigger than old it will be forced to create new section. 17 | * 18 | * Result is new section if it was created or nil if old section was replaced. 19 | } 20 | function ReBuildDirData(PE: TObject; DDIR_ID: integer; Overwrite: boolean): TPESection; 21 | 22 | implementation 23 | 24 | uses 25 | PE.Image, 26 | PE.Types.Directories, 27 | PE.Build.Common, 28 | 29 | PE.Build.Export, 30 | PE.Build.Import, 31 | PE.Build.Resource, 32 | PE.Build.Relocs; 33 | 34 | const 35 | RebuilderTable: array [0 .. DDIR_LAST] of TDirectoryBuilderClass = 36 | ( 37 | PE.Build.Export.TExportBuilder, // export 38 | PE.Build.Import.TImportBuilder, // import 39 | PE.Build.Resource.TRsrcBuilder, // resources 40 | nil, // exception 41 | nil, // certificate 42 | PE.Build.Relocs.TRelocBuilder, // relocations 43 | nil, // debug 44 | nil, // architecture 45 | nil, // global ptr 46 | nil, // tls 47 | nil, // load config 48 | nil, // bound import 49 | nil, // iat 50 | nil, // delay import 51 | nil // clr runtime header 52 | ); 53 | 54 | function ReBuildDirData(PE: TObject; DDIR_ID: integer; Overwrite: boolean): TPESection; 55 | var 56 | stream: TMemoryStream; 57 | builder: TDirectoryBuilder; 58 | img: TPEImage; 59 | sec: TPESection; 60 | dir: TImageDataDirectory; 61 | prognoseRVA, destRVA: TRVA; 62 | destMem: Pointer; 63 | destSize: uint32; 64 | begin 65 | Result := nil; 66 | 67 | if (DDIR_ID < 0) or (DDIR_ID > High(RebuilderTable)) then 68 | exit; // no builder found 69 | 70 | if RebuilderTable[DDIR_ID] = nil then 71 | exit; // no builder found 72 | 73 | img := PE as TPEImage; 74 | 75 | builder := RebuilderTable[DDIR_ID].Create(img); 76 | stream := TMemoryStream.Create; 77 | try 78 | 79 | // Prognose dest RVA. 80 | if img.DataDirectories.Get(DDIR_ID, @dir) then 81 | prognoseRVA := dir.VirtualAddress 82 | else 83 | prognoseRVA := 0; 84 | 85 | // Build to get size. 86 | builder.Build(prognoseRVA, stream); 87 | 88 | sec := nil; 89 | destRVA := 0; // compiler friendly 90 | destSize := 0; // compiler friendly 91 | 92 | // Try to get old section space. 93 | if Overwrite then 94 | if img.DataDirectories.Get(DDIR_ID, @dir) then 95 | if dir.Size >= stream.Size then 96 | if img.RVAToSec(dir.VirtualAddress, @sec) then 97 | begin 98 | // If directory occupies whole section. 99 | if (sec.RVA = dir.VirtualAddress) and (sec.RawSize = dir.Size) then 100 | begin 101 | // Leave section as it is. 102 | end; 103 | // Set dest rva/size (reuse this section). 104 | destRVA := dir.VirtualAddress; 105 | destSize := dir.Size; 106 | end; 107 | 108 | // If stream is empty, no need to rebuild anything. 109 | if stream.Size <> 0 then 110 | begin 111 | 112 | // If we still have no section, create new with default name and flags. 113 | // User can change it later. 114 | if sec = nil then 115 | begin 116 | sec := img.Sections.AddNew(builder.GetDefaultSectionName, 117 | stream.Size, builder.GetDefaultSectionFlags, nil); 118 | destRVA := sec.RVA; 119 | destSize := stream.Size; 120 | // Make old data directory region unused. 121 | if img.DataDirectories.Get(DDIR_ID, @dir) then 122 | img.RegionRemove(dir.VirtualAddress, dir.Size); 123 | end; 124 | 125 | // Rebuild data to have valid RVAs (if prognose is wrong) 126 | if builder.NeedRebuildingIfRVAChanged then 127 | if prognoseRVA <> destRVA then 128 | begin 129 | stream.Clear; 130 | builder.Build(destRVA, stream); 131 | end; 132 | 133 | // Get address where data of built directory should reside. 134 | destMem := img.RVAToMem(destRVA); 135 | 136 | // Move built data to section. 137 | Move(stream.Memory^, destMem^, stream.Size); 138 | end 139 | else 140 | begin 141 | // If stream size = 0 142 | destRVA := 0; 143 | destSize := 0; 144 | end; 145 | 146 | // Update directory pointer. 147 | img.DataDirectories.Put(DDIR_ID, destRVA, destSize); 148 | 149 | // For imports also update IAT table. 150 | if DDIR_ID = DDIR_IMPORT then 151 | begin 152 | img.DataDirectories.Put(DDIR_IAT, 153 | TImportBuilder(builder).BuiltIatRVA, 154 | TImportBuilder(builder).BuiltIatSize); 155 | end; 156 | 157 | Result := sec; 158 | 159 | finally 160 | builder.Free; 161 | stream.Free; 162 | end; 163 | end; 164 | 165 | end. 166 | -------------------------------------------------------------------------------- /PE.COFF.Types.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.COFF.Types.pas -------------------------------------------------------------------------------- /PE.COFF.pas: -------------------------------------------------------------------------------- 1 | unit PE.COFF; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | PE.COFF.Types; 9 | 10 | type 11 | TCOFF = class 12 | private 13 | FPE: TObject; 14 | FStrings: TBytes; 15 | procedure LoadStrings(AStream: TStream); 16 | public 17 | constructor Create(PEImage: TObject); 18 | 19 | procedure Clear; 20 | procedure LoadFromStream(AStream: TStream); 21 | 22 | function GetString(Offset: integer; out Str: String): boolean; 23 | end; 24 | 25 | implementation 26 | 27 | uses 28 | // Expand 29 | PE.Types.FileHeader, 30 | // 31 | PE.Common, 32 | PE.Image, 33 | PE.Utils; 34 | 35 | { TCOFF } 36 | 37 | procedure TCOFF.Clear; 38 | begin 39 | SetLength(FStrings, 0); 40 | end; 41 | 42 | constructor TCOFF.Create(PEImage: TObject); 43 | begin 44 | self.FPE := PEImage; 45 | end; 46 | 47 | function TCOFF.GetString(Offset: integer; out Str: String): boolean; 48 | begin 49 | Result := (Offset >= 0) and (Offset < Length(FStrings)); 50 | if Result then 51 | Str := String(PAnsiChar(@FStrings[Offset])); 52 | end; 53 | 54 | procedure TCOFF.LoadFromStream(AStream: TStream); 55 | begin 56 | LoadStrings(AStream); 57 | end; 58 | 59 | procedure TCOFF.LoadStrings(AStream: TStream); 60 | var 61 | StrTableOfs, EndPos: uint64; 62 | cbStringData: uint32; 63 | FileHdr: TImageFileHeader; 64 | begin 65 | Clear; 66 | 67 | // 4.6. COFF String Table 68 | 69 | FileHdr := TPEImage(FPE).FileHeader^; 70 | 71 | if FileHdr.PointerToSymbolTable = 0 then 72 | exit; 73 | 74 | if FileHdr.PointerToSymbolTable >= AStream.Size then 75 | begin 76 | TPEImage(FPE).Msg.Write('[FileHeader] Bad PointerToSymbolTable (0x%x)', [FileHdr.PointerToSymbolTable]); 77 | exit; 78 | end; 79 | 80 | StrTableOfs := 81 | FileHdr.PointerToSymbolTable + 82 | FileHdr.NumberOfSymbols * SizeOf(TCOFFSymbolTable); 83 | 84 | if not StreamSeek(AStream, StrTableOfs) then 85 | exit; // table not found 86 | 87 | if not StreamPeek(AStream, cbStringData, SizeOf(cbStringData)) then 88 | exit; 89 | 90 | EndPos := AStream.Position + cbStringData; 91 | 92 | if EndPos > AStream.Size then 93 | exit; 94 | 95 | // Load string block. 96 | if cbStringData <> 0 then 97 | begin 98 | SetLength(FStrings, cbStringData); 99 | StreamRead(AStream, FStrings[0], cbStringData); 100 | end; 101 | end; 102 | 103 | end. 104 | -------------------------------------------------------------------------------- /PE.Common.pas: -------------------------------------------------------------------------------- 1 | unit PE.Common; 2 | 3 | interface 4 | 5 | {$MINENUMSIZE 4} 6 | 7 | { Base types } 8 | 9 | type 10 | Int8 = ShortInt; 11 | Int16 = SmallInt; 12 | Int32 = Integer; 13 | IntPtr = NativeInt; 14 | UInt8 = Byte; 15 | UInt16 = Word; 16 | UInt32 = Cardinal; 17 | 18 | Dword = UInt32; 19 | PDword = ^Dword; 20 | 21 | TVA = UInt64; 22 | TRVA = UInt64; 23 | 24 | PInt8 = ^Int8; 25 | PInt16 = ^Int16; 26 | PInt32 = ^Int32; 27 | PInt64 = ^Int64; 28 | 29 | PUInt8 = ^UInt8; 30 | PUInt16 = ^UInt16; 31 | PUInt32 = ^UInt32; 32 | PUInt64 = ^UInt64; 33 | 34 | TFileOffset = type UInt64; 35 | 36 | TParserFlag = ( 37 | PF_EXPORT, 38 | PF_IMPORT, 39 | PF_IMPORT_DELAYED, 40 | PF_RELOCS, 41 | PF_TLS, 42 | PF_RESOURCES 43 | ); 44 | 45 | TParserFlags = set of TParserFlag; 46 | 47 | TPEImageKind = ( 48 | PEIMAGE_KIND_DISK, 49 | PEIMAGE_KIND_MEMORY 50 | ); 51 | 52 | TPEImageObject = TObject; // Meant to cast TObject -> TPEImage 53 | 54 | TParserOption = ( 55 | // If section vsize is 0 try to use rsize instead. 56 | PO_SECTION_VSIZE_FALLBACK, 57 | 58 | // Rename non-alphanumeric section names. 59 | PO_SECTION_AUTORENAME_NON_ALPHANUMERIC, 60 | 61 | // If data directory is invalid directory RVA and Size nulled. 62 | PO_NULL_INVALID_DIRECTORY 63 | ); 64 | 65 | TParserOptions = set of TParserOption; 66 | 67 | const 68 | MAX_PATH_WIN = 260; 69 | 70 | SUSPICIOUS_MIN_LIMIT_EXPORTS = $10000; 71 | DEFAULT_SECTOR_SIZE = 512; 72 | DEFAULT_PAGE_SIZE = 4096; 73 | 74 | ALL_PARSER_FLAGS = [PF_EXPORT, PF_IMPORT, PF_IMPORT_DELAYED, PF_RELOCS, 75 | PF_TLS, PF_RESOURCES]; 76 | 77 | DEFAULT_PARSER_FLAGS = ALL_PARSER_FLAGS; 78 | DEFAULT_OPTIONS = [ 79 | PO_SECTION_VSIZE_FALLBACK, 80 | 81 | // This is disabled by default because now it can reject good names, like 82 | // .text, .data. In future this option must be either removed or reworked. 83 | // PO_SECTION_AUTORENAME_NON_ALPHANUMERIC, 84 | 85 | PO_NULL_INVALID_DIRECTORY 86 | ]; 87 | 88 | // Data directories. 89 | DDIR_EXPORT = 0; 90 | DDIR_IMPORT = 1; 91 | DDIR_RESOURCE = 2; 92 | DDIR_EXCEPTION = 3; 93 | DDIR_CERTIFICATE = 4; 94 | DDIR_RELOCATION = 5; 95 | DDIR_DEBUG = 6; 96 | DDIR_ARCHITECTURE = 7; 97 | DDIR_GLOBALPTR = 8; 98 | DDIR_TLS = 9; 99 | DDIR_LOADCONFIG = 10; 100 | DDIR_BOUNDIMPORT = 11; 101 | DDIR_IAT = 12; 102 | DDIR_DELAYIMPORT = 13; 103 | DDIR_CLRRUNTIMEHEADER = 14; 104 | 105 | DDIR_LAST = 14; 106 | 107 | type 108 | TParserResult = (PR_OK, PR_ERROR, PR_SUSPICIOUS); 109 | 110 | { Overlay } 111 | 112 | type 113 | TOverlay = packed record 114 | Offset: TFileOffset; 115 | Size: UInt64; 116 | end; 117 | 118 | POverlay = ^TOverlay; 119 | 120 | {$SCOPEDENUMS ON} 121 | TEndianness = (Little, Big); 122 | {$SCOPEDENUMS OFF} 123 | 124 | 125 | const 126 | SCategoryLoadFromFile = 'LoadFromFile'; 127 | SCategoryDOSHeader = 'DOS Header'; 128 | SCategorySections = 'Sections'; 129 | SCategoryDataDirecory = 'Data Directories'; 130 | SCategoryResources = 'Resources'; 131 | SCategoryImports = 'Imports'; 132 | SCategoryTLS = 'TLS'; 133 | SCategoryRelocs = 'Relocs'; 134 | 135 | implementation 136 | 137 | end. 138 | -------------------------------------------------------------------------------- /PE.ExportSym.pas: -------------------------------------------------------------------------------- 1 | { 2 | PE Exported symbols are case-sensitive. 3 | } 4 | unit PE.ExportSym; 5 | 6 | interface 7 | 8 | uses 9 | System.Generics.Collections, 10 | System.SysUtils, 11 | PE.Common; 12 | 13 | type 14 | 15 | { TPEExportSym } 16 | 17 | TPEExportSym = class 18 | public 19 | RVA: TRVA; 20 | Ordinal: uint32; 21 | Name: String; 22 | ForwarderName: String; 23 | Forwarder: boolean; 24 | 25 | // Is this symbol has RVA or is forwarder. 26 | function IsValid: boolean; inline; 27 | 28 | procedure Clear; 29 | function Clone: TPEExportSym; 30 | 31 | // Parse forwarder name of following structure: "LibName.FuncName". 32 | // Return true if both names before and after dot found. 33 | function GetForwarderLibAndFuncName(out Lib, Name: string): boolean; 34 | end; 35 | 36 | PPEExportSym = ^TPEExportSym; 37 | 38 | TPEExportSymVec = TList; 39 | TPEExportSymByRVA = TDictionary; 40 | 41 | { TPEExportSyms } 42 | 43 | TPEExportSyms = class 44 | private 45 | FItems: TPEExportSymVec; 46 | // FItemsByRVA: TPEExportSymByRVA; 47 | function GetCount: integer; 48 | procedure ExportSymNotify(Sender: TObject; const Item: TPEExportSym; 49 | Action: TCollectionNotification); 50 | public 51 | constructor Create; 52 | destructor Destroy; override; 53 | 54 | // Add item to list of symbols. 55 | // If SetOrdinal is True, src Item ordinal will be set to last sym number. 56 | procedure Add(Sym: TPEExportSym; SetOrdinal: boolean = false); 57 | 58 | // Add symbol by Name. 59 | procedure AddByName(RVA: TRVA; const Name: String); 60 | 61 | // Usually you don't need to set Ordinal, because ordinals are auto-incremented. 62 | procedure AddByOrdinal(RVA: TRVA; Ordinal: dword = 0); 63 | 64 | procedure AddForwarder(const Name, ForwarderName: String); 65 | 66 | procedure Clear; 67 | 68 | // Get item by RVA or nil if not found. 69 | // todo: there can be many exports with same RVA 70 | // function GetItemByRVA(RVA: TRVA): TPEExportSym; inline; 71 | 72 | property Count: integer read GetCount; 73 | property Items: TPEExportSymVec read FItems; 74 | end; 75 | 76 | implementation 77 | 78 | function TPEExportSym.Clone: TPEExportSym; 79 | begin 80 | result := TPEExportSym.Create; 81 | result.RVA := self.RVA; 82 | result.Ordinal := self.Ordinal; 83 | result.Name := self.Name; 84 | result.ForwarderName := self.ForwarderName; 85 | result.Forwarder := self.Forwarder; 86 | end; 87 | 88 | function TPEExportSym.GetForwarderLibAndFuncName(out Lib, Name: string): boolean; 89 | var 90 | arr: TArray; 91 | begin 92 | arr := string(ForwarderName).Split(['.']); 93 | result := length(arr) = 2; 94 | if result then 95 | begin 96 | Lib := arr[0]; 97 | name := arr[1]; 98 | end 99 | else 100 | begin 101 | Lib := ''; 102 | name := ''; 103 | end; 104 | end; 105 | 106 | function TPEExportSym.IsValid: boolean; 107 | begin 108 | // Either forwarder or has rva. 109 | result := Forwarder or (RVA <> 0); 110 | end; 111 | 112 | procedure TPEExportSym.Clear; 113 | begin 114 | RVA := 0; 115 | Ordinal := 0; 116 | Name := ''; 117 | ForwarderName := ''; 118 | Forwarder := false; 119 | end; 120 | 121 | { TExportSyms } 122 | 123 | procedure TPEExportSyms.Add(Sym: TPEExportSym; SetOrdinal: boolean = false); 124 | begin 125 | if SetOrdinal then 126 | Sym.Ordinal := FItems.Count + 1; 127 | FItems.Add(Sym); 128 | end; 129 | 130 | procedure TPEExportSyms.AddByName(RVA: TRVA; const Name: String); 131 | var 132 | Sym: TPEExportSym; 133 | begin 134 | Sym := TPEExportSym.Create; 135 | Sym.RVA := RVA; 136 | Sym.Name := Name; 137 | Add(Sym, True); 138 | end; 139 | 140 | procedure TPEExportSyms.AddByOrdinal(RVA: TRVA; Ordinal: dword); 141 | var 142 | Sym: TPEExportSym; 143 | begin 144 | Sym := TPEExportSym.Create; 145 | Sym.RVA := RVA; 146 | Sym.Ordinal := Ordinal; 147 | Add(Sym, Ordinal = 0); 148 | end; 149 | 150 | procedure TPEExportSyms.AddForwarder(const Name, ForwarderName: String); 151 | var 152 | Sym: TPEExportSym; 153 | begin 154 | Sym := TPEExportSym.Create; 155 | Sym.Name := Name; 156 | Sym.ForwarderName := ForwarderName; 157 | Sym.Forwarder := True; 158 | Add(Sym, True); 159 | end; 160 | 161 | procedure TPEExportSyms.Clear; 162 | begin 163 | FItems.Clear; 164 | // FItemsByRVA.Clear; 165 | end; 166 | 167 | constructor TPEExportSyms.Create; 168 | begin 169 | FItems := TPEExportSymVec.Create; 170 | FItems.OnNotify := ExportSymNotify; 171 | 172 | // FItemsByRVA := TPEExportSymByRVA.Create; 173 | end; 174 | 175 | destructor TPEExportSyms.Destroy; 176 | begin 177 | // FItemsByRVA.Free; 178 | FItems.Free; 179 | inherited; 180 | end; 181 | 182 | procedure TPEExportSyms.ExportSymNotify(Sender: TObject; 183 | const Item: TPEExportSym; Action: TCollectionNotification); 184 | begin 185 | if Action = cnRemoved then 186 | Item.Free; 187 | end; 188 | 189 | function TPEExportSyms.GetCount: integer; 190 | begin 191 | result := FItems.Count; 192 | end; 193 | 194 | // function TPEExportSyms.GetItemByRVA(RVA: TRVA): TPEExportSym; 195 | // begin 196 | // if not FItemsByRVA.TryGetValue(RVA, result) then 197 | // result := nil; 198 | // end; 199 | 200 | end. 201 | -------------------------------------------------------------------------------- /PE.FileHeaderToStr.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.FileHeaderToStr.pas -------------------------------------------------------------------------------- /PE.ID.pas: -------------------------------------------------------------------------------- 1 | { 2 | This is unit to parse PEID signature base text file. 3 | } 4 | unit PE.ID; 5 | 6 | interface 7 | 8 | uses 9 | System.Classes, 10 | System.Generics.Collections, 11 | System.SysUtils, 12 | 13 | PE.Common, 14 | PE.Image, 15 | PE.Section, 16 | PE.Search; 17 | 18 | type 19 | TPEIDSignature = record 20 | public 21 | Text: string; 22 | Pattern: TBytes; 23 | Mask: TBytes; 24 | end; 25 | 26 | TPEIDSignatureList = TList; 27 | 28 | { Class which contain PEID signatures. } 29 | TPEIDSignatures = class 30 | private 31 | FSigsEpOnly: TPEIDSignatureList; 32 | FSigsAnywhere: TPEIDSignatureList; 33 | public 34 | constructor Create; 35 | destructor Destroy; override; 36 | 37 | property SigsEpOnly: TPEIDSignatureList read FSigsEpOnly; 38 | property SigsAnywhere: TPEIDSignatureList read FSigsAnywhere; 39 | end; 40 | 41 | { 42 | Try to load signatures. 43 | Returns nil if failed or created class on success. 44 | } 45 | function PeidLoadSignatures(const SigFileName: string): TPEIDSignatures; 46 | 47 | { 48 | Scan section at RVA for Signatures and return Found signature names. 49 | } 50 | procedure PeidScanSection( 51 | PE: TPEImage; 52 | RVA: TRVA; 53 | Signatures: TPEIDSignatureList; 54 | Found: TStringList); 55 | 56 | procedure PeidScan( 57 | PE: TPEImage; 58 | Signatures: TPEIDSignatures; 59 | Found: TStringList); 60 | 61 | implementation 62 | 63 | { For string like "key = value" return value } 64 | function GetStrPairValue(const Line: string): string; 65 | var 66 | a: integer; 67 | begin 68 | a := Line.IndexOf('='); 69 | if a < 0 then 70 | result := '' 71 | else 72 | result := Line.Substring(a + 1).Trim; 73 | end; 74 | 75 | function PeidLoadSignatures(const SigFileName: string): TPEIDSignatures; 76 | var 77 | sl: TStringList; 78 | i: integer; 79 | Line, PatternText: string; 80 | sig: TPEIDSignature; 81 | EntryPointOnly: Boolean; 82 | begin 83 | result := nil; 84 | 85 | if not FileExists(SigFileName) then 86 | exit; 87 | 88 | { Do so simple parsing by loading into string list and parsing each line. } 89 | sl := TStringList.Create; 90 | try 91 | sl.LoadFromFile(SigFileName); 92 | i := 0; 93 | while i < sl.count do 94 | begin 95 | Line := sl[i]; 96 | 97 | { Skip empty lines and comments } 98 | if Line.IsEmpty or Line.StartsWith(';') then 99 | begin 100 | inc(i); 101 | continue; 102 | end; 103 | 104 | { Expect signature text like [Some text] } 105 | if Line.StartsWith('[') and Line.EndsWith(']') then 106 | begin 107 | { This is 3-line signature descriptor. Check if we have 3 lines. } 108 | if i + 3 > sl.count then 109 | begin 110 | break; 111 | end; 112 | 113 | { Check format of next 2 lines } 114 | if 115 | (not sl[i + 1].StartsWith('signature = ')) or 116 | (not sl[i + 2].StartsWith('ep_only = ')) then 117 | begin 118 | break; 119 | end; 120 | 121 | { Check pattern format is correct and get fields. } 122 | PatternText := GetStrPairValue(sl[i + 1]); 123 | 124 | if not StringToPattern(PatternText, sig.Pattern, sig.Mask) then 125 | begin 126 | break; 127 | end; 128 | 129 | sig.Text := Line.Substring(1, Line.Length - 2); 130 | 131 | EntryPointOnly := GetStrPairValue(sl[i + 2]).ToLower = 'true'; 132 | 133 | { Got at least one signature, add it to list } 134 | if not Assigned(result) then 135 | result := TPEIDSignatures.Create; 136 | 137 | if EntryPointOnly then 138 | result.FSigsEpOnly.Add(sig) 139 | else 140 | result.FSigsAnywhere.Add(sig); 141 | 142 | inc(i, 3); 143 | end; 144 | end; 145 | 146 | if Assigned(result) then 147 | begin 148 | result.FSigsEpOnly.TrimExcess; 149 | result.FSigsAnywhere.TrimExcess; 150 | end; 151 | finally 152 | sl.Free; 153 | end; 154 | end; 155 | 156 | procedure PeidScanSection( 157 | PE: TPEImage; 158 | RVA: TRVA; 159 | Signatures: TPEIDSignatureList; 160 | Found: TStringList); 161 | var 162 | sec: TPESection; 163 | sig: TPEIDSignature; 164 | offset: uint32; 165 | begin 166 | if not PE.RVAToSec(RVA, @sec) then 167 | exit; 168 | 169 | for sig in Signatures do 170 | begin 171 | offset := RVA - sec.RVA; 172 | if SearchBytes(sec, sig.Pattern, sig.Mask, offset, 0) then 173 | begin 174 | Found.Add(sig.Text); 175 | end; 176 | end; 177 | end; 178 | 179 | procedure PeidScan( 180 | PE: TPEImage; 181 | Signatures: TPEIDSignatures; 182 | Found: TStringList); 183 | var 184 | sec: TPESection; 185 | sig: TPEIDSignature; 186 | offset: uint32; 187 | begin 188 | if PE.Sections.count = 0 then 189 | exit; 190 | 191 | { First try EPOnly signatures } 192 | PeidScanSection(PE, PE.EntryPointRVA, Signatures.SigsEpOnly, Found); 193 | 194 | sec := PE.Sections.First; 195 | 196 | for sig in Signatures.SigsAnywhere do 197 | begin 198 | offset := 0; 199 | while SearchBytes(sec, sig.Pattern, sig.Mask, offset, 1) do 200 | begin 201 | inc(offset); 202 | Found.Add(sig.Text); 203 | end; 204 | end; 205 | end; 206 | 207 | { TPEIDSignatures } 208 | 209 | constructor TPEIDSignatures.Create; 210 | begin 211 | inherited Create; 212 | FSigsEpOnly := TPEIDSignatureList.Create; 213 | FSigsAnywhere := TPEIDSignatureList.Create; 214 | end; 215 | 216 | destructor TPEIDSignatures.Destroy; 217 | begin 218 | FSigsEpOnly.Free; 219 | FSigsAnywhere.Free; 220 | inherited; 221 | end; 222 | 223 | end. 224 | -------------------------------------------------------------------------------- /PE.Image.Defaults.pas: -------------------------------------------------------------------------------- 1 | unit PE.Image.Defaults; 2 | 3 | interface 4 | 5 | type 6 | TPEDefaults = record 7 | private 8 | FPE: TObject; 9 | public 10 | constructor Create(PEImage: TObject); 11 | 12 | procedure SetImageBits; 13 | procedure SetFileAlignment; 14 | procedure SetSectionAlignment; 15 | procedure SetFileHdr; 16 | procedure SetOptionalHeader; 17 | procedure SetLFANew; 18 | 19 | procedure SetAll; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | // Expand 26 | PE.Headers, 27 | // 28 | PE.Common, 29 | PE.Image, 30 | PE.Types.DOSHeader, 31 | PE.Types.FileHeader; 32 | 33 | { TPEDefaults } 34 | 35 | constructor TPEDefaults.Create(PEImage: TObject); 36 | begin 37 | FPE := PEImage; 38 | end; 39 | 40 | procedure TPEDefaults.SetAll; 41 | begin 42 | SetLFANew; 43 | SetImageBits; 44 | SetFileAlignment; 45 | SetSectionAlignment; 46 | SetFileHdr; 47 | SetOptionalHeader; 48 | 49 | TPEImage(FPE).DataDirectories.Put(15, 0, 0); // 16 directories by default 50 | end; 51 | 52 | procedure TPEDefaults.SetFileAlignment; 53 | begin 54 | with TPEImage(FPE) do 55 | if FileAlignment = 0 then 56 | FileAlignment := DEFAULT_SECTOR_SIZE; 57 | end; 58 | 59 | procedure TPEDefaults.SetFileHdr; 60 | begin 61 | with TPEImage(FPE).FileHeader^ do 62 | begin 63 | if Machine = 0 then 64 | Machine := IMAGE_FILE_MACHINE_I386; 65 | if Characteristics = 0 then 66 | Characteristics := IMAGE_FILE_RELOCS_STRIPPED + 67 | IMAGE_FILE_EXECUTABLE_IMAGE + IMAGE_FILE_32BIT_MACHINE; 68 | end; 69 | end; 70 | 71 | procedure TPEDefaults.SetImageBits; 72 | begin 73 | with TPEImage(FPE) do 74 | if ImageBits = 0 then 75 | ImageBits := 32; 76 | end; 77 | 78 | procedure TPEDefaults.SetLFANew; 79 | begin 80 | if TPEImage(FPE).LFANew = 0 then 81 | TPEImage(FPE).LFANew := SizeOf(TImageDosHeader); 82 | end; 83 | 84 | procedure TPEDefaults.SetOptionalHeader; 85 | begin 86 | with TPEImage(FPE).OptionalHeader^ do 87 | begin 88 | if MajorSubsystemVersion = 0 then 89 | MajorSubsystemVersion := 4; 90 | if SizeOfStackCommit = 0 then 91 | SizeOfStackCommit := $1000; 92 | if SizeOfHeapReserve = 0 then 93 | SizeOfHeapReserve := $100000; 94 | if Subsystem = 0 then 95 | Subsystem := IMAGE_SUBSYSTEM_WINDOWS_GUI; 96 | if ImageBase = 0 then 97 | ImageBase := $400000; 98 | end; 99 | end; 100 | 101 | procedure TPEDefaults.SetSectionAlignment; 102 | begin 103 | with TPEImage(FPE) do 104 | if SectionAlignment = 0 then 105 | SectionAlignment := DEFAULT_PAGE_SIZE; 106 | end; 107 | 108 | end. 109 | -------------------------------------------------------------------------------- /PE.Image.Saving.pas: -------------------------------------------------------------------------------- 1 | {$WARN COMBINING_SIGNED_UNSIGNED OFF} 2 | unit PE.Image.Saving; 3 | 4 | interface 5 | 6 | uses 7 | System.Classes, 8 | System.SysUtils; 9 | 10 | // If SavingModifiedImage is True some fields like header size and image size 11 | // are recalculated and updated. If it's False it's assumed image is already 12 | // built (or just loaded) and only headers writing done. 13 | function SaveHeaders(APE: TObject; AStream: TStream; SavingModifiedImage: boolean): boolean; 14 | 15 | function SaveImageToStream(APE: TObject; AStream: TStream): boolean; 16 | 17 | implementation 18 | 19 | uses 20 | // To expand. 21 | PE.Headers, 22 | PE.Common, 23 | PE.DataDirectories, 24 | // 25 | PE.Types.DOSHeader, 26 | PE.Types.FileHeader, 27 | PE.Types.NTHeaders, 28 | PE.Types.OptionalHeader, 29 | PE.Types.Directories, 30 | PE.Types.Sections, 31 | PE.Image, 32 | PE.Section, 33 | PE.Sections, 34 | PE.Utils, 35 | 36 | PE.Build.Export; 37 | 38 | { DOS } 39 | 40 | function DoDosHdr(PE: TPEImage; AStream: TStream): boolean; 41 | var 42 | h: PImageDOSHeader; 43 | DosBlockSize: integer; 44 | begin 45 | h := PE.DOSHeader; 46 | h^.e_magic.SetMZ; 47 | h^.e_lfanew := PE.LFANew; 48 | 49 | // Write DOS header. 50 | Result := StreamWrite(AStream, h^, SizeOf(h^)); 51 | 52 | // Write DOS block. 53 | DosBlockSize := Length(PE.DosBlock); 54 | if DosBlockSize <> 0 then 55 | StreamWrite(AStream, PE.DosBlock[0], DosBlockSize); 56 | end; 57 | 58 | { NT } 59 | 60 | function DoFileHdr(PE: TPEImage; AStream: TStream): boolean; 61 | begin 62 | if StreamWrite(AStream, PE00_SIGNATURE, SizeOf(PE00_SIGNATURE)) then 63 | if StreamWrite(AStream, PE.FileHeader^, SizeOf(PE.FileHeader^)) then 64 | exit(true); 65 | exit(false); 66 | end; 67 | 68 | { Optional } 69 | 70 | function DoOptHdrAndDirs(PE: TPEImage; AStream: TStream): boolean; 71 | var 72 | OptHdrSize: integer; 73 | DDirSize: integer; 74 | begin 75 | // Update # of dirs. 76 | PE.OptionalHeader.NumberOfRvaAndSizes := PE.DataDirectories.Count; 77 | // Write optional header. 78 | OptHdrSize := PE.OptionalHeader.WriteToStream(AStream, PE.ImageBits, -1); 79 | // Write dirs. 80 | DDirSize := PE.DataDirectories.SaveDirectoriesToStream(AStream); 81 | // Update size of opt. hdr. 82 | PE.FileHeader.SizeOfOptionalHeader := OptHdrSize + DDirSize; 83 | 84 | Result := PE.FileHeader.SizeOfOptionalHeader <> 0; 85 | end; 86 | 87 | { Sec Hdr } 88 | 89 | procedure FillSecHdrRawOfs(PE: TPEImage; ofsSecHdr: uint32); 90 | var 91 | sec: TPESection; 92 | ofs: uint64; 93 | begin 94 | ofs := ofsSecHdr + PE.Sections.Count * SizeOf(TImageSectionHeader); 95 | for sec in PE.Sections do 96 | begin 97 | // Process only TPESection. 98 | if sec.ClassType <> TPESection then 99 | continue; 100 | 101 | ofs := AlignUp(ofs, PE.FileAlignment); 102 | sec.RawOffset := ofs; 103 | inc(ofs, sec.RawSize); 104 | end; 105 | end; 106 | 107 | function DoSecHdr(PE: TPEImage; AStream: TStream): boolean; 108 | var 109 | Sec: TPESection; 110 | h: TImageSectionHeader; 111 | begin 112 | for Sec in PE.Sections do 113 | begin 114 | // Process only TPESection. 115 | if sec.ClassType <> TPESection then 116 | continue; 117 | 118 | h := Sec.ImageSectionHeader; 119 | if not StreamWrite(AStream, h, SizeOf(h)) then 120 | exit(False); 121 | end; 122 | exit(true); 123 | end; 124 | 125 | function DoSecHdrGap(PE: TPEImage; AStream: TStream): boolean; 126 | var 127 | size: integer; 128 | begin 129 | size := Length(PE.SecHdrGap); 130 | if size <> 0 then 131 | if not StreamWrite(AStream, PE.SecHdrGap[0], size) then 132 | exit(False); 133 | exit(true); 134 | end; 135 | 136 | procedure DoSecData(PE: TPEImage; AStream: TStream); 137 | var 138 | sec: TPESection; 139 | SizeToWrite: uint32; 140 | PaddingSize: uint32; 141 | begin 142 | for sec in PE.Sections do 143 | begin 144 | // Process only TPESection. 145 | if sec.ClassType <> TPESection then 146 | continue; 147 | 148 | StreamSeekWithPadding(AStream, sec.RawOffset); 149 | 150 | if sec.RawSize > sec.VirtualSize then 151 | begin 152 | SizeToWrite := sec.VirtualSize; 153 | PaddingSize := sec.RawSize - sec.VirtualSize; 154 | end 155 | else 156 | begin 157 | SizeToWrite := sec.RawSize; 158 | PaddingSize := 0; 159 | end; 160 | 161 | AStream.Write(sec.Mem^, SizeToWrite); 162 | WritePattern(AStream, PaddingSize, nil, 0); 163 | end; 164 | end; 165 | 166 | function SaveHeaders(APE: TObject; AStream: TStream; SavingModifiedImage: boolean): boolean; 167 | var 168 | PE: TPEImage; 169 | ofsFileHdr, ofsSecHdr: uint32; 170 | begin 171 | Result := False; 172 | 173 | PE := TPEImage(APE); 174 | 175 | // save dos 176 | if not DoDosHdr(PE, AStream) then 177 | exit; 178 | 179 | ofsFileHdr := PE.LFANew; 180 | 181 | // skip file header now 182 | if not StreamSeek(AStream, ofsFileHdr + SizeOf(TImageFileHeader) + 4) then 183 | exit; 184 | 185 | if SavingModifiedImage then 186 | begin 187 | // update size of image header 188 | PE.FixSizeOfImage; 189 | 190 | // update size of headers 191 | PE.FixSizeOfHeaders; 192 | end; 193 | 194 | // save optional 195 | if not DoOptHdrAndDirs(PE, AStream) then 196 | exit; 197 | 198 | ofsSecHdr := AStream.Position; 199 | 200 | // now write file header 201 | if not StreamSeek(AStream, ofsFileHdr) then 202 | exit; 203 | if not DoFileHdr(PE, AStream) then 204 | exit; 205 | 206 | // go back to sec hdr 207 | if not StreamSeek(AStream, ofsSecHdr) then 208 | exit; 209 | 210 | if SavingModifiedImage then 211 | begin 212 | // Fill RawData offsets for Section Headers. 213 | FillSecHdrRawOfs(PE, ofsSecHdr); 214 | end; 215 | 216 | // write sec hdr 217 | if not DoSecHdr(PE, AStream) then 218 | exit; 219 | 220 | exit(true); 221 | end; 222 | 223 | function SaveImageToStream(APE: TObject; AStream: TStream): boolean; 224 | var 225 | PE: TPEImage; 226 | begin 227 | Result := False; 228 | 229 | PE := TPEImage(APE); 230 | 231 | // Ensure we have all needed values set. 232 | PE.Defaults.SetAll; 233 | 234 | if not SaveHeaders(PE, AStream, true) then 235 | exit; 236 | 237 | // write sec hdr gap 238 | DoSecHdrGap(PE, AStream); 239 | 240 | // write sec data 241 | DoSecData(PE, AStream); 242 | 243 | Result := true; 244 | end; 245 | 246 | end. 247 | -------------------------------------------------------------------------------- /PE.Image.x86.pas: -------------------------------------------------------------------------------- 1 | { 2 | * 3 | * Class for X86, X86-64 specifics. 4 | * 5 | } 6 | unit PE.Image.x86; 7 | 8 | interface 9 | 10 | uses 11 | System.Generics.Collections, 12 | System.SysUtils, 13 | PE.Common, 14 | PE.Image, 15 | PE.Section; 16 | 17 | type 18 | // Buf points to data which should be matched. 19 | // Set MatchedSize to size of matched sequence. If nothing matched don't set it. 20 | // VA is address to be matched. 21 | // Size is size left in scanned region you can check starting from VA. 22 | TPatternMatchFunc = reference to procedure(VA: TVA; Size: integer; Buf: PByte; var MatchedSize: integer); 23 | 24 | TPEImageX86 = class(TPEImage) 25 | protected 26 | // Find relative jump or call in section, e.g e8,x,x,x,x or e9,x,x,x,x. 27 | // List must be created before passing it to the function. 28 | // Found VAs will be appended to list. 29 | function FindRelativeJumpInternal( 30 | const Sec: TPESection; 31 | ByteOpcode: Byte; 32 | TargetVA: TVA; 33 | const List: TList): boolean; 34 | public 35 | function FindRelativeJump( 36 | const Sec: TPESection; 37 | TargetVA: TVA; 38 | const List: TList): boolean; 39 | 40 | function FindRelativeCall( 41 | const Sec: TPESection; 42 | TargetVA: TVA; 43 | const List: TList): boolean; 44 | 45 | // Fill Count bytes at VA with nops (0x90). 46 | // Result is number of nops written. 47 | function Nop(VA: TVA; Count: integer = 1): UInt32; 48 | 49 | // Nop range. 50 | // BeginVA: inclusive 51 | // EndVA: exclusive 52 | function NopRange(BeginVA, EndVA: TVA): UInt32; inline; 53 | 54 | // Nop Call or Jump. 55 | function NopCallOrJump(VA: TVA): boolean; 56 | 57 | // Write call or jump, like: 58 | // E8/E9 xx xx xx xx 59 | // IsCall: True - call, False - jump. 60 | function WriteRelCallOrJump(SrcVA, DstVA: TVA; IsCall: boolean): boolean; 61 | 62 | // Perform custom pattern matching scan for Sec section. 63 | // All found addresses are stored in List (if it is not nil, otherwise 64 | // user must handle it manually). 65 | // PatternMatchFunc function used to match pattern. 66 | // VA0, Size define range (optional). Whole section is scanned by default. 67 | function ScanRange( 68 | const Sec: TPESection; 69 | PatternMatchFunc: TPatternMatchFunc; 70 | const List: TList = nil; 71 | VA: TVA = 0; 72 | Size: integer = 0 73 | ): boolean; 74 | end; 75 | 76 | implementation 77 | 78 | const 79 | OPCODE_NOP = $90; 80 | OPCODE_CALL_REL = $E8; 81 | OPCODE_JUMP_REL = $E9; 82 | 83 | { TPEImageX86 } 84 | 85 | function TPEImageX86.FindRelativeCall( 86 | const Sec: TPESection; 87 | TargetVA: TVA; 88 | const List: TList): boolean; 89 | begin 90 | Result := FindRelativeJumpInternal(Sec, OPCODE_CALL_REL, TargetVA, List); 91 | end; 92 | 93 | function TPEImageX86.FindRelativeJump( 94 | const Sec: TPESection; 95 | TargetVA: TVA; 96 | const List: TList): boolean; 97 | begin 98 | Result := FindRelativeJumpInternal(Sec, OPCODE_JUMP_REL, TargetVA, List); 99 | end; 100 | 101 | function TPEImageX86.FindRelativeJumpInternal( 102 | const Sec: TPESection; 103 | ByteOpcode: Byte; 104 | TargetVA: TVA; 105 | const List: TList): boolean; 106 | var 107 | curVa, VA0, VA1, tstVa: TVA; 108 | delta: int32; 109 | opc: Byte; 110 | begin 111 | Result := False; 112 | 113 | VA0 := RVAToVA(Sec.RVA); 114 | VA1 := RVAToVA(Sec.GetEndRVA - SizeOf(ByteOpcode) - SizeOf(delta)); 115 | 116 | if not SeekVA(VA0) then 117 | exit(False); 118 | 119 | while self.PositionVA <= VA1 do 120 | begin 121 | curVa := self.PositionVA; 122 | 123 | // get opcode 124 | if Read(@opc, SizeOf(ByteOpcode)) <> SizeOf(ByteOpcode) then 125 | exit; 126 | if opc = ByteOpcode then 127 | // on found probably jmp/call 128 | begin 129 | delta := int32(ReadUInt32); 130 | tstVa := curVa + SizeOf(ByteOpcode) + SizeOf(delta) + delta; 131 | if tstVa = TargetVA then 132 | begin // hit 133 | List.Add(curVa); 134 | Result := True; // at least 1 result is ok 135 | end 136 | else 137 | begin 138 | if not SeekVA(curVa + SizeOf(ByteOpcode)) then 139 | exit; 140 | end; 141 | end; 142 | end; 143 | end; 144 | 145 | function TPEImageX86.Nop(VA: TVA; Count: integer): UInt32; 146 | begin 147 | Result := Sections.FillMemory(VAToRVA(VA), Count, OPCODE_NOP); 148 | end; 149 | 150 | function TPEImageX86.NopRange(BeginVA, EndVA: TVA): UInt32; 151 | begin 152 | if EndVA > BeginVA then 153 | Result := Nop(BeginVA, EndVA - BeginVA) 154 | else 155 | Result := 0; 156 | end; 157 | 158 | function TPEImageX86.ScanRange( 159 | const Sec: TPESection; 160 | PatternMatchFunc: TPatternMatchFunc; 161 | const List: TList; 162 | VA: TVA; 163 | Size: integer): boolean; 164 | var 165 | Buf: PByte; 166 | MatchedSize: integer; 167 | begin 168 | // Define range. 169 | if VA = 0 then 170 | VA := RVAToVA(Sec.RVA); 171 | if Size = 0 then 172 | Size := Sec.VirtualSize; 173 | 174 | // Start scan at VA0. 175 | Buf := self.VAToMem(VA); 176 | if Buf = nil then 177 | exit(False); // such address not found 178 | 179 | while Size > 0 do 180 | begin 181 | MatchedSize := 0; 182 | PatternMatchFunc(VA, Size, Buf, MatchedSize); 183 | if MatchedSize <> 0 then 184 | begin 185 | if Assigned(List) then 186 | List.Add(VA); 187 | end 188 | else 189 | MatchedSize := 1; 190 | 191 | inc(Buf, MatchedSize); 192 | inc(VA, MatchedSize); 193 | dec(Size, MatchedSize); 194 | end; 195 | 196 | exit(True); 197 | end; 198 | 199 | function TPEImageX86.NopCallOrJump(VA: TVA): boolean; 200 | begin 201 | Result := Sections.FillMemoryEx(VAToRVA(VA), 5, True, OPCODE_NOP) = 5; 202 | end; 203 | 204 | function TPEImageX86.WriteRelCallOrJump(SrcVA, DstVA: TVA; IsCall: boolean): boolean; 205 | type 206 | TJump = packed record 207 | Opcode: Byte; 208 | delta: integer; 209 | end; 210 | var 211 | jmp: TJump; 212 | 213 | begin 214 | if IsCall then 215 | jmp.Opcode := OPCODE_CALL_REL 216 | else 217 | jmp.Opcode := OPCODE_JUMP_REL; 218 | jmp.delta := DstVA - (SrcVA + SizeOf(TJump)); 219 | self.PositionVA := SrcVA; 220 | Result := self.WriteEx(@jmp, SizeOf(TJump)); 221 | end; 222 | 223 | end. 224 | -------------------------------------------------------------------------------- /PE.Imports.Func.pas: -------------------------------------------------------------------------------- 1 | unit PE.Imports.Func; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | System.SysUtils, 8 | PE.Common; 9 | 10 | type 11 | TPEImportFunction = class 12 | public 13 | Ordinal: uint16; 14 | Name: String; 15 | procedure Clear; inline; 16 | constructor CreateEmpty; 17 | constructor Create(const Name: String; Ordinal: uint16 = 0); 18 | end; 19 | 20 | TPEImportFunctionDelayed = class(TPEImportFunction) 21 | public 22 | end; 23 | 24 | TPEImportFunctions = TObjectList; 25 | 26 | implementation 27 | 28 | { TImportFunction } 29 | 30 | procedure TPEImportFunction.Clear; 31 | begin 32 | self.Ordinal := 0; 33 | self.Name := ''; 34 | end; 35 | 36 | constructor TPEImportFunction.Create(const Name: String; Ordinal: uint16); 37 | begin 38 | self.Name := Name; 39 | self.Ordinal := Ordinal; 40 | end; 41 | 42 | constructor TPEImportFunction.CreateEmpty; 43 | begin 44 | end; 45 | 46 | end. 47 | -------------------------------------------------------------------------------- /PE.Imports.Lib.pas: -------------------------------------------------------------------------------- 1 | unit PE.Imports.Lib; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | 9 | PE.Common, 10 | PE.Imports.Func; 11 | 12 | type 13 | TPEImportLibrary = class 14 | private 15 | FName: String; // imported library name 16 | FBound: Boolean; 17 | FFunctions: TPEImportFunctions; 18 | FTimeDateStamp: uint32; 19 | FOriginal: boolean; 20 | procedure CheckAddingToOriginalLib; 21 | public 22 | // Relative address of IAT region for this library. 23 | // It is address of first word in array of words (4/8 bytes) corresponding 24 | // to each imported function in same order as in Functions list. 25 | // 26 | // Each RVA is patched by loader if it's mapped into process memory. 27 | // 28 | // If image is not bound loader get address of function and write it at RVA. 29 | // If image is bound nothing changed because value at RVA is already set. 30 | // 31 | // This value is modified when import directory parsed on loading or 32 | // when import directory is rebuilt. 33 | IatRva: TRVA; 34 | 35 | constructor Create(const AName: String; Bound: Boolean = False; Original: Boolean = False); 36 | destructor Destroy; override; 37 | 38 | function NewFunction(const Name: string): TPEImportFunction; overload; 39 | function NewFunction(Ordinal: uint16): TPEImportFunction; overload; 40 | 41 | property Name: String read FName; 42 | 43 | // List of imported functions. 44 | // Order must be kept to match array of words at IatRva. 45 | property Functions: TPEImportFunctions read FFunctions; 46 | 47 | property Bound: Boolean read FBound; 48 | property TimeDateStamp: uint32 read FTimeDateStamp write FTimeDateStamp; 49 | 50 | // True if it is library parsed from executable. 51 | // You can't add new functions to this library, because IAT must stay untouched. 52 | // Add new library instead. 53 | property Original: boolean read FOriginal; 54 | end; 55 | 56 | implementation 57 | 58 | { TImportLibrary } 59 | 60 | constructor TPEImportLibrary.Create(const AName: String; Bound: Boolean; Original: Boolean); 61 | begin 62 | inherited Create; 63 | FFunctions := TPEImportFunctions.Create; 64 | FName := AName; 65 | FBound := Bound; 66 | FOriginal := Original; 67 | end; 68 | 69 | destructor TPEImportLibrary.Destroy; 70 | begin 71 | FFunctions.Free; 72 | inherited; 73 | end; 74 | 75 | procedure TPEImportLibrary.CheckAddingToOriginalLib(); 76 | begin 77 | if (Original) then 78 | raise Exception.Create('You can''t add new function to original library.'); 79 | end; 80 | 81 | function TPEImportLibrary.NewFunction(const Name: string): TPEImportFunction; 82 | begin 83 | CheckAddingToOriginalLib(); 84 | Result := TPEImportFunction.Create(Name); 85 | FFunctions.Add(Result); 86 | end; 87 | 88 | function TPEImportLibrary.NewFunction(Ordinal: uint16): TPEImportFunction; 89 | begin 90 | CheckAddingToOriginalLib(); 91 | Result := TPEImportFunction.Create('', Ordinal); 92 | FFunctions.Add(Result); 93 | end; 94 | 95 | end. 96 | -------------------------------------------------------------------------------- /PE.Imports.pas: -------------------------------------------------------------------------------- 1 | unit PE.Imports; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | System.SysUtils, 8 | 9 | PE.Common, 10 | PE.Imports.Func, 11 | PE.Imports.Lib; 12 | 13 | type 14 | TPEImportLibraryObjectList = TObjectList; 15 | 16 | TPEImport = class 17 | private 18 | FLibs: TPEImportLibraryObjectList; 19 | public 20 | constructor Create; 21 | destructor Destroy; override; 22 | 23 | procedure Clear; 24 | 25 | function Add(Lib: TPEImportLibrary): TPEImportLibrary; inline; 26 | function NewLib(const Name: string): TPEImportLibrary; 27 | 28 | property Libs: TPEImportLibraryObjectList read FLibs; 29 | end; 30 | 31 | implementation 32 | 33 | { TPEImports } 34 | 35 | constructor TPEImport.Create; 36 | begin 37 | inherited Create; 38 | FLibs := TPEImportLibraryObjectList.Create; 39 | end; 40 | 41 | destructor TPEImport.Destroy; 42 | begin 43 | FLibs.Free; 44 | inherited; 45 | end; 46 | 47 | function TPEImport.NewLib(const Name: string): TPEImportLibrary; 48 | begin 49 | result := Add(TPEImportLibrary.Create(Name)); 50 | end; 51 | 52 | procedure TPEImport.Clear; 53 | begin 54 | FLibs.Clear; 55 | end; 56 | 57 | function TPEImport.Add(Lib: TPEImportLibrary): TPEImportLibrary; 58 | begin 59 | FLibs.Add(Lib); 60 | result := Lib; 61 | end; 62 | 63 | end. 64 | -------------------------------------------------------------------------------- /PE.MemoryStream.pas: -------------------------------------------------------------------------------- 1 | { 2 | Memory Stream based on already mapped PE image in current process. 3 | Basically it's TMemoryStream with Memory pointing to ImageBase and Size equal 4 | to SizeOfImage. 5 | } 6 | unit PE.MemoryStream; 7 | 8 | interface 9 | 10 | uses 11 | System.Classes, 12 | System.SysUtils; 13 | 14 | type 15 | TPECustomMemoryStream = class(TStream) 16 | protected 17 | FMemory: Pointer; 18 | FSize, FPosition: NativeInt; 19 | public 20 | constructor CreateFromPointer(Ptr: Pointer; Size: integer); 21 | 22 | procedure SetPointer(Ptr: Pointer; const Size: NativeInt); 23 | 24 | function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override; 25 | 26 | function Read(var Buffer; Count: Longint): Longint; override; 27 | function Write(const Buffer; Count: integer): integer; override; 28 | 29 | procedure SaveToStream(Stream: TStream); virtual; 30 | procedure SaveToFile(const FileName: string); 31 | 32 | property Memory: Pointer read FMemory; 33 | end; 34 | 35 | TPEMemoryStream = class(TPECustomMemoryStream) 36 | private 37 | FModuleToUnload: HMODULE; // needed if module loading is forced. 38 | FModuleFileName: string; 39 | FModuleSize: uint32; 40 | private 41 | procedure CreateFromModulePtr(ModulePtr: Pointer); 42 | public 43 | // Create stream from module in current process. 44 | // If module is not found exception raised. 45 | // To force loading module set ForceLoadingModule to True. 46 | constructor Create(const ModuleName: string; ForceLoadingModule: boolean = False); overload; 47 | 48 | // Create from module by known base address. 49 | constructor Create(ModuleBase: NativeUInt); overload; 50 | 51 | destructor Destroy; override; 52 | 53 | // Simply read SizeOfImage from memory. 54 | class function GetModuleImageSize(ModulePtr: PByte): uint32; static; 55 | end; 56 | 57 | implementation 58 | 59 | uses 60 | WinApi.Windows, 61 | 62 | PE.Types.DosHeader, 63 | PE.Types.NTHeaders; 64 | 65 | { TPECustomMemoryStream } 66 | 67 | procedure TPECustomMemoryStream.SetPointer(Ptr: Pointer; const Size: NativeInt); 68 | begin 69 | FMemory := Ptr; 70 | FSize := Size; 71 | FPosition := 0; 72 | end; 73 | 74 | constructor TPECustomMemoryStream.CreateFromPointer(Ptr: Pointer; Size: integer); 75 | begin 76 | inherited Create; 77 | SetPointer(Ptr, Size); 78 | end; 79 | 80 | procedure TPECustomMemoryStream.SaveToFile(const FileName: string); 81 | var 82 | Stream: TStream; 83 | begin 84 | Stream := TFileStream.Create(FileName, fmCreate); 85 | try 86 | SaveToStream(Stream); 87 | finally 88 | Stream.Free; 89 | end; 90 | end; 91 | 92 | procedure TPECustomMemoryStream.SaveToStream(Stream: TStream); 93 | begin 94 | if FSize <> 0 then 95 | Stream.WriteBuffer(FMemory^, FSize); 96 | end; 97 | 98 | function TPECustomMemoryStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; 99 | begin 100 | case Origin of 101 | soBeginning: 102 | FPosition := Offset; 103 | soCurrent: 104 | Inc(FPosition, Offset); 105 | soEnd: 106 | FPosition := FSize + Offset; 107 | end; 108 | Result := FPosition; 109 | end; 110 | 111 | function TPECustomMemoryStream.Read(var Buffer; Count: integer): Longint; 112 | begin 113 | if Count = 0 then 114 | Exit(0); 115 | Result := FSize - FPosition; 116 | if Result > 0 then 117 | begin 118 | if Result > Count then 119 | Result := Count; 120 | Move(PByte(FMemory)[FPosition], Buffer, Result); 121 | Inc(FPosition, Result); 122 | end; 123 | end; 124 | 125 | function TPECustomMemoryStream.Write(const Buffer; Count: integer): integer; 126 | begin 127 | if Count = 0 then 128 | Exit(0); 129 | Result := FSize - FPosition; 130 | if Result > 0 then 131 | begin 132 | if Result > Count then 133 | Result := Count; 134 | Move(Buffer, PByte(FMemory)[FPosition], Result); 135 | Inc(FPosition, Result); 136 | end; 137 | end; 138 | 139 | { TPEMemoryStream } 140 | 141 | procedure TPEMemoryStream.CreateFromModulePtr(ModulePtr: Pointer); 142 | begin 143 | if ModulePtr = nil then 144 | raise Exception.CreateFmt('Module "%s" not found in address space', 145 | [FModuleFileName]); 146 | 147 | FModuleSize := TPEMemoryStream.GetModuleImageSize(ModulePtr); 148 | 149 | SetPointer(ModulePtr, FModuleSize); 150 | end; 151 | 152 | constructor TPEMemoryStream.Create(const ModuleName: string; 153 | ForceLoadingModule: boolean); 154 | var 155 | FModulePtr: Pointer; 156 | begin 157 | inherited Create; 158 | FModuleFileName := ModuleName; 159 | 160 | FModulePtr := Pointer(GetModuleHandle(PChar(ModuleName))); 161 | FModuleToUnload := 0; 162 | 163 | if (FModulePtr = nil) and (ForceLoadingModule) then 164 | begin 165 | FModuleToUnload := LoadLibrary(PChar(ModuleName)); 166 | FModulePtr := Pointer(FModuleToUnload); 167 | end; 168 | 169 | CreateFromModulePtr(FModulePtr); 170 | end; 171 | 172 | constructor TPEMemoryStream.Create(ModuleBase: NativeUInt); 173 | begin 174 | inherited Create; 175 | FModuleFileName := GetModuleName(ModuleBase); 176 | 177 | FModuleToUnload := 0; // we didn't load it and won't free it 178 | 179 | CreateFromModulePtr(Pointer(ModuleBase)); 180 | end; 181 | 182 | destructor TPEMemoryStream.Destroy; 183 | begin 184 | if FModuleToUnload <> 0 then 185 | FreeLibrary(FModuleToUnload); 186 | inherited; 187 | end; 188 | 189 | class function TPEMemoryStream.GetModuleImageSize(ModulePtr: PByte): uint32; 190 | var 191 | dos: PImageDOSHeader; 192 | nt: PImageNTHeaders; 193 | begin 194 | dos := PImageDOSHeader(ModulePtr); 195 | 196 | if not dos.e_magic.IsMZ then 197 | raise Exception.Create('Not PE image'); 198 | 199 | nt := PImageNTHeaders(ModulePtr + dos^.e_lfanew); 200 | 201 | if not nt.Signature.IsPE00 then 202 | raise Exception.Create('Not PE image'); 203 | 204 | Result := nt^.OptionalHeader.pe32.SizeOfImage; 205 | end; 206 | 207 | end. 208 | -------------------------------------------------------------------------------- /PE.Msg.pas: -------------------------------------------------------------------------------- 1 | unit PE.Msg; 2 | 3 | interface 4 | 5 | type 6 | TMsgProc = procedure(Text: PWideChar); stdcall; 7 | 8 | TMsgMgr = record 9 | private 10 | FMsgProc: TMsgProc; 11 | public 12 | constructor Create(AMsgProc: TMsgProc); 13 | 14 | procedure Write(const AText: UnicodeString); overload; 15 | procedure Write(const AFmt: UnicodeString; const AArgs: array of const); overload; 16 | 17 | procedure Write(const Category: string; AText: UnicodeString); overload; 18 | procedure Write(const Category: string; AFmt: UnicodeString; const AArgs: array of const); overload; 19 | end; 20 | 21 | PMsgMgr = ^TMsgMgr; 22 | 23 | implementation 24 | 25 | uses 26 | System.SysUtils; 27 | 28 | { TMessageMgr } 29 | 30 | procedure TMsgMgr.Write(const AText: UnicodeString); 31 | begin 32 | if Assigned(FMsgProc) then 33 | FMsgProc(PWideChar(AText)); 34 | end; 35 | 36 | constructor TMsgMgr.Create(AMsgProc: TMsgProc); 37 | begin 38 | FMsgProc := AMsgProc; 39 | end; 40 | 41 | procedure TMsgMgr.Write(const AFmt: UnicodeString; const AArgs: array of const); 42 | begin 43 | Write(Format(AFmt, AArgs)); 44 | end; 45 | 46 | procedure TMsgMgr.Write(const Category: string; AText: UnicodeString); 47 | begin 48 | write(Format('[%s] %s', [Category, AText])); 49 | end; 50 | 51 | procedure TMsgMgr.Write(const Category: string; AFmt: UnicodeString; 52 | const AArgs: array of const); 53 | begin 54 | write(Category, Format(AFmt, AArgs)); 55 | end; 56 | 57 | end. 58 | -------------------------------------------------------------------------------- /PE.Parser.Export.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.Export; 2 | 3 | interface 4 | 5 | uses 6 | PE.Common, 7 | PE.Types, 8 | PE.Types.Directories, 9 | PE.Types.Export; 10 | 11 | type 12 | TPEExportParser = class(TPEParser) 13 | public 14 | function Parse: TParserResult; override; 15 | end; 16 | 17 | implementation 18 | 19 | uses 20 | PE.Image, 21 | PE.ExportSym; 22 | 23 | { TPEExportParser } 24 | 25 | function TPEExportParser.Parse: TParserResult; 26 | var 27 | PE: TPEImage; 28 | ExpIDD: TImageDataDirectory; 29 | ExpDir: TImageExportDirectory; 30 | i, base: uint32; 31 | ordnl: uint16; 32 | RVAs: array of uint32; 33 | NamePointerRVAs: array of uint32; 34 | OrdinalTableRVAs: array of uint16; 35 | Exp: array of TPEExportSym; 36 | Item: TPEExportSym; 37 | begin 38 | PE := TPEImage(FPE); 39 | 40 | // Clear exports. 41 | PE.ExportSyms.Clear; 42 | 43 | // Get export dir. 44 | if not PE.DataDirectories.Get(DDIR_EXPORT, @ExpIDD) then 45 | exit(PR_OK); 46 | 47 | // No exports is ok. 48 | if ExpIDD.IsEmpty then 49 | exit(PR_OK); 50 | 51 | // If can't find Export dir, failure. 52 | if not PE.SeekRVA(ExpIDD.VirtualAddress) then 53 | exit(PR_ERROR); 54 | 55 | // If can't read whole table, failure. 56 | if not PE.ReadEx(@ExpDir, Sizeof(ExpDir)) then 57 | begin 58 | PE.Msg.Write('Export Parser: not enough space to read dir. header.'); 59 | exit(PR_ERROR); 60 | end; 61 | 62 | // If no addresses, ok. 63 | if ExpDir.AddressTableEntries = 0 then 64 | begin 65 | PE.Msg.Write('Export Parser: directory present, but there are no functions.'); 66 | exit(PR_OK); 67 | end; 68 | 69 | if ExpDir.ExportFlags <> 0 then 70 | begin 71 | PE.Msg.Write('Export Parser: reserved directory flags <> 0'); 72 | exit(PR_ERROR); 73 | end; 74 | 75 | // Read lib exported name. 76 | if (ExpDir.NameRVA <> 0) then 77 | begin 78 | if not PE.SeekRVA(ExpDir.NameRVA) then 79 | begin 80 | PE.Msg.Write('Export Parser: Wrong RVA of dll exported name = 0x%x', [ExpDir.NameRVA]); 81 | exit(PR_ERROR); 82 | end; 83 | PE.ExportedName := PE.ReadAnsiString; 84 | end; 85 | 86 | base := ExpDir.OrdinalBase; 87 | 88 | // Check if there's too many exports. 89 | if (ExpDir.AddressTableEntries >= SUSPICIOUS_MIN_LIMIT_EXPORTS) or 90 | (ExpDir.NumberOfNamePointers >= SUSPICIOUS_MIN_LIMIT_EXPORTS) then 91 | begin 92 | exit(PR_SUSPICIOUS); 93 | end; 94 | 95 | SetLength(Exp, ExpDir.AddressTableEntries); 96 | SetLength(RVAs, ExpDir.AddressTableEntries); 97 | 98 | // load RVAs of exported data 99 | if not(PE.SeekRVA(ExpDir.ExportAddressTableRVA) and 100 | PE.ReadEx(@RVAs[0], 4 * ExpDir.AddressTableEntries)) then 101 | exit(PR_ERROR); 102 | 103 | if ExpDir.NumberOfNamePointers <> 0 then 104 | begin 105 | // name/ordinal only 106 | SetLength(NamePointerRVAs, ExpDir.NumberOfNamePointers); 107 | SetLength(OrdinalTableRVAs, ExpDir.NumberOfNamePointers); 108 | 109 | // load RVAs of name pointers 110 | if not((PE.SeekRVA(ExpDir.NamePointerRVA)) and 111 | PE.ReadEx(@NamePointerRVAs[0], 4 * ExpDir.NumberOfNamePointers)) then 112 | exit(PR_ERROR); 113 | 114 | // load ordinals according to names 115 | if not((PE.SeekRVA(ExpDir.OrdinalTableRVA)) and 116 | PE.ReadEx(@OrdinalTableRVAs[0], 2 * ExpDir.NumberOfNamePointers)) then 117 | exit(PR_ERROR); 118 | end; 119 | 120 | if ExpDir.AddressTableEntries <> 0 then 121 | begin 122 | for i := 0 to ExpDir.AddressTableEntries - 1 do 123 | begin 124 | Item := TPEExportSym.Create; 125 | Item.Ordinal := i + base; 126 | Item.RVA := RVAs[i]; 127 | 128 | Exp[i] := Item; 129 | 130 | // if rva in export section, it's forwarder 131 | Exp[i].Forwarder := ExpIDD.Contain(RVAs[i]); 132 | end; 133 | end; 134 | 135 | // read names 136 | if ExpDir.NumberOfNamePointers <> 0 then 137 | begin 138 | for i := 0 to ExpDir.NumberOfNamePointers - 1 do 139 | begin 140 | if (NamePointerRVAs[i] <> 0) then 141 | begin 142 | ordnl := OrdinalTableRVAs[i]; 143 | 144 | // Check if ordinal is correct. 145 | if ordnl >= length(Exp) then 146 | continue; 147 | 148 | if not Exp[ordnl].IsValid then 149 | continue; 150 | 151 | // Read export name. 152 | if not PE.SeekRVA(NamePointerRVAs[i]) then 153 | exit(PR_ERROR); 154 | 155 | Exp[ordnl].Name := PE.ReadAnsiString; 156 | 157 | // Read forwarder, if it is. 158 | if Exp[ordnl].Forwarder then 159 | begin 160 | // if it is forwarder, rva will point inside of export dir. 161 | if not PE.SeekRVA(Exp[ordnl].RVA) then 162 | exit(PR_ERROR); 163 | Exp[ordnl].ForwarderName := PE.ReadAnsiString; 164 | Exp[ordnl].RVA := 0; // no real address 165 | end; 166 | 167 | end; 168 | end; 169 | end; 170 | 171 | // finally array to list 172 | for i := low(Exp) to high(Exp) do 173 | if Exp[i].IsValid then 174 | PE.ExportSyms.Add(Exp[i]) 175 | else 176 | Exp[i].Free; 177 | 178 | exit(PR_OK); 179 | end; 180 | 181 | end. 182 | -------------------------------------------------------------------------------- /PE.Parser.Headers.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.Headers; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | 8 | PE.Common, 9 | PE.Types.DOSHeader, 10 | PE.Types.FileHeader, 11 | PE.Types.OptionalHeader, 12 | PE.Types.NTHeaders, 13 | 14 | PE.Utils; 15 | 16 | function LoadDosHeader(AStream: TStream; out AHdr: TImageDOSHeader): boolean; 17 | function LoadFileHeader(AStream: TStream; out AHdr: TImageFileHeader): boolean; inline; 18 | 19 | implementation 20 | 21 | function LoadDosHeader; 22 | begin 23 | Result := StreamRead(AStream, AHdr, SizeOf(AHdr)) and AHdr.e_magic.IsMZ; 24 | end; 25 | 26 | function LoadFileHeader; 27 | begin 28 | Result := StreamRead(AStream, AHdr, SizeOf(AHdr)); 29 | end; 30 | 31 | end. 32 | -------------------------------------------------------------------------------- /PE.Parser.Import.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.Import; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | System.SysUtils, 8 | 9 | PE.Common, 10 | PE.Types, 11 | PE.Types.Imports, 12 | PE.Types.FileHeader, 13 | PE.Imports, 14 | PE.Imports.Func, 15 | PE.Imports.Lib, 16 | PE.Utils; 17 | 18 | type 19 | TPEImportParser = class(TPEParser) 20 | public 21 | function Parse: TParserResult; override; 22 | end; 23 | 24 | implementation 25 | 26 | uses 27 | PE.Types.Directories, 28 | PE.Image; 29 | 30 | { TPEImportParser } 31 | 32 | function TPEImportParser.Parse: TParserResult; 33 | type 34 | TImpDirs = TList; 35 | TILTs = TList; 36 | var 37 | dir: TImageDataDirectory; 38 | bIs32: boolean; 39 | dq: uint64; 40 | sizet: byte; 41 | IDir: TImportDirectoryTable; 42 | IATRVA: uint64; 43 | PATCHRVA: uint64; // place where loader will put new address 44 | IDirs: TImpDirs; 45 | ILT: TImportLookupTable; 46 | ILTs: TILTs; 47 | ImpFn: TPEImportFunction; 48 | Lib: TPEImportLibrary; 49 | PE: TPEImage; 50 | LibraryName: string; 51 | dwLeft: uint32; 52 | bEmptyLastDirFound: boolean; 53 | IDirNumber: integer; 54 | begin 55 | PE := TPEImage(FPE); 56 | 57 | result := PR_ERROR; 58 | IDirs := TImpDirs.Create; 59 | ILTs := TILTs.Create; 60 | try 61 | PE.Imports.Clear; 62 | 63 | bIs32 := PE.Is32bit; 64 | sizet := PE.ImageBits div 8; 65 | 66 | // If no imports, it's ok. 67 | if not PE.DataDirectories.Get(DDIR_IMPORT, @dir) then 68 | exit(PR_OK); 69 | if dir.IsEmpty then 70 | exit(PR_OK); 71 | 72 | // Seek import dir. 73 | if not PE.SeekRVA(dir.VirtualAddress) then 74 | exit; 75 | 76 | // Read import descriptors. 77 | dwLeft := dir.Size; 78 | bEmptyLastDirFound := false; 79 | while dwLeft >= sizeof(IDir) do 80 | begin 81 | // Read IDir. 82 | if not PE.ReadEx(@IDir, sizeof(IDir)) then 83 | exit; 84 | 85 | if IDir.IsEmpty then // it's last dir 86 | begin 87 | bEmptyLastDirFound := true; 88 | break; 89 | end; 90 | 91 | // Check RVA. 92 | if not(PE.RVAExists(IDir.NameRVA)) then 93 | begin 94 | PE.Msg.Write(SCategoryImports, 'Bad RVAs in directory. Imports are incorrect.'); 95 | exit; 96 | end; 97 | 98 | IDirs.Add(IDir); // add read dir 99 | 100 | dec(dwLeft, sizeof(IDir)); 101 | end; 102 | 103 | if IDirs.Count = 0 then 104 | begin 105 | PE.Msg.Write(SCategoryImports, 'No directories found.'); 106 | exit; 107 | end; 108 | 109 | if not bEmptyLastDirFound then 110 | begin 111 | PE.Msg.Write(SCategoryImports, 'No last (empty) directory found.'); 112 | end; 113 | 114 | // Parse import descriptors. 115 | IDirNumber := -1; 116 | for IDir in IDirs do 117 | begin 118 | inc(IDirNumber); 119 | 120 | ILTs.Clear; 121 | 122 | // Read library name. 123 | if (not PE.SeekRVA(IDir.NameRVA)) then 124 | begin 125 | PE.Msg.Write(SCategoryImports, 'Library name RVA not found (0x%x) for dir # %d.', [IDir.NameRVA, IDirNumber]); 126 | Continue; 127 | end; 128 | 129 | LibraryName := PE.ReadAnsiString; 130 | 131 | if LibraryName.IsEmpty then 132 | begin 133 | PE.Msg.Write(SCategoryImports, 'Library # %d has empty name.', [IDirNumber]); 134 | Continue; 135 | end; 136 | 137 | PATCHRVA := IDir.FirstThunk; 138 | if PATCHRVA = 0 then 139 | begin 140 | PE.Msg.Write(SCategoryImports, 'Library # %d (%s) has NULL patch RVA.', [IDirNumber, LibraryName]); 141 | break; 142 | end; 143 | 144 | if IDir.ImportAddressTable <> 0 then 145 | IATRVA := IDir.ImportAddressTable 146 | else 147 | IATRVA := IDir.ImportLookupTableRVA; 148 | 149 | if IATRVA = 0 then 150 | begin 151 | PE.Msg.Write(SCategoryImports, 'Library # %d (%s) has NULL IAT RVA.', [IDirNumber, LibraryName]); 152 | break; 153 | end; 154 | 155 | // Lib will be created just in time. 156 | Lib := nil; 157 | 158 | // Read IAT elements. 159 | while PE.SeekRVA(IATRVA) do 160 | begin 161 | if not PE.ReadWordEx(0, @dq) then 162 | begin 163 | // Failed to read word and not null yet reached. 164 | FreeAndNil(Lib); 165 | PE.Msg.Write(SCategoryImports, 'Bad directory # %d. Skipped.', [IDirNumber]); 166 | break; 167 | end; 168 | 169 | if dq = 0 then 170 | break; 171 | 172 | ILT.Create(dq, bIs32); 173 | 174 | ImpFn := TPEImportFunction.CreateEmpty; 175 | 176 | // By ordinal. 177 | if ILT.IsImportByOrdinal then 178 | begin 179 | ImpFn.Ordinal := ILT.OrdinalNumber; 180 | ImpFn.Name := ''; 181 | end 182 | 183 | // By name. 184 | else if PE.SeekRVA(ILT.HintNameTableRVA) then 185 | begin 186 | dq := 0; 187 | PE.ReadEx(@dq, 2); 188 | ImpFn.Name := PE.ReadAnsiString; 189 | end; 190 | 191 | if not assigned(Lib) then 192 | begin 193 | // Create lib once in loop. 194 | // Added after loop (if not discarded). 195 | Lib := TPEImportLibrary.Create(LibraryName, IDir.IsBound, True); 196 | Lib.TimeDateStamp := IDir.TimeDateStamp; 197 | Lib.IATRVA := IATRVA; 198 | end; 199 | 200 | Lib.Functions.Add(ImpFn); 201 | 202 | inc(IATRVA, sizet); // next item 203 | inc(PATCHRVA, sizet); 204 | end; 205 | 206 | // If lib is generated, add it. 207 | if assigned(Lib) then 208 | PE.Imports.Add(Lib); 209 | 210 | end; 211 | 212 | result := PR_OK; 213 | 214 | finally 215 | IDirs.Free; 216 | ILTs.Free; 217 | end; 218 | end; 219 | 220 | end. 221 | -------------------------------------------------------------------------------- /PE.Parser.ImportDelayed.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.ImportDelayed; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | System.SysUtils, 8 | 9 | PE.Common, 10 | PE.Imports, 11 | PE.Types, 12 | PE.Types.Directories, 13 | PE.Types.FileHeader, // expand TPEImage.Is32bit 14 | PE.Types.Imports, 15 | PE.Types.ImportsDelayed, 16 | PE.Utils; 17 | 18 | type 19 | TPEImportDelayedParser = class(TPEParser) 20 | public 21 | function Parse: TParserResult; override; 22 | end; 23 | 24 | implementation 25 | 26 | uses 27 | PE.Image, 28 | PE.Imports.Func, 29 | PE.Imports.Lib; 30 | 31 | // Testing mode: check if read fields are correct. 32 | function ParseTable( 33 | const PE: TPEImage; 34 | const Table: TDelayLoadDirectoryTable; 35 | Testing: boolean 36 | ): boolean; 37 | var 38 | DllName: string; 39 | FnName: string; 40 | Fn: TPEImportFunctionDelayed; 41 | HintNameRva: TRVA; 42 | Ilt: TImportLookupTable; 43 | iFunc: uint32; 44 | var 45 | iLen: integer; 46 | Ordinal: UInt16; 47 | Hint: UInt16 absolute Ordinal; 48 | Iat: TRVA; 49 | SubValue: uint32; 50 | Lib: TPEImportLibrary; 51 | begin 52 | if Table.UsesVA then 53 | SubValue := PE.ImageBase 54 | else 55 | SubValue := 0; 56 | 57 | if Testing then 58 | begin 59 | if (Table.Name = 0) or (Table.Name < SubValue) then 60 | begin 61 | PE.Msg.Write('Delayed Import: Name address incorrect.'); 62 | exit(false); 63 | end; 64 | 65 | if (Table.DelayImportNameTable = 0) or (Table.DelayImportNameTable < SubValue) then 66 | begin 67 | PE.Msg.Write('Delayed Import: Name table address incorrect.'); 68 | exit(false); 69 | end; 70 | 71 | if (Table.DelayImportAddressTable = 0) or (Table.DelayImportAddressTable < SubValue) then 72 | begin 73 | PE.Msg.Write('Delayed Import: Address table incorrect.'); 74 | exit(false); 75 | end; 76 | end; 77 | 78 | if not PE.SeekRVA(Table.Name - SubValue) then 79 | exit(false); 80 | 81 | if not PE.ReadAnsiStringLen(MAX_PATH_WIN, iLen, DllName) then 82 | exit(false); 83 | 84 | if not Testing then 85 | begin 86 | Lib := TPEImportLibrary.Create(DllName, False, True); 87 | PE.ImportsDelayed.Add(Lib); 88 | end 89 | else 90 | begin 91 | Lib := nil; // compiler friendly 92 | end; 93 | 94 | iFunc := 0; 95 | Iat := Table.DelayImportAddressTable - SubValue; 96 | 97 | while PE.SeekRVA(Table.DelayImportNameTable - SubValue + iFunc * PE.ImageWordSize) do 98 | begin 99 | HintNameRva := PE.ReadWord(); 100 | if HintNameRva = 0 then 101 | break; 102 | 103 | Ilt.Create(HintNameRva, PE.Is32bit); 104 | 105 | Ordinal := 0; 106 | FnName := ''; 107 | 108 | if Ilt.IsImportByOrdinal then 109 | begin 110 | // Import by ordinal only. No hint/name. 111 | Ordinal := Ilt.OrdinalNumber; 112 | end 113 | else 114 | begin 115 | // Import by name. Get hint/name 116 | if not PE.SeekRVA(HintNameRva - SubValue) then 117 | begin 118 | PE.Msg.Write('Delayed Import: incorrect Hint/Name RVA encountered.'); 119 | exit(false); 120 | end; 121 | 122 | Hint := PE.ReadWord(2); 123 | FnName := PE.ReadANSIString; 124 | end; 125 | 126 | if not Testing then 127 | begin 128 | Fn := TPEImportFunctionDelayed.Create(FnName, Ordinal); 129 | Lib.Functions.Add(Fn); 130 | end; 131 | 132 | inc(Iat, PE.ImageWordSize); 133 | inc(iFunc); 134 | end; 135 | 136 | exit(true); 137 | end; 138 | 139 | function TPEImportDelayedParser.Parse: TParserResult; 140 | var 141 | PE: TPEImage; 142 | ddir: TImageDataDirectory; 143 | ofs: uint32; 144 | Table: TDelayLoadDirectoryTable; 145 | Tables: TList; 146 | TablesUseRVA: boolean; 147 | begin 148 | PE := TPEImage(FPE); 149 | 150 | result := PR_ERROR; 151 | 152 | // If no imports, it's ok. 153 | if not PE.DataDirectories.Get(DDIR_DELAYIMPORT, @ddir) then 154 | exit(PR_OK); 155 | if ddir.IsEmpty then 156 | exit(PR_OK); 157 | 158 | // Seek import dir. 159 | if not PE.SeekRVA(ddir.VirtualAddress) then 160 | exit; 161 | 162 | Tables := TList.Create; 163 | try 164 | 165 | // Delay-load dir. tables. 166 | ofs := 0; 167 | TablesUseRVA := true; // default, compiler-friendly 168 | while true do 169 | begin 170 | if ofs > ddir.Size then 171 | exit(PR_ERROR); 172 | 173 | if not PE.ReadEx(Table, SizeOf(Table)) then 174 | break; 175 | 176 | if Table.Empty then 177 | break; 178 | 179 | // Attribute: 180 | // 0: addresses are VA (old VC6 binaries) 181 | // 1: addresses are RVA 182 | 183 | if (ofs = 0) then 184 | begin 185 | TablesUseRVA := Table.UsesRVA; // initialize once 186 | end 187 | else if TablesUseRVA <> Table.UsesRVA then 188 | begin 189 | // Normally all tables must use either VA or RVA. No mix allowed. 190 | // If mix found it must be not real table. 191 | // For example, some Delphi versions used such optimization. 192 | break; 193 | end; 194 | 195 | Tables.Add(Table); 196 | inc(ofs, SizeOf(Table)); 197 | end; 198 | 199 | // Parse tables. 200 | for Table in Tables do 201 | // First test if fields are correct 202 | if ParseTable(PE, Table, true) then 203 | // Then do real reading. 204 | ParseTable(PE, Table, false); 205 | 206 | exit(PR_OK); 207 | finally 208 | Tables.Free; 209 | end; 210 | end; 211 | 212 | end. 213 | -------------------------------------------------------------------------------- /PE.Parser.PData.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.Parser.PData.pas -------------------------------------------------------------------------------- /PE.Parser.Relocs.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.Relocs; 2 | 3 | interface 4 | 5 | uses 6 | PE.Common, 7 | PE.Types, 8 | PE.Types.Directories, 9 | PE.Types.Relocations; 10 | 11 | type 12 | TPERelocParser = class(TPEParser) 13 | public 14 | function Parse: TParserResult; override; 15 | end; 16 | 17 | implementation 18 | 19 | uses 20 | PE.Image; 21 | 22 | { TRelocParser } 23 | 24 | function TPERelocParser.Parse: TParserResult; 25 | var 26 | dir: TImageDataDirectory; 27 | block: TBaseRelocationBlock; 28 | blCnt, iBlock: Integer; 29 | entry: TBaseRelocationEntry; 30 | r_ofs, r_type: dword; 31 | r_rva: dword; 32 | Ofs: dword; 33 | reloc: TReloc; 34 | PE: TPEImage; 35 | var 36 | tmpRVA: TRVA; 37 | begin 38 | PE := TPEImage(FPE); 39 | PE.Relocs.Clear; 40 | 41 | if not PE.DataDirectories.Get(DDIR_RELOCATION, @dir) then 42 | exit(PR_OK); 43 | 44 | if dir.IsEmpty then 45 | exit(PR_OK); 46 | 47 | if not PE.SeekRVA(dir.VirtualAddress) then 48 | begin 49 | PE.Msg.Write(SCategoryRelocs, 'Bad directory RVA (0x%x)', [dir.VirtualAddress]); 50 | exit(PR_ERROR); 51 | end; 52 | 53 | Ofs := 0; 54 | 55 | while (Ofs < dir.Size) do 56 | begin 57 | tmpRVA := PE.PositionRVA; 58 | 59 | if (not PE.ReadEx(@block, SizeOf(block))) then 60 | break; 61 | 62 | if Assigned(PE.ParseCallbacks) then 63 | PE.ParseCallbacks.ParsedRelocationBlockHeader(tmpRVA, block); 64 | 65 | if block.IsEmpty then 66 | break; 67 | 68 | inc(Ofs, SizeOf(block)); 69 | 70 | if block.BlockSize < SizeOf(TBaseRelocationBlock) then 71 | begin 72 | PE.Msg.Write(SCategoryRelocs, 'Bad size of block (%d).', [block.BlockSize]); 73 | continue; 74 | end; 75 | 76 | blCnt := block.Count; 77 | 78 | for iBlock := 0 to blCnt - 1 do 79 | begin 80 | if (Ofs + SizeOf(entry)) > dir.Size then 81 | begin 82 | PE.Msg.Write(SCategoryRelocs, 'Relocation is out of table. PageRVA:0x%x #:%d', [block.PageRVA, iBlock]); 83 | PE.Msg.Write(SCategoryRelocs, 'Skipping next relocs.'); 84 | exit(PR_OK); 85 | end; 86 | 87 | if not PE.ReadEx(@entry, SizeOf(entry)) then 88 | exit(PR_ERROR); 89 | 90 | inc(Ofs, SizeOf(entry)); 91 | r_type := entry.GetType; 92 | r_ofs := entry.GetOffset; 93 | r_rva := r_ofs + block.PageRVA; 94 | if r_type <> IMAGE_REL_BASED_ABSOLUTE then 95 | begin 96 | reloc.RVA := r_rva; 97 | reloc.&Type := r_type; 98 | PE.Relocs.Put(reloc); 99 | end; 100 | end; 101 | end; 102 | 103 | exit(PR_OK); 104 | end; 105 | 106 | end. 107 | -------------------------------------------------------------------------------- /PE.Parser.Resources.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.Resources; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | PE.Common, 8 | PE.Types, 9 | PE.Types.Directories, 10 | PE.Types.Resources, 11 | PE.Resources; 12 | 13 | type 14 | TPEResourcesParser = class(TPEParser) 15 | protected 16 | FBaseRVA: TRVA; // RVA of RSRC section base 17 | FTree: TResourceTree; 18 | 19 | // Read resource node entry. 20 | function ReadEntry( 21 | ParentNode: TResourceTreeBranchNode; 22 | RVA: TRVA; 23 | Index: integer; 24 | RDT: PResourceDirectoryTable): TResourceTreeNode; 25 | 26 | // Read resource node. 27 | function ReadNode( 28 | ParentNode: TResourceTreeBranchNode; 29 | RVA: TRVA): TParserResult; 30 | 31 | function LogInvalidResourceSizesTraverse(Node: TResourceTreeNode): boolean; 32 | procedure LogInvalidResourceSizes; 33 | public 34 | function Parse: TParserResult; override; 35 | end; 36 | 37 | implementation 38 | 39 | uses 40 | PE.Image; 41 | 42 | { TPEResourcesParser } 43 | 44 | procedure TPEResourcesParser.LogInvalidResourceSizes; 45 | begin 46 | FTree.Root.Traverse(LogInvalidResourceSizesTraverse); 47 | end; 48 | 49 | function TPEResourcesParser.LogInvalidResourceSizesTraverse(Node: TResourceTreeNode): boolean; 50 | begin 51 | if Node.IsLeaf then 52 | if not TResourceTreeLeafNode(Node).ValidSize then 53 | TPEImage(FPE).Msg.Write(SCategoryResources, 'Bad size of resource (probably packed): %s', [Node.GetPath]); 54 | 55 | Result := True; 56 | end; 57 | 58 | function TPEResourcesParser.Parse: TParserResult; 59 | var 60 | Img: TPEImage; 61 | dir: TImageDataDirectory; 62 | begin 63 | Img := TPEImage(FPE); 64 | 65 | // Check if directory present. 66 | if not Img.DataDirectories.Get(DDIR_RESOURCE, @dir) then 67 | exit(PR_OK); 68 | 69 | if dir.IsEmpty then 70 | exit(PR_OK); 71 | 72 | // Store base RVA. 73 | FBaseRVA := dir.VirtualAddress; 74 | 75 | // Try to seek resource dir. 76 | if not Img.SeekRVA(FBaseRVA) then 77 | exit(PR_ERROR); 78 | 79 | // Read root and children. 80 | FTree := Img.ResourceTree; 81 | ReadNode(FTree.Root, FBaseRVA); 82 | 83 | // Log invalid leaf nodes. 84 | LogInvalidResourceSizes; 85 | 86 | exit(PR_OK); 87 | end; 88 | 89 | function TPEResourcesParser.ReadEntry( 90 | ParentNode: TResourceTreeBranchNode; 91 | RVA: TRVA; 92 | Index: integer; 93 | RDT: PResourceDirectoryTable): TResourceTreeNode; 94 | var 95 | Img: TPEImage; 96 | Entry: TResourceDirectoryEntry; 97 | DataEntry: TResourceDataEntry; 98 | SubRVA, DataRVA, NameRVA: TRVA; 99 | LeafNode: TResourceTreeLeafNode; 100 | BranchNode: TResourceTreeBranchNode; 101 | EntryName: string; 102 | begin 103 | Result := nil; 104 | Img := TPEImage(FPE); 105 | 106 | // Try to read entry. 107 | if not Img.SeekRVA(RVA + Index * SizeOf(Entry)) then 108 | begin 109 | Img.Msg.Write(SCategoryResources, 'Bad resource entry RVA.'); 110 | exit; 111 | end; 112 | 113 | if not Img.ReadEx(@Entry, SizeOf(Entry)) then 114 | begin 115 | Img.Msg.Write(SCategoryResources, 'Bad resource entry.'); 116 | exit; 117 | end; 118 | 119 | // Prepare entry name. 120 | EntryName := ''; 121 | if Entry.EntryType = ResourceEntryByName then 122 | begin 123 | NameRVA := FBaseRVA + Entry.NameRVA; 124 | if not Img.SeekRVA(NameRVA) then 125 | begin 126 | Img.Msg.Write(SCategoryResources, 'Bad entry name RVA (0x%x)', [NameRVA]); 127 | exit; 128 | end; 129 | EntryName := Img.ReadUnicodeStringLenPfx2; 130 | end; 131 | 132 | // Check if RVA of child is correct. 133 | DataRVA := Entry.DataEntryRVA + FBaseRVA; 134 | if not Img.RVAExists(DataRVA) then 135 | begin 136 | Img.Msg.Write(SCategoryResources, 'Bad entry RVA (0x%x)', [DataRVA]); 137 | exit; 138 | end; 139 | 140 | // Handle Leaf or Branch. 141 | if Entry.IsDataEntryRVA then 142 | begin 143 | { 144 | Leaf node 145 | } 146 | 147 | DataRVA := Entry.DataEntryRVA + FBaseRVA; 148 | if not(Img.SeekRVA(DataRVA) and Img.ReadEx(@DataEntry, SizeOf(DataEntry))) then 149 | begin 150 | Img.Msg.Write(SCategoryResources, 'Bad resource leaf node.'); 151 | exit; 152 | end; 153 | LeafNode := TResourceTreeLeafNode.CreateFromEntry(FPE, DataEntry); 154 | Result := LeafNode; 155 | end 156 | else 157 | begin 158 | { 159 | Branch Node. 160 | } 161 | 162 | // Alloc and fill node. 163 | BranchNode := TResourceTreeBranchNode.Create; 164 | if RDT <> nil then 165 | begin 166 | BranchNode.Characteristics := RDT^.Characteristics; 167 | BranchNode.TimeDateStamp := RDT^.TimeDateStamp; 168 | BranchNode.MajorVersion := RDT^.MajorVersion; 169 | BranchNode.MinorVersion := RDT^.MinorVersion; 170 | end; 171 | // Get sub-level RVA. 172 | SubRVA := Entry.SubdirectoryRVA + FBaseRVA; 173 | // Read children. 174 | ReadNode(BranchNode, SubRVA); 175 | Result := BranchNode; 176 | end; 177 | 178 | // Set id or name. 179 | if Entry.EntryType = ResourceEntryById then 180 | Result.Id := Entry.IntegerID 181 | else 182 | Result.Name := EntryName; 183 | 184 | // Add node. 185 | ParentNode.Add(Result); 186 | end; 187 | 188 | function TPEResourcesParser.ReadNode(ParentNode: TResourceTreeBranchNode; RVA: TRVA): TParserResult; 189 | var 190 | Img: TPEImage; 191 | RDT: TResourceDirectoryTable; 192 | i, Total: integer; 193 | begin 194 | Img := TPEImage(FPE); 195 | 196 | if not Img.SeekRVA(RVA) then 197 | begin 198 | Img.Msg.Write(SCategoryResources, 'Bad resource directory table RVA (0x%x)', [RVA]); 199 | exit(PR_ERROR); 200 | end; 201 | 202 | // Read Directory Table. 203 | if not Img.ReadEx(@RDT, SizeOf(RDT)) then 204 | begin 205 | Img.Msg.Write(SCategoryResources, 'Failed to read resource directory table.'); 206 | exit(PR_ERROR); 207 | end; 208 | 209 | inc(RVA, SizeOf(RDT)); 210 | 211 | if (RDT.NumberOfNameEntries = 0) and (RDT.NumberOfIDEntries = 0) then 212 | begin 213 | Img.Msg.Write(SCategoryResources, 'Node have no name/id entries.'); 214 | exit(PR_ERROR); 215 | end; 216 | 217 | // Total number of entries. 218 | Total := RDT.NumberOfNameEntries + RDT.NumberOfIDEntries; 219 | 220 | // Read entries. 221 | for i := 0 to Total - 1 do 222 | if ReadEntry(ParentNode, RVA, i, @RDT) = nil then 223 | exit(PR_ERROR); 224 | 225 | exit(PR_OK); 226 | end; 227 | 228 | end. 229 | -------------------------------------------------------------------------------- /PE.Parser.TLS.pas: -------------------------------------------------------------------------------- 1 | unit PE.Parser.TLS; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | 8 | PE.Common, 9 | PE.Types, 10 | PE.Types.Directories, 11 | PE.Types.TLS, 12 | PE.TLS; 13 | 14 | type 15 | TPETLSParser = class(TPEParser) 16 | public 17 | function Parse: TParserResult; override; 18 | end; 19 | 20 | implementation 21 | 22 | uses 23 | PE.Image; 24 | 25 | { TPETLSParser } 26 | 27 | function TPETLSParser.Parse: TParserResult; 28 | var 29 | PE: TPEImage; 30 | var 31 | Dir: TImageDataDirectory; 32 | TLSDir: TTLSDirectory; 33 | AddressofCallbacks: TVA; 34 | CurRVA, CallbackVA: uint64; 35 | bRead: boolean; 36 | begin 37 | PE := TPEImage(FPE); 38 | 39 | if not PE.DataDirectories.Get(DDIR_TLS, @Dir) then 40 | exit(PR_OK); 41 | if Dir.IsEmpty then 42 | exit(PR_OK); 43 | 44 | if not PE.SeekRVA(Dir.VirtualAddress) then 45 | begin 46 | PE.Msg.Write(SCategoryTLS, 'Incorrect directory RVA.'); 47 | exit(PR_ERROR); 48 | end; 49 | 50 | case PE.ImageBits of 51 | 32: 52 | begin 53 | bRead := PE.ReadEx(TLSDir.tls32, SizeOf(TLSDir.tls32)); 54 | AddressofCallbacks := TLSDir.tls32.AddressofCallbacks; 55 | end; 56 | 64: 57 | begin 58 | bRead := PE.ReadEx(TLSDir.tls64, SizeOf(TLSDir.tls64)); 59 | AddressofCallbacks := TLSDir.tls64.AddressofCallbacks; 60 | end; 61 | else 62 | exit(PR_ERROR); 63 | end; 64 | 65 | if not bRead then 66 | begin 67 | PE.Msg.Write(SCategoryTLS, 'Failed to read directory.'); 68 | exit(PR_ERROR); 69 | end; 70 | 71 | // Assign dir. 72 | PE.TLS.Dir := TLSDir; 73 | 74 | // Try to read callback addresses if available. 75 | 76 | // It's ok if there's no callbacks. 77 | if AddressofCallbacks = 0 then 78 | exit(PR_OK); 79 | 80 | if not PE.SeekVA(AddressofCallbacks) then 81 | begin 82 | PE.Msg.Write(SCategoryTLS, 'Incorrect address of callbacks.'); 83 | exit(PR_OK); 84 | end; 85 | 86 | while True do 87 | begin 88 | CurRVA := PE.PositionRVA; 89 | 90 | // Try to read callback address. 91 | if not PE.ReadWordEx(0, @CallbackVA) then 92 | begin 93 | PE.Msg.Write(SCategoryTLS, 'Failed to read callback address at RVA: 0x%x. Probably malformed data.', [CurRVA]); 94 | break; 95 | end; 96 | 97 | // Is it terminator? 98 | if CallbackVA = 0 then 99 | break; 100 | 101 | // Does the address exist? 102 | if not PE.VAExists(CallbackVA) then 103 | begin 104 | PE.Msg.Write(SCategoryTLS, 'Bad callback address (0x%x) at RVA: 0x%x', [CallbackVA, CurRVA]); 105 | break; 106 | end; 107 | 108 | // Add existing address. 109 | PE.TLS.CallbackRVAs.Add(PE.VAToRVA(CallbackVA)) 110 | end; 111 | 112 | exit(PR_OK); 113 | end; 114 | 115 | end. 116 | -------------------------------------------------------------------------------- /PE.ParserCallbacks.pas: -------------------------------------------------------------------------------- 1 | unit PE.ParserCallbacks; 2 | 3 | interface 4 | 5 | uses 6 | PE.Common, 7 | PE.Types.Relocations; 8 | 9 | type 10 | IPEParserCallbacks = interface 11 | procedure ParsedRelocationBlockHeader(RVA: TRVA; const Block: TBaseRelocationBlock); 12 | end; 13 | 14 | implementation 15 | 16 | end. 17 | -------------------------------------------------------------------------------- /PE.ProcessModuleStream.pas: -------------------------------------------------------------------------------- 1 | { 2 | * Class to access memory of Windows process. 3 | * 4 | * Stream begin is base of module. 5 | * Stream size is size of image of target module. 6 | } 7 | unit PE.ProcessModuleStream; 8 | 9 | interface 10 | 11 | uses 12 | System.Classes, 13 | System.SysUtils, 14 | 15 | WinApi.PsApi, 16 | WinApi.TlHelp32, 17 | WinApi.Windows, 18 | 19 | WinHelper; 20 | 21 | type 22 | TProcessModuleStream = class(TStream) 23 | private 24 | FProcessHandle: THandle; 25 | FModuleBase: NativeUInt; 26 | FModuleSize: DWORD; 27 | private 28 | FCurrentRVA: UInt64; 29 | public 30 | constructor Create(ProcessID: DWORD; const me: TModuleEntry32); 31 | 32 | // Create from known process ID. Module base is found from ModuleName. 33 | // If process id is invalid or no module found exception raised. 34 | constructor CreateFromPidAndModuleName(ProcessID: DWORD; const ModuleName: string); 35 | 36 | constructor CreateFromPidAndAddress(ProcessID: DWORD; Address: NativeUInt); 37 | 38 | // Create from known process id. Main module used (i.e. started exe). 39 | constructor CreateFromPid(ProcessID: DWORD); 40 | 41 | destructor Destroy; override; 42 | 43 | function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override; 44 | function Read(var Buffer; Count: Longint): Longint; override; 45 | 46 | property ModuleBase: NativeUInt read FModuleBase; 47 | end; 48 | 49 | implementation 50 | 51 | { TProcessModuleStream } 52 | 53 | procedure RaiseFailedToFindModule; 54 | begin 55 | raise Exception.Create('Failed to find main module.'); 56 | end; 57 | 58 | constructor TProcessModuleStream.Create(ProcessID: DWORD; const me: TModuleEntry32); 59 | begin 60 | inherited Create; 61 | FProcessHandle := OpenProcess(MAXIMUM_ALLOWED, False, ProcessID); 62 | if FProcessHandle = 0 then 63 | RaiseLastOSError; 64 | FModuleBase := NativeUInt(me.modBaseAddr); 65 | FModuleSize := me.modBaseSize; 66 | end; 67 | 68 | constructor TProcessModuleStream.CreateFromPidAndModuleName(ProcessID: DWORD; const ModuleName: string); 69 | var 70 | me: TModuleEntry32; 71 | begin 72 | if not FindModuleByName(ProcessID, ModuleName) then 73 | RaiseFailedToFindModule; 74 | Create(ProcessID, me); 75 | end; 76 | 77 | constructor TProcessModuleStream.CreateFromPidAndAddress(ProcessID: DWORD; Address: NativeUInt); 78 | var 79 | me: TModuleEntry32; 80 | begin 81 | if not FindModuleByAddress(ProcessID, Address, me) then 82 | RaiseFailedToFindModule; 83 | Create(ProcessID, me); 84 | end; 85 | 86 | constructor TProcessModuleStream.CreateFromPid(ProcessID: DWORD); 87 | var 88 | me: TModuleEntry32; 89 | begin 90 | if not FindMainModule(ProcessID, me) then 91 | RaiseFailedToFindModule; 92 | Create(ProcessID, me); 93 | end; 94 | 95 | destructor TProcessModuleStream.Destroy; 96 | begin 97 | CloseHandle(FProcessHandle); 98 | inherited; 99 | end; 100 | 101 | function TProcessModuleStream.Read(var Buffer; Count: Integer): Longint; 102 | var 103 | p: pbyte; 104 | done: NativeUInt; 105 | begin 106 | p := pbyte(FModuleBase) + FCurrentRVA; 107 | done := 0; 108 | ReadProcessMemory(FProcessHandle, p, @Buffer, Count, done); 109 | inc(FCurrentRVA, done); 110 | Result := done; 111 | end; 112 | 113 | function TProcessModuleStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; 114 | begin 115 | case Origin of 116 | soBeginning: 117 | FCurrentRVA := Offset; 118 | soCurrent: 119 | FCurrentRVA := FCurrentRVA + Offset; 120 | soEnd: 121 | FCurrentRVA := FModuleSize + Offset; 122 | end; 123 | Result := FCurrentRVA; 124 | end; 125 | 126 | end. 127 | -------------------------------------------------------------------------------- /PE.RTTI.pas: -------------------------------------------------------------------------------- 1 | unit PE.RTTI; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes; 7 | 8 | type 9 | TRecordFieldDesc = record 10 | Flags: uint32; 11 | FieldName: PChar; 12 | end; 13 | 14 | PRecordFieldDesc = ^TRecordFieldDesc; 15 | 16 | // TRttiReadFunc must return: 17 | // - OutFieldSize: size of field. 18 | // - OutReadSize size to read into field. 19 | TRttiFieldResolveProc = procedure(Desc: PRecordFieldDesc; 20 | OutFieldSize, OutReadWriteSize: PInteger; ud: pointer); 21 | 22 | TRttiOperation = (RttiRead, RttiWrite, RttiCalcSize); 23 | 24 | // MaxSize: -1 means no limit. 25 | function RTTI_Process(Stream: TStream; Op: TRttiOperation; Buf: PByte; 26 | FieldDesc: PRecordFieldDesc; FieldDescCnt: integer; MaxSize: integer; 27 | ResolveProc: TRttiFieldResolveProc; ud: pointer): uint32; 28 | 29 | implementation 30 | 31 | {$IFDEF DIAG} 32 | 33 | uses 34 | System.SysUtils; 35 | 36 | procedure DbgLogData(Desc: PRecordFieldDesc; Stream: TStream; 37 | FieldSize: integer; IOSize: integer); 38 | begin 39 | writeln(Format('"%s" @ %x FieldSize: %x IOSize: %x', 40 | [Desc.FieldName, Stream.Position, FieldSize, IOSize])); 41 | end; 42 | {$ENDIF} 43 | 44 | function RTTI_Process; 45 | var 46 | i: integer; 47 | FieldSize, ReadWriteSize, tmp: integer; 48 | begin 49 | Result := 0; 50 | 51 | if (Buf = nil) or (FieldDesc = nil) or (FieldDescCnt = 0) or (MaxSize = 0) 52 | then 53 | exit; 54 | 55 | for i := 0 to FieldDescCnt - 1 do 56 | begin 57 | ResolveProc(FieldDesc, @FieldSize, @ReadWriteSize, ud); 58 | 59 | {$IFDEF DIAG} 60 | DbgLogData(FieldDesc, Stream, FieldSize, ReadWriteSize); 61 | {$ENDIF} 62 | if ReadWriteSize <> 0 then 63 | begin 64 | case Op of 65 | RttiRead: 66 | begin 67 | if ReadWriteSize < FieldSize then 68 | FillChar(Buf^, FieldSize, 0); 69 | tmp := Stream.Read(Buf^, ReadWriteSize); 70 | end; 71 | RttiWrite: 72 | tmp := Stream.Write(Buf^, ReadWriteSize); 73 | RttiCalcSize: 74 | tmp := ReadWriteSize; 75 | else 76 | tmp := ReadWriteSize; 77 | end; 78 | 79 | if tmp <> ReadWriteSize then 80 | break; // read error 81 | {$IFDEF DIAG} 82 | case tmp of 83 | 1: 84 | writeln(Format('= %x', [PByte(Buf)^])); 85 | 2: 86 | writeln(Format('= %x', [PWord(Buf)^])); 87 | 4: 88 | writeln(Format('= %x', [PCardinal(Buf)^])); 89 | 8: 90 | writeln(Format('= %x', [PUint64(Buf)^])); 91 | end; 92 | {$ENDIF} 93 | end; 94 | 95 | inc(Result, ReadWriteSize); 96 | inc(Buf, FieldSize); 97 | inc(FieldDesc); 98 | 99 | {$WARN COMPARING_SIGNED_UNSIGNED OFF} 100 | if (MaxSize <> -1) and (Result >= MaxSize) then 101 | break; 102 | {$WARN COMPARING_SIGNED_UNSIGNED ON} 103 | end; 104 | 105 | end; 106 | 107 | end. 108 | -------------------------------------------------------------------------------- /PE.Resources.Extract.pas: -------------------------------------------------------------------------------- 1 | unit PE.Resources.Extract; 2 | 3 | interface 4 | 5 | uses 6 | PE.Common, 7 | PE.Resources; 8 | 9 | // Extract raw resource data from Root node and save it to Dir folder. 10 | // If Root is nil, the main root is taken. 11 | // Result is number of resources extracted. 12 | function ExtractRawResources(Img: TPEImageObject; const Dir: string; 13 | Root: TResourceTreeNode = nil): integer; 14 | 15 | implementation 16 | 17 | uses 18 | System.IOUtils, 19 | System.SysUtils, 20 | PE.Image; 21 | 22 | type 23 | 24 | { TExtractor } 25 | 26 | TExtractor = class 27 | private 28 | FImg: TPEImage; 29 | FDir: string; 30 | FCount: integer; 31 | function Callback(Node: TResourceTreeNode): boolean; 32 | public 33 | function Extract(Img: TPEImage; const Dir: string; Root: TResourceTreeNode): integer; 34 | end; 35 | 36 | function TExtractor.Callback(Node: TResourceTreeNode): boolean; 37 | var 38 | Leaf: TResourceTreeLeafNode; 39 | FileName: string; 40 | Path: string; 41 | begin 42 | if Node.IsLeaf then 43 | begin 44 | Leaf := Node as TResourceTreeLeafNode; 45 | // Make filename and path. 46 | FileName := Format('%s\%s', [FDir, Leaf.GetPath]); 47 | Path := ExtractFilePath(FileName); 48 | // Create path and save file. 49 | TDirectory.CreateDirectory(Path); 50 | Leaf.Data.SaveToFile(FileName); 51 | inc(FCount); 52 | end; 53 | Result := True; // continue 54 | end; 55 | 56 | function ExtractRawResources(Img: TPEImageObject; const Dir: string; Root: TResourceTreeNode = nil): integer; 57 | var 58 | Extractor: TExtractor; 59 | begin 60 | Extractor := TExtractor.Create; 61 | try 62 | Result := Extractor.Extract(Img as TPEImage, 63 | ExcludeTrailingPathDelimiter(Dir), Root); 64 | finally 65 | Extractor.Free; 66 | end; 67 | end; 68 | 69 | function TExtractor.Extract(Img: TPEImage; const Dir: string; 70 | Root: TResourceTreeNode): integer; 71 | begin 72 | FImg := Img; 73 | FDir := Dir; 74 | FCount := 0; 75 | if Root = nil then 76 | Root := Img.ResourceTree.Root; 77 | if Root = nil then 78 | Exit(0); 79 | TDirectory.CreateDirectory(Dir); 80 | Img.ResourceTree.Root.Traverse(Callback); 81 | Exit(FCount); 82 | end; 83 | 84 | end. 85 | 86 | -------------------------------------------------------------------------------- /PE.Resources.Windows.Bitmap.pas: -------------------------------------------------------------------------------- 1 | unit PE.Resources.Windows.Bitmap; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | 9 | PE.Utils; 10 | 11 | // Parse RT_BITMAP into BMP stream. 12 | function ParseBitmapResource(const Stream: TStream): TStream; 13 | 14 | implementation 15 | 16 | {$ALIGN 1} 17 | 18 | 19 | type 20 | TBitmapFileHeader = record 21 | bfType: uint16; // BM 22 | bfSize: uint32; // Size of bitmap file/stream. 23 | bfReserved1: uint16; // 24 | bfReserved2: uint16; // 25 | bfOffBits: uint32; // Offset of pixels. 26 | end; 27 | 28 | TBitmapInfoHeader = record 29 | biSize: uint32; 30 | biWidth: int32; 31 | biHeight: int32; 32 | biPlanes: uint16; 33 | biBitCount: uint16; 34 | biCompression: uint32; 35 | biSizeImage: uint32; 36 | biXPelsPerMeter: int32; 37 | biYPelsPerMeter: int32; 38 | biClrUsed: uint32; 39 | biClrImportant: uint32; 40 | end; 41 | 42 | function ParseBitmapResource(const Stream: TStream): TStream; 43 | var 44 | BmpHdr: TBitmapFileHeader; 45 | InfoHdr: TBitmapInfoHeader; 46 | begin 47 | if not StreamRead(Stream, InfoHdr, SizeOf(InfoHdr)) then 48 | raise Exception.Create('Stream too small.'); 49 | 50 | BmpHdr.bfType := $4D42; // BM 51 | BmpHdr.bfSize := SizeOf(TBitmapFileHeader) + Stream.Size; 52 | BmpHdr.bfReserved1 := 0; 53 | BmpHdr.bfReserved2 := 0; 54 | BmpHdr.bfOffBits := 0; // Nowadays viewers are smart enough to calc this offset themselves. 55 | 56 | // Create bitmap. 57 | Stream.Position := 0; 58 | Result := TMemoryStream.Create; 59 | Result.Write(BmpHdr, SizeOf(BmpHdr)); 60 | Result.CopyFrom(Stream, Stream.Size); 61 | 62 | Result.Position := 0; 63 | end; 64 | 65 | end. 66 | -------------------------------------------------------------------------------- /PE.Resources.Windows.Strings.pas: -------------------------------------------------------------------------------- 1 | unit PE.Resources.Windows.Strings; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils; 8 | 9 | type 10 | TStringBundleID = uint32; 11 | 12 | TResourceStringBundle = class 13 | public 14 | id: TStringBundleID; 15 | Strings: TStringList; 16 | 17 | function GetStringId(stringNumber: cardinal): cardinal; 18 | 19 | constructor Create(id: TStringBundleID); 20 | destructor Destroy; override; 21 | 22 | procedure LoadFromStream(Stream: TStream); 23 | end; 24 | 25 | function BundleToStringId(bundleID: TStringBundleID): cardinal; inline; 26 | 27 | // Parse string bundle. 28 | function ParseStringResource(const Stream: TStream; bundleID: TStringBundleID): TResourceStringBundle; 29 | 30 | implementation 31 | 32 | { 33 | see 34 | https://msdn.microsoft.com/en-us/library/windows/desktop/aa381050 35 | http://blogs.msdn.com/b/oldnewthing/archive/2004/01/30/65013.aspx 36 | } 37 | 38 | // Bundle id to first string id in block of 16 strings. 39 | function BundleToStringId(bundleID: TStringBundleID): cardinal; 40 | begin 41 | if bundleID < 1 then 42 | raise Exception.Create('Wrong bundle id.'); 43 | result := (bundleID - 1) * 16; 44 | end; 45 | 46 | { TResourceStringBundle } 47 | 48 | constructor TResourceStringBundle.Create(id: TStringBundleID); 49 | begin 50 | self.id := id; 51 | self.Strings := TStringList.Create; 52 | self.Strings.Capacity := 16; 53 | end; 54 | 55 | destructor TResourceStringBundle.Destroy; 56 | begin 57 | Strings.Free; 58 | inherited; 59 | end; 60 | 61 | function TResourceStringBundle.GetStringId(stringNumber: cardinal): cardinal; 62 | begin 63 | result := BundleToStringId(id) + stringNumber; 64 | end; 65 | 66 | procedure TResourceStringBundle.LoadFromStream(Stream: TStream); 67 | var 68 | dwLen: uint16; 69 | allocLen: integer; 70 | bytes: TBytes; 71 | str: string; 72 | begin 73 | self.Strings.Clear; 74 | 75 | while Stream.Position < Stream.Size do 76 | begin 77 | if Stream.Read(dwLen, 2) <> 2 then 78 | raise Exception.Create('Failed to read string length.'); 79 | 80 | if dwLen = 0 then 81 | begin 82 | continue; 83 | end; 84 | 85 | dwLen := dwLen * 2; // 2 bytes per char 86 | 87 | if length(bytes) < dwLen then 88 | begin 89 | allocLen := ((dwLen + 128) div 128) * 128; 90 | setlength(bytes, allocLen); 91 | end; 92 | 93 | if Stream.Read(bytes, dwLen) <> dwLen then 94 | raise Exception.Create('String read error.'); 95 | 96 | str := TEncoding.Unicode.GetString(bytes, 0, dwLen); 97 | 98 | Strings.Add(str); 99 | end; 100 | end; 101 | 102 | function ParseStringResource(const Stream: TStream; bundleID: TStringBundleID): TResourceStringBundle; 103 | begin 104 | result := TResourceStringBundle.Create(bundleID); 105 | try 106 | result.LoadFromStream(Stream); 107 | except 108 | result.Free; 109 | result := nil; 110 | end; 111 | end; 112 | 113 | end. 114 | -------------------------------------------------------------------------------- /PE.Search.pas: -------------------------------------------------------------------------------- 1 | unit PE.Search; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | PE.Section; 8 | 9 | { 10 | * 11 | * Search byte pattern in ASection starting from AOffset. 12 | * Result is True if found and false otherwise. 13 | * AOffset will be set to last position scanned. 14 | * 15 | * Each byte of AMask is AND'ed with source byte and compared to pattern. 16 | * AMask can be smaller than APattern (or empty), but cannot be bigger. 17 | * 18 | * ADirection can be negative or positive to choose search direction. 19 | * If it is 0 the only match checked. 20 | * 21 | * Example: 22 | * 23 | * AA ?? BB should be represented like: 24 | * APattern: AA 00 BB 25 | * AMask: AA 00 BB 26 | * 27 | * AA 00 BB should be represented like: 28 | * APattern: AA 00 BB 29 | * AMask: AA FF BB 30 | * 31 | } 32 | function SearchBytes( 33 | const ASection: TPESection; 34 | const APattern: array of byte; 35 | const AMask: array of byte; 36 | var AOffset: UInt32; 37 | ADirection: Integer 38 | ): boolean; 39 | 40 | { 41 | * Check string contains valid pattern text and return number of elements on 42 | * success. Result is 0 if pattern text is invalid or string is empty. 43 | } 44 | function ValidatePattern(const S: string): Integer; 45 | 46 | { 47 | * Convert string like AA??BB (or AA ?? BB) 48 | * to pattern AA00BB 49 | * and mask FF00FF 50 | * 51 | * String must not contain spaces. 52 | * Output length of Pattern and Mask is same. 53 | } 54 | function StringToPattern( 55 | const S: string; 56 | out Pattern: TBytes; 57 | out Mask: TBytes): boolean; 58 | 59 | implementation 60 | 61 | function MatchPattern( 62 | pSrc: pbyte; 63 | const APattern: array of byte; 64 | const AMask: array of byte 65 | ): boolean; 66 | var 67 | MaskLeft: Integer; 68 | Mask: byte; 69 | i: Integer; 70 | begin 71 | Result := True; 72 | 73 | MaskLeft := Length(AMask); 74 | for i := 0 to High(APattern) do 75 | begin 76 | if MaskLeft <> 0 then 77 | Mask := AMask[i] 78 | else 79 | Mask := $FF; 80 | 81 | if (pSrc[i] and Mask) <> APattern[i] then 82 | begin 83 | Result := False; 84 | break; 85 | end; 86 | 87 | if MaskLeft <> 0 then 88 | dec(MaskLeft); 89 | end; 90 | end; 91 | 92 | function SearchBytes; 93 | var 94 | pSrc: pbyte; 95 | LastOffset: UInt32; 96 | begin 97 | Result := False; 98 | 99 | if Length(APattern) = 0 then 100 | Exit; 101 | 102 | if (AOffset + Length(APattern)) > ASection.AllocatedSize then 103 | Exit; 104 | 105 | if ADirection < 0 then 106 | ADirection := -1 107 | else if ADirection > 0 then 108 | ADirection := 1; 109 | 110 | pSrc := @ASection.Mem[AOffset]; 111 | LastOffset := ASection.AllocatedSize - Length(APattern); 112 | 113 | while AOffset <= LastOffset do 114 | begin 115 | Result := MatchPattern(pSrc, APattern, AMask); 116 | 117 | // Break if: found/no direction/at lower bound. 118 | if (Result) or (ADirection = 0) or ((ADirection < 0) and (AOffset = 0)) then 119 | break; 120 | 121 | // Next address/offset. 122 | inc(AOffset, ADirection); 123 | inc(pSrc, ADirection); 124 | end; 125 | end; 126 | 127 | function ValidatePattern(const S: string): Integer; 128 | var 129 | i: Integer; 130 | ElementLen: Integer; 131 | c: char; 132 | begin 133 | Result := 0; 134 | 135 | if S.IsEmpty then 136 | Exit; 137 | 138 | { Any element is 2 chars max } 139 | ElementLen := 0; 140 | 141 | i := 0; 142 | while i < S.Length do 143 | begin 144 | c := S.Chars[i]; 145 | case c of 146 | '?': 147 | begin 148 | if (ElementLen = 0) or ((ElementLen < 2) and (S.Chars[i - 1] = '?')) then 149 | inc(ElementLen) 150 | else 151 | Exit(0); 152 | end; 153 | '0' .. '9', 'A' .. 'F', 'a' .. 'f': 154 | begin 155 | if (ElementLen = 0) or ((ElementLen < 2) and CharInSet(S.Chars[i - 1], ['0' .. '9', 'A' .. 'F', 'a' .. 'f'])) then 156 | inc(ElementLen) 157 | else 158 | Exit(0); 159 | end; 160 | end; 161 | 162 | inc(i); 163 | 164 | if (ElementLen <> 0) and ((i = S.Length) or (c = ' ')) then 165 | inc(Result); 166 | 167 | if c = ' ' then 168 | ElementLen := 0; 169 | end; 170 | end; 171 | 172 | function StringToPattern( 173 | const S: string; 174 | out Pattern: TBytes; 175 | out Mask: TBytes): boolean; 176 | var 177 | i, hcn, masked: Integer; 178 | hc: array [0 .. 1] of byte; 179 | c: char; 180 | element, count: Integer; 181 | begin 182 | count := ValidatePattern(S); 183 | if count = 0 then 184 | Exit(False); 185 | 186 | SetLength(Pattern, count); 187 | SetLength(Mask, count); 188 | 189 | element := 0; 190 | 191 | hcn := 0; 192 | hc[0] := 0; 193 | hc[1] := 0; 194 | masked := 0; 195 | 196 | i := 0; 197 | while i < S.Length do 198 | begin 199 | c := S.Chars[i]; 200 | 201 | case c of 202 | '0' .. '9': 203 | begin 204 | hc[hcn] := Integer(c) - Integer('0'); 205 | inc(hcn); 206 | end; 207 | 'A' .. 'F': 208 | begin 209 | hc[hcn] := Integer(c) - Integer('A') + 10; 210 | inc(hcn); 211 | end; 212 | 'a' .. 'f': 213 | begin 214 | hc[hcn] := Integer(c) - Integer('a') + 10; 215 | inc(hcn); 216 | end; 217 | '?': 218 | inc(masked); 219 | end; 220 | 221 | inc(i); 222 | 223 | if (i = S.Length) or (c = ' ') then 224 | begin 225 | case hcn of 226 | 0: 227 | if masked <> 0 then 228 | begin 229 | Pattern[element] := 0; 230 | Mask[element] := 0; 231 | inc(element); 232 | end; 233 | 1: 234 | begin 235 | Pattern[element] := hc[0]; 236 | Mask[element] := $FF; 237 | inc(element); 238 | end; 239 | 2: 240 | begin 241 | Pattern[element] := (hc[0] shl 4) or hc[1]; 242 | Mask[element] := $FF; 243 | inc(element); 244 | end; 245 | end; 246 | 247 | hcn := 0; 248 | hc[0] := 0; 249 | hc[1] := 0; 250 | masked := 0; 251 | end; 252 | end; 253 | 254 | Exit(True); 255 | end; 256 | 257 | end. 258 | -------------------------------------------------------------------------------- /PE.Sections.pas: -------------------------------------------------------------------------------- 1 | unit PE.Sections; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.Generics.Collections, 8 | System.SysUtils, 9 | 10 | PE.Common, 11 | PE.Types.Sections, 12 | PE.Section; 13 | 14 | type 15 | TPESections = class(TList) 16 | private 17 | FPE: TObject; 18 | procedure ItemNotify(Sender: TObject; const Item: TPESection; 19 | Action: TCollectionNotification); 20 | public 21 | constructor Create(APEImage: TObject); 22 | 23 | function Add(const Sec: TPESection): TPESection; 24 | procedure Clear; 25 | 26 | // Change section Raw and Virtual size. 27 | // Virtual size is aligned to section alignment. 28 | procedure Resize(Sec: TPESection; NewSize: UInt32); 29 | 30 | function CalcNextSectionRVA: TRVA; 31 | 32 | // Create new section but don't add it. 33 | // See AddNew for list of parameters. 34 | function CreateNew(const AName: String; ASize, AFlags: UInt32; 35 | Mem: pointer; ForceVA: TVA = 0): TPESection; 36 | 37 | // Add new named section. 38 | // If Mem <> nil, data from Mem will be copied to newly allocated block. 39 | // If Mem = nil, block will be allocated and filled with 0s. 40 | // Normally Virtual Address of section is calculated to come after previous 41 | // section (aligned). But if ForceVA is not 0 it is used instead of 42 | // calculation. 43 | function AddNew(const AName: String; ASize, AFlags: UInt32; 44 | Mem: pointer; ForceVA: TVA = 0): TPESection; 45 | 46 | // Add new section using raw data from file. 47 | function AddNewFromFile(const AFileName: string; const AName: String; 48 | AFlags: UInt32; ForceVA: TVA = 0): TPESection; 49 | 50 | function SizeOfAllHeaders: UInt32; inline; 51 | 52 | function RVAToOfs(RVA: TRVA; OutOfs: PDword): boolean; 53 | function RVAToSec(RVA: TRVA; OutSec: PPESection): boolean; 54 | 55 | function FindByName(const AName: String; IgnoreCase: boolean = True): TPESection; 56 | 57 | // Fill section memory with specified byte and return number of bytes 58 | // actually written. 59 | function FillMemory(RVA: TRVA; Size: UInt32; FillByte: Byte = 0): UInt32; 60 | 61 | // WholeSizeOrNothing: either write Size bytes or write nothing. 62 | function FillMemoryEx(RVA: TRVA; Size: UInt32; WholeSizeOrNothing: boolean; 63 | FillByte: Byte = 0): UInt32; 64 | end; 65 | 66 | implementation 67 | 68 | uses 69 | // Expand 70 | PE.Types.FileHeader, 71 | // 72 | PE.Image, 73 | PE.Utils; 74 | 75 | { TPESections } 76 | 77 | function TPESections.Add(const Sec: TPESection): TPESection; 78 | begin 79 | inherited Add(Sec); 80 | Result := Sec; 81 | end; 82 | 83 | function TPESections.CreateNew(const AName: String; ASize, AFlags: UInt32; 84 | Mem: pointer; ForceVA: TVA): TPESection; 85 | var 86 | PE: TPEImage; 87 | sh: TImageSectionHeader; 88 | begin 89 | PE := TPEImage(FPE); 90 | 91 | sh.Clear; 92 | sh.Name := AName; 93 | sh.VirtualSize := AlignUp(ASize, PE.SectionAlignment); 94 | 95 | if ForceVA = 0 then 96 | sh.RVA := CalcNextSectionRVA 97 | else 98 | sh.RVA := ForceVA; 99 | 100 | sh.SizeOfRawData := ASize; 101 | // sh.PointerToRawData will be calculated later during image saving. 102 | sh.Flags := AFlags; 103 | 104 | Result := TPESection.Create(sh, Mem); 105 | end; 106 | 107 | function TPESections.AddNew(const AName: String; ASize, AFlags: UInt32; 108 | Mem: pointer; ForceVA: TVA): TPESection; 109 | begin 110 | Result := CreateNew(AName, ASize, AFlags, Mem, ForceVA); 111 | Add(Result); 112 | end; 113 | 114 | function TPESections.AddNewFromFile(const AFileName: string; 115 | const AName: String; AFlags: UInt32; ForceVA: TVA): TPESection; 116 | var 117 | ms: TMemoryStream; 118 | begin 119 | ms := TMemoryStream.Create; 120 | try 121 | ms.LoadFromFile(AFileName); 122 | Result := AddNew(AName, ms.Size, AFlags, ms.Memory, ForceVA); 123 | finally 124 | ms.Free; 125 | end; 126 | end; 127 | 128 | function TPESections.CalcNextSectionRVA: TRVA; 129 | var 130 | PE: TPEImage; 131 | begin 132 | PE := TPEImage(FPE); 133 | if Count = 0 then 134 | Result := AlignUp(PE.CalcHeadersSizeNotAligned, PE.SectionAlignment) 135 | else 136 | Result := AlignUp(Last.RVA + Last.VirtualSize, PE.SectionAlignment); 137 | end; 138 | 139 | procedure TPESections.Clear; 140 | begin 141 | inherited Clear; 142 | TPEImage(FPE).FileHeader^.NumberOfSections := 0; 143 | end; 144 | 145 | constructor TPESections.Create(APEImage: TObject); 146 | begin 147 | inherited Create; 148 | FPE := APEImage; 149 | self.OnNotify := ItemNotify; 150 | end; 151 | 152 | function TPESections.FillMemory(RVA: TRVA; Size: UInt32; 153 | FillByte: Byte): UInt32; 154 | begin 155 | Result := FillMemoryEx(RVA, Size, False, FillByte); 156 | end; 157 | 158 | function TPESections.FillMemoryEx(RVA: TRVA; Size: UInt32; 159 | WholeSizeOrNothing: boolean; FillByte: Byte): UInt32; 160 | var 161 | Sec: TPESection; 162 | Ofs, CanWrite: UInt32; 163 | p: PByte; 164 | begin 165 | if not RVAToSec(RVA, @Sec) then 166 | Exit(0); 167 | Ofs := RVA - Sec.RVA; // offset of RVA in section 168 | CanWrite := Sec.GetAllocatedSize - Ofs; // max we can write before section end 169 | if CanWrite < Size then 170 | begin 171 | if WholeSizeOrNothing then 172 | Exit(0); // 173 | Result := CanWrite; 174 | end 175 | else 176 | Result := Size; 177 | p := Sec.Mem + Ofs; 178 | System.FillChar(p^, Result, FillByte); 179 | end; 180 | 181 | function TPESections.FindByName(const AName: String; IgnoreCase: boolean): TPESection; 182 | var 183 | a, b: string; 184 | begin 185 | if IgnoreCase then 186 | a := AName.ToLower 187 | else 188 | a := AName; 189 | for Result in self do 190 | begin 191 | if IgnoreCase then 192 | b := Result.Name.ToLower 193 | else 194 | b := Result.Name; 195 | if a = b then 196 | Exit; 197 | end; 198 | Exit(nil); 199 | end; 200 | 201 | procedure TPESections.ItemNotify(Sender: TObject; const Item: TPESection; 202 | Action: TCollectionNotification); 203 | begin 204 | case Action of 205 | cnAdded: 206 | inc(TPEImage(FPE).FileHeader^.NumberOfSections); 207 | cnRemoved: 208 | begin 209 | dec(TPEImage(FPE).FileHeader^.NumberOfSections); 210 | if Item <> nil then 211 | Item.Free; 212 | end; 213 | cnExtracted: 214 | dec(TPEImage(FPE).FileHeader^.NumberOfSections); 215 | end; 216 | end; 217 | 218 | procedure TPESections.Resize(Sec: TPESection; NewSize: UInt32); 219 | var 220 | NewVirtualSize: UInt32; 221 | LastRVA: TRVA; 222 | begin 223 | // Last section can be changed freely, other sections must be checked. 224 | if Sec <> self.Last then 225 | begin 226 | if NewSize = 0 then 227 | begin 228 | Remove(Sec); 229 | end 230 | else 231 | begin 232 | // Get new size and rva for this section. 233 | NewVirtualSize := AlignUp(NewSize, TPEImage(FPE).SectionAlignment); 234 | LastRVA := Sec.RVA + NewVirtualSize - 1; 235 | // Check if new section end would be already occupied. 236 | if RVAToSec(LastRVA, nil) then 237 | raise Exception.Create('Cannot resize section: size is too big'); 238 | end; 239 | end; 240 | Sec.Resize(NewSize); 241 | end; 242 | 243 | function TPESections.RVAToOfs(RVA: TRVA; OutOfs: PDword): boolean; 244 | var 245 | Sec: TPESection; 246 | begin 247 | for Sec in self do 248 | begin 249 | if Sec.ContainRVA(RVA) then 250 | begin 251 | if Assigned(OutOfs) then 252 | OutOfs^ := (RVA - Sec.RVA) + Sec.RawOffset; 253 | Exit(True); 254 | end; 255 | end; 256 | Exit(False); 257 | end; 258 | 259 | function TPESections.RVAToSec(RVA: TRVA; OutSec: PPESection): boolean; 260 | var 261 | Sec: TPESection; 262 | begin 263 | for Sec in self do 264 | if Sec.ContainRVA(RVA) then 265 | begin 266 | if OutSec <> nil then 267 | OutSec^ := Sec; 268 | Exit(True); 269 | end; 270 | Result := False; 271 | end; 272 | 273 | function TPESections.SizeOfAllHeaders: UInt32; 274 | begin 275 | Result := Count * sizeof(TImageSectionHeader) 276 | end; 277 | 278 | end. 279 | -------------------------------------------------------------------------------- /PE.TLS.pas: -------------------------------------------------------------------------------- 1 | unit PE.TLS; 2 | 3 | interface 4 | 5 | uses 6 | PE.Types, 7 | PE.Types.TLS; 8 | 9 | type 10 | TTLS = class 11 | public 12 | Dir: TTLSDirectory; 13 | CallbackRVAs: TRVAs; 14 | constructor Create; 15 | destructor Destroy; override; 16 | procedure Clear; 17 | end; 18 | 19 | implementation 20 | 21 | procedure TTLS.Clear; 22 | begin 23 | FillChar(Dir, SizeOf(Dir), 0); 24 | CallbackRVAs.Clear; 25 | end; 26 | 27 | constructor TTLS.Create; 28 | begin 29 | CallbackRVAs := TRVAs.Create; 30 | end; 31 | 32 | destructor TTLS.Destroy; 33 | begin 34 | CallbackRVAs.Free; 35 | inherited Destroy; 36 | end; 37 | 38 | end. 39 | -------------------------------------------------------------------------------- /PE.Types.DOSHeader.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.DOSHeader; 2 | 3 | interface 4 | 5 | type 6 | TDOSMagic = packed record 7 | public 8 | function IsMZ: boolean; inline; 9 | procedure SetMZ; inline; 10 | public 11 | case integer of 12 | 0: 13 | (chars: array [0 .. 1] of AnsiChar); 14 | end; 15 | 16 | type 17 | TImageDOSHeader = packed record 18 | e_magic: TDOSMagic; // Magic number. 19 | e_cblp: uint16; // Bytes on last page of file. 20 | e_cp: uint16; // Pages in file. 21 | e_crlc: uint16; // Relocations. 22 | e_cparhdr: uint16; // Size of header in paragraphs. 23 | e_minalloc: uint16; // Minimum extra paragraphs needed. 24 | e_maxalloc: uint16; // Maximum extra paragraphs needed. 25 | e_ss: uint16; // Initial (relative) SS value. 26 | e_sp: uint16; // Initial SP value. 27 | e_csum: uint16; // Checksum. 28 | e_ip: uint16; // Initial IP value. 29 | e_cs: uint16; // Initial (relative) CS value. 30 | e_lfarlc: uint16; // File address of relocation table. 31 | e_ovno: uint16; // Overlay number. 32 | e_res: array [0 .. 3] of uint16; // Reserved words. 33 | e_oemid: uint16; // OEM identifier (for e_oeminfo). 34 | e_oeminfo: uint16; // OEM information; e_oemid specific. 35 | e_res2: array [0 .. 9] of uint16; // Reserved words. 36 | e_lfanew: uint32; // File address of new exe header. 37 | end; 38 | 39 | PImageDOSHeader = ^TImageDOSHeader; 40 | 41 | const 42 | DOSSTUB: packed array [0 .. 56] of byte = ($0E, $1F, $BA, $0E, $00, $B4, $09, 43 | $CD, $21, $B8, $01, $4C, $CD, $21, $54, $68, $69, $73, $20, $70, $72, $6F, 44 | $67, $72, $61, $6D, $20, $63, $61, $6E, $6E, $6F, $74, $20, $62, $65, $20, 45 | $72, $75, $6E, $20, $69, $6E, $20, $44, $4F, $53, $20, $6D, $6F, $64, $65, 46 | $2E, $0D, $0D, $0A, $24); 47 | 48 | implementation 49 | 50 | { TDOSMagic } 51 | 52 | function TDOSMagic.IsMZ: boolean; 53 | begin 54 | result := self.chars = 'MZ'; 55 | end; 56 | 57 | procedure TDOSMagic.SetMZ; 58 | begin 59 | self.chars[0] := 'M'; 60 | self.chars[1] := 'Z'; 61 | end; 62 | 63 | end. 64 | -------------------------------------------------------------------------------- /PE.Types.Directories.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.Directories; 2 | 3 | interface 4 | 5 | type 6 | TImageDataDirectory = packed record 7 | RVA: uint32; 8 | Size: uint32; 9 | function IsEmpty: boolean; inline; 10 | function Contain(rva: uint32): boolean; inline; 11 | 12 | // todo: VirtualAddress is deprecated, use RVA instead. 13 | property VirtualAddress: uint32 read RVA write RVA; 14 | end; 15 | 16 | PImageDataDirectory = ^TImageDataDirectory; 17 | 18 | type 19 | // 2.4.3. Optional Header Data Directories (Image Only) 20 | 21 | // variant #1 22 | TImageDataDirectories = packed record 23 | ExportTable: TImageDataDirectory; // The export table address and size. 24 | ImportTable: TImageDataDirectory; // The import table address and size. 25 | ResourceTable: TImageDataDirectory; // The resource table address and size. 26 | ExceptionTable: TImageDataDirectory; // The exception table address and size. 27 | CertificateTable: TImageDataDirectory; // The attribute certificate table address and size. 28 | BaseRelocationTable: TImageDataDirectory; // The base relocation table address and size. 29 | Debug: TImageDataDirectory; // The debug data starting address and size. 30 | Architecture: TImageDataDirectory; // Reserved, must be 0 31 | GlobalPtr: TImageDataDirectory; // The RVA of the value to be stored in the global pointer register. 32 | // The size member of this structure must be set to zero. 33 | TLSTable: TImageDataDirectory; // The thread local storage (TLS) table address and size. 34 | LoadConfigTable: TImageDataDirectory; // The load configuration table address and size. 35 | BoundImport: TImageDataDirectory; // The bound import table address and size. 36 | IAT: TImageDataDirectory; // The import address table address and size. 37 | DelayImportDescriptor: TImageDataDirectory; // The delay import descriptor address and size. 38 | CLRRuntimeHeader: TImageDataDirectory; // The CLR runtime header address and size. 39 | RESERVED: TImageDataDirectory; // Reserved, must be zero 40 | end; 41 | 42 | PImageDataDirectories = ^TImageDataDirectories; 43 | 44 | const 45 | NULL_IMAGE_DATA_DIRECTORY: TImageDataDirectory = (RVA: 0; Size: 0); 46 | 47 | IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; 48 | 49 | TYPICAL_NUMBER_OF_DIRECTORIES = IMAGE_NUMBEROF_DIRECTORY_ENTRIES; 50 | 51 | // variant #2 52 | // TImageDataDirectories = packed array [0 .. IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of TImageDataDirectory; 53 | 54 | // Get directory name by index or format index as a string (like dir_0001) if 55 | // it's not in range of known names. 56 | function GetDirectoryName(Index: integer): string; 57 | 58 | implementation 59 | 60 | uses 61 | System.SysUtils; 62 | 63 | function TImageDataDirectory.Contain(rva: uint32): boolean; 64 | begin 65 | Result := (rva >= Self.VirtualAddress) and (rva < Self.VirtualAddress + Self.Size); 66 | end; 67 | 68 | function TImageDataDirectory.IsEmpty: boolean; 69 | begin 70 | // In some cases Size can be 0, but VirtualAddress will point to valid data. 71 | Result := (VirtualAddress = 0); 72 | end; 73 | 74 | const 75 | DirectoryNames: array [0 .. IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of string = 76 | ( 77 | 'Export', 78 | 'Import', 79 | 'Resource', 80 | 'Exception', 81 | 'Certificate', 82 | 'Base Relocation', 83 | 'Debug', 84 | 'Architecture', 85 | 'Global Pointer', 86 | 'Thread Local Storage', 87 | 'Load Config', 88 | 'Bound Import', 89 | 'Import Address Table', 90 | 'Delay Import Descriptor', 91 | 'CLR Runtime Header', 92 | '' 93 | ); 94 | 95 | function GetDirectoryName(Index: integer): string; 96 | begin 97 | if (Index >= 0) and (Index < Length(DirectoryNames)) then 98 | Result := DirectoryNames[Index] 99 | else 100 | Result := format('dir_%4.4d', [index]); 101 | end; 102 | 103 | end. 104 | -------------------------------------------------------------------------------- /PE.Types.Export.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.Export; 2 | 3 | interface 4 | 5 | uses 6 | PE.Common; 7 | 8 | type 9 | TImageExportDirectory = packed record 10 | ExportFlags: uint32; // Reserved, must be 0. 11 | TimeDateStamp: uint32; // The time and date that the export data was created. 12 | MajorVersion: uint16; // The major version number. 13 | // The major and minor version numbers can be set by the user. 14 | MinorVersion: uint16; // The minor version number. 15 | NameRVA: uint32; // The address of the ASCII string that contains the name of the DLL. 16 | // This address is relative to the image base. 17 | OrdinalBase: uint32; // The starting ordinal number for exports in this image. 18 | // This field specifies the starting ordinal number for the export address table. 19 | // It is usually set to 1. 20 | AddressTableEntries: uint32; // NumberOfFunctions; The number of entries in the export address table. 21 | NumberOfNamePointers: uint32; // The number of entries in the name pointer table. 22 | // This is also the number of entries in the ordinal table. 23 | ExportAddressTableRVA: uint32; // The address of the export address table, relative to the image base. 24 | NamePointerRVA: uint32; // The address of the export name pointer table, relative to the image base. 25 | // The table size is given by the Number of Name Pointers field. 26 | OrdinalTableRVA: uint32; // The address of the ordinal table, relative to the image base. 27 | end; 28 | 29 | PImageExportDirectory = ^TImageExportDirectory; 30 | 31 | implementation 32 | 33 | end. 34 | -------------------------------------------------------------------------------- /PE.Types.FileHeader.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.Types.FileHeader.pas -------------------------------------------------------------------------------- /PE.Types.Imports.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.Types.Imports.pas -------------------------------------------------------------------------------- /PE.Types.ImportsDelayed.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.Types.ImportsDelayed.pas -------------------------------------------------------------------------------- /PE.Types.NTHeaders.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.NTHeaders; 2 | 3 | interface 4 | 5 | uses 6 | PE.Types.FileHeader, 7 | PE.Types.OptionalHeader; 8 | 9 | type 10 | { 11 | TImageNTHeaders32 = packed record 12 | Signature: uint32; 13 | FileHeader: TImageFileHeader; 14 | OptionalHeader: TImageOptionalHeader32; 15 | end; 16 | 17 | PImageNTHeaders32 = ^TImageNTHeaders32; 18 | 19 | TImageNTHeaders64 = packed record 20 | Signature: uint32; 21 | FileHeader: TImageFileHeader; 22 | OptionalHeader: TImageOptionalHeader64; 23 | end; 24 | 25 | PImageNTHeaders64 = ^TImageNTHeaders64; 26 | } 27 | 28 | TNTSignature = record 29 | public 30 | function IsPE00: boolean; inline; 31 | public 32 | case integer of 33 | 0: 34 | (chars: array [0 .. 3] of AnsiChar); 35 | end; 36 | 37 | TImageNTHeaders = packed record 38 | Signature: TNTSignature; 39 | FileHeader: TImageFileHeader; 40 | OptionalHeader: TImageOptionalHeader; 41 | end; 42 | 43 | PImageNTHeaders = ^TImageNTHeaders; 44 | 45 | const 46 | PE00_SIGNATURE: TNTSignature = (chars: 'PE'#0#0); 47 | 48 | implementation 49 | 50 | { TNTSignature } 51 | 52 | function TNTSignature.IsPE00: boolean; 53 | begin 54 | result := self.chars = PE00_SIGNATURE.chars; 55 | end; 56 | 57 | end. 58 | -------------------------------------------------------------------------------- /PE.Types.OptionalHeader.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.OptionalHeader; 2 | 3 | interface 4 | 5 | uses 6 | PE.Types.Directories; 7 | 8 | // 2.4.1. Optional Header Standard Fields (Image Only) 9 | // 2.4.2. Optional Header Windows-Specific Fields (Image Only) 10 | type 11 | // PImageOptionalHeader = ^TImageOptionalHeader; 12 | // TImageOptionalHeader = packed record 13 | // // Standard fields. 14 | // Magic : uint16; // 0x10b: PE32 15 | // // 0x20b: PE32+ 16 | // MajorLinkerVersion : uint8; 17 | // MinorLinkerVersion : uint8; 18 | // SizeOfCode : uint32; 19 | // SizeOfInitializedData : uint32; 20 | // SizeOfUninitializedData : uint32; 21 | // AddressOfEntryPoint : uint32; 22 | // BaseOfCode : uint32; 23 | // 24 | // BaseOfData : uint32; // PE32 only 25 | // 26 | // // NT additional fields. 27 | // ImageBase : uint3264; 28 | // SectionAlignment : uint32; 29 | // FileAlignment : uint32; 30 | // MajorOperatingSystemVersion : uint16; 31 | // MinorOperatingSystemVersion : uint16; 32 | // MajorImageVersion : uint16; 33 | // MinorImageVersion : uint16; 34 | // MajorSubsystemVersion : uint16; 35 | // MinorSubsystemVersion : uint16; 36 | // Win32VersionValue : uint32; 37 | // SizeOfImage : uint32; 38 | // SizeOfHeaders : uint32; 39 | // CheckSum : uint32; 40 | // Subsystem : uint16; 41 | // DllCharacteristics : uint16; 42 | // SizeOfStackReserve : uint3264; 43 | // SizeOfStackCommit : uint3264; 44 | // SizeOfHeapReserve : uint3264; 45 | // SizeOfHeapCommit : uint3264; 46 | // LoaderFlags : uint32; 47 | // NumberOfRvaAndSizes : uint32; 48 | // 49 | //// DataDirectory: array [0 .. IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of TImageDataDirectory; 50 | // DataDirectories : TImageDataDirectories; 51 | // end; 52 | 53 | TImageOptionalHeader32 = packed record 54 | 55 | // Standard fields. 56 | Magic : uint16; // 0x10b: PE32 57 | // 0x20b: PE32+ 58 | MajorLinkerVersion : uint8; 59 | MinorLinkerVersion : uint8; 60 | SizeOfCode : uint32; 61 | SizeOfInitializedData : uint32; 62 | SizeOfUninitializedData : uint32; 63 | AddressOfEntryPoint : uint32; 64 | BaseOfCode : uint32; 65 | 66 | BaseOfData : uint32; // PE32 only 67 | 68 | // NT additional fields. 69 | ImageBase : uint32; 70 | 71 | SectionAlignment : uint32; 72 | FileAlignment : uint32; 73 | MajorOperatingSystemVersion : uint16; 74 | MinorOperatingSystemVersion : uint16; 75 | MajorImageVersion : uint16; 76 | MinorImageVersion : uint16; 77 | MajorSubsystemVersion : uint16; 78 | MinorSubsystemVersion : uint16; 79 | Win32VersionValue : uint32; 80 | SizeOfImage : uint32; 81 | SizeOfHeaders : uint32; 82 | CheckSum : uint32; 83 | Subsystem : uint16; 84 | DllCharacteristics : uint16; 85 | 86 | SizeOfStackReserve : uint32; 87 | SizeOfStackCommit : uint32; 88 | SizeOfHeapReserve : uint32; 89 | SizeOfHeapCommit : uint32; 90 | 91 | LoaderFlags : uint32; 92 | NumberOfRvaAndSizes : uint32; 93 | 94 | // DataDirectory: array [0 .. IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of TImageDataDirectory; 95 | DataDirectories : TImageDataDirectories; 96 | end; 97 | PImageOptionalHeader32 = ^TImageOptionalHeader32; 98 | 99 | TImageOptionalHeader64 = packed record 100 | 101 | // Standard fields. 102 | Magic : uint16; // 0x10b: PE32 103 | // 0x20b: PE32+ 104 | MajorLinkerVersion : uint8; 105 | MinorLinkerVersion : uint8; 106 | SizeOfCode : uint32; 107 | SizeOfInitializedData : uint32; 108 | SizeOfUninitializedData : uint32; 109 | AddressOfEntryPoint : uint32; 110 | BaseOfCode : uint32; 111 | 112 | // NT additional fields. 113 | ImageBase : uint64; 114 | 115 | SectionAlignment : uint32; 116 | FileAlignment : uint32; 117 | MajorOperatingSystemVersion : uint16; 118 | MinorOperatingSystemVersion : uint16; 119 | MajorImageVersion : uint16; 120 | MinorImageVersion : uint16; 121 | MajorSubsystemVersion : uint16; 122 | MinorSubsystemVersion : uint16; 123 | Win32VersionValue : uint32; 124 | SizeOfImage : uint32; 125 | SizeOfHeaders : uint32; 126 | CheckSum : uint32; 127 | Subsystem : uint16; 128 | DllCharacteristics : uint16; 129 | 130 | SizeOfStackReserve : uint64; 131 | SizeOfStackCommit : uint64; 132 | SizeOfHeapReserve : uint64; 133 | SizeOfHeapCommit : uint64; 134 | 135 | LoaderFlags : uint32; 136 | NumberOfRvaAndSizes : uint32; 137 | 138 | // DataDirectory: array [0 .. IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of TImageDataDirectory; 139 | DataDirectories : TImageDataDirectories; 140 | end; 141 | PImageOptionalHeader64 = ^TImageOptionalHeader64; 142 | 143 | TImageOptionalHeader = packed record 144 | case integer of 145 | 32: (pe32: TImageOptionalHeader32); 146 | 64: (pe64: TImageOptionalHeader64); 147 | end; 148 | 149 | 150 | implementation 151 | 152 | end. 153 | -------------------------------------------------------------------------------- /PE.Types.Relocations.inc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.Types.Relocations.inc -------------------------------------------------------------------------------- /PE.Types.Relocations.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.Relocations; 2 | 3 | interface 4 | 5 | uses 6 | {$IFDEF DEBUG} 7 | System.SysUtils, 8 | {$ENDIF} 9 | System.Generics.Collections, 10 | PE.Common, 11 | gRBTree; 12 | 13 | {$I 'PE.Types.Relocations.inc'} // Include constants. 14 | 15 | type 16 | 17 | { TBaseRelocationBlock } 18 | 19 | TBaseRelocationBlock = packed record 20 | // The image base plus the page RVA is added to each offset 21 | // to create the VA where the base relocation must be applied. 22 | PageRVA: UInt32; 23 | 24 | // The total number of bytes in the base relocation block, including the 25 | // Page RVA and Block Size fields and the Type/Offset fields that follow. 26 | BlockSize: UInt32; 27 | 28 | // Get count of relocation elements (entries). 29 | function Count: integer; {$IFNDEF DEBUG} inline; {$ENDIF} 30 | // Check if this block's size:0 or rva:0. 31 | function IsEmpty: Boolean; inline; 32 | end; 33 | 34 | { TBaseRelocationEntry } 35 | 36 | TBaseRelocationEntry = packed record 37 | raw: uint16; 38 | function GetOffset: uint16; 39 | function GetType: byte; 40 | end; 41 | 42 | { TReloc } 43 | 44 | TReloc = packed record 45 | RVA: uint64; // relocatable rva 46 | &Type: integer; // see 5.6.2. Base Relocation Types (IMAGE_REL_BASED_...) 47 | end; 48 | 49 | PReloc = ^TReloc; 50 | 51 | { TRelocs } 52 | 53 | TRelocTree = TRBTree; 54 | 55 | TRelocs = class 56 | private 57 | FItems: TRelocTree; 58 | // Find relocation. Result is pointer to relocation otherwise it's nil. 59 | function FindReloc(RVA: TRVA): PReloc; overload; 60 | public 61 | constructor Create; 62 | destructor Destroy; override; 63 | 64 | procedure Clear; 65 | 66 | function Count: integer; 67 | 68 | function Find(RVA: TRVA; out Reloc: TReloc): Boolean; 69 | 70 | // Add non-existing item, or update existing item. 71 | procedure Put(const Value: TReloc); overload; 72 | procedure Put(RVA: TRVA; &Type: integer); overload; 73 | 74 | // Result is True if reloc was found and removed. 75 | function Remove(RVA: TRVA): Boolean; 76 | 77 | property Items: TRelocTree read FItems; 78 | end; 79 | 80 | implementation 81 | 82 | uses 83 | PE.Utils; 84 | 85 | { TBaseRelocationBlock } 86 | 87 | function TBaseRelocationBlock.Count: integer; 88 | begin 89 | {$IFDEF DEBUG} 90 | if BlockSize < SizeOf(TBaseRelocationBlock) then 91 | raise Exception.Create('Relocation block is too small.'); 92 | {$ENDIF} 93 | result := (BlockSize - SizeOf(TBaseRelocationBlock)) div SizeOf(TBaseRelocationEntry); 94 | end; 95 | 96 | function TBaseRelocationBlock.IsEmpty: Boolean; 97 | begin 98 | result := (PageRVA = 0) or (BlockSize = 0); 99 | end; 100 | 101 | { TBaseRelocationEntry } 102 | 103 | function TBaseRelocationEntry.GetOffset: uint16; 104 | begin 105 | result := raw and $0FFF; 106 | end; 107 | 108 | function TBaseRelocationEntry.GetType: byte; 109 | begin 110 | result := raw shr 12; 111 | end; 112 | 113 | { TRelocs } 114 | 115 | procedure TRelocs.Put(const Value: TReloc); 116 | var 117 | p: PReloc; 118 | begin 119 | p := FindReloc(Value.RVA); 120 | // If item exists, modify it. 121 | if p <> nil then 122 | // This will modify type, rva will stay same. 123 | // If we'll modify rva, tree will be corrupted. 124 | p^ := Value 125 | else 126 | // If not yet exists, add it. 127 | FItems.Add(Value); 128 | end; 129 | 130 | function TRelocs.Remove(RVA: TRVA): Boolean; 131 | var 132 | r: TReloc; 133 | begin 134 | r.RVA := RVA; 135 | r.&Type := 0; // don't care 136 | result := FItems.Remove(r); 137 | end; 138 | 139 | procedure TRelocs.Clear; 140 | begin 141 | FItems.Clear; 142 | end; 143 | 144 | function TRelocs.Count: integer; 145 | begin 146 | result := FItems.Count; 147 | end; 148 | 149 | constructor TRelocs.Create; 150 | begin 151 | FItems := TRelocTree.Create( 152 | function(const A, B: TReloc): Boolean 153 | begin 154 | result := A.RVA < B.RVA; 155 | end); 156 | end; 157 | 158 | destructor TRelocs.Destroy; 159 | begin 160 | FItems.Free; 161 | inherited; 162 | end; 163 | 164 | function TRelocs.Find(RVA: TRVA; out Reloc: TReloc): Boolean; 165 | var 166 | r: TReloc; 167 | p: TRelocTree.TRBNodePtr; 168 | begin 169 | r.RVA := RVA; 170 | r.&Type := 0; // don't care 171 | p := FItems.Find(r); 172 | if p = nil then 173 | Exit(False); 174 | Reloc := p^.K; 175 | Exit(True); 176 | end; 177 | 178 | function TRelocs.FindReloc(RVA: TRVA): PReloc; 179 | var 180 | r: TReloc; 181 | p: TRelocTree.TRBNodePtr; 182 | begin 183 | r.RVA := RVA; 184 | r.&Type := 0; // don't care 185 | p := FItems.Find(r); 186 | if p = nil then 187 | Exit(nil); 188 | Exit(@p^.K); 189 | end; 190 | 191 | procedure TRelocs.Put(RVA: TRVA; &Type: integer); 192 | var 193 | r: TReloc; 194 | begin 195 | r.RVA := RVA; 196 | r.&Type := &Type; 197 | Put(r); 198 | end; 199 | 200 | end. 201 | -------------------------------------------------------------------------------- /PE.Types.Resources.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.Resources; 2 | 3 | interface 4 | 5 | {$IFDEF DEBUG} 6 | uses 7 | System.SysUtils; // to Raise 8 | {$ENDIF} 9 | 10 | // 5.9.1. Resource Directory Table 11 | 12 | type 13 | 14 | { TResourceDirectoryTable } 15 | 16 | TResourceDirectoryTable = packed record 17 | 18 | // Resource flags. This field is reserved for future use. 19 | // It is currently set to zero. 20 | Characteristics: uint32; 21 | 22 | // The time that the resource data was created by the resource compiler. 23 | TimeDateStamp: uint32; 24 | 25 | // The major version number, set by the user. 26 | MajorVersion: uint16; 27 | 28 | // The minor version number, set by the user. 29 | MinorVersion: uint16; 30 | 31 | // The number of directory entries immediately following the table that 32 | // use strings to identify Type, Name, or Language entries (depending on 33 | // the level of the table). 34 | NumberOfNameEntries: uint16; 35 | 36 | // The number of directory entries immediately following the Name entries 37 | // that use numeric IDs for Type, Name, or Language entries. 38 | NumberOfIDEntries: uint16; 39 | 40 | end; 41 | 42 | PResourceDirectoryTable = ^TResourceDirectoryTable; 43 | 44 | TResourceEntryType = (ResourceEntryById, ResourceEntryByName); 45 | 46 | { TResourceDirectoryEntry } 47 | 48 | TResourceDirectoryEntry = packed record 49 | private 50 | 51 | // Either Name RVA or Id. 52 | FEntry: uint32; 53 | 54 | DataEntryRVAorSubdirectoryRVA: uint32; 55 | 56 | function GetDataEntryRVAorSubdirectoryRVA: uint32; inline; 57 | function GetIntegerID: uint32; inline; 58 | function GetNameRVA: uint32; inline; 59 | procedure SetSubDirRVA(const Value: uint32); inline; 60 | procedure SetDataEntryRVA(const Value: uint32); inline; 61 | procedure SetNameRVA(const Value: uint32); inline; 62 | procedure SetIntegerID(const Value: uint32); inline; 63 | function GetResourceEntryType: TResourceEntryType; inline; 64 | 65 | public 66 | 67 | procedure Clear; 68 | 69 | // To check which union select. 70 | function IsDataEntryRVA: boolean; inline; 71 | function IsSubdirectoryRVA: boolean; inline; 72 | 73 | // High bit 0. Address of a Resource Data entry (a leaf). 74 | property DataEntryRVA: uint32 read GetDataEntryRVAorSubdirectoryRVA write SetDataEntryRVA; 75 | 76 | // High bit 1. The lower 31 bits are the address of another resource 77 | // directory table (the next level down). 78 | property SubdirectoryRVA: uint32 read GetDataEntryRVAorSubdirectoryRVA write SetSubDirRVA; 79 | 80 | property EntryType: TResourceEntryType read GetResourceEntryType; 81 | 82 | property NameRVA: uint32 read GetNameRVA write SetNameRVA; 83 | property IntegerID: uint32 read GetIntegerID write SetIntegerID; 84 | end; 85 | 86 | PResourceDirectoryEntry = ^TResourceDirectoryEntry; 87 | 88 | { TResourceDataEntry } 89 | 90 | TResourceDataEntry = packed record 91 | // The address of a unit of resource data in the Resource Data area. 92 | DataRVA: uint32; 93 | 94 | // The size, in bytes, of the resource data that is pointed to by the 95 | // Data RVA field. 96 | Size: uint32; 97 | 98 | // The code page that is used to decode code point values within the 99 | // resource data. Typically, the code page would be the Unicode code page. 100 | Codepage: uint32; 101 | 102 | // Reserved, must be 0. 103 | Reserved: uint32; 104 | end; 105 | 106 | implementation 107 | 108 | procedure TResourceDirectoryEntry.Clear; 109 | begin 110 | FEntry := 0; 111 | DataEntryRVAorSubdirectoryRVA := 0; 112 | end; 113 | 114 | function TResourceDirectoryEntry.GetDataEntryRVAorSubdirectoryRVA: uint32; 115 | begin 116 | Result := DataEntryRVAorSubdirectoryRVA and $7FFFFFFF; 117 | end; 118 | 119 | function TResourceDirectoryEntry.GetIntegerID: uint32; 120 | begin 121 | {$IFDEF DEBUG} 122 | if EntryType <> ResourceEntryById then 123 | raise Exception.Create('Attempt to get ID of named-entry.'); 124 | {$ENDIF} 125 | Result := FEntry and $FFFF; 126 | end; 127 | 128 | function TResourceDirectoryEntry.GetNameRVA: uint32; 129 | begin 130 | {$IFDEF DEBUG} 131 | if EntryType <> ResourceEntryByName then 132 | raise Exception.Create('Attempt to get name RVA of ID-entry.'); 133 | {$ENDIF} 134 | Result := FEntry and $7FFFFFFF; 135 | end; 136 | 137 | function TResourceDirectoryEntry.GetResourceEntryType: TResourceEntryType; 138 | begin 139 | if (self.FEntry and $80000000) = 0 then 140 | Result := ResourceEntryById 141 | else 142 | Result := ResourceEntryByName; 143 | end; 144 | 145 | function TResourceDirectoryEntry.IsDataEntryRVA: boolean; 146 | begin 147 | Result := (DataEntryRVAorSubdirectoryRVA and $80000000) = 0; 148 | end; 149 | 150 | function TResourceDirectoryEntry.IsSubdirectoryRVA: boolean; 151 | begin 152 | Result := (DataEntryRVAorSubdirectoryRVA and $80000000) <> 0; 153 | end; 154 | 155 | procedure TResourceDirectoryEntry.SetDataEntryRVA(const Value: uint32); 156 | begin 157 | DataEntryRVAorSubdirectoryRVA := Value and $7FFFFFFF; 158 | end; 159 | 160 | procedure TResourceDirectoryEntry.SetIntegerID(const Value: uint32); 161 | begin 162 | FEntry := Value and $7FFFFFFF; 163 | end; 164 | 165 | procedure TResourceDirectoryEntry.SetNameRVA(const Value: uint32); 166 | begin 167 | FEntry := Value or $80000000; 168 | end; 169 | 170 | procedure TResourceDirectoryEntry.SetSubDirRVA(const Value: uint32); 171 | begin 172 | DataEntryRVAorSubdirectoryRVA := Value or $80000000; 173 | end; 174 | 175 | end. 176 | -------------------------------------------------------------------------------- /PE.Types.Sections.inc: -------------------------------------------------------------------------------- 1 | {$region 'constants'} 2 | const 3 | // Max length of short section name. 4 | IMAGE_SIZEOF_SHORT_NAME = 8; 5 | 6 | // 3.1 Section Flags 7 | // The section flags in the Characteristics field of the section 8 | // header indicate characteristics of the section. 9 | const 10 | // = $00000000; // Reserved for future use. 11 | // = $00000001; // Reserved for future use. 12 | // = $00000002; // Reserved for future use. 13 | // = $00000004; // Reserved for future use. 14 | IMAGE_SCN_TYPE_NO_PAD = $00000008; // The section should not be padded to the next boundary. 15 | // This flag is obsolete and is replaced by 16 | // IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. 17 | // = $00000010 // Reserved for future use. 18 | IMAGE_SCN_CNT_CODE = $00000020; // The section contains executable code. 19 | IMAGE_SCN_CNT_INITIALIZED_DATA = $00000040; // The section contains initialized data. 20 | IMAGE_SCN_CNT_UNINITIALIZED_DATA = $00000080; // The section contains uninitialized data. 21 | IMAGE_SCN_LNK_OTHER = $00000100; // Reserved for future use. 22 | IMAGE_SCN_LNK_INFO = $00000200; // The section contains comments or other information. 23 | // The .drectve section has this type. This is valid for object files only. 24 | // = $00000400 // Reserved for future use. 25 | IMAGE_SCN_LNK_REMOVE = $00000800; // The section will not become part of the image. This is valid only for object files. 26 | IMAGE_SCN_LNK_COMDAT = $00001000; // The section contains COMDAT data. 27 | IMAGE_SCN_GPREL = $00008000; // The section contains data referenced through the global pointer (GP). 28 | IMAGE_SCN_MEM_PURGEABLE = $00020000; // Reserved for future use. 29 | IMAGE_SCN_MEM_16BIT = $00020000; // For ARM machine types, the section contains Thumb code. 30 | // Reserved for future use with other machine types. 31 | IMAGE_SCN_MEM_LOCKED = $00040000; // Reserved for future use. 32 | IMAGE_SCN_MEM_PRELOAD = $00080000; // Reserved for future use. 33 | IMAGE_SCN_ALIGN_1BYTES = $00100000; // Align data on a 1-byte boundary. Valid only for object files. 34 | IMAGE_SCN_ALIGN_2BYTES = $00200000; // Align data on a 2-byte boundary. Valid only for object files. 35 | IMAGE_SCN_ALIGN_4BYTES = $00300000; // Align data on a 4-byte boundary. Valid only for object files. 36 | IMAGE_SCN_ALIGN_8BYTES = $00400000; // Align data on an 8-byte boundary. Valid only for object files. 37 | IMAGE_SCN_ALIGN_16BYTES = $00500000; // Align data on a 16-byte boundary. Valid only for object files. 38 | IMAGE_SCN_ALIGN_32BYTES = $00600000; // Align data on a 32-byte boundary. Valid only for object files. 39 | IMAGE_SCN_ALIGN_64BYTES = $00700000; // Align data on a 64-byte boundary. Valid only for object files. 40 | IMAGE_SCN_ALIGN_128BYTES = $00800000; // Align data on a 128-byte boundary. Valid only for object files. 41 | IMAGE_SCN_ALIGN_256BYTES = $00900000; // Align data on a 256-byte boundary. Valid only for object files. 42 | IMAGE_SCN_ALIGN_512BYTES = $00A00000; // Align data on a 512-byte boundary. Valid only for object files. 43 | IMAGE_SCN_ALIGN_1024BYTES = $00B00000; // Align data on a 1024-byte boundary. Valid only for object files. 44 | IMAGE_SCN_ALIGN_2048BYTES = $00C00000; // Align data on a 2048-byte boundary. Valid only for object files. 45 | IMAGE_SCN_ALIGN_4096BYTES = $00D00000; // Align data on a 4096-byte boundary. Valid only for object files. 46 | IMAGE_SCN_ALIGN_8192BYTES = $00E00000; // Align data on an 8192-byte boundary. Valid only for object files. 47 | IMAGE_SCN_LNK_NRELOC_OVFL = $01000000; // The section contains extended relocations. 48 | IMAGE_SCN_MEM_DISCARDABLE = $02000000; // The section can be discarded as needed. 49 | IMAGE_SCN_MEM_NOT_CACHED = $04000000; // The section cannot be cached. 50 | IMAGE_SCN_MEM_NOT_PAGED = $08000000; // The section is not pageable. 51 | IMAGE_SCN_MEM_SHARED = $10000000; // The section can be shared in memory. 52 | IMAGE_SCN_MEM_EXECUTE = $20000000; // The section can be executed as code. 53 | IMAGE_SCN_MEM_READ = $40000000; // The section can be read. 54 | IMAGE_SCN_MEM_WRITE = $80000000; // The section can be written to. 55 | {$endregion 'constants'} 56 | -------------------------------------------------------------------------------- /PE.Types.Sections.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types.Sections; 2 | 3 | interface 4 | 5 | {$I 'PE.Types.Sections.inc'} 6 | 7 | 8 | type 9 | TImageSectionHeader = packed record 10 | private 11 | FName: packed array [0 .. IMAGE_SIZEOF_SHORT_NAME - 1] of AnsiChar; 12 | FVirtualSize: uint32; 13 | FRVA: uint32; 14 | FSizeOfRawData: uint32; 15 | FPointerToRawData: uint32; 16 | FPointerToRelocations: uint32; 17 | FPointerToLinenumbers: uint32; 18 | FNumberOfRelocations: uint16; 19 | FNumberOfLinenumbers: uint16; 20 | FFlags: uint32; 21 | private 22 | function GetName: string; 23 | procedure SetName(const Value: string); // length trimmed to 8 chars 24 | public 25 | procedure Clear; inline; 26 | 27 | property Name: string read GetName write SetName; 28 | property VirtualSize: uint32 read FVirtualSize write FVirtualSize; 29 | property RVA: uint32 read FRVA write FRVA; 30 | property SizeOfRawData: uint32 read FSizeOfRawData write FSizeOfRawData; 31 | property PointerToRawData: uint32 read FPointerToRawData write FPointerToRawData; 32 | property Flags: uint32 read FFlags write FFlags; 33 | end; 34 | 35 | PImageSectionHeader = ^TImageSectionHeader; 36 | 37 | implementation 38 | 39 | { TImageSectionHeader } 40 | 41 | procedure TImageSectionHeader.Clear; 42 | begin 43 | fillchar(self, sizeof(self), 0); 44 | end; 45 | 46 | function TImageSectionHeader.GetName: string; 47 | var 48 | i: Integer; 49 | begin 50 | i := 0; 51 | while (i < IMAGE_SIZEOF_SHORT_NAME) and (FName[i] <> #0) do 52 | inc(i); 53 | 54 | if i = 0 then 55 | exit(''); 56 | 57 | setlength(result, i); 58 | 59 | dec(i); 60 | 61 | while i >= 0 do 62 | begin 63 | result[low(result) + i] := char(FName[i]); 64 | dec(i); 65 | end; 66 | end; 67 | 68 | procedure TImageSectionHeader.SetName(const Value: string); 69 | var 70 | i, len: Integer; 71 | begin 72 | i := 0; 73 | len := length(Value); 74 | 75 | while i < len do 76 | begin 77 | FName[i] := AnsiChar(Value[low(Value) + i]); 78 | inc(i); 79 | end; 80 | 81 | while i < IMAGE_SIZEOF_SHORT_NAME do 82 | begin 83 | FName[i] := #0; 84 | inc(i); 85 | end; 86 | end; 87 | 88 | end. 89 | -------------------------------------------------------------------------------- /PE.Types.TLS.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/PE.Types.TLS.pas -------------------------------------------------------------------------------- /PE.Types.pas: -------------------------------------------------------------------------------- 1 | unit PE.Types; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, 7 | PE.Common; 8 | 9 | type 10 | TRVAs = TList; 11 | 12 | TPEParser = class 13 | FPE: TObject; 14 | constructor Create(PEImage: TObject); 15 | function Parse: TParserResult; virtual; abstract; 16 | end; 17 | 18 | TPEParserClass = class of TPEParser; 19 | 20 | implementation 21 | 22 | { TPEParser } 23 | 24 | constructor TPEParser.Create(PEImage: TObject); 25 | begin 26 | FPE := PEImage; 27 | end; 28 | 29 | end. 30 | -------------------------------------------------------------------------------- /PE.Utils.pas: -------------------------------------------------------------------------------- 1 | unit PE.Utils; 2 | 3 | interface 4 | 5 | // When writing padding use PADDINGX string instead of zeros. 6 | {$DEFINE WRITE_PADDING_STRING} 7 | 8 | 9 | uses 10 | System.Classes, 11 | System.SysUtils, 12 | 13 | PE.Common; 14 | 15 | function StreamRead(AStream: TStream; var Buf; Count: longint): boolean; inline; 16 | function StreamPeek(AStream: TStream; var Buf; Count: longint): boolean; inline; 17 | function StreamWrite(AStream: TStream; const Buf; Count: longint): boolean; inline; 18 | 19 | // Read 0-terminated 1-byte string. 20 | function StreamReadStringA(AStream: TStream; var S: string): boolean; 21 | 22 | // Read 0-terminated 2-byte string 23 | function StreamReadStringW(AStream: TStream; var S: string): boolean; 24 | 25 | // Write string and return number of bytes written. 26 | // If AlignAfter isn't 0 zero bytes will be written to align it up to AlignAfter value. 27 | function StreamWriteString(AStream: TStream; const S: string; Encoding: TEncoding; AlignAfter: integer = 0): uint32; 28 | // Write ANSI string and return number of bytes written. 29 | function StreamWriteStringA(AStream: TStream; const S: string; AlignAfter: integer = 0): uint32; 30 | 31 | const 32 | PATTERN_PADDINGX: array [0 .. 7] of AnsiChar = ('P', 'A', 'D', 'D', 'I', 'N', 'G', 'X'); 33 | 34 | // Write pattern to stream. If Pattern is nil or size of patter is 0 then 35 | // nulls are written (default). 36 | procedure WritePattern(AStream: TStream; Count: uint32; Pattern: Pointer = nil; PatternSize: integer = 0); 37 | 38 | function StreamSeek(AStream: TStream; Offset: TFileOffset): boolean; inline; 39 | function StreamSkip(AStream: TStream; Count: integer = 1): boolean; inline; 40 | 41 | // Seek from current position to keep alignment. 42 | function StreamSeekAlign(AStream: TStream; Align: integer): boolean; inline; 43 | 44 | // Try to seek Offset and insert padding if Offset < stream Size. 45 | procedure StreamSeekWithPadding(AStream: TStream; Offset: TFileOffset); 46 | 47 | function Min(const A, B: uint64): uint64; inline; overload; 48 | function Min(const A, B, C: uint64): uint64; inline; overload; 49 | 50 | function Max(const A, B: uint64): uint64; inline; 51 | 52 | function AlignUp(Value: uint64; Align: uint32): uint64; inline; 53 | function AlignDown(Value: uint64; Align: uint32): uint64; inline; 54 | 55 | function IsAlphaNumericString(const S: String): boolean; 56 | 57 | function CompareRVA(A, B: TRVA): integer; inline; 58 | 59 | function ReplaceSpecialSymobls(const source: string): string; 60 | 61 | implementation 62 | 63 | { Stream } 64 | 65 | function StreamRead(AStream: TStream; var Buf; Count: longint): boolean; 66 | begin 67 | Result := AStream.Read(Buf, Count) = Count; 68 | end; 69 | 70 | function StreamPeek(AStream: TStream; var Buf; Count: longint): boolean; inline; 71 | var 72 | Read: integer; 73 | begin 74 | Read := AStream.Read(Buf, Count); 75 | AStream.Seek(-Read, soFromCurrent); 76 | Result := Read = Count; 77 | end; 78 | 79 | function StreamWrite(AStream: TStream; const Buf; Count: longint): boolean; 80 | begin 81 | Result := AStream.Write(Buf, Count) = Count; 82 | end; 83 | 84 | function StreamReadStringA(AStream: TStream; var S: string): boolean; 85 | var 86 | C: byte; 87 | begin 88 | S := ''; 89 | while True do 90 | if AStream.Read(C, SizeOf(C)) <> SizeOf(C) then 91 | break 92 | else if (C = 0) then 93 | exit(True) 94 | else 95 | S := S + Char(C); 96 | exit(False); 97 | end; 98 | 99 | function StreamReadStringW(AStream: TStream; var S: string): boolean; 100 | var 101 | C: word; 102 | begin 103 | S := ''; 104 | while True do 105 | if AStream.Read(C, SizeOf(C)) <> SizeOf(C) then 106 | break 107 | else if (C = 0) then 108 | exit(True) 109 | else 110 | S := S + Char(C); 111 | exit(False); 112 | end; 113 | 114 | function StreamWriteString(AStream: TStream; const S: string; Encoding: TEncoding; AlignAfter: integer): uint32; 115 | var 116 | Bytes: TBytes; 117 | begin 118 | Bytes := Encoding.GetBytes(S); 119 | Result := AStream.Write(Bytes, Length(Bytes)); 120 | 121 | if AlignAfter <> 0 then 122 | begin 123 | // Number of bytes left to write to be aligned. 124 | AlignAfter := AlignAfter - (AStream.Size mod AlignAfter); 125 | WritePattern(AStream, AlignAfter, nil, 0); 126 | end; 127 | end; 128 | 129 | function StreamWriteStringA(AStream: TStream; const S: string; AlignAfter: integer): uint32; 130 | begin 131 | Result := StreamWriteString(AStream, S, TEncoding.ANSI, AlignAfter); 132 | end; 133 | 134 | procedure WritePattern(AStream: TStream; Count: uint32; Pattern: Pointer; PatternSize: integer); 135 | var 136 | p: pbyte; 137 | i: integer; 138 | begin 139 | if Count = 0 then 140 | exit; 141 | 142 | if Assigned(Pattern) and (PatternSize > 0) then 143 | begin 144 | p := GetMemory(Count); 145 | if PatternSize = 1 then 146 | FillChar(p^, Count, pbyte(Pattern)^) 147 | else 148 | begin 149 | for i := 0 to Count - 1 do 150 | p[i] := pbyte(Pattern)[i mod PatternSize]; 151 | end; 152 | end 153 | else 154 | begin 155 | p := AllocMem(Count); // filled with nulls 156 | end; 157 | 158 | try 159 | AStream.Write(p^, Count); 160 | finally 161 | FreeMem(p); 162 | end; 163 | end; 164 | 165 | function StreamSeek(AStream: TStream; Offset: TFileOffset): boolean; 166 | begin 167 | Result := AStream.Seek(Offset, TSeekOrigin.soBeginning) = Offset; 168 | end; 169 | 170 | function StreamSkip(AStream: TStream; Count: integer): boolean; inline; 171 | var 172 | Offset: TFileOffset; 173 | begin 174 | Offset := AStream.Position + Count; 175 | Result := AStream.Seek(Offset, TSeekOrigin.soBeginning) = Offset; 176 | end; 177 | 178 | function StreamSeekAlign(AStream: TStream; Align: integer): boolean; 179 | var 180 | m: integer; 181 | pos: TFileOffset; 182 | begin 183 | if Align in [0, 1] then 184 | exit(True); // don't need alignment 185 | pos := AStream.Position; 186 | m := pos mod Align; 187 | if m = 0 then 188 | exit(True); // already aligned 189 | inc(pos, Align - m); // next aligned position 190 | Result := AStream.Seek(pos, TSeekOrigin.soBeginning) = pos; 191 | end; 192 | 193 | procedure StreamSeekWithPadding(AStream: TStream; Offset: TFileOffset); 194 | begin 195 | if Offset <= AStream.Size then 196 | begin 197 | AStream.Seek(Offset, TSeekOrigin.soBeginning); 198 | exit; 199 | end; 200 | // Insert padding if need. 201 | AStream.Seek(AStream.Size, TSeekOrigin.soBeginning); 202 | WritePattern(AStream, Offset - AStream.Size, nil, 0); 203 | end; 204 | 205 | { Min / Max } 206 | 207 | function Min(const A, B: uint64): uint64; 208 | begin 209 | if A < B then 210 | exit(A) 211 | else 212 | exit(B); 213 | end; 214 | 215 | function Min(const A, B, C: uint64): uint64; 216 | begin 217 | Result := Min(Min(A, B), C); 218 | end; 219 | 220 | function Max(const A, B: uint64): uint64; 221 | begin 222 | if A > B then 223 | exit(A) 224 | else 225 | exit(B); 226 | end; 227 | 228 | { AlignUp } 229 | 230 | function AlignUp(Value: uint64; Align: uint32): uint64; 231 | var 232 | d, m: uint32; 233 | begin 234 | d := Value div Align; 235 | m := Value mod Align; 236 | if m = 0 then 237 | Result := Value 238 | else 239 | Result := (d + 1) * Align; 240 | end; 241 | 242 | function AlignDown(Value: uint64; Align: uint32): uint64; 243 | begin 244 | Result := (Value div Align) * Align; 245 | end; 246 | 247 | function IsAlphaNumericString(const S: String): boolean; 248 | const 249 | ALLOWED_CHARS = ['0' .. '9', 'A' .. 'Z', 'a' .. 'z']; 250 | var 251 | C: Char; 252 | begin 253 | for C in S do 254 | if not CharInSet(c, ALLOWED_CHARS) then 255 | exit(False); 256 | exit(True); 257 | end; 258 | 259 | function CompareRVA(A, B: TRVA): integer; 260 | begin 261 | if A > B then 262 | exit(1) 263 | else if A < B then 264 | exit(-1) 265 | else 266 | exit(0); 267 | end; 268 | 269 | function ReplaceSpecialSymobls(const source: string): string; 270 | begin 271 | Result := source. 272 | Replace(#10, '\n'). 273 | Replace(#13, '\r'); 274 | end; 275 | 276 | end. 277 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pe-image-for-delphi 2 | 3 | This is Delphi library to work with Portable Executable Image files. 4 | The main purpose is to make parsing image structures of 32/64 bit image easy. 5 | Now it can parse most used things, like: sections, imports, exports, resources and tls. 6 | 7 | Also it can write image, but that was not primary goal. 8 | 9 | ToDo: 10 | 11 | - There must be introduced sparsed/paged virtual memory concept (just like Windows does) 12 | Currently all section data is loaded into memory. 13 | Some virus samples tested can crash loading because of using too big virtual address range. 14 | With normal compiler generated images you won't have such problem. 15 | -------------------------------------------------------------------------------- /Test/test.dpr: -------------------------------------------------------------------------------- 1 | program test; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | Windows, 9 | SysUtils, 10 | PE.Common, 11 | PE.Image, 12 | PE.Imports.Lib, 13 | PE.Imports.Func, 14 | PE.Build, 15 | PE.Section; 16 | 17 | const 18 | NORMAL_EXIT_CODE = $C0DE; 19 | SRC_FILENAME = 'test.exe'; 20 | DST_FILENAME = 'test.out.exe'; 21 | 22 | procedure Log(text: string); 23 | begin 24 | WriteLn(Format('[%4x] %s', [GetCurrentProcessId(), text])); 25 | end; 26 | 27 | function RunWithParam(exe: string; param: string = ''): boolean; 28 | var 29 | cmdLine: string; 30 | si: TStartupInfo; 31 | pi: TProcessInformation; 32 | code: dword; 33 | begin 34 | cmdLine := param; 35 | 36 | ZeroMemory(@si, sizeof(si)); 37 | si.cb := sizeof(si); 38 | 39 | ZeroMemory(@pi, sizeof(pi)); 40 | 41 | if (CreateProcess(Pointer(exe), Pointer(cmdLine), nil, nil, false, 0, nil, nil, si, pi)) then 42 | begin 43 | WaitForSingleObject(pi.hProcess, INFINITE); 44 | if (GetExitCodeProcess(pi.hProcess, code)) then 45 | begin 46 | CloseHandle(pi.hThread); 47 | CloseHandle(pi.hProcess); 48 | exit(code = NORMAL_EXIT_CODE); 49 | end; 50 | end; 51 | 52 | exit(false); 53 | end; 54 | 55 | procedure WritePushDword(img: TPEImage; value: dword); 56 | var 57 | rec: packed record 58 | op: byte; 59 | value: dword; 60 | end; 61 | begin 62 | rec.op := $68; 63 | rec.value := value; 64 | img.Write(rec, sizeof(rec)); 65 | end; 66 | 67 | function GetIatEntryAddr(img: TPEImage; lib: TPEImportLibrary; fn: TPEImportFunction): NativeUInt; 68 | var 69 | i: integer; 70 | begin 71 | for i := 0 to lib.Functions.Count - 1 do 72 | if (lib.Functions[i] = fn) then 73 | exit(lib.IatRva + i * img.ImageWordSize); 74 | 75 | raise Exception.Create('Function not found'); 76 | end; 77 | 78 | procedure WriteCall(img: TPEImage; lib: TPEImportLibrary; fn: TPEImportFunction); 79 | var 80 | rec: packed record 81 | op1, op2: byte; 82 | addr: NativeUInt; 83 | end; 84 | begin 85 | rec.op1 := $FF; 86 | rec.op2 := $15; 87 | rec.addr := img.RVAToVA(GetIatEntryAddr(img, lib, fn)); 88 | img.Write(rec, sizeof(rec)); 89 | end; 90 | 91 | procedure WriteJmpRel(img: TPEImage; dst: TRVA); 92 | var 93 | rec: packed record 94 | op: byte; 95 | delta: dword; 96 | end; 97 | begin 98 | rec.op := $E9; 99 | rec.delta := dst - (img.PositionRVA + sizeof(rec)); 100 | img.Write(rec, sizeof(rec)); 101 | end; 102 | 103 | procedure TestImportRebuilding(); 104 | var 105 | img: TPEImage; 106 | oep: TRVA; 107 | sec: TPESection; 108 | lib: TPEImportLibrary; 109 | fn: TPEImportFunction; 110 | begin 111 | img := TPEImage.Create(); 112 | try 113 | if (img.LoadFromFile(SRC_FILENAME)) then 114 | begin 115 | lib := img.Imports.NewLib(kernel32); 116 | fn := lib.NewFunction('Beep'); 117 | 118 | if (ReBuildDirData(img, DDIR_IMPORT, true) <> nil) then 119 | begin 120 | // Make some code to call beep at new entry point (in new section). 121 | // Then jump to original entry point. 122 | // Without relocations to make it simpler. 123 | oep := img.EntryPointRVA; 124 | sec := img.Sections.AddNew('.my', 32, $60000020, nil); 125 | img.EntryPointRVA := sec.RVA; 126 | 127 | img.SeekRVA(sec.RVA); 128 | WritePushDword(img, 1000); // dwDuration 129 | WritePushDword(img, 5000); // dwFreq 130 | WriteCall(img, lib, fn); 131 | WriteJmpRel(img, oep); 132 | 133 | // Save to file and try to run. 134 | img.SaveToFile(DST_FILENAME); 135 | 136 | if (RunWithParam(DST_FILENAME)) then 137 | begin 138 | Log('Import rebuilt OK'); 139 | end 140 | else 141 | begin 142 | Log('Rebuilt image failed'); 143 | end; 144 | end 145 | else 146 | begin 147 | Log('Failed to rebuld imports'); 148 | end; 149 | end 150 | else 151 | begin 152 | Log('Failed to parse image'); 153 | end; 154 | finally 155 | img.Free(); 156 | end; 157 | 158 | RunWithParam(SRC_FILENAME); 159 | end; 160 | 161 | begin 162 | if (ParamStr(1) = 'test') then 163 | begin 164 | Log('Test call'); 165 | TestImportRebuilding(); 166 | end 167 | else 168 | begin 169 | Log('Normal call'); 170 | Halt(NORMAL_EXIT_CODE); 171 | end; 172 | end. 173 | -------------------------------------------------------------------------------- /VerRsrc.inc: -------------------------------------------------------------------------------- 1 | { 2 | * 3 | * verrsrc.h - Version Resource definitions 4 | * 5 | * Include file declaring version resources in rc files 6 | * 7 | * Copyright (c) Microsoft Corporation. All rights reserved. 8 | * 9 | } 10 | 11 | const 12 | 13 | (* ----- Symbols ----- *) 14 | // VS_FILE_INFO = RT_VERSION; 15 | VS_VERSION_INFO = 1; 16 | VS_USER_DEFINED = 100; 17 | 18 | (* ----- VS_VERSION.dwFileFlags ----- *) 19 | {$IFNDEF _MAC} 20 | VS_FFI_SIGNATURE = $FEEF04BD; 21 | {$ELSE} 22 | VS_FFI_SIGNATURE = $BD04EFFE; 23 | {$ENDIF} 24 | VS_FFI_STRUCVERSION = $00010000; 25 | VS_FFI_FILEFLAGSMASK = $0000003F; 26 | 27 | (* ----- VS_VERSION.dwFileFlags ----- *) 28 | VS_FF_DEBUG = $00000001; 29 | VS_FF_PRERELEASE = $00000002; 30 | VS_FF_PATCHED = $00000004; 31 | VS_FF_PRIVATEBUILD = $00000008; 32 | VS_FF_INFOINFERRED = $00000010; 33 | VS_FF_SPECIALBUILD = $00000020; 34 | 35 | (* ----- VS_VERSION.dwFileOS ----- *) 36 | VOS_UNKNOWN = $00000000; 37 | VOS_DOS = $00010000; 38 | VOS_OS216 = $00020000; 39 | VOS_OS232 = $00030000; 40 | VOS_NT = $00040000; 41 | VOS_WINCE = $00050000; 42 | 43 | VOS__BASE = $00000000; 44 | VOS__WINDOWS16 = $00000001; 45 | VOS__PM16 = $00000002; 46 | VOS__PM32 = $00000003; 47 | VOS__WINDOWS32 = $00000004; 48 | 49 | VOS_DOS_WINDOWS16 = $00010001; 50 | VOS_DOS_WINDOWS32 = $00010004; 51 | VOS_OS216_PM16 = $00020002; 52 | VOS_OS232_PM32 = $00030003; 53 | VOS_NT_WINDOWS32 = $00040004; 54 | 55 | (* ----- VS_VERSION.dwFileType ----- *) 56 | VFT_UNKNOWN = $00000000; 57 | VFT_APP = $00000001; 58 | VFT_DLL = $00000002; 59 | VFT_DRV = $00000003; 60 | VFT_FONT = $00000004; 61 | VFT_VXD = $00000005; 62 | VFT_STATIC_LIB = $00000007; 63 | 64 | (* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_DRV ----- *) 65 | VFT2_UNKNOWN = $00000000; 66 | VFT2_DRV_PRINTER = $00000001; 67 | VFT2_DRV_KEYBOARD = $00000002; 68 | VFT2_DRV_LANGUAGE = $00000003; 69 | VFT2_DRV_DISPLAY = $00000004; 70 | VFT2_DRV_MOUSE = $00000005; 71 | VFT2_DRV_NETWORK = $00000006; 72 | VFT2_DRV_SYSTEM = $00000007; 73 | VFT2_DRV_INSTALLABLE = $00000008; 74 | VFT2_DRV_SOUND = $00000009; 75 | VFT2_DRV_COMM = $0000000A; 76 | VFT2_DRV_INPUTMETHOD = $0000000B; 77 | VFT2_DRV_VERSIONED_PRINTER = $0000000C; 78 | 79 | (* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_FONT ----- *) 80 | VFT2_FONT_RASTER = $00000001; 81 | VFT2_FONT_VECTOR = $00000002; 82 | VFT2_FONT_TRUETYPE = $00000003; 83 | 84 | (* ----- VerFindFile() flags ----- *) 85 | VFFF_ISSHAREDFILE = $0001; 86 | 87 | VFF_CURNEDEST = $0001; 88 | VFF_FILEINUSE = $0002; 89 | VFF_BUFFTOOSMALL = $0004; 90 | 91 | (* ----- VerInstallFile() flags ----- *) 92 | VIFF_FORCEINSTALL = $0001; 93 | VIFF_DONTDELETEOLD = $0002; 94 | 95 | VIF_TEMPFILE = $00000001; 96 | VIF_MISMATCH = $00000002; 97 | VIF_SRCOLD = $00000004; 98 | 99 | VIF_DIFFLANG = $00000008; 100 | VIF_DIFFCODEPG = $00000010; 101 | VIF_DIFFTYPE = $00000020; 102 | 103 | VIF_WRITEPROT = $00000040; 104 | VIF_FILEINUSE = $00000080; 105 | VIF_OUTOFSPACE = $00000100; 106 | VIF_ACCESSVIOLATION = $00000200; 107 | VIF_SHARINGVIOLATION = $00000400; 108 | VIF_CANNOTCREATE = $00000800; 109 | VIF_CANNOTDELETE = $00001000; 110 | VIF_CANNOTRENAME = $00002000; 111 | VIF_CANNOTDELETECUR = $00004000; 112 | VIF_OUTOFMEMORY = $00008000; 113 | 114 | VIF_CANNOTREADSRC = $00010000; 115 | VIF_CANNOTREADDST = $00020000; 116 | 117 | VIF_BUFFTOOSMALL = $00040000; 118 | VIF_CANNOTLOADLZ32 = $00080000; 119 | VIF_CANNOTLOADCABINET = $00100000; 120 | 121 | {$IFNDEF RC_INVOKED} (* RC doesn't need to see the rest of this *) 122 | 123 | (* 124 | FILE_VER_GET_... flags are for use by 125 | GetFileVersionInfoSizeEx 126 | GetFileVersionInfoExW 127 | *) 128 | FILE_VER_GET_LOCALISED = $01; 129 | FILE_VER_GET_NEUTRAL = $02; 130 | FILE_VER_GET_PREFETCHED = $04; 131 | 132 | (* ----- Types and structures ----- *) 133 | 134 | type 135 | tagVS_FIXEDFILEINFO = packed record 136 | dwSignature: uint32; (* e.g. =$feef04bd *) 137 | dwStrucVersion: uint32; (* e.g. =$00000042 = "0.42" *) 138 | dwFileVersionMS: uint32; (* e.g. =$00030075 = "3.75" *) 139 | dwFileVersionLS: uint32; (* e.g. =$00000031 = "0.31" *) 140 | dwProductVersionMS: uint32; (* e.g. =$00030010 = "3.10" *) 141 | dwProductVersionLS: uint32; (* e.g. =$00000031 = "0.31" *) 142 | dwFileFlagsMask: uint32; (* = $3F for version "0.42" *) 143 | dwFileFlags: uint32; (* e.g. VFF_DEBUG | VFF_PRERELEASE *) 144 | dwFileOS: uint32; (* e.g. VOS_DOS_WINDOWS16 *) 145 | dwFileType: uint32; (* e.g. VFT_DRIVER *) 146 | dwFileSubtype: uint32; (* e.g. VFT2_DRV_KEYBOARD *) 147 | dwFileDateMS: uint32; (* e.g. 0 *) 148 | dwFileDateLS: uint32; (* e.g. 0 *) 149 | end; 150 | 151 | VS_FIXEDFILEINFO = tagVS_FIXEDFILEINFO; 152 | 153 | {$ENDIF} (* !RC_INVOKED *) 154 | 155 | -------------------------------------------------------------------------------- /bin/examples/UserDB.TXT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdisasm/pe-image-for-delphi/e80b88c0ccd97b4c5c30279db45376e9d17097ae/bin/examples/UserDB.TXT -------------------------------------------------------------------------------- /bin/examples/dummy.txt: -------------------------------------------------------------------------------- 1 | Hello, I'm dummy text file. --------------------------------------------------------------------------------