Skip to main content

No way out picoCTF 2023 Solution

Escape the bounds of a Unity game to reach a hidden area that contains the flag.

Published: April 26, 2023Updated: August 25, 2026

Description

A Unity game walls you in with invisible boundaries. The flag is readable with strings, or you can use dnSpy to modify the game assembly and unlock infinite jumping to fly over the wall.

Download and unzip the game. Get the files from the challenge page on CyLab Security Academy (formerly play.picoctf.org).

bash
unzip no-way-out.zip

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Fast path: strings on the level data
    Observation
    This is a Unity build with a data folder full of binary level files. Scene text objects, a displayed flag included, sit as plain strings in level0 and come out without running the game at all.
    Unity games store scene data including text objects in the level data files. Running strings on the level0 file reveals the flag directly without running the game at all.
    bash
    strings 'no-way-out_Data/level0' | grep -i 'WELCOME\|picoCTF'
    bash
    strings 'no-way-out_Data/level0'

    Expected output

    picoCTF{WELCOME_TO_UNITY!!}
    What didn't work first

    Tried: Running strings on the main game executable instead of the level data file.

    The executable holds engine code and startup logic, not scene assets. Scene text objects are serialized into the level0 binary inside the data folder, so running strings on the wrong file returns thousands of engine symbols and no flag.

    Tried: Using grep -i 'picoctf' without also grepping for 'WELCOME' or checking the raw strings output.

    The flag text sits just above the picoCTF wrapper in the file, and a grep pattern anchored on the braces can miss it when the encoding splits the prefix. Run strings unfiltered first, read the surrounding lines, and narrow the pattern once you can see the whole thing.

    Learn more

    Unity stores scene data for each level in a binary file called level0, level1, etc. inside the *_Data/ folder. Text objects placed in the scene (like 3D text meshes showing the flag) are stored as plain UTF-16 or UTF-8 strings in this file. The strings command extracts any printable run of characters, which surfaces the flag text without running the game.

    This is the fastest CTF approach: run strings on every data file in the game bundle before spending time on dynamic analysis. Unity games are particularly friendly to this because scene assets are not encrypted by default.

  2. Step 2Alternative: patch the game with dnSpy
    Observation
    The game ships Assembly-CSharp.dll in its Managed folder, so the C# player controller, grounded jump restriction and all, decompiles in dnSpy. Patch that check out and you can jump indefinitely, over the invisible wall.
    For the more interactive approach, open the game assembly in dnSpy and remove the grounded check from the jump logic so you can jump infinitely and fly over the wall.
    bash
    # Download dnSpy from https://github.com/dnSpy/dnSpy/releases
    bash
    # Open no-way-out_Data/Managed/Assembly-CSharp.dll in dnSpy
    bash
    # Navigate to PlayerController -> Update method
    bash
    # Remove the isGrounded condition from the jump check
    bash
    # File -> Save Module, then run the game
    What didn't work first

    Tried: Opening the game executable directly in dnSpy instead of Assembly-CSharp.dll.

    dnSpy is a .NET tool and cannot parse the native Unity launcher. The C# game logic compiles into Assembly-CSharp.dll inside the Managed folder. Open the wrong file and you get an error, or an empty assembly tree with no controller class to edit.

    Tried: Editing the jump height or speed values in dnSpy rather than removing the isGrounded condition.

    Raising the jump height only lets you jump higher from the ground, and the invisible wall is tall enough that no finite height clears it. Remove the grounded check instead, which allows repeated mid-air jumps and lets you climb indefinitely until you are past the boundary.

    Learn more

    dnSpy is a .NET assembly editor and debugger. Unity games compile C# scripts to Assembly-CSharp.dll in the Managed/ folder. dnSpy decompiles this DLL back to readable C# and lets you edit the code directly, then save the modified assembly.

    The jump logic in this game checks if (Input.GetButton("Jump") && canMove && isGrounded && !isClimbing). Removing the isGrounded condition lets you jump while airborne, effectively giving infinite flight. After patching, jump over the invisible wall, land outside, turn around, and read the flag displayed as a 3D text object in the enclosed area.

    The flag reads: WELCOME TO UNITY!!

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

Flag

Reveal flag

picoCTF{WELCOME_TO_UNITY!!}

The simplest approach is strings on the level0 data file. The dnSpy patch is more fun but takes longer.

Key takeaway

Unity games built on the Mono backend ship their logic as a .NET assembly, Assembly-CSharp.dll, that keeps full metadata and decompiles straight back to readable C# in dnSpy or ILSpy. An IL2CPP build instead translates that IL to C++ and compiles it to native code, so dnSpy cannot open it and you reach for Il2CppDumper and Ghidra instead. Client-side controls, invisible walls, locked doors, flags sitting in scene data, always fall, because the attacker owns the runtime. The same holds for any game with client-enforced restrictions, DRM, or license checks, and for any application trusting client-side validation: enforcement belongs on the server.

Related reading

Useful tools for Reverse Engineering

Where to go next