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 | ||
