├── .gitignore ├── Android ├── Android.mk ├── Application.mk ├── Dobby │ ├── arm64-v8a │ │ ├── libdobby.a │ │ └── libdobby.so │ ├── armeabi-v7a │ │ ├── libdobby.a │ │ └── libdobby.so │ ├── dobby.h │ └── x86_64 │ │ ├── libdobby.a │ │ └── libdobby.so ├── Includes │ ├── curl.h │ └── xorstr.hpp ├── Library.h ├── Log.h ├── README.md ├── Url.h ├── Util.h └── main.cpp ├── LICENSE ├── README.md ├── Windows ├── Core │ ├── Constants.h │ ├── Core.cpp │ ├── Core.h │ └── Unreal │ │ ├── Array.h │ │ ├── Memory.h │ │ └── String.h ├── Main.cpp ├── README.md ├── Sinum.sln ├── Sinum.vcxproj ├── Sinum.vcxproj.filters ├── Sinum │ ├── Sinum.cpp │ └── Sinum.h ├── Utilities │ ├── Windows.h │ └── memcury.h └── framework.h └── iOS ├── Main.m ├── README.md ├── build-linux.sh └── build-mac.sh /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | # Build Files 401 | *.o 402 | *.dylib 403 | *.dll 404 | *.exe 405 | -------------------------------------------------------------------------------- /Android/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | MAIN_LOCAL_PATH := $(call my-dir) 3 | 4 | ifeq ($(TARGET_ABI),armeabi-v7a) 5 | LOCAL_SRC_FILES := Dobby/armeabi-v7a/libdobby.a 6 | else ifeq ($(TARGET_ABI),arm64-v8a) 7 | LOCAL_SRC_FILES := Dobby/arm64-v8a/libdobby.a 8 | else ifeq ($(TARGET_ABI),x86) 9 | LOCAL_SRC_FILES := Dobby/x86/libdobby.a 10 | else ifeq ($(TARGET_ABI),x86_64) 11 | LOCAL_SRC_FILES := Dobby/x86_64/libdobby.a 12 | else 13 | LOCAL_SRC_FILES := Dobby/arm64-v8a/libdobby.a 14 | endif 15 | 16 | LOCAL_MODULE := dobby 17 | include $(PREBUILT_STATIC_LIBRARY) 18 | 19 | include $(CLEAR_VARS) 20 | LOCAL_MODULE := sinum 21 | 22 | LOCAL_CFLAGS := -Wno-error=format-security -fpermissive 23 | LOCAL_CFLAGS += -fno-rtti -fno-exceptions -g --std=c++2a 24 | 25 | LOCAL_STATIC_LIBRARIES := dobby 26 | 27 | LOCAL_C_INCLUDES += $(MAIN_LOCAL_PATH) 28 | 29 | LOCAL_SRC_FILES := Main.cpp 30 | 31 | LOCAL_LDLIBS := -llog 32 | 33 | include $(BUILD_SHARED_LIBRARY) 34 | -------------------------------------------------------------------------------- /Android/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := $(TARGET_ABI) 2 | ifeq ($(APP_ABI),) 3 | APP_ABI := arm64-v8a 4 | endif 5 | 6 | APP_OPTIM := release 7 | APP_PLATFORM := android-21 8 | APP_STL := c++_static 9 | APP_THIN_ARCHIVE := true 10 | APP_PIE := true 11 | 12 | $(info APP_OPTIM is $(APP_OPTIM)) 13 | $(info APP_ABI is $(APP_ABI)) 14 | 15 | ifneq ($(APP_OPTIM), debug) 16 | APP_LDFLAGS += -Wl,--strip-all 17 | APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden 18 | APP_CFLAGS += -g0 -O3 -fomit-frame-pointer -ffunction-sections -fdata-sections 19 | APP_CFLAGS += -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti 20 | endif 21 | -------------------------------------------------------------------------------- /Android/Dobby/arm64-v8a/libdobby.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectnovafn/Sinum/58a3f3732debcb830322e4fdb3781c83312b1eec/Android/Dobby/arm64-v8a/libdobby.a -------------------------------------------------------------------------------- /Android/Dobby/arm64-v8a/libdobby.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectnovafn/Sinum/58a3f3732debcb830322e4fdb3781c83312b1eec/Android/Dobby/arm64-v8a/libdobby.so -------------------------------------------------------------------------------- /Android/Dobby/armeabi-v7a/libdobby.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectnovafn/Sinum/58a3f3732debcb830322e4fdb3781c83312b1eec/Android/Dobby/armeabi-v7a/libdobby.a -------------------------------------------------------------------------------- /Android/Dobby/armeabi-v7a/libdobby.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectnovafn/Sinum/58a3f3732debcb830322e4fdb3781c83312b1eec/Android/Dobby/armeabi-v7a/libdobby.so -------------------------------------------------------------------------------- /Android/Dobby/dobby.h: -------------------------------------------------------------------------------- 1 | #ifndef dobby_h 2 | #define dobby_h 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef uintptr_t addr_t; 12 | typedef uint32_t addr32_t; 13 | typedef uint64_t addr64_t; 14 | 15 | typedef void *dobby_dummy_func_t; 16 | typedef void *asm_func_t; 17 | 18 | #if defined(__arm__) 19 | typedef struct { 20 | uint32_t dummy_0; 21 | uint32_t dummy_1; 22 | 23 | uint32_t dummy_2; 24 | uint32_t sp; 25 | 26 | union { 27 | uint32_t r[13]; 28 | struct { 29 | uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12; 30 | } regs; 31 | } general; 32 | 33 | uint32_t lr; 34 | } DobbyRegisterContext; 35 | #elif defined(__arm64__) || defined(__aarch64__) 36 | #define ARM64_TMP_REG_NDX_0 17 37 | 38 | typedef union _FPReg { 39 | __int128_t q; 40 | struct { 41 | double d1; 42 | double d2; 43 | } d; 44 | struct { 45 | float f1; 46 | float f2; 47 | float f3; 48 | float f4; 49 | } f; 50 | } FPReg; 51 | 52 | // register context 53 | typedef struct { 54 | uint64_t dmmpy_0; // dummy placeholder 55 | uint64_t sp; 56 | 57 | uint64_t dmmpy_1; // dummy placeholder 58 | union { 59 | uint64_t x[29]; 60 | struct { 61 | uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, 62 | x23, x24, x25, x26, x27, x28; 63 | } regs; 64 | } general; 65 | 66 | uint64_t fp; 67 | uint64_t lr; 68 | 69 | union { 70 | FPReg q[32]; 71 | struct { 72 | FPReg q0, q1, q2, q3, q4, q5, q6, q7; 73 | // [!!! READ ME !!!] 74 | // for Arm64, can't access q8 - q31, unless you enable full floating-point register pack 75 | FPReg q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29, 76 | q30, q31; 77 | } regs; 78 | } floating; 79 | } DobbyRegisterContext; 80 | #elif defined(_M_IX86) || defined(__i386__) 81 | typedef struct _RegisterContext { 82 | uint32_t dummy_0; 83 | uint32_t esp; 84 | 85 | uint32_t dummy_1; 86 | uint32_t flags; 87 | 88 | union { 89 | struct { 90 | uint32_t eax, ebx, ecx, edx, ebp, esp, edi, esi; 91 | } regs; 92 | } general; 93 | 94 | } DobbyRegisterContext; 95 | #elif defined(_M_X64) || defined(__x86_64__) 96 | typedef struct { 97 | uint64_t dummy_0; 98 | uint64_t rsp; 99 | 100 | union { 101 | struct { 102 | uint64_t rax, rbx, rcx, rdx, rbp, rsp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15; 103 | } regs; 104 | } general; 105 | 106 | uint64_t dummy_1; 107 | uint64_t flags; 108 | } DobbyRegisterContext; 109 | #endif 110 | 111 | #define install_hook_name(name, fn_ret_t, fn_args_t...) \ 112 | static fn_ret_t fake_##name(fn_args_t); \ 113 | static fn_ret_t (*orig_##name)(fn_args_t); \ 114 | /* __attribute__((constructor)) */ static void install_hook_##name(void *sym_addr) { \ 115 | DobbyHook(sym_addr, (dobby_dummy_func_t)fake_##name, (dobby_dummy_func_t *)&orig_##name); \ 116 | return; \ 117 | } \ 118 | fn_ret_t fake_##name(fn_args_t) 119 | 120 | // memory code patch 121 | int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size); 122 | 123 | // function inline hook 124 | int DobbyHook(void *address, dobby_dummy_func_t replace_func, dobby_dummy_func_t *origin_func); 125 | 126 | // dynamic binary instruction instrument 127 | // for Arm64, can't access q8 - q31, unless enable full floating-point register pack 128 | typedef void (*dobby_instrument_callback_t)(void *address, DobbyRegisterContext *ctx); 129 | int DobbyInstrument(void *address, dobby_instrument_callback_t pre_handler); 130 | 131 | // destroy and restore code patch 132 | int DobbyDestroy(void *address); 133 | 134 | const char *DobbyGetVersion(); 135 | 136 | // symbol resolver 137 | void *DobbySymbolResolver(const char *image_name, const char *symbol_name); 138 | 139 | // import table replace 140 | int DobbyImportTableReplace(char *image_name, char *symbol_name, dobby_dummy_func_t fake_func, 141 | dobby_dummy_func_t *orig_func); 142 | 143 | // for arm, Arm64, try use b xxx instead of ldr absolute indirect branch 144 | // for x86, x64, always use absolute indirect jump 145 | void dobby_enable_near_branch_trampoline(); 146 | void dobby_disable_near_branch_trampoline(); 147 | 148 | #ifdef __cplusplus 149 | } 150 | #endif 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /Android/Dobby/x86_64/libdobby.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectnovafn/Sinum/58a3f3732debcb830322e4fdb3781c83312b1eec/Android/Dobby/x86_64/libdobby.a -------------------------------------------------------------------------------- /Android/Dobby/x86_64/libdobby.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectnovafn/Sinum/58a3f3732debcb830322e4fdb3781c83312b1eec/Android/Dobby/x86_64/libdobby.so -------------------------------------------------------------------------------- /Android/Includes/xorstr.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 - 2021 Justas Masiulis 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef JM_XORSTR_HPP 18 | #define JM_XORSTR_HPP 19 | 20 | #if defined(_M_ARM64) || defined(__aarch64__) || defined(_M_ARM) || defined(__arm__) 21 | #include 22 | #elif defined(_M_X64) || defined(__amd64__) || defined(_M_IX86) || defined(__i386__) 23 | #include 24 | #else 25 | #error Unsupported platform 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define xorstr(str) ::jm::xor_string([]() { return str; }, std::integral_constant{}, std::make_index_sequence<::jm::detail::_buffer_size()>{}) 34 | #define xorstr_(str) xorstr(str).crypt_get() 35 | 36 | #ifdef _MSC_VER 37 | #define XORSTR_FORCEINLINE __forceinline 38 | #else 39 | #define XORSTR_FORCEINLINE __attribute__((always_inline)) inline 40 | #endif 41 | 42 | #define JM_XORSTR_DISABLE_AVX_INTRINSICS 43 | 44 | namespace jm { 45 | 46 | namespace detail { 47 | 48 | template 49 | XORSTR_FORCEINLINE constexpr std::size_t _buffer_size() 50 | { 51 | return ((Size / 16) + (Size % 16 != 0)) * 2; 52 | } 53 | 54 | template 55 | XORSTR_FORCEINLINE constexpr std::uint32_t key4() noexcept 56 | { 57 | std::uint32_t value = Seed; 58 | for(char c : __TIME__) 59 | value = static_cast((value ^ c) * 16777619ull); 60 | return value; 61 | } 62 | 63 | template 64 | XORSTR_FORCEINLINE constexpr std::uint64_t key8() 65 | { 66 | constexpr auto first_part = key4<2166136261 + S>(); 67 | constexpr auto second_part = key4(); 68 | return (static_cast(first_part) << 32) | second_part; 69 | } 70 | 71 | // loads up to 8 characters of string into uint64 and xors it with the key 72 | template 73 | XORSTR_FORCEINLINE constexpr std::uint64_t 74 | load_xored_str8(std::uint64_t key, std::size_t idx, const CharT* str) noexcept 75 | { 76 | using cast_type = typename std::make_unsigned::type; 77 | constexpr auto value_size = sizeof(CharT); 78 | constexpr auto idx_offset = 8 / value_size; 79 | 80 | std::uint64_t value = key; 81 | for(std::size_t i = 0; i < idx_offset && i + idx * idx_offset < N; ++i) 82 | value ^= 83 | (std::uint64_t{ static_cast(str[i + idx * idx_offset]) } 84 | << ((i % idx_offset) * 8 * value_size)); 85 | 86 | return value; 87 | } 88 | 89 | // forces compiler to use registers instead of stuffing constants in rdata 90 | XORSTR_FORCEINLINE std::uint64_t load_from_reg(std::uint64_t value) noexcept 91 | { 92 | #if defined(__clang__) || defined(__GNUC__) 93 | asm("" : "=r"(value) : "0"(value) :); 94 | return value; 95 | #else 96 | volatile std::uint64_t reg = value; 97 | return reg; 98 | #endif 99 | } 100 | 101 | } // namespace detail 102 | 103 | template 104 | class xor_string; 105 | 106 | template 107 | class xor_string, std::index_sequence> { 108 | #ifndef JM_XORSTR_DISABLE_AVX_INTRINSICS 109 | constexpr static inline std::uint64_t alignment = ((Size > 16) ? 32 : 16); 110 | #else 111 | constexpr static inline std::uint64_t alignment = 16; 112 | #endif 113 | 114 | alignas(alignment) std::uint64_t _storage[sizeof...(Keys)]; 115 | 116 | public: 117 | using value_type = CharT; 118 | using size_type = std::size_t; 119 | using pointer = CharT*; 120 | using const_pointer = const CharT*; 121 | 122 | template 123 | XORSTR_FORCEINLINE xor_string(L l, std::integral_constant, std::index_sequence) noexcept 124 | : _storage{ ::jm::detail::load_from_reg((std::integral_constant(Keys, Indices, l())>::value))... } 125 | {} 126 | 127 | XORSTR_FORCEINLINE constexpr size_type size() const noexcept 128 | { 129 | return Size - 1; 130 | } 131 | 132 | XORSTR_FORCEINLINE void crypt() noexcept 133 | { 134 | // everything is inlined by hand because a certain compiler with a certain linker is _very_ slow 135 | #if defined(__clang__) 136 | alignas(alignment) 137 | std::uint64_t arr[]{ ::jm::detail::load_from_reg(Keys)... }; 138 | std::uint64_t* keys = 139 | (std::uint64_t*)::jm::detail::load_from_reg((std::uint64_t)arr); 140 | #else 141 | alignas(alignment) std::uint64_t keys[]{ ::jm::detail::load_from_reg(Keys)... }; 142 | #endif 143 | 144 | #if defined(_M_ARM64) || defined(__aarch64__) || defined(_M_ARM) || defined(__arm__) 145 | #if defined(__clang__) 146 | ((Indices >= sizeof(_storage) / 16 ? static_cast(0) : __builtin_neon_vst1q_v( 147 | reinterpret_cast(_storage) + Indices * 2, 148 | veorq_u64(__builtin_neon_vld1q_v(reinterpret_cast(_storage) + Indices * 2, 51), 149 | __builtin_neon_vld1q_v(reinterpret_cast(keys) + Indices * 2, 51)), 150 | 51)), ...); 151 | #else // GCC, MSVC 152 | ((Indices >= sizeof(_storage) / 16 ? static_cast(0) : vst1q_u64( 153 | reinterpret_cast(_storage) + Indices * 2, 154 | veorq_u64(vld1q_u64(reinterpret_cast(_storage) + Indices * 2), 155 | vld1q_u64(reinterpret_cast(keys) + Indices * 2)))), ...); 156 | #endif 157 | #elif !defined(JM_XORSTR_DISABLE_AVX_INTRINSICS) 158 | ((Indices >= sizeof(_storage) / 32 ? static_cast(0) : _mm256_store_si256( 159 | reinterpret_cast<__m256i*>(_storage) + Indices, 160 | _mm256_xor_si256( 161 | _mm256_load_si256(reinterpret_cast(_storage) + Indices), 162 | _mm256_load_si256(reinterpret_cast(keys) + Indices)))), ...); 163 | 164 | if constexpr(sizeof(_storage) % 32 != 0) 165 | _mm_store_si128( 166 | reinterpret_cast<__m128i*>(_storage + sizeof...(Keys) - 2), 167 | _mm_xor_si128(_mm_load_si128(reinterpret_cast(_storage + sizeof...(Keys) - 2)), 168 | _mm_load_si128(reinterpret_cast(keys + sizeof...(Keys) - 2)))); 169 | #else 170 | ((Indices >= sizeof(_storage) / 16 ? static_cast(0) : _mm_store_si128( 171 | reinterpret_cast<__m128i*>(_storage) + Indices, 172 | _mm_xor_si128(_mm_load_si128(reinterpret_cast(_storage) + Indices), 173 | _mm_load_si128(reinterpret_cast(keys) + Indices)))), ...); 174 | #endif 175 | } 176 | 177 | XORSTR_FORCEINLINE const_pointer get() const noexcept 178 | { 179 | return reinterpret_cast(_storage); 180 | } 181 | 182 | XORSTR_FORCEINLINE pointer get() noexcept 183 | { 184 | return reinterpret_cast(_storage); 185 | } 186 | 187 | XORSTR_FORCEINLINE pointer crypt_get() noexcept 188 | { 189 | // crypt() is inlined by hand because a certain compiler with a certain linker is _very_ slow 190 | #if defined(__clang__) 191 | alignas(alignment) 192 | std::uint64_t arr[]{ ::jm::detail::load_from_reg(Keys)... }; 193 | std::uint64_t* keys = 194 | (std::uint64_t*)::jm::detail::load_from_reg((std::uint64_t)arr); 195 | #else 196 | alignas(alignment) std::uint64_t keys[]{ ::jm::detail::load_from_reg(Keys)... }; 197 | #endif 198 | 199 | #if defined(_M_ARM64) || defined(__aarch64__) || defined(_M_ARM) || defined(__arm__) 200 | #if defined(__clang__) 201 | ((Indices >= sizeof(_storage) / 16 ? static_cast(0) : __builtin_neon_vst1q_v( 202 | reinterpret_cast(_storage) + Indices * 2, 203 | veorq_u64(__builtin_neon_vld1q_v(reinterpret_cast(_storage) + Indices * 2, 51), 204 | __builtin_neon_vld1q_v(reinterpret_cast(keys) + Indices * 2, 51)), 205 | 51)), ...); 206 | #else // GCC, MSVC 207 | ((Indices >= sizeof(_storage) / 16 ? static_cast(0) : vst1q_u64( 208 | reinterpret_cast(_storage) + Indices * 2, 209 | veorq_u64(vld1q_u64(reinterpret_cast(_storage) + Indices * 2), 210 | vld1q_u64(reinterpret_cast(keys) + Indices * 2)))), ...); 211 | #endif 212 | #elif !defined(JM_XORSTR_DISABLE_AVX_INTRINSICS) 213 | ((Indices >= sizeof(_storage) / 32 ? static_cast(0) : _mm256_store_si256( 214 | reinterpret_cast<__m256i*>(_storage) + Indices, 215 | _mm256_xor_si256( 216 | _mm256_load_si256(reinterpret_cast(_storage) + Indices), 217 | _mm256_load_si256(reinterpret_cast(keys) + Indices)))), ...); 218 | 219 | if constexpr(sizeof(_storage) % 32 != 0) 220 | _mm_store_si128( 221 | reinterpret_cast<__m128i*>(_storage + sizeof...(Keys) - 2), 222 | _mm_xor_si128(_mm_load_si128(reinterpret_cast(_storage + sizeof...(Keys) - 2)), 223 | _mm_load_si128(reinterpret_cast(keys + sizeof...(Keys) - 2)))); 224 | #else 225 | ((Indices >= sizeof(_storage) / 16 ? static_cast(0) : _mm_store_si128( 226 | reinterpret_cast<__m128i*>(_storage) + Indices, 227 | _mm_xor_si128(_mm_load_si128(reinterpret_cast(_storage) + Indices), 228 | _mm_load_si128(reinterpret_cast(keys) + Indices)))), ...); 229 | #endif 230 | 231 | return (pointer)(_storage); 232 | } 233 | }; 234 | 235 | template 236 | xor_string(L l, std::integral_constant, std::index_sequence) -> xor_string< 237 | std::remove_const_t>, 238 | Size, 239 | std::integer_sequence()...>, 240 | std::index_sequence>; 241 | 242 | } // namespace jm 243 | 244 | #endif // include guard 245 | -------------------------------------------------------------------------------- /Android/Library.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "Log.h" 15 | #include "Util.h" 16 | 17 | namespace Library 18 | { 19 | void* FindByName(const char* library_name) 20 | { 21 | FILE* fp = fopen("/proc/self/maps", "rt"); 22 | if (fp != NULL) 23 | { 24 | char line[512], mod_name[64]; 25 | void* base; 26 | while (fgets(line, sizeof(line), fp)) 27 | { 28 | if (std::sscanf(line, _("%llx-%*llx %*s %*ld %*s %*d %s"), &base, mod_name)) 29 | { 30 | if (std::strstr(mod_name, library_name)) 31 | { 32 | fclose(fp); 33 | return base; 34 | } 35 | } 36 | } 37 | } 38 | 39 | return nullptr; 40 | } 41 | 42 | bool IsLoaded(const char* lib) 43 | { 44 | char line[512] = { 0 }; 45 | FILE* fp = fopen(_("/proc/self/maps"), _("rt")); 46 | if (fp != NULL) 47 | { 48 | while (fgets(line, sizeof(line), fp)) 49 | { 50 | if (strstr(line, lib)) 51 | return true; 52 | } 53 | fclose(fp); 54 | } 55 | return false; 56 | } 57 | 58 | void WaitFor(const char* lib) 59 | { 60 | while (!IsLoaded(lib)) 61 | { 62 | sleep(1); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Android/Log.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #ifndef LOGGER_H 4 | #define LOGGER_H 5 | #include 6 | 7 | enum daLogType { 8 | daDEBUG = 3, 9 | daERROR = 6, 10 | daINFO = 4, 11 | daWARN = 5 12 | }; 13 | 14 | #define TAG _("sinum") 15 | 16 | #define LOGD(...) ((void)__android_log_print(daDEBUG, TAG, __VA_ARGS__)) 17 | #define LOGE(...) ((void)__android_log_print(daERROR, TAG, __VA_ARGS__)) 18 | #define LOGI(...) ((void)__android_log_print(daINFO, TAG, __VA_ARGS__)) 19 | #define LOGW(...) ((void)__android_log_print(daWARN, TAG, __VA_ARGS__)) 20 | 21 | #endif //LOGGER_H -------------------------------------------------------------------------------- /Android/README.md: -------------------------------------------------------------------------------- 1 | # Sinum Android 2 | 3 | Sinum Android redirects requests via hooking **curl_easy_setopt** and modifying the outgoing url to https://api.novafn.dev. 4 | 5 | ## Building & Usage 6 | 7 | ### Requirements 8 | 9 | - Android NDK - Install [Android Studio](https://developer.android.com/studio) (recommended) or using [Standalone](https://developer.android.com/ndk/downloads) 10 | - [apktool](https://apktool.org/) 11 | 12 | ### Build steps 13 | 14 | 1. Locate the NDK path and the `ndk-build` file. 15 | 2. Copy the full path to it. 16 | 2. ` NDK_PROJECT_PATH=./ NDK_APPLICATION_MK=Application.mk APP_BUILD_SCRIPT=Android.mk TARGET_ABI=arm64-v8a` 17 | 18 | ### Packaging steps 19 | 20 | 1. `apktool decode ./mod/fortnite.apk -o ./mod/apk` 21 | 2. Patch `./smali/com/epicgames/ue4/GameActivity.smali` 22 | ```diff 23 | .method public onCreate(Landroid/os/Bundle;)V 24 | .locals 11 25 | 26 | + const-string v0, "sinum" 27 | + invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V 28 | ``` 29 | - Optional (disables minimal system requirements) 30 | ```diff 31 | .method private processSystemInfo(Ljava/lang/String;Ljava/lang/String;)Z 32 | .locals 29 33 | 34 | + const/4 v0, 0x1 35 | + return v0 36 | 37 | :try_start_0 38 | ``` 39 | 2. `cp ./libs/arm64-v8a/libsinum.so ./mod/apk/lib/arm64-v8a/libsinum.so` 40 | 3. `apktool build ./mod/apk -o ./mod/unsigned.apk` 41 | -------------------------------------------------------------------------------- /Android/Url.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Util.h" 9 | 10 | struct Uri 11 | { 12 | private: 13 | typedef std::string_view::const_iterator iterator_t; 14 | 15 | public: 16 | std::string_view Protocol, Host, Port, Path, QueryString; 17 | 18 | static Uri Parse(const std::string_view& uri) 19 | { 20 | Uri result; 21 | 22 | if (uri.length() == 0) return result; 23 | 24 | iterator_t uriEnd = uri.end(); 25 | 26 | iterator_t queryStart = std::find(uri.begin(), uriEnd, '?'); 27 | 28 | iterator_t protocolStart = uri.begin(); 29 | iterator_t protocolEnd = std::find(protocolStart, uriEnd, ':'); 30 | 31 | if (protocolEnd != uriEnd) 32 | { 33 | std::string_view prot = &*(protocolEnd); 34 | if ((prot.length() > 3) && (prot.substr(0, 3) == _("://"))) 35 | { 36 | result.Protocol = make_string_view(uri, protocolStart, protocolEnd); 37 | protocolEnd += 3; 38 | } 39 | else protocolEnd = uri.begin(); 40 | } 41 | else protocolEnd = uri.begin(); 42 | 43 | iterator_t hostStart = protocolEnd; 44 | iterator_t pathStart = std::find(hostStart, uriEnd, '/'); 45 | 46 | iterator_t hostEnd = std::find(protocolEnd, (pathStart != uriEnd) ? pathStart : queryStart, ':'); 47 | 48 | result.Host = make_string_view(uri, hostStart, hostEnd); 49 | 50 | if ((hostEnd != uriEnd) && ((&*(hostEnd))[0] == ':')) 51 | { 52 | ++hostEnd; 53 | iterator_t portEnd = (pathStart != uriEnd) ? pathStart : queryStart; 54 | result.Port = make_string_view(uri, hostEnd, portEnd); 55 | } 56 | 57 | if (pathStart != uriEnd) result.Path = make_string_view(uri, pathStart, queryStart); 58 | 59 | if (queryStart != uriEnd) result.QueryString = make_string_view(uri, queryStart, uri.end()); 60 | 61 | return result; 62 | } 63 | 64 | static std::string CreateUri(std::string_view Protocol, std::string_view Host, std::string_view Port, std::string_view Path, std::string_view QueryString) 65 | { 66 | std::ostringstream str; 67 | if (!Protocol.empty()) 68 | { 69 | str.write(Protocol.data(), Protocol.size()); 70 | str.write(_("://"), 3); 71 | } 72 | str.write(Host.data(), Host.size()); 73 | if (!Port.empty()) 74 | { 75 | str.write(_(":"), 1); 76 | str.write(Port.data(), Port.size()); 77 | } 78 | if (!Path.empty()) 79 | { 80 | str.write(Path.data(), Path.size()); 81 | } 82 | if (!QueryString.empty()) 83 | { 84 | str.write(QueryString.data(), QueryString.size()); 85 | } 86 | return str.str(); 87 | } 88 | 89 | private: 90 | static constexpr std::string_view make_string_view(const std::string_view& base, iterator_t first, iterator_t last) 91 | { 92 | return base.substr(std::distance(base.begin(), first), std::distance(first, last)); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /Android/Util.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "Includes/xorstr.hpp" 13 | 14 | #define _(str) xorstr(str).crypt_get() 15 | 16 | namespace Util 17 | { 18 | bool IsPointerBad(void* p) 19 | { 20 | int fh = open((const char*)p, 0, 0); 21 | int e = errno; 22 | 23 | if (-1 == fh && e == EFAULT) 24 | { 25 | return true; 26 | } 27 | else if (fh != -1) 28 | { 29 | close(fh); 30 | } 31 | 32 | return false; 33 | } 34 | } -------------------------------------------------------------------------------- /Android/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #include "Url.h" 4 | #include "Library.h" 5 | #include "Util.h" 6 | #include "Dobby/dobby.h" 7 | #include "Includes/curl.h" 8 | #include 9 | 10 | #define URL_PROTOCOL_HTTP _("https") 11 | #define URL_HOST _("api.novafn.dev") 12 | #define URL_PORT std::string() 13 | 14 | install_hook_name(curl_easy_setopt, void*, void* curl, int option, void* arg) 15 | { 16 | // LOGI(_("curl_easy_setopt: %i"), option); 17 | 18 | if (!Util::IsPointerBad(arg) && option == CURLOPT_URL) 19 | { 20 | std::string url = reinterpret_cast(arg); 21 | 22 | Uri uri = Uri::Parse(url); 23 | 24 | if (uri.Host.ends_with(_("ol.epicgames.com")) 25 | || uri.Host.ends_with(_(".akamaized.net")) 26 | || uri.Host.ends_with(_("on.epicgames.com"))) 27 | { 28 | url = Uri::CreateUri(URL_PROTOCOL_HTTP, URL_HOST, URL_PORT, uri.Path, uri.QueryString); 29 | } 30 | 31 | return orig_curl_easy_setopt(curl, option, (void*)url.c_str()); 32 | } 33 | else if (option == CURLOPT_SSL_VERIFYPEER) 34 | { 35 | return orig_curl_easy_setopt(curl, option, (void*)0); 36 | } 37 | 38 | return orig_curl_easy_setopt(curl, option, arg); 39 | } 40 | 41 | void* Main(void*) 42 | { 43 | auto Base = dlopen(_("libUE4.so"), RTLD_NOW); 44 | 45 | auto curl_easy_setopt = dlsym(Base, _("curl_easy_setopt")); 46 | 47 | if (!curl_easy_setopt) return nullptr; 48 | 49 | install_hook_curl_easy_setopt(curl_easy_setopt); 50 | 51 | return nullptr; 52 | } 53 | 54 | __attribute__((constructor)) void libsinum_main() 55 | { 56 | pthread_t ptid; 57 | pthread_create(&ptid, NULL, Main, NULL); 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Project Nova LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including, without limitation, the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | **Anti-Usage for Project Era Clause:** 14 | The Software or any derivative works of the Software cannot be used, directly or indirectly, for any purpose related to "Project Era" or any project associated with "Project Era." 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, 21 | OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sinum 2 | 3 | Sinum is used to redirect requests from a target URL, such as ol.epicgames.com, to another server, like api.novafn.dev. 4 | 5 | # Support 6 | 7 | Sinum provides support for the following platforms: 8 | 9 | - [Android](Android) through a custom .so file 10 | - [iOS](iOS) through dylib injection 11 | - [Windows](Windows) through DLL injection 12 | - [Xbox](Windows) through DLL injection 13 | 14 | # Contributions 15 | 16 | - Consult [Ender](https://github.com/Ender-0001) for any fundamental changes. 17 | - Use Common Sense. 18 | 19 | # Contributors 20 | 21 | - Project Nova Developers 22 | - [Kyiro](https://github.com/kyiro) for [Android](Android) 23 | - [Ender](https://github.com/ender-0001) for [Windows](Windows) & [iOS](iOS) 24 | - [Kemo](https://github.com/kem0x) 25 | - Helping with [iOS](iOS) & [Android](Android) 26 | - Creating [Memcury](https://github.com/kem0x/Memcury) 27 | - [Lupus](https://github.com/EZFNDEV) 28 | - Adding Chapter 2 to [Windows](Windows) 29 | 30 | # License Disclosure 31 | 32 | **Portions of the materials used are trademarks and/or copyrighted works of Epic Games, Inc. All rights reserved by Epic. This material is not official and is not endorsed by Epic.** 33 | -------------------------------------------------------------------------------- /Windows/Core/Constants.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | 5 | namespace Constants 6 | { 7 | constexpr auto API_URL = L"https://api.novafn.dev"; 8 | 9 | constexpr auto ProcessRequest = L"Could not set libcurl options for easy handle, processing HTTP request failed. Increase verbosity for additional information."; 10 | constexpr auto ProcessRequest_C2 = L"STAT_FCurlHttpRequest_ProcessRequest"; 11 | constexpr auto URLOffset = L"ProcessRequest failed. URL '%s' is not a valid HTTP request. %p"; 12 | constexpr auto Realloc = L"AbilitySystem.Debug.NextTarget"; 13 | } -------------------------------------------------------------------------------- /Windows/Core/Core.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #include "Core.h" 4 | 5 | void Core::Init() 6 | { 7 | FMemory::_Realloc = Memcury::Scanner::FindStringRef(Constants::Realloc) 8 | .ScanFor({ Memcury::ASM::MNEMONIC::CALL }) 9 | .RelativeOffset(1) 10 | .GetAs(); 11 | } -------------------------------------------------------------------------------- /Windows/Core/Core.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | 5 | #include "..\Utilities\memcury.h" 6 | 7 | #include "Constants.h" 8 | 9 | #include "Unreal\Memory.h" 10 | #include "Unreal\Array.h" 11 | #include "Unreal\String.h" 12 | 13 | namespace Core 14 | { 15 | void Init(); 16 | } -------------------------------------------------------------------------------- /Windows/Core/Unreal/Array.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include 5 | #include "Memory.h" 6 | 7 | template 8 | class TArray 9 | { 10 | friend class FString; 11 | 12 | T* Data; 13 | int32_t NumElements; 14 | int32_t MaxElements; 15 | 16 | public: 17 | 18 | inline TArray() 19 | { 20 | Data = nullptr; 21 | NumElements = 0; 22 | MaxElements = 0; 23 | }; 24 | 25 | inline void Free() 26 | { 27 | FMemory::Free(Data); 28 | Data = nullptr; 29 | NumElements = 0; 30 | MaxElements = 0; 31 | } 32 | 33 | inline void Reset() 34 | { 35 | Free(); 36 | } 37 | 38 | inline auto GetData() 39 | { 40 | return Data; 41 | } 42 | 43 | inline int GetCount() const 44 | { 45 | return NumElements; 46 | } 47 | 48 | inline int Num() const 49 | { 50 | return NumElements; 51 | } 52 | 53 | inline auto& Get(const int Index) 54 | { 55 | return Data[Index]; 56 | } 57 | 58 | inline auto& First() 59 | { 60 | return Get(0); 61 | } 62 | 63 | inline auto GetRef(const int Index, int Size = sizeof(T)) 64 | { 65 | return (T*)((uint8_t*)Data + (Index * Size)); 66 | } 67 | 68 | inline T& operator[](int i) 69 | { 70 | return Get(i); 71 | }; 72 | 73 | inline const T& operator[](int i) const 74 | { 75 | return Get(i); 76 | }; 77 | 78 | inline bool Remove(const int Index, int Size = sizeof(T)) 79 | { 80 | if (Index < NumElements) 81 | { 82 | if (Index != NumElements - 1) 83 | Get(Index) = Get(NumElements - 1); 84 | 85 | --NumElements; 86 | 87 | return true; 88 | } 89 | return false; 90 | }; 91 | 92 | inline bool Any(std::function Func) 93 | { 94 | for (int i = 0; i < NumElements; ++i) 95 | { 96 | if (Func(Get(i))) 97 | return true; 98 | } 99 | return false; 100 | } 101 | 102 | inline T Select(std::function Func) 103 | { 104 | for (int i = 0; i < NumElements; ++i) 105 | { 106 | if (Func(Get(i))) 107 | return Get(i); 108 | } 109 | 110 | return NULL; 111 | } 112 | 113 | inline void ForEach(std::function Func) 114 | { 115 | for (int i = 0; i < NumElements; ++i) 116 | { 117 | Func(Get(i)); 118 | } 119 | } 120 | 121 | inline int Count(std::function Func) 122 | { 123 | int Num = 0; 124 | 125 | for (int i = 0; i < NumElements; ++i) 126 | { 127 | if (Func(Get(i))) 128 | Num++; 129 | } 130 | return Num; 131 | } 132 | 133 | inline int Find(const T& Item) 134 | { 135 | for (int i = 0; i < NumElements; i++) 136 | { 137 | if (this->operator[](i) == Item) 138 | return i; 139 | } 140 | 141 | return -1; 142 | } 143 | }; -------------------------------------------------------------------------------- /Windows/Core/Unreal/Memory.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | class FMemory 8 | { 9 | public: 10 | static inline void* (*_Realloc)(void*, size_t, int64_t); 11 | 12 | static void Free(void* Data) 13 | { 14 | _Realloc(Data, 0, 0); 15 | } 16 | 17 | static void* Malloc(size_t Size) 18 | { 19 | return _Realloc(0, Size, 0); 20 | } 21 | 22 | static void* Realloc(void* Data, size_t NewSize) 23 | { 24 | return _Realloc(Data, NewSize, 0); 25 | } 26 | 27 | static void* Memmove(void* Dest, const void* Src, size_t Count) 28 | { 29 | return memmove(Dest, Src, Count); 30 | } 31 | 32 | static int Memcmp(const void* Buf1, const void* Buf2, size_t Count) 33 | { 34 | return memcmp(Buf1, Buf2, Count); 35 | } 36 | 37 | static void* Memset(void* Dest, uint8_t Char, size_t Count) 38 | { 39 | return memset(Dest, Char, Count); 40 | } 41 | 42 | template< class T > 43 | static void Memset(T& Src, uint8_t ValueToSet) 44 | { 45 | Memset(&Src, ValueToSet, sizeof(T)); 46 | } 47 | 48 | static void* Memzero(void* Dest, size_t Count) 49 | { 50 | return ZeroMemory(Dest, Count); 51 | } 52 | 53 | template 54 | static void Memzero(T& Src) 55 | { 56 | Memzero(&Src, sizeof(T)); 57 | } 58 | 59 | static void* Memcpy(void* Dest, const void* Src, size_t Count) 60 | { 61 | return memcpy(Dest, Src, Count); 62 | } 63 | 64 | template 65 | static void Memcpy(T& Dest, const T& Src) 66 | { 67 | Memcpy(&Dest, &Src, sizeof(T)); 68 | } 69 | 70 | static void* Calloc(size_t NumElements, size_t ElementSize) 71 | { 72 | auto TotalSize = NumElements * ElementSize; 73 | auto Data = FMemory::Malloc(TotalSize); 74 | 75 | if (!Data) 76 | return NULL; 77 | 78 | FMemory::Memzero(Data, TotalSize); 79 | 80 | return Data; 81 | } 82 | 83 | static char* Strdup(const char* Str) 84 | { 85 | auto StrLen = strlen(Str) + 1; 86 | auto StrDup = (char*)FMemory::Malloc(StrLen); 87 | 88 | FMemory::Memcpy(StrDup, Str, StrLen); 89 | 90 | return StrDup; 91 | } 92 | }; -------------------------------------------------------------------------------- /Windows/Core/Unreal/String.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include "Array.h" 5 | #include "Memory.h" 6 | 7 | class FString : private TArray 8 | { 9 | public: 10 | 11 | inline FString() 12 | { 13 | Data = nullptr; 14 | NumElements = 0; 15 | MaxElements = 0; 16 | } 17 | 18 | inline FString(const char* Other) 19 | { 20 | if (Other) 21 | { 22 | auto NumCharacters = (int)std::strlen(Other); 23 | MaxElements = NumElements = NumCharacters + 1; 24 | 25 | Data = static_cast(FMemory::Malloc(NumElements * sizeof(wchar_t))); 26 | 27 | size_t ConvertedChars = 0; 28 | mbstowcs_s(&ConvertedChars, Data, NumElements, Other, _TRUNCATE); 29 | } 30 | else 31 | { 32 | MaxElements = NumElements = 0; 33 | Data = nullptr; 34 | } 35 | }; 36 | 37 | inline FString(const wchar_t* Other) 38 | { 39 | MaxElements = NumElements = *Other ? (int)std::wcslen(Other) + 1 : 0; 40 | 41 | if (NumElements && Other) 42 | { 43 | Data = static_cast(FMemory::Malloc(NumElements * 2)); 44 | 45 | memcpy_s(Data, NumElements * 2, Other, NumElements * 2); 46 | } 47 | }; 48 | 49 | 50 | inline auto c_str() 51 | { 52 | return Data; 53 | } 54 | }; -------------------------------------------------------------------------------- /Windows/Main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #include "framework.h" 4 | 5 | static void Main() 6 | { 7 | Sleep(7500); 8 | 9 | Core::Init(); 10 | Sinum::Init(); 11 | } 12 | 13 | bool DllMain(HMODULE hModule, DWORD dwReason, void* lpReserved) 14 | { 15 | if (dwReason == DLL_PROCESS_ATTACH) 16 | { 17 | Windows::Thread::Create(Main); 18 | } 19 | 20 | return true; 21 | } -------------------------------------------------------------------------------- /Windows/README.md: -------------------------------------------------------------------------------- 1 | # Sinum Windows 2 | 3 | Sinum Windows redirects requests via hooking **FCurlHttpRequest::ProcessRequest** and modifying the outgoing url to https://api.novafn.dev. 4 | -------------------------------------------------------------------------------- /Windows/Sinum.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34031.279 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Sinum", "Sinum.vcxproj", "{E7291B57-1B5B-497C-9C2E-C78A556A06CF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Release|x64 = Release|x64 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {E7291B57-1B5B-497C-9C2E-C78A556A06CF}.Release|x64.ActiveCfg = Release|x64 14 | {E7291B57-1B5B-497C-9C2E-C78A556A06CF}.Release|x64.Build.0 = Release|x64 15 | EndGlobalSection 16 | GlobalSection(SolutionProperties) = preSolution 17 | HideSolutionNode = FALSE 18 | EndGlobalSection 19 | GlobalSection(ExtensibilityGlobals) = postSolution 20 | SolutionGuid = {7975A2E1-0078-4AF8-AB10-0A71112857A5} 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Windows/Sinum.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | Win32Proj 24 | {e7291b57-1b5b-497c-9c2e-c78a556a06cf} 25 | Sinum 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 78 | true 79 | Use 80 | pch.h 81 | 82 | 83 | Windows 84 | true 85 | false 86 | 87 | 88 | 89 | 90 | Level3 91 | true 92 | true 93 | true 94 | WIN32;NDEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 95 | true 96 | Use 97 | pch.h 98 | stdcpplatest 99 | 100 | 101 | Windows 102 | true 103 | true 104 | true 105 | false 106 | 107 | 108 | 109 | 110 | Level3 111 | true 112 | _DEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 113 | true 114 | Use 115 | pch.h 116 | 117 | 118 | Windows 119 | true 120 | false 121 | 122 | 123 | 124 | 125 | Level3 126 | true 127 | true 128 | true 129 | NDEBUG;SINUM_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 130 | true 131 | NotUsing 132 | pch.h 133 | stdcpplatest 134 | MaxSpeed 135 | 136 | 137 | Windows 138 | true 139 | true 140 | false 141 | false 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /Windows/Sinum.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | Source Files 55 | 56 | 57 | -------------------------------------------------------------------------------- /Windows/Sinum/Sinum.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #include "Sinum.h" 4 | 5 | bool Sinum::ProcessRequestHook(FCurlHttpRequest* Request) 6 | { 7 | std::wstring URL(Request->GetURL().c_str()); 8 | size_t PathIndex = URL.find(L"ol.epicgames.com"); 9 | 10 | if (PathIndex != std::wstring::npos) 11 | { 12 | auto Path = URL.substr(PathIndex + 16); 13 | auto NewURL = Constants::API_URL + Path; 14 | 15 | Request->SetURL(NewURL.c_str()); 16 | } 17 | 18 | return _ProcessRequest(Request); 19 | } 20 | 21 | void Sinum::Init() 22 | { 23 | auto StringRef = Memcury::Scanner::FindStringRef(Constants::ProcessRequest); 24 | if (StringRef.IsValid()) 25 | { 26 | _ProcessRequest = StringRef 27 | .ScanFor({ 0x48, 0x81, 0xEC }, false) 28 | .ScanFor({ 0x40 }, false) 29 | .GetAs(); 30 | } 31 | else 32 | { 33 | _ProcessRequest = Memcury::Scanner::FindStringRef(Constants::ProcessRequest_C2) 34 | .ScanFor({ 0x4C, 0x8B, 0xDC }, false) 35 | .GetAs(); 36 | } 37 | 38 | *Memcury::Scanner::FindPointerRef(_ProcessRequest) 39 | .GetAs() = ProcessRequestHook; 40 | } -------------------------------------------------------------------------------- /Windows/Sinum/Sinum.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include "../framework.h" 5 | 6 | class FCurlHttpRequest 7 | { 8 | private: 9 | void** VTable; 10 | 11 | public: 12 | 13 | FString GetURL() 14 | { 15 | FString Result; 16 | return ((FString& (*)(FCurlHttpRequest*, FString&))(*VTable))(this, Result); 17 | } 18 | 19 | void SetURL(FString URL) 20 | { 21 | ((void (*)(FCurlHttpRequest*, FString&))(VTable[10]))(this, URL); 22 | } 23 | }; 24 | 25 | namespace Sinum 26 | { 27 | static bool (*_ProcessRequest)(FCurlHttpRequest*); 28 | static bool ProcessRequestHook(FCurlHttpRequest* Request); 29 | 30 | void Init(); 31 | } -------------------------------------------------------------------------------- /Windows/Utilities/Windows.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include "../framework.h" 5 | 6 | namespace Windows 7 | { 8 | namespace Thread 9 | { 10 | static HANDLE Create(void* Routine, void* Param = NULL) 11 | { 12 | return CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Routine, Param, 0, NULL); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Windows/Utilities/memcury.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Memcury is a single-header file library for memory manipulation in C++. 5 | 6 | Containers: 7 | -PE::Address: A pointer container. 8 | -PE::Section: Portable executable section container for internal usage. 9 | 10 | Modules: 11 | -Scanner: 12 | -Constructors: 13 | -Default: Takes a pointer to start the scanning from. 14 | -FindPattern: Finds a pattern in memory. 15 | -FindStringRef: Finds a string reference in memory, supports all types of strings. 16 | -Functions: 17 | -SetTargetModule: Sets the target module for the scanner. 18 | -ScanFor: Scans for a byte(s) near the current address. 19 | -FindFunctionBoundary: Finds the boundary of a function near the current address. 20 | -RelativeOffset: Gets the relative offset of the current address. 21 | -AbsoluteOffset: Gets the absolute offset of the current address. 22 | -GetAs: Gets the current address as a type. 23 | -Get: Gets the current address as an int64. 24 | 25 | -TrampolineHook: 26 | -Constructors: 27 | -Default: Takes a pointer pointer to the target function and a pointer to the hook function. 28 | -Functions: 29 | -Commit: Commits the hook. 30 | -Revert: Reverts the hook. 31 | -Toggle: Toggles the hook on\off. 32 | 33 | -VEHHook: 34 | -Functions: 35 | -Init: Initializes the VEH Hook system. 36 | -AddHook: Adds a hook to the VEH Hook system. 37 | -RemoveHook: Removes a hook from the VEH Hook system. 38 | */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #pragma comment(lib, "Dbghelp.lib") 50 | 51 | #define MemcuryAssert(cond) \ 52 | if (!(cond)) \ 53 | { \ 54 | MessageBoxA(nullptr, #cond, __FUNCTION__, MB_ICONERROR | MB_OK); \ 55 | Memcury::Safety::FreezeCurrentThread(); \ 56 | } 57 | 58 | #define MemcuryAssertM(cond, msg) \ 59 | if (!(cond)) \ 60 | { \ 61 | MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ 62 | Memcury::Safety::FreezeCurrentThread(); \ 63 | } 64 | 65 | #define MemcuryThrow(msg) \ 66 | MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \ 67 | Memcury::Safety::FreezeCurrentThread(); 68 | 69 | namespace Memcury 70 | { 71 | extern "C" IMAGE_DOS_HEADER __ImageBase; 72 | 73 | inline auto GetCurrentModule() -> HMODULE 74 | { 75 | return reinterpret_cast(&__ImageBase); 76 | } 77 | 78 | namespace Util 79 | { 80 | template 81 | constexpr static auto IsInRange(T value, T min, T max) -> bool 82 | { 83 | return value >= min && value < max; 84 | } 85 | 86 | constexpr auto StrHash(const char* str, int h = 0) -> unsigned int 87 | { 88 | return !str[h] ? 5381 : (StrHash(str, h + 1) * 33) ^ str[h]; 89 | } 90 | 91 | inline auto IsSamePage(void* A, void* B) -> bool 92 | { 93 | MEMORY_BASIC_INFORMATION InfoA; 94 | if (!VirtualQuery(A, &InfoA, sizeof(InfoA))) 95 | { 96 | return true; 97 | } 98 | 99 | MEMORY_BASIC_INFORMATION InfoB; 100 | if (!VirtualQuery(B, &InfoB, sizeof(InfoB))) 101 | { 102 | return true; 103 | } 104 | 105 | return InfoA.BaseAddress == InfoB.BaseAddress; 106 | } 107 | 108 | inline auto GetModuleStartAndEnd() -> std::pair 109 | { 110 | auto HModule = GetCurrentModule(); 111 | auto NTHeaders = reinterpret_cast((uintptr_t)HModule + reinterpret_cast((uintptr_t)HModule)->e_lfanew); 112 | 113 | uintptr_t dllStart = (uintptr_t)HModule; 114 | uintptr_t dllEnd = (uintptr_t)HModule + NTHeaders->OptionalHeader.SizeOfImage; 115 | 116 | return { dllStart, dllEnd }; 117 | } 118 | 119 | inline auto CopyToClipboard(std::string str) 120 | { 121 | auto mem = GlobalAlloc(GMEM_FIXED, str.size() + 1); 122 | memcpy(mem, str.c_str(), str.size() + 1); 123 | 124 | OpenClipboard(nullptr); 125 | EmptyClipboard(); 126 | SetClipboardData(CF_TEXT, mem); 127 | CloseClipboard(); 128 | 129 | GlobalFree(mem); 130 | } 131 | } 132 | 133 | namespace Safety 134 | { 135 | enum class ExceptionMode 136 | { 137 | None, 138 | CatchDllExceptionsOnly, 139 | CatchAllExceptions 140 | }; 141 | 142 | static auto FreezeCurrentThread() -> void 143 | { 144 | SuspendThread(GetCurrentThread()); 145 | } 146 | 147 | static auto PrintStack(CONTEXT* ctx) -> void 148 | { 149 | STACKFRAME64 stack; 150 | memset(&stack, 0, sizeof(STACKFRAME64)); 151 | 152 | auto process = GetCurrentProcess(); 153 | auto thread = GetCurrentThread(); 154 | 155 | SymInitialize(process, NULL, TRUE); 156 | 157 | bool result; 158 | DWORD64 displacement = 0; 159 | 160 | char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]{ 0 }; 161 | char name[256]{ 0 }; 162 | char module[256]{ 0 }; 163 | 164 | PSYMBOL_INFO symbolInfo = (PSYMBOL_INFO)buffer; 165 | 166 | for (ULONG frame = 0;; frame++) 167 | { 168 | result = StackWalk64( 169 | IMAGE_FILE_MACHINE_AMD64, 170 | process, 171 | thread, 172 | &stack, 173 | ctx, 174 | NULL, 175 | SymFunctionTableAccess64, 176 | SymGetModuleBase64, 177 | NULL); 178 | 179 | if (!result) 180 | break; 181 | 182 | symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); 183 | symbolInfo->MaxNameLen = MAX_SYM_NAME; 184 | SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbolInfo); 185 | 186 | HMODULE hModule = NULL; 187 | lstrcpyA(module, ""); 188 | GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (const wchar_t*)(stack.AddrPC.Offset), &hModule); 189 | 190 | if (hModule != NULL) 191 | GetModuleFileNameA(hModule, module, 256); 192 | 193 | printf("[%lu] Name: %s - Address: %p - Module: %s\n", frame, symbolInfo->Name, (void*)symbolInfo->Address, module); 194 | } 195 | } 196 | 197 | template 198 | auto MemcuryGlobalHandler(EXCEPTION_POINTERS* ExceptionInfo) -> long 199 | { 200 | auto [dllStart, dllEnd] = Util::GetModuleStartAndEnd(); 201 | 202 | if constexpr (mode == ExceptionMode::CatchDllExceptionsOnly) 203 | { 204 | if (!Util::IsInRange(ExceptionInfo->ContextRecord->Rip, dllStart, dllEnd)) 205 | { 206 | return EXCEPTION_CONTINUE_SEARCH; 207 | } 208 | } 209 | 210 | 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); 211 | if (MessageBoxA(nullptr, message.c_str(), "Error", MB_ICONERROR | MB_YESNO) == IDYES) 212 | { 213 | std::string clip = std::format("{:x}", ExceptionInfo->ContextRecord->Rip); 214 | Util::CopyToClipboard(clip); 215 | } 216 | 217 | PrintStack(ExceptionInfo->ContextRecord); 218 | 219 | FreezeCurrentThread(); 220 | 221 | return EXCEPTION_EXECUTE_HANDLER; 222 | } 223 | 224 | template 225 | static auto SetExceptionMode() -> void 226 | { 227 | SetUnhandledExceptionFilter(MemcuryGlobalHandler); 228 | } 229 | } 230 | 231 | namespace Globals 232 | { 233 | constexpr const bool bLogging = true; 234 | 235 | inline const char* moduleName = nullptr; 236 | } 237 | 238 | namespace ASM 239 | { 240 | //@todo: this whole namespace needs a rework, should somehow make this more modern and less ugly. 241 | enum MNEMONIC : uint8_t 242 | { 243 | JMP_REL8 = 0xEB, 244 | JMP_REL32 = 0xE9, 245 | JMP_EAX = 0xE0, 246 | CALL = 0xE8, 247 | LEA = 0x8D, 248 | CDQ = 0x99, 249 | CMOVL = 0x4C, 250 | CMOVS = 0x48, 251 | CMOVNS = 0x49, 252 | NOP = 0x90, 253 | INT3 = 0xCC, 254 | RETN_REL8 = 0xC2, 255 | RETN = 0xC3, 256 | POP = 0x58, 257 | MAXOP = 0x5F, 258 | CMOVNO = 0x41, 259 | NONE = 0x00, 260 | PUSH = 0x40 261 | }; 262 | 263 | constexpr int SIZE_OF_JMP_RELATIVE_INSTRUCTION = 5; 264 | constexpr int SIZE_OF_JMP_ABSLOUTE_INSTRUCTION = 13; 265 | 266 | constexpr auto MnemonicToString(MNEMONIC e) -> const char* 267 | { 268 | switch (e) 269 | { 270 | case JMP_REL8: 271 | return "JMP_REL8"; 272 | case JMP_REL32: 273 | return "JMP_REL32"; 274 | case JMP_EAX: 275 | return "JMP_EAX"; 276 | case CALL: 277 | return "CALL"; 278 | case LEA: 279 | return "LEA"; 280 | case CDQ: 281 | return "CDQ"; 282 | case CMOVL: 283 | return "CMOVL"; 284 | case CMOVS: 285 | return "CMOVS"; 286 | case CMOVNS: 287 | return "CMOVNS"; 288 | case NOP: 289 | return "NOP"; 290 | case INT3: 291 | return "INT3"; 292 | case RETN_REL8: 293 | return "RETN_REL8"; 294 | case RETN: 295 | return "RETN"; 296 | case NONE: 297 | return "NONE"; 298 | default: 299 | return "UNKNOWN"; 300 | } 301 | } 302 | 303 | constexpr auto Mnemonic(const char* s) -> MNEMONIC 304 | { 305 | switch (Util::StrHash(s)) 306 | { 307 | case Util::StrHash("JMP_REL8"): 308 | return JMP_REL8; 309 | case Util::StrHash("JMP_REL32"): 310 | return JMP_REL32; 311 | case Util::StrHash("JMP_EAX"): 312 | return JMP_EAX; 313 | case Util::StrHash("CALL"): 314 | return CALL; 315 | case Util::StrHash("LEA"): 316 | return LEA; 317 | case Util::StrHash("CDQ"): 318 | return CDQ; 319 | case Util::StrHash("CMOVL"): 320 | return CMOVL; 321 | case Util::StrHash("CMOVS"): 322 | return CMOVS; 323 | case Util::StrHash("CMOVNS"): 324 | return CMOVNS; 325 | case Util::StrHash("NOP"): 326 | return NOP; 327 | case Util::StrHash("INT3"): 328 | return INT3; 329 | case Util::StrHash("RETN_REL8"): 330 | return RETN_REL8; 331 | case Util::StrHash("RETN"): 332 | return RETN; 333 | default: 334 | return NONE; 335 | } 336 | } 337 | 338 | inline auto byteIsA(uint8_t byte, MNEMONIC opcode) -> bool 339 | { 340 | return byte == opcode; 341 | } 342 | 343 | inline auto byteIsAscii(uint8_t byte) -> bool 344 | { 345 | static constexpr bool isAscii[0x100] = { 346 | false, false, false, false, false, false, false, false, false, true, true, false, false, true, false, false, 347 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 348 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 349 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 350 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 351 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 352 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 353 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, 354 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 355 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 356 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 357 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 358 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 359 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 360 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, 361 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false 362 | }; 363 | 364 | return isAscii[byte]; 365 | } 366 | 367 | inline bool isJump(uint8_t byte) 368 | { 369 | return byte >= 0x70 && byte <= 0x7F; 370 | } 371 | 372 | static auto pattern2bytes(const char* pattern) -> std::vector 373 | { 374 | auto bytes = std::vector{}; 375 | const auto start = const_cast(pattern); 376 | const auto end = const_cast(pattern) + strlen(pattern); 377 | 378 | for (auto current = start; current < end; ++current) 379 | { 380 | if (*current == '?') 381 | { 382 | ++current; 383 | if (*current == '?') 384 | ++current; 385 | bytes.push_back(-1); 386 | } 387 | else 388 | { 389 | bytes.push_back(strtoul(current, ¤t, 16)); 390 | } 391 | } 392 | return bytes; 393 | } 394 | } 395 | 396 | namespace PE 397 | { 398 | inline auto SetCurrentModule(const char* moduleName) -> void 399 | { 400 | Globals::moduleName = moduleName; 401 | } 402 | 403 | inline auto GetModuleBase() -> uintptr_t 404 | { 405 | return reinterpret_cast(GetModuleHandleA(Globals::moduleName)); 406 | } 407 | 408 | inline auto GetDOSHeader() -> PIMAGE_DOS_HEADER 409 | { 410 | return reinterpret_cast(GetModuleBase()); 411 | } 412 | 413 | inline auto GetNTHeaders() -> PIMAGE_NT_HEADERS 414 | { 415 | return reinterpret_cast(GetModuleBase() + GetDOSHeader()->e_lfanew); 416 | } 417 | 418 | class Address 419 | { 420 | uintptr_t _address; 421 | 422 | public: 423 | Address() 424 | { 425 | _address = 0; 426 | } 427 | 428 | Address(uintptr_t address) 429 | : _address(address) 430 | { 431 | } 432 | 433 | Address(void* address) 434 | : _address(reinterpret_cast(address)) 435 | { 436 | } 437 | 438 | auto operator=(uintptr_t address) -> Address 439 | { 440 | _address = address; 441 | return *this; 442 | } 443 | 444 | auto operator=(void* address) -> Address 445 | { 446 | _address = reinterpret_cast(address); 447 | return *this; 448 | } 449 | 450 | auto operator+(uintptr_t offset) -> Address 451 | { 452 | return Address(_address + offset); 453 | } 454 | 455 | bool operator>(uintptr_t offset) 456 | { 457 | return _address > offset; 458 | } 459 | 460 | bool operator>(Address address) 461 | { 462 | return _address > address._address; 463 | } 464 | 465 | bool operator<(uintptr_t offset) 466 | { 467 | return _address < offset; 468 | } 469 | 470 | bool operator<(Address address) 471 | { 472 | return _address < address._address; 473 | } 474 | 475 | bool operator>=(uintptr_t offset) 476 | { 477 | return _address >= offset; 478 | } 479 | 480 | bool operator>=(Address address) 481 | { 482 | return _address >= address._address; 483 | } 484 | 485 | bool operator<=(uintptr_t offset) 486 | { 487 | return _address <= offset; 488 | } 489 | 490 | bool operator<=(Address address) 491 | { 492 | return _address <= address._address; 493 | } 494 | 495 | bool operator==(uintptr_t offset) 496 | { 497 | return _address == offset; 498 | } 499 | 500 | bool operator==(Address address) 501 | { 502 | return _address == address._address; 503 | } 504 | 505 | bool operator!=(uintptr_t offset) 506 | { 507 | return _address != offset; 508 | } 509 | 510 | bool operator!=(Address address) 511 | { 512 | return _address != address._address; 513 | } 514 | 515 | auto RelativeOffset(uint32_t offset) -> Address 516 | { 517 | _address = ((_address + offset + 4) + *(int32_t*)(_address + offset)); 518 | return *this; 519 | } 520 | 521 | auto AbsoluteOffset(uint32_t offset) -> Address 522 | { 523 | _address = _address + offset; 524 | return *this; 525 | } 526 | 527 | auto Jump() -> Address 528 | { 529 | if (ASM::isJump(*reinterpret_cast(_address))) 530 | { 531 | UINT8 toSkip = *reinterpret_cast(_address + 1); 532 | _address = _address + 2 + toSkip; 533 | } 534 | 535 | return *this; 536 | } 537 | 538 | auto Get() -> uintptr_t 539 | { 540 | return _address; 541 | } 542 | 543 | template 544 | auto GetAs() -> T 545 | { 546 | return reinterpret_cast(_address); 547 | } 548 | 549 | auto IsValid() -> bool 550 | { 551 | return _address != 0; 552 | } 553 | }; 554 | 555 | class Section 556 | { 557 | public: 558 | std::string sectionName; 559 | IMAGE_SECTION_HEADER rawSection; 560 | 561 | static auto GetAllSections() -> std::vector
562 | { 563 | std::vector
sections; 564 | 565 | auto sectionsSize = GetNTHeaders()->FileHeader.NumberOfSections; 566 | auto section = IMAGE_FIRST_SECTION(GetNTHeaders()); 567 | 568 | for (WORD i = 0; i < sectionsSize; i++, section++) 569 | { 570 | auto secName = std::string((char*)section->Name); 571 | 572 | sections.push_back({ secName, *section }); 573 | } 574 | 575 | return sections; 576 | } 577 | 578 | static auto GetSection(std::string sectionName) -> Section 579 | { 580 | for (auto& section : GetAllSections()) 581 | { 582 | if (section.sectionName == sectionName) 583 | { 584 | return section; 585 | } 586 | } 587 | 588 | MemcuryThrow("Section not found"); 589 | return Section{}; 590 | } 591 | 592 | auto GetSectionSize() -> uint32_t 593 | { 594 | return rawSection.Misc.VirtualSize; 595 | } 596 | 597 | auto GetSectionStart() -> Address 598 | { 599 | return Address(GetModuleBase() + rawSection.VirtualAddress); 600 | } 601 | 602 | auto GetSectionEnd() -> Address 603 | { 604 | return Address(GetSectionStart() + GetSectionSize()); 605 | } 606 | 607 | auto isInSection(Address address) -> bool 608 | { 609 | return address >= GetSectionStart() && address < GetSectionEnd(); 610 | } 611 | }; 612 | } 613 | 614 | class Scanner 615 | { 616 | PE::Address _address; 617 | 618 | public: 619 | Scanner(PE::Address address) 620 | : _address(address) 621 | { 622 | } 623 | 624 | static auto SetTargetModule(const char* moduleName) -> void 625 | { 626 | PE::SetCurrentModule(moduleName); 627 | } 628 | 629 | static auto FindPatternEx(HANDLE handle, const char* pattern, const char* mask, uint64_t begin, uint64_t end) -> Scanner 630 | { 631 | auto scan = [](const char* pattern, const char* mask, char* begin, unsigned int size) -> char* 632 | { 633 | size_t patternLen = strlen(mask); 634 | for (unsigned int i = 0; i < size - patternLen; i++) 635 | { 636 | bool found = true; 637 | for (unsigned int j = 0; j < patternLen; j++) 638 | { 639 | if (mask[j] != '?' && pattern[j] != *(begin + i + j)) 640 | { 641 | found = false; 642 | break; 643 | } 644 | } 645 | 646 | if (found) 647 | return (begin + i); 648 | } 649 | return nullptr; 650 | }; 651 | 652 | uint64_t match = NULL; 653 | SIZE_T bytesRead; 654 | char* buffer = nullptr; 655 | MEMORY_BASIC_INFORMATION mbi = { 0 }; 656 | 657 | uint64_t curr = begin; 658 | 659 | for (uint64_t curr = begin; curr < end; curr += mbi.RegionSize) 660 | { 661 | if (!VirtualQueryEx(handle, (void*)curr, &mbi, sizeof(mbi))) 662 | continue; 663 | 664 | if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS) 665 | continue; 666 | 667 | buffer = new char[mbi.RegionSize]; 668 | 669 | if (ReadProcessMemory(handle, mbi.BaseAddress, buffer, mbi.RegionSize, &bytesRead)) 670 | { 671 | char* internalAddr = scan(pattern, mask, buffer, (unsigned int)bytesRead); 672 | 673 | if (internalAddr != nullptr) 674 | { 675 | match = curr + (uint64_t)(internalAddr - buffer); 676 | break; 677 | } 678 | } 679 | } 680 | delete[] buffer; 681 | 682 | MemcuryAssertM(match != 0, "FindPatternEx return nullptr"); 683 | 684 | return Scanner(match); 685 | } 686 | 687 | static auto FindPatternEx(HANDLE handle, const char* sig) -> Scanner 688 | { 689 | char pattern[100]; 690 | char mask[100]; 691 | 692 | char lastChar = ' '; 693 | unsigned int j = 0; 694 | 695 | for (unsigned int i = 0; i < strlen(sig); i++) 696 | { 697 | if ((sig[i] == '?' || sig[i] == '*') && (lastChar != '?' && lastChar != '*')) 698 | { 699 | pattern[j] = mask[j] = '?'; 700 | j++; 701 | } 702 | 703 | else if (isspace(lastChar)) 704 | { 705 | pattern[j] = lastChar = (char)strtol(&sig[i], 0, 16); 706 | mask[j] = 'x'; 707 | j++; 708 | } 709 | lastChar = sig[i]; 710 | } 711 | pattern[j] = mask[j] = '\0'; 712 | 713 | auto module = (uint64_t)GetModuleHandle(nullptr); 714 | 715 | return FindPatternEx(handle, pattern, mask, module, module + Memcury::PE::GetNTHeaders()->OptionalHeader.SizeOfImage); 716 | } 717 | 718 | static auto FindPattern(const char* signature) -> Scanner 719 | { 720 | PE::Address add{ nullptr }; 721 | 722 | const auto sizeOfImage = PE::GetNTHeaders()->OptionalHeader.SizeOfImage; 723 | auto patternBytes = ASM::pattern2bytes(signature); 724 | const auto scanBytes = reinterpret_cast(PE::GetModuleBase()); 725 | 726 | const auto s = patternBytes.size(); 727 | const auto d = patternBytes.data(); 728 | 729 | for (auto i = 0ul; i < sizeOfImage - s; ++i) 730 | { 731 | bool found = true; 732 | for (auto j = 0ul; j < s; ++j) 733 | { 734 | if (scanBytes[i + j] != d[j] && d[j] != -1) 735 | { 736 | found = false; 737 | break; 738 | } 739 | } 740 | 741 | if (found) 742 | { 743 | add = reinterpret_cast(&scanBytes[i]); 744 | break; 745 | } 746 | } 747 | 748 | MemcuryAssertM(add != 0, "FindPattern return nullptr"); 749 | 750 | return Scanner(add); 751 | } 752 | 753 | // Supports wide and normal strings both std and pointers 754 | template 755 | static auto FindStringRef(T string) -> Scanner 756 | { 757 | PE::Address add{ nullptr }; 758 | 759 | constexpr auto bIsWide = std::is_same::value; 760 | constexpr auto bIsChar = std::is_same::value; 761 | 762 | constexpr auto bIsPtr = bIsWide || bIsChar; 763 | 764 | auto textSection = PE::Section::GetSection(".text"); 765 | auto rdataSection = PE::Section::GetSection(".rdata"); 766 | 767 | const auto scanBytes = reinterpret_cast(textSection.GetSectionStart().Get()); 768 | 769 | // scan only text section 770 | for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++) 771 | { 772 | if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && scanBytes[i + 1] == ASM::LEA) 773 | { 774 | auto stringAdd = PE::Address(&scanBytes[i]).RelativeOffset(3); 775 | 776 | // Check if the string is in the .rdata section 777 | if (rdataSection.isInSection(stringAdd)) 778 | { 779 | auto strBytes = stringAdd.GetAs(); 780 | 781 | // Check if the first char is printable 782 | if (ASM::byteIsAscii(strBytes[0])) 783 | { 784 | if constexpr (!bIsPtr) 785 | { 786 | typedef T::value_type char_type; 787 | 788 | auto lea = stringAdd.GetAs(); 789 | 790 | T leaT(lea); 791 | 792 | if (leaT == string) 793 | { 794 | add = PE::Address(&scanBytes[i]); 795 | } 796 | } 797 | else 798 | { 799 | auto lea = stringAdd.GetAs(); 800 | 801 | if constexpr (bIsWide) 802 | { 803 | if (wcscmp(string, lea) == 0) 804 | { 805 | add = PE::Address(&scanBytes[i]); 806 | } 807 | } 808 | else 809 | { 810 | if (strcmp(string, lea) == 0) 811 | { 812 | add = PE::Address(&scanBytes[i]); 813 | } 814 | } 815 | } 816 | } 817 | } 818 | } 819 | } 820 | 821 | MemcuryAssertM(add != 0, "FindStringRef return nullptr"); 822 | 823 | return Scanner(add); 824 | } 825 | 826 | static auto FindPointerRef(void* pointer) -> Scanner 827 | { 828 | PE::Address add{ nullptr }; 829 | 830 | auto rdataSection = PE::Section::GetSection(".rdata"); 831 | const auto scanBytes = reinterpret_cast(rdataSection.GetSectionStart().Get()); 832 | 833 | for (DWORD i = 0; i < rdataSection.GetSectionSize(); i++) 834 | { 835 | auto currentPointer = *reinterpret_cast(scanBytes + i); 836 | 837 | if (currentPointer == pointer) 838 | { 839 | add = PE::Address(&scanBytes[i]); 840 | break; 841 | } 842 | } 843 | 844 | MemcuryAssertM(add != 0, "FindPointerRef return nullptr"); 845 | 846 | return Scanner(add); 847 | } 848 | 849 | auto Jump() -> Scanner 850 | { 851 | _address.Jump(); 852 | return *this; 853 | } 854 | 855 | auto ScanFor(std::vector opcodesToFind, bool forward = true, int toSkip = 0) -> Scanner 856 | { 857 | const auto scanBytes = _address.GetAs(); 858 | 859 | for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--) 860 | { 861 | bool found = true; 862 | 863 | for (int k = 0; k < opcodesToFind.size() && found; k++) 864 | { 865 | if (opcodesToFind[k] == -1) 866 | continue; 867 | found = opcodesToFind[k] == scanBytes[i + k]; 868 | } 869 | 870 | if (found) 871 | { 872 | _address = &scanBytes[i]; 873 | if (toSkip != 0) 874 | { 875 | return ScanFor(opcodesToFind, forward, toSkip - 1); 876 | } 877 | 878 | break; 879 | } 880 | } 881 | 882 | return *this; 883 | } 884 | 885 | auto RelativeOffset(uint32_t offset) -> Scanner 886 | { 887 | _address.RelativeOffset(offset); 888 | 889 | return *this; 890 | } 891 | 892 | auto AbsoluteOffset(uint32_t offset) -> Scanner 893 | { 894 | _address.AbsoluteOffset(offset); 895 | 896 | return *this; 897 | } 898 | 899 | template 900 | auto GetAs() -> T 901 | { 902 | return _address.GetAs(); 903 | } 904 | 905 | auto Get() -> uintptr_t 906 | { 907 | return _address.Get(); 908 | } 909 | 910 | auto IsValid() -> bool 911 | { 912 | return _address.IsValid(); 913 | } 914 | }; 915 | 916 | /* Bad don't use it tbh... */ 917 | class TrampolineHook 918 | { 919 | void** originalFunctionPtr; 920 | PE::Address originalFunction; 921 | PE::Address hookFunction; 922 | PE::Address allocatedPage; 923 | std::vector restore; 924 | 925 | void PointToCodeIfNot(PE::Address& ptr) 926 | { 927 | auto bytes = ptr.GetAs(); 928 | 929 | if (ASM::byteIsA(bytes[0], ASM::MNEMONIC::JMP_REL32)) 930 | { 931 | ptr = bytes + 5 + *(int32_t*)&bytes[1]; 932 | } 933 | } 934 | 935 | void* AllocatePageNearAddress(void* targetAddr) 936 | { 937 | SYSTEM_INFO sysInfo; 938 | GetSystemInfo(&sysInfo); 939 | const uint64_t PAGE_SIZE = sysInfo.dwPageSize; 940 | 941 | uint64_t startAddr = (uint64_t(targetAddr) & ~(PAGE_SIZE - 1)); // round down to nearest page boundary 942 | uint64_t minAddr = min(startAddr - 0x7FFFFF00, (uint64_t)sysInfo.lpMinimumApplicationAddress); 943 | uint64_t maxAddr = max(startAddr + 0x7FFFFF00, (uint64_t)sysInfo.lpMaximumApplicationAddress); 944 | 945 | uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE)); 946 | 947 | for (uint64_t pageOffset = 1; pageOffset; pageOffset++) 948 | { 949 | uint64_t byteOffset = pageOffset * PAGE_SIZE; 950 | uint64_t highAddr = startPage + byteOffset; 951 | uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0; 952 | 953 | bool needsExit = highAddr > maxAddr && lowAddr < minAddr; 954 | 955 | if (highAddr < maxAddr) 956 | { 957 | void* outAddr = VirtualAlloc((void*)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 958 | if (outAddr) 959 | return outAddr; 960 | } 961 | 962 | if (lowAddr > minAddr) 963 | { 964 | void* outAddr = VirtualAlloc((void*)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 965 | if (outAddr != nullptr) 966 | return outAddr; 967 | } 968 | 969 | if (needsExit) 970 | { 971 | break; 972 | } 973 | } 974 | 975 | return nullptr; 976 | } 977 | 978 | void WriteAbsoluteJump(void* jumpLocation, void* destination) 979 | { 980 | uint8_t absJumpInstructions[] = { 981 | ASM::Mnemonic("CMOVNS"), 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr 982 | 0x41, 0xFF, 0xE2 // jmp r10 983 | }; 984 | 985 | auto destination64 = (uint64_t)destination; 986 | memcpy(&absJumpInstructions[2], &destination64, sizeof(destination64)); 987 | memcpy(jumpLocation, absJumpInstructions, sizeof(absJumpInstructions)); 988 | } 989 | 990 | uintptr_t PrepareRestore() 991 | { 992 | /* 993 | This is not a correct way to do it at all, since not all functions sub from the stack 994 | This needs so much more tests, but it works for now. 995 | */ 996 | 997 | Scanner scanner(originalFunction); 998 | scanner.ScanFor({ 0x48, 0x83, 0xEC }); // sub rsp 999 | 1000 | auto restoreSize = scanner.Get() - originalFunction.Get(); 1001 | 1002 | MemcuryAssert(restoreSize > 0 && restoreSize < 0x100); 1003 | 1004 | restore.reserve(restoreSize); 1005 | for (auto i = 0; i < restoreSize; i++) 1006 | { 1007 | restore.push_back(originalFunction.GetAs()[i]); 1008 | } 1009 | 1010 | return restoreSize; 1011 | } 1012 | 1013 | void WriteRestore() 1014 | { 1015 | auto restorePtr = allocatedPage + ASM::SIZE_OF_JMP_ABSLOUTE_INSTRUCTION + 2; 1016 | 1017 | memcpy(restorePtr.GetAs(), restore.data(), restore.size()); 1018 | 1019 | *originalFunctionPtr = restorePtr.GetAs(); 1020 | 1021 | // Write a jump back to where the execution should resume 1022 | restorePtr.AbsoluteOffset((uint32_t)restore.size()); 1023 | 1024 | auto contuineExecution = originalFunction + restore.size(); 1025 | 1026 | WriteAbsoluteJump(restorePtr.GetAs(), contuineExecution.GetAs()); 1027 | } 1028 | 1029 | auto PrepareJMPInstruction(uint64_t dst) 1030 | { 1031 | uint8_t bytes[5] = { ASM::Mnemonic("JMP_REL32"), 0x0, 0x0, 0x0, 0x0 }; 1032 | 1033 | const uint64_t relAddr = dst - (originalFunction.Get() + ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); 1034 | memcpy(bytes + 1, &relAddr, 4); 1035 | 1036 | return std::move(bytes); 1037 | } 1038 | 1039 | bool IsHooked() 1040 | { 1041 | return originalFunction.GetAs()[0] == ASM::Mnemonic("JMP_REL32"); 1042 | } 1043 | 1044 | public: 1045 | TrampolineHook(void** originalFunction, void* hookFunction) 1046 | { 1047 | this->originalFunctionPtr = originalFunction; 1048 | 1049 | this->originalFunction = *originalFunction; 1050 | this->hookFunction = hookFunction; 1051 | 1052 | PointToCodeIfNot(this->originalFunction); 1053 | PointToCodeIfNot(this->hookFunction); 1054 | }; 1055 | 1056 | bool Commit() 1057 | { 1058 | auto fnStart = originalFunction.GetAs(); 1059 | 1060 | auto restoreSize = PrepareRestore(); 1061 | 1062 | if (!allocatedPage.IsValid()) 1063 | { 1064 | allocatedPage = AllocatePageNearAddress(fnStart); 1065 | } 1066 | 1067 | memset(allocatedPage.GetAs(), ASM::MNEMONIC::INT3, 0x1000); 1068 | 1069 | WriteAbsoluteJump(allocatedPage.GetAs(), hookFunction.GetAs()); 1070 | 1071 | DWORD oldProtect; 1072 | VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); 1073 | 1074 | auto jmpInstruction = PrepareJMPInstruction(allocatedPage.Get()); 1075 | 1076 | WriteRestore(); 1077 | 1078 | memset(fnStart, ASM::MNEMONIC::INT3, restoreSize); 1079 | memcpy(fnStart, jmpInstruction, ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION); 1080 | 1081 | return true; 1082 | } 1083 | 1084 | bool Revert() 1085 | { 1086 | auto fnStart = originalFunction.GetAs(); 1087 | 1088 | DWORD oldProtect; 1089 | VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect); 1090 | 1091 | memcpy(fnStart, restore.data(), restore.size()); 1092 | 1093 | *originalFunctionPtr = originalFunction.GetAs(); 1094 | 1095 | // VirtualFree(allocatedPage.GetAs(), 0x1000, MEM_RELEASE); 1096 | 1097 | return true; 1098 | } 1099 | 1100 | auto Toggle() 1101 | { 1102 | if (IsHooked()) 1103 | Revert(); 1104 | else 1105 | Commit(); 1106 | 1107 | return IsHooked(); 1108 | } 1109 | }; 1110 | 1111 | namespace VEHHook 1112 | { 1113 | struct HOOK_INFO 1114 | { 1115 | void* Original; 1116 | void* Detour; 1117 | 1118 | HOOK_INFO(void* Original, void* Detour) 1119 | : Original(Original) 1120 | , Detour(Detour) 1121 | { 1122 | } 1123 | }; 1124 | 1125 | inline std::vector Hooks; 1126 | inline std::vector HookProtections; 1127 | inline HANDLE ExceptionHandler; 1128 | 1129 | inline long Handler(EXCEPTION_POINTERS* Exception) 1130 | { 1131 | if (Exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) 1132 | { 1133 | auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Rip = Exception->ContextRecord->Rip](const HOOK_INFO& Hook) 1134 | { return Hook.Original == (void*)Rip; }); 1135 | if (Itr != Hooks.end()) 1136 | { 1137 | Exception->ContextRecord->Rip = (uintptr_t)Itr->Detour; 1138 | } 1139 | 1140 | Exception->ContextRecord->EFlags |= 0x100; // SINGLE_STEP_FLAG 1141 | 1142 | return EXCEPTION_CONTINUE_EXECUTION; 1143 | } 1144 | else if (Exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) 1145 | { 1146 | // TODO: find a way to only vp the function that about to get executed 1147 | for (auto& Hook : Hooks) 1148 | { 1149 | DWORD dwOldProtect; 1150 | VirtualProtect(Hook.Original, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect); 1151 | } 1152 | 1153 | return EXCEPTION_CONTINUE_EXECUTION; 1154 | } 1155 | 1156 | return EXCEPTION_CONTINUE_SEARCH; 1157 | } 1158 | 1159 | inline bool Init() 1160 | { 1161 | if (ExceptionHandler == nullptr) 1162 | { 1163 | ExceptionHandler = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)Handler); 1164 | } 1165 | return ExceptionHandler != nullptr; 1166 | } 1167 | 1168 | inline bool AddHook(void* Target, void* Detour) 1169 | { 1170 | if (ExceptionHandler == nullptr) 1171 | { 1172 | return false; 1173 | } 1174 | 1175 | if (Util::IsSamePage(Target, Detour)) 1176 | { 1177 | return false; 1178 | } 1179 | 1180 | if (!VirtualProtect(Target, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &HookProtections.emplace_back())) 1181 | { 1182 | HookProtections.pop_back(); 1183 | return false; 1184 | } 1185 | 1186 | Hooks.emplace_back(Target, Detour); 1187 | return true; 1188 | } 1189 | 1190 | inline bool RemoveHook(void* Original) 1191 | { 1192 | auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Original](const HOOK_INFO& Hook) 1193 | { return Hook.Original == Original; }); 1194 | 1195 | if (Itr == Hooks.end()) 1196 | { 1197 | return false; 1198 | } 1199 | 1200 | const auto ProtItr = HookProtections.begin() + std::distance(Hooks.begin(), Itr); 1201 | Hooks.erase(Itr); 1202 | 1203 | DWORD dwOldProtect; 1204 | bool Ret = VirtualProtect(Original, 1, *ProtItr, &dwOldProtect); 1205 | HookProtections.erase(ProtItr); 1206 | 1207 | return false; 1208 | } 1209 | } 1210 | } -------------------------------------------------------------------------------- /Windows/framework.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #pragma once 4 | #include 5 | 6 | #include "Utilities\memcury.h" 7 | #include "Utilities\Windows.h" 8 | 9 | #include "Core\Core.h" 10 | #include "Sinum\Sinum.h" -------------------------------------------------------------------------------- /iOS/Main.m: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Project Nova LLC 2 | 3 | #import 4 | #import 5 | 6 | #define API_URL @"https://api.novafn.dev" 7 | #define EPIC_GAMES_URL @"ol.epicgames.com" 8 | 9 | @interface CustomURLProtocol : NSURLProtocol 10 | @end 11 | 12 | @implementation CustomURLProtocol 13 | 14 | + (BOOL)canInitWithRequest:(NSURLRequest *)request 15 | { 16 | NSString *absoluteURLString = [[request URL] absoluteString]; 17 | if ([absoluteURLString containsString:EPIC_GAMES_URL] && ![absoluteURLString containsString:@"/CloudDir/"]) { 18 | if ([NSURLProtocol propertyForKey:@"Handled" inRequest:request]) { 19 | return NO; 20 | } 21 | return YES; 22 | } 23 | return NO; 24 | } 25 | 26 | + (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request 27 | { 28 | return request; 29 | } 30 | 31 | - (void)startLoading 32 | { 33 | NSMutableURLRequest* modifiedRequest = [[self request] mutableCopy]; 34 | 35 | NSString* originalPath = [modifiedRequest.URL path]; 36 | NSString* originalQuery = [modifiedRequest.URL query]; 37 | 38 | NSString* newBaseURLString = API_URL; 39 | NSURLComponents* components = [NSURLComponents componentsWithString:newBaseURLString]; 40 | 41 | components.path = originalPath; 42 | if (originalQuery) 43 | { 44 | components.query = originalQuery; 45 | } 46 | 47 | [modifiedRequest setURL:components.URL]; 48 | [NSURLProtocol setProperty:@YES forKey:@"Handled" inRequest:modifiedRequest]; 49 | 50 | #pragma clang diagnostic push 51 | #pragma clang diagnostic ignored "-Wnonnull" 52 | [[self client] URLProtocol:self 53 | wasRedirectedToRequest:modifiedRequest 54 | redirectResponse:nil]; 55 | #pragma clang diagnostic pop 56 | } 57 | 58 | - (void)stopLoading 59 | { 60 | } 61 | @end 62 | 63 | __attribute__((constructor)) void entry() 64 | { 65 | [NSURLProtocol registerClass:[CustomURLProtocol class]]; 66 | } -------------------------------------------------------------------------------- /iOS/README.md: -------------------------------------------------------------------------------- 1 | # Sinum iOS 2 | 3 | Sinum iOS is a versatile traffic redirection tool designed to streamline URL modification within your application. It achieves this by harnessing a custom **NSURLProtocol**, enabling the seamless redirection of traffic from one URL, such as https://example.com, to a specified destination—in this case, https://api.novafn.dev. 4 | 5 | ## Building & Usage 6 | 7 | ### **MacOS** 8 | 9 | 1. **Open Terminal:** 10 | - Launch the Terminal application on your MacOS system. 11 | 12 | 2. **Clone Sinum:** 13 | - Employ `git` to clone Sinum into your present directory. 14 | 15 | ```bash 16 | git clone https://github.com/projectnovafn/Sinum.git 17 | ``` 18 | 19 | 3. **Navigate to the Sinum iOS Directory:** 20 | - Change into the Sinum iOS directory using the following command: 21 | 22 | ```bash 23 | cd Sinum/iOS 24 | ``` 25 | 26 | 4. **Run the Build Script:** 27 | - Execute the build script by running the following command: 28 | 29 | ```bash 30 | ./build-mac.sh 31 | ``` 32 | 33 | ### **Linux / Windows via WSL** 34 | 35 | 1. **Open Terminal:** 36 | - Launch the Linux terminal on Native Linux or via WSL. 37 | 38 | 2. **Install and Set Up cctools-port:** 39 | - To use cctools-port, follow the installation and setup instructions provided in the [cctools-port GitHub repository](https://github.com/tpoechtrager/cctools-port). Note that this tutorial does not include the distribution of proprietary Apple files. 40 | 41 | 3. **Clone Sinum:** 42 | - Employ `git` to clone Sinum into your present directory. 43 | 44 | ```bash 45 | git clone https://github.com/projectnovafn/Sinum.git 46 | ``` 47 | 48 | 4. **Navigate to the Sinum iOS Directory:** 49 | - Change into the Sinum iOS directory using the following command: 50 | 51 | ```bash 52 | cd Sinum/iOS 53 | ``` 54 | 55 | 5. **Run the Build Script:** 56 | - Execute the build script: 57 | 58 | ```bash 59 | ./build-linux.sh 60 | ``` 61 | -------------------------------------------------------------------------------- /iOS/build-linux.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Project Nova LLC 2 | 3 | rm -rf build 4 | mkdir -p build 5 | 6 | arm-apple-darwin11-clang++ Main.m -dynamiclib -arch arm64 -o build/libSinum.dylib -framework Foundation -framework UIKit -------------------------------------------------------------------------------- /iOS/build-mac.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Project Nova LLC 2 | 3 | rm -rf build 4 | mkdir -p build 5 | 6 | clang++ Main.m -dynamiclib -arch arm64 -o build/libSinum.dylib -framework Foundation --------------------------------------------------------------------------------