diff options
author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2013-10-11 23:54:51 +0200 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2013-10-11 23:54:51 +0200 |
commit | 23214ad83c36ca2d6f74e1532bcd7f1acd87db82 (patch) | |
tree | 9e0c40d1f9b5776de5f053e5f22b988f86606c0e | |
parent | 68878f0fdde4749be50eeb141631eafa5b7aa8e3 (diff) | |
download | luarocks-23214ad83c36ca2d6f74e1532bcd7f1acd87db82.tar.gz luarocks-23214ad83c36ca2d6f74e1532bcd7f1acd87db82.tar.bz2 luarocks-23214ad83c36ca2d6f74e1532bcd7f1acd87db82.zip |
included pe-parser module (no longer objdump.exe, which is removed), updated installer to check architecture (32/64) interpreter was compiled for (instead of system architecture). Runtime detection should now also properly work on 64bit binaries.
-rw-r--r-- | install.bat | 106 | ||||
-rw-r--r-- | win32/bin/bin/objdump.exe | bin | 2247694 -> 0 bytes | |||
-rw-r--r-- | win32/bin/pe-parser.lua | 546 |
3 files changed, 576 insertions, 76 deletions
diff --git a/install.bat b/install.bat index 0f34276d..f6601aa8 100644 --- a/install.bat +++ b/install.bat | |||
@@ -31,6 +31,9 @@ local REGISTRY = false | |||
31 | --- | 31 | --- |
32 | -- Some helpers | 32 | -- Some helpers |
33 | -- | 33 | -- |
34 | |||
35 | local pe = assert(loadfile(".\\bin\\pe-parser.lua"))() | ||
36 | |||
34 | local function die(message) | 37 | local function die(message) |
35 | if message then print(message) end | 38 | if message then print(message) end |
36 | print() | 39 | print() |
@@ -275,71 +278,35 @@ local function look_for_headers (directory) | |||
275 | return false | 278 | return false |
276 | end | 279 | end |
277 | 280 | ||
278 | -- Checks a binary file for the runtime dll used by it. If nu runtime is found, it returns an | ||
279 | -- array of dll's is depends upon. | ||
280 | -- result: string = runtime used, table = list of dll's depended upon, nil = nothing found. | ||
281 | local function get_file_runtime(p,f) -- path, filename | ||
282 | local infile = p.."\\"..f | ||
283 | local outfile = "output.txt" | ||
284 | local content | ||
285 | -- analyze binary | ||
286 | if exec([[.\bin\bin\objdump -x "]]..infile..[[" > ]]..outfile..[[ 2<&1]]) then | ||
287 | -- read temp file | ||
288 | local fh = io.open(outfile) | ||
289 | content = fh:read("*a") | ||
290 | fh:close() | ||
291 | end | ||
292 | -- delete temp file | ||
293 | os.remove(outfile) | ||
294 | if not content then | ||
295 | print(" Failed to analyze "..infile.." for the runtime used") | ||
296 | return nil | ||
297 | end | ||
298 | |||
299 | -- lookup | ||
300 | content = content:upper() | ||
301 | local result = content:match('DLL NAME%: (MSVCR%d*)%.DLL') | ||
302 | if not result then | ||
303 | result = content:match('DLL NAME%: (MSVCRT)%.DLL') | ||
304 | end | ||
305 | |||
306 | if result then | ||
307 | print(" "..f.." uses "..tostring(result)..".DLL as runtime") | ||
308 | else | ||
309 | print(" No runtime found for "..f) | ||
310 | -- so; create a list of dll's this file is depending upon, next level of the tree | ||
311 | result = {} | ||
312 | for name in content:gmatch("DLL NAME%: (.-%.DLL)") do | ||
313 | --print("found dll:", name) | ||
314 | table.insert(result, name) | ||
315 | end | ||
316 | end | ||
317 | return result | ||
318 | end | ||
319 | 281 | ||
320 | local function get_runtime() | 282 | local function get_runtime() |
321 | -- first check interpreter | 283 | local f |
322 | vars.LUA_RUNTIME = get_file_runtime(vars.LUA_BINDIR, vars.LUA_INTERPRETER) | 284 | vars.LUA_RUNTIME, f = pe.msvcrt(vars.LUA_BINDIR.."\\"..vars.LUA_INTERPRETER) |
323 | if type(vars.LUA_RUNTIME) == "table" then | ||
324 | -- a table with dll's depended upon was returned, check this list | ||
325 | -- note: we only check 1 level deep | ||
326 | for _,dll in ipairs(vars.LUA_RUNTIME) do | ||
327 | local t = get_file_runtime(vars.LUA_BINDIR, dll) | ||
328 | if type(t) == "string" then | ||
329 | -- found it | ||
330 | vars.LUA_RUNTIME = t | ||
331 | break | ||
332 | end | ||
333 | end | ||
334 | end | ||
335 | if type(vars.LUA_RUNTIME) ~= "string" then | 285 | if type(vars.LUA_RUNTIME) ~= "string" then |
336 | -- analysis failed, issue a warning | 286 | -- analysis failed, issue a warning |
337 | vars.LUA_RUNTIME = "MSVCR80" | 287 | vars.LUA_RUNTIME = "MSVCR80" |
338 | print("*** WARNING ***: could not analyse the runtime used, defaulting to "..vars.LUA_RUNTIME) | 288 | print("*** WARNING ***: could not analyse the runtime used, defaulting to "..vars.LUA_RUNTIME) |
289 | else | ||
290 | print(" "..f.." uses "..vars.LUA_RUNTIME..".DLL as runtime") | ||
339 | end | 291 | end |
340 | return true | 292 | return true |
341 | end | 293 | end |
342 | 294 | ||
295 | local function get_architecture() | ||
296 | -- detect processor arch interpreter was compiled for | ||
297 | local proc = (pe.parse(vars.LUA_BINDIR.."\\"..vars.LUA_INTERPRETER) or {}).Machine | ||
298 | if not proc then | ||
299 | die("Could not detect processor architecture used in "..vars.LUA_INTERPRETER) | ||
300 | end | ||
301 | proc = pe.const.Machine[proc] -- collect name from constant value | ||
302 | if proc == "IMAGE_FILE_MACHINE_I386" then | ||
303 | proc = "x86" | ||
304 | else | ||
305 | proc = "x86_64" | ||
306 | end | ||
307 | return proc | ||
308 | end | ||
309 | |||
343 | local function look_for_lua_install () | 310 | local function look_for_lua_install () |
344 | print("Looking for Lua interpreter") | 311 | print("Looking for Lua interpreter") |
345 | local directories = { [[c:\lua5.1.2]], [[c:\lua]], [[c:\kepler\1.1]] } | 312 | local directories = { [[c:\lua5.1.2]], [[c:\lua]], [[c:\kepler\1.1]] } |
@@ -388,26 +355,6 @@ local function look_for_lua_install () | |||
388 | return false | 355 | return false |
389 | end | 356 | end |
390 | 357 | ||
391 | local function get_architecture() | ||
392 | -- detect processor arch | ||
393 | local tmpname = [[.\_architect_temp.txt]] | ||
394 | local cmd = [[REG.exe Query HKLM\Hardware\Description\System\CentralProcessor\0 >"]]..tmpname.. [["]] | ||
395 | if not exec(cmd) then | ||
396 | die("Could not detect processor architecture") | ||
397 | end | ||
398 | local f = io.open(tmpname, "r") | ||
399 | local proc = f:read('*a') | ||
400 | f:close() | ||
401 | os.remove(tmpname) | ||
402 | |||
403 | if proc:match("x86") then | ||
404 | proc = "x86" | ||
405 | else | ||
406 | proc = "x86_64" | ||
407 | end | ||
408 | return proc | ||
409 | end | ||
410 | |||
411 | --- | 358 | --- |
412 | -- Poor man's command-line parsing | 359 | -- Poor man's command-line parsing |
413 | local config = {} | 360 | local config = {} |
@@ -449,7 +396,6 @@ vars.LIBDIR = vars.FULL_PREFIX | |||
449 | vars.LUADIR = S"$FULL_PREFIX\\lua" | 396 | vars.LUADIR = S"$FULL_PREFIX\\lua" |
450 | vars.INCDIR = S"$FULL_PREFIX\\include" | 397 | vars.INCDIR = S"$FULL_PREFIX\\include" |
451 | vars.LUA_SHORTV = vars.LUA_VERSION:gsub("%.", "") | 398 | vars.LUA_SHORTV = vars.LUA_VERSION:gsub("%.", "") |
452 | vars.UNAME_M = get_architecture() | ||
453 | 399 | ||
454 | if not look_for_lua_install() then | 400 | if not look_for_lua_install() then |
455 | print("Could not find Lua. Will install its own copy.") | 401 | print("Could not find Lua. Will install its own copy.") |
@@ -464,7 +410,9 @@ if not look_for_lua_install() then | |||
464 | vars.LUA_INCDIR = vars.INCDIR | 410 | vars.LUA_INCDIR = vars.INCDIR |
465 | vars.LUA_LIBNAME = "lua5.1.lib" | 411 | vars.LUA_LIBNAME = "lua5.1.lib" |
466 | vars.LUA_RUNTIME = "MSVCR80" | 412 | vars.LUA_RUNTIME = "MSVCR80" |
413 | vars.UNAME_M = "x86" | ||
467 | else | 414 | else |
415 | vars.UNAME_M = get_architecture() | ||
468 | print(S[[ | 416 | print(S[[ |
469 | 417 | ||
470 | Will configure LuaRocks with the following paths: | 418 | Will configure LuaRocks with the following paths: |
@@ -668,8 +616,14 @@ if REGISTRY then | |||
668 | exec( S[[lua5.1\bin\lua5.1.exe "$FULL_PREFIX\LuaRocks.reg.lua" "$FULL_PREFIX\LuaRocks.reg.template"]] ) | 616 | exec( S[[lua5.1\bin\lua5.1.exe "$FULL_PREFIX\LuaRocks.reg.lua" "$FULL_PREFIX\LuaRocks.reg.template"]] ) |
669 | exec( S"$FULL_PREFIX\\LuaRocks.reg" ) | 617 | exec( S"$FULL_PREFIX\\LuaRocks.reg" ) |
670 | end | 618 | end |
619 | |||
620 | -- *********************************************************** | ||
621 | -- Cleanup | ||
622 | -- *********************************************************** | ||
671 | -- remove regsitry related files, no longer needed | 623 | -- remove regsitry related files, no longer needed |
672 | exec( S[[del "$FULL_PREFIX\LuaRocks.reg.*" > nul]] ) | 624 | exec( S[[del "$FULL_PREFIX\LuaRocks.reg.*" > nul]] ) |
625 | -- remove pe-parser module | ||
626 | exec( S[[del "$FULL_PREFIX\pe-parser.lua" > nul]] ) | ||
673 | 627 | ||
674 | -- *********************************************************** | 628 | -- *********************************************************** |
675 | -- Exit handlers | 629 | -- Exit handlers |
diff --git a/win32/bin/bin/objdump.exe b/win32/bin/bin/objdump.exe deleted file mode 100644 index 4429d103..00000000 --- a/win32/bin/bin/objdump.exe +++ /dev/null | |||
Binary files differ | |||
diff --git a/win32/bin/pe-parser.lua b/win32/bin/pe-parser.lua new file mode 100644 index 00000000..30bb8390 --- /dev/null +++ b/win32/bin/pe-parser.lua | |||
@@ -0,0 +1,546 @@ | |||
1 | --------------------------------------------------------------------------------------- | ||
2 | -- Lua module to parse a Portable Executable (.exe , .dll, etc.) file and extract metadata. | ||
3 | -- | ||
4 | -- Version 0.1, [copyright (c) 2013 - Thijs Schreijer](http://www.thijsschreijer.nl) | ||
5 | -- @name pe-parser | ||
6 | -- @class module | ||
7 | |||
8 | local M = {} | ||
9 | |||
10 | --- Table with named constants/flag-constants. | ||
11 | -- Named elements can be looked up by their name in the `const` table. The sub tables are index by value. | ||
12 | -- For flag fields the name is extended with `_flags`. | ||
13 | -- @usage -- lookup descriptive name for the myobj.Magic value | ||
14 | -- local desc = pe.const.Magic(myobj.Magic) | ||
15 | -- | ||
16 | -- -- get list of flag names, indexed by flag values, for the Characteristics field | ||
17 | -- local flag_list = pe.const.Characteristics_flags | ||
18 | M.const = { | ||
19 | Magic = { | ||
20 | ["10b"] = "PE32", | ||
21 | ["20b"] = "PE32+", | ||
22 | }, | ||
23 | Machine = { | ||
24 | ["0"] = "IMAGE_FILE_MACHINE_UNKNOWN", | ||
25 | ["1d3"] = "IMAGE_FILE_MACHINE_AM33", | ||
26 | ["8664"] = "IMAGE_FILE_MACHINE_AMD64", | ||
27 | ["1c0"] = "IMAGE_FILE_MACHINE_ARM", | ||
28 | ["1c4"] = "IMAGE_FILE_MACHINE_ARMNT", | ||
29 | ["aa64"] = "IMAGE_FILE_MACHINE_ARM64", | ||
30 | ["ebc"] = "IMAGE_FILE_MACHINE_EBC", | ||
31 | ["14c"] = "IMAGE_FILE_MACHINE_I386", | ||
32 | ["200"] = "IMAGE_FILE_MACHINE_IA64", | ||
33 | ["9041"] = "IMAGE_FILE_MACHINE_M32R", | ||
34 | ["266"] = "IMAGE_FILE_MACHINE_MIPS16", | ||
35 | ["366"] = "IMAGE_FILE_MACHINE_MIPSFPU", | ||
36 | ["466"] = "IMAGE_FILE_MACHINE_MIPSFPU16", | ||
37 | ["1f0"] = "IMAGE_FILE_MACHINE_POWERPC", | ||
38 | ["1f1"] = "IMAGE_FILE_MACHINE_POWERPCFP", | ||
39 | ["166"] = "IMAGE_FILE_MACHINE_R4000", | ||
40 | ["1a2"] = "IMAGE_FILE_MACHINE_SH3", | ||
41 | ["1a3"] = "IMAGE_FILE_MACHINE_SH3DSP", | ||
42 | ["1a6"] = "IMAGE_FILE_MACHINE_SH4", | ||
43 | ["1a8"] = "IMAGE_FILE_MACHINE_SH5", | ||
44 | ["1c2"] = "IMAGE_FILE_MACHINE_THUMB", | ||
45 | ["169"] = "IMAGE_FILE_MACHINE_WCEMIPSV2", | ||
46 | }, | ||
47 | Characteristics_flags = { | ||
48 | ["1"] = "IMAGE_FILE_RELOCS_STRIPPED", | ||
49 | ["2"] = "IMAGE_FILE_EXECUTABLE_IMAGE", | ||
50 | ["4"] = "IMAGE_FILE_LINE_NUMS_STRIPPED", | ||
51 | ["8"] = "IMAGE_FILE_LOCAL_SYMS_STRIPPED", | ||
52 | ["10"] = "IMAGE_FILE_AGGRESSIVE_WS_TRIM", | ||
53 | ["20"] = "IMAGE_FILE_LARGE_ADDRESS_AWARE", | ||
54 | ["40"] = "Reserved for future use", | ||
55 | ["80"] = "IMAGE_FILE_BYTES_REVERSED_LO", | ||
56 | ["100"] = "IMAGE_FILE_32BIT_MACHINE", | ||
57 | ["200"] = "IMAGE_FILE_DEBUG_STRIPPED", | ||
58 | ["400"] = "IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP", | ||
59 | ["800"] = "IMAGE_FILE_NET_RUN_FROM_SWAP", | ||
60 | ["1000"] = "IMAGE_FILE_SYSTEM", | ||
61 | ["2000"] = "IMAGE_FILE_DLL", | ||
62 | ["4000"] = "IMAGE_FILE_UP_SYSTEM_ONLY", | ||
63 | ["8000"] = "IMAGE_FILE_BYTES_REVERSED_HI", | ||
64 | }, | ||
65 | Subsystem = { | ||
66 | ["0"] = "IMAGE_SUBSYSTEM_UNKNOWN", | ||
67 | ["1"] = "IMAGE_SUBSYSTEM_NATIVE", | ||
68 | ["2"] = "IMAGE_SUBSYSTEM_WINDOWS_GUI", | ||
69 | ["3"] = "IMAGE_SUBSYSTEM_WINDOWS_CUI", | ||
70 | ["7"] = "IMAGE_SUBSYSTEM_POSIX_CUI", | ||
71 | ["9"] = "IMAGE_SUBSYSTEM_WINDOWS_CE_GUI", | ||
72 | ["a"] = "IMAGE_SUBSYSTEM_EFI_APPLICATION", | ||
73 | ["b"] = "IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", | ||
74 | ["c"] = "IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER", | ||
75 | ["d"] = "IMAGE_SUBSYSTEM_EFI_ROM", | ||
76 | ["e"] = "IMAGE_SUBSYSTEM_XBOX", | ||
77 | }, | ||
78 | DllCharacteristics_flags = { | ||
79 | ["40"] = "IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE", | ||
80 | ["80"] = "IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY", | ||
81 | ["100"] = "IMAGE_DLL_CHARACTERISTICS_NX_COMPAT", | ||
82 | ["200"] = "IMAGE_DLLCHARACTERISTICS_NO_ISOLATION", | ||
83 | ["400"] = "IMAGE_DLLCHARACTERISTICS_NO_SEH", | ||
84 | ["800"] = "IMAGE_DLLCHARACTERISTICS_NO_BIND", | ||
85 | ["2000"] = "IMAGE_DLLCHARACTERISTICS_WDM_DRIVER", | ||
86 | ["8000"] = "IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE", | ||
87 | }, | ||
88 | Sections = { | ||
89 | Characteristics_flags = { | ||
90 | ["8"] = "IMAGE_SCN_TYPE_NO_PAD", | ||
91 | ["20"] = "IMAGE_SCN_CNT_CODE", | ||
92 | ["40"] = "IMAGE_SCN_CNT_INITIALIZED_DATA", | ||
93 | ["80"] = "IMAGE_SCN_CNT_UNINITIALIZED_ DATA", | ||
94 | ["100"] = "IMAGE_SCN_LNK_OTHER", | ||
95 | ["200"] = "IMAGE_SCN_LNK_INFO", | ||
96 | ["800"] = "IMAGE_SCN_LNK_REMOVE", | ||
97 | ["1000"] = "IMAGE_SCN_LNK_COMDAT", | ||
98 | ["8000"] = "IMAGE_SCN_GPREL", | ||
99 | ["20000"] = "IMAGE_SCN_MEM_PURGEABLE", | ||
100 | ["20000"] = "IMAGE_SCN_MEM_16BIT", | ||
101 | ["40000"] = "IMAGE_SCN_MEM_LOCKED", | ||
102 | ["80000"] = "IMAGE_SCN_MEM_PRELOAD", | ||
103 | ["100000"] = "IMAGE_SCN_ALIGN_1BYTES", | ||
104 | ["200000"] = "IMAGE_SCN_ALIGN_2BYTES", | ||
105 | ["300000"] = "IMAGE_SCN_ALIGN_4BYTES", | ||
106 | ["400000"] = "IMAGE_SCN_ALIGN_8BYTES", | ||
107 | ["500000"] = "IMAGE_SCN_ALIGN_16BYTES", | ||
108 | ["600000"] = "IMAGE_SCN_ALIGN_32BYTES", | ||
109 | ["700000"] = "IMAGE_SCN_ALIGN_64BYTES", | ||
110 | ["800000"] = "IMAGE_SCN_ALIGN_128BYTES", | ||
111 | ["900000"] = "IMAGE_SCN_ALIGN_256BYTES", | ||
112 | ["a00000"] = "IMAGE_SCN_ALIGN_512BYTES", | ||
113 | ["b00000"] = "IMAGE_SCN_ALIGN_1024BYTES", | ||
114 | ["c00000"] = "IMAGE_SCN_ALIGN_2048BYTES", | ||
115 | ["d00000"] = "IMAGE_SCN_ALIGN_4096BYTES", | ||
116 | ["e00000"] = "IMAGE_SCN_ALIGN_8192BYTES", | ||
117 | ["1000000"] = "IMAGE_SCN_LNK_NRELOC_OVFL", | ||
118 | ["2000000"] = "IMAGE_SCN_MEM_DISCARDABLE", | ||
119 | ["4000000"] = "IMAGE_SCN_MEM_NOT_CACHED", | ||
120 | ["8000000"] = "IMAGE_SCN_MEM_NOT_PAGED", | ||
121 | ["10000000"] = "IMAGE_SCN_MEM_SHARED", | ||
122 | ["20000000"] = "IMAGE_SCN_MEM_EXECUTE", | ||
123 | ["40000000"] = "IMAGE_SCN_MEM_READ", | ||
124 | ["80000000"] = "IMAGE_SCN_MEM_WRITE", | ||
125 | }, | ||
126 | }, | ||
127 | |||
128 | } | ||
129 | |||
130 | |||
131 | --- convert integer to HEX representation | ||
132 | -- @param IN the number to convert to hex | ||
133 | -- @param len the size to return, any result smaller will be prefixed by "0"s | ||
134 | -- @return string containing hex representation | ||
135 | function M.toHex(IN, len) | ||
136 | local B,K,OUT,I,D=16,"0123456789abcdef","",0 | ||
137 | while IN>0 do | ||
138 | I=I+1 | ||
139 | IN,D=math.floor(IN/B),math.fmod(IN,B)+1 | ||
140 | OUT=string.sub(K,D,D)..OUT | ||
141 | end | ||
142 | len = len or string.len(OUT) | ||
143 | if len<1 then len = 1 end | ||
144 | return (string.rep("0",len) .. OUT):sub(-len,-1) | ||
145 | end | ||
146 | |||
147 | --- convert HEX to integer | ||
148 | -- @param IN the string to convert to dec | ||
149 | -- @return number in dec format | ||
150 | function M.toDec(IN) | ||
151 | assert(type(IN)=="string") | ||
152 | local OUT = 0 | ||
153 | IN = IN:lower() | ||
154 | while #IN > 0 do | ||
155 | local b = string.find("0123456789abcdef",IN:sub(1,1)) | ||
156 | OUT = OUT * 16 + (b-1) | ||
157 | IN = IN:sub(2,-1) | ||
158 | end | ||
159 | return OUT | ||
160 | end | ||
161 | |||
162 | local function get_int(str) | ||
163 | -- convert a byte-sequence to an integer | ||
164 | assert(str) | ||
165 | local r = 0 | ||
166 | for i = #str, 1, -1 do | ||
167 | r = r*256 + string.byte(str,i,i) | ||
168 | end | ||
169 | return r | ||
170 | end | ||
171 | |||
172 | local function get_hex(str) | ||
173 | -- convert a byte-sequence to a hex string | ||
174 | assert(str) | ||
175 | local r = "" | ||
176 | for i = #str, 1, -1 do | ||
177 | r = r .. M.toHex(string.byte(str,i,i),2) | ||
178 | end | ||
179 | while (#r > 1) and (r:sub(1,1) == "0") do | ||
180 | r = r:sub(2, -1) | ||
181 | end | ||
182 | return r | ||
183 | end | ||
184 | |||
185 | local function get_list(list, f, add_to) | ||
186 | -- list: list of tables with 'size' and 'name' and is_str | ||
187 | -- f: file to read from | ||
188 | -- add_to: table to add results to (optional) | ||
189 | local r = add_to or {} | ||
190 | for i, t in ipairs(list) do | ||
191 | assert(r[t.name] == nil, "Value for '"..t.name.."' already set") | ||
192 | local val,err = f:read(t.size) -- read specified size in bytes | ||
193 | val = val or "\0" | ||
194 | if t.is_str then -- entry is marked as a string value, read as such | ||
195 | for i = 1, #val do | ||
196 | if val:sub(i,i) == "\0" then | ||
197 | r[t.name] = val:sub(1,i-1) | ||
198 | break | ||
199 | end | ||
200 | end | ||
201 | r[t.name] = r[t.name] or val | ||
202 | else -- entry not marked, so always read as hex value | ||
203 | r[t.name] = get_hex(val) | ||
204 | end | ||
205 | end | ||
206 | return r | ||
207 | end | ||
208 | |||
209 | --- Calculates the fileoffset of a given RVA. | ||
210 | -- This function is also available as a method on the parsed output table | ||
211 | -- @param obj a parsed object (return value from `parse`) | ||
212 | -- @param RVA an RVA value to convert to a fileoffset (either number or hex-string) | ||
213 | -- @return fileoffset of the given RVA (number) | ||
214 | M.get_fileoffset = function(obj, RVA) | ||
215 | -- given an object with a section table, and an RVA, it returns | ||
216 | -- the fileoffset for the data | ||
217 | if type(RVA)=="string" then RVA = M.toDec(RVA) end | ||
218 | local section | ||
219 | for i, s in ipairs(obj.Sections) do | ||
220 | if M.toDec(s.VirtualAddress) <= RVA and M.toDec(s.VirtualAddress) + M.toDec(s.VirtualSize) >= RVA then | ||
221 | section = s | ||
222 | break | ||
223 | end | ||
224 | end | ||
225 | if not section then return nil, "No match RVA with Section list, RVA out of bounds" end | ||
226 | return RVA - M.toDec(section.VirtualAddress) + M.toDec(section.PointerToRawData) | ||
227 | end | ||
228 | |||
229 | local function readstring(f) | ||
230 | -- reads a null-terminated string from the current file posistion | ||
231 | local name = "" | ||
232 | while true do | ||
233 | local c = f:read(1) | ||
234 | if c == "\0" then break end | ||
235 | name = name .. c | ||
236 | end | ||
237 | return name | ||
238 | end | ||
239 | |||
240 | --- Parses a file and extracts the information. | ||
241 | -- All numbers are delivered as "string" types containing hex values, see `toHex` and `toDec` conversion functions. | ||
242 | -- @return table with data, or nil + error | ||
243 | -- @usage local pe = require("pe-parser") | ||
244 | -- local obj = pe.parse("c:\lua\lua.exe") | ||
245 | -- obj:dump() | ||
246 | M.parse = function(target) | ||
247 | |||
248 | local list = { -- list of known architectures | ||
249 | [332] = "x86", -- IMAGE_FILE_MACHINE_I386 | ||
250 | [512] = "x86_64", -- IMAGE_FILE_MACHINE_IA64 | ||
251 | [34404] = "x86_64", -- IMAGE_FILE_MACHINE_AMD64 | ||
252 | } | ||
253 | |||
254 | local f, err = io.open(target, "rb") | ||
255 | if not f then return nil, err end | ||
256 | |||
257 | local MZ = f:read(2) | ||
258 | if MZ ~= "MZ" then | ||
259 | f:close() | ||
260 | return nil, "Not a valid image" | ||
261 | end | ||
262 | |||
263 | f:seek("set", 60) -- position of PE header position | ||
264 | local peoffset = get_int(f:read(4)) -- read position of PE header | ||
265 | |||
266 | f:seek("set", peoffset) -- move to position of PE header | ||
267 | local out = get_list({ | ||
268 | { size = 4, | ||
269 | name = "PEheader", | ||
270 | is_str = true }, | ||
271 | { size = 2, | ||
272 | name = "Machine" }, | ||
273 | { size = 2, | ||
274 | name = "NumberOfSections"}, | ||
275 | { size = 4, | ||
276 | name = "TimeDateStamp" }, | ||
277 | { size = 4, | ||
278 | name = "PointerToSymbolTable"}, | ||
279 | { size = 4, | ||
280 | name = "NumberOfSymbols"}, | ||
281 | { size = 2, | ||
282 | name = "SizeOfOptionalHeader"}, | ||
283 | { size = 2, | ||
284 | name = "Characteristics"}, | ||
285 | }, f) | ||
286 | |||
287 | if out.PEheader ~= "PE" then | ||
288 | f:close() | ||
289 | return nil, "Invalid PE header" | ||
290 | end | ||
291 | out.PEheader = nil -- remove it, has no value | ||
292 | out.dump = M.dump -- export dump function as a method | ||
293 | |||
294 | if M.toDec(out.SizeOfOptionalHeader) > 0 then | ||
295 | -- parse optional header; standard | ||
296 | get_list({ | ||
297 | { size = 2, | ||
298 | name = "Magic" }, | ||
299 | { size = 1, | ||
300 | name = "MajorLinkerVersion"}, | ||
301 | { size = 1, | ||
302 | name = "MinorLinkerVersion"}, | ||
303 | { size = 4, | ||
304 | name = "SizeOfCode"}, | ||
305 | { size = 4, | ||
306 | name = "SizeOfInitializedData"}, | ||
307 | { size = 4, | ||
308 | name = "SizeOfUninitializedData"}, | ||
309 | { size = 4, | ||
310 | name = "AddressOfEntryPoint"}, | ||
311 | { size = 4, | ||
312 | name = "BaseOfCode"}, | ||
313 | }, f, out) | ||
314 | local plus = (out.Magic == "20b") | ||
315 | if not plus then -- plain PE32, not PE32+ | ||
316 | get_list({ | ||
317 | { size = 4, | ||
318 | name = "BaseOfData" }, | ||
319 | }, f, out) | ||
320 | end | ||
321 | -- parse optional header; windows-fields | ||
322 | local plussize = 4 | ||
323 | if plus then plussize = 8 end | ||
324 | get_list({ | ||
325 | { size = plussize, | ||
326 | name = "ImageBase"}, | ||
327 | { size = 4, | ||
328 | name = "SectionAlignment"}, | ||
329 | { size = 4, | ||
330 | name = "FileAlignment"}, | ||
331 | { size = 2, | ||
332 | name = "MajorOperatingSystemVersion"}, | ||
333 | { size = 2, | ||
334 | name = "MinorOperatingSystemVersion"}, | ||
335 | { size = 2, | ||
336 | name = "MajorImageVersion"}, | ||
337 | { size = 2, | ||
338 | name = "MinorImageVersion"}, | ||
339 | { size = 2, | ||
340 | name = "MajorSubsystemVersion"}, | ||
341 | { size = 2, | ||
342 | name = "MinorSubsystemVersion"}, | ||
343 | { size = 4, | ||
344 | name = "Win32VersionValue"}, | ||
345 | { size = 4, | ||
346 | name = "SizeOfImage"}, | ||
347 | { size = 4, | ||
348 | name = "SizeOfHeaders"}, | ||
349 | { size = 4, | ||
350 | name = "CheckSum"}, | ||
351 | { size = 2, | ||
352 | name = "Subsystem"}, | ||
353 | { size = 2, | ||
354 | name = "DllCharacteristics"}, | ||
355 | { size = plussize, | ||
356 | name = "SizeOfStackReserve"}, | ||
357 | { size = plussize, | ||
358 | name = "SizeOfStackCommit"}, | ||
359 | { size = plussize, | ||
360 | name = "SizeOfHeapReserve"}, | ||
361 | { size = plussize, | ||
362 | name = "SizeOfHeapCommit"}, | ||
363 | { size = 4, | ||
364 | name = "LoaderFlags"}, | ||
365 | { size = 4, | ||
366 | name = "NumberOfRvaAndSizes"}, | ||
367 | }, f, out) | ||
368 | -- Read data directory entries | ||
369 | for i = 1, M.toDec(out.NumberOfRvaAndSizes) do | ||
370 | out.DataDirectory = out.DataDirectory or {} | ||
371 | out.DataDirectory[i] = get_list({ | ||
372 | { size = 4, | ||
373 | name = "VirtualAddress"}, | ||
374 | { size = 4, | ||
375 | name = "Size"}, | ||
376 | }, f) | ||
377 | end | ||
378 | for i, name in ipairs{"ExportTable", "ImportTable", "ResourceTable", | ||
379 | "ExceptionTable", "CertificateTable", "BaseRelocationTable", | ||
380 | "Debug", "Architecture", "GlobalPtr", "TLSTable", | ||
381 | "LoadConfigTable", "BoundImport", "IAT", | ||
382 | "DelayImportDescriptor", "CLRRuntimeHeader", "Reserved"} do | ||
383 | out.DataDirectory[name] = out.DataDirectory[i] | ||
384 | if out.DataDirectory[name] then out.DataDirectory[name].name = name end | ||
385 | end | ||
386 | end | ||
387 | |||
388 | -- parse section table | ||
389 | for i = 1, M.toDec(out.NumberOfSections) do | ||
390 | out.Sections = out.Sections or {} | ||
391 | out.Sections[i] = get_list({ | ||
392 | { size = 8, | ||
393 | name = "Name", | ||
394 | is_str = true}, | ||
395 | { size = 4, | ||
396 | name = "VirtualSize"}, | ||
397 | { size = 4, | ||
398 | name = "VirtualAddress"}, | ||
399 | { size = 4, | ||
400 | name = "SizeOfRawData"}, | ||
401 | { size = 4, | ||
402 | name = "PointerToRawData"}, | ||
403 | { size = 4, | ||
404 | name = "PointerToRelocations"}, | ||
405 | { size = 4, | ||
406 | name = "PointerToLinenumbers"}, | ||
407 | { size = 2, | ||
408 | name = "NumberOfRelocations"}, | ||
409 | { size = 2, | ||
410 | name = "NumberOfLinenumbers"}, | ||
411 | { size = 4, | ||
412 | name = "Characteristics"}, | ||
413 | }, f) | ||
414 | end | ||
415 | -- we now have section data, so add RVA convertion method | ||
416 | out.get_fileoffset = M.get_fileoffset | ||
417 | |||
418 | -- get the import table | ||
419 | f:seek("set", out:get_fileoffset(out.DataDirectory.ImportTable.VirtualAddress)) | ||
420 | local done = false | ||
421 | local cnt = 1 | ||
422 | while not done do | ||
423 | local dll = get_list({ | ||
424 | { size = 4, | ||
425 | name = "ImportLookupTableRVA"}, | ||
426 | { size = 4, | ||
427 | name = "TimeDateStamp"}, | ||
428 | { size = 4, | ||
429 | name = "ForwarderChain"}, | ||
430 | { size = 4, | ||
431 | name = "NameRVA"}, | ||
432 | { size = 4, | ||
433 | name = "ImportAddressTableRVA"}, | ||
434 | }, f) | ||
435 | if M.toDec(dll.NameRVA) == 0 then | ||
436 | -- this is the final NULL entry, so we're done | ||
437 | done = true | ||
438 | else | ||
439 | -- store the import entry | ||
440 | out.DataDirectory.ImportTable[cnt] = dll | ||
441 | cnt = cnt + 1 | ||
442 | end | ||
443 | end | ||
444 | -- resolve imported DLL names | ||
445 | for i, dll in ipairs(out.DataDirectory.ImportTable) do | ||
446 | f:seek("set", out:get_fileoffset(dll.NameRVA)) | ||
447 | dll.Name = readstring(f) | ||
448 | end | ||
449 | |||
450 | f:close() | ||
451 | return out | ||
452 | end | ||
453 | |||
454 | -- pad a string (prefix) to a specific length | ||
455 | local function pad(str, l, chr) | ||
456 | chr = chr or " " | ||
457 | l = l or 0 | ||
458 | return string.rep(chr,l-#str)..str | ||
459 | end | ||
460 | |||
461 | --- Dumps the output parsed. | ||
462 | -- This function is also available as a method on the parsed output table | ||
463 | M.dump = function(obj) | ||
464 | local l = 0 | ||
465 | for k,v in pairs(obj) do if #k > l then l = #k end end | ||
466 | |||
467 | for k,v in pairs(obj) do | ||
468 | if (M.const[k] and type(v)=="string") then | ||
469 | -- look up named value | ||
470 | print(k..string.rep(" ", l - #k + 1)..": "..M.const[k][v]) | ||
471 | elseif M.const[k.."_flags"] then | ||
472 | -- flags should be listed | ||
473 | print(k..string.rep(" ", l - #k + 1)..": "..v.." (flag field)") | ||
474 | else | ||
475 | -- regular values | ||
476 | if type(v) == "number" then | ||
477 | print(k..string.rep(" ", l - #k + 1)..": "..v.." (dec)") | ||
478 | else | ||
479 | if (type(v)=="string") and (k ~= "DataDirectory") and (k ~= "Sections") then | ||
480 | print(k..string.rep(" ", l - #k + 1)..": "..v) | ||
481 | end | ||
482 | end | ||
483 | end | ||
484 | end | ||
485 | |||
486 | if obj.DataDirectory then | ||
487 | print("DataDirectory (RVA, size):") | ||
488 | for i, v in ipairs(obj.DataDirectory) do | ||
489 | print(" Entry "..M.toHex(i-1).." "..pad(v.VirtualAddress,8,"0").." "..pad(v.Size,8,"0").." "..v.name) | ||
490 | end | ||
491 | end | ||
492 | |||
493 | if obj.Sections then | ||
494 | print("Sections:") | ||
495 | print("idx name RVA VSize Offset RawSize") | ||
496 | for i, v in ipairs(obj.Sections) do | ||
497 | print(" "..i.." "..v.Name.. string.rep(" ",9-#v.Name)..pad(v.VirtualAddress,8,"0").." "..pad(v.VirtualSize,8,"0").." "..pad(v.PointerToRawData,8,"0").." "..pad(v.SizeOfRawData,8,"0")) | ||
498 | end | ||
499 | end | ||
500 | |||
501 | print("Imports:") | ||
502 | for i, dll in ipairs(obj.DataDirectory.ImportTable) do | ||
503 | print(" "..dll.Name) | ||
504 | end | ||
505 | end | ||
506 | |||
507 | --- Checks the msvcrt dll the binary was linked against. | ||
508 | -- Mixing and matching dlls only works when they all are using the same runtime, if | ||
509 | -- not unexpected errors will probably occur. | ||
510 | -- Checks the binary provided and then traverses all imported dlls to find the msvcrt | ||
511 | -- used (it will only look for the dlls in the same directory). | ||
512 | -- @param infile binary file to check | ||
513 | -- @return msvcrt name (uppercase, without extension) + file where the reference was found, or nil + error | ||
514 | function M.msvcrt(infile) | ||
515 | local path, file = infile:match("(.+)\\(.+)$") | ||
516 | if not path then | ||
517 | path = "" | ||
518 | file = infile | ||
519 | else | ||
520 | path=path .. "\\" | ||
521 | end | ||
522 | local obj, err = M.parse(path..file) | ||
523 | if not obj then return obj, err end | ||
524 | |||
525 | for i, dll in ipairs(obj.DataDirectory.ImportTable) do | ||
526 | dll = dll.Name:upper() | ||
527 | local result = dll:match('(MSVCR%d*)%.DLL') | ||
528 | if not result then | ||
529 | result = dll:match('(MSVCRT)%.DLL') | ||
530 | end | ||
531 | -- success, found it return name + binary where it was found | ||
532 | if result then return result, infile end | ||
533 | end | ||
534 | |||
535 | -- not found, so traverse all imported dll's | ||
536 | for i, dll in ipairs(obj.DataDirectory.ImportTable) do | ||
537 | local rt, ref = M.msvcrt(path..dll.Name) | ||
538 | if rt then | ||
539 | return rt, ref -- found it | ||
540 | end | ||
541 | end | ||
542 | |||
543 | return nil, "No msvcrt found" | ||
544 | end | ||
545 | |||
546 | return M | ||