A multi-stage PE loader for @carrot_c4k3's CollateralDamage Xbox One exploit.
This project has only been built and tested using x86_64-pc-windows-msvc on Windows 11. It will likely build on any 64-bit Windows, but has not been tested across different versions.
git clone https://github.com/exploits-forsale/solstice.git
cd solstice
rust toolchain install nightlyjust: https://github.com/casey/justjust build-exploit
All necessary outputs will be in the outputs/ directory.
If you would like to test on the PC version of GameScript, running the following command will execute build-exploit and copy the artifacts to the GameScript directory. This will overwrite its autosave state so all you have to do is click "run code":
just generate
Currently the stage2 shellcode attempts to open and run run.exe out of GameScript's LocalState directory. An args.txt file can be placed in the same directory contaning the command line string to pass to run.exe.
ENSURE THAT A host_ip.txt EXISTS IN THE ROOT OF THE REPO THAT POINTS TO THE IP ADDRESS THAT WILL BE HOSTING THE STAGE2/run.exe DELIVERY SERVER
just generate-dev
outputs/gamescript_autosave_network.txt to GameScript# Defaults to using the test_rust_program.exe as run.exe
just run-server
# Optionally use the following command to specify a run.exe
just run-server ./path/to/file.exe
If testing on PC, you can build in debug mode to get debug output in WinDbg.
just generate debug
If WinDbg is not attached the application will crash.
solstice uses a 3 stage loader. stage1 and stage2 can be replaced with custom stages and are not unique to Xbox or GameScript.
The stage1 loader is intended to work well with GameScript, and the project is intended to provide a streamlined experience with exploitation against GameScript. You can even use your own, better reflective PE loader, but you may have issues caused by slight differences between the Xbox SystemOS/UWP post-exploitation and a normal Windows exploitation.
For example:
The 3-stage loader works as shown in the following diagram:
┌──────────────────────────┐
│ GameScript With Exploit │
│ ┌──────────────┐ │
┌────┐ │ │ │ │
│ 1 │ │ │ stage1.bin │◀─ReadFile/VirtualAlloc─┐
└────┘ │ │ │ │ │
│ └──────────────┘ │ │
└──────────────────────────┘ │
│
┌─────────▼─────────────────────┐
│ ┌──────────────┐ ┌──────────┐ │
│ │ │ │ │ │
│ │ stage2.bin │ │ run.exe │ │
│ │ │ │ │ │
│ └──────────────┘ └──────────┘ │
│ File System ▲ │
└────────────────────────┼──────┘
│
Reflective
┌─────────────────────────────────────────────────────┐ PE Loader
│ GameScript With Exploit │ │
│ ┌──────────────┐ ┌──────────────┐ │ │
┌────┐│ │ │ │ │ │ │
│ 2 ││ │ stage1.bin │────invoke()───▶│ stage2.bin │◀───┼────────┘
└────┘│ │ │ │ │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ GameScript With Exploit │
┌────┐│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ 3 ││ │ │ │ │ │ │ │
└────┘│ │ stage1.bin │────invoke()───▶│ stage2.bin │───invoke()───▶│ run.exe │ │
│ │ │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
shellcode_stage1shellcode_stage1/ is the stage1 shellcode that is embedded directly in the GameScript exploit. This is intended to be as small as possible so that less typing is required from a Rubber Ducky/Flipper if that approach is used. Additionally, GameScript tends to hit parsing errors if the shellcode array has too many elements so we must keep this stage small.
stage1 loads stage2 from disk, maps it into memory, and executes it.
shellcode_stage1_networkThis works basically the same as shellcode_stage, but works by opening a socket to a remote server and loading stage2 and stage3 over the network.
stage2 is executed directly in-memory, but the run.exe (stage3) is written to disk. Since the same stage2 is shared across non-network and network-based payloads, it makes things easier to load things in the same manner after stage1.
shellcode_stage2shellcode_stage2/ is read by stage1 from disk, made executable, and executed. stage2 then reads a PE file from disk, specified at AppData\Local\Packages\27878ConstantineTarasenko.458004FD2C47C_c8b3w9r5va522\LocalState\run.exe, and manually loads the PE using our solstice_loader. This shellcode can be arbitrarily large since it's not limited by typing or GameScript sizes.
solstice_loaderThe main reflective PE loader. This is a fork of Thoxy67/rspe that's been heavily modified.
payload_serverThe server that communicates with shellcode_stage1_network over port 8080. Its purpose is to send 4 main TCP packets:
stage2 length (4 bytes)stage2.binrun.exe length (4 bytes)run.exeOn port 8081 it can listen for text-based content and print out to the terminal.
shellcode_utilsshellcode_utils/ contains common functionality shared between the shellcode stages including function definitions and helpers to retrieve functions at runtime.
shellcode_genReads the resulting exe files from shellcode_stage*/, applies some patches to make the first few bytes jump to the entrypoint, and generates a flattened .bin file containing only the .text section.
solstice_daemonChanges firewall settings on the Xbox to allow traffic on port 22 and runs an SSH/SFTP server.
shellcode_stage1/, shellcode_stage1_network, and shellcode_stage2/ have a special .cargo/config.toml that merges almost all PE sections into just a single .text section, and ensures there are no external dependencies (i.e. no runtime linkage required). They are #![no_std], #![no_main] binaries that resolve every platform function at runtime itself.

shellcode_gen/'s main job is to read the .text section and do some patches to make it position-independent. This idea
was from hasherezade's project masm_shc. It has also been modified to output a new GameScript exploit file with the latest shellcode_stage1/ automatically embedded in it, placed in outputs/.
This repo is a heavily modified version of b1tg/rust-windows-shellcode. Thank you to b1tg for their work.
Unfortunately this project is not a proper cargo workspace because Cargo does not allow you to specify a different profile per-crate in a workspace. See: https://github.com/rust-lang/cargo/issues/8264.
Because I could. And I know how to build/debug/deploy my own code better than an off-the-shelf project. The PE loader is certainly not as mature as others, but it works for our purposes.
b1tg/rust-windows-shellcode. Thank you to b1tg for their work.$ claude mcp add solstice \
-- python -m otcore.mcp_server <graph>