From 6f8a1237505034adb98eee7cb24ebfde5586727c Mon Sep 17 00:00:00 2001 From: Milxnor Date: Sat, 20 May 2023 21:12:10 -0400 Subject: [PATCH] main --- - Copy.gitignore | 387 ---------- Cobalt/Cobalt.vcxproj | 165 ++++ Cobalt/Cobalt.vcxproj.filters | 39 + Cobalt/README.md | 2 + Cobalt/curldefinitions.h | 146 ++++ Cobalt/curlhook.h | 74 ++ Cobalt/dllmain.cpp | 109 +++ Cobalt/exithook.h | 14 + Cobalt/memcury.h | 1329 +++++++++++++++++++++++++++++++++ Cobalt/packages.config | 4 + 10 files changed, 1882 insertions(+), 387 deletions(-) delete mode 100644 - Copy.gitignore create mode 100644 Cobalt/Cobalt.vcxproj create mode 100644 Cobalt/Cobalt.vcxproj.filters create mode 100644 Cobalt/README.md create mode 100644 Cobalt/curldefinitions.h create mode 100644 Cobalt/curlhook.h create mode 100644 Cobalt/dllmain.cpp create mode 100644 Cobalt/exithook.h create mode 100644 Cobalt/memcury.h create mode 100644 Cobalt/packages.config diff --git a/ - Copy.gitignore b/ - Copy.gitignore deleted file mode 100644 index 9e5d671..0000000 --- a/ - Copy.gitignore +++ /dev/null @@ -1,387 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Oo]ut/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd -.vs/Cobalt/v17/Browse.VC.db -.vs/Cobalt/v17/.suo -.vs/Cobalt/v17/fileList.bin -.vs/Cobalt/v17/ipch/AutoPCH/389a1fa1f6dcd16f/DLLMAIN.ipch -.vs/Cobalt/v17/ipch/AutoPCH/6b05e7771d6ded15/UTIL.ipch -Cobalt/x64/Release/Cobalt.tlog/CL.read.1.tlog -Cobalt/x64/Release/Cobalt.tlog/link.read.1.tlog -Cobalt/x64/Release/Cobalt.tlog/CL.write.1.tlog -Cobalte/x64/Release/vc143.pdb -x64/Release/Cobalt.pdb -Cobalt/x64/Release/Cobalt.log -Cobalt/x64/Release/Cobalt.ipdb -Cobalt/x64/Release/Cobalt.iobj -.vs/Cobalt/v17/Browse.VC.db -.vs/Cobalt/v17/.suo -.vs/Cobalt/v17/.suo -*.db -*.bin -*.ipch -*.ipdb -*.iobj -*.tlog -*.ifc -*.pdb diff --git a/Cobalt/Cobalt.vcxproj b/Cobalt/Cobalt.vcxproj new file mode 100644 index 0000000..de707e4 --- /dev/null +++ b/Cobalt/Cobalt.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {355fb80e-3008-430e-a074-cdb93819396f} + Cobalt + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;COBALT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;COBALT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;COBALT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpplatest + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;COBALT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpplatest + + + Windows + true + true + true + false + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Cobalt/Cobalt.vcxproj.filters b/Cobalt/Cobalt.vcxproj.filters new file mode 100644 index 0000000..e4570f9 --- /dev/null +++ b/Cobalt/Cobalt.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + \ No newline at end of file diff --git a/Cobalt/README.md b/Cobalt/README.md new file mode 100644 index 0000000..71790af --- /dev/null +++ b/Cobalt/README.md @@ -0,0 +1,2 @@ +# Cobalt + SSL Bypass for Fortnite diff --git a/Cobalt/curldefinitions.h b/Cobalt/curldefinitions.h new file mode 100644 index 0000000..308aa50 --- /dev/null +++ b/Cobalt/curldefinitions.h @@ -0,0 +1,146 @@ +#pragma once + +//got these definitions from reversing curl +#define CURLOPT_URL 0x2712 +#define CURLOPT_NOPROXY 0x27C1 +#define CURLOPT_SSL_VERIFYPEER 0x40 +#define CURLOPT_SSL_VERIFYHOST 0x51 +#define CURLOPT_PINNEDPUBLICKEY 0x27F6 +#define CURLOPT_POSTFIELDS 0x271F + +//got from curl includes +typedef enum { + CURLE_OK = 0, + CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ + CURLE_FAILED_INIT, /* 2 */ + CURLE_URL_MALFORMAT, /* 3 */ + CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for + 7.17.0, reused in April 2011 for 7.21.5] */ + CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ + CURLE_COULDNT_RESOLVE_HOST, /* 6 */ + CURLE_COULDNT_CONNECT, /* 7 */ + CURLE_WEIRD_SERVER_REPLY, /* 8 */ + CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server + due to lack of access - when login fails + this is not returned. */ + CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for + 7.15.4, reused in Dec 2011 for 7.24.0]*/ + CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ + CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server + [was obsoleted in August 2007 for 7.17.0, + reused in Dec 2011 for 7.24.0]*/ + CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ + CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ + CURLE_FTP_CANT_GET_HOST, /* 15 */ + CURLE_HTTP2, /* 16 - A problem in the http2 framing layer. + [was obsoleted in August 2007 for 7.17.0, + reused in July 2014 for 7.38.0] */ + CURLE_FTP_COULDNT_SET_TYPE, /* 17 */ + CURLE_PARTIAL_FILE, /* 18 */ + CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ + CURLE_OBSOLETE20, /* 20 - NOT USED */ + CURLE_QUOTE_ERROR, /* 21 - quote command failure */ + CURLE_HTTP_RETURNED_ERROR, /* 22 */ + CURLE_WRITE_ERROR, /* 23 */ + CURLE_OBSOLETE24, /* 24 - NOT USED */ + CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ + CURLE_READ_ERROR, /* 26 - couldn't open/read from file */ + CURLE_OUT_OF_MEMORY, /* 27 */ + /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error + instead of a memory allocation error if CURL_DOES_CONVERSIONS + is defined + */ + CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ + CURLE_OBSOLETE29, /* 29 - NOT USED */ + CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ + CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ + CURLE_OBSOLETE32, /* 32 - NOT USED */ + CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_HTTP_POST_ERROR, /* 34 */ + CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_FILE_COULDNT_READ_FILE, /* 37 */ + CURLE_LDAP_CANNOT_BIND, /* 38 */ + CURLE_LDAP_SEARCH_FAILED, /* 39 */ + CURLE_OBSOLETE40, /* 40 - NOT USED */ + CURLE_FUNCTION_NOT_FOUND, /* 41 - NOT USED starting with 7.53.0 */ + CURLE_ABORTED_BY_CALLBACK, /* 42 */ + CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ + CURLE_OBSOLETE44, /* 44 - NOT USED */ + CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */ + CURLE_OBSOLETE46, /* 46 - NOT USED */ + CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */ + CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */ + CURLE_TELNET_OPTION_SYNTAX, /* 49 - Malformed telnet option */ + CURLE_OBSOLETE50, /* 50 - NOT USED */ + CURLE_OBSOLETE51, /* 51 - NOT USED */ + CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ + CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ + CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as + default */ + CURLE_SEND_ERROR, /* 55 - failed sending network data */ + CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ + CURLE_OBSOLETE57, /* 57 - NOT IN USE */ + CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ + CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint + wasn't verified fine */ + CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ + CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */ + CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ + CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ + CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind + that failed */ + CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ + CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not + accepted and we failed to login */ + CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ + CURLE_TFTP_PERM, /* 69 - permission problem on server */ + CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */ + CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ + CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ + CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */ + CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ + CURLE_CONV_FAILED, /* 75 - conversion failed */ + CURLE_CONV_REQD, /* 76 - caller must register conversion + callbacks using curl_easy_setopt options + CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOPT_CONV_TO_NETWORK_FUNCTION, and + CURLOPT_CONV_FROM_UTF8_FUNCTION */ + CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing + or wrong format */ + CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ + CURLE_SSH, /* 79 - error from the SSH layer, somewhat + generic so the error message will be of + interest when this has happened */ + + CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL + connection */ + CURLE_AGAIN, /* 81 - socket is not ready for send/recv, + wait till it's ready and try again (Added + in 7.18.2) */ + CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or + wrong format (Added in 7.19.0) */ + CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in + 7.19.0) */ + CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */ + CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */ + CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */ + CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */ + CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */ + CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the + session will be queued */ + CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not + match */ + CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ + CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer + */ + CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from + inside a callback */ + CURLE_AUTH_ERROR, /* 94 - an authentication function returned an + error */ + CURLE_HTTP3, /* 95 - An HTTP/3 layer problem */ + CURL_LAST /* never use! */ +} CURLcode; + +typedef void CURL; \ No newline at end of file diff --git a/Cobalt/curlhook.h b/Cobalt/curlhook.h new file mode 100644 index 0000000..4c95b3a --- /dev/null +++ b/Cobalt/curlhook.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include "curldefinitions.h" +#include "memcury.h" + +// #define HYBRID_ENABLED + +auto (*curl_easy_setopt_original)(CURL* Curl, uintptr_t opt, ...)->CURLcode; + +std::string FNhost = "127.0.0.1:3551"; + +//This routine is used for setting up curl. we will be hijacking this to change the values. +auto Hijacked_curl_easy_setopt(CURL* Curl, uintptr_t opt, va_list info) -> CURLcode +{ + int OFF = 0; + switch (opt) + { + case CURLOPT_NOPROXY: + return curl_easy_setopt_original(Curl, opt, ""); + break; + + case CURLOPT_SSL_VERIFYPEER: + return curl_easy_setopt_original(Curl, opt, OFF); + break; + case CURLOPT_SSL_VERIFYHOST: + return curl_easy_setopt_original(Curl, opt, OFF); + break; + case CURLOPT_PINNEDPUBLICKEY: + return CURLcode::CURLE_OK; + break; + case CURLOPT_URL: + std::string url = info; + std::regex Host(("(.*).ol.epicgames.com")); +#ifdef HYBRID_ENABLED + if (std::regex_search(info, std::regex(("/fortnite/api/cloudstorage/system")))) { + url = std::regex_replace(info, Host, FNhost); + } + else if (std::regex_search(info, std::regex(("/fortnite/api/v2/versioncheck/")))) { + url = std::regex_replace(info, Host, FNhost); + } + else if (std::regex_search(info, std::regex(("/fortnite/api/game/v2/profile")))) { + url = std::regex_replace(info, Host, FNhost); + } + else if (std::regex_search(info, std::regex(("/content/api/pages/fortnite-game")))) { + url = std::regex_replace(info, Host, FNhost); + } + else if (std::regex_search(info, std::regex(("/affiliate/api/public/affiliates/slug")))) { + url = std::regex_replace(info, Host, FNhost); + } + else if (std::regex_search(info, std::regex(("/socialban/api/public/v1")))) { + url = std::regex_replace(info, Host, FNhost); + } + /* else if (std::regex_search(info, std::regex(ENC("player.platform")))) { // idk if this even works + if (version == S13) + { + url = std::regex_replace(info, Host, "IOS"); + Log(ENC("Changed Windows to IOS")); + } + } */ +#else + if (std::regex_search(info, Host)) { + url = std::regex_replace(info, Host, FNhost); + } +#endif + return curl_easy_setopt_original(Curl, opt, url.c_str()); + break; + } + return curl_easy_setopt_original(Curl, opt, info); +} + +__int64 sigscan(const std::string& Str) { return Memcury::Scanner::FindPattern(Str.c_str()).Get(); } \ No newline at end of file diff --git a/Cobalt/dllmain.cpp b/Cobalt/dllmain.cpp new file mode 100644 index 0000000..3324c8f --- /dev/null +++ b/Cobalt/dllmain.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include "curlhook.h" +#include "exithook.h" + +#define DetoursEasy(address, hook) \ + DetourTransactionBegin(); \ + DetourUpdateThread(GetCurrentThread()); \ + DetourAttach(reinterpret_cast(&address), hook); \ + DetourTransactionCommit(); + +auto FindPushWidget() +{ + return sigscan("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 30 48 8B E9 49 8B D9 48 8D 0D ? ? ? ? 49 8B F8 48 8B F2 E8 ? ? ? ? 4C 8B CF 48 89 5C 24 ? 4C 8B C6 48 8B D5 48 8B 48 78"); +} + +bool InitializeCurlHook() +{ + auto CurlEasySetOptAddr = sigscan("89 54 24 10 4C 89 44 24 18 4C 89 4C 24 20 48 83 EC 28 48 85 C9 75 08 8D 41 2B 48 83 C4 28 C3 4C"); + + if (!CurlEasySetOptAddr) + { + CurlEasySetOptAddr = sigscan("89 54 24 10 4C 89 44 24 18 4C 89 4C 24 20 48 83 EC 28 48 85 C9 75 08 8D 41 2B 48 83 C4 28 C3 4C"); + } + + if (!CurlEasySetOptAddr) + { + std::cout << "Failed to find CurlEasySetOptAddr!"; + return false; + } + + curl_easy_setopt_original = decltype(curl_easy_setopt_original)(CurlEasySetOptAddr); + + if (FindPushWidget()) + { + DetoursEasy(curl_easy_setopt_original, Hijacked_curl_easy_setopt); + } + else + { + Memcury::VEHHook::AddHook(curl_easy_setopt_original, Hijacked_curl_easy_setopt); + } + + return true; +} + +void InitializeExitHook() +{ + if (!FindPushWidget()) + return; + + auto UnsafeEnvironmentPopupAddr = sigscan("4C 8B DC 55 49 8D AB ? ? ? ? 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 85 ? ? ? ? 49 89 73 F0 49 89 7B E8 48 8B F9 4D 89 63 E0 4D 8B E0 4D 89 6B D8"); + + if (!UnsafeEnvironmentPopupAddr) + { + UnsafeEnvironmentPopupAddr = sigscan("4C 8B DC 55 49 8D AB ? ? ? ? 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 85 ? ? ? ?"); + } + + auto RequestExitWithStatusAddr = sigscan("48 89 5C 24 ? 57 48 83 EC 40 41 B9 ? ? ? ? 0F B6 F9 44 38 0D ? ? ? ? 0F B6 DA 72 24 89 5C 24 30 48 8D 05 ? ? ? ? 89 7C 24 28 4C 8D 05 ? ? ? ? 33 D2 48 89 44 24 ? 33 C9 E8 ? ? ? ?"); + + if (!RequestExitWithStatusAddr) + { + RequestExitWithStatusAddr = sigscan("48 8B C4 48 89 58 18 88 50 10 88 48 08 57 48 83 EC 30"); + } + + DetoursEasy(UnsafeEnvironmentPopupAddr, UnsafeEnvironmentPopupHook); + DetoursEasy(RequestExitWithStatusAddr, RequestExitWithStatusHook); +} + +DWORD WINAPI Main(LPVOID) +{ + AllocConsole(); + + FILE* fptr; + freopen_s(&fptr, "CONOUT$", "w+", stdout); + + bool curlResult = InitializeCurlHook(); + InitializeExitHook(); + + bool result = curlResult; + + if (result) + { + std::cout << "Cobalt v0.1 initialized sucessfully."; + } + else + { + MessageBoxA(0, "Failed to initialize!", "Cobalt", MB_ICONERROR); + } + + return 0; +} + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(0, 0, Main, 0, 0, 0); + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/Cobalt/exithook.h b/Cobalt/exithook.h new file mode 100644 index 0000000..4485dbe --- /dev/null +++ b/Cobalt/exithook.h @@ -0,0 +1,14 @@ +#pragma once + +void (*RequestExitWithStatus)(bool Force, unsigned char Code); +void RequestExitWithStatusHook(bool Force, unsigned char Code) +{ + // printf("[VEH] RequestExitWithStatus Call Forced: %i ReturnCode: %u\n", Force, Code); +} + +void (*UnsafeEnvironmentPopup)(wchar_t** unknown1, unsigned __int8 _case, __int64 unknown2, char unknown3); + +void UnsafeEnvironmentPopupHook(wchar_t** unknown1, unsigned __int8 _case, __int64 unknown2, char unknown3) +{ + // printf("[VEH] +#include +#include +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "Dbghelp.lib") + +#define MemcuryAssert(cond) \ + if (!(cond)) \ + { \ + MessageBoxA(nullptr, #cond, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); \ + } + +#define MemcuryAssertM(cond, msg) \ + if (!(cond)) \ + { \ + MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); \ + } + +#define MemcuryThrow(msg) \ + MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ + Memcury::Safety::FreezeCurrentThread(); + +namespace Memcury +{ + extern "C" IMAGE_DOS_HEADER __ImageBase; + + inline auto GetCurrentModule() -> HMODULE + { + return reinterpret_cast(&__ImageBase); + } + + namespace Util + { + template + constexpr static auto IsInRange(T value, T min, T max) -> bool + { + return value >= min && value < max; + } + + constexpr auto StrHash(const char* str, int h = 0) -> unsigned int + { + return !str[h] ? 5381 : (StrHash(str, h + 1) * 33) ^ str[h]; + } + + inline auto IsSamePage(void* A, void* B) -> bool + { + MEMORY_BASIC_INFORMATION InfoA; + if (!VirtualQuery(A, &InfoA, sizeof(InfoA))) + { + return true; + } + + MEMORY_BASIC_INFORMATION InfoB; + if (!VirtualQuery(B, &InfoB, sizeof(InfoB))) + { + return true; + } + + return InfoA.BaseAddress == InfoB.BaseAddress; + } + + inline auto GetModuleStartAndEnd() -> std::pair + { + auto HModule = GetCurrentModule(); + auto NTHeaders = reinterpret_cast((uintptr_t)HModule + reinterpret_cast((uintptr_t)HModule)->e_lfanew); + + uintptr_t dllStart = (uintptr_t)HModule; + uintptr_t dllEnd = (uintptr_t)HModule + NTHeaders->OptionalHeader.SizeOfImage; + + return { dllStart, dllEnd }; + } + + inline auto CopyToClipboard(std::string str) + { + auto mem = GlobalAlloc(GMEM_FIXED, str.size() + 1); + memcpy(mem, str.c_str(), str.size() + 1); + + OpenClipboard(nullptr); + EmptyClipboard(); + SetClipboardData(CF_TEXT, mem); + CloseClipboard(); + + GlobalFree(mem); + } + } + + namespace Safety + { + enum class ExceptionMode + { + None, + CatchDllExceptionsOnly, + CatchAllExceptions + }; + + static auto FreezeCurrentThread() -> void + { + SuspendThread(GetCurrentThread()); + } + + static auto PrintStack(CONTEXT* ctx) -> void + { + STACKFRAME64 stack; + memset(&stack, 0, sizeof(STACKFRAME64)); + + auto process = GetCurrentProcess(); + auto thread = GetCurrentThread(); + + SymInitialize(process, NULL, TRUE); + + bool result; + DWORD64 displacement = 0; + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]{ 0 }; + char name[256]{ 0 }; + char module[256]{ 0 }; + + PSYMBOL_INFO symbolInfo = (PSYMBOL_INFO)buffer; + + for (ULONG frame = 0;; frame++) + { + result = StackWalk64( + IMAGE_FILE_MACHINE_AMD64, + process, + thread, + &stack, + ctx, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL); + + if (!result) + break; + + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); + symbolInfo->MaxNameLen = MAX_SYM_NAME; + SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbolInfo); + + HMODULE hModule = NULL; + lstrcpyA(module, ""); + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (const wchar_t*)(stack.AddrPC.Offset), &hModule); + + if (hModule != NULL) + GetModuleFileNameA(hModule, module, 256); + + printf("[%lu] Name: %s - Address: %p - Module: %s\n", frame, symbolInfo->Name, (void*)symbolInfo->Address, module); + } + } + + template + auto MemcuryGlobalHandler(EXCEPTION_POINTERS* ExceptionInfo) -> long + { + auto [dllStart, dllEnd] = Util::GetModuleStartAndEnd(); + + if constexpr (mode == ExceptionMode::CatchDllExceptionsOnly) + { + if (!Util::IsInRange(ExceptionInfo->ContextRecord->Rip, dllStart, dllEnd)) + { + return EXCEPTION_CONTINUE_SEARCH; + } + } + + auto message = std::format("Memcury caught an exception at [{:x}]\nPress Yes if you want the address to be copied to your clipboard", ExceptionInfo->ContextRecord->Rip); + if (MessageBoxA(nullptr, message.c_str(), "Error", MB_ICONERROR | MB_YESNO) == IDYES) + { + std::string clip = std::format("{:x}", ExceptionInfo->ContextRecord->Rip); + Util::CopyToClipboard(clip); + } + + PrintStack(ExceptionInfo->ContextRecord); + + FreezeCurrentThread(); + + return EXCEPTION_EXECUTE_HANDLER; + } + + template + static auto SetExceptionMode() -> void + { + SetUnhandledExceptionFilter(MemcuryGlobalHandler); + } + } + + namespace Globals + { + constexpr const bool bLogging = true; + + inline const char* moduleName = nullptr; + } + + namespace ASM + { + //@todo: this whole namespace needs a rework, should somehow make this more modern and less ugly. + enum MNEMONIC : uint8_t + { + JMP_REL8 = 0xEB, + JMP_REL32 = 0xE9, + JMP_EAX = 0xE0, + CALL = 0xE8, + LEA = 0x8D, + CDQ = 0x99, + CMOVL = 0x4C, + CMOVS = 0x48, + CMOVNS = 0x49, + NOP = 0x90, + INT3 = 0xCC, + RETN_REL8 = 0xC2, + RETN = 0xC3, + NONE = 0x00 + }; + + constexpr int SIZE_OF_JMP_RELATIVE_INSTRUCTION = 5; + constexpr int SIZE_OF_JMP_ABSLOUTE_INSTRUCTION = 13; + + constexpr auto MnemonicToString(MNEMONIC e) -> const char* + { + switch (e) + { + case JMP_REL8: + return "JMP_REL8"; + case JMP_REL32: + return "JMP_REL32"; + case JMP_EAX: + return "JMP_EAX"; + case CALL: + return "CALL"; + case LEA: + return "LEA"; + case CDQ: + return "CDQ"; + case CMOVL: + return "CMOVL"; + case CMOVS: + return "CMOVS"; + case CMOVNS: + return "CMOVNS"; + case NOP: + return "NOP"; + case INT3: + return "INT3"; + case RETN_REL8: + return "RETN_REL8"; + case RETN: + return "RETN"; + case NONE: + return "NONE"; + default: + return "UNKNOWN"; + } + } + + constexpr auto Mnemonic(const char* s) -> MNEMONIC + { + switch (Util::StrHash(s)) + { + case Util::StrHash("JMP_REL8"): + return JMP_REL8; + case Util::StrHash("JMP_REL32"): + return JMP_REL32; + case Util::StrHash("JMP_EAX"): + return JMP_EAX; + case Util::StrHash("CALL"): + return CALL; + case Util::StrHash("LEA"): + return LEA; + case Util::StrHash("CDQ"): + return CDQ; + case Util::StrHash("CMOVL"): + return CMOVL; + case Util::StrHash("CMOVS"): + return CMOVS; + case Util::StrHash("CMOVNS"): + return CMOVNS; + case Util::StrHash("NOP"): + return NOP; + case Util::StrHash("INT3"): + return INT3; + case Util::StrHash("RETN_REL8"): + return RETN_REL8; + case Util::StrHash("RETN"): + return RETN; + default: + return NONE; + } + } + + inline auto byteIsA(uint8_t byte, MNEMONIC opcode) -> bool + { + return byte == opcode; + } + + inline auto byteIsAscii(uint8_t byte) -> bool + { + static constexpr bool isAscii[0x100] = { + false, false, false, false, false, false, false, false, false, true, true, false, false, true, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false + }; + + return isAscii[byte]; + } + + inline bool isJump(uint8_t byte) + { + return byte >= 0x70 && byte <= 0x7F; + } + + static auto pattern2bytes(const char* pattern) -> std::vector + { + auto bytes = std::vector{}; + const auto start = const_cast(pattern); + const auto end = const_cast(pattern) + strlen(pattern); + + for (auto current = start; current < end; ++current) + { + if (*current == '?') + { + ++current; + if (*current == '?') + ++current; + bytes.push_back(-1); + } + else + { + bytes.push_back(strtoul(current, ¤t, 16)); + } + } + return bytes; + } + } + + namespace PE + { + inline auto SetCurrentModule(const char* moduleName) -> void + { + Globals::moduleName = moduleName; + } + + inline auto GetModuleBase() -> uintptr_t + { + return reinterpret_cast(GetModuleHandleA(Globals::moduleName)); + } + + inline auto GetDOSHeader() -> PIMAGE_DOS_HEADER + { + return reinterpret_cast(GetModuleBase()); + } + + inline auto GetNTHeaders() -> PIMAGE_NT_HEADERS + { + return reinterpret_cast(GetModuleBase() + GetDOSHeader()->e_lfanew); + } + + class Address + { + uintptr_t _address; + + public: + Address() + { + _address = 0; + } + + Address(uintptr_t address) + : _address(address) + { + } + + Address(void* address) + : _address(reinterpret_cast(address)) + { + } + + auto operator=(uintptr_t address) -> Address + { + _address = address; + return *this; + } + + auto operator=(void* address) -> Address + { + _address = reinterpret_cast(address); + return *this; + } + + auto operator+(uintptr_t offset) -> Address + { + return Address(_address + offset); + } + + bool operator>(uintptr_t offset) + { + return _address > offset; + } + + bool operator>(Address address) + { + return _address > address._address; + } + + bool operator<(uintptr_t offset) + { + return _address < offset; + } + + bool operator<(Address address) + { + return _address < address._address; + } + + bool operator>=(uintptr_t offset) + { + return _address >= offset; + } + + bool operator>=(Address address) + { + return _address >= address._address; + } + + bool operator<=(uintptr_t offset) + { + return _address <= offset; + } + + bool operator<=(Address address) + { + return _address <= address._address; + } + + bool operator==(uintptr_t offset) + { + return _address == offset; + } + + bool operator==(Address address) + { + return _address == address._address; + } + + bool operator!=(uintptr_t offset) + { + return _address != offset; + } + + bool operator!=(Address address) + { + return _address != address._address; + } + + auto RelativeOffset(uint32_t offset) -> Address + { + _address = ((_address + offset + 4) + *(int32_t*)(_address + offset)); + return *this; + } + + auto AbsoluteOffset(uint32_t offset) -> Address + { + _address = _address + offset; + return *this; + } + + auto Jump() -> Address + { + if (ASM::isJump(*reinterpret_cast(_address))) + { + UINT8 toSkip = *reinterpret_cast(_address + 1); + _address = _address + 2 + toSkip; + } + + return *this; + } + + auto Get() -> uintptr_t + { + return _address; + } + + template + auto GetAs() -> T + { + return reinterpret_cast(_address); + } + + auto IsValid() -> bool + { + return _address != 0; + } + }; + + class Section + { + public: + std::string sectionName; + IMAGE_SECTION_HEADER rawSection; + + static auto GetAllSections() -> std::vector
+ { + std::vector
sections; + + auto sectionsSize = GetNTHeaders()->FileHeader.NumberOfSections; + auto section = IMAGE_FIRST_SECTION(GetNTHeaders()); + + for (WORD i = 0; i < sectionsSize; i++, section++) + { + auto secName = std::string((char*)section->Name); + + sections.push_back({ secName, *section }); + } + + return sections; + } + + static auto GetSection(std::string sectionName) -> Section + { + for (auto& section : GetAllSections()) + { + if (section.sectionName == sectionName) + { + return section; + } + } + + MemcuryThrow("Section not found"); + return Section{}; + } + + auto GetSectionSize() -> uint32_t + { + return rawSection.Misc.VirtualSize; + } + + auto GetSectionStart() -> Address + { + return Address(GetModuleBase() + rawSection.VirtualAddress); + } + + auto GetSectionEnd() -> Address + { + return Address(GetSectionStart() + GetSectionSize()); + } + + auto isInSection(Address address) -> bool + { + return address >= GetSectionStart() && address < GetSectionEnd(); + } + }; + } + + class Scanner + { + PE::Address _address; + + public: + Scanner(PE::Address address) + : _address(address) + { + } + + static auto SetTargetModule(const char* moduleName) -> void + { + PE::SetCurrentModule(moduleName); + } + + static auto FindPatternEx(HANDLE handle, const char* pattern, const char* mask, uint64_t begin, uint64_t end) -> Scanner + { + auto scan = [](const char* pattern, const char* mask, char* begin, unsigned int size) -> char* + { + size_t patternLen = strlen(mask); + for (unsigned int i = 0; i < size - patternLen; i++) + { + bool found = true; + for (unsigned int j = 0; j < patternLen; j++) + { + if (mask[j] != '?' && pattern[j] != *(begin + i + j)) + { + found = false; + break; + } + } + + if (found) + return (begin + i); + } + return nullptr; + }; + + uint64_t match = NULL; + SIZE_T bytesRead; + char* buffer = nullptr; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + + uint64_t curr = begin; + + for (uint64_t curr = begin; curr < end; curr += mbi.RegionSize) + { + if (!VirtualQueryEx(handle, (void*)curr, &mbi, sizeof(mbi))) + continue; + + if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS) + continue; + + buffer = new char[mbi.RegionSize]; + + if (ReadProcessMemory(handle, mbi.BaseAddress, buffer, mbi.RegionSize, &bytesRead)) + { + char* internalAddr = scan(pattern, mask, buffer, (unsigned int)bytesRead); + + if (internalAddr != nullptr) + { + match = curr + (uint64_t)(internalAddr - buffer); + break; + } + } + } + delete[] buffer; + + MemcuryAssertM(match != 0, "FindPatternEx return nullptr"); + + return Scanner(match); + } + + static auto FindPatternEx(HANDLE handle, const char* sig) -> Scanner + { + char pattern[100]; + char mask[100]; + + char lastChar = ' '; + unsigned int j = 0; + + for (unsigned int i = 0; i < strlen(sig); i++) + { + if ((sig[i] == '?' || sig[i] == '*') && (lastChar != '?' && lastChar != '*')) + { + pattern[j] = mask[j] = '?'; + j++; + } + + else if (isspace(lastChar)) + { + pattern[j] = lastChar = (char)strtol(&sig[i], 0, 16); + mask[j] = 'x'; + j++; + } + lastChar = sig[i]; + } + pattern[j] = mask[j] = '\0'; + + auto module = (uint64_t)GetModuleHandle(nullptr); + + return FindPatternEx(handle, pattern, mask, module, module + Memcury::PE::GetNTHeaders()->OptionalHeader.SizeOfImage); + } + + static auto FindPattern(const char* signature) -> Scanner + { + PE::Address add{ nullptr }; + + const auto sizeOfImage = PE::GetNTHeaders()->OptionalHeader.SizeOfImage; + auto patternBytes = ASM::pattern2bytes(signature); + const auto scanBytes = reinterpret_cast(PE::GetModuleBase()); + + const auto s = patternBytes.size(); + const auto d = patternBytes.data(); + + for (auto i = 0ul; i < sizeOfImage - s; ++i) + { + bool found = true; + for (auto j = 0ul; j < s; ++j) + { + if (scanBytes[i + j] != d[j] && d[j] != -1) + { + found = false; + break; + } + } + + if (found) + { + add = reinterpret_cast(&scanBytes[i]); + break; + } + } + + // MemcuryAssertM(add != 0, "FindPattern return nullptr"); + + return Scanner(add); + } + + static auto FindPointerRef(void* Pointer, int useRefNum = 0, bool bUseFirstResult = false) -> Scanner // credit me and ender + { + PE::Address add{ nullptr }; + + auto textSection = PE::Section::GetSection(".text"); + + const auto scanBytes = reinterpret_cast(textSection.GetSectionStart().Get()); + + int aa = 0; + + // scan only text section + for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++) + { + if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && (scanBytes[i + 1] == ASM::LEA || scanBytes[i + 1] == 0x8B)) + { + if (PE::Address(&scanBytes[i]).RelativeOffset(3).GetAs() == Pointer) + { + add = PE::Address(&scanBytes[i]); + + // LOG_INFO(LogDev, "2add: 0x{:x}", add.Get() - __int64(GetModuleHandleW(0))); + + if (bUseFirstResult) + return Scanner(add); + + /* if (++aa > useRefNum) + break; */ + } + } + + if (scanBytes[i] == ASM::CALL) + { + if (PE::Address(&scanBytes[i]).RelativeOffset(1).GetAs() == Pointer) + { + add = PE::Address(&scanBytes[i]); + + // LOG_INFO(LogDev, "1add: 0x{:x}", add.Get() - __int64(GetModuleHandleW(0))); + + if (bUseFirstResult) + return Scanner(add); + + /* if (++aa > useRefNum) + break; */ + } + } + } + + if (add == 0) + { + MessageBoxA(0, "FindPointerRef return nullptr", "Memcury", MB_OK); + } + else + { + // MessageBoxA(0, std::format("FindPointerRef return 0x{:x}", add.Get() - __int64(GetModuleHandleW(0))).c_str(), "Memcury", MB_OK); + } + + return Scanner(add); + } + + + // Supports wide and normal strings both std and pointers + template + static auto FindStringRef(T string, bool bWarnIfNotFound = true, int useRefNum = 0, bool bIsInFunc = false) -> Scanner + { + PE::Address add{ nullptr }; + + constexpr auto bIsWide = std::is_same::value; + constexpr auto bIsChar = std::is_same::value; + + constexpr auto bIsPtr = bIsWide || bIsChar; + + auto textSection = PE::Section::GetSection(".text"); + auto rdataSection = PE::Section::GetSection(".rdata"); + + const auto scanBytes = reinterpret_cast(textSection.GetSectionStart().Get()); + + int aa = 0; + + // scan only text section + for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++) + { + if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && scanBytes[i + 1] == ASM::LEA) + { + auto stringAdd = PE::Address(&scanBytes[i]).RelativeOffset(3); + + // Check if the string is in the .rdata section + if (rdataSection.isInSection(stringAdd)) + { + auto strBytes = stringAdd.GetAs(); + + // Check if the first char is printable + if (ASM::byteIsAscii(strBytes[0])) + { + if constexpr (!bIsPtr) + { + // typedef T::value_type char_type; + using char_type = std::decay_t>; + + auto lea = stringAdd.GetAs(); + + T leaT(lea); + + if (leaT == string) + { + add = PE::Address(&scanBytes[i]); + + if (++aa > useRefNum) + break; + } + } + else + { + auto lea = stringAdd.GetAs(); + + if constexpr (bIsWide) + { + if (wcscmp(string, lea) == 0) + { + add = PE::Address(&scanBytes[i]); + + if (++aa > useRefNum) + break; + } + } + else + { + if (strcmp(string, lea) == 0) + { + add = PE::Address(&scanBytes[i]); + + if (++aa > useRefNum) + break; + } + } + } + } + } + } + } + + // MemcuryAssertM(add != 0, "FindStringRef return nullptr"); + + if (bWarnIfNotFound) + { + if (add == 0) + { + if constexpr (bIsWide) + { + std::wstring wstr = std::wstring(string); + // LOG_WARN(LogMemory, "Failed to find String {}", std::string(wstr.begin(), wstr.end())); + + // auto aaa = (L"failed FindStringRef " + std::wstring(string)); + // MessageBoxA(0, std::string(aaa.begin(), aaa.end()).c_str(), "Memcury", MB_ICONERROR); + } + else + { + // LOG_WARN(LogMemory, "Failed to find String {}", string); + // MessageBoxA(0, ("failed FindStringRef " + std::string(string)).c_str(), "Memcury", MB_ICONERROR); + } + } + } + + if (add.Get()) + { + if (bIsInFunc) + { + for (int i = 0; i < 300; i++) + { + if (*(uint8_t*)(add.Get() - i) == 0x48 && *(uint8_t*)(add.Get() - i + 1) == 0x83) + { + // MessageBoxA(0, std::format("0x{:x}", (__int64(add.Get() - i) - __int64(GetModuleHandleW(0)))).c_str(), "Memcury", MB_OK); + + auto beginFunc = Scanner(add.Get() - i); + + auto ref = FindPointerRef(beginFunc.GetAs()); + + return ref; + } + } + } + } + + return Scanner(add); + } + + auto Jump() -> Scanner + { + _address.Jump(); + return *this; + } + + auto ScanFor(std::vector opcodesToFind, bool forward = true, int toSkip = 0) -> Scanner + { + const auto scanBytes = _address.GetAs(); + + for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) + { + bool found = true; + + for (int k = 0; k < opcodesToFind.size() && found; k++) + { + if (opcodesToFind[k] == -1) + continue; + found = opcodesToFind[k] == scanBytes[i + k]; + } + + if (found) + { + _address = &scanBytes[i]; + if (toSkip != 0) + { + return ScanFor(opcodesToFind, forward, toSkip - 1); + } + + break; + } + } + + return *this; + } + + auto FindFunctionBoundary(bool forward = false) -> Scanner + { + const auto scanBytes = _address.GetAs(); + + for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) + { + if ( // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL8) || + // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL32) || + // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_EAX) || + ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN_REL8) || ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN) || ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::INT3)) + { + _address = (uintptr_t)&scanBytes[i + 1]; + break; + } + } + + return *this; + } + + auto RelativeOffset(uint32_t offset) -> Scanner + { + _address.RelativeOffset(offset); + + return *this; + } + + auto AbsoluteOffset(uint32_t offset) -> Scanner + { + _address.AbsoluteOffset(offset); + + return *this; + } + + template + auto GetAs() -> T + { + return _address.GetAs(); + } + + auto Get() -> uintptr_t + { + return _address.Get(); + } + + auto IsValid() -> bool + { + return _address.IsValid(); + } + }; + + /* Bad don't use it tbh... */ + class TrampolineHook + { + void** originalFunctionPtr; + PE::Address originalFunction; + PE::Address hookFunction; + PE::Address allocatedPage; + std::vector restore; + + void PointToCodeIfNot(PE::Address& ptr) + { + auto bytes = ptr.GetAs(); + + if (ASM::byteIsA(bytes[0], ASM::MNEMONIC::JMP_REL32)) + { + ptr = bytes + 5 + *(int32_t*)&bytes[1]; + } + } + + void* AllocatePageNearAddress(void* targetAddr) + { + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + const uint64_t PAGE_SIZE = sysInfo.dwPageSize; + + uint64_t startAddr = (uint64_t(targetAddr) & ~(PAGE_SIZE - 1)); // round down to nearest page boundary + uint64_t minAddr = min(startAddr - 0x7FFFFF00, (uint64_t)sysInfo.lpMinimumApplicationAddress); + uint64_t maxAddr = max(startAddr + 0x7FFFFF00, (uint64_t)sysInfo.lpMaximumApplicationAddress); + + uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE)); + + for (uint64_t pageOffset = 1; pageOffset; pageOffset++) + { + uint64_t byteOffset = pageOffset * PAGE_SIZE; + uint64_t highAddr = startPage + byteOffset; + uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0; + + bool needsExit = highAddr > maxAddr && lowAddr < minAddr; + + if (highAddr < maxAddr) + { + void* outAddr = VirtualAlloc((void*)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (outAddr) + return outAddr; + } + + if (lowAddr > minAddr) + { + void* outAddr = VirtualAlloc((void*)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (outAddr != nullptr) + return outAddr; + } + + if (needsExit) + { + break; + } + } + + return nullptr; + } + + void WriteAbsoluteJump(void* jumpLocation, void* destination) + { + uint8_t absJumpInstructions[] = { + ASM::Mnemonic("CMOVNS"), 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr + 0x41, 0xFF, 0xE2 // jmp r10 + }; + + auto destination64 = (uint64_t)destination; + memcpy(&absJumpInstructions[2], &destination64, sizeof(destination64)); + memcpy(jumpLocation, absJumpInstructions, sizeof(absJumpInstructions)); + } + + uintptr_t PrepareRestore() + { + /* + This is not a correct way to do it at all, since not all functions sub from the stack + This needs so much more tests, but it works for now. + */ + + Scanner scanner(originalFunction); + scanner.ScanFor({ 0x48, 0x83, 0xEC }); // sub rsp + + auto restoreSize = scanner.Get() - originalFunction.Get(); + + MemcuryAssert(restoreSize > 0 && restoreSize < 0x100); + + restore.reserve(restoreSize); + for (auto i = 0; i < restoreSize; i++) + { + restore.push_back(originalFunction.GetAs()[i]); + } + + return restoreSize; + } + + void WriteRestore() + { + auto restorePtr = allocatedPage + ASM::SIZE_OF_JMP_ABSLOUTE_INSTRUCTION + 2; + + memcpy(restorePtr.GetAs(), restore.data(), restore.size()); + + *originalFunctionPtr = restorePtr.GetAs(); + + // Write a jump back to where the execution should resume + restorePtr.AbsoluteOffset((uint32_t)restore.size()); + + auto contuineExecution = originalFunction + restore.size(); + + WriteAbsoluteJump(restorePtr.GetAs(), contuineExecution.GetAs()); + } + + auto PrepareJMPInstruction(uint64_t dst) + { + uint8_t bytes[5] = { ASM::Mnemonic("JMP_REL32"), 0x0, 0x0, 0x0, 0x0 }; + + const uint64_t relAddr = dst - (originalFunction.Get() + ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); + memcpy(bytes + 1, &relAddr, 4); + + return std::move(bytes); + } + + bool IsHooked() + { + return originalFunction.GetAs()[0] == ASM::Mnemonic("JMP_REL32"); + } + + public: + TrampolineHook(void** originalFunction, void* hookFunction) + { + this->originalFunctionPtr = originalFunction; + + this->originalFunction = *originalFunction; + this->hookFunction = hookFunction; + + PointToCodeIfNot(this->originalFunction); + PointToCodeIfNot(this->hookFunction); + }; + + bool Commit() + { + auto fnStart = originalFunction.GetAs(); + + auto restoreSize = PrepareRestore(); + + if (!allocatedPage.IsValid()) + { + allocatedPage = AllocatePageNearAddress(fnStart); + } + + memset(allocatedPage.GetAs(), ASM::MNEMONIC::INT3, 0x1000); + + WriteAbsoluteJump(allocatedPage.GetAs(), hookFunction.GetAs()); + + DWORD oldProtect; + VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + auto jmpInstruction = PrepareJMPInstruction(allocatedPage.Get()); + + WriteRestore(); + + memset(fnStart, ASM::MNEMONIC::INT3, restoreSize); + memcpy(fnStart, jmpInstruction, ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); + + return true; + } + + bool Revert() + { + auto fnStart = originalFunction.GetAs(); + + DWORD oldProtect; + VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); + + memcpy(fnStart, restore.data(), restore.size()); + + *originalFunctionPtr = originalFunction.GetAs(); + + // VirtualFree(allocatedPage.GetAs(), 0x1000, MEM_RELEASE); + + return true; + } + + auto Toggle() + { + if (IsHooked()) + Revert(); + else + Commit(); + + return IsHooked(); + } + }; + + namespace VEHHook + { + struct HOOK_INFO + { + void* Original; + void* Detour; + + HOOK_INFO(void* Original, void* Detour) + : Original(Original) + , Detour(Detour) + { + } + }; + + inline std::vector Hooks; + inline std::vector HookProtections; + inline HANDLE ExceptionHandler; + + inline long Handler(EXCEPTION_POINTERS* Exception) + { + if (Exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) + { + auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Rip = Exception->ContextRecord->Rip](const HOOK_INFO& Hook) + { return Hook.Original == (void*)Rip; }); + if (Itr != Hooks.end()) + { + Exception->ContextRecord->Rip = (uintptr_t)Itr->Detour; + } + + Exception->ContextRecord->EFlags |= 0x100; // SINGLE_STEP_FLAG + + return EXCEPTION_CONTINUE_EXECUTION; + } + else if (Exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) + { + // TODO: find a way to only vp the function that about to get executed + for (auto& Hook : Hooks) + { + DWORD dwOldProtect; + VirtualProtect(Hook.Original, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect); + } + + return EXCEPTION_CONTINUE_EXECUTION; + } + + return EXCEPTION_CONTINUE_SEARCH; + } + + inline bool Init() + { + if (ExceptionHandler == nullptr) + { + ExceptionHandler = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)Handler); + } + return ExceptionHandler != nullptr; + } + + inline bool AddHook(void* Target, void* Detour) + { + if (ExceptionHandler == nullptr) + { + return false; + } + + if (Util::IsSamePage(Target, Detour)) + { + return false; + } + + if (!VirtualProtect(Target, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &HookProtections.emplace_back())) + { + HookProtections.pop_back(); + return false; + } + + Hooks.emplace_back(Target, Detour); + return true; + } + + inline bool RemoveHook(void* Original) + { + auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Original](const HOOK_INFO& Hook) + { return Hook.Original == Original; }); + + if (Itr == Hooks.end()) + { + return false; + } + + const auto ProtItr = HookProtections.begin() + std::distance(Hooks.begin(), Itr); + Hooks.erase(Itr); + + DWORD dwOldProtect; + bool Ret = VirtualProtect(Original, 1, *ProtItr, &dwOldProtect); + HookProtections.erase(ProtItr); + + return false; + } + } +} + +inline uintptr_t FindFunctionCall(const wchar_t* Name, const std::vector& Bytes = std::vector{ 0x48, 0x89, 0x5C }, int skip = 0) // credit ender & me +{ + auto FunctionPtr = Memcury::Scanner::FindStringRef(Name, true, skip).ScanFor({ 0x48, 0x8D, 0x0D }).RelativeOffset(3).GetAs(); + + auto PtrRef = Memcury::Scanner::FindPointerRef(FunctionPtr); + + /* if (!PtrRef.Get() || PtrRef.Get() == __int64(FunctionPtr)) + { + std::wstring NameWStr = std::wstring(Name); + LOG_WARN(LogMemory, "Failed to find pointer reference for {}", std::string(NameWStr.begin(), NameWStr.end())); + return 0; + } */ + + return PtrRef.ScanFor(Bytes, false).Get(); +} \ No newline at end of file diff --git a/Cobalt/packages.config b/Cobalt/packages.config new file mode 100644 index 0000000..31cfc2d --- /dev/null +++ b/Cobalt/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file