240 lines
6.7 KiB
Lua
240 lines
6.7 KiB
Lua
-- Stuff used by dfusion
|
|
local _ENV = mkmodule('plugins.dfusion')
|
|
|
|
local ms=require("memscan")
|
|
|
|
local marker={0xDE,0xAD,0xBE,0xEF}
|
|
--utility functions
|
|
function dwordToTable(dword)
|
|
local b={bit32.extract(dword,0,8),bit32.extract(dword,8,8),bit32.extract(dword,16,8),bit32.extract(dword,24,8)}
|
|
return b
|
|
end
|
|
function concatTables(t1,t2)
|
|
for k,v in pairs(t2) do
|
|
table.insert(t1,v)
|
|
end
|
|
end
|
|
function makeCall(from,to)
|
|
local ret={0xe8}
|
|
concatTables(ret,dwordToTable(to-from-5))
|
|
return ret
|
|
end
|
|
-- A reversable binary patch
|
|
patches={}
|
|
BinaryPatch=defclass(BinaryPatch)
|
|
BinaryPatch.ATTRS {pre_data=DEFAULT_NIL,data=DEFAULT_NIL,address=DEFAULT_NIL,name=DEFAULT_NIL}
|
|
function BinaryPatch:init(args)
|
|
self.is_applied=false
|
|
if args.pre_data==nil or args.data==nil or args.address==nil or args.name==nil then
|
|
error("Invalid parameters to binary patch")
|
|
end
|
|
if patches[self.name]~=nil then
|
|
error("Patch already exist")
|
|
end
|
|
|
|
for k,v in ipairs(args.data) do
|
|
if args.pre_data[k]==nil then
|
|
error("can't edit without revert data")
|
|
end
|
|
end
|
|
end
|
|
function BinaryPatch:postinit(args)
|
|
patches[args.name]=self
|
|
end
|
|
function BinaryPatch:test()
|
|
local arr=ms.CheckedArray.new('uint8_t',self.address,self.address+#self.pre_data)
|
|
for k,v in ipairs(self.pre_data) do
|
|
if arr[k-1]~=v then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
function BinaryPatch:apply()
|
|
if not self:test() then
|
|
error(string.format("pre-data for binary patch does not match expected"))
|
|
end
|
|
|
|
local post_buf=df.new('uint8_t',#self.pre_data)
|
|
for k,v in ipairs(self.pre_data) do
|
|
if self.data[k]==nil then
|
|
post_buf[k-1]=v
|
|
else
|
|
post_buf[k-1]=self.data[k]
|
|
end
|
|
end
|
|
local ret=dfhack.with_finalize(function() post_buf:delete() end,dfhack.internal.patchMemory,self.address,post_buf,#self.pre_data)
|
|
if not ret then
|
|
error("Patch application failed!")
|
|
end
|
|
self.is_applied=true
|
|
end
|
|
function BinaryPatch:repatch(newdata)
|
|
if newdata==nil then newdata=self.data end
|
|
self:remove()
|
|
self.data=newdata
|
|
self:apply()
|
|
end
|
|
function BinaryPatch:remove(delete)
|
|
if delete==nil then
|
|
delete=true
|
|
end
|
|
if not self.is_applied then
|
|
error("can't remove BinaryPatch, not applied.")
|
|
end
|
|
local arr=ms.CheckedArray.new('uint8_t',self.address,self.address+#self.pre_data)
|
|
|
|
local post_buf=df.new('uint8_t',#self.pre_data)
|
|
for k,v in pairs(self.pre_data) do
|
|
post_buf[k-1]=v
|
|
end
|
|
local ret=dfhack.with_finalize(function() post_buf:delete() end,dfhack.internal.patchMemory,self.address,post_buf,#self.pre_data)
|
|
if not ret then
|
|
error("Patch remove failed!")
|
|
end
|
|
self.is_applied=false
|
|
if delete then
|
|
patches[self.name]=nil
|
|
end
|
|
|
|
end
|
|
|
|
function BinaryPatch:__gc()
|
|
if self.is_applied then
|
|
self:remove()
|
|
end
|
|
end
|
|
-- A binary hack (obj file) loader/manager
|
|
-- has to have: a way to get offsets for marked areas (for post load modification) or some way to do that pre-load
|
|
-- page managing (including excecute/write flags for DEP and the like)
|
|
-- TODO plugin state enum, a way to modify post install (could include repatching code...)
|
|
plugins=plugins or {}
|
|
BinaryPlugin=defclass(BinaryPlugin)
|
|
BinaryPlugin.ATTRS {filename=DEFAULT_NIL,reloc_table={},name=DEFAULT_NIL}
|
|
function BinaryPlugin:init(args)
|
|
|
|
end
|
|
function BinaryPlugin:postinit(args)
|
|
if self.name==nil then error("Not a valid plugin name!") end
|
|
if plugins[args.name]==nil then
|
|
plugins[self.name]=self
|
|
else
|
|
error("Trying to create a same plugin")
|
|
end
|
|
self.allocated_object={}
|
|
self:load()
|
|
end
|
|
function BinaryPlugin:get_or_alloc(name,typename,arrsize)
|
|
if self.allocated_object[name]~=nil then
|
|
return self.allocated_object[name]
|
|
else
|
|
return self:allocate(name,typename,arrsize)
|
|
end
|
|
end
|
|
function BinaryPlugin:allocate(name,typename,arrsize)
|
|
local trg
|
|
if df[typename]==nil then
|
|
trg=df.new(typename,arrsize)
|
|
self.allocated_object[name]=trg
|
|
else
|
|
trg=df[typename]:new(arrsize)
|
|
self.allocated_object[name]=trg
|
|
end
|
|
return trg
|
|
end
|
|
function BinaryPlugin:load()
|
|
local obj=loadObjectFile(self.filename)
|
|
self.data=df.reinterpret_cast("uint8_t",obj.data)
|
|
self.size=obj.data_size
|
|
for _,v in pairs(obj.symbols) do
|
|
if string.sub(v.name,1,5)=="mark_" then
|
|
local new_pos=self:find_marker(v.pos)
|
|
self.reloc_table[string.sub(v.name,6)]=new_pos
|
|
end
|
|
end
|
|
end
|
|
function BinaryPlugin:find_marker(start_pos)
|
|
local matched=0
|
|
for i=start_pos,self.size do
|
|
if self.data[i]==marker[4-matched] then
|
|
matched=matched+1
|
|
if matched == 4 then
|
|
return i-4
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function BinaryPlugin:set_marker_dword(marker,dword) -- i hope Toady does not make a 64bit version...
|
|
if self.reloc_table[marker]==nil then
|
|
error("marker ".. marker.. " not found")
|
|
end
|
|
local b=dwordToTable(dword)
|
|
local off=self.reloc_table[marker]
|
|
for k,v in ipairs(b) do
|
|
self.data[off+k]=b[k]
|
|
end
|
|
end
|
|
function BinaryPlugin:move_to_df()
|
|
local _,addr=df.sizeof(self.data)
|
|
markAsExecutable(addr)
|
|
end
|
|
function BinaryPlugin:print_data()
|
|
local out=""
|
|
for i=0,self.size do
|
|
out=out..string.format(" %02x",self.data[i])
|
|
if math.modf(i,16)==15 then
|
|
print(out)
|
|
out=""
|
|
end
|
|
end
|
|
print(out)
|
|
end
|
|
function BinaryPlugin:status()
|
|
return "invalid, base class only!"
|
|
end
|
|
function BinaryPlugin:__gc()
|
|
for k,v in pairs(self.allocated_object) do
|
|
df.delete(v)
|
|
end
|
|
if self.unload then
|
|
self:unload()
|
|
end
|
|
self.data:delete()
|
|
end
|
|
-- a Menu for some stuff. Maybe add a posibility of it working as a gui, or a gui adaptor?
|
|
-- Todo add hints, and parse them to make a "smart" choice of parameters to pass
|
|
SimpleMenu=defclass(SimpleMenu)
|
|
SimpleMenu.ATTRS{title=DEFAULT_NIL}
|
|
function SimpleMenu:init(args)
|
|
self.items={}
|
|
end
|
|
function SimpleMenu:add(name,entry,hints)
|
|
table.insert(self.items,{entry,name,hints})
|
|
end
|
|
function SimpleMenu:display()
|
|
print("Select choice (q exits):")
|
|
for p,c in pairs(self.items) do
|
|
print(string.format("%3d).%s",p,c[2]))
|
|
end
|
|
local ans
|
|
repeat
|
|
local r
|
|
r=io.stdin:read()
|
|
if r==nil then return end
|
|
if r=='q' then return end
|
|
ans=tonumber(r)
|
|
|
|
if ans==nil or not(ans<=#self.items and ans>0) then
|
|
print("Invalid choice.")
|
|
end
|
|
|
|
until ans~=nil and (ans<=#self.items and ans>0)
|
|
if type(self.items[ans][1])=="function" then
|
|
self.items[ans][1]()
|
|
else
|
|
self.items[ans][1]:display()
|
|
end
|
|
end
|
|
|
|
return _ENV |