4 min left
702 words
4 minutes

Building a Native x64-to-C# Transpiler: Why I Built Alicorn

Why I Built Alicorn#

Alicorn started with a question I couldn’t stop asking: what if you could take raw x64 machine code and turn it into readable, editable C#?

Not decompiling. Not reverse engineering. Something stranger and more useful: a transpiler that carries native execution into managed C#.

The Moment It Clicked#

I was working on a project that needed to call into native libraries. P/Invoke worked, but it felt like a seam—something that could tear. I started wondering: what if instead of calling across the boundary, you dissolved it?

What if a DLL’s logic could become just another C# class?

That’s the core idea of Alicorn: native x64 binaries, transformed into C# that runs inside the CLR. No unmanaged calls. No marshaling overhead. Just C# that happens to do what the original binary did.

What It Actually Does#

Alicorn takes x64 machine code and lifts it into C# through a few stages:

  1. Disassembly — using Iced, it parses raw bytes into instructions
  2. Control flow analysis — building a graph of blocks and branches
  3. Semantic lifting — translating x64 operations into C# equivalents
  4. Code generation — emitting valid, compileable C#

Four-stage breakdown of how Alicorn lifts x64 into C#: disassembly of bytes into instructions, control flow analysis to build a CFG, semantic lifting that recovers intent and makes state explicit, and code generation emitting readable C# — with a case study on how the implicit flags register becomes an explicit flags object

The hardest part cuts across every stage: the assumptions.

The Assumptions That Break#

x64 code assumes things the CLR doesn’t allow. Direct memory access. Self-modifying code. Hardware exceptions as control flow. Every one of these is a small earthquake in the translation.

The most interesting challenge so far: flags.

x64 has a flags register (RFLAGS) that instructions implicitly read and write. CMP sets flags. JE reads them. In C#, there is no flags register. You have to make the data flow explicit:

// x64:
// cmp eax, ebx
// je equal
// Becomes C# (illustrative fragment — the `equal` label is generated elsewhere):
var flags = Comparisons.CompareEAX_EBX();
if (flags.Zero)
{
goto equal;
}

This is where the project lives: in the gap between two execution models, finding honest translations.

What I Learned About Boundaries#

Building Alicorn taught me something I didn’t expect: boundaries are stories we tell ourselves about where one thing ends and another begins.

The native/managed boundary is a convention — one that looks absolute from a distance but doesn’t survive close scrutiny. The CPU doesn’t care whether the bytes it’s executing came from a JIT or a linker. What matters is whether the abstraction holds.

Alicorn is an experiment in making that abstraction porous. Understanding it — not removing it.

Current State#

Alicorn is early. It handles a subset of x64 instructions. It generates C# that compiles and runs for simple cases. It breaks on anything involving system calls, floating point, or complex memory layouts.

But the path forward is clear:

  • Expand instruction coverage
  • Handle memory models safely
  • Support more calling conventions
  • Eventually: transpile real-world binaries

Why I Keep Building It#

Because it’s the kind of project that asks better questions than it answers.

Every time I sit down to implement a new instruction, I have to ask: what does this mean? The manual describes behavior. The code expresses intent. Alicorn lives in the space between them.

Also, it’s fun. There’s something deeply satisfying about watching a transpiler produce C# that compiles on the first try. Even if that C# is weird. Even if it’s full of goto. It works. And it came from something that wasn’t C# at all.

Not Open Source Yet#

Alicorn isn’t open source yet. I’m doing a big refactor and cleanup on the project — the kind that turns a working prototype into something other people can actually read. The plan is to publish it by the end of 2026, depending on how much time I can give it.

If that timeline slips, it slips. The transpiler itself works; what’s missing is the polish. If you’ve ever wondered what happens when you erase the line between native and managed, this is one answer — and the code will land when it’s ready to be read.

Series Navigation#

This article is part of the Projects series:

Building a Native x64-to-C# Transpiler: Why I Built Alicorn
https://corentings.dev/blog/why-i-built-alicorn/
Author
Corentin Giaufer Saubert
Published at
2026-07-07
License
CC BY-NC-SA 4.0
Share this post