Skip to content

LUA script, convert DUMPBIN exports to fasmg

Rickey Bowers Jr edited this page Feb 9, 2021 · 4 revisions

Kind of general solution to consume the DUMPBIN /export output.

  • standard names are given their identity symbolic name
  • ordinal exports are prefixed with the DLL ex: "my.dll.Ordinal#"
  • mangled exports are also named like an ordinal export
    (often these need a custom naming scheme)
  • SystemFunction# are given unique symbolic names ex: "my.dll.SystemFunction#"
function file_exists(file)
  local f = io.open(file, "rb")
  if f then f:close() end
  return f ~= nil
end

if not file_exists(arg[1]) then
  print("usage: lua54.exe " .. arg[0] .. ' "<filename>"')
  print("\tunable to use: " .. arg[1])
  return
end

-- to generate file to process use: dumpbin /EXPORTS My.DLL >My.exports
-- (not compatible with objdump)
out,baseName,ordinal,header = "","",-1,true
for line in io.lines(arg[1]) do
  if header then
    if string.find(line,"Dump of file ")==1 then
      baseName = string.match(line,"[^\\/]+%.[^.]+$")
    elseif string.find(line,"File Type: ")==1 then
      if not string.find(line,"DLL") then return end
    elseif string.find(line," time date stamp")==13 then
      -- TODO: output time date stamp ; file version ; hash ; comment to identify DLL version
    elseif string.find(line,"    ordinal hint RVA      name")==1 then
      -- setup for next phase of table read
      header,skip = false,0
      -- must have filename for DLL
      if string.len(baseName) < 1 then return end
      out = "import '" .. baseName .. "',\\\n"
    end
  else
    if string.len(line) > 0 then
      ordinal = 0+string.match(line,"%d+")
      funString = string.match(line,"[%p%w]+",27)

      if funString == "[NONAME]" then
        -- must import with ordinal number
        out = out .. string.format("%s.Ordinal%d,%d,\\\n", baseName, ordinal, ordinal)
      elseif string.find(funString,"SystemFunction")==1 then
	-- try to insure label is unique (many DLLs have SystemFunction's)
	out = out .. string.format("%s.%s,'%s',\\\n", baseName, funString, funString)
      elseif string.match(funString,"^?.*Z$") then
	-- decorated names need a personal touch until a demangler is programmed
	out = out .. string.format("%s.Ordinal%d,'%s',\\\n", baseName, ordinal, funString)
      else
	-- most will default to labels the same as their import string
	out = out .. string.format("%s,'%s',\\\n", funString, funString)
      end
    else
      skip = skip + 1
      if skip > 1 then
        -- TODO: trim final ",\\\n" from out
        io.write(out)
        return
      end
    end
  end
end

One way to use the above script (dumpbin_to_fasmg.lua) is from CMD prompt:

@REM pass in the search path to process or single file
for %%G in (%*) do (
	dumpbin /EXPORTS "%%G" >"%%~nG.exports"
	lua54 dumpbin_to_fasmg.lua "%%~nG.exports" >"%%~nG.fasmg"
	del "%%~nG.exports"
)

Some care should be taken using DLLs directly. There are DLLs that export addresses for things other than functions. For example, COM interface GUIDs. Additionally, some APIs have their own functions for returning different versions of interfaces. We see this in modern graphics APIs.