TL;DR: We found another delayed RCE in MECCHA CHAMELEON. When playing on an attacker’s map, they can abuse an exposed function to arbitrarily write files to the victim’s system. This can lead to remote code execution on the victim’s system after a restart. We reported the issue to the game’s maintainers, and they fixed the vulnerability in the 4.0.0 update. This update is automatically installed before launching the game.
If you're a gamer, you've probably, you've probably heard of MECCHA CHAMELEON. The online hide-and-seek game has become a global obsession, selling 15 million copies in its first month!
As a player, you have one very silly job: Find a good hiding spot and paint a blank white character to match the scenery. Then one of your friends will come hunt you down. You win if the hunter can't find you. It's as simple as that!
Community-made maps are a big part of the appeal. Anyone can create a new map for players to hide in and publish it through the Steam Workshop. These new maps can be an art gallery, nostalgic areas from other games, and so on.
One specific feature stood out to us: when you play on a server, the lobby host will pick the map and then all players in that lobby will be urged to download this map (the screen will call you out if you haven’t downloaded yet and are holding up the game!). That's very convenient, but it also means joining a match may involve loading a map made by somebody you have never met. We wanted to figure out what that downloaded map was actually allowed to do.
In the past, MECCHA CHAMELEON had already suffered an RCE upon map load, as detailed in this blog post. That specific bug has since been patched, but did that remove every route to RCE?
What is a MECCHA CHAMELEON map really?
You may assume that a map is just some assets: models for the scenery and textures to make everything look right. That is how a map works in a lot of games, where the downloaded file describes the level, while the game itself controls the logic.
MECCHA CHAMELEON maps have more control than that. The game is built with Unreal Engine 5.6.1, and its maps can contain Blueprints. Blueprint is Unreal's visual programming system. You can connect nodes to make something happen when a player presses a button, enters an area or simply loads the map.
Of course, a map cannot call every function inside Unreal or MECCHA CHAMELEON. A native function first has to be exposed to Blueprint. When the map starts, its graphs run inside the MECCHA CHAMELEON process. If a graph calls an exposed function, the game performs that action using the player's account and permissions.
This means custom maps are an interesting attack surface:
- A lobby host can prompt every other player to download and execute a map.
- That map can then execute Blueprint graphs inside the game.
So, I started paying attention to the engine functions that I could reach.
Finding callable functions
Fortunately, we do not have to guess which functions Unreal exposes. We can open a Blueprint graph and right-click an empty area. Now Unreal shows a searchable menu of nodes that can be added to that graph.
From a security perspective, I was mainly interested in functions that interacted with the operating system in some way. So I searched for ones containing save
, export
, write
, record
, open
, launch
, and load
. We went from a huge list of APIs to a manageable list that I could look into.
So one by one, I investigated these functions until I came across Finish Recording Output
. This one stood out because it could create a file on the filesystem with a path that can be absolute or relative. It's also storing audio output, which we can likely control in some way.
Writing files anywhere
So to use the Finish Recording Output
function, a map creator should start a recording, play a sound, and then stop the recording. We then supply a name and a path to save to. The function is further documented here.
So this is a clear-cut case of “Just supply any directory and any file name, and we can save these files anywhere on the system”, right?
Well yes. You can just supply C:/Users/Public/
as the file path and write files there, or you can supply a relative path. The default directory used is C:\Users\\AppData\Local\Chameleon\Saved\BouncedWavFiles
, so by supplying a path starting with ../../../../../
, we could get to the user's home folder without needing to know their username.
So now we could write files anywhere on the filesystem. The easiest way to escalate that into a delayed remote code execution is to write an executable to C:\Users\\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
. Whenever your device starts up, it will automatically execute all files in this folder.
So we supplied this value as the recording name:
../../../../Roaming/Microsoft/Windows/Start Menu/Programs/Startup/poc.exe
And when we packaged and executed our map, we noticed ...
It worked, kinda? We did indeed write a file to the startup folder. But our .exe extension didn't stick. Unreal Engine is adding the .wav extension to whatever file name we supply, and thus Windows still treats the file as a WAV instead of an executable.
Removing the .wav suffix
I ruminated a bit over how we could bypass this. What we have here is a classic case of 2 different systems making assumptions. Unreal Engine has its way of handling a path and Windows has its way of interpreting a path. I just had to find a discrepancy between them.
For Unreal Engine, it's incredibly simple: It just sees a string and appends .wav
to it. Windows is a little more complex. Windows has maximum file name lengths and characters that are not allowed in a path, such as the null character (U+0000
).
But what happens when we try to create a file with a null character in its name? Well, most of the time, you get an error:
But not all APIs throw an error. Some lower-level APIs will actually see the null bytes as a terminator and discard anything that follows:
So what if we try to use our malicious map to save a recording at:
POC.exe\u0000IGNORED
We can see that it worked! We can now save files with arbitrary names at arbitrary locations on the filesystem!
But one big issue remains. We're just writing the bytes of a WAV audio recording into a file with an .exe
extension. We need to somehow also control the contents of that file to make it executable!
Malicious bytes in an audio recording
We now had a new challenge in front of us. We were saving a real audio recording as a WAV file to an arbitrary location. But for this exploit to work, we also need to be able to control what's written in that file.
In order to do that, we're going to use an uncompressed PCM WAV file. WAV is the file container, holding some metadata followed by audio data. WAV files can store audio in several formats, so the .wav
extension alone does not tell us how the samples are encoded.
PCM stands for Pulse-Code Modulation. With PCM, each measurement of the sound wave is stored directly as a number. Our proof of concept used 16-bit PCM, so every sample occupied exactly two bytes. "Uncompressed" means those numbers were written without passing them through a codec such as MP3.
An uncompressed PCM WAV file consists of audio sample values. If we control those numbers precisely, we also control the bytes used to store them.
This is very important because normal audio compression tries to preserve how a sound is perceived, not the exact bytes of every sample. So a compressed version may sound identical while containing very different bytes.
A simplified WAV looks like this:
+-------------------------------+
| RIFF/WAVE header | Created by Unreal
| Format, channels, sample rate |
| Size of the data section |
+-------------------------------+
| PCM sample 0 | Controlled through the sound wave
| PCM sample 1 |
| PCM sample 2 |
| ... |
Unreal controls the header, including the RIFF
and WAVE
identifiers. We cannot modify these because our control of the file only begins in the PCM data section. That rules out a lot of executables for our exploit. We cannot upload a normal .exe
or .dll
, since both need a valid PE header to execute.
We therefore needed a file type whose parser did not insist on finding its header at the first byte. Windows HTML Applications turned out to be a perfect fit.
An HTML Application is an old Windows format with the .hta
extension. The file looks much like a web page: it can contain HTML, CSS and either JScript or VBScript. Windows opens it with mshta.exe
(Microsoft HTML Application Host).
The important difference from a normal web page is where that script runs. An HTA is treated as a desktop application rather than a page inside the browser sandbox. Its script can create Windows COM objects, including WScript.Shell
, which can start another process.
A simplified version of our proof-of-concept payload looks like this:
[RIFF/WAVE header ...]
new ActiveXObject("WScript.Shell").Run("calc.exe");
When this file is opened, mshta.exe
ignores the WAV metadata at the top and runs the script. Then WScript.Shell
launches Calculator.
The remaining challenge was placing that HTML and script inside the WAV's PCM samples.
Our payload used 16-bit PCM. Each sample is a 16-bit number stored in little-endian order, meaning the least significant byte is written first. Take the sample value 0x683C: it appears in the WAV as 3C 68, which also happens to be the ASCII text has these ASCII bytes:
3C 68 74 6D 6C 3E
Grouping them into pairs gives us three 16-bit sample values:
Bytes: 3C 68 | 74 6D | 6C 3E
Samples: 0x683C 0x6D74 0x3E6C
When those samples are written back into a little-endian WAV, the original bytes reappear. In reality, it took a lot of fiddling to get it to work, but in the end we managed to create a real WAV file that contained our hta
payload.
Putting the map together
At this point every part worked on its own. The remaining job was to put it all into a map that could perform the complete attack at once when the level loaded.
So we attach to BeginPlay
and do the following:
StartRecordingOutput
on a private submix (to avoid interference from the usual game audio)PlaySound2D
with our prepared uncompressed PCMStopRecordingOutput
using../../../../Roaming/Microsoft/Windows/Start Menu/Programs/Startup/poc.exe\u0000IGNORED
as the filename
We built the map and (privately!) uploaded it to Steam.
And that is how we got to this proof of concept video:
So is MECCHA CHAMELEON secure now?
The vulnerability we reported has been patched and cannot be exploited anymore. The StopRecordingOutput
function no longer creates any files (even intended files).
We also checked all Steam Workshop maps to see if any were already exploiting this issue but found none that were.
However, while working on this PoC, several individuals reached out to us to notify us that they may have other vulnerabilities in MECCHA CHAMELEON. They did not provide details and these are not proven or checked by us.
We therefore urge you to be cautious and vigilant when playing the game using custom maps. We recommend only playing on the official maps provided by MECCHA CHAMELEON.
Disclosure timeline
- 11 August 2026: Tried to get into contact via Twitter
- 12 August 2026: Tried to get into contact via Discord
- 13 August 2026: Tried to get into contact with Steam support & developer's email address
- 17 August 2026: Was able to reach them again via Twitter. The developer replied that he would fix the issue.
- 20 August 2026: MECCHA CHAMELEON 4.0.0 was released with a patch for this vulnerability
