├── .gitmodules ├── LICENSE ├── README.md ├── glueimport.dpr ├── glueimport.dproj └── vmsweeper.reffile.pas /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "peimage"] 2 | path = peimage 3 | url = https://github.com/vdisasm/pe-image-for-delphi 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pas-vmp-glueimport 2 | 3 | It's a simple project to add imports recovered by VMSweeper tool (by Vamit) to VMProtect dump file. 4 | Tested to compile with Delphi XE8. 5 | 6 | As you can see from the sources, it parses reference file generated by VMSweeper, extracts import infos and generates new import table for dump file (uses pe-image-for-delphi project). 7 | 8 | It's not guaranteed to work on any file. 9 | 10 | This repository was created in case it will be helpful. 11 | It's not going to be updated. 12 | -------------------------------------------------------------------------------- /glueimport.dpr: -------------------------------------------------------------------------------- 1 | program glueimport; 2 | 3 | {$APPTYPE CONSOLE} 4 | {$R *.res} 5 | {$WARN SYMBOL_PLATFORM OFF} 6 | 7 | 8 | uses 9 | System.Classes, 10 | System.Generics.Collections, 11 | System.SysUtils, 12 | 13 | vmsweeper.reffile in 'vmsweeper.reffile.pas', 14 | 15 | pe.build, 16 | 17 | pe.build.import, 18 | pe.common, 19 | pe.image, 20 | pe.imports, 21 | pe.imports.lib, 22 | pe.imports.func, 23 | pe.section, 24 | pe.types.directories; 25 | 26 | procedure generate_new_imports(img: TPEImage; Libs: TLibs); 27 | const 28 | opcJmpPtr: word = $25FF; 29 | opcCallPtr: word = $15FF; 30 | var 31 | libPair: TLibPair; 32 | funcPair: TFuncPair; 33 | tmpVA, iatVA: uint64; 34 | lib: TPEImportLibrary; 35 | iatIndex: UInt32; 36 | imps: TImpList; 37 | i: Integer; 38 | rec: TImp; 39 | FlatImpList: TImpList; 40 | iatDir: TImageDataDirectory; 41 | ordinal: integer; 42 | begin 43 | 44 | FlatImpList := TImpList.Create; 45 | try 46 | // Fill image imports. 47 | 48 | img.imports.Clear; 49 | 50 | iatIndex := 0; 51 | for libPair in Libs do 52 | begin 53 | writeln(' ', libPair.Key); 54 | 55 | lib := img.imports.NewLib(libPair.Key); 56 | for funcPair in libPair.Value do 57 | begin 58 | if (funcPair.Key.StartsWith('#')) then 59 | begin 60 | // By ordinal. 61 | if (not integer.TryParse(funcPair.Key.Substring(1), ordinal)) then 62 | begin 63 | Writeln('Ordinal not parsed: ', libPair.Key, ', ordinal ', funcPair.Key); 64 | continue; 65 | end; 66 | lib.NewFunction(ordinal); 67 | end 68 | else 69 | begin 70 | // By name. 71 | lib.NewFunction(funcPair.Key); 72 | end; 73 | 74 | imps := funcPair.Value; 75 | for i := 0 to imps.Count - 1 do 76 | begin 77 | rec := imps[i]; 78 | rec.iatIndex := iatIndex; 79 | FlatImpList.Add(rec); 80 | end; 81 | 82 | inc(iatIndex); 83 | end; 84 | 85 | inc(iatIndex); // null item 86 | end; 87 | 88 | // Create new import table. 89 | ReBuildDirData(img, DDIR_IMPORT, true); 90 | 91 | // Redirect imports to new IAT. 92 | if not img.DataDirectories.Get(DDIR_IAT, @iatDir) then 93 | raise Exception.Create('Failed to get IAT.'); 94 | 95 | iatVA := img.RVAToVA(iatDir.VirtualAddress); 96 | 97 | for rec in FlatImpList do 98 | begin 99 | // jmp dword ptr [...] 100 | img.PositionVA := rec.srcVA; 101 | 102 | if (rec.dispType = TImpDispatchType.Jump) then 103 | begin 104 | img.Write(opcJmpPtr, sizeof(opcJmpPtr)); 105 | end 106 | else 107 | begin 108 | img.Write(opcCallPtr, sizeof(opcCallPtr)); 109 | end; 110 | 111 | // address 112 | tmpVA := iatVA + rec.iatIndex * img.ImageWordSize; 113 | img.Write(tmpVA, img.ImageWordSize) 114 | end; 115 | finally 116 | FreeAndNil(FlatImpList); 117 | end; 118 | end; 119 | 120 | procedure PrintHeader; 121 | begin 122 | writeln('VMProtect import fixer based on VMSweeper'); 123 | writeln('Credits to Vamit'); 124 | writeln; 125 | writeln('This tool applies imports found by VMSweeper to existing dump'); 126 | writeln('Generally it''s for analysis, not for complete image unpack'); 127 | writeln; 128 | writeln('Usage:'); 129 | writeln(' []'); 130 | writeln(' : text file, just copy it from VMSweeper''s VM References window'); 131 | writeln(' it must contain resolved imports'); 132 | writeln(' : executable dump file name used to add imports'); 133 | writeln(' : optional file name for result executable'); 134 | end; 135 | 136 | procedure main(const refFn, dumpFn, outFn: string); 137 | var 138 | Libs: TLibs; 139 | img: TPEImage; 140 | outputFn: string; 141 | begin 142 | if (refFn.IsEmpty) or (dumpFn.IsEmpty) then 143 | begin 144 | PrintHeader; 145 | exit; 146 | end; 147 | 148 | Libs := TLibs.Create([doOwnsValues]); 149 | try 150 | write('Parsing reference file... '); 151 | ParseImportsFromRefFile(refFn, Libs); 152 | writeln(Libs.Count, ' libraries'); 153 | 154 | img := TPEImage.Create; 155 | try 156 | write('Reading dump... '); 157 | // load dump only, don't parse directories' content 158 | if not img.LoadFromFile(dumpFn, []) then 159 | begin 160 | writeln('failed'); 161 | exit; 162 | end; 163 | writeln('ok'); 164 | 165 | writeln('Creating new imports'); 166 | generate_new_imports(img, Libs); 167 | 168 | if outFn <> '' then 169 | outputFn := outFn 170 | else 171 | outputFn := ChangeFileExt(dumpFn, '_imp' + ExtractFileExt(dumpFn)); 172 | 173 | writeln('Saving new image'); 174 | img.SaveToFile(outputFn); 175 | 176 | writeln('Done'); 177 | finally 178 | img.Free; 179 | end; 180 | finally 181 | FreeAndNil(Libs); 182 | end; 183 | end; 184 | 185 | begin 186 | ReportMemoryLeaksOnShutdown := true; 187 | try 188 | main(paramstr(1), paramstr(2), paramstr(3)); 189 | if DebugHook <> 0 then 190 | readln; 191 | except 192 | on E: Exception do 193 | writeln(E.ClassName, ': ', E.Message); 194 | end; 195 | end. 196 | -------------------------------------------------------------------------------- /glueimport.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {C4F17033-8BCC-48F3-B054-6C55636A5FFC} 4 | glueimport.dpr 5 | True 6 | Debug 7 | 1 8 | Console 9 | None 10 | 18.0 11 | Win32 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Base 34 | true 35 | 36 | 37 | true 38 | Base 39 | true 40 | 41 | 42 | true 43 | Base 44 | true 45 | 46 | 47 | true 48 | Cfg_1 49 | true 50 | true 51 | 52 | 53 | true 54 | Base 55 | true 56 | 57 | 58 | true 59 | Cfg_2 60 | true 61 | true 62 | 63 | 64 | false 65 | 1033 66 | false 67 | false 68 | $(BDS)\bin\delphi_PROJECTICON.ico 69 | false 70 | glueimport 71 | peimage;$(DCC_UnitSearchPath) 72 | $(BDS)\bin\delphi_PROJECTICNS.icns 73 | bin\$(platform)-$(config) 74 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= 75 | 00400000 76 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 77 | false 78 | 79 | 80 | $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png 81 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png 82 | $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png 83 | true 84 | true 85 | $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png 86 | true 87 | true 88 | true 89 | true 90 | true 91 | true 92 | $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png 93 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png 94 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png 95 | true 96 | true 97 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png 98 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png 99 | 100 | 101 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png 102 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png 103 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png 104 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png 105 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png 106 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png 107 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png 108 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png 109 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png 110 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png 111 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png 112 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png 113 | 114 | 115 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png 116 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png 117 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png 118 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png 119 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png 120 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png 121 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png 122 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png 123 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png 124 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png 125 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png 126 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png 127 | 128 | 129 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png 130 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png 131 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png 132 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png 133 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png 134 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png 135 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png 136 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png 137 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png 138 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png 139 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png 140 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png 141 | 142 | 143 | 1033 144 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 145 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 146 | 147 | 148 | 0 149 | false 150 | 0 151 | RELEASE;$(DCC_Define) 152 | 153 | 154 | None 155 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 156 | 157 | 158 | true 159 | DEBUG;$(DCC_Define) 160 | false 161 | 162 | 163 | None 164 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 165 | 166 | 167 | 168 | MainSource 169 | 170 | 171 | 172 | Cfg_2 173 | Base 174 | 175 | 176 | Base 177 | 178 | 179 | Cfg_1 180 | Base 181 | 182 | 183 | 184 | Delphi.Personality.12 185 | 186 | 187 | 188 | 189 | glueimport.dpr 190 | 191 | 192 | Microsoft Office 2000 Sample Automation Server Wrapper Components 193 | Microsoft Office XP Sample Automation Server Wrapper Components 194 | 195 | 196 | 197 | False 198 | False 199 | False 200 | False 201 | False 202 | True 203 | False 204 | 205 | 206 | 12 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /vmsweeper.reffile.pas: -------------------------------------------------------------------------------- 1 | unit vmsweeper.reffile; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.Generics.Collections, 8 | System.SysUtils; 9 | 10 | type 11 | TImpDispatchType = 12 | ( 13 | Jump, // FF25.. 14 | Call // FF15.. 15 | ); 16 | 17 | // List of addresses referencing same function. 18 | TImp = record 19 | dispType: TImpDispatchType; 20 | srcVA: uint64; 21 | iatIndex: uint32; 22 | end; 23 | 24 | TImpList = TList; 25 | 26 | TFuncName = string; 27 | TFuncPair = TPair; 28 | TFuncs = TObjectDictionary; 29 | 30 | TLibName = string; 31 | TLibPair = TPair; 32 | TLibs = TObjectDictionary; 33 | 34 | procedure ParseImportsFromRefFile(const RefFileName: string; Libs: TLibs); 35 | 36 | implementation 37 | 38 | const 39 | HexDigits = ['0' .. '9', 'a' .. 'f', 'A' .. 'F']; 40 | 41 | function IsHex(const s: string): boolean; 42 | var 43 | c: char; 44 | begin 45 | if s.IsEmpty then 46 | exit(false); 47 | for c in s do 48 | if not(CharInSet(c, HexDigits)) then 49 | exit(false); 50 | exit(true); 51 | end; 52 | 53 | procedure SplitWords(const line: string; words: TStringList); 54 | var 55 | word: string; 56 | p: pchar; 57 | i, w0: integer; 58 | begin 59 | words.Clear; 60 | if line.IsEmpty then 61 | exit; 62 | p := pchar(line); 63 | i := 0; 64 | w0 := -1; 65 | while i <= length(line) do 66 | begin 67 | if (p[0] = #0) or ((p[0] = ' ') and (p[1] = ' ')) then 68 | begin 69 | if w0 <> -1 then 70 | begin 71 | word := line.Substring(w0, i - w0); 72 | words.Add(word); 73 | w0 := -1; 74 | end; 75 | end 76 | else if (p[0] <> ' ') then 77 | begin 78 | if w0 = -1 then 79 | w0 := i; 80 | end; 81 | 82 | inc(p); 83 | inc(i); 84 | end; 85 | end; 86 | 87 | procedure AddFunc(va: uint64; const lib, func: string; Libs: TLibs; call: boolean); 88 | var 89 | libName: string; 90 | funcs: TFuncs; 91 | list: TImpList; 92 | rec: TImp; 93 | begin 94 | libName := lib.ToLower; 95 | if not Libs.TryGetValue(libName, funcs) then 96 | begin 97 | funcs := TFuncs.Create([doOwnsValues]); 98 | Libs.Add(libName, funcs); 99 | end; 100 | 101 | if not funcs.TryGetValue(func, list) then 102 | begin 103 | list := TImpList.Create; 104 | funcs.Add(func, list); 105 | end; 106 | 107 | if (call) then 108 | rec.dispType := TImpDispatchType.Call 109 | else 110 | rec.dispType := TImpDispatchType.Jump; 111 | 112 | rec.srcVA := va; 113 | rec.iatIndex := 0; 114 | 115 | list.Add(rec); 116 | end; 117 | 118 | function IsStatusDoneOrProcessing(txt: string): boolean; inline; 119 | begin 120 | txt := txt.ToUpper(); 121 | result := txt.Equals('DONE') or 122 | txt.Equals('PROCESSING'); 123 | end; 124 | 125 | procedure ParseImportsFromRefFile(const RefFileName: string; Libs: TLibs); 126 | var 127 | sl: TStringList; 128 | i: integer; 129 | words: TStringList; 130 | libFunc: TArray; 131 | va: uint64; 132 | isCall: boolean; 133 | begin 134 | sl := TStringList.Create; 135 | words := TStringList.Create; 136 | try 137 | sl.LoadFromFile(RefFileName); 138 | for i := 0 to sl.Count - 1 do 139 | begin 140 | SplitWords(sl[i], words); 141 | if (words.Count = 4) and IsStatusDoneOrProcessing(words[3]) and IsHex(words[0]) then 142 | begin 143 | libFunc := words[2].Split(['.']); 144 | if (length(libFunc) = 2) then 145 | begin 146 | isCall := words[1].ToUpper().StartsWith('CALL'); 147 | va := uint64(StrToInt64('$' + words[0])); 148 | AddFunc(va, libFunc[0], libFunc[1], Libs, isCall); 149 | end; 150 | end; 151 | end; 152 | finally 153 | sl.Free; 154 | words.Free; 155 | end; 156 | end; 157 | 158 | end. 159 | --------------------------------------------------------------------------------