Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. --[[
  2. -- ver 0.1 , 2021-08-02-aug
  3.  
  4. -- update to previous custom jump assembler, previous version should be removed
  5. -- extend jmp!near <address> to
  6.           jmp!near <address> <: or ;> <near_cave_aob_1> ( ; <near_cave_aob_n> )*
  7. -- before allocate new trampoline persist memory, the <near_cave_aob> is scanned to try find possible trampoline position,
  8.  
  9. -- The aob pattern is a bit different from ce's
  10. -- 1. wildcard must use '?';
  11. -- 2. cc* means 14x cc bytes for short hand;
  12. -- 3. 45|67 here | mean the position offset for trampoline, offset 0 if missing (see examples);
  13.  
  14. -- example 1
  15.      00 00* 55  
  16.    expand to
  17.      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55
  18.   (ie. 15 bytes of 00 then follow 55 (eg push rbp), trampoline offset 0)
  19.  
  20. -- example 2
  21.      c3 | 00*
  22.    expand to
  23.      c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  24.   (ie. c3 (ret) then follow 14 bytes 00 , trampoline offset 1, so trampoline will insert just after c3)
  25.  
  26.   use ':' or ';' for wanting to scan near cave
  27.   use ';' to separate different aob patterns
  28.  
  29.   only briefly tested, use in your own risk.
  30. --]]
  31. if not _customJumpCall then
  32. _pidContext = _pidContext or {now=os.clock()+3; reset=os.clock()+60}
  33.  
  34. local function pf(...) if indebug then print(string.format(...))end end
  35.  
  36. --- modify to default +X and allow one result
  37. local function AOBScanEx(aob,s,e,bAllResult,reuseMS,p,a,n,pb)
  38. --  pf('scan '..aob)
  39.   local p,a,n,s,e = p or '+X*W',a or fsmNotAligned,n or '0',s or 0x0,e or 0xffffffffffffffff
  40.   reuseMS = type(reuseMS)=='userdata'and reuseMS.ClassName:lower():find'scan' and reuseMS
  41.   local ms, result = reuseMS or pb and createMemScan(pb) or createMemScan()
  42.   ms.OnlyOneResult = not bAllResult
  43.   if ms.OnlyOneResult then
  44.     ms.firstScan(soExactValue,vtByteArray,nil,aob,nil,s,e,p,a,n,true,false,false,false)
  45.     ms.waitTillDone()
  46.     local r = ms.Result
  47.     if type(r)=='number' and r~=0 then result = r end
  48.   else
  49.     local fl = createFoundList(ms)
  50.     ms.firstScan(soExactValue,vtByteArray,nil,aob,nil,s,e,p,a,n,true,false,false,false)
  51.     ms.waitTillDone()
  52.     fl.initialize()
  53.     if fl.getCount() > 0 then
  54.       result = {}
  55.       for i=1,fl.getCount() do result[i]=tonumber(fl.getAddress(i-1),16) end
  56.     end
  57.     fl.destroy()
  58.   end
  59.   if not reuseMS then ms.destroy() end
  60.   return result
  61. end
  62.  
  63.  
  64. if not getPidContext then
  65. function getPidContext()
  66.   local p, pid, now = _pidContext, getOpenedProcessID(), os.clock()
  67.   if now > p.reset then
  68.     p.reset = now + 60
  69.     local ps = getProcessList()
  70.     for k,v in pairs(p)do
  71.       if type(k)=='number' and (not ps[k] or ps[k]~=v[1]) then p[k] = nil end
  72.     end
  73.   end
  74.   if now > p.now and not readInteger(process)then
  75.     return {}
  76.   elseif p[pid] and now<p.now and p.pid == pid and p.process==process then
  77.     return p[pid][2]
  78.   elseif not readInteger(process) then
  79.     return {}
  80.   end
  81.   p.now, p.pid, p.process = now+3, pid, process
  82.   if not p[pid] or p[pid][1]~=process then
  83.     local px = enumModules()[1]
  84.     p[pid] = {process,{PID=pid,PROCESS=process,ExeBase=px.Address, ExeFile=px.PathToFile,ExeSize=getModuleSize(process)}}
  85.   end
  86.   return p[pid][2]
  87. end
  88. end
  89. getPidContext()
  90. local EMPTY = -1
  91.  
  92.  
  93. local function s2aob(s)
  94. --  print('s2aob:',type(s))
  95.   return s:gsub('.',function(c)return string.format(' %02X',c:byte())end):sub(2)
  96. end
  97. local function toInt(n)return type(n)=='number'and math.tointeger(n)end
  98. local function makeKey(target,hint)
  99.   return string.format('%x->%x',hint,target)
  100. end
  101. local function makeTrampoline(from,target,hint,t)
  102.   local  diff = from - hint
  103. --   pf('try make: %X %X %X %X',from,target,hint, diff)
  104.   if diff>-0x7ffffffb and diff<0x80000005 then
  105.  
  106.     local bs, r = string.pack('I6I8',0x25ff ,target),{}
  107.     for c in bs:gmatch'.'do r[1+#r]=c:byte()end
  108.     fullAccess(from,64)
  109.     local x = writeBytes(from, r)
  110.     local rs = readBytes(from,14,true)
  111. --    pf('written %X', x or -1)
  112.     if rs and byteTableToString(rs)==bs then
  113.   --    pf('ok write: %X %X %X // %s  || %s',from,target,hint,s2aob(bs),s2aob(byteTableToString(rs)))
  114.       t[makeKey(target,hint)] = {from,target}
  115.       return from
  116.     else
  117.       return nil,'fail write bytes'
  118. --      pf('fail write: %X %X %X',from,target,hint)
  119.     end
  120.   end
  121. end
  122. function GetTrampoline(target, hint, noNewAlloc, nearCavePatterns)
  123.  
  124.   if not toInt(target)or not toInt(hint) then return nil end
  125.   local p = getPidContext()
  126.   p.Trampoline = p.Trampoline or {}
  127.   local key = makeKey(target,hint)
  128.   local t,tcnt, diff = p.Trampoline,0
  129.   if t[key] then return t[key][1]  end
  130.  
  131.   if not noNewAlloc and nearCavePatterns then
  132.     local cs,ms = nearCavePatterns, createMemScan()
  133. --    print('AOB scan --',#cs)
  134.     for i=1,#cs do
  135.       local aob,ofs = unpack(cs[i])
  136.       local found = AOBScanEx(aob,hint-0x7fff0000,hint+0x7fff0000,false,ms)
  137. --      pf('aob found: %02d %02X %X %s ',i,ofs,found or -1,aob)
  138.       if found and makeTrampoline(found+ofs,target,hint,t) then
  139.         t[found+ofs] = target
  140. --        pf('ok %X', found+ofs)
  141.           ms.Destroy()
  142.         return found+ofs
  143.       else
  144. --        print'fail'
  145.       end
  146.     end
  147.     ms.Destroy()
  148.   end
  149. ------
  150.   for from, to in pairs(t) do
  151.     if to==EMPTY and makeTrampoline(from,target,hint,t) then
  152. --      pf('new alloc: %X %X',from,target)
  153.       t[from] = target
  154.       return from
  155.     end
  156.   end
  157.   -- no previous allocation, make new one
  158.   if not noNewAlloc then
  159.     local addr = allocateMemory(0x1000,hint)
  160.     diff = addr and addr - hint
  161.     if not diff or diff<=-0x7ffffffb or diff>=0x7ffff005 then
  162.       if addr then deAlloc(addr)end
  163.       return nil,'fail allocate trampoline'
  164.     end
  165.     p.TrmpAllocCnt = not p.TrmpAllocCnt and 1 or p.TrmpAllocCnt + 1
  166.     for i=0,255 do t[addr+i*16]=EMPTY end
  167.     return GetTrampoline(target, hint, true)
  168.   end
  169. end
  170.  
  171. if _customJumpCall then
  172.   _customJumpCall = nil,unregisterAssembler(_customJumpCall)
  173. end
  174. indebug = true
  175. _customJumpCall = registerAssembler(function (addr, inst)
  176. --  pf('enter: %X - %s',addr, inst)
  177.   local force, target, nearCave, isJmp, forceShort, forceNear, forceLong =
  178.     inst:match'^%s*[jJ][mM][pP]!(%a*)%s+([^:;]+)%s*(.-)%s*$'
  179.   if target then isJmp = true else
  180.     force, target, nearCave = inst:match'^%s*[cC][aA][lL][lL]!(%a*)%s+([^:;]+)%s*(.-)%s*$'
  181.   end
  182. --  pf('fmt: force[%s] target[%s] nearCave[%s]',force or '?', target or '?',nearCave or '?')
  183.  
  184.   if not target then return end
  185.  
  186.   local nearCavePatterns
  187.   if nearCave:len()>0 then -- near cave specificed
  188.     local nc, cs, i, tag, _ = nearCave:sub(2), {},0
  189. --    pf('nearCave:',nc)
  190.  
  191.  
  192.     local pat = '^%s*([%x%?][%x%?]?)(%s*|?[|%*]?|?)'
  193.     for aob in nc:gmatch'%s*([^;]+)%s*'do
  194.       i = i+1
  195. --      print(i,'aob:',aob)
  196.       local as,nxt,ofs,tmp,foundOfs = {},0,1,aob
  197.       if aob:sub(1,1)=='|' then foundOfs,aob = 0,aob:sub(2)end
  198.       while aob:find(pat,nxt+1)do
  199.         _, nxt,as[1+#as],tag = aob:find(pat,nxt+1)
  200. --        pf('---->%d : %s, %s, %s, <<%s>>',i,_, nxt,as[#as],tag)
  201.         if tag:find'^%s*%*' then
  202.           as[#as] = (as[#as]..' '):rep(14)
  203.           ofs = ofs + 13
  204.           if tag:find'|' then
  205.             if foundOfs then foundOfs = true break else foundOfs = ofs end
  206.           end
  207.         end
  208.         if tag:find'^%s*|' then
  209.           if foundOfs then foundOfs = true break else foundOfs = ofs end
  210.           if tag:find'%*' then foundOfs = false break end
  211.         end
  212.         ofs = ofs + 1
  213.       end
  214. --      print(i,'>'..table.concat(as,' ')..'<','/',tostring(foundOfs),'/',aob)
  215.  
  216.       if foundOfs == true then
  217. --        pf('err: %s', 'multiple "|" jmp ofs on aob #'..i..': '..tmp)
  218.         return nil,'multiple "|" jmp ofs on aob #'..i..': '..tmp
  219.       elseif foundOfs == false then
  220. --        pf('err: %s', '"|*" pattern not allowed, aob #'..i..': '..tmp)
  221.         return nil,'"|*" pattern not allowed, aob #'..i..': '..tmp
  222.       end
  223. --      pf('aob%d- %d , %s %s',i,ofs,aob,foundOfs)
  224.  
  225.       ofs = foundOfs or 0
  226.       if #as>0 then
  227.         cs[1+#cs] = {table.concat(as,' '):gsub('%s+',' '),ofs}
  228.       end
  229. --      print('-->',unpack(cs[#cs]))
  230.     end
  231.     nearCavePatterns = cs
  232.   end
  233.  
  234.   target = target and target:len()>1 and GetAddressSafe(target)
  235.  
  236.   if target and force:len()>1 then
  237.     force=force:lower()
  238.     if force=='short' then forceShort = true-- should be redundancy?
  239.     elseif force=='near' then forceNear = true
  240.     elseif force=='long' then forceLong = true
  241.     else target = nil end -- unknown jump distance modifier, error
  242.   end
  243.  
  244.   if not target then
  245.     return --nil,'invalid :'..inst
  246.   else
  247.     local cmd = isJmp and {0xeb,0xe9,0x25ff} or {0xe8,0xe8,0x08eb0000000215ff}
  248.     local diff, r, bs = target - addr, {}
  249.     if isJmp and (forceShort or not forceNear and not forceLong) and diff>-0x7e and diff <0x82 then
  250.       bs = string.pack('Bb',cmd[1], diff-2)
  251.     elseif not targetIs64Bit() or not forceLong and diff>-0x7ffffffb and diff<0x80000005 then
  252.       bs = string.pack('Bi4',cmd[2],diff-5)
  253.     elseif not forceNear or addr<0x1000 then
  254.       if isJmp then
  255.         bs = string.pack('I6I8',cmd[3],target)
  256.       else
  257.         bs = string.pack('I8I8',cmd[3],target)
  258.       end
  259.     elseif diff<=-0x7ffffffb or diff>=0x80000005 then  -- force near and long jmp trampoline need
  260.       local trmp, errmsg = GetTrampoline(target, addr, nil,nearCavePatterns)
  261.       if not trmp then return nil,(errmsg or '!')..', no trampoline:'..inst end
  262.       bs = string.pack('Bi4',cmd[2], trmp - addr -5)
  263.     end
  264.     if bs then
  265.       for c in bs:gmatch'.' do r[1+#r]=c:byte()end
  266.       return r
  267.     end
  268.   end
  269. end)
  270. end --- cutsomJumpCall
  271.