Skip to content

Architecture

Ezz Aldeen Bayoumi edited this page Aug 26, 2026 · 2 revisions

Brovan's Architecture

Brovan is a multi-guest emulator, with a large set of components shared between the guests. It runs Windows and Linux programs, and it runs on Windows, Linux and Android hosts.

Brovan is not tied to one CPU backend anymore. Three backends are available today, and you select one with --backend=<name>.

  • Unicorn is the default. It translates the guest code, so the emulator sees every instruction and can hook it. This is the backend to use for analysis.
  • KVM runs the guest on the real CPU through the Linux hypervisor.
  • WHP runs the guest on the real CPU through the Windows Hypervisor Platform.

The hypervisor backends are much faster, but they don't have the analysis capabilities unicorn has. so some analysis features stay Unicorn-only.

Main core components

  • Core/BinaryFile.cs which parses binary file formats. Many parts of the emulator use it, either from a guest or from the core, to do the operations related to that guest, or to do analysis. For example, the Windows guest uses it to parse other modules and to fill their information into WinModule.

  • Core/Emulation/BinaryEmulator.cs which is the main binary emulation interface. It accepts a guest directly, or a BinaryFile that tells it which guest to build. It owns the guest memory map, the handle and object state, the MLFQ scheduler, and the callbacks that guests plug into, such as ExecuteThreadSlice and OnThreadContextLoaded. It also receives the runtime events like CPUID, syscalls, interrupts and memory faults, and it sends them to the active guest.

  • Core/Emulation/BinaryEmulatorHelper.cs which holds the contracts and the small types the core needs. IGuestEnvironment is the interface every guest implements. IWinSyscall and ILinuxSyscall are the two syscall handler interfaces, and each one documents its argument convention for x86 and for x64. SyscallDispatchTable<TEntry> is the packed table used to find a handler from a syscall number without a dictionary lookup. MemoryRegion, the allocation and protection flags, and BrovanSocket, which applies the network policy, also live here.

  • Core/Emulation/RuntimeTypes.cs which holds the runtime thread types. EmulatedThread and CpuContext describe a guest thread and its saved registers, including the vector registers. WakeSignal is the counter that tells the scheduler that something can unblock a thread.

  • Core/Emulation/SyscallManager.cs which holds the user-facing syscall rules, the syscall trace, and the call history that the interactive menu shows. It is separate from the handlers themselves.

  • Core/Emulation/Backends/ which contains IEmulationBackend, the interface that hides the CPU backend from the rest of the emulator, BackendFactory, which builds the one you asked for, and one folder for each backend. The interface also reports what the backend can and cannot do, for example the highest address it can map and whether RDTSC reaches the emulator.

  • Core/Emulation/UnicornBinding/, Core/Emulation/KvmBinding/ and Core/Emulation/WhpBinding/ which contain the native bindings, constants and imports for each backend. UnicornCodeCache.cs saves and reloads the translated code, so a program does not translate itself again on every launch.

  • Core/Emulation/Guests/ which contains GuestFactory and the guests. A PE file goes to WindowsGuest, an ELF file goes to LinuxGuest, and GenericGuest runs a raw blob or a memory dump that has no recognized format, including ARM code.

  • Core/Helpers/ which contains the shared helpers. BinaryHelpers.cs holds the PE, .NET and ELF parsing types and the common enums. Utils.cs and SimdStringHelpers.cs hold general utilities. RegistryManager.cs reads the real registry hives that the guest sees. WindowsImage/ holds the importer used by --install-windows, with the ISO, WIM, LZX, XPRESS and LZMS readers, plus the Visual C++ runtime and DXVK importers.

  • GeneralHelper.cs which is the general-purpose helper. It contains the native imports for some Windows and Linux functions, the IO helper that gives sandboxed and cross-platform file access, and other utility functions.

  • Core/Emulation/OS/SharedHelpers/StructSerializer.cs which is the general struct helper. It reads a struct from guest memory, writes one back, and reports its size. StructSerializerGenerator generates the fast path for it, so a call site does not change.

  • Core/Emulation/OS/SharedHelpers/WindowManager/ which is the host window layer that both guests use. WindowManager.cs holds the shared state and the host event queue, GuiThreadManager.cs runs the event-driven GUI thread, and WindowsWinManager.cs and LinuxWinManager.cs implement the host side. The Android host adds its own manager under Brovan/Android/.

  • Core/Emulation/OS/SharedHelpers/AudioManager/ which is the host audio layer, with a sink for each host. The Windows guest audio path runs on top of it.

  • Analysis/ which contains BinaryAnalyzer.cs, the disassembly and analysis code built on Iced, and NetworkTrafficPcapCapture.cs, which writes the emulated network traffic to a pcap file.

  • EmulationMenu/ which contains the interactive command menu and the built-in debugger, including the call trace, the function monitor and the string scanner.

  • Brovan.Generators/ which contains the Roslyn source generators. StructSerializerGenerator emits the struct read and write code. VulkanForwardGenerator reads vk.xml and emits the Vulkan forwarding layer. WinRegistryGenerator emits registry-related code. Generators cannot rewrite call sites, so each one dispatches from a registry instead.

  • Brovan.Graphics/ which contains the graphics side. brovvulk-icd/ is the small Vulkan ICD that the guest loads, vulkan-headers/ holds the headers it builds against, and demo/ is a sample.

  • Brovan/Android/ and Brovan.Android/ which contain the Android host code and the launcher application. Brovan/Android/ holds the host implementations, such as the window manager, the audio sink, input, text and the Vulkan WSI. Brovan.Android/ is the app project that packages them.

Scheduling and the wake epoch

The MLFQ scheduler lives in BinaryEmulator. It gives each runnable thread a slice, drops a thread that uses its whole slice to a lower queue, and ages a thread that waited too long back up.

The scheduler skips its wakeup scan when nothing changed, and it decides that from WakeSignal, a counter that every event able to unblock a thread must increase. The eight waitable handle types increase it from their setters. Everything else increases it at the point where the event happens, such as window messages, host input, APCs, thread termination and resume, futex wake, signals and eventfd.

A missing increase drops a wakeup. That reads as a hang, and no test fails, so be careful when you add a new way to unblock a thread. EmulatedThread.State has no setter that increases the counter for you, and that is on purpose.

Windows-specific core files

All of these are under Core/Emulation/OS/Windows/.

  • Guests/WindowsGuest.cs which contains the Windows guest itself. It handles the process setup, module loading, PEB and TEB creation, thread start, APC dispatch, and the Windows-specific flow used at runtime. It also builds the WOW64 view for 32-bit guests, which needs a second TEB and PEB pair and the System32 to SysWOW64 path redirection.

  • BinaryEmulator.WindowsBridge.cs which connects BinaryEmulator to the Windows state and helpers, and exposes the Windows accessors that the core uses. This part is legacy and will be removed later. It only exists because the guest-style implementation came after many syscalls already expected everything to live in BinaryEmulator.cs.

  • WinSyscallsHelper.cs which contains the shared Windows syscall support code, such as handle and object management, the shared buffers, the device control helpers, and the logic reused across the handlers. Use the shared buffer it already exposes instead of allocating a new one.

  • WinInternalHelper.cs and WinHelperConstants.cs which hold the internal Windows helper types and the constants and device data types that the handlers share.

  • WinStructs.cs which contains many Windows structure definitions and their helpers. Some structures are written straight into guest memory by hand, so you will not find every structure the emulator uses in this file.

  • WinThreading.cs which contains the Windows thread state, such as APC tracking, exception state, worker factory state, and the other per-thread Windows data.

  • WindowsFileStream.cs which is the Windows file stream used by the guest. It prefers the Windows VFS and falls back to the allowed host files when that is correct.

  • WindowsVersionInfo.cs which contains the version and build information written into guest memory and reported to Windows programs.

  • Devices/ which contains the emulated Windows devices. It includes ConDrv for the console, the AFD socket device, the mount point manager, the physical disk and volume devices, WMI, the KsecDD device that serves CNG, and BrovVulk, which forwards Vulkan from the guest to the host driver.

  • Session/ which contains the guest session table, the mailbox, and the remote process support. Each guest process runs in its own Brovan process, and these files let the members of one session find and talk to each other.

  • RPC/ which contains the ALPC and LRPC support, the NDR reader, and the ports under RPC/Ports/, such as the API port and the audio service port.

  • Files/, Misc/, Process/, Registry/ and Win32k/ which contain the Windows syscall handlers grouped by subsystem. They cover file operations, synchronization and object handling, process, thread and memory control, registry access, and the win32k and user-mode GUI behaviour, which includes GDI drawing, window management, input and DPI.

Linux-specific core files

All of these are under Core/Emulation/OS/Linux/, except the guest itself.

  • Guests/LinuxGuest.cs which contains the Linux guest. It handles ELF loading, module mapping, thread and signal state, syscall dispatch, and the Linux-specific flow.

  • LinuxSyscallsHelper.cs which contains the shared Linux syscall support code, such as the errno values, the stat helpers, the socket and file helpers, and the memory and process utilities. It also exposes a shared buffer, the same as the Windows side.

  • LinuxFileStream.cs which is the Linux file stream used by the guest. It prefers the Linux VFS paths and falls back to host files when that is correct.

  • LinuxStructs.cs which contains the Linux structure definitions and their helpers.

  • SpecialPathsHandlers.cs which handles the special paths such as /dev, /proc and /sys, and gives the emulated content for the virtual files and the device-like paths that Linux programs read.

  • Events/ which contains the event syscalls such as epoll, eventfd and timerfd.

  • Files/ which contains the file syscalls such as open, read, write, stat, fcntl, lseek, mount and directory enumeration.

  • Misc/ which contains the remaining syscalls such as clock_gettime, poll, pselect6, getrandom, uname and close.

  • network/ which contains the networking syscalls such as socket, bind, connect, accept, sendto, recvfrom, setsockopt and socketcall.

  • Process/ which contains the process and memory syscalls such as clone, mmap, mprotect, mremap, munmap, prctl, futex, brk and arch_prctl.

  • Signals/ which contains the signal syscalls such as kill, tgkill, tkill, rt_sigaction, rt_sigprocmask, rt_sigsuspend and sigaltstack.

Flow

The flow keeps the core generic and lets each guest control its own behaviour.

  1. A BinaryFile is created from the input file.

    • It parses the file format and extracts the information needed for analysis and loading.
    • This can include the headers, sections, imports, exports, symbols and other format-specific data.
  2. A backend is created from BackendFactory.

    • The --backend option selects Unicorn, KVM or WHP. Unicorn is the default.
    • The rest of the emulator only sees IEmulationBackend, so the choice does not change the code above it.
  3. BinaryEmulator receives either the BinaryFile or a guest directly.

    • If a BinaryFile is given, GuestFactory picks the guest from the file format.
    • A file with no recognized format can still run through GenericGuest.
  4. The selected guest does its own setup.

    • The Windows guest loads the modules, builds the module list, creates the PEB and TEB, and prepares the memory layout. For a 32-bit program it also builds the WOW64 view.
    • The Linux guest maps the ELF image and does its own process and memory setup.
  5. BinaryEmulator configures the execution state.

    • Memory mappings, registers, hooks and backend-specific settings are applied here.
    • The shared helpers such as StructSerializer and GeneralHelper are used when needed.
  6. Execution starts through the backend.

    • The backend executes the instructions.
    • During execution, the callbacks go back to the guest when guest-specific handling is needed.
  7. The guest and the scheduler work together at runtime.

    • The MLFQ scheduler manages the threads and their slices.
    • ExecuteThreadSlice and OnThreadContextLoaded let the guest join the scheduling without repeating the logic.
    • Anything that makes a blocked thread runnable must increase WakeSignal, or the scheduler does not look.
  8. Runtime events go through the shared callbacks.

    • CPUID, syscalls, interrupts, exceptions and memory faults arrive at BinaryEmulator.
    • The emulator sends them to the active guest, or handles them with the common logic when it can.

Clone this wiki locally