Reconstructed Lua scripting infrastructure consumed by every per-game
flash script. Two source files preserved at D:\B\Srift\games\Flash\:
- functional.lua (109 lines): table.from/max/min/find/filter/map
- internal.lua (172 lines): Markers, Rule.All/Any sugar, Hex/Addr,
print override, gadget enumerators
Surface inventory at D:\B\Srift\games\Flash\SURFACE.md confirms
these two files cover scriptcore's lua-level responsibilities for
CODMW, CODMW2, IW9, DayZ, Template. RB6 may need a Rule.Refs stub.
Server contract (Builder.ts:286-380): pushing this repo to Gitea as
srift/core triggers the standard webhook pipeline and produces
S.Build doc { kind:'script', product:'scriptcore' }.
173 lines
4.2 KiB
Lua
173 lines
4.2 KiB
Lua
|
|
-- Implement the marker table.
|
|
--
|
|
local markerObjects = {}
|
|
_G.Markers = { Clear = function() markerObjects = {} end }
|
|
setmetatable(_G.Markers, {
|
|
__index = function(_, name)
|
|
local marker = markerObjects[name]
|
|
return marker and Rule.ReadMarker(marker)
|
|
end
|
|
})
|
|
|
|
-- Parses a table of rules and returns a table of valid Rule objects.
|
|
--
|
|
local function parseRuleList(arg)
|
|
local result = {}
|
|
local it = 1
|
|
while true do
|
|
local v = arg[it]
|
|
if v == nil then break end
|
|
local t = type(v)
|
|
|
|
if t == "string" then
|
|
if v:sub(1,1) == "#" then
|
|
v = v:sub(2)
|
|
local m = markerObjects[v]
|
|
if not m then
|
|
m = Rule.Marker()
|
|
markerObjects[v] = m
|
|
end
|
|
result[#result+1] = m
|
|
elseif type(arg[it+1]) == "number" then
|
|
result[#result+1] = Rule.Instruction(v, arg[it+1])
|
|
it = it + 1
|
|
else
|
|
result[#result+1] = Rule.Pattern(v)
|
|
end
|
|
elseif t == "table" then
|
|
result[#result+1] = Rule.Pad(v[1], v[2])
|
|
elseif t == "function" then
|
|
result[#result+1] = Rule.Filter(v)
|
|
elseif t == "userdata" then
|
|
result[#result+1] = v
|
|
else
|
|
error("Did not expect: " .. t)
|
|
end
|
|
it = it + 1
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Implement Rule.All and Rule.Any.
|
|
--
|
|
function Rule.All(arg)
|
|
return Rule.AllImpl(parseRuleList(arg))
|
|
end
|
|
function Rule.Any(arg)
|
|
return Rule.AnyImpl(parseRuleList(arg))
|
|
end
|
|
|
|
-- Hex helper, override print.
|
|
--
|
|
function Hex(value) return value and string.format("%x", value) or "nil" end
|
|
function Addr(value) return value and string.format("%x", value + Image.base) or "nil" end
|
|
|
|
function print(...)
|
|
local args = table.pack(...)
|
|
local msg = ""
|
|
for i=1, args.n do
|
|
if #msg ~= 0 then
|
|
msg = msg .. " " .. tostring(args[i])
|
|
else
|
|
msg = tostring(args[i])
|
|
end
|
|
end
|
|
Log(msg)
|
|
end
|
|
|
|
local function findAny(patternList)
|
|
local output = {0}
|
|
local n = 0
|
|
for k,v in ipairs(patternList) do
|
|
for rva in Image:Find(Rule.Pattern(v[1])) do
|
|
local res = rva + ((#v[1]+1)/3) + v[2]
|
|
output[n+2] = res
|
|
n = n + 1
|
|
end
|
|
end
|
|
output[1] = n
|
|
return output
|
|
end
|
|
|
|
-- Implement searchers for user call gadgets.
|
|
--
|
|
Register("_fast_return_gadgets", R_ARR, function()
|
|
return findAny{
|
|
{ "E8 ?? 0F 05 00", -3 }, -- call rel32
|
|
{ "0F 8? 0F 05 00", -3 }, -- jcc rel32
|
|
{ "FF 15 ?? 0F 05", -2 }, -- call [rip+rel32]
|
|
{ "48 8D ?? ?? 0F 05", -2 }, -- lea [rip+rel32]
|
|
{ "48 89 ?? ?? 0F 05", -2 }, -- mov [rip+rel32]
|
|
{ "4C 8D ?? ?? 0F 05", -2 }, -- lea [rip+rel32]
|
|
{ "4C 89 ?? ?? 0F 05", -2 }, -- mov [rip+rel32]
|
|
}
|
|
end)
|
|
|
|
Register("_fix_arg_gadgets", R_ARR, function()
|
|
return findAny{
|
|
{ "F3 41 0F 59 C3", -2 }, -- mulss, mulps, vmulss, vmulps
|
|
{ "F3 43 0F 59 C3", -2 },
|
|
{ "41 0F 59 C3", -2 },
|
|
{ "F3 0F 59 C3", -2 },
|
|
{ "C4 C1 ?? 59 C3", -2 }
|
|
}
|
|
end)
|
|
|
|
Register("_stack_spoof_gadgets", R_ARR, function()
|
|
-- Matches a call m64 instruction
|
|
-- reg = /2
|
|
local callPattern = { "FF 10", "FF 38" }
|
|
|
|
-- Matches an epilogue
|
|
--
|
|
local epiPatterns = {
|
|
{ "48 83 C4 08 C3", "FF FF FF 8F FF" }, -- [add rsp, imm8], no negative/misaligned immediates
|
|
{ "48 81 C4 08 00 00 00 C3", "FF FF FF 0F FF FF FF FF" } -- [add rsp, imm32], no negative/misaligned/huge immediates
|
|
}
|
|
|
|
-- For each epilogue pattern:
|
|
--
|
|
local results = {}
|
|
for _,v in ipairs(epiPatterns) do
|
|
-- For each call instruction suffix length:
|
|
--
|
|
for suf=5,0,-1 do
|
|
-- Find the gadget:
|
|
--
|
|
local value = callPattern[1] .. string.rep(" 00", suf) .. " " .. v[1]
|
|
local mask = callPattern[2] .. string.rep(" 00", suf) .. " " .. v[2]
|
|
for match in Image:Find(Rule.Pattern(value, mask)) do
|
|
-- If valid:
|
|
--
|
|
if Image:DecodeLen(match) == (2+suf) then
|
|
-- Decode the sub instruction, if it is not at arg area, write the result:
|
|
--
|
|
local subinsn = Image:Decode(match+2+suf)
|
|
if subinsn.imm0 >= (8*9) then
|
|
results[#results+1] = {
|
|
callRva = match,
|
|
subRva = match + 2 + suf,
|
|
delta = subinsn.imm0 + 8
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Sort the table based on call complexity.
|
|
--
|
|
table.sort(results, function(a, b) return (a.subRva-a.callRva)>(b.subRva-b.callRva) end)
|
|
|
|
-- Get table length, trimming to a maximum of 32 and write the immediates.
|
|
--
|
|
local len = math.min(32, #results)
|
|
local output = {}
|
|
output[1] = len
|
|
for i=1,len do
|
|
output[i*2 + 0] = results[i].subRva
|
|
output[i*2 + 1] = results[i].delta
|
|
end
|
|
return output
|
|
end) |