-- SecroAuth loader local script_key = script_key or getgenv().script_key or "" local SCRIPT_ID = "dummy" local WS_URL = "wss://example.invalid/ws" if string.sub(WS_URL, 1, 6) ~= "wss://" then error("SecroAuth: secure websocket required") end local g_type, g_pcall, g_rawequal, g_tostring = type, pcall, rawequal, tostring local type, pcall, rawequal, tostring = g_type, g_pcall, g_rawequal, g_tostring local byte, char, concat = string.byte, string.char, table.concat local str_find, str_lower = string.find, string.lower local bxor, band, rshift, lshift = bit32.bxor, bit32.band, bit32.rshift, bit32.lshift local remove = table.remove local wait = task and task.wait local pcall_ref = g_pcall local getgenv_ref = getgenv local rawget_ref = rawget local getfenv_ref = getfenv local setfenv_ref = setfenv local env = getgenv and getgenv() or _G local global_ls = loadstring local env_ls = env and env.loadstring local dbg = debug local info_ref = dbg and (dbg.info or dbg.getinfo) local trace_ref = dbg and dbg.traceback local gethook_ref = dbg and dbg.gethook local dump_ref = string.dump local json_decode = game:GetService("HttpService").JSONDecode local HttpService = game:GetService("HttpService") local function native_loader(fn) if type(fn) ~= "function" then return false, "type" end local ok, chunk = pcall(fn, "return 0x5EC0A1") if not ok or type(chunk) ~= "function" then return false, "canary_load" end local ok2, value = pcall(chunk) if not ok2 or value ~= 0x5EC0A1 then return false, "canary_run" end if dump_ref then local dumped = pcall(dump_ref, fn) if dumped then return false, "dump" end end if info_ref then local got, info = pcall(info_ref, fn, "s") if got and info ~= nil then if type(info) == "string" and info ~= "[C]" then return false, "info" end if type(info) == "table" and info.what and info.what ~= "C" then return false, "info" end end end return true end local L = {} if env_ls and global_ls and not rawequal(env_ls, global_ls) then error("SecroAuth: loader identity mismatch") end local candidates = { global_ls, env_ls } if dbg and dbg.loadstring then candidates[#candidates + 1] = dbg.loadstring end for i = 1, #candidates do local fn = candidates[i] if not L.loader and native_loader(fn) then L.loader = fn end end if not L.loader then error("SecroAuth: environment check failed") end local function stable() local trusted = L.loader local ok, why = native_loader(trusted) if not ok then return "native:" .. tostring(why) end if global_ls and not rawequal(global_ls, loadstring) then return "global" end if env_ls and not rawequal(env_ls, env and env.loadstring) then return "env" end if info_ref and not rawequal(info_ref, dbg and (dbg.info or dbg.getinfo)) then return "info" end if dump_ref and not rawequal(dump_ref, string.dump) then return "dump" end if pcall_ref and env and env.pcall ~= nil and not rawequal(pcall_ref, env.pcall) then return "pcall" end if getgenv_ref and not rawequal(getgenv_ref, getgenv) then return "getgenv" end if getfenv_ref and not rawequal(getfenv_ref, getfenv) then return "getfenv" end if setfenv_ref and not rawequal(setfenv_ref, setfenv) then return "setfenv" end if rawget_ref and not rawequal(rawget_ref, rawget) then return "rawget" end if not rawequal(byte, string.byte) or not rawequal(char, string.char) or not rawequal(str_find, string.find) or not rawequal(str_lower, string.lower) then return "string" end if not rawequal(concat, table.concat) or not rawequal(remove, table.remove) then return "table" end if not rawequal(bxor, bit32.bxor) then return "bit32" end return nil end -- Conservative harness detector. Only strong test-framework signatures are -- fatal. Executor globals (getgenv/hookfunction/...) are expected and are -- intentionally not considered here. local function harness_probe() local markers = { "busted", "luassert", "TestEZ", "__TESTEZ_RUNNING", "__HARNESS__", "jest", "mocha", "chai", "sinon", } for i = 1, #markers do local name = markers[i] local ok, value = pcall(function() return env[name] end) if ok and value ~= nil then return "instrumentation:" .. name end end if trace_ref then local ok, tb = pcall(trace_ref, "", 2) if ok and type(tb) == "string" and #tb > 0 then local low = str_lower(tb) local needles = { "busted", "luassert", "testez", "__harness__", "jest", "mocha", "chai", "sinon", } for i = 1, #needles do if str_find(low, needles[i], 1, true) then return "instrumentation:" .. needles[i] end end end end if gethook_ref then local ok, hook = pcall(gethook_ref) if ok and hook ~= nil and info_ref then local ok2, src = pcall(info_ref, hook, "s") if ok2 and type(src) == "string" then local low = str_lower(src) if str_find(low, "busted", 1, true) or str_find(low, "luassert", 1, true) or str_find(low, "testez", 1, true) or str_find(low, "__harness__", 1, true) then return "instrumentation:hook" end end end end return nil end local B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" local B64R = {} for i = 1, #B64 do B64R[byte(B64, i)] = i - 1 end local function b64(s, expected_len) local out, cur, cn = {}, {}, 0 local len = #s local i = 1 local next_yield = 262144 -- Four input characters produce three output bytes. Decoding in one -- operation per character pair is what keeps this loop fast on mobile. while i + 3 <= len do local b1, b2, b3, b4 = byte(s, i, i + 3) if b2 == 61 or b3 == 61 or b4 == 61 then break end local v1, v2, v3, v4 = B64R[b1], B64R[b2], B64R[b3], B64R[b4] if v1 and v2 and v3 and v4 then local x = v1 * 262144 + v2 * 4096 + v3 * 64 + v4 cn = cn + 1 cur[cn] = char(band(rshift(x, 16), 255), band(rshift(x, 8), 255), band(x, 255)) if cn >= 4096 then out[#out + 1] = concat(cur) cur = {} cn = 0 end end i = i + 4 if wait and i >= next_yield then wait(0) next_yield = next_yield + 262144 end end local acc, bits = 0, 0 while i <= len do local c = byte(s, i) if c == 61 then break end local v = B64R[c] if v then acc = acc * 64 + v bits = bits + 6 if bits >= 8 then bits = bits - 8 cn = cn + 1 cur[cn] = char(band(rshift(acc, bits), 255)) acc = band(acc, lshift(1, bits) - 1) end end i = i + 1 end out[#out + 1] = concat(cur) local result = concat(out) if expected_len and #result ~= expected_len then return nil end return result end local buffer_lib = type(buffer) == "table" and type(buffer.fromstring) == "function" and type(buffer.create) == "function" and type(buffer.tostring) == "function" and type(buffer.readu32) == "function" and type(buffer.writeu32) == "function" and type(buffer.readu8) == "function" and type(buffer.writeu8) == "function" and buffer or nil local function make_ks64(key) local ks = {} for lb = 0, 511 do local idx = ((lb % 256) - 1) % 32 + 1 ks[lb + 1] = char(band(bxor(byte(key, idx), band(lb, 255)), 255)) end local kss = concat(ks) local ksb = buffer_lib.fromstring(kss) local out = buffer_lib.create(65536) local p = 1 while p <= 65536 do local lo = p % 256 local hi = band(rshift(p, 8), 255) local hi2 = band(rshift(p + 3, 8), 255) if hi == hi2 then local hi4 = hi + hi * 256 + hi * 65536 + hi * 16777216 buffer_lib.writeu32(out, p - 1, bxor(buffer_lib.readu32(ksb, lo), hi4)) else for q = p, p + 3 do buffer_lib.writeu8(out, q - 1, bxor(byte(kss, (q % 256) + 1), band(rshift(q, 8), 255))) end end p = p + 4 end return buffer_lib.tostring(out) end local decrypt_buf = SECRO_NO_VIRTUALIZE(function(s, key, ks) local n = #s local src = buffer_lib.fromstring(s) local dst = buffer_lib.create(n) local ksb = buffer_lib.fromstring(ks) local off, ko = 0, 0 local next_yield = 262144 while off + 64 <= n do local a1 = buffer_lib.readu32(src, off) local a2 = buffer_lib.readu32(src, off + 4) local a3 = buffer_lib.readu32(src, off + 8) local a4 = buffer_lib.readu32(src, off + 12) local a5 = buffer_lib.readu32(src, off + 16) local a6 = buffer_lib.readu32(src, off + 20) local a7 = buffer_lib.readu32(src, off + 24) local a8 = buffer_lib.readu32(src, off + 28) local a9 = buffer_lib.readu32(src, off + 32) local a10 = buffer_lib.readu32(src, off + 36) local a11 = buffer_lib.readu32(src, off + 40) local a12 = buffer_lib.readu32(src, off + 44) local a13 = buffer_lib.readu32(src, off + 48) local a14 = buffer_lib.readu32(src, off + 52) local a15 = buffer_lib.readu32(src, off + 56) local a16 = buffer_lib.readu32(src, off + 60) local k1 = buffer_lib.readu32(ksb, ko) local k2 = buffer_lib.readu32(ksb, ko + 4) local k3 = buffer_lib.readu32(ksb, ko + 8) local k4 = buffer_lib.readu32(ksb, ko + 12) local k5 = buffer_lib.readu32(ksb, ko + 16) local k6 = buffer_lib.readu32(ksb, ko + 20) local k7 = buffer_lib.readu32(ksb, ko + 24) local k8 = buffer_lib.readu32(ksb, ko + 28) local k9 = buffer_lib.readu32(ksb, ko + 32) local k10 = buffer_lib.readu32(ksb, ko + 36) local k11 = buffer_lib.readu32(ksb, ko + 40) local k12 = buffer_lib.readu32(ksb, ko + 44) local k13 = buffer_lib.readu32(ksb, ko + 48) local k14 = buffer_lib.readu32(ksb, ko + 52) local k15 = buffer_lib.readu32(ksb, ko + 56) local k16 = buffer_lib.readu32(ksb, ko + 60) buffer_lib.writeu32(dst, off, bxor(a1, k1)) buffer_lib.writeu32(dst, off + 4, bxor(a2, k2)) buffer_lib.writeu32(dst, off + 8, bxor(a3, k3)) buffer_lib.writeu32(dst, off + 12, bxor(a4, k4)) buffer_lib.writeu32(dst, off + 16, bxor(a5, k5)) buffer_lib.writeu32(dst, off + 20, bxor(a6, k6)) buffer_lib.writeu32(dst, off + 24, bxor(a7, k7)) buffer_lib.writeu32(dst, off + 28, bxor(a8, k8)) buffer_lib.writeu32(dst, off + 32, bxor(a9, k9)) buffer_lib.writeu32(dst, off + 36, bxor(a10, k10)) buffer_lib.writeu32(dst, off + 40, bxor(a11, k11)) buffer_lib.writeu32(dst, off + 44, bxor(a12, k12)) buffer_lib.writeu32(dst, off + 48, bxor(a13, k13)) buffer_lib.writeu32(dst, off + 52, bxor(a14, k14)) buffer_lib.writeu32(dst, off + 56, bxor(a15, k15)) buffer_lib.writeu32(dst, off + 60, bxor(a16, k16)) off = off + 64 ko = ko + 64 if ko >= 65536 then ko = 0 end if wait and off >= next_yield then wait(0) next_yield = next_yield + 262144 end end while off + 16 <= n do local a1 = buffer_lib.readu32(src, off) local a2 = buffer_lib.readu32(src, off + 4) local a3 = buffer_lib.readu32(src, off + 8) local a4 = buffer_lib.readu32(src, off + 12) local k1 = buffer_lib.readu32(ksb, ko) local k2 = buffer_lib.readu32(ksb, ko + 4) local k3 = buffer_lib.readu32(ksb, ko + 8) local k4 = buffer_lib.readu32(ksb, ko + 12) buffer_lib.writeu32(dst, off, bxor(a1, k1)) buffer_lib.writeu32(dst, off + 4, bxor(a2, k2)) buffer_lib.writeu32(dst, off + 8, bxor(a3, k3)) buffer_lib.writeu32(dst, off + 12, bxor(a4, k4)) off = off + 16 ko = ko + 16 if ko >= 65536 then ko = 0 end end while off + 4 <= n do buffer_lib.writeu32(dst, off, bxor(buffer_lib.readu32(src, off), buffer_lib.readu32(ksb, ko))) off = off + 4 ko = ko + 4 if ko >= 65536 then ko = 0 end end while off < n do buffer_lib.writeu8(dst, off, bxor(buffer_lib.readu8(src, off), buffer_lib.readu8(ksb, ko))) off = off + 1 ko = ko + 1 if ko >= 65536 then ko = 0 end end return buffer_lib.tostring(dst) end) local function decrypt_fast(s, key) local n = #s local out, cur, cn = {}, {}, 0 local ks = {} for lb = 0, 511 do local idx = ((lb % 256) - 1) % 32 + 1 ks[lb + 1] = char(band(bxor(byte(key, idx), band(lb, 255)), 255)) end local kss = concat(ks) local i = 1 local next_yield = 262144 while i + 15 <= n do local lo = i % 256 local hi = band(rshift(i, 8), 255) local hi2 = band(rshift(i + 15, 8), 255) if hi == hi2 then local b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 = byte(s, i, i + 15) local k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15, k16 = byte(kss, lo + 1, lo + 16) cn = cn + 1 cur[cn] = char( bxor(b1, k1, hi), bxor(b2, k2, hi), bxor(b3, k3, hi), bxor(b4, k4, hi), bxor(b5, k5, hi), bxor(b6, k6, hi), bxor(b7, k7, hi), bxor(b8, k8, hi), bxor(b9, k9, hi), bxor(b10, k10, hi), bxor(b11, k11, hi), bxor(b12, k12, hi), bxor(b13, k13, hi), bxor(b14, k14, hi), bxor(b15, k15, hi), bxor(b16, k16, hi)) else for p = i, i + 15 do cn = cn + 1 cur[cn] = char(bxor(byte(s, p), byte(kss, (p % 256) + 1), band(rshift(p, 8), 255))) end end i = i + 16 if cn >= 4096 then out[#out + 1] = concat(cur) cur = {} cn = 0 end if wait and i >= next_yield then wait(0) next_yield = next_yield + 262144 end end while i <= n do cn = cn + 1 cur[cn] = char(bxor(byte(s, i), byte(kss, (i % 256) + 1), band(rshift(i, 8), 255))) i = i + 1 end out[#out + 1] = concat(cur) return concat(out) end local function decrypt(s, key) if #key == 0 then return nil end if buffer_lib then local ok, out = pcall(function() return decrypt_buf(s, key, make_ks64(key)) end) if ok and type(out) == "string" and #out == #s then return out end end return decrypt_fast(s, key) end local function sample(s) local h = 0 for i = 1, #s, 128 do h = band(bxor(h * 33, byte(s, i)), 0xFFFFFFFF) end return h end local function executor_name() local ok, name = pcall(function() if identifyexecutor then return identifyexecutor() end if getexecutorname then return getexecutorname() end if syn and syn.identifyexecutor then return syn:identifyexecutor() end if fluxus and fluxus.identifyexecutor then return fluxus:identifyexecutor() end if is_sirhurt_closure then return "SirHurt" end if Delta then return "Delta" end if Krnl or KRNL then return "Krnl" end return nil end) if ok and name and tostring(name) ~= "" then return tostring(name):sub(1, 80) end return "Unknown" end local function hwid() local values = {} local function add(fn) if type(fn) == "function" then local ok, value = pcall(fn) if ok and value ~= nil then values[#values + 1] = tostring(value) end end end add(gethwid) add(get_hwid) add(getdeviceid) add(get_device_id) if syn and syn.get_hwid then add(function() return syn:get_hwid() end) end if fluxus and fluxus.get_hwid then add(function() return fluxus:get_hwid() end) end for i = 1, #values do local value = tostring(values[i]) if value ~= "" and value ~= "0" and value ~= "nil" and value ~= "unknown" and #value <= 200 then return value end end error("SecroAuth: hardware ID unavailable on this executor") end local band, bor, bxor, brshift, blshift, bnot = bit32.band, bit32.bor, bit32.bxor, bit32.rshift, bit32.lshift, bit32.bnot local byte, char, concat, rep = string.byte, string.char, table.concat, string.rep local floor = math.floor local K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, } local function add(...) local s = 0 for i = 1, select("#", ...) do s = (s + select(i, ...)) % 4294967296 end return s end local function ror(x, n) n = n % 32 if n == 0 then return x end return bor(brshift(x, n), blshift(x, 32 - n)) end local function sha256(message) local data = {} for i = 1, #message do data[i] = byte(message, i) end local bitlen = #message * 8 data[#data + 1] = 0x80 while (#data % 64) ~= 56 do data[#data + 1] = 0 end local high = floor(bitlen / 4294967296) local low = bitlen % 4294967296 local function push32(n) data[#data + 1] = floor(n / 16777216) % 256 data[#data + 1] = floor(n / 65536) % 256 data[#data + 1] = floor(n / 256) % 256 data[#data + 1] = n % 256 end push32(high) push32(low) local h0, h1, h2, h3 = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a local h4, h5, h6, h7 = 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 for base = 1, #data, 64 do local w = {} for i = 0, 15 do local j = base + i * 4 w[i] = ((data[j] * 256 + data[j + 1]) * 256 + data[j + 2]) * 256 + data[j + 3] end for i = 16, 63 do local x = w[i - 15] local y = w[i - 2] local s0 = bxor(ror(x, 7), ror(x, 18), brshift(x, 3)) local s1 = bxor(ror(y, 17), ror(y, 19), brshift(y, 10)) w[i] = add(w[i - 16], s0, w[i - 7], s1) end local a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7 for i = 0, 63 do local S1 = bxor(ror(e, 6), ror(e, 11), ror(e, 25)) local ch = bxor(band(e, f), band(bnot(e), g)) local t1 = add(h, S1, ch, K[i + 1], w[i]) local S0 = bxor(ror(a, 2), ror(a, 13), ror(a, 22)) local maj = bxor(band(a, b), band(a, c), band(b, c)) local t2 = add(S0, maj) h, g, f, e, d, c, b, a = g, f, e, add(d, t1), c, b, a, add(t1, t2) end h0, h1, h2, h3 = add(h0, a), add(h1, b), add(h2, c), add(h3, d) h4, h5, h6, h7 = add(h4, e), add(h5, f), add(h6, g), add(h7, h) end local out = {} local function push(n) out[#out + 1] = char(floor(n / 16777216) % 256, floor(n / 65536) % 256, floor(n / 256) % 256, n % 256) end push(h0); push(h1); push(h2); push(h3) push(h4); push(h5); push(h6); push(h7) return concat(out) end local HEX = "0123456789abcdef" local function tohex(s) local out = {} for i = 1, #s do local b = byte(s, i) local hi = floor(b / 16) + 1 local lo = b % 16 + 1 out[#out + 1] = string.sub(HEX, hi, hi) .. string.sub(HEX, lo, lo) end return concat(out) end local function fromhex(s) local out = {} for i = 1, #s, 2 do out[#out + 1] = char(tonumber(string.sub(s, i, i + 1), 16)) end return concat(out) end local function hmac(key, message) if #key > 64 then key = sha256(key) end key = key .. rep("\0", 64 - #key) local ipad, opad = {}, {} for i = 1, 64 do local b = byte(key, i) ipad[i] = char(bxor(b, 0x36)) opad[i] = char(bxor(b, 0x5c)) end return sha256(concat(opad) .. sha256(concat(ipad) .. message)) end local function __sha256hex(message) return tohex(sha256(message)) end local function __hmachex(key, message) return tohex(hmac(key, message)) end -- PRF-based KDF. The license key is already a uniformly random 190-bit -- secret, so a single labelled HMAC is sufficient and avoids a binary-key -- HKDF path that the protected VM handles poorly. local function __kdf(key, salt, info) return hmac(key, info .. "|" .. salt) end local function __random(n) local ok, value = pcall(function() if crypt and crypt.generatebytes then return crypt.generatebytes(n) end if crypt and crypt.random then return crypt.random(n) end if syn and syn.crypt and syn.crypt.generatebytes then return syn.crypt.generatebytes(n) end if fluxus and fluxus.crypt and fluxus.crypt.generatebytes then return fluxus.crypt.generatebytes(n) end return nil end) if ok and type(value) == "string" and #value >= n then return string.sub(value, 1, n) end local seed = tostring(os.clock()) .. tostring(os.time()) .. tostring({}) .. tostring(math.random()) local out = "" while #out < n do seed = sha256(seed) .. seed out = out .. seed end return string.sub(out, 1, n) end local function __randomhex(n) return tohex(__random(n)) end local __why = stable() if __why then error("SecroAuth: integrity check failed (" .. __why .. ")") end local __harness = harness_probe() if __harness then error("SecroAuth: " .. __harness) end local okws, ws = pcall(WebSocket.connect, WS_URL) if not okws or not ws then error("SecroAuth: websocket unavailable") end local queue = {} local S = {closed = false, closed_at = nil} local events = false if ws.OnMessage then local okc = pcall(function() ws.OnMessage:Connect(function(message) queue[#queue + 1] = message end) end) events = okc end if ws.OnClose then pcall(function() ws.OnClose:Connect(function() S.closed = true S.closed_at = os.clock() end) end) end if not events then error("SecroAuth: websocket events unavailable") end local function read_one(deadline) while true do local raw = remove(queue, 1) if raw then local ok, data = pcall(json_decode, HttpService, raw) if ok and type(data) == "table" then return data end else if S.closed then error("SecroAuth: connection closed during handshake") end if os.clock() > deadline then error("SecroAuth: handshake timeout") end task.wait(0.02) end end end local hello = read_one(os.clock() + 30) local nonce = tostring(hello.nonce or "") local hello_ts = tostring(hello.ts or "") local proto = tostring(hello.v or "") local hs_session = tostring(hello.session_id or "") if hello.type ~= "hello" or (proto ~= "3" and proto ~= "4") or nonce == "" then error("SecroAuth: invalid hello") end if proto == "4" and hs_session == "" then error("SecroAuth: invalid session") end local hwid_value = hwid() local executor_value = executor_name() local client_ts = tostring(os.time()) local client_nonce = tohex(__random(16)) local key_id = __sha256hex(script_key) local auth_salt local auth_text if proto == "4" then auth_salt = SCRIPT_ID .. ":" .. nonce .. ":" .. hs_session .. ":" .. client_nonce auth_text = "v4|auth|" .. SCRIPT_ID .. "|" .. nonce .. "|" .. hello_ts .. "|" .. client_ts .. "|" .. client_nonce .. "|" .. key_id .. "|" .. hwid_value .. "|" .. executor_value .. "|" .. hs_session else auth_salt = SCRIPT_ID .. ":" .. nonce .. ":" .. client_nonce auth_text = "v3|auth|" .. SCRIPT_ID .. "|" .. nonce .. "|" .. hello_ts .. "|" .. client_ts .. "|" .. client_nonce .. "|" .. key_id .. "|" .. hwid_value .. "|" .. executor_value end ws:Send(HttpService:JSONEncode({ type = "auth", v = proto == "4" and 4 or 3, nonce = nonce, hello_ts = hello_ts, ts = client_ts, cnonce = client_nonce, key_id = key_id, proof = __hmachex(script_key, auth_text), hwid = hwid_value, executor = executor_value, session_id = proto == "4" and hs_session or nil, })) local enc_data = "" local binary_mode = false local k_auth = __kdf(script_key, auth_salt, proto == "4" and "secro-auth-v4" or "secro-auth-v3") local k_wrap = nil local k_enc = "" local key_fp = "" if proto == "4" then k_wrap = __kdf(script_key, auth_salt, "secro-session-wrap-v4") else k_enc = __kdf(script_key, auth_salt, "secro-artifact-v3") end local session_id = "" local expected_check = nil local expected_len = nil local deadline = os.clock() + 60 while true do local raw = remove(queue, 1) if not raw then if S.closed then S.closed_at = S.closed_at or os.clock() if os.clock() - S.closed_at > 2 then error("SecroAuth: connection closed q=" .. tostring(#queue)) end end if os.clock() > deadline then error("SecroAuth: timeout") end task.wait(0.02) else local handled = false if type(raw) == "string" then local tag = string.sub(raw, 1, 2) if tag == "\0P" then pcall(function() ws:Send('{"type":"bin_ack"}') end) handled = true elseif tag == "\0C" then binary_mode = true enc_data = enc_data .. string.sub(raw, 3) handled = true end end if not handled then local ok, data = pcall(json_decode, HttpService, raw) if ok and type(data) == "table" then if data.type == "chunk" then enc_data = enc_data .. tostring(data.data or "") elseif data.type == "auth_ok" then session_id = tostring(data.session_id or "") expected_check = tonumber(data.check) expected_len = tonumber(data.len) if proto == "4" then key_fp = tostring(data.key_fp or "") local text = "v4|auth_ok|" .. SCRIPT_ID .. "|" .. nonce .. "|" .. hello_ts .. "|" .. client_ts .. "|" .. client_nonce .. "|" .. key_id .. "|" .. hwid_value .. "|" .. executor_value .. "|" .. session_id .. "|" .. tostring(expected_len) .. "|" .. tostring(expected_check) .. "|" .. key_fp if __hmachex(k_auth, text) ~= tostring(data.sig or "") then error("SecroAuth: auth signature failed") end local wrapped = b64(tostring(data.key_wrap or ""), 32) local unwrapped = decrypt(wrapped, k_wrap) if not unwrapped or #unwrapped ~= 32 then error("SecroAuth: session key failed") end local fp = __hmachex(unwrapped, "secro-session-fp-v4|" .. session_id) if fp ~= key_fp then error("SecroAuth: session fingerprint failed") end k_enc = unwrapped else local text = "v3|auth_ok|" .. SCRIPT_ID .. "|" .. nonce .. "|" .. hello_ts .. "|" .. client_ts .. "|" .. client_nonce .. "|" .. key_id .. "|" .. hwid_value .. "|" .. executor_value .. "|" .. session_id .. "|" .. tostring(expected_len) .. "|" .. tostring(expected_check) if __hmachex(k_auth, text) ~= tostring(data.sig or "") then error("SecroAuth: auth signature failed") end end elseif data.type == "done" then local text if proto == "4" then text = "v4|done|" .. SCRIPT_ID .. "|" .. nonce .. "|" .. client_nonce .. "|" .. session_id .. "|" .. tostring(expected_len) .. "|" .. tostring(expected_check) .. "|" .. key_fp else text = "v3|done|" .. SCRIPT_ID .. "|" .. nonce .. "|" .. client_nonce .. "|" .. session_id .. "|" .. tostring(expected_len) .. "|" .. tostring(expected_check) end if __hmachex(k_auth, text) ~= tostring(data.sig or "") then error("SecroAuth: done signature failed") end break elseif data.type == "error" then pcall(function() ws:Close() end) error("SecroAuth: " .. tostring(data.msg or data.code or "denied")) end end end end end pcall(function() ws:Close() end) if #k_enc == 0 or session_id == "" or not expected_check or not expected_len then error("SecroAuth: incomplete stream") end local encrypted if binary_mode then encrypted = enc_data else encrypted = b64(enc_data, expected_len) end if type(encrypted) ~= "string" or #encrypted ~= expected_len then error("SecroAuth: stream length mismatch") end local source = decrypt(encrypted, k_enc) if not source or #source ~= expected_len then error("SecroAuth: stream decrypt failed") end if sample(source) ~= expected_check then error("SecroAuth: stream integrity failed") end __why = stable() if __why then error("SecroAuth: loader changed (" .. __why .. ")") end local loaded, chunk = pcall(L.loader, source) if not loaded or type(chunk) ~= "function" then error("SecroAuth: load failed") end getgenv().script_key = script_key local ok, err = pcall(chunk) if not ok then error(err) end