--[[
-- ver 0.1 , 2021-08-02-aug
-- update to previous custom jump assembler, previous version should be removed
-- extend jmp!near <address> to
jmp!near <address> <: or ;> <near_cave_aob_1> ( ; <near_cave_aob_n> )*
-- before allocate new trampoline persist memory, the <near_cave_aob> is scanned to try find possible trampoline position,
-- The aob pattern is a bit different from ce's
-- 1. wildcard must use '?';
-- 2. cc* means 14x cc bytes for short hand;
-- 3. 45|67 here | mean the position offset for trampoline, offset 0 if missing (see examples);
-- example 1
00 00* 55
expand to
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55
(ie. 15 bytes of 00 then follow 55 (eg push rbp), trampoline offset 0)
-- example 2
c3 | 00*
expand to
c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
(ie. c3 (ret) then follow 14 bytes 00 , trampoline offset 1, so trampoline will insert just after c3)
use ':' or ';' for wanting to scan near cave
use ';' to separate different aob patterns
only briefly tested, use in your own risk.
--]]
if not _customJumpCall then
_pidContext = _pidContext or {now=os.clock()+3; reset=os.clock()+60}
local function pf(...) if indebug then print(string.format(...))end end
--- modify to default +X and allow one result
local function AOBScanEx(aob,s,e,bAllResult,reuseMS,p,a,n,pb)
-- pf('scan '..aob)
local p,a,n,s,e = p or '+X*W',a or fsmNotAligned,n or '0',s or 0x0,e or 0xffffffffffffffff
reuseMS = type(reuseMS)=='userdata'and reuseMS.ClassName:lower():find'scan' and reuseMS
local ms, result = reuseMS or pb and createMemScan(pb) or createMemScan()
ms.OnlyOneResult = not bAllResult
if ms.OnlyOneResult then
ms.firstScan(soExactValue,vtByteArray,nil,aob,nil,s,e,p,a,n,true,false,false,false)
ms.waitTillDone()
local r = ms.Result
if type(r)=='number' and r~=0 then result = r end
else
local fl = createFoundList(ms)
ms.firstScan(soExactValue,vtByteArray,nil,aob,nil,s,e,p,a,n,true,false,false,false)
ms.waitTillDone()
fl.initialize()
if fl.getCount() > 0 then
result = {}
for i=1,fl.getCount() do result[i]=tonumber(fl.getAddress(i-1),16) end
end
fl.destroy()
end
if not reuseMS then ms.destroy() end
return result
end
if not getPidContext then
function getPidContext()
local p, pid, now = _pidContext, getOpenedProcessID(), os.clock()
if now > p.reset then
p.reset = now + 60
local ps = getProcessList()
for k,v in pairs(p)do
if type(k)=='number' and (not ps[k] or ps[k]~=v[1]) then p[k] = nil end
end
end
if now > p.now and not readInteger(process)then
return {}
elseif p[pid] and now<p.now and p.pid == pid and p.process==process then
return p[pid][2]
elseif not readInteger(process) then
return {}
end
p.now, p.pid, p.process = now+3, pid, process
if not p[pid] or p[pid][1]~=process then
local px = enumModules()[1]
p[pid] = {process,{PID=pid,PROCESS=process,ExeBase=px.Address, ExeFile=px.PathToFile,ExeSize=getModuleSize(process)}}
end
return p[pid][2]
end
end
getPidContext()
local EMPTY = -1
local function s2aob(s)
-- print('s2aob:',type(s))
return s:gsub('.',function(c)return string.format(' %02X',c:byte())end):sub(2)
end
local function toInt(n)return type(n)=='number'and math.tointeger(n)end
local function makeKey(target,hint)
return string.format('%x->%x',hint,target)
end
local function makeTrampoline(from,target,hint,t)
local diff = from - hint
-- pf('try make: %X %X %X %X',from,target,hint, diff)
if diff>-0x7ffffffb and diff<0x80000005 then
local bs, r = string.pack('I6I8',0x25ff ,target),{}
for c in bs:gmatch'.'do r[1+#r]=c:byte()end
fullAccess(from,64)
local x = writeBytes(from, r)
local rs = readBytes(from,14,true)
-- pf('written %X', x or -1)
if rs and byteTableToString(rs)==bs then
-- pf('ok write: %X %X %X // %s || %s',from,target,hint,s2aob(bs),s2aob(byteTableToString(rs)))
t[makeKey(target,hint)] = {from,target}
return from
else
return nil,'fail write bytes'
-- pf('fail write: %X %X %X',from,target,hint)
end
end
end
function GetTrampoline(target, hint, noNewAlloc, nearCavePatterns)
if not toInt(target)or not toInt(hint) then return nil end
local p = getPidContext()
p.Trampoline = p.Trampoline or {}
local key = makeKey(target,hint)
local t,tcnt, diff = p.Trampoline,0
if t[key] then return t[key][1] end
if not noNewAlloc and nearCavePatterns then
local cs,ms = nearCavePatterns, createMemScan()
-- print('AOB scan --',#cs)
for i=1,#cs do
local aob,ofs = unpack(cs[i])
local found = AOBScanEx(aob,hint-0x7fff0000,hint+0x7fff0000,false,ms)
-- pf('aob found: %02d %02X %X %s ',i,ofs,found or -1,aob)
if found and makeTrampoline(found+ofs,target,hint,t) then
t[found+ofs] = target
-- pf('ok %X', found+ofs)
ms.Destroy()
return found+ofs
else
-- print'fail'
end
end
ms.Destroy()
end
------
for from, to in pairs(t) do
if to==EMPTY and makeTrampoline(from,target,hint,t) then
-- pf('new alloc: %X %X',from,target)
t[from] = target
return from
end
end
-- no previous allocation, make new one
if not noNewAlloc then
local addr = allocateMemory(0x1000,hint)
diff = addr and addr - hint
if not diff or diff<=-0x7ffffffb or diff>=0x7ffff005 then
if addr then deAlloc(addr)end
return nil,'fail allocate trampoline'
end
p.TrmpAllocCnt = not p.TrmpAllocCnt and 1 or p.TrmpAllocCnt + 1
for i=0,255 do t[addr+i*16]=EMPTY end
return GetTrampoline(target, hint, true)
end
end
if _customJumpCall then
_customJumpCall = nil,unregisterAssembler(_customJumpCall)
end
indebug = true
_customJumpCall = registerAssembler(function (addr, inst)
-- pf('enter: %X - %s',addr, inst)
local force, target, nearCave, isJmp, forceShort, forceNear, forceLong =
inst:match'^%s*[jJ][mM][pP]!(%a*)%s+([^:;]+)%s*(.-)%s*$'
if target then isJmp = true else
force, target, nearCave = inst:match'^%s*[cC][aA][lL][lL]!(%a*)%s+([^:;]+)%s*(.-)%s*$'
end
-- pf('fmt: force[%s] target[%s] nearCave[%s]',force or '?', target or '?',nearCave or '?')
if not target then return end
local nearCavePatterns
if nearCave:len()>0 then -- near cave specificed
local nc, cs, i, tag, _ = nearCave:sub(2), {},0
-- pf('nearCave:',nc)
local pat = '^%s*([%x%?][%x%?]?)(%s*|?[|%*]?|?)'
for aob in nc:gmatch'%s*([^;]+)%s*'do
i = i+1
-- print(i,'aob:',aob)
local as,nxt,ofs,tmp,foundOfs = {},0,1,aob
if aob:sub(1,1)=='|' then foundOfs,aob = 0,aob:sub(2)end
while aob:find(pat,nxt+1)do
_, nxt,as[1+#as],tag = aob:find(pat,nxt+1)
-- pf('---->%d : %s, %s, %s, <<%s>>',i,_, nxt,as[#as],tag)
if tag:find'^%s*%*' then
as[#as] = (as[#as]..' '):rep(14)
ofs = ofs + 13
if tag:find'|' then
if foundOfs then foundOfs = true break else foundOfs = ofs end
end
end
if tag:find'^%s*|' then
if foundOfs then foundOfs = true break else foundOfs = ofs end
if tag:find'%*' then foundOfs = false break end
end
ofs = ofs + 1
end
-- print(i,'>'..table.concat(as,' ')..'<','/',tostring(foundOfs),'/',aob)
if foundOfs == true then
-- pf('err: %s', 'multiple "|" jmp ofs on aob #'..i..': '..tmp)
return nil,'multiple "|" jmp ofs on aob #'..i..': '..tmp
elseif foundOfs == false then
-- pf('err: %s', '"|*" pattern not allowed, aob #'..i..': '..tmp)
return nil,'"|*" pattern not allowed, aob #'..i..': '..tmp
end
-- pf('aob%d- %d , %s %s',i,ofs,aob,foundOfs)
ofs = foundOfs or 0
if #as>0 then
cs[1+#cs] = {table.concat(as,' '):gsub('%s+',' '),ofs}
end
-- print('-->',unpack(cs[#cs]))
end
nearCavePatterns = cs
end
target = target and target:len()>1 and GetAddressSafe(target)
if target and force:len()>1 then
force=force:lower()
if force=='short' then forceShort = true-- should be redundancy?
elseif force=='near' then forceNear = true
elseif force=='long' then forceLong = true
else target = nil end -- unknown jump distance modifier, error
end
if not target then
return --nil,'invalid :'..inst
else
local cmd = isJmp and {0xeb,0xe9,0x25ff} or {0xe8,0xe8,0x08eb0000000215ff}
local diff, r, bs = target - addr, {}
if isJmp and (forceShort or not forceNear and not forceLong) and diff>-0x7e and diff <0x82 then
bs = string.pack('Bb',cmd[1], diff-2)
elseif not targetIs64Bit() or not forceLong and diff>-0x7ffffffb and diff<0x80000005 then
bs = string.pack('Bi4',cmd[2],diff-5)
elseif not forceNear or addr<0x1000 then
if isJmp then
bs = string.pack('I6I8',cmd[3],target)
else
bs = string.pack('I8I8',cmd[3],target)
end
elseif diff<=-0x7ffffffb or diff>=0x80000005 then -- force near and long jmp trampoline need
local trmp, errmsg = GetTrampoline(target, addr, nil,nearCavePatterns)
if not trmp then return nil,(errmsg or '!')..', no trampoline:'..inst end
bs = string.pack('Bi4',cmd[2], trmp - addr -5)
end
if bs then
for c in bs:gmatch'.' do r[1+#r]=c:byte()end
return r
end
end
end)
end --- cutsomJumpCall