diff --git a/ClosureManager.cpp b/ClosureManager.cpp index 03d645b..59f6ff5 100644 --- a/ClosureManager.cpp +++ b/ClosureManager.cpp @@ -3,6 +3,7 @@ // #include "ClosureManager.hpp" #include +#include #include "Communication/Communication.hpp" #include "Environment/Libraries/Globals.hpp" @@ -34,17 +35,17 @@ void ClosureManager::ResetManager(const RBX::DataModelType &resetTarget) { int ClosureManager::newcclosure_handler(lua_State *L) { const auto argc = lua_gettop(L); - const auto manager = RobloxManager::GetSingleton(); + const auto robloxManager = RobloxManager::GetSingleton(); - const auto scriptContext = manager->GetScriptContext(L); + const auto scriptContext = robloxManager->GetScriptContext(L); if (!scriptContext.has_value()) luaL_error(L, "call resolution failed; ScriptContext unavailable"); - const auto dataModel = manager->GetDataModelFromScriptContext(scriptContext.value()); + const auto dataModel = robloxManager->GetDataModelFromScriptContext(scriptContext.value()); if (!dataModel.has_value()) luaL_error(L, "call resolution failed; DataModel unavailable"); - const auto currentDataModel = manager->GetDataModelType(dataModel.value()); + const auto currentDataModel = robloxManager->GetDataModelType(dataModel.value()); const auto clManager = ClosureManager::GetSingleton(); if (!clManager->m_newcclosureMap[currentDataModel].contains(clvalue(L->ci->func))) @@ -52,20 +53,20 @@ int ClosureManager::newcclosure_handler(lua_State *L) { // Due to the nature of the Lua Registry, our closures can be replaced with complete garbage. // Thus we must be careful, and validate that the Lua Registry ref IS present. - Closure *closure = clManager->m_newcclosureMap[currentDataModel].at(clvalue(L->ci->func)); + ClosureWrapInformation closure = clManager->m_newcclosureMap[currentDataModel].at(clvalue(L->ci->func)); luaC_threadbarrier(L); - L->top->value.p = closure; + L->top->value.p = closure.closure; L->top->tt = lua_Type::LUA_TFUNCTION; L->top++; const auto scheduler = Scheduler::GetSingleton(); - if (!RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel].contains(closure)) + if (!RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel].contains(closure.closure)) luaL_error(L, "call resolution failed, invalid closure state."); - lua_getref(L, RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel][closure]); - if (lua_type(L, -1) == LUA_TFUNCTION && lua_toclosure(L, -1) == closure) { + lua_getref(L, RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel][closure.closure]); + if (lua_type(L, -1) == LUA_TFUNCTION && lua_toclosure(L, -1) == closure.closure) { lua_pop(L, 1); } else { luaL_error(L, "call resolution failed, invalid ref."); // If the pointers aren't the same, and the lua type @@ -73,16 +74,83 @@ int ClosureManager::newcclosure_handler(lua_State *L) { // on the devil, but we don't have a crucifix to remove // him, so just crash. } + lua_insert(L, 1); + if (closure.requiresYielding) { + /* + * Due to the very clear possibility of the current thread not being yieldable, we must re-schedule the closure + * into a new thread, then we must exchange the returns of that thread into our own, whilst keeping this one + * waiting for whatever that function returns (Yielding on this side until that other function answers and + * fulfills the request!) + */ + + if (robloxManager->GetRobloxTaskDefer().has_value()) { + const auto defer = robloxManager->GetRobloxTaskDefer().value(); + defer(L); + } else if (robloxManager->GetRobloxTaskSpawn().has_value()) { + const auto spawn = robloxManager->GetRobloxTaskSpawn().value(); + spawn(L); + } else { + throw std::exception("cannot handle yieldable newcclosure; no schedule function available"); + } + + const auto nL = lua_tothread(L, -1); + nL->namecall = L->namecall; + + scheduler->ScheduleJob(SchedulerJob( + L, [nL](lua_State *L, std::shared_future> *callbackToExecute) { + printf("waiting for ex...\n"); + while (lua_costatus(L, nL) != lua_CoStatus::LUA_COFIN && + lua_costatus(L, nL) != lua_CoStatus::LUA_COERR) { + _mm_pause(); + } + printf("call complete!\n"); + + // ready to resume into original thread, the call has been successfully dispatched. + + *callbackToExecute = std::async(std::launch::async, [nL]() -> std::function { + return [nL](lua_State *L) -> int { + if (lua_status(nL) == lua_CoStatus::LUA_COERR) { + // We must move to the original thread again, except just the first argument and signal + // the scheduler the task failed. + lua_xmove(nL, L, 1); + + /* + * When we throw from Luau, we are using Roblox's rawrununprotected and we are going + * through its VM this leads for our pusherror modificaton to never run; meaning the + * callstack information will be present on the error message. To prevent this, we must + * manually strip the data with a few string operations. this guarantees our metamethod + * hooks to be undetected, whilst the error message doesn't change. + */ + const auto newError = Utilities::StripLuaErrorMessage(lua_tostring(L, -1)); + lua_pop(L, 1); + lua_pushstring(L, newError.c_str()); + return -1; + } + + lua_xmove(nL, L, lua_gettop(nL)); + + return lua_gettop(L); + }; + }); + })); + + return lua_yield(L, 0); + } + const auto callResult = lua_pcall(L, argc, LUA_MULTRET, 0); if (callResult != LUA_OK && callResult != LUA_YIELD && std::strcmp(luaL_optstring(L, -1, ""), "attempt to yield across metamethod/C-call boundary") == 0) { return lua_yield(L, LUA_MULTRET); } - if (callResult == LUA_ERRRUN) + if (callResult == LUA_ERRRUN) { + const auto newError = Utilities::StripLuaErrorMessage(lua_tostring(L, -1)); + lua_pop(L, 1); + lua_pushstring(L, newError.c_str()); lua_error(L); // error string at stack top + } return lua_gettop(L); } @@ -107,9 +175,9 @@ bool ClosureManager::IsWrapped(lua_State *L, const Closure *closure) { const auto currentDataModel = manager->GetDataModelType(dataModel.value()); - return std::ranges::any_of(this->m_newcclosureMap[currentDataModel].begin(), - this->m_newcclosureMap[currentDataModel].end(), - [&closure](const std::pair cl) { return closure == cl.second; }); + return std::ranges::any_of( + this->m_newcclosureMap[currentDataModel].begin(), this->m_newcclosureMap[currentDataModel].end(), + [&closure](const std::pair cl) { return closure == cl.second.closure; }); } int ClosureManager::hookfunction(lua_State *L) { @@ -200,7 +268,7 @@ int ClosureManager::hookfunction(lua_State *L) { [hookTarget]; // We must substitute the refs on original to point to the new, // but we must keep the clones'. - clManager->m_newcclosureMap[currentDataModel][hookTarget] = hookWith; + clManager->m_newcclosureMap[currentDataModel][hookTarget] = {hookWith, false}; L->top->value.p = cl; L->top->tt = LUA_TFUNCTION; L->top++; @@ -341,13 +409,27 @@ void ClosureManager::FixClosure(lua_State *L, Closure *closure) { int ClosureManager::newcclosure(lua_State *L) { // Using relative offsets, since this function may be called by other functions! (hookfunc) auto closureIndex = -1; - if (lua_type(L, -1) == LUA_TSTRING) + auto nameIndex = -1; + if (lua_type(L, -2) == LUA_TSTRING || lua_type(L, -2) == LUA_TNIL) { + closureIndex--; + closureIndex--; + nameIndex--; + } + + if (lua_type(L, -1) == LUA_TSTRING || lua_type(L, -1) == LUA_TNIL) { closureIndex--; + } luaL_checktype(L, closureIndex, LUA_TFUNCTION); + bool mustYield = false; + const char *functionName = nullptr; - if (lua_type(L, -1) == LUA_TSTRING) - functionName = luaL_optstring(L, -1, nullptr); + + if (lua_type(L, nameIndex) == LUA_TSTRING) + functionName = luaL_optstring(L, nameIndex, nullptr); + + if (lua_type(L, nameIndex + 1) == LUA_TBOOLEAN) + mustYield = static_cast(lua_toboolean(L, nameIndex + 1)); const auto manager = RobloxManager::GetSingleton(); @@ -361,8 +443,8 @@ int ClosureManager::newcclosure(lua_State *L) { const auto closure = lua_toclosure(L, closureIndex); clManager->FixClosure(L, closure); lua_pushcclosurek(L, ClosureManager::newcclosure_handler, functionName, 0, nullptr); - const auto cclosure = lua_toclosure(L, closureIndex); - clManager->m_newcclosureMap[currentDataModel][cclosure] = closure; + const auto cclosure = lua_toclosure(L, -1); + clManager->m_newcclosureMap[currentDataModel][cclosure] = ClosureWrapInformation{closure, mustYield}; lua_remove(L, lua_gettop(L) - 1); // Balance lua stack. return 1; } diff --git a/ClosureManager.hpp b/ClosureManager.hpp index 2e42859..f554c0b 100644 --- a/ClosureManager.hpp +++ b/ClosureManager.hpp @@ -23,12 +23,17 @@ struct LuauHookInformation { Closure *hookedClosure; }; +struct ClosureWrapInformation { + Closure *closure; + bool requiresYielding; +}; + class ClosureManager final { static std::shared_ptr pInstance; std::map> m_hookMap; // TODO: Make hook map use DataModel Types' - std::map> m_newcclosureMap; + std::map> m_newcclosureMap; /// @brief Handles a newcclosure call. static int newcclosure_handler(lua_State *L); diff --git a/Dependencies/Luau/VM/src/ldebug.cpp b/Dependencies/Luau/VM/src/ldebug.cpp index 07cc117..6748051 100644 --- a/Dependencies/Luau/VM/src/ldebug.cpp +++ b/Dependencies/Luau/VM/src/ldebug.cpp @@ -308,7 +308,7 @@ l_noret luaG_readonlyerror(lua_State* L) static void pusherror(lua_State* L, const char* msg) { CallInfo* ci = L->ci; - if (isLua(ci)) + if (isLua(ci) && clvalue(ci->func)->l.p->linedefined != -1) { TString* source = getluaproto(ci)->source; char chunkbuf[LUA_IDSIZE]; // add file:line information diff --git a/Dependencies/Luau/VM/src/ldo.cpp b/Dependencies/Luau/VM/src/ldo.cpp index 8946b25..cfbbc5f 100644 --- a/Dependencies/Luau/VM/src/ldo.cpp +++ b/Dependencies/Luau/VM/src/ldo.cpp @@ -121,7 +121,7 @@ int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud) ->GetOffset("luaD_rawrununprotected"))(L, f, ud); int status = 0; - + printf("Using RbxStu V2's luaD_rawrununprotected!\n"); try { f(L, ud); @@ -160,6 +160,7 @@ l_noret luaD_throw(lua_State* L, int errcode) { if (nullptr != RbxStuOffsets::GetSingleton()->GetOffset("luaD_throw")) return reinterpret_cast(RbxStuOffsets::GetSingleton()->GetOffset("luaD_throw"))(L, errcode); + printf("Using RbxStu V2's luaD_throw!\n"); // throw lua_exception(L, errcode); } #endif diff --git a/Dependencies/Luau/VM/src/lgc.cpp b/Dependencies/Luau/VM/src/lgc.cpp index bee0cbd..627da32 100644 --- a/Dependencies/Luau/VM/src/lgc.cpp +++ b/Dependencies/Luau/VM/src/lgc.cpp @@ -306,6 +306,8 @@ static const char* gettablemode(global_State* g, Table* h) static int traversetable(global_State* g, Table* h) { + return 0; + int i; int weakkey = 0; int weakvalue = 0; @@ -700,6 +702,8 @@ static void markroot(lua_State* L) static size_t remarkupvals(global_State* g) { + return 0; + size_t work = 0; for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next) @@ -719,6 +723,8 @@ static size_t remarkupvals(global_State* g) static size_t clearupvals(lua_State* L) { + return 0; + global_State* g = L->global; size_t work = 0; @@ -753,6 +759,7 @@ static size_t clearupvals(lua_State* L) static size_t atomic(lua_State* L) { + return 0; global_State* g = L->global; LUAU_ASSERT(g->gcstate == GCSatomic); @@ -818,6 +825,8 @@ static size_t atomic(lua_State* L) // a version of generic luaM_visitpage specialized for the main sweep stage static int sweepgcopage(lua_State* L, lua_Page* page) { + return 0; + char* start; char* end; int busyBlocks; @@ -864,6 +873,8 @@ static int sweepgcopage(lua_State* L, lua_Page* page) static size_t gcstep(lua_State* L, size_t limit) { + return 0; + size_t cost = 0; global_State* g = L->global; switch (g->gcstate) @@ -961,6 +972,8 @@ static size_t gcstep(lua_State* L, size_t limit) static int64_t getheaptriggererroroffset(global_State* g) { + return 0; + // adjust for error using Proportional-Integral controller // https://en.wikipedia.org/wiki/PID_controller int32_t errorKb = int32_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.heapgoalsizebytes) / 1024); @@ -993,6 +1006,8 @@ static int64_t getheaptriggererroroffset(global_State* g) static size_t getheaptrigger(global_State* g, size_t heapgoal) { + return 0; + // adjust threshold based on a guess of how many bytes will be allocated between the cycle start and sweep phase // our goal is to begin the sweep when used memory has reached the heap goal const double durationthreshold = 1e-3; @@ -1016,7 +1031,8 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal) size_t luaC_step(lua_State* L, bool assist) { return 0; - //return reinterpret_cast(RbxStuOffsets::GetSingleton()->GetOffset("luaC_Step"))(L, assist); + printf("running luaC_step\n"); + // return reinterpret_cast(RbxStuOffsets::GetSingleton()->GetOffset("luaC_Step"))(L, assist); global_State* g = L->global; @@ -1080,6 +1096,7 @@ size_t luaC_step(lua_State* L, bool assist) void luaC_fullgc(lua_State* L) { + return; global_State* g = L->global; #ifdef LUAI_GCMETRICS diff --git a/Dependencies/Luau/VM/src/lvmexecute.cpp b/Dependencies/Luau/VM/src/lvmexecute.cpp index e7fef28..f46b7b9 100644 --- a/Dependencies/Luau/VM/src/lvmexecute.cpp +++ b/Dependencies/Luau/VM/src/lvmexecute.cpp @@ -3043,6 +3043,7 @@ void luau_execute(lua_State* L) { return reinterpret_cast(RbxStuOffsets::GetSingleton()->GetOffset("luau_execute"))(L); + printf("Utilizing RbxStu V2 luau_execute!\n"); if (L->singlestep) luau_execute(L); else diff --git a/Environment/EnvironmentManager.cpp b/Environment/EnvironmentManager.cpp index c584c4a..525e4ce 100644 --- a/Environment/EnvironmentManager.cpp +++ b/Environment/EnvironmentManager.cpp @@ -264,259 +264,311 @@ void EnvironmentManager::PushEnvironment(_In_ lua_State *L) { __index_game_original = __index->c.f; __index->c.f = [](lua_State *L) -> int { - if (lua_type(L, 2) != lua_Type::LUA_TSTRING) + if (lua_gettop(L) < 2 || lua_type(L, 1) != lua_Type::LUA_TUSERDATA || + lua_type(L, 2) != lua_Type::LUA_TSTRING || !Security::GetSingleton()->IsOurThread(L)) return __index_game_original(L); - auto index = lua_tostring(L, 2); + try { + auto index = luaL_optstring(L, 2, nullptr); - const auto loweredIndex = Utilities::ToLower(index); - for (const auto &func: blockedFunctions) { - if (loweredIndex.find(func) != std::string::npos) { - goto banned__index; + if (index == nullptr) + return __index_game_original(L); + + const auto loweredIndex = Utilities::ToLower(index); + for (const auto &func: blockedFunctions) { + if (loweredIndex.find(func) != std::string::npos) { + goto banned__index; + } } - } - for (const auto &func: blockedServices) { - if (loweredIndex.find(func) != std::string::npos) { - goto banned__index; + for (const auto &func: blockedServices) { + if (loweredIndex.find(func) != std::string::npos) { + goto banned__index; + } } - } - lua_getmetatable(L, 1); - lua_getfield(L, -1, "__type"); - - if (const auto s = lua_tostring(L, -1); - strcmp(s, "Instance") == 0 && Security::GetSingleton()->IsOurThread(L)) { - lua_pop(L, 3); - lua_pushstring(L, "ClassName"); - __index_game_original(L); - const auto instanceClassName = Utilities::ToLower(lua_tostring(L, -1)); - lua_pop(L, 2); - lua_pushstring(L, index); - - const auto indexAsString = std::string(index); - for (const auto &[bannedName, sound]: specificBlockage) { - if (Utilities::ToLower(bannedName).find(instanceClassName) != std::string::npos) { - for (const auto &func: sound) { - if (indexAsString.find(func) != std::string::npos && - strstr(indexAsString.c_str(), func.c_str()) == indexAsString.c_str()) { - goto banned__index; + lua_getmetatable(L, 1); + lua_getfield(L, -1, "__type"); + + if (const auto s = lua_tostring(L, -1); + strcmp(s, "Instance") == 0 && Security::GetSingleton()->IsOurThread(L)) { + lua_pop(L, 3); + lua_pushstring(L, "ClassName"); + __index_game_original(L); + const auto instanceClassName = Utilities::ToLower(lua_tostring(L, -1)); + lua_pop(L, 2); + lua_pushstring(L, index); + + const auto indexAsString = std::string(index); + for (const auto &[bannedName, sound]: specificBlockage) { + if (Utilities::ToLower(bannedName).find(instanceClassName) != std::string::npos) { + for (const auto &func: sound) { + if (indexAsString.find(func) != std::string::npos && + strstr(indexAsString.c_str(), func.c_str()) == indexAsString.c_str()) { + goto banned__index; + } + if (func == "BLOCK_ALL" && strcmp(loweredIndex.c_str(), "classname") != 0 && + strcmp(loweredIndex.c_str(), "name") != 0) + goto banned__index; // Block all regardless. } - if (func == "BLOCK_ALL" && strcmp(loweredIndex.c_str(), "classname") != 0 && - strcmp(loweredIndex.c_str(), "name") != 0) - goto banned__index; // Block all regardless. } } + } else { + lua_pop(L, 2); } - } else { - lua_pop(L, 2); - } + if (index == nullptr) + return __index_game_original(L); - if (!Communication::GetSingleton()->CanAccessScriptSource() && strcmp(index, "Source") == 0) { - Utilities::checkInstance(L, 1, "LuaSourceContainer"); - lua_pushstring(L, ""); - return 1; - } + if (!Communication::GetSingleton()->CanAccessScriptSource() && strcmp(index, "Source") == 0) { + Utilities::checkInstance(L, 1, "LuaSourceContainer"); + lua_pushstring(L, ""); + return 1; + } - if (loweredIndex.find("getservice") != std::string::npos || - loweredIndex.find("findservice") != std::string::npos || - strstr(index, "service") != - nullptr) { // manages getservice, GetService, FindService and service all at once. - lua_pushcclosure( - L, - [](lua_State *L) -> int { - luaL_checktype(L, 1, lua_Type::LUA_TUSERDATA); - auto dataModel = lua_topointer(L, 1); - auto serviceName = luaL_checkstring(L, 2); - const auto svcName = Utilities::ToLower(serviceName); - for (const auto &func: blockedServices) { - if (svcName.find(func) != std::string::npos) { + if (loweredIndex.find("getservice") != std::string::npos || + loweredIndex.find("findservice") != std::string::npos || + strstr(index, "service") != + nullptr) { // manages getservice, GetService, FindService and service all at once. + lua_pushcclosure( + L, + [](lua_State *L) -> int { + luaL_checktype(L, 1, lua_Type::LUA_TUSERDATA); + auto dataModel = lua_topointer(L, 1); + auto serviceName = luaL_checkstring(L, 2); + const auto svcName = Utilities::ToLower(serviceName); + for (const auto &func: blockedServices) { + if (svcName.find(func) != std::string::npos) { + Logger::GetSingleton()->PrintWarning( + RbxStu::Anonymous, + std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " + "BLACKLISTED SERVICE! SERVICE ACCESSED: {}", + serviceName)); + + if (Communication::GetSingleton()->IsUnsafeMode()) + break; + + luaL_errorL(L, "This service has been blocked for security"); + } + } + lua_pop(L, 1); + L->top->value.p = L->ci->func->value.p; + L->top->tt = lua_Type::LUA_TFUNCTION; + L->top++; + lua_getupvalue(L, -1, 1); + lua_remove(L, 2); + __index_game_original(L); + lua_pushvalue(L, 1); + lua_pushstring(L, serviceName); + try { + lua_pcall(L, 2, 1, 0); + } catch (const std::exception &ex) { Logger::GetSingleton()->PrintWarning( - RbxStu::Anonymous, std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " - "BLACKLISTED SERVICE! SERVICE ACCESSED: {}", - serviceName)); - - if (Communication::GetSingleton()->IsUnsafeMode()) - break; - - luaL_errorL(L, "This service has been blocked for security"); + RbxStu::Anonymous, + std::format( + "RBX::CRASH! AN IRRECOVERABLE ERROR HAS OCCURRED THAT SHOULD NOT " + "HAVE OCCURRED! REDIRECTING CALL!")); + lua_pushstring(L, "fatal error while trying to call DataModel.GetService()"); + lua_error(L); + return 1; } - } - lua_pop(L, 1); - L->top->value.p = L->ci->func->value.p; - L->top->tt = lua_Type::LUA_TFUNCTION; - L->top++; - lua_getupvalue(L, -1, 1); - lua_remove(L, 2); - __index_game_original(L); - lua_pushvalue(L, 1); - lua_pushstring(L, serviceName); - lua_pcall(L, 2, 1, 0); - return 1; - }, - nullptr, 1); - const auto cl = lua_toclosure(L, -1); - lua_pushstring(L, index); - lua_setupvalue(L, -2, 1); - return 1; - } - - if (!Security::GetSingleton()->IsOurThread(L) || index == nullptr) - return __index_game_original(L); + return 1; + }, + nullptr, 1); + const auto cl = lua_toclosure(L, -1); + lua_pushstring(L, index); + lua_setupvalue(L, -2, 1); + return 1; + } - if (loweredIndex.find("httpget") != std::string::npos || - loweredIndex.find("httpgetasync") != std::string::npos) { - lua_getglobal(L, "httpget"); - return 1; - } + if (loweredIndex.find("httpget") != std::string::npos || + loweredIndex.find("httpgetasync") != std::string::npos) { + lua_getglobal(L, "httpget"); + return 1; + } - if (loweredIndex.find("getobjects") != std::string::npos) { - lua_getglobal(L, "GetObjects"); - return 1; - } + if (loweredIndex.find("getobjects") != std::string::npos) { + lua_getglobal(L, "GetObjects"); + return 1; + } - if (false) { - banned__index: - Logger::GetSingleton()->PrintWarning(RbxStu::Anonymous, - std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " - "BLACKLISTED FUNCTION/SERVICE! FUNCTION ACCESSED: {}", - index)); - if (Communication::GetSingleton()->IsUnsafeMode()) - return __index_game_original(L); + if (false) { + banned__index: + Logger::GetSingleton()->PrintWarning( + RbxStu::Anonymous, std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " + "BLACKLISTED FUNCTION/SERVICE! FUNCTION ACCESSED: {}", + index)); + if (Communication::GetSingleton()->IsUnsafeMode()) + return __index_game_original(L); - luaL_errorL(L, "This service/function has been blocked for security"); + luaL_errorL(L, "This service/function has been blocked for security"); + } + } catch (const std::exception &ex) { + // Logger::GetSingleton()->PrintWarning( + // RbxStu::Anonymous, std::format("FATAL ERROR CAUGHT ON DATAMODEL::__INDEX -> {}", ex.what())); + } catch (...) { + // Logger::GetSingleton()->PrintWarning(RbxStu::Anonymous, + // "FATAL ERROR CAUGHT ON DATAMODEL::__INDEX -> UNKNOWN"); } - + if (lua_gettop(L) > 2 && + lua_type(L, -1) == lua_Type::LUA_TSTRING) // The lua stack should only have self and the index. + lua_error(L); return __index_game_original(L); }; lua_pop(L, 1); lua_rawgetfield(L, -1, "__namecall"); + auto __namecall = lua_toclosure(L, -1); __namecall_game_original = __namecall->c.f; __namecall->c.f = [](lua_State *L) -> int { - auto namecall = L->namecall->data; + auto lastStackElement = luaA_toobject(L, -1); - const auto loweredNamecall = Utilities::ToLower(namecall); - for (const auto &func: blockedFunctions) { - if (loweredNamecall.find(func) != std::string::npos) { - goto banned__namecall; + try { + if (!Security::GetSingleton()->IsOurThread(L) || lua_gettop(L) == 0 || + lua_type(L, 1) != lua_Type::LUA_TUSERDATA) + return __namecall_game_original(L); + + auto namecall = lua_namecallatom(L, nullptr); + + if (namecall == nullptr) + return __namecall_game_original(L); + + const auto stack = lua_gettop(L); + + const auto loweredNamecall = Utilities::ToLower(namecall); + for (const auto &func: blockedFunctions) { + if (loweredNamecall.find(func) != std::string::npos) { + goto banned__namecall; + } } - } - for (const auto &func: blockedServices) { - if (loweredNamecall.find(func) != std::string::npos) { - goto banned__namecall; + for (const auto &func: blockedServices) { + if (loweredNamecall.find(func) != std::string::npos) { + goto banned__namecall; + } } - } - lua_getmetatable(L, 1); - lua_getfield(L, -1, "__type"); + lua_getmetatable(L, 1); + lua_getfield(L, -1, "__type"); - if (const auto s = lua_tostring(L, -1); strcmp(s, "Instance") == 0) { - lua_pushvalue(L, 1); - lua_getfield(L, -1, "ClassName"); - const auto instanceClassName = Utilities::ToLower(lua_tostring(L, -1)); - lua_pop(L, 4); + if (const auto s = lua_tostring(L, -1); strcmp(s, "Instance") == 0) { + lua_pushvalue(L, 1); + lua_getfield(L, -1, "ClassName"); + const auto instanceClassName = Utilities::ToLower(lua_tostring(L, -1)); + lua_settop(L, stack); - const auto namecallAsString = std::string(namecall); + const auto namecallAsString = std::string(namecall); - for (const auto &[bannedName, sound]: specificBlockage) { - if (Utilities::ToLower(bannedName).find(instanceClassName) != std::string::npos) { - for (const auto &func: sound) { - if (namecallAsString.find(func) != std::string::npos) { - goto banned__namecall; + for (const auto &[bannedName, sound]: specificBlockage) { + if (Utilities::ToLower(bannedName).find(instanceClassName) != std::string::npos) { + for (const auto &func: sound) { + if (namecallAsString.find(func) != std::string::npos) { + goto banned__namecall; + } + if (func == "BLOCK_ALL") + goto banned__namecall; // Block all regardless. } - if (func == "BLOCK_ALL") - goto banned__namecall; // Block all regardless. } } + } else { + lua_settop(L, stack); } - } else { - lua_pop(L, 2); - } - - if (false) { - banned__namecall: - Logger::GetSingleton()->PrintWarning(RbxStu::Anonymous, - std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " - "BLACKLISTED FUNCTION/SERVICE! FUNCTION ACCESSED: {}", - namecall)); - if (Communication::GetSingleton()->IsUnsafeMode()) - return __namecall_game_original(L); - - luaL_errorL(L, "This service/function has been blocked for security"); - } - if (!Security::GetSingleton()->IsOurThread(L) || namecall == nullptr) - return __namecall_game_original(L); + if (false) { + banned__namecall: + Logger::GetSingleton()->PrintWarning( + RbxStu::Anonymous, std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " + "BLACKLISTED FUNCTION/SERVICE! FUNCTION ACCESSED: {}", + namecall)); + if (Communication::GetSingleton()->IsUnsafeMode()) + return __namecall_game_original(L); - if (loweredNamecall.find("getservice") != std::string::npos || - loweredNamecall.find("findservice") != std::string::npos || strcmp(namecall, "service") == 0) { - // getservice / findservice - auto serviceName = luaL_checkstring(L, 2); - const auto svcName = Utilities::ToLower(serviceName); - for (const auto &func: blockedServices) { - if (svcName.find(func) != std::string::npos) { - Logger::GetSingleton()->PrintWarning(RbxStu::Anonymous, - std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " - "BLACKLISTED SERVICE! SERVICE ACCESSED: {}", - serviceName)); - - if (Communication::GetSingleton()->IsUnsafeMode()) - break; + luaL_errorL(L, "This service/function has been blocked for security"); + } - luaL_errorL(L, "This service has been blocked for security"); + if (loweredNamecall.find("getservice") != std::string::npos || + loweredNamecall.find("findservice") != std::string::npos || + loweredNamecall.find("service") != std::string::npos) { + // getservice / findservice / service + if (lua_type(L, 2) != lua_Type::LUA_TSTRING) + return __namecall_game_original(L); + + auto serviceName = luaL_checkstring(L, 2); + const auto svcName = Utilities::ToLower(serviceName); + for (const auto &func: blockedServices) { + if (svcName.find(func) != std::string::npos) { + Logger::GetSingleton()->PrintWarning( + RbxStu::Anonymous, std::format("WARNING! AN ELEVATED THREAD HAS ACCESSED A " + "BLACKLISTED SERVICE! SERVICE ACCESSED: {}", + serviceName)); + + if (Communication::GetSingleton()->IsUnsafeMode()) + break; + + luaL_errorL(L, "This service has been blocked for security"); + } } + return __namecall_game_original(L); } - __namecall_game_original(L); - return 1; - } + if (loweredNamecall.find("httpget") != std::string::npos || + loweredNamecall.find("httpgetasync") != std::string::npos) { + if (lua_type(L, 2) != lua_Type::LUA_TSTRING) + return __namecall_game_original(L); + luaL_checkstring(L, 2); + lua_getglobal(L, "httpget"); + lua_pushvalue(L, 2); + const auto err = lua_pcall(L, 1, 1, 0); + if (lua_type(L, -1) == lua_Type::LUA_TSTRING && + strcmp(lua_tostring(L, -1), "attempt to yield across metamethod/C-call boundary") == 0) + return lua_yield(L, 1); + + if (err == LUA_ERRRUN || err == LUA_ERRMEM || err == LUA_ERRERR) + lua_error(L); + + return 1; + } - if (loweredNamecall.find("httpget") != std::string::npos || - loweredNamecall.find("httpgetasync") != std::string::npos) { - luaL_checkstring(L, 2); - lua_getglobal(L, "httpget"); - lua_pushvalue(L, 2); - const auto err = lua_pcall(L, 1, 1, 0); - if (lua_type(L, -1) == lua_Type::LUA_TSTRING && - strcmp(lua_tostring(L, -1), "attempt to yield across metamethod/C-call boundary") == 0) - return lua_yield(L, 1); + if (loweredNamecall.find("getobjects") != std::string::npos) { + lua_getglobal(L, "GetObjects"); + lua_pushvalue(L, 2); + const auto err = lua_pcall(L, 1, 1, 0); + if (lua_type(L, -1) == lua_Type::LUA_TSTRING && + strcmp(lua_tostring(L, -1), "attempt to yield across metamethod/C-call boundary") == 0) + return lua_yield(L, 1); + if (err == LUA_ERRRUN || err == LUA_ERRMEM || err == LUA_ERRERR) + lua_error(L); - if (err == LUA_ERRRUN || err == LUA_ERRMEM || err == LUA_ERRERR) - lua_error(L); + if (err == LUA_YIELD) + return lua_yield(L, 1); - return 1; + return 1; + } + } catch (const std::exception &ex) { + // Logger::GetSingleton()->PrintWarning( + // RbxStu::Anonymous, std::format("FATAL ERROR CAUGHT ON DATAMODEL::__NAMECALL -> {}", + // ex.what())); + } catch (...) { + // Logger::GetSingleton()->PrintWarning(RbxStu::Anonymous, + // "FATAL ERROR CAUGHT ON DATAMODEL::__NAMECALL -> UNKNOWN"); } - if (loweredNamecall.find("getobjects") != std::string::npos) { - lua_getglobal(L, "GetObjects"); - lua_pushvalue(L, 2); - const auto err = lua_pcall(L, 1, 1, 0); - if (lua_type(L, -1) == lua_Type::LUA_TSTRING && - strcmp(lua_tostring(L, -1), "attempt to yield across metamethod/C-call boundary") == 0) - return lua_yield(L, 1); - if (err == LUA_ERRRUN || err == LUA_ERRMEM || err == LUA_ERRERR) - lua_error(L); - - if (err == LUA_YIELD) - return lua_yield(L, 1); - - return 1; + if (L->top - 1 != lastStackElement && lua_type(L, -1) == lua_Type::LUA_TSTRING) { + lua_error(L); } return __namecall_game_original(L); }; - lua_pop(L, lua_gettop(L)); + lua_settop(L, 0); } catch (const std::exception &ex) { logger->PrintError(RbxStu::EnvironmentManager, std::format("Failed to initialize RbxStu Environment. Error from Lua: {}", ex.what())); } + Scheduler::GetSingleton()->ScheduleJob(SchedulerJob( R"( local insertservice_LoadLocalAsset = clonefunction(cloneref(game.GetService(game, "InsertService")).LoadLocalAsset) diff --git a/LuauManager.cpp b/LuauManager.cpp index ea79bba..9e8860e 100644 --- a/LuauManager.cpp +++ b/LuauManager.cpp @@ -75,22 +75,28 @@ namespace RbxStu { static void luau__freeblock(lua_State *L, uint32_t sizeClass, void *block) { // This has been fixed, the pointer check no longer needs to exist. - // if (reinterpret_cast(block) > 0x00007FF000000000) { - // Logger::GetSingleton()->PrintWarning( - // RbxStu::HookedFunction, - // std::format("Suspicious address caught (non-heap range): {}. Deallocation blocked!", block)); - // return; - // } - // if (!Utilities::IsPointerValid(static_cast(block)) || - // !Utilities::IsPointerValid(reinterpret_cast(reinterpret_cast(block) - 8)) - // || !Utilities::IsPointerValid(*reinterpret_cast(reinterpret_cast(block) - - // 8))) { Logger::GetSingleton()->PrintWarning( - // RbxStu::HookedFunction, std::format("Suspicious address caught: {}. Deallocation blocked!", block)); - // return; - // } + if (reinterpret_cast(block) > 0x00007FF000000000) { + Logger::GetSingleton()->PrintWarning( + RbxStu::HookedFunction, + std::format("Suspicious address caught (non-heap range): {}. Deallocation blocked!", block)); + return; + } + if (!Utilities::IsPointerValid(static_cast(block)) || + !Utilities::IsPointerValid(reinterpret_cast(reinterpret_cast(block) - 8)) || + !Utilities::IsPointerValid(*reinterpret_cast(reinterpret_cast(block) - 8))) { + Logger::GetSingleton()->PrintWarning( + RbxStu::HookedFunction, std::format("Suspicious address caught: {}. Deallocation blocked!", block)); + return; + } - return (reinterpret_cast( - LuauManager::GetSingleton()->GetHookOriginal("freeblock"))(L, sizeClass, block)); + try { + return (reinterpret_cast( + LuauManager::GetSingleton()->GetHookOriginal("freeblock"))(L, sizeClass, block)); + } catch (const std::exception &ex) { + Logger::GetSingleton()->PrintWarning( + RbxStu::HookedFunction, + std::format("freeblock invoke failed with block: {}; exception caught -> {}", block, ex.what())); + } } static std::shared_mutex __luaumanager__singletonmutex; @@ -230,8 +236,8 @@ void LuauManager::Initialize() { // logger->PrintInformation(RbxStu::LuauManager, "- Installing pointer check hook into freeblock..."); // this->m_mapHookMap["freeblock"] = new void *(); - //// Error checking, because Dottik didn't add it. - //// - MakeSureDudeDies + // Error checking, because Dottik didn't add it. + // - MakeSureDudeDies // if (MH_CreateHook(this->m_mapLuauFunctions["freeblock"], luau__freeblock, &this->m_mapHookMap["freeblock"]) != // MH_OK) { // logger->PrintError(RbxStu::LuauManager, "Failed to create freeblock hook!"); diff --git a/Scheduler.cpp b/Scheduler.cpp index fddf0ed..33601e6 100644 --- a/Scheduler.cpp +++ b/Scheduler.cpp @@ -35,8 +35,19 @@ SchedulerJob Scheduler::GetSchedulerJob(bool pop) { if (this->m_qSchedulerJobs.empty()) return SchedulerJob(""); auto job = this->m_qSchedulerJobs.front(); - if (pop) + + // Due to yielding jobs possibly depending on others, we must leave yielding jobs to be re-scheduled to be last + // executed if they were not ready last tick! This allows us to keep multiple yielding jobs and they resuming + // without any deadlocks! + + if (pop) { + this->m_qSchedulerJobs.pop(); + } else if (job.bIsYieldingJob && !job.IsJobCompleted()) { + // Logger::GetSingleton()->PrintWarning(RbxStu::Scheduler, "Re-Organizing yielding job..."); this->m_qSchedulerJobs.pop(); + this->m_qSchedulerJobs.push(job); + } + return job; } @@ -277,7 +288,7 @@ void Scheduler::InitializeWith(lua_State *L, lua_State *rL, RBX::DataModel *data opts.debugLevel = 2; opts.optimizationLevel = 0; const auto bytecode = - Luau::compile(std::format("print(\"RbxStu V2: Scheduler initialized!\"); return game:GetService(\"RunService\").Heartbeat:Connect({})", + Luau::compile(std::format("print\"hi\"; return game:GetService(\"RunService\").Heartbeat:Connect({})", std::format("scheduler_", reinterpret_cast(L))), opts); diff --git a/Utilities.cpp b/Utilities.cpp index a9f8583..66029d8 100644 --- a/Utilities.cpp +++ b/Utilities.cpp @@ -3,3 +3,20 @@ // #include "Utilities.hpp" + +void Utilities::Initialize() { + try { + RbxStu::Regexes::LUA_ERROR_CALLSTACK_REGEX = + std::regex(R"(.*"\]:(\d)*: )", std::regex::optimize | std::regex::icase); + } catch (const std::exception &ex) { + Logger::GetSingleton()->PrintError(RbxStu::Anonymous, std::format("wow: {}", ex.what())); + } +} + +std::string Utilities::StripLuaErrorMessage(const std::string &message) { + if (std::regex_search(message.begin(), message.end(), RbxStu::Regexes::LUA_ERROR_CALLSTACK_REGEX)) { + const auto fixed = std::regex_replace(message, RbxStu::Regexes::LUA_ERROR_CALLSTACK_REGEX, ""); + return fixed; + } + return message; +} diff --git a/Utilities.hpp b/Utilities.hpp index 8f2a57f..eb6f7e3 100644 --- a/Utilities.hpp +++ b/Utilities.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,12 @@ #include "Logger.hpp" #include "lualib.h" +namespace RbxStu { + namespace Regexes { + static std::regex LUA_ERROR_CALLSTACK_REGEX; + } +} // namespace RbxStu + class Utilities final { struct ThreadInformation { bool bWasSuspended; @@ -100,6 +107,9 @@ class Utilities final { this->state = RESUMED; } }; + + static void Initialize(); + __forceinline static std::pair getInstanceType(lua_State *L, int index) { luaL_checktype(L, index, LUA_TUSERDATA); @@ -121,6 +131,8 @@ class Utilities final { return {true, className}; } + static std::string StripLuaErrorMessage(const std::string &message); + __forceinline static void checkInstance(lua_State *L, int index, const char *expectedClassname) { luaL_checktype(L, index, LUA_TUSERDATA); diff --git a/main.cpp b/main.cpp index c9149b2..2f6adc2 100644 --- a/main.cpp +++ b/main.cpp @@ -47,7 +47,7 @@ long exception_filter(PEXCEPTION_POINTERS pExceptionPointers) { } - printf("Thread RI P @ %p\r\n", reinterpret_cast(pExceptionPointers->ContextRecord->Rip)); + printf("Thread RIP @ %p\r\n", reinterpret_cast(pExceptionPointers->ContextRecord->Rip)); printf("Module.dll @ %p\r\n", reinterpret_cast(GetModuleHandleA("Module.dll"))); printf("Rebased Module @ %p\r\n", reinterpret_cast(pExceptionPointers->ContextRecord->Rip - @@ -129,7 +129,7 @@ long exception_filter(PEXCEPTION_POINTERS pExceptionPointers) { printf("Stack frames captured - Waiting for 30s before handing to executee... \r\n"); Sleep(30000); - return EXCEPTION_EXECUTE_HANDLER; + return EXCEPTION_CONTINUE_EXECUTION; } int main() { @@ -155,6 +155,11 @@ int main() { logger->PrintInformation(RbxStu::MainThread, std::format("-- RbxStu Base: {}", static_cast(GetModuleHandle("Module.dll")))); SetConsoleTitleA("-- RbxStu V2 --"); + + logger->PrintInformation(RbxStu::MainThread, "-- Initializing Internal Utilities..."); + Utilities::Initialize(); + logger->PrintInformation(RbxStu::MainThread, "-- Initialized."); + logger->PrintInformation(RbxStu::MainThread, "-- Initializing ModManager..."); auto modManager = ModManager::GetSingleton(); modManager->LoadMods();