How to open calc.exe from S&Box

How to open calc.exe from S&Box

如何从 S&Box 中打开 calc.exe

Thursday May 21, 2026 2026年5月21日,星期四

So, S&Box went “open source”. I don’t personally have any interest in the platform, but I did have interest in how they securely execute C# code… So S&Box is “Garry’s Mod 2”, or maybe it’s Roblox Source 2, I really don’t care about it directly. The thing that’s relevant to me is that instead of using Lua or something, they use full powered C# for game scripting. C#/.NET is not Lua: it is not designed to run untrusted code, so how do they get away with it? 所以,S&Box “开源”了。我个人对这个平台没什么兴趣,但我对他们如何安全地执行 C# 代码很感兴趣……S&Box 可以说是“Garry’s Mod 2”,或者说是 Roblox 版的 Source 2,我并不关心它本身。与我相关的是,他们没有使用 Lua 之类的脚本语言,而是使用功能完整的 C# 进行游戏脚本编写。C#/.NET 并非 Lua:它并非为运行不受信任的代码而设计,那么他们是怎么做到的呢?

The solution is quite simple: they scan your code and block loading it if you use any APIs like File.Open(). Now, is this secure? Well… To be clear: if you are a security-minded person, this is an atrocious idea. The .NET runtime is not hardened to intentionally execute hostile code like a browser Javascript engine is. It used to be in the .NET Framework days, but they gave up on that with modern .NET, and I’ve heard there was a giant streak of vulnerabilities back then anyway. 解决方案很简单:他们会扫描你的代码,如果你使用了像 File.Open() 这样的 API,就会阻止加载。那么,这安全吗?嗯……明确地说:如果你是一个注重安全的人,这简直是个糟糕透顶的主意。.NET 运行时并没有像浏览器 JavaScript 引擎那样,为了执行恶意代码而进行加固。在 .NET Framework 时代曾经有过,但现代 .NET 放弃了这一点,而且我听说那时候也有一连串巨大的漏洞。

On the other hand… Space Station 14 does exactly the same thing, and I wrote the damn code for that. I’m not going to pretend I’m any better than them. The reason why we’re both using C# is extremely simple: C# is hands down one of the best programming languages ever, full stop. I could do an entire blog post on just how good C# is, but I’ll spare you that here. In Space Station 14’s case, it’s no exaggeration to say that it’s a huge factor of why the project even succeeded in the first place. 另一方面……《空间站 14》(Space Station 14) 做的也是同样的事情,而那该死的代码还是我写的。我不会假装自己比他们强。我们都使用 C# 的原因非常简单:C# 绝对是有史以来最好的编程语言之一,没有之一。我可以专门写一篇博客来夸赞 C# 有多好,但这里就不赘述了。就《空间站 14》而言,毫不夸张地说,这是该项目能够成功的一个重要因素。

But anyways, let’s check the sandbox whitelist, sha- spits out drink // Compiler generates all this scary shit that the user shouldn’t be using // User code is checked in Sandbox.Compiling blacklist “System.Private.CoreLib/System.Runtime.CompilerServices.Unsafe.Add*”, “System.Private.CoreLib/System.Runtime.CompilerServices.Unsafe.As*”, “System.Private.CoreLib/System.Runtime.CompilerServices.Unsafe.AsRef*”, What are they thinking? It can’t be that easy can it??? Can we smash the stack? Alright. Let’s just try this. There’s no way this works. Let’s install the damn game, open the edit- the editor crashes on load. God damnit. And so does the game? What is going on? 总之,让我们看看沙盒白名单,(喷出饮料) // 编译器生成了所有用户不应该使用的可怕玩意儿 // 用户代码在 Sandbox.Compiling 黑名单中进行检查 “System.Private.CoreLib/System.Runtime.CompilerServices.Unsafe.Add*”, “System.Private.CoreLib/System.Runtime.CompilerServices.Unsafe.As*”, “System.Private.CoreLib/System.Runtime.CompilerServices.Unsafe.AsRef*”, 他们在想什么?不可能这么简单吧???我们能破坏堆栈吗?好吧,试试看。这绝对不可能成功。安装该死游戏,打开编辑器——编辑器在加载时崩溃了。该死。游戏也崩溃了?到底发生了什么?

Half an hour of fudging around later I bother to attach WinDBG to the editor and… Of course I don’t regret buying an AMD GPU, why do you ask? Yes, AMD’s GPU driver has a bug where it will cause an app crash if an app checks for VR without SteamVR running but with a Valve Index plugged in. Unplugs DisplayPort cable. I open the a new project in the editor, open the C# example component it generated, paste in the following code: var i = 5; ref var r = ref i; while (true) { r = 0; r = ref Unsafe.Add(ref r, 1); } …and the editor refuses to compile it. Okay, whatever, we saw that coming. Thankfully I don’t even need dnSpy for this, we can just compile it ourselves!. I removed the compile blacklist with a simple && false in the right spot, and opened it again. It compiles the code without a hitch. 折腾了半小时后,我终于想起来把 WinDBG 附加到编辑器上……当然,我不后悔买 AMD 显卡,你问这个干嘛?是的,AMD 的 GPU 驱动程序有个 Bug:如果应用程序在 SteamVR 未运行的情况下检查 VR,且插着 Valve Index,就会导致程序崩溃。拔掉 DisplayPort 线。我在编辑器中打开一个新项目,打开它生成的 C# 示例组件,粘贴以下代码: var i = 5; ref var r = ref i; while (true) { r = 0; r = ref Unsafe.Add(ref r, 1); } ……结果编辑器拒绝编译它。好吧,无所谓,我们预料到了。谢天谢地,我甚至不需要 dnSpy,我们可以自己编译!我在合适的位置加了一个简单的 && false 移除了编译黑名单,然后再次打开。它顺利地编译了代码。

I make a scene, add the component to an empty object, press run… and the editor crashes! As expected. But will it work in-game? I find the publish button, create an organization first (I claimed slugcat), publish it, check the game settings to make sure it’s unlisted (I am somewhat responsible), and… I can’t find my game in the UI anywhere. It says that unlisted games can be found “by URL” or “by people in the organization” but I sure didn’t find shit like that in the game’s game browser. 我创建了一个场景,将组件添加到一个空对象上,按下运行……编辑器崩溃了!不出所料。但它在游戏中能运行吗?我找到发布按钮,先创建一个组织(我认领了 slugcat),发布它,检查游戏设置确保它是未列出的(我还是有点责任感的),然后……我在 UI 的任何地方都找不到我的游戏。它说未列出的游戏可以通过“URL”或“组织内的人员”找到,但我确实在游戏浏览器里没找到任何类似的东西。

After 20 minutes of flipping through UIs, going insane, and even asking a friend, I realized I could run game slugcat.calc in the console to load the game. Aaand… it didn’t crash, because I made a new scene instead of modifying the default example scene that actually got loaded. One publish later (thankfully, publishing is quite fast) and… Game crash. Oh my gooood 在翻阅了 20 分钟 UI、快要疯掉甚至问了朋友之后,我意识到可以在控制台运行 game slugcat.calc 来加载游戏。然后……它没有崩溃,因为我创建了一个新场景,而不是修改了默认加载的示例场景。再次发布后(谢天谢地,发布速度很快)……游戏崩溃了。天哪。

Allowing unsafe code Okay, so, since they’re clearly not checking shit when you load the game, and they sure as hell don’t have an ILVerify step like we do in SS14, we should just be able to compile full unsafe code and get away with it. So just modify the editor again. I want to say this was “pretty easy” but admittedly it took quite a bit of trial and effort. Editing & restarting the editor took a good while. 允许不安全代码 (Unsafe code) 好吧,既然他们在加载游戏时显然没有进行任何检查,而且他们肯定没有像我们在《空间站 14》中那样进行 ILVerify 步骤,我们应该可以直接编译完整的 unsafe 代码并蒙混过关。所以再次修改编辑器。我想说这“相当容易”,但不得不承认这花了不少尝试和努力。编辑并重启编辑器花了不少时间。

I couldn’t just tell it to allow unsafe code, because that makes the compiler put [UnverifiableCode] on the module and that trips their loader sandbox. So I had to whip out Mono.Cecil and strip that shit. It wasn’t hard. It was just painful iteration, me not reading error messages correctly, and being a bit of a clown. Also, extremely annoyingly, if you restart the editor while your code is failing to load due to a sandbox error, it will never load it again. This is, of course, likely to never matter to anybody but me. 我不能直接告诉它允许 unsafe 代码,因为这会让编译器在模块上加上 [UnverifiableCode],从而触发他们的加载器沙盒。所以我不得不祭出 Mono.Cecil 把那玩意儿剥离掉。这并不难,只是痛苦的迭代过程,加上我没正确阅读错误信息,表现得像个小丑。此外,极其烦人的是,如果你在代码因沙盒错误而无法加载时重启编辑器,它就再也加载不了了。当然,这可能除了我之外对任何人都不重要。

With that, I had unsafe code running in the editor. Strictly speaking I didn’t need this, as C#’s unsafe doesn’t work like Rust’s: it only gates pointer types themselves, not any of the equally lethal APIs like Unsafe.As(). That said, writing pointer code with pointer syntax is a whole lot easier than whatever you’d make with Unsafe.As(). 至此,我已经在编辑器中运行了 unsafe 代码。严格来说我并不需要这个,因为 C# 的 unsafe 与 Rust 的不同:它只限制指针类型本身,而不限制像 Unsafe.As() 这样同样致命的 API。话虽如此,用指针语法编写指针代码比用 Unsafe.As() 搞出来的东西要容易得多。

Getting up to shenanigans In my years working on SS14 I’ve spent a fair amount of time in the C# Discord, and the most fun place there is #allow-unsafe-blocks. I’ve done my fair share of cursed Unsafe.As shenanigans for optimizations that I’m pretty sure won’t break anything. I’ve never used this knowledge for malice, however. For example, S&Box’s whitelist allows us to get some basic info about Type objects, but it blocks access to the Assembly property (and the Assembly type itself, of course). I know, however, that Type.Assembly { get; } is a virtual function, and virtual functions are implemented with vtables. So if we just use Unsafe.As() to pretend a Type is something else, we can call any virtual function we want on it: var type = typeof(Type); var assembly = Unsafe.As(type).REAL(); public class Lol { public virtual void A4() { } public virtual void A5() { } public 搞点恶作剧 在《空间站 14》工作的这些年里,我在 C# Discord 上花了不少时间,那里最有趣的地方是 #allow-unsafe-blocks 频道。我做过不少为了优化而进行的、我确信不会破坏任何东西的“诅咒级” Unsafe.As 恶作剧。然而,我从未将这些知识用于恶意目的。例如,S&Box 的白名单允许我们获取关于 Type 对象的一些基本信息,但它阻止了对 Assembly 属性(当然还有 Assembly 类型本身)的访问。然而我知道,Type.Assembly { get; } 是一个虚函数,而虚函数是通过 vtable 实现的。所以如果我们只是使用 Unsafe.As() 把一个 Type 伪装成其他东西,我们就可以在它上面调用任何我们想要的虚函数: var type = typeof(Type); var assembly = Unsafe.As(type).REAL(); public class Lol { public virtual void A4() { } public virtual void A5() { } public