diff --git a/PolygonClientUtilities/Config.h b/PolygonClientUtilities/Config.h index 42e0fa7..7c90197 100644 --- a/PolygonClientUtilities/Config.h +++ b/PolygonClientUtilities/Config.h @@ -1,11 +1,12 @@ #pragma once -#define MFC2011 +#define MFC2010 #define ARBITERBUILD // RobloxApp (2010) #ifdef MFC2010 #define ADDRESS_STANDARDOUT__PRINT 0x0059F340 +#define ADDRESS_HTTP__TRUSTCHECK 0x005A2680 #define ADDRESS_CAPP__CREATEGAME 0x00405D20 #define ADDRESS_CAPP__ROBLOXAUTHENTICATE 0x00408060 #define ADDRESS_CROBLOXAPP__INITINSTANCE 0x00452900 diff --git a/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk b/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk index 91fccf3..56220e6 100644 Binary files a/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk and b/PolygonClientUtilities/Debug/PolygonClientUtilities.ilk differ diff --git a/PolygonClientUtilities/LUrlParser.cpp b/PolygonClientUtilities/LUrlParser.cpp new file mode 100644 index 0000000..f8bb998 --- /dev/null +++ b/PolygonClientUtilities/LUrlParser.cpp @@ -0,0 +1,268 @@ +/* + * Lightweight URL & URI parser (RFC 1738, RFC 3986) + * https://github.com/corporateshark/LUrlParser + * + * The MIT License (MIT) + * + * Copyright (C) 2015-2020 Sergey Kosarevsky (sk@linderdaum.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "LUrlParser.h" + +#include +#include +#include + +namespace +{ + // check if the scheme name is valid + bool isSchemeValid(const std::string& schemeName) + { + for (auto c : schemeName) + { + if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false; + } + + return true; + } +} + +bool LUrlParser::ParseURL::getPort(int* outPort) const +{ + if (!isValid()) { return false; } + + const int port = atoi(port_.c_str()); + + if (port <= 0 || port > 65535) { return false; } + + if (outPort) { *outPort = port; } + + return true; +} + +// based on RFC 1738 and RFC 3986 +LUrlParser::ParseURL LUrlParser::ParseURL::parseURL(const std::string& URL) +{ + LUrlParser::ParseURL result; + + const char* currentString = URL.c_str(); + + /* + * : + * := [a-z\+\-\.]+ + * For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names + */ + + // try to read scheme + { + const char* localString = strchr(currentString, ':'); + + if (!localString) + { + return ParseURL(LUrlParserError_NoUrlCharacter); + } + + // save the scheme name + result.scheme_ = std::string(currentString, localString - currentString); + + if (!isSchemeValid(result.scheme_)) + { + return ParseURL(LUrlParserError_InvalidSchemeName); + } + + // scheme should be lowercase + std::transform(result.scheme_.begin(), result.scheme_.end(), result.scheme_.begin(), ::tolower); + + // skip ':' + currentString = localString + 1; + } + + /* + * //:@:/ + * any ":", "@" and "/" must be normalized + */ + + // skip "//" + if (*currentString++ != '/') return ParseURL(LUrlParserError_NoDoubleSlash); + if (*currentString++ != '/') return ParseURL(LUrlParserError_NoDoubleSlash); + + // check if the user name and password are specified + bool bHasUserName = false; + + const char* localString = currentString; + + while (*localString) + { + if (*localString == '@') + { + // user name and password are specified + bHasUserName = true; + break; + } + else if (*localString == '/') + { + // end of : specification + bHasUserName = false; + break; + } + + localString++; + } + + // user name and password + localString = currentString; + + if (bHasUserName) + { + // read user name + while (*localString && *localString != ':' && *localString != '@') localString++; + + result.userName_ = std::string(currentString, localString - currentString); + + // proceed with the current pointer + currentString = localString; + + if (*currentString == ':') + { + // skip ':' + currentString++; + + // read password + localString = currentString; + + while (*localString && *localString != '@') localString++; + + result.password_ = std::string(currentString, localString - currentString); + + currentString = localString; + } + + // skip '@' + if (*currentString != '@') + { + return ParseURL(LUrlParserError_NoAtSign); + } + + currentString++; + } + + const bool bHasBracket = (*currentString == '['); + + // go ahead, read the host name + localString = currentString; + + while (*localString) + { + if (bHasBracket && *localString == ']') + { + // end of IPv6 address + localString++; + break; + } + else if (!bHasBracket && (*localString == ':' || *localString == '/')) + { + // port number is specified + break; + } + + localString++; + } + + result.host_ = std::string(currentString, localString - currentString); + + currentString = localString; + + // is port number specified? + if (*currentString == ':') + { + currentString++; + + // read port number + localString = currentString; + + while (*localString && *localString != '/') localString++; + + result.port_ = std::string(currentString, localString - currentString); + + currentString = localString; + } + + // end of string + if (!*currentString) + { + result.errorCode_ = LUrlParserError_Ok; + + return result; + } + + // skip '/' + if (*currentString != '/') + { + return ParseURL(LUrlParserError_NoSlash); + } + + currentString++; + + // parse the path + localString = currentString; + + while (*localString && *localString != '#' && *localString != '?') localString++; + + result.path_ = std::string(currentString, localString - currentString); + + currentString = localString; + + // check for query + if (*currentString == '?') + { + // skip '?' + currentString++; + + // read query + localString = currentString; + + while (*localString&&* localString != '#') localString++; + + result.query_ = std::string(currentString, localString - currentString); + + currentString = localString; + } + + // check for fragment + if (*currentString == '#') + { + // skip '#' + currentString++; + + // read fragment + localString = currentString; + + while (*localString) localString++; + + result.fragment_ = std::string(currentString, localString - currentString); + + currentString = localString; + } + + result.errorCode_ = LUrlParserError_Ok; + + return result; +} diff --git a/PolygonClientUtilities/LUrlParser.h b/PolygonClientUtilities/LUrlParser.h new file mode 100644 index 0000000..f434092 --- /dev/null +++ b/PolygonClientUtilities/LUrlParser.h @@ -0,0 +1,74 @@ +/* + * Lightweight URL & URI parser (RFC 1738, RFC 3986) + * https://github.com/corporateshark/LUrlParser + * + * The MIT License (MIT) + * + * Copyright (C) 2015-2020 Sergey Kosarevsky (sk@linderdaum.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include + +namespace LUrlParser +{ + enum LUrlParserError + { + LUrlParserError_Ok = 0, + LUrlParserError_Uninitialized = 1, + LUrlParserError_NoUrlCharacter = 2, + LUrlParserError_InvalidSchemeName = 3, + LUrlParserError_NoDoubleSlash = 4, + LUrlParserError_NoAtSign = 5, + LUrlParserError_UnexpectedEndOfLine = 6, + LUrlParserError_NoSlash = 7, + }; + + class ParseURL + { + public: + LUrlParserError errorCode_ = LUrlParserError_Uninitialized; + std::string scheme_; + std::string host_; + std::string port_; + std::string path_; + std::string query_; + std::string fragment_; + std::string userName_; + std::string password_; + + /// return 'true' if the parsing was successful + bool isValid() const { return errorCode_ == LUrlParserError_Ok; } + + /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range) + bool getPort(int* outPort) const; + + /// parse the URL + static ParseURL parseURL(const std::string& url); + + private: + ParseURL() = default; + explicit ParseURL(LUrlParserError errorCode) + : errorCode_(errorCode) + {} + }; +} // namespace LUrlParser diff --git a/PolygonClientUtilities/PolygonClientUtilities.vcxproj b/PolygonClientUtilities/PolygonClientUtilities.vcxproj index 32aca89..88f7fe7 100644 --- a/PolygonClientUtilities/PolygonClientUtilities.vcxproj +++ b/PolygonClientUtilities/PolygonClientUtilities.vcxproj @@ -158,6 +158,7 @@ + @@ -165,6 +166,10 @@ + + NotUsing + NotUsing + Create diff --git a/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters b/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters index d7cac03..8d978f6 100644 --- a/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters +++ b/PolygonClientUtilities/PolygonClientUtilities.vcxproj.filters @@ -30,6 +30,9 @@ Header Files + + Header Files + @@ -44,5 +47,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/PolygonClientUtilities/RobloxMFCClasses.h b/PolygonClientUtilities/RobloxMFCClasses.h index 0e8cba6..efa6893 100644 --- a/PolygonClientUtilities/RobloxMFCClasses.h +++ b/PolygonClientUtilities/RobloxMFCClasses.h @@ -1,40 +1,40 @@ #pragma once #include "Config.h" + #include class CWorkspace; +// 2010 struct definitions: +// 0x47E010: CWorkspace->DoExecScript() +// 0x47EC10: CWorkspace->ExecUrlScript() + const auto CWorkspace__ExecUrlScript = (HRESULT(__stdcall*)(CWorkspace * workspace, LPCWSTR, VARIANTARG, VARIANTARG, VARIANTARG, VARIANTARG, LPVOID))ADDRESS_CWORKSPACE__EXECURLSCRIPT; -class CRobloxDoc +struct CRobloxDoc { -private: void* padding1[40]; -public: CWorkspace* workspace; }; -class CApp; +struct CApp; const auto CApp__CreateGame = (CWorkspace * (__thiscall*)(CApp * _this, LPCWSTR, LPCWSTR))ADDRESS_CAPP__CREATEGAME; const auto CApp__RobloxAuthenticate = (void * (__thiscall*)(CApp * _this, LPCWSTR, LPCWSTR))ADDRESS_CAPP__ROBLOXAUTHENTICATE; -class CRobloxApp -{ -private: - void* padding1[124]; -public: - CApp* app; -}; +struct CRobloxApp; + +// 2010 struct definitions: +// 0x405D20: CRobloxApp->CreateDocument() +// 0x44F6F0: CRobloxApp->ExitInstance() +// 0x452900: CRobloxApp->InitInstance() const auto CRobloxApp__CreateDocument = (CRobloxDoc * (__thiscall*)(CRobloxApp * _this))ADDRESS_CROBLOXAPP__CREATEDOCUMENT; -class CCommandLineInfo +struct CCommandLineInfo { -private: void* padding1[3]; -public: BOOL m_bRunAutomated; }; diff --git a/PolygonClientUtilities/RobloxMFCHooks.cpp b/PolygonClientUtilities/RobloxMFCHooks.cpp index ddc3a49..abb50d5 100644 --- a/PolygonClientUtilities/RobloxMFCHooks.cpp +++ b/PolygonClientUtilities/RobloxMFCHooks.cpp @@ -1,9 +1,11 @@ #include "pch.h" #include "Config.h" #include "RobloxMFCHooks.h" +#include "LUrlParser.h" static HANDLE handle; -static std::ofstream jobLog; +static std::ofstream jobOutputLog; +static std::ofstream jobHttpLog; static bool hasAuthUrlArg = false; static bool hasAuthTicketArg = false; @@ -24,7 +26,7 @@ BOOL __fastcall CRobloxApp__InitInstance_hook(CRobloxApp* _this) if (hasAuthUrlArg && hasAuthTicketArg && !authenticationUrl.empty() && !authenticationTicket.empty()) { - // TODO: implement this + // TODO: implement this using CApp__RobloxAuthenticate } if (hasJoinArg && !joinScriptUrl.empty()) @@ -33,7 +35,7 @@ BOOL __fastcall CRobloxApp__InitInstance_hook(CRobloxApp* _this) { // TODO: use CApp__CreateGame instead CRobloxDoc* document = CRobloxApp__CreateDocument(_this); - // CWorkspace__ExecUrlScript(document->workspace, joinScriptUrl.c_str(), VARIANTARG(), VARIANTARG(), VARIANTARG(), VARIANTARG(), nullptr); + CWorkspace__ExecUrlScript(document->workspace, joinScriptUrl.c_str(), VARIANTARG(), VARIANTARG(), VARIANTARG(), VARIANTARG(), nullptr); // CApp__CreateGame(NULL, L"", L"44340105256"); // CApp__RobloxAuthenticate(_this->app, L"http://polygondev.pizzaboxer.xyz/", L"test"); @@ -89,7 +91,8 @@ void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* if (hasJobId && jobId.empty()) { jobId = std::string(pszParam); - jobLog = std::ofstream(jobId + std::string(".txt")); + jobOutputLog = std::ofstream(jobId + std::string("-Output.txt")); + jobHttpLog = std::ofstream(jobId + std::string("-Http.txt")); AllocConsole(); freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); @@ -134,6 +137,57 @@ void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* CRobloxCommandLineInfo__ParseParam(_this, pszParam, bFlag, bLast); } +Http__trustCheck_t Http__trustCheck = (Http__trustCheck_t)ADDRESS_HTTP__TRUSTCHECK; + +BOOL __fastcall Http__trustCheck_hook(const char* url) +{ + const std::vector allowedHosts + { + "polygon.pizzaboxer.xyz", + "polygondev.pizzaboxer.xyz", + "polygonapi.pizzaboxer.xyz", + + "roblox.com", + "www.roblox.com", + "assetdelivery.roblox.com", + + "tadah.rocks", + "www.tadah.rocks" + }; + + const std::vector allowedSchemes + { + "http", + "https", + "ftp", + }; + + const std::vector allowedEmbeddedSchemes + { + "javascript", + "jscript", + "res", + }; + + LUrlParser::ParseURL parsedUrl = LUrlParser::ParseURL::parseURL(url); + + if (!parsedUrl.isValid()) + return false; + + jobHttpLog << url << std::endl; + + if (std::string("about:blank") == url) + return true; + + if (std::find(allowedSchemes.begin(), allowedSchemes.end(), parsedUrl.scheme_) != allowedSchemes.end()) + return std::find(allowedHosts.begin(), allowedHosts.end(), parsedUrl.host_) != allowedHosts.end(); + + if (std::find(allowedEmbeddedSchemes.begin(), allowedEmbeddedSchemes.end(), parsedUrl.scheme_) != allowedEmbeddedSchemes.end()) + return true; + + return false; +} + #ifdef ARBITERBUILD StandardOut__print_t StandardOut__print = (StandardOut__print_t)ADDRESS_STANDARDOUT__PRINT; @@ -142,19 +196,19 @@ void __fastcall StandardOut__print_hook(void* _this, void*, int type, const std: switch (type) { case 1: // RBX::MESSAGE_OUTPUT: - jobLog << "[RBX::MESSAGE_OUTPUT] " << message.c_str() << std::endl; + jobOutputLog << "[RBX::MESSAGE_OUTPUT] " << message.c_str() << std::endl; SetConsoleTextAttribute(handle, FOREGROUND_BLUE | FOREGROUND_INTENSITY); break; case 0: // RBX::MESSAGE_INFO: - jobLog << "[RBX::MESSAGE_INFO] " << message.c_str() << std::endl; + jobOutputLog << "[RBX::MESSAGE_INFO] " << message.c_str() << std::endl; SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); break; case 2: // RBX::MESSAGE_WARNING: - jobLog << "[RBX::MESSAGE_WARNING] " << message.c_str() << std::endl; + jobOutputLog << "[RBX::MESSAGE_WARNING] " << message.c_str() << std::endl; SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN); break; case 3: // RBX::MESSAGE_ERROR: - jobLog << "[RBX::MESSAGE_ERROR] " << message.c_str() << std::endl; + jobOutputLog << "[RBX::MESSAGE_ERROR] " << message.c_str() << std::endl; SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_INTENSITY); break; } diff --git a/PolygonClientUtilities/RobloxMFCHooks.h b/PolygonClientUtilities/RobloxMFCHooks.h index 72245d1..9cd0d2f 100644 --- a/PolygonClientUtilities/RobloxMFCHooks.h +++ b/PolygonClientUtilities/RobloxMFCHooks.h @@ -12,6 +12,11 @@ extern CRobloxCommandLineInfo__ParseParam_t CRobloxCommandLineInfo__ParseParam; void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* _this, void*, const char* pszParam, BOOL bFlag, BOOL bLast); +typedef void(__thiscall* Http__trustCheck_t)(const char* url); +extern Http__trustCheck_t Http__trustCheck; + +BOOL __fastcall Http__trustCheck_hook(const char* url); + #ifdef ARBITERBUILD typedef void(__thiscall* StandardOut__print_t)(void* _this, int type, const std::string& message); extern StandardOut__print_t StandardOut__print; diff --git a/PolygonClientUtilities/dllmain.cpp b/PolygonClientUtilities/dllmain.cpp index 001160a..01c6fa6 100644 --- a/PolygonClientUtilities/dllmain.cpp +++ b/PolygonClientUtilities/dllmain.cpp @@ -4,6 +4,7 @@ #include "RobloxMFCHooks.h" START_PATCH_LIST() +ADD_PATCH(Http__trustCheck, Http__trustCheck_hook) #ifdef ADDRESS_CROBLOXAPP__INITINSTANCE ADD_PATCH(CRobloxApp__InitInstance, CRobloxApp__InitInstance_hook) #endif @@ -15,8 +16,11 @@ ADD_PATCH(StandardOut__print, StandardOut__print_hook) #endif END_PATCH_LIST() -// To be added to game imports +// DLLs for release will be attached with VMProtect, so this isn't necessary +// Arbiter will still use Stud_PE for ease in swapping DLLs +#ifdef ARBITERBUILD void __declspec(dllexport) doNothing() {} +#endif BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {