aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--win32/pe-parser.lua103
1 files changed, 49 insertions, 54 deletions
diff --git a/win32/pe-parser.lua b/win32/pe-parser.lua
index 1aff7107..ae603a59 100644
--- a/win32/pe-parser.lua
+++ b/win32/pe-parser.lua
@@ -5,7 +5,7 @@
5-- case of 64 bit fields (bit/flag fields). Pointer arithmetic is still done numerically, so for 5-- case of 64 bit fields (bit/flag fields). Pointer arithmetic is still done numerically, so for
6-- very large files this could lead to undefined results. Use with care! 6-- very large files this could lead to undefined results. Use with care!
7-- 7--
8-- Version 0.4, [copyright (c) 2013-2015 Thijs Schreijer](http://www.thijsschreijer.nl) 8-- Version 0.5, [copyright (c) 2013-2018 Thijs Schreijer](http://www.thijsschreijer.nl)
9-- @name pe-parser 9-- @name pe-parser
10-- @class module 10-- @class module
11 11
@@ -16,7 +16,7 @@ local M = {}
16-- For flag fields the name is extended with `_flags`. 16-- For flag fields the name is extended with `_flags`.
17-- @usage -- lookup descriptive name for the myobj.Magic value 17-- @usage -- lookup descriptive name for the myobj.Magic value
18-- local desc = pe.const.Magic(myobj.Magic) 18-- local desc = pe.const.Magic(myobj.Magic)
19-- 19--
20-- -- get list of flag names, indexed by flag values, for the Characteristics field 20-- -- get list of flag names, indexed by flag values, for the Characteristics field
21-- local flag_list = pe.const.Characteristics_flags 21-- local flag_list = pe.const.Characteristics_flags
22M.const = { 22M.const = {
@@ -100,7 +100,7 @@ M.const = {
100 ["800"] = "IMAGE_SCN_LNK_REMOVE", 100 ["800"] = "IMAGE_SCN_LNK_REMOVE",
101 ["1000"] = "IMAGE_SCN_LNK_COMDAT", 101 ["1000"] = "IMAGE_SCN_LNK_COMDAT",
102 ["8000"] = "IMAGE_SCN_GPREL", 102 ["8000"] = "IMAGE_SCN_GPREL",
103 ["20000"] = "IMAGE_SCN_MEM_PURGEABLE", 103 ["10000"] = "IMAGE_SCN_MEM_PURGEABLE",
104 ["20000"] = "IMAGE_SCN_MEM_16BIT", 104 ["20000"] = "IMAGE_SCN_MEM_16BIT",
105 ["40000"] = "IMAGE_SCN_MEM_LOCKED", 105 ["40000"] = "IMAGE_SCN_MEM_LOCKED",
106 ["80000"] = "IMAGE_SCN_MEM_PRELOAD", 106 ["80000"] = "IMAGE_SCN_MEM_PRELOAD",
@@ -128,7 +128,7 @@ M.const = {
128 ["80000000"] = "IMAGE_SCN_MEM_WRITE", 128 ["80000000"] = "IMAGE_SCN_MEM_WRITE",
129 }, 129 },
130 }, 130 },
131 131
132} 132}
133 133
134 134
@@ -143,7 +143,7 @@ function M.toHex(IN, len)
143 IN,D=math.floor(IN/B),math.fmod(IN,B)+1 143 IN,D=math.floor(IN/B),math.fmod(IN,B)+1
144 OUT=string.sub(K,D,D)..OUT 144 OUT=string.sub(K,D,D)..OUT
145 end 145 end
146 len = len or string.len(OUT) 146 len = len or #OUT
147 if len<1 then len = 1 end 147 if len<1 then len = 1 end
148 return (string.rep("0",len) .. OUT):sub(-len,-1) 148 return (string.rep("0",len) .. OUT):sub(-len,-1)
149end 149end
@@ -193,8 +193,8 @@ local function get_list(list, f, add_to)
193 local r = add_to or {} 193 local r = add_to or {}
194 for i, t in ipairs(list) do 194 for i, t in ipairs(list) do
195 assert(r[t.name] == nil, "Value for '"..t.name.."' already set") 195 assert(r[t.name] == nil, "Value for '"..t.name.."' already set")
196 local val,err = f:read(t.size) -- read specified size in bytes 196 local val = f:read(t.size) -- read specified size in bytes
197 val = val or "\0" 197 val = val or "\0"
198 if t.is_str then -- entry is marked as a string value, read as such 198 if t.is_str then -- entry is marked as a string value, read as such
199 for i = 1, #val do 199 for i = 1, #val do
200 if val:sub(i,i) == "\0" then 200 if val:sub(i,i) == "\0" then
@@ -248,25 +248,25 @@ end
248-- local obj = pe.parse("c:\lua\lua.exe") 248-- local obj = pe.parse("c:\lua\lua.exe")
249-- obj:dump() 249-- obj:dump()
250M.parse = function(target) 250M.parse = function(target)
251 251
252 local list = { -- list of known architectures 252 -- local list = { -- list of known architectures
253 [332] = "x86", -- IMAGE_FILE_MACHINE_I386 253 -- [332] = "x86", -- IMAGE_FILE_MACHINE_I386
254 [512] = "x86_64", -- IMAGE_FILE_MACHINE_IA64 254 -- [512] = "x86_64", -- IMAGE_FILE_MACHINE_IA64
255 [34404] = "x86_64", -- IMAGE_FILE_MACHINE_AMD64 255 -- [34404] = "x86_64", -- IMAGE_FILE_MACHINE_AMD64
256 } 256 -- }
257 257
258 local f, err = io.open(target, "rb") 258 local f, err = io.open(target, "rb")
259 if not f then return nil, err end 259 if not f then return nil, err end
260 260
261 local MZ = f:read(2) 261 local MZ = f:read(2)
262 if MZ ~= "MZ" then 262 if MZ ~= "MZ" then
263 f:close() 263 f:close()
264 return nil, "Not a valid image" 264 return nil, "Not a valid image"
265 end 265 end
266 266
267 f:seek("set", 60) -- position of PE header position 267 f:seek("set", 60) -- position of PE header position
268 local peoffset = get_int(f:read(4)) -- read position of PE header 268 local peoffset = get_int(f:read(4)) -- read position of PE header
269 269
270 f:seek("set", peoffset) -- move to position of PE header 270 f:seek("set", peoffset) -- move to position of PE header
271 local out = get_list({ 271 local out = get_list({
272 { size = 4, 272 { size = 4,
@@ -287,14 +287,14 @@ M.parse = function(target)
287 { size = 2, 287 { size = 2,
288 name = "Characteristics"}, 288 name = "Characteristics"},
289 }, f) 289 }, f)
290 290
291 if out.PEheader ~= "PE" then 291 if out.PEheader ~= "PE" then
292 f:close() 292 f:close()
293 return nil, "Invalid PE header" 293 return nil, "Invalid PE header"
294 end 294 end
295 out.PEheader = nil -- remove it, has no value 295 out.PEheader = nil -- remove it, has no value
296 out.dump = M.dump -- export dump function as a method 296 out.dump = M.dump -- export dump function as a method
297 297
298 if M.toDec(out.SizeOfOptionalHeader) > 0 then 298 if M.toDec(out.SizeOfOptionalHeader) > 0 then
299 -- parse optional header; standard 299 -- parse optional header; standard
300 get_list({ 300 get_list({
@@ -388,7 +388,7 @@ M.parse = function(target)
388 if out.DataDirectory[name] then out.DataDirectory[name].name = name end 388 if out.DataDirectory[name] then out.DataDirectory[name].name = name end
389 end 389 end
390 end 390 end
391 391
392 -- parse section table 392 -- parse section table
393 for i = 1, M.toDec(out.NumberOfSections) do 393 for i = 1, M.toDec(out.NumberOfSections) do
394 out.Sections = out.Sections or {} 394 out.Sections = out.Sections or {}
@@ -416,9 +416,9 @@ M.parse = function(target)
416 name = "Characteristics"}, 416 name = "Characteristics"},
417 }, f) 417 }, f)
418 end 418 end
419 -- we now have section data, so add RVA conversion method 419 -- we now have section data, so add RVA convertion method
420 out.get_fileoffset = M.get_fileoffset 420 out.get_fileoffset = M.get_fileoffset
421 421
422 -- get the import table 422 -- get the import table
423 f:seek("set", out:get_fileoffset(out.DataDirectory.ImportTable.VirtualAddress)) 423 f:seek("set", out:get_fileoffset(out.DataDirectory.ImportTable.VirtualAddress))
424 local done = false 424 local done = false
@@ -450,7 +450,7 @@ M.parse = function(target)
450 f:seek("set", out:get_fileoffset(dll.NameRVA)) 450 f:seek("set", out:get_fileoffset(dll.NameRVA))
451 dll.Name = readstring(f) 451 dll.Name = readstring(f)
452 end 452 end
453 453
454 f:close() 454 f:close()
455 return out 455 return out
456end 456end
@@ -467,10 +467,10 @@ end
467M.dump = function(obj) 467M.dump = function(obj)
468 local l = 0 468 local l = 0
469 for k,v in pairs(obj) do if #k > l then l = #k end end 469 for k,v in pairs(obj) do if #k > l then l = #k end end
470 470
471 for k,v in pairs(obj) do 471 for k,v in pairs(obj) do
472 if (M.const[k] and type(v)=="string") then 472 if (M.const[k] and type(v)=="string") then
473 -- look up named value 473 -- look up named value
474 print(k..string.rep(" ", l - #k + 1)..": "..M.const[k][v]) 474 print(k..string.rep(" ", l - #k + 1)..": "..M.const[k][v])
475 elseif M.const[k.."_flags"] then 475 elseif M.const[k.."_flags"] then
476 -- flags should be listed 476 -- flags should be listed
@@ -486,14 +486,14 @@ M.dump = function(obj)
486 end 486 end
487 end 487 end
488 end 488 end
489 489
490 if obj.DataDirectory then 490 if obj.DataDirectory then
491 print("DataDirectory (RVA, size):") 491 print("DataDirectory (RVA, size):")
492 for i, v in ipairs(obj.DataDirectory) do 492 for i, v in ipairs(obj.DataDirectory) do
493 print(" Entry "..M.toHex(i-1).." "..pad(v.VirtualAddress,8,"0").." "..pad(v.Size,8,"0").." "..v.name) 493 print(" Entry "..M.toHex(i-1).." "..pad(v.VirtualAddress,8,"0").." "..pad(v.Size,8,"0").." "..v.name)
494 end 494 end
495 end 495 end
496 496
497 if obj.Sections then 497 if obj.Sections then
498 print("Sections:") 498 print("Sections:")
499 print("idx name RVA VSize Offset RawSize") 499 print("idx name RVA VSize Offset RawSize")
@@ -501,7 +501,7 @@ M.dump = function(obj)
501 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")) 501 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"))
502 end 502 end
503 end 503 end
504 504
505 print("Imports:") 505 print("Imports:")
506 for i, dll in ipairs(obj.DataDirectory.ImportTable) do 506 for i, dll in ipairs(obj.DataDirectory.ImportTable) do
507 print(" "..dll.Name) 507 print(" "..dll.Name)
@@ -515,7 +515,7 @@ end
515-- used (it will only look for the dlls in the same directory). 515-- used (it will only look for the dlls in the same directory).
516-- @param infile binary file to check 516-- @param infile binary file to check
517-- @return msvcrt name (uppercase, without extension) + file where the reference was found, or nil + error 517-- @return msvcrt name (uppercase, without extension) + file where the reference was found, or nil + error
518function M.msvcrt(infile) 518function M.msvcrt(infile)
519 local path, file = infile:match("(.+)\\(.+)$") 519 local path, file = infile:match("(.+)\\(.+)$")
520 if not path then 520 if not path then
521 path = "" 521 path = ""
@@ -525,24 +525,34 @@ function M.msvcrt(infile)
525 end 525 end
526 local obj, err = M.parse(path..file) 526 local obj, err = M.parse(path..file)
527 if not obj then return obj, err end 527 if not obj then return obj, err end
528 528
529 for i, dll in ipairs(obj.DataDirectory.ImportTable) do 529 for i, dll in ipairs(obj.DataDirectory.ImportTable) do
530 dll = dll.Name:upper() 530 dll = dll.Name:upper()
531 local result = dll:match('(MSVCR%d*D?)%.DLL') 531 local result = dll:match('(MSVCR%d*D?)%.DLL')
532 if not result then 532 if not result then
533 result = dll:match('(MSVCRTD?)%.DLL') 533 result = dll:match('(MSVCRTD?)%.DLL')
534 end 534 end
535 if not result then 535 if not result then
536 result = dll:match('(VCRUNTIME%d*D?)%.DLL') 536 result = dll:match('(VCRUNTIME%d*D?)%.DLL')
537 end 537 end
538 if not result then
539 result = dll:match('(UCRTBASED?)%.DLL')
540 end
541 if not result then
542 -- api-ms-win-crt-xxx also indicate the universal runtime
543 result = dll:match('(API%-MS%-WIN%-CRT%-RUNTIME%-)')
544 if result then
545 result = "UCRTBASE"
546 end
547 end
538 -- success, found it return name + binary where it was found 548 -- success, found it return name + binary where it was found
539 if result then return result, infile end 549 if result then return result, infile end
540 end 550 end
541 551
542 -- not found, so traverse all imported dll's 552 -- not found, so traverse all imported dll's
543 for i, dll in ipairs(obj.DataDirectory.ImportTable) do 553 for i, dll in ipairs(obj.DataDirectory.ImportTable) do
544 local rt, ref = M.msvcrt(path..dll.Name) 554 local rt, ref = M.msvcrt(path..dll.Name)
545 if rt then 555 if rt then
546 return rt, ref -- found it 556 return rt, ref -- found it
547 end 557 end
548 end 558 end
@@ -550,19 +560,4 @@ function M.msvcrt(infile)
550 return nil, "No msvcrt found" 560 return nil, "No msvcrt found"
551end 561end
552 562
553function M.get_architecture(program)
554 -- detect processor arch interpreter was compiled for
555 local proc = (M.parse(program) or {}).Machine
556 if not proc then
557 return nil, "Could not detect processor architecture used in "..program
558 end
559 proc = M.const.Machine[proc] -- collect name from constant value
560 if proc == "IMAGE_FILE_MACHINE_I386" then
561 proc = "x86"
562 else
563 proc = "x86_64"
564 end
565 return proc
566end
567
568return M 563return M