2-Click Remote Code Execution in Meccha Chameleon

tester4571 pts0 comments

2-Click Remote Code Execution in Meccha Chameleon

Introduction

MECCHA CHAMELEON, a hide-and-seek game on Steam built with Unreal Engine 5, has been quite the rage recently. An indie game developer’s dream: 7 million downloads (as of writing) without a single cent spent on advertising, and for good reason. It’s a take on the classic prop hunt game, where players attempt to hide in a cluttered map from hunters, but with the added twist of being able to literally paint themselves into the scenery. For example, can you spot me below? I’ve added an arrow to help.

However, these types of indie games often sport rich attack surfaces, as we’ll examine.

Custom Maps

One of the best parts of the game is the built-in support for custom maps. Without modding the game, players can create maps in UE5, package them into .pak files, and upload them to Steam’s official Workshop for others to play.

When a lobby host selects a custom map, all players are prompted to download the map from Steam before the game can start. The gameplay loop itself reinforces this; nobody wants to be the one holding up the lobby.

Crucially, Workshop maps can contain more than just levels and textures. They can also include Blueprint logic, which is UE5’s visual scripting system. Any Blueprint placed in a map has its BeginPlay event fire automatically when the map loads on a client. This is standard behavior, used for things like animated doors or environmental effects, but it also means that any logic the map creator includes will execute on every player’s machine upon being loaded. But what can actually be included in this logic?

Digging into Blueprint Scripting & LaunchURL

Many of the functions available were pretty boring from a security perspective. Lighting changes, visual effects, debug info, and so on. Surprisingly, Python commands were available, but they were restricted to the development environment and wouldn’t execute in a deployed map.

One node did stand out, though: LaunchURL. At first glance, it seemed somewhat harmless–a function for opening URLs in the user’s default browser. You could imagine a map creator using it to link to their social media or a Discord server.

To understand what it actually did under the hood, I pulled the game’s shipping binary (PenguinHotel-Win64-Shipping.exe) into Ghidra. The Blueprint-callable entry point, UKismetSystemLibrary::execLaunchURL, lives at 0x143e305e0. Following the call chain, it goes through LaunchURLFiltered, LaunchURL, and eventually lands on FWindowsPlatformProcess::LaunchURLInternal, which calls ShellExecuteW(NULL, "open", URL, NULL, NULL, SW_SHOWNORMAL).

This immediately became a lot more interesting. ShellExecuteW with the "open" verb isn’t just for web pages. It tells Windows to launch the path - similar to double-clicking a file - including running executables.

However, as some of you may have noticed, we don’t have argument control over the ShellExecuteW call - lpParameters is always NULL:

pHVar6 = ShellExecuteW((HWND)0x0,L"open",param_1,(LPCWSTR)0x0,(LPCWSTR)0x0,1)

This means something like "cmd.exe", "/c whoami" is off the table, and passing "cmd.exe /c whoami" would attempt to open the entire string as a path. Running arbitrary commands requires somehow providing a malicious file to the LaunchURL command.

The natural next thought is a UNC path–host a batch file on an attacker-controlled SMB share and pass \\attacker\share\payload.bat. Unfortunately, LaunchURLInternal only routes valid, non-HTTP/HTTPS schemes (like file:// or steam://) to ShellExecuteW. UNC paths don’t have a scheme, so they were routed to LaunchWebURL and opened in the browser.

Thus, the question became: how do you plant a file on the victim’s machine, at a path you know in advance?

Arbitrary File Delivery

Lucky for me, Steam Workshop items (such as maps) are essentially just folders. When a player uploads content, Steam intentionally allows every file type to be uploaded, leaving it up to the developer to restrict what files are mounted. Their documentation even calls this out, noting to developers that “your submission tool should accept just the file formats your game client expects to load.”

Upon testing, Meccha Chameleon enforced no restrictions on uploaded file types. So let’s just toss a file in the map folder itself, and LaunchURL that path!

To pull this off, I needed a Workshop item ID first. Steam downloads Workshop content to a predictable path on every subscriber’s machine - C:\Program Files (x86)\Steam\steamapps\workshop\content\\\ - but you don’t get an item ID until you’ve uploaded something. So the first step was uploading a benign map just to claim one.

After following many, many tutorials, I created a test map in UE5 and uploaded it.

Steam assigned it ID 3747657449. Combined with the game’s AppID (4704690), this meant most subscribers would download the map’s files to C:\Program Files (x86)\Steam\steamapps\workshop\content\4704690\3747657449\.

With the path known, I...

steam file game workshop path maps

Related Articles