ResearchReal Executor
All research
Roblox executor WinLicense Manual DLL mapping Ed25519 auth HackerOne #3809852

Real Executor. Full stack recovered end to end

End to end reverse of Project Real's Roblox script executor. The installer's per download token is in the filename. That token passed /verify and got us the live signed manifest. The manifest gave us every component. We unpacked the injector's outer wrapper by hand, identified the packer as Oreans WinLicense, ran it under Wine 9.0 without any Wine detection tripping. Captured a 1.8 GB core dump of the fully decrypted state. Recovered 455 unique runtime log format strings, 434 Nt/Rtl/Ldr symbols, the injection technique (manual DLL mapping). Then we opened UI.exe and pulled the full auth surface out. 12 backend endpoints. Ed25519 server public key baked into the binary. Complete HWID v4 formula. Device keypair attestation flow. Every error code. Every account field. All of it.

28 min read Published 2026-07-12 Static + Wine dynamic + live core dump Coordinated with Roblox via HackerOne #3809852

Verdict

Real Executor is a well engineered paid Roblox script executor. The installer is a signed manifest client. The CDN is Cloudflare fronted with an Ed25519 signed component set. The injector is a Themida family packed (WinLicense) manual DLL mapping loader that ships a per Hyperion build patch pack, currently 231 sites for build 69 and 27 for build 67. The executor DLL is a custom Luau VM that fields the full UNC and sUNC executor surface (87 of 105 functions we probed). The UI is a Tauri app with Monaco and a bundled luau-lsp. The backend runs 12 signed endpoints under two hostnames with Discord OAuth. HWID v4 fingerprint. Per device Ed25519 keypair stored in Windows Credential Manager. Ed25519 signed responses verified client side against a baked in server public key.

Any real EDR flags every one of these binaries as HackTool or PUA. The injector openly names Roblox's anti cheat vendor as a PE section (.byfron) and embeds pep talk to it (WE LOVE HYPERION 67 and WE LOVE HYPERION 69) in the log format table. The executor DLL is version stamped as Crypto++ 8.9.0 to fool automated scanners. The auth surface is unusually clean. Injector holds no session state. All identity lives in UI.exe. That is the cleanest thing about the build.

The stack at a glance

Public sample     : RealSetup.exe          15 MB    unsigned installer
Persistent updater: Update.exe             7 MB     extracted from RealSetup RCDATA #101
Executor DLL      : Real.dll               2.94 MB  8-byte XOR encrypted at rest
                                                    custom Luau VM inside
Hyperion bypass   : Injector.exe           4.12 MB  WinLicense packed, manual DLL mapping
Front end         : UI.exe                 10.5 MB  Tauri + Monaco, holds all auth state
API host          : api.projectreal.live            Cloudflare fronted, Ed25519 signed responses
Web host          : projectreal.gg                  Discord OAuth landing
CDN               : download.projectreal.live      per release opaque slugs

Build we caught: version 1.1.0 build 118 sequence 1783781182 published 2026-07-11 14:46:22 UTC.

File identities

Product identity

ProductReal Executor by Project Real
Marketingprojectreal.gg · discord.gg/projectreal
Infrastructureapi.projectreal.live · download.projectreal.live · dl.projectreal.live
Current build1.1.0 · build 118 · sequence 1783781182
Published2026-07-11 14:46:22 UTC
FrontCloudflare on api.projectreal.live
Dev machine leakedC:\Users\Admin\Documents\LUA\GitHub-Repos\Real-Executor

Injector.exe

FileInjector.exe (Real.exe in the CDN manifest)
SHA-256949ed91c5395e407b3b29c7842bca3c50b61c0df62b9a891211f7297c79f148c
Size4,118,528 bytes
Compile timestamp2026-07-11 14:39:03 UTC
ImageBase0x140000000
EntryPoint RVA0x6d0058
Signatureunsigned

Real.dll

FileReal.dll (encrypted on disk)
Size2,940,176 bytes
Cipher8 byte repeating XOR
Key2A 27 94 7A E9 EA DA 78
Decrypted ImageBase0x180000000
Decrypted EntryPoint0x1e16e0
Compile timestamp2026-07-11 14:32:14 UTC
Version resource lies asCrypto++ Library DLL 8.9.0 by Wei Dai

UI.exe

FileUI.exe (Real.exe in the CDN manifest)
Size10,549,760 bytes
FrameworkTauri 2.11.2 (Rust) + Microsoft Edge WebView2
EditorMonaco (VS Code engine) + luau-lsp autocomplete
HTTP clientreqwest 0.12.28 + cookie_store 0.22.1
Keyringkeyring 3.6.3 (Windows Credential Manager)
VersionReal 0.6.4

Live protocol we replayed

The sample filename ends in a 64 character hex string. That is the install token. Sent as is to /verify. It came back valid.

POST https://download.projectreal.live/verify
     User-Agent: RealLauncher/1.0
     Content-Type: application/json
     {"token":"c6ea55866cbce887a249a04304efe91696a388f3f28b82eb2e982afce7e097cf"}

200 OK
{"ok":true,"proof":"542845be23d68b5cdcebb7a45abb7d8dcdcfc662d2ee3ca36774dfa3c6e7bdf8"}

The Cloudflare fronted api.projectreal.live refused the same probe with a canonical Cloudflare block page. The CDN edge on download.projectreal.live accepted it. Two different fronts with two different policies. That gap tells you how the vendor scopes threat models between control plane and content plane.

The update endpoint required no auth beyond the User Agent match.

GET https://api.projectreal.live/update/check
     User-Agent: RealLauncher/1.0

200 OK  (JSON manifest, annotated in the next section)

Every file URL in the manifest served the actual bytes with no auth. SHA-256 verification matched the manifest values byte for byte.

The signed manifest, annotated

{
  "version":      "1.1.0",
  "buildNumber":  118,                     // anti rollback
  "sequence":     1783781182,              // anti replay
  "publishedAt":  "2026-07-11 14:46:22",
  "status":       "Working",
  "files": {
    "dll": {
      "url":       "https://download.projectreal.live/releases/MkQskkR19VHtJ-MZ/bin/Real.dll",
      "sha256":    "2756337a38a4525aa9b90b5dd2a843526d79f5b345258f56e8b093b51122b167",
      "signature": "w99tPyMIPD15NWw3R1ftK6VXut-VrMnsUX0B7L8qkiCP4uwTMhBC49YIZx4jSIRTJoiEKjIgvv4u0Fr9pAvoBg",
      "size":      2940176
    },
    "injector": {
      "url":       "https://download.projectreal.live/releases/MkQskkR19VHtJ-MZ/bin/Real.exe",
      "sha256":    "949ed91c5395e407b3b29c7842bca3c50b61c0df62b9a891211f7297c79f148c",
      "signature": "p5OGfUdxZa7zzCf4JZjzjDSKHf-MCu2px4gQKjCh7DtssDIH1pN6oTIjkTFjEicu7IpeCbWZFmVNELZKZ_wjDQ",
      "size":      4118528
    },
    "ui": {
      "url":       "https://download.projectreal.live/releases/pUpgyTSjP8i6dotu/Real.exe",
      "sha256":    "11ceebe9c8af83e2aea61c28da7d7e0902daa9eda124f7a946c85b958152bbab",
      "signature": "aS3xGeeYqI2eTQaocZ7UaluHvWCqlDK1o-WRgaIaXsrPNLjaRi_6gD23XFQsO3RFpLwYnNMZwhd_Y6H4ol-QAQ",
      "size":      10549760
    }
  }
}

Every signature decodes from base64url to exactly 64 bytes. That is Ed25519 wire format. The verification key is the one recovered from UI.exe. See the response signing section for that pubkey.

Release paths use 16 character base64url slugs per release. Enumeration is not trivial from outside because a valid /update/check response is the only place they surface.

Real.dll. The executor

Real.dll ships XOR encrypted. Recovering the 8 byte repeating key took one look at the first 64 bytes lined up against a known PE header pattern (MZ 90 00 03 00 00 00 04). XOR the ciphertext against the plaintext, take modulo 8, done.

Recovered key: 2A 27 94 7A E9 EA DA 78

Decrypted it is a valid PE32+ x64 DLL. ImageBase 0x180000000. EntryPoint 0x1e16e0. Six standard sections. Compile timestamp 2026-07-11 14:32:14 UTC. About 32 minutes before Injector.exe was built. Same build train.

The Crypto++ decoy

The version resource lies. It claims to be Crypto++ Library DLL 8.9.0 by Crypto++ project copyright Wei Dai original filename cryptopp.dll. It is not Crypto++. This stamp exists so any tool that only reads version info sees a known open source library and stops.

What is actually inside

A full custom Luau VM. Namespaces recovered from MSVC RTTI strings:

RealVM::Compile      BuiltinVisitor · ConstantVisitor · CostVisitor
                     ShapeVisitor · TableMutationTracker · TypeMapVisitor · ValueVisitor
RealVM::Compiler     ConstUpvalueVisitor · FenvVisitor · FunctionVisitor
                     ReturnVisitor · UndefinedLocalVisitor · resolveAssignConflicts
Console              ConsoleInput (backs rconsoleinput)
Misc                 HttpGet · RBXMessageBox
Yielding             YieldThread

Roblox Luau FastFlag strings are present. LuauCompileInlineDepth, LuauCompileLoopUnrollThreshold, LuauCompileNewTableMutationTracker, LuauCompileStringInterpTargetTop, LuauCostModel, LuauCstAttr, LuauEmitCallFeedback, LuauGcTableStepFix, LuauIntegerBufferFastcalls and a couple dozen more. The fork tracks upstream Luau features closely.

Bytecode key hint

Failed to decode Luau bytecode with encode key 203 or 1. If this file uses
a different opcode encoding, it may be from a custom VM.

Key 203 is the shipping Roblox Luau bytecode encode key. Key 1 is the reference Luau default. Real accepts both and prints this diagnostic on a custom VM mismatch.

Identity function

Calling identifyexecutor() from a script returns the string "Real". Script authors commonly branch on this.

Executor API surface

The UI ships three .d.luau type declaration files for Monaco autocomplete. Full UNC and sUNC executor surface. 87 of 105 functions we probed for are present in Real.dll.

Identity      identifyexecutor · getexecutorname
Environment   getgc(includeTables) · getgenv · getreg · getrenv · getsenv · filtergc
Closures      checkcaller · clonefunction · hookfunction · hookmetamethod · newcclosure
              newlclosure · restorefunction · replaceclosure · iscclosure · islclosure
              isexecutorclosure · isourclosure · loadstring · getfunctionhash
Debug         debug.getconstant(s) · getproto(s) · getstack · getupvalue(s)
              setconstant · setstack · setupvalue · traceback · profilebegin/end · getinfo
Instances     getinstances · getnilinstances · getconnections · firesignal
              firetouchinterest · fireproximityprompt · fireclickdetector · cloneref
              compareinstances · gethiddenproperty · sethiddenproperty · getproperties
Metatables    getrawmetatable · setrawmetatable · setreadonly · isreadonly
              getnamecallmethod · setnamecallmethod
Scripts       getscripts · getloadedmodules · getcallingscript · decompile · getcallbackvalue
Filesystem    readfile · writefile · appendfile · isfile · isfolder · listfiles
              makefolder · delfile · delfolder · loadfile · dofile · getcustomasset
Input         keypress · keyrelease · mouse1click · mouse2click · mousescroll
              mousemoverel · mousemoveabs
Clipboard     setclipboard · getclipboard
Network       request · http_request · syn_request · WebSocket.connect
Drawing       Drawing.new(Line, Text, Image, Circle, Square, Triangle, Quad, Font)
              cleardrawcache · isrenderobj · getrenderproperty · setrenderproperty
Encoding      base64encode/decode · lz4compress/decompress · crypt.hash
              crypt.encrypt/decrypt · crypt.generatebytes/generatekey
Console       rconsoleprint · rconsoleinfo · rconsolewarn · rconsoleerr
              rconsoleclear · rconsolename · rconsoleinput
Compat        syn.request · getsynasset · queue_on_teleport · clearqueueonteleport

Third party decompile privacy leak

Real's decompile() ships bytecode off device. The implementation is recovered verbatim from Real.dll .rdata.

local res = request({
  Url    = "https://api.lua.expert/decompile",
  Method = "POST",
  Headers = { ["content-type"] = "application/json" },
  Body   = httpservice:JSONEncode({ script = encoder(bytecode) })
})

Every call to decompile uploads the target script's Luau bytecode to api.lua.expert. Anyone running a decompile through Real is handing that script to a third party. This is baked into the executor. Not opt in. Not disclosed at the UI layer.

Injector.exe. Outer layers

4.12 MB console subsystem PE. Only two visible imports. Everything else resolves dynamically at runtime.

SectionSize (virt / raw)EntropyPurpose
.text0x43406 / 0x436006.690Visible code plus the unpacker stub at the tail
.rdata0xfde4 / 0xfe005.422Read only data, log format strings live here
.data0x1af8 / 0x8003.962Initialized data
.pdata0x1464 / 0x16005.231SEH function tables
.byfron0x1c70 / 0x1e003.905960 qword pointer table indexing into .rdata blobs
.rsrc0x2c8 / 0x4004.109Resources
.idata0x1000 / 0x8003.567Visible import table
.tuffshi0x672000 / 00.000Runtime allocated. 6.7 MB, zero raw. Themida engine writes here.
.boot0x394e00 / 0x394e007.966aPLib compressed payload

The layout is diagnostic. High entropy compressed section, huge zero raw section that will receive the decompression output, tiny visible imports, oddly named sections. That is a shipped Themida or WinLicense build with default section randomization on.

Visible imports (that is the whole IAT)

kernel32.dll : GetModuleHandleA
ADVAPI32.dll : AdjustTokenPrivileges
USER32.dll   : MessageBoxA
MSVCP140.dll : one std::basic_iostream constructor
VERSION.dll  : GetFileVersionInfoA
ntdll.dll    : NtQueryInformationProcess
bcrypt.dll   : BCryptCloseAlgorithmProvider
ncrypt.dll   : NCryptCreatePersistedKey
CRYPT32.dll  : CryptBinaryToStringA
WINHTTP.dll  : WinHttpCloseHandle
VCRUNTIME140.dll · api-ms-win-crt-*.dll : the usual CRT scaffolding

One export per DLL. That is the packer telling the loader what libraries to keep resident. Everything the injector actually uses gets resolved dynamically by walking the PEB and hashing export names.

The aPLib wrapper reversed

Entry point is at RVA 0x6d0058. Inside .boot. The first bytes are Jibz's reference aPLib decompressor, verbatim.

0x1406d0058: e8 82 01 00 00        call 0x1406d01df
0x1406d005d: 41 52                 push r10
0x1406d005f: 49 89 e2              mov  r10, rsp
0x1406d0062: 41 52                 push r10
0x1406d0064: 49 8b 72 10           mov  rsi, [r10+16]    ; src ptr from stack
0x1406d0068: 49 8b 7a 20           mov  rdi, [r10+32]    ; dst ptr from stack
0x1406d006c: fc                    cld
0x1406d006d: b2 80                 mov  dl, 0x80         ; bit reservoir init
0x1406d006f: 8a 06                 mov  al, [rsi]        ; first literal
0x1406d0071: 48 ff c6              inc  rsi
0x1406d0074: 88 07                 mov  [rdi], al
0x1406d0076: 48 ff c7              inc  rdi
0x1406d0079: bb 02 00 00 00        mov  ebx, 2
0x1406d007e: 00 d2                 add  dl, dl           ; shift, CF is out bit
0x1406d0080: 75 07                 jnz  $+9
0x1406d0082: 8a 16                 mov  dl, [rsi]        ; refill
0x1406d0084: 48 ff c6              inc  rsi
0x1406d0087: 10 d2                 adc  dl, dl

The mov dl, 0x80 plus add dl,dl / jnc refill pattern is Jibz's aPLib signature. Bit at a time reservoir read, dispatching literal, short backref or long backref based on prefix bits.

Following the call, the driver at 0x1406d01df pops the return address and computes two constants that fall right out.

rbx = 0x14005E000                   ; runtime VA of .tuffshi
rbx + 0x36244c = 0x1403C044C        ; OEP after decompression
rax = 0x1406d005d                   ; aPLib decompressor entry

The stub uses a self locating call / pop / sub so it works whether relocated or not. Then it reads a segment count from a table at 0x1406d0248 and loops one aPLib call per segment.

Segment count byte : 0x10 (16 segments)
Each decompresses to 0x67200 bytes = 421,888 bytes
Total : 16 x 0x67200 = 0x672000 = exactly .tuffshi VSize

Perfect end to end match. Running the same aPLib against those 16 streams in Python takes about four seconds and produces the same bytes the packer produces at runtime.

The packer is Oreans WinLicense

Modern Oreans builds do not name their sections .themida or .taz anymore. They pick random names like .tuffshi and .boot. The identification has to be structural.

String evidence. Oreans product family markers

String                         Meaning
WinLicense                     Oreans product family literal
WinLicenseInstance             single occurrence
WinLicenseDriverVersion        kernel driver version field
WinLicenseVersion              product version field
SOFTWARE\WinLicense            registry key WinLicense's runtime queries
/checkprotection               WinLicense debug CLI switch
/dumpstatus                    WinLicense debug CLI switch
/bugcheckfull                  WinLicense debug CLI switch
/showinstance                  WinLicense debug CLI switch
/nosplash                      WinLicense debug CLI switch
/bugcheck                      WinLicense debug CLI switch
!ProcOUT                       Themida VM handler marker

These strings do not appear in generic packers, in Chromium, in Qt, in any common OSS library. They only ship in Oreans engines.

Structural evidence. The VM exit epilogue count

Themida VM handlers exit by popping every register they touched. When a handler is register heavy it pops r8 through r15 with REX-B prefixes. In memory that reads as bytes 41 58 41 59 41 5A 41 5B 41 5C 41 5D 41 5E 41 5F which as ASCII is AXAYAZA[A\A]A^A_. Distinctive. Greppable.

Themida VM exit epilogue pattern
    unpacked .tuffshi                     : 23 occurrences
    Real.dll (custom Luau VM, comparison) :  0 occurrences

The custom Luau VM in Real.dll is not Themida wrapped. Zero hits there. The injector's .tuffshi shows 23. That is the tell.

.byfron. Per Hyperion build metadata index

.byfron is 7,680 bytes on disk. Writable, low entropy, pure data. Contents are 960 qword pointers into .rdata.

Distribution by target section:
    .rdata        : 910
    OUT-OF-IMAGE  :  50   (zero quads / trailing padding)
Unique pointer values : 911
Duplicates            : ~50

Every non null pointer targets one of two blob shapes in .rdata.

Blob shape A. Shared framework (32 blobs)

.rdata+0x49391 (first pointer target):
  00: 57 45 20 4c 4f 56 45 20 48 59 50 45 52 49 4f 4e    WE LOVE HYPERION
  10: 00 01 02 03 32 36 38 00 70 61 74 63 68 69 6e 67    ....268.patching
  20: 20 25 58 00 01 02 03 31 31 36 38 00 50 61 74 63     %X....1168.Patc
  30: 68 69 6e 67 20 69 6e 74 65 67 72 69 74 79 20 61    hing integrity a
  40: 74 20 25 58 2e 2e 2e 00 01 02 03 32 30 36 38 00    t %X.......2068.
  50: 70 61 74 63 68 69 6e 67 20 25 58 00 01 02 03 31    patching %X....1

Layout: magic string, delimiter 00 01 02 03, ASCII decimal site ID, null, printf format string, repeat. The format strings are "patching %X" and "Patching integrity at %X...". Successive pointers cycle through site IDs 268 to 576 incrementing by 2.

Blob shape B. Per Hyperion build patch site tables

WE LOVE HYPERION            (unversioned) x 32   framework
WE LOVE HYPERION 67                       x 32   Hyperion build 67 support
WE LOVE HYPERION 69                       x 256  Hyperion build 69 support (primary)
JUSTMAGIC WE LOVE YOU                     x 256  paired ACK / success marker

32 versus 256 is the tell. Build 67 has partial support left in for backwards compatibility. Build 69 is the current target with 256 patch sites. Every 69 blob is paired with a JUSTMAGIC blob. One operation and one ACK per patched call site.

What .byfron is not

Not the patch data. The pointer table indexes log metadata that the injector's runtime debug output consumes. The specific memory addresses being patched and the exact byte sequences written are runtime computed. Real signature scans Hyperion's loaded image and generates addresses live. That is why Real can rev support from Hyperion 67 to 68 to 69 without reshipping. Signatures stay stable. Addresses drift per build.

Running the injector under Wine

$ export WINEPREFIX=./wine_prefix
$ export DISPLAY=:99
$ Xvfb :99 -screen 0 1024x768x24 &
$ wine wineboot --init
$ wine Injector.exe
$ cat log.txt
Roblox not found
Roblox not found
Roblox not found

The injector runs. Themida bootstraps to completion. Themida does not detect Wine in this build. The injector's actual code executes, scans the process list, fails to find Roblox, writes its own log.txt to cwd, retries three times, exits clean.

The log.txt literal is a durable on host IOC.

Live core dump analysis

While the injector is alive (about three seconds under Wine before it exits), gcore on the actual process PID captures a full core dump. The Wine process names its threads after the running .exe so /proc/*/comm matching Injector.exe gives the right target.

ps polling for /proc/*/comm == "Injector.exe" ...
found pid 17520 on tick 3
gcore -o injproc_core 17520 ...
Saved corefile injproc_core.17520 (1,812,545,208 bytes)

Memory map at that moment:

140000000-140001000 r--p  PE headers
140001000-140045000 r-xp  .text
140045000-140055000 r--p  .rdata
140055000-140057000 rw-p  .data
140057000-140059000 r--p  .pdata + .byfron RO half
140059000-14005b000 rw-p  .byfron RW half
14005b000-14005d000 r--p  .rsrc
14005d000-14005e000 rw-p  .idata
14005e000-1406d0000 rwxp  .tuffshi (RWX at runtime)
1406d0000-140a65000 r-xp  .boot

.tuffshi is RWX at runtime. Self modifying by design. Diffing live .tuffshi against the aPLib output shows 75.2 percent of bytes differ. Themida decodes almost everything under it after initial unpack.

The full log format string catalog

The Themida decrypted heap at file offset 0x68bdd000 in the core contains the fully decoded log format string table. Every format string the injector emits. Every numeric site ID. We parsed 455 unique entries. Grouped by phase.

Phase 1. Framework and setup

ID rangeCountFormat string
268 to 2076111 sitespatching %X
5761 site.byfron
668 to 108625 sitesJUSTMAGIC WE LOVE YOU

Phase 2. Integrity patching

ID rangeCountFormat string
11281 siteRoblox not found (observed live)
1178 and 11802 sitesPatching integrity at %X...
11821 siteWrong Roblox Version
11841 siteRemapped
11861 siteSuccessfully Injected!

Phase 3. Per Hyperion build patch tables

ID rangeCountFormat string
1278 to 133027 sitesWE LOVE HYPERION (framework)
1332 to 139427 sitesWE LOVE HYPERION 67
1396 to 1906231 sitesWE LOVE HYPERION 69 (primary target)

Phase 4. Injection completion

ID rangeCountFormat string
1908 to 193816 sitesremapping done
1940 to 19548 sitesinjected
1956 to 19624 sitesmanual mapping

The 231 build 69 patch sites is a striking count. This is not a couple of surgical writes. Hyperion's integrity check surface has grown into the hundreds of touch points that the bypass has to neutralize.

The API resolution table

Themida decrypted its full API resolution table at runtime and left it on the heap. Recovered from the core dump at file offset 0x699a0000. 434 Nt, Zw, Rtl, Ldr, Mm, Ps, Cm, Se, Ob, Io symbols plus 70 plus kernel32 exports.

Process handle acquisition

NtOpenProcess                open Roblox handle
NtOpenProcessToken           adjust privileges
NtQueryInformationProcess    metadata, integrity level

Memory allocation in target

NtAllocateVirtualMemory      allocate space in Roblox for the DLL image
NtAllocateVirtualMemoryEx    Win10+ variant
NtProtectVirtualMemory       set section permissions post write
NtFreeVirtualMemory          cleanup

Payload transfer

NtWriteVirtualMemory         write DLL headers plus sections
NtReadVirtualMemory          read for verification
NtFlushInstructionCache      force i-cache flush before exec
NtCreateSection              alt path, kernel assisted mapping
NtMapViewOfSection / Ex      map section into target

Thread manipulation for execution

NtGetContextThread           thread hijack context capture
CreateThread                 classic trigger
CreateRemoteThreadEx         remote trigger
NtDelayExecution             timing pauses between stages

Self side resolution

LdrGetProcedureAddress       manual GetProcAddress
LdrGetDllHandleEx            manual GetModuleHandle
LdrLoadDll                   last resort fallback
LdrResolveDelayLoadedAPI

The Nt heavy preference (Nt everywhere kernel32 would suffice) is the identifying signature of a bypass that expects Hyperion to hook user mode kernel32 exports. Going one layer down to the syscall boundary defeats those hooks. Hook the syscall, not the wrapper. Standard pattern in modern anti cheat bypass loaders.

Injection technique. Manual DLL mapping

The manual mapping and remapping done string family plus the Nt heavy API vocabulary confirm this is a manual DLL mapping injector. Not LoadLibrary. The distinction matters.

Manual DLL mapping steps (reconstructed from string + API evidence):

  1. Locate target process (Process32Next enumeration by name)
  2. NtOpenProcess with PROCESS_ALL_ACCESS
  3. NtAllocateVirtualMemory in target for full image size
     - preferred base if free, else relocatable base
  4. NtWriteVirtualMemory to copy DLL headers + each section raw bytes
  5. For each section: NtProtectVirtualMemory to correct permissions
     (this is what "remapping done" x16 marks)
  6. Fix relocations in the written image (base delta)
  7. Fix imports by resolving with LdrGetProcedureAddress against
     target's loaded module set, patch IAT
  8. Execute TLS callbacks if any
  9. NtCreateThreadEx or SetThreadContext + ResumeThread to call DllMain
 10. NtFlushInstructionCache to force i-cache invalidation
 11. Log "Successfully Injected!"

Because the loaded image never touches LdrLoadDll, it never appears in the target process's PEB.Ldr module list. It never appears in EnumProcessModules output. It is invisible to Hyperion's own module walk based checks. That is the whole point.

Reconstructed injection timeline

Cross referencing log format strings, ID ranges and API vocabulary produces the following ordered sequence. This is what the injector does on a successful run.

[1128]      locate Roblox process              (failed here under Wine)
[268-576]   framework init (patching %X across 111 setup sites)
[576]       .byfron section reference
[668-1086]  JUSTMAGIC WE LOVE YOU ACK markers along framework path
[1180]      check Hyperion image, identify version (67 or 69)
[1182]      abort with 'Wrong Roblox Version' if unsupported version
[1178]      'Patching integrity at %X...' phase begins
[1332-1394] apply 27 build 67 specific patches    (IF version = 67)
      OR
[1396-1906] apply 231 build 69 specific patches   (IF version = 69)
[1956-1962] manual mapping (4 stages: alloc, write headers,
                                       write sections, fix imports)
[1908-1938] 16 remapping operations
            (section permissions, alignment fixes)
[1940-1954] 8 injection completion markers
            (per thread or per callback)
[1184]      'Remapped' terminal
[1186]      'Successfully Injected!'

UI.exe. The front end

10.5 MB Tauri app. Rust backend. Microsoft Edge WebView2 for the shell. Monaco (VS Code's editor engine) with bundled Luau LSP for autocomplete.

Attach button flow

UI button "Attach to Roblox"
  -> tauri invoke  attach
  -> rust backend calls into  src\auth\commands.rs
  -> POST /injector/launch-token   (needs valid session)
  -> receives real-launch-token-v3 signed payload
  -> verify Ed25519 signature (see auth section)
  -> CreateProcessW spawn Injector.exe with launch token
     (probably passed via env var, IPC, or command line arg)

VS Code extension integration

UI.exe ships a bundled VS Code extension package. Commands registered.

Real.attach     writes IPC file  attach.txt
Real.execute    writes IPC file  execute.txt with script content
Real.openTab    writes IPC file  open_tab.txt with { name, path, content }
Real.launch     writes IPC file  launch.txt

The IPC directory is a shared temp directory whose path gets injected into the extension at load time. Bidirectional sync uses sync_to_vscode.txt and sync_from_vscode.txt. This is how a script written in VS Code gets executed inside Roblox with one click.

Auth surface. Deep dive

Where auth lives

The Injector.exe binary itself has zero auth. No cookie handling. No OAuth. No HWID computation. No Ed25519. Search of the 1.8 GB core dump for terms like account_session, launch-token, x-hwid, Bearer, real-executor returns zero hits. All auth lives in UI.exe. What follows is the complete surface recovered from that binary.

Domain topology

projectreal.live         API base host          Cloudflare fronted
    api.projectreal.live     control plane
    download.projectreal.live release CDN
    dl.projectreal.live      mirror
    cdn.projectreal.live     static assets

projectreal.gg           web / OAuth landing
    api.projectreal.gg       OAuth token exchange
    projectreal.gg           marketing + login pages

Complete endpoint table

MethodEndpointPurpose
GETapi.projectreal.live/statusService liveness
POSTapi.projectreal.live/verifyVerify per install token from installer
GETapi.projectreal.live/update/checkSigned CDN manifest
POSTapi.projectreal.live/injector/launch-tokenPer launch token before injection
POSTapi.projectreal.live/account/devices/challengeDevice attestation nonce challenge
POSTapi.projectreal.live/account/devicesRegister a device public key
GETapi.projectreal.live/account/meAuthenticated account details
POSTapi.projectreal.live/account/heartbeatSession keepalive
POSTapi.projectreal.live/account/logoutEnd session
POSTapi.projectreal.live/account/claim-app-auth-ticketClaim Roblox auth ticket for linked account
GETapi.projectreal.gg/auth/discord?mode=login&app_auth=1&state=<s>Discord OAuth start
GETprojectreal.gg/login/?loggedout=1&app_auth=1&state=<s>Post logout redirect

The login flow, step by step

Reconstructed by chaining recovered strings against the reqwest call sites in src\auth\api.rs and src\auth\commands.rs.

Step 1. User clicks Sign in with Discord in the UI.

Step 2. UI (Tauri backend) generates a random state token and opens:
        https://api.projectreal.gg/auth/discord?mode=login&app_auth=1&state=<RANDOM>

        The app_auth=1 flag tells the server this is a desktop client
        that expects a deep link callback, not a browser redirect.

Step 3. Browser opens. User authenticates against discord.com.
        Server exchanges Discord OAuth code, mints a per session ticket.

Step 4. Server redirects the browser to a URL of the form:
        projectreal://auth?ticket=<TICKET>

        The projectreal:// scheme was registered by the installer under
        HKCU\Software\Classes\projectreal\shell\open\command.
        Windows picks up the deep link, launches UI.exe with it as
        argv[1] (if not already running, otherwise sends to existing).

Step 5. UI receives the deep link (event tag 'projectreal://deep-link-received'),
        parses out <TICKET>.

Step 6. UI POSTs the ticket to the API. Server sets two cookies on the
        reqwest cookie store:
            Set-Cookie: account_session=<SESS>    HttpOnly, Secure
            Set-Cookie: account_csrf=<CSRF>       readable, session bound
        Both stored under cookie store identifier "session-cookies-v1".

Step 7. UI immediately checks for a stored device keypair in Windows
        Credential Manager under service="real-executor" account="device-key".
        If missing: run the device registration flow.
        If present: run the device challenge flow on the next auth
        gated request to prove ownership.

Step 8. UI GETs /account/me. Server returns AuthSuccess.
        Now the user is authenticated. Plan tier, balance and free trial
        status are all visible in the account object.

The standard request shape

Every authenticated request from UI.exe looks like this.

POST https://api.projectreal.live/<endpoint> HTTP/1.1
Content-Type: application/json
User-Agent: RealExecutor/1.0
X-CSRF-Token: <CSRF from account_csrf cookie>
x-hwid: HWIDv4_<hex hash>
Cookie: account_session=<SESS>; account_csrf=<CSRF>
Accept-Language: (whatever the OS default is)

{
  "appVersion": "1.1.0",
  "osVersion":  "windows 11",
  ... endpoint specific body ...
}

Notes on each header. User-Agent is a fixed string, requests from other UAs would look anomalous in access logs. X-CSRF-Token is the value of the account_csrf cookie resent as a header (double submit pattern), server rejects mismatches with CSRF_NOT_AVAILABLE. x-hwid is the HWID v4 hash, server compares against expected value for the session's account, drift causes a refuse. Cookie is standard, sent by the reqwest cookie store.

HWID v4. The full formula

The client computes an HWID v4 fingerprint from four hardware inputs. The formula was recovered from the block of concatenated strings around file offset 0x8a06a0 in UI.exe.

Recovered raw layout in UI.exe .rodata:
    "id="
    "system_volume="
    "smbios_sha256="
    "cpu_id="
    "RealExecClient-HWID-v4"
    "HWIDv4_"
    "SOFTWARE\Microsoft\Cryptography"    (registry key for MachineGuid)
    "MachineGuid"                        (value name)
    "Could not derive HWID from this system"
    "x86_64" "windows 11" "Windows 10"

Interpretation. The client builds a canonical string of the form:

id=<MachineGuid>
&system_volume=<volume serial of SystemDrive C:>
&smbios_sha256=<SHA-256 over concatenated SMBIOS strings>
&cpu_id=<CPUID vendor + brand + feature bits>

Prefixes with the domain separator "RealExecClient-HWID-v4" then hashes. Final result is prefixed with "HWIDv4_" and used as the x-hwid header value.

Individual input sources

id

Registry read HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid. Windows Product Activation identifier. Stable across reinstalls of the same Windows install. Changes on a fresh Windows install.

system_volume

Volume serial number of the drive holding %SystemDrive% (usually C:). Changes when the disk is reformatted or when a new Windows install lands on a different volume.

smbios_sha256

SHA-256 over concatenated SMBIOS strings. BIOS vendor, BIOS version, mainboard manufacturer, mainboard product, system UUID and system serial. Changes only when the physical mainboard changes or when the BIOS ROM is reflashed with a different SMBIOS payload.

cpu_id

CPUID vendor string, brand string and feature bits. Changes only when the CPU package is replaced.

The combination targets a "same physical PC" definition. Reinstalling Windows keeps three of the four inputs stable. Replacing motherboard or CPU changes it. That is the model.

Device registration flow

First time a customer signs in on a machine, the client generates a fresh Ed25519 keypair, ships the public half to the server and stores the private half in Windows Credential Manager. All names in the flow were recovered verbatim.

Step 1. UI attempts Windows Credential Manager read:
            service = "real-executor"
            account = "device-key"
        On miss (DEVICE_KEYPAIR_LOAD_FAILED), fall into registration.

Step 2. UI calls into the Rust keyring crate to generate a fresh Ed25519
        keypair. Errors here surface as DEVICE_KEYGEN.

Step 3. UI POSTs to /account/devices with a JSON body of:
            {
              "devicePublicKey":    "<base64url pubkey>",
              "keyProtectionLevel": "software"
            }
        keyProtectionLevel = "software" is telling. It means the private
        key is not held in a TPM. It is DPAPI wrapped and stored via
        Credential Manager. That is a deliberate compromise: allows the
        client to run without TPM, but means anyone with the user's
        Windows login session can extract the device key.

Step 4. Server response (verified server side, signed as always):
            { "device_id": "<stable id>", ...responseSig+Ts+Nonce... }

        Server side, the device_id is joined to the account with a fresh
        row. Server tracks: (account_id, device_id, pubkey, hwid_hash,
        first_seen_at, last_seen_at).

Step 5. UI writes the private key to Windows Credential Manager under
            service = "real-executor"
            account = "device-key"
        Errors here surface as DEVICE_STORE_ID.

Step 6. UI writes device_id into the local encrypted account vault
        so it can be referenced without another server call.

Device challenge and response

On every auth gated request, the client is expected to prove ownership of the device keypair. This runs before or alongside the actual API call.

Step 1. Client GETs /account/devices/challenge.
        Server returns:
            {
              "nonce":       "<fresh random nonce>",
              "account_id":  "<current account>",
              "hwid_hash":   "<the current expected hwid v4 hash>",
              ...responseSig+Ts+Nonce...
            }

Step 2. Client checks server responseSig with the baked in Ed25519 pubkey.
        If verify fails, abort with INTEGRITY_FAILED.

Step 3. Client checks account_id matches session, nonce is non empty,
        hwid_hash matches locally computed HWID v4. On any check fail:
            CHALLENGE_NO_ACCOUNT_ID
            CHALLENGE_NO_NONCE
            CHALLENGE_NO_HWID_HASH
            CHALLENGE_PARSE

Step 4. Client loads Ed25519 private key from Windows Credential Manager
        under (real-executor, device-key). On failure:
            DEVICE_KEYRING: <msg>
            DEVICE_KEYPAIR_LOAD_FAILED

Step 5. Client signs the challenge nonce with the private key.
        On failure: DEVICE_SIGN_FAILED.

Step 6. Signature attached to the next authenticated request as body
        fields deviceChallengeNonce and deviceSignature.

Step 7. Server verifies the signature against the account's registered
        pubkey. On success, the actual API call proceeds. On failure,
        the request is rejected with 401.

This is a proper challenge response, not a static bearer token. A leaked cookie alone will not authenticate. You need the cookie plus the device private key. On the client side, the private key sits in Windows Credential Manager under the user account, unencrypted at the OS level (DPAPI wraps it but any process running as that user can decrypt). So the model is: "same Windows user session as the enrolling one" is treated as equivalent to the device.

The launch token flow

When the user clicks Attach in the UI, the flow is:

Step 1. UI runs a device challenge if the last one is stale.

Step 2. POST /injector/launch-token with:
            body: { appVersion, osVersion, deviceChallengeNonce,
                    deviceSignature, ... }
            headers: cookie, csrf, hwid, standard shape.

Step 3. Server side account state check:
            NO_SESSION            no valid session cookie
            SUSPENDED             account is suspended
            NO_BALANCE            no active plan / no free tier remaining
            MUST_CHANGE_USERNAME  forced credential change
            MUST_CHANGE_PASSWORD  forced credential change
            ROBLOX_NOT_FOUND      no attached Roblox account claim
        Any of these returns a JSON error. Otherwise proceeds.

Step 4. Server mints a launch token. Payload scheme identifier:
            real-launch-token-v3

        The "v3" suffix implies at least two prior revisions. Server
        signs it with the Ed25519 key documented below.

Step 5. Response:
            { "launch_token": "<opaque>",
              "expires_at":   <unix ts>,
              ...responseSig+Ts+Nonce... }

Step 6. UI CreateProcessW spawns Injector.exe. Passes launch_token
        forward (mechanism most likely a command line arg or env var,
        possibly an inherited handle for a named pipe). Injector consumes
        it and does not persist it.

Response signing. The baked in Ed25519 pubkey

Every response from api.projectreal.live is signed by the server. The client verifies with a pubkey compiled into UI.exe. Recovered from file offset 0x8a08e0.

Recovered literal in UI.exe .rodata:
    responseSig
    responseAlg
    responseTs
    Ed25519
    responseNonce
    real-response-v1

    NzfoKTM_wDqKgse8D0-OWUt6Rmxt4vKj-t6rOTW0dm4

    integrity_failed
    CLOCK_SKEW
    SERVER_PROTOCOL_ERROR
    Invalid server response

The server pubkey is NzfoKTM_wDqKgse8D0-OWUt6Rmxt4vKj-t6rOTW0dm4. base64url, 43 characters, decodes to exactly 32 bytes. Wire format of an Ed25519 public key.

Server verification pubkey
base64url: NzfoKTM_wDqKgse8D0-OWUt6Rmxt4vKj-t6rOTW0dm4
hex: 3737e829333fc03a8a82c7bc0f4f8e594b7a466c6de2f2a3fadeab3935b4766e
sha256(pk): ae5f80e44b8a29361ee38991d35a1a6a8c010c9e9424bcd581b2bf1c737ce635

This is the same key used to verify the update manifest signatures. Same key used to verify launch tokens. Same key used to verify every API response envelope. It is Project Real's signing authority. Any signed blob out in the wild that verifies against this key is provably from their infrastructure.

Response wrapper fields

Field           Type            Notes
responseSig     base64url       Ed25519 signature over (Ts, Nonce, Body)
responseAlg     string          Always "Ed25519" in this build
responseTs      unix seconds    Rejected if drift > CLOCK_SKEW threshold
                                (typical is 300s)
responseNonce   base64url       Per response nonce. Server side anti replay.
                                Also carried as HTTP header X-Response-Nonce.

Signature scheme identifier real-response-v1 is the domain separator. It tells the signature verifier which fields to include. In which order. What canonical encoding.

Local credential storage

Three separate stores on the client machine. Together they hold everything an attacker would want to lift a session.

a. Windows Credential Manager (device keypair)

Service : real-executor
Account : device-key
Value   : Ed25519 private key (raw 32 or 64 byte)

Storage backend: keyring 3.6.3 (Rust crate) bindings to Windows Credential
Manager. DPAPI wrapped by the OS. Any process running as the current user
can read it via CredRead + CryptUnprotectData.

b. account-vault.dpapi (persistent session and accounts)

Filename    : account-vault.dpapi
Location    : somewhere under %APPDATA%\Real\ (path structure recovered
              but not exact directory)
Vault key   : label "Real-UI account vault key v1R"
              key material derived via DPAPI unwrap of a stored blob
Prefix      : "dev:v1:C" on every wrapped value
Encryption  : AES-GCM most likely (given Rust ecosystem and keyring context)

Contents (inferred from Account struct):
    account_session cookie value
    account_csrf cookie value
    All Roblox account entries (.ROBLOSECURITY per account)
    Cookie rotation timestamps
    Local device_id
    Recent games list (from Roblox API)

c. Discord account state

discord_connected
discord_username
discord_avatar_url

Held in memory during the session, persisted to the vault when written to disk.

Vault key error taxonomy

"Wrong password or data corrupted"
"Account vault is locked or belongs to a different Windows profile"
"Migrated account vault key has an invalid length"
"Legacy account vault key was invalid and has been removed"
"Incorrect answers to security questions"
"Incorrect current password"

The security question fallback means there is an escrow path. If a user forgets their vault password and cannot use DPAPI (say they moved the file to another machine), they can answer their security questions to recover. That means the server holds enough state to validate those answers. It also means the vault contents can be reopened on a different machine with those answers plus a server call.

Roblox account claim flow

Real can attach multiple Roblox accounts. Each attach uses the customer's own .ROBLOSECURITY cookie plus a Roblox authentication ticket flow.

Step 1. User pastes .ROBLOSECURITY into the UI's Add Account dialog.
        Stored in the encrypted vault above.

Step 2. UI performs GET https://auth.roblox.com/v1/authentication-ticket/<>
        with the cookie attached.
        Roblox returns a ticket via response header rbx-authentication-ticket.

        On failure, one of these surfaces:
            "Could not obtain security token from Roblox.
             Is your account cookie still valid?"
            "Failed to get auth-ticket (HTTP %d). Cookie may have expired,
             please re-add the account."
            "Auth-ticket missing from response headers"

Step 3. UI POSTs to /account/claim-app-auth-ticket with:
            { "ticket": "<from roblox header>",
              "target_username": "<the roblox account name>", ... }

Step 4. Real's server holds the mapping (account_id -> roblox_account_id).
        Real's server can now issue Roblox launcher URLs on the customer's
        behalf, enabling one click launch of arbitrary Roblox accounts.

Step 5. AuthSuccess now includes the account under an entries list
        (Account struct fields: username, user_id, display_name,
         avatar_url, added_at, alias, sort_index, cookie_rotated_at).

Complete account object

The AuthSuccess response from /account/me returns this shape. Every field recovered as a JSON key string in UI.exe.

FieldType / meaning
account_idopaque server side id
emailuser email
hwidHashserver side hwid v4 hash for the current session
discord_connectedbool
discord_usernamestring
discord_avatar_urlcdn.discordapp.com/avatars/...
planstring label
plan_tierfree | basic | premium | ...
plan_activebool
plan_expires_atunix seconds
effective_tierstring
effective_tier_labelstring
balance_activebool
balance_pausedbool
free_phase_ends_atunix seconds
free_trial_expires_atunix seconds
booster_activebool
linked_devicescount
max_devicesdevice cap for the account

Complete client side error taxonomy

CodeMeaning
LAUNCH_TOKEN_HTTP_<code>server returned non 2xx
LAUNCH_TOKEN_NETnetwork layer failed
LAUNCH_TOKEN_PARSEresponse body malformed
LAUNCH_TOKEN_INVALIDsignature verify failed
CSRF_NOT_AVAILABLEno csrf token in session
CHALLENGE_HTTP_<code>challenge request failed
CHALLENGE_NETnetwork layer failed
CHALLENGE_PARSEresponse malformed
CHALLENGE_NO_ACCOUNT_IDserver did not include account_id
CHALLENGE_NO_NONCEserver did not include nonce
CHALLENGE_NO_HWID_HASHserver did not include hwid_hash
DEVICE_SIGN_FAILEDed25519 signing failed
DEVICE_REG_HTTP_<code>device registration HTTP fail
DEVICE_REG_NETdevice registration net fail
DEVICE_REG_PARSEdevice registration parse fail
DEVICE_REG_NO_IDserver did not return device_id
DEVICE_STORE_IDCredential Manager write failed
DEVICE_KEYGENed25519 keypair generation failed
DEVICE_KEYRINGkeyring read failure
DEVICE_KEYPAIR_LOAD_FAILEDdevice keypair not present
NO_ACCOUNT_SESSIONno session cookie
NO_SESSIONserver rejected session
SUSPENDEDaccount suspended
NO_BALANCEno active plan / no free tier remaining
ROBLOX_NOT_FOUNDno attached Roblox account claim
MUST_CHANGE_USERNAMEforced credential change
MUST_CHANGE_PASSWORDforced credential change
INTEGRITY_FAILEDsignature did not verify against pubkey
CLOCK_SKEWresponseTs was too far from local clock
SERVER_PROTOCOL_ERRORwrapper shape mismatch
COOKIE_STORE_UNAVAILABLEreqwest cookie store not available

GitHub Gist integration (side flow)

UI.exe carries a full GitHub Device Auth OAuth flow. Recovered strings:

https://github.com/login/device/code           start
https://github.com/login/oauth/access_token    poll for token
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
User-Agent: Real-UI GitHub Gists
scope: gist read:user

Errors:
    authorization_pending
    slow_down
    expired_token
    GITHUB_DEVICE_AUTH_FAILED
    GITHUB_DEVICE_CODE_EXPIRED
    GITHUB_RATE_LIMITED
    GIST_NOT_FOUND
    GITHUB_FORBIDDEN_OR_RATE_LIMITED
    GITHUB_BAD_CREDENTIALS
    GITHUB_VALIDATION_FAILED
    GITHUB_NO_ACCESS_TOKEN

Content hosts:
    raw.githubusercontent.com
    gist.githubusercontent.com

Script sharing flow. Users can save scripts to Gists and load scripts from Gists. Separate from the Project Real auth surface. Uses the GitHub Device Auth flow so the client never sees a client secret.

Injector auth boundary

Worth stating explicitly. The Injector.exe binary does not carry any of the auth surface above. The 1.8 GB core dump from the live Wine run contains zero hits for:

account_session   0 hits
launch-token      0 hits
x-hwid            0 hits
Bearer            0 hits
Ed25519           0 hits (as a string; the actual crypto lives in UI.exe)
real-executor     0 hits

The injector receives only the launch token from the UI, presumably via command line arg, env var or inherited handle. If it fails to consume it, no auth. If the token is invalid or expired, the launch is rejected at the injector's own gate before it does anything to Roblox's process.

This is a clean design boundary. An isolated injector fails safe. It cannot forge tokens without the UI's session state. Even a stolen injector binary alone cannot inject.

Extra API parameters recovered from a second packed field blob

There is a second concatenated field name run in UI.exe at file offset 0x89c008. Splitting it produces the parameter names for endpoints that are not surfaced by URL constants directly. Together they imply half a dozen more routes we did not enumerate.

Recovered raw run:
userIdsaccountIdlistTypelimituserIdmultiRobloxmaxAgeDaysoldFilename
newFilenameisPubliccursoranswer1answer2newPasswordquerysortFilter
sessionIdknownUpdatedAtoverwritecurrentPassword

Grouped by likely endpoint:

Multi Roblox account listing / management:
    userIds     accountId     userId     multiRoblox     listType     limit

File / script sharing:
    oldFilename     newFilename     isPublic     overwrite

Pagination / query:
    cursor     limit     query     sortFilter     maxAgeDays     knownUpdatedAt

Security question recovery for the vault:
    answer1     answer2     newPassword     currentPassword

Session management:
    sessionId     overwrite

Also recovered nearby: a second batch of auth error codes not in the earlier taxonomy. These sit in src\auth\device.rs alongside src\auth\api.rs and src\auth\commands.rs.

OAuth callback errors:
    STATE_MISMATCH             Discord OAuth state token mismatch (CSRF against browser)
    INVALID_DEEP_LINK          projectreal:// URL malformed
    INVALID_URL                url parse failed

Device key errors:
    DEVICE_KEY_LOAD_FAILED     could not read the raw bytes from Credential Manager
    DEVICE_KEY_DECODE_FAILED   raw bytes did not decode as Ed25519 pubkey
    DEVICE_KEY_NOT_FOUND       no entry present
    DEVICE_NOT_REGISTERED      server responded that this device_id is not on the account
    DEVICE_KEYGEN_FAILED       Ed25519 keypair generation failed
    DEVICE_STORE_KEY           private key write to Credential Manager failed

Also new: device-key-v1 as the Credential Manager schema version tag. Suggests a v0 existed at some point and a v2 is planned.

Live infrastructure recon

Everything you can see from outside. All API hosts are Cloudflare fronted so there is no raw origin IP visible without CT log historical trawling or a leaked backend cert. That is a materially better opsec posture than the FiveM cheat ecosystem, where sellers like Anxiety and ClubXJefe hardcode bare IP:port pairs into their loaders. Real does not.

DNS resolution as observed

Zone: projectreal.live (Cloudflare)
    api.projectreal.live       104.26.10.42, 104.26.11.42, 172.67.68.127
    download.projectreal.live  same set (shared Cloudflare edge)
    dl.projectreal.live        same set
    projectreal.live           same set
    cdn.projectreal.live       NXDOMAIN (referenced in binary, not deployed)
    IPv6                       2606:4700:20::681a:a2a, ::681a:b2a, ::ac43:447f

Zone: projectreal.gg (different Cloudflare tenant)
    api.projectreal.gg         104.21.16.24, 172.67.165.250
    status.projectreal.gg      104.21.16.24
    IPv6                       2606:4700:3037::6815:1018, ::ac43:a5fa

projectreal.gg (marketing site, NOT Cloudflare)
    A records                  185.199.108.153, 185.199.109.153,
                               185.199.110.153, 185.199.111.153
    PTR                        cdn-185-199-108-153.github.com etc
    Verdict                    hosted on GitHub Pages

projectreal.net (Cloudflare, purchase redirect)
    A records                  172.67.209.109, 104.21.37.145
    Behavior                   /pricing/ 301 redirects to projectreal.gg/pricing/

Two Cloudflare tenants (one per zone). Marketing on GitHub Pages. Purchase host acting as a URL shortener. No exposed origin IP anywhere in this map.

Origin egress port

Everything terminates at Cloudflare on standard 443/tcp (HTTPS) and 443/udp (HTTP/3). No non-standard ports observed. For contrast: the ClubXJefe loader we documented in a prior post ships a hardcoded literal 185.242.3.132:41327. Real Executor has no such wire IOC. Any network detection has to be by SNI or by TLS fingerprint.

CSP declared dependency graph

The marketing site (projectreal.gg) ships its own Content Security Policy as a meta tag (since GitHub Pages does not serve custom headers). This is a verbatim disclosure of every backend the browser side is allowed to talk to:

Content-Security-Policy:
    default-src 'self';
    script-src  'self' 'unsafe-inline' https://challenges.cloudflare.com;
    connect-src 'self'
                https://api.projectreal.gg
                https://status.projectreal.gg
                https://discord.com
                https://api.rubis.app;
    img-src     'self' data: https:;
    style-src   'self' 'unsafe-inline' https://fonts.googleapis.com;
    font-src    'self' https://fonts.gstatic.com;
    frame-src   https://challenges.cloudflare.com;
    object-src  'none';
    base-uri    'self';

Fresh finding from CSP: the marketing site directly talks to api.rubis.app and uses Cloudflare Turnstile (challenges.cloudflare.com) as its bot filter. See the next two sections for what happens on those hosts.

Live /status endpoint response

Both api.projectreal.live/status and api.projectreal.gg/status return the same JSON to any unauthenticated caller. No cookies. No API key. Just a valid User Agent. We captured this against the live host at time of publication.

GET https://api.projectreal.live/status HTTP/2
200 OK  content-type: application/json; charset=utf-8

{
  "title":         "Real",
  "version":       "1.1.0",
  "updatedDate":   "07/11/2026 at 2:46 PM UTC",
  "free":          true,
  "detected":      false,
  "rbxversion":    "version-36a2600cebf1487d",
  "updateStatus":  true,
  "websitelink":   "https://projectreal.gg/",
  "discordlink":   "https://discord.gg/projectreal/",
  "purchaselink":  "https://projectreal.net/pricing/",
  "beta":          true,
  "platform":      "Windows",
  "extype":        "wexecutor",
  "cost":          "Key System",
  "reseller":      true,
  "decompiler":    true,
  "multiInject":   true,
  "myriadPercentage": 85,
  "suncPercentage":  100,
  "uncPercentage":    99,
  "sunc": {
    "suncScrap": "k597N6wevNbuwVdT",
    "suncKey":   "gHwa20vz7NZF18BMHf82et0shmtSxmky"
  },
  "update": {
    "version":  "1.1.0",
    "pub_date": "2026-07-11T14:46:22.000Z",
    "changelog":
      "* Luau 0.728 -> 0.729\n"
      "* Fixed hookmetamethod not working\n"
      "* Added Real-Decompiler (no more LE)\n"
      "* Fixed a vulnerability regarding opening log files (FileManagerService)"
  }
}

What every field means

FieldMeaning
rbxversionThe Roblox client version the current build is tuned for. Client polls this to warn when Roblox pushed an update ahead of Real.
detectedServer side kill switch. Set to true to disable execution across every customer at once.
freeFree tier is currently enabled. Used by the front page to show or hide the free key ad flow.
betaBuild is a beta channel release.
extype'wexecutor' identifies as a Windows executor. Rubis and similar aggregators consume this.
cost'Key System'. Real Executor uses a per key model rather than a subscription.
resellerReseller program is active. Third parties can sell keys.
decompilerReal Decompiler feature is available. Note: paired with the changelog line 'no more LE', meaning they moved off Lua Expert.
multiInjectMulti Roblox process injection is supported (attach to more than one Roblox at a time).
myriadPercentageCompatibility score against the Myriad executor test bench. Range 0-100.
suncPercentageCompatibility score against the sUNC test bench. 100 means all sUNC tests pass.
uncPercentageCompatibility score against the classical UNC test. 99 means one test fails.
sunc.suncScrapRubis text sharing scrap id. Public. Used as URL parameter.
sunc.suncKeyRubis access key for the same scrap. Public. Anyone can fetch the raw test result.
Changelog reveals a live disclosure response

Real's 1.1.0 changelog contains the literal line "Added Real-Decompiler (no more LE)". LE is Lua Expert. That is the third party api.lua.expert/decompile service that Real's decompile() was silently shipping customer bytecode to every call. This entry in the changelog is Real acknowledging that leak and switching to a local decompiler on the same day (2026-07-11 14:46:22 UTC). If you are one of the customers who used decompile() on a build before 1.1.0, your target scripts were on api.lua.expert.

The api.rubis.app dependency

api.rubis.app (numelon rubiš) is a third party paste bin and text sharing service. Search of UI.exe strings around src\sunc.rs shows Real uses two URL templates against it.

https://api.rubis.app/v2/scrap/{scrap}/raw?accessKey={key}     (raw JSON API)
https://sunc.rubis.app/?scrap={scrap}&key={key}                (HTML viewer)

Substituting the suncScrap and suncKey we recovered from /status returns Real's sUNC compatibility test result live.

GET https://api.rubis.app/v2/scrap/k597N6wevNbuwVdT/raw?accessKey=gHwa20vz7NZF18BMHf82et0shmtSxmky
200 OK

{
  "timestamp": 1779207595.061,
  "version":   "2.1.5",
  "bibip":     false,
  "timeTaken": 18.58,
  "tests": {
    "passed": [
      "getrawmetatable", "makefolder", "getscriptbytecode", "setthreadidentity",
      "delfile", "request", "Drawing.Fonts", "isscriptable", "iscclosure",
      "debug.setconstant", "debug.getprotos", "lz4compress", "getscripts",
      "isfolder", "sethiddenproperty", "getthreadidentity", "readfile",
      "getscriptclosure", "delfolder", "setscriptable", "Drawing.new",
      "debug.getupvalues", "hookmetamethod", "debug.getproto", "getrunningscripts",
      "checkcaller", "debug.setupvalue", "setrawmetatable", "gethiddenproperty",
      "writefile", "setrenderproperty", "getnamecallmethod", "isfile",
      "fireclickdetector", "getnilinstances", "getcustomasset", "getconnections",
      "islclosure", "restorefunction", "loadstring", "cache.iscached",
      "cache.invalidate", "cloneref", "cache.replace", "getgc", "compareinstances",
      "base64_encode", "getrenv", "hookfunction", "debug.getupvalue", "setreadonly",
      "getloadedmodules", "debug.getinfo", "getscriptfromthread", "fireproximityprompt",
      "WebSocket.connect", "listfiles", "gethui", "isreadonly", "getrenderproperty",
      "lz4decompress", "appendfile", "loadfile", "getinstances", "isexecutorclosure",
      "getcallbackvalue", "getfunctionhash", "replicatesignal", "cleardrawcache",
      "decompile", "filtergc", "identifyexecutor", "getscripthash", "firesignal",
      "firetouchinterest", "debug.setstack", "isrenderobj", "getcallingscript",
      "debug.getstack", "getsenv", "clonefunction", "debug.getconstant", "getgenv",
      "newcclosure", "base64_decode", "debug.getconstants"
    ],
    "failed": []
  },
  "__SUNC":    true,
  "executor":  "Real/V0.2.6"
}

85 functions passed. Zero failed. The executor: "Real/V0.2.6" field is not the current 1.1.0 build. It is the version the sUNC run was executed on. Real does not rotate the scrap for every release. The bibip flag is a rubiš internal marker (paste protection bit). The whole result is public to anyone with the scrap plus key. The client fetches it once and displays the result inside the UI.

About the "compatibility percentage" fields in /status

/status reports suncPercentage: 100. The live sUNC result confirms it. 85 passed, 0 failed. That is the pitch Real makes to prospective customers who care about script compatibility.

Public pricing tiers

Recovered from the live pricing page at projectreal.gg/pricing/. Four tiers. Free is genuinely free and gated on watching ads for daily keys. The paid tiers unlock quotas and features.

Tier hierarchy (planTier value shown in the client):
    free        Free                        (default)
    booster     Booster                     (Server Booster on Discord)
    boost       Boost                       ($2.99 tier observed)
    plus        Plus            best value  ($9.99 tier observed)

Feature matrix (from pricing page cells):

                            Free   Booster   Boost   Plus
    Start balance           +30d
    Free keys / 24h          1        2        4      5 (or 10)
    No ad-steps needed       -        -        yes     yes
    Ad-free download         -        -        yes     yes
    Device reset cooldown    long   medium   short    fastest
    Spoofer cooldown         long   medium   short    fastest
    Alt generator            -    Standard  Priority  High priority
    Aged accounts (+30d)     -        -       yes      yes
    Full executor access     yes    yes      yes      yes
    Discord role             -    Server B  Boost    Plus
    Image permissions        -        -       yes      yes
    Support                  std     std     Priority High priority
    Downtime compensation    -        -       yes      yes

The Booster tier is unlocked by boosting the Discord server rather than a paid transaction. So the actual paid tiers are Boost and Plus. The Free tier gates on ads through work.ink (recovered as a ?wt= return token handler in the front page HTML).

Discord and free key generation

Discord invite          discord.gg/projectreal
Free key ad handler     work.ink AdWall handler (wt return token snapshotted before
                        history.replaceState strips it, per the recovered inline script)
Bot filter              Cloudflare Turnstile (challenges.cloudflare.com)

Installer artifacts

On disk layout after install

%LOCALAPPDATA%\Real\
  Real.exe                            = UI.exe (Tauri app, 10.5 MB)
  Real.dll                            = encrypted executor DLL
  bin\
    Injector                          = Injector.exe (Hyperion bypass, 4.12 MB)
    Update.exe                        = persistent updater (RealLauncher from RCDATA #101)
    luau-lsp.exe                      = JohnnyMorganz/luau-lsp for Monaco autocomplete
    EBWebView\x64\
      EmbeddedBrowserWebView.dll      = Edge WebView2 runtime host

Persistence

Registry autorun:
    HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
    Value: "Real Startup" -> path to Real.exe

Scheduled task:
    Real\Launch     at HIGHEST integrity, /ru "" /it
    Real/Delete/TN Real Startup

Custom URL scheme:
    HKCU\Software\Classes\projectreal\shell\open\command
    Handler: UI.exe with %1

Anti defense manipulation

Defender exclusion (from elevated PowerShell):
    Add-MpPreference -ExclusionPath "<install path>"
    (Tamper Protection may block this; installer logs
     "Could not update Defender exclusions".)

DoH (DNS over HTTPS) baked into the installer's DNS layer:
    cloudflare-dns.com
    dns.google
    dns.quad9.net

"Repair" mode (elevated):
    Renames C:\Windows\System32\catroot2 to catroot2.REAL.old
    Edits %SystemRoot%\System32\drivers\etc\hosts
    netsh winsock reset
    Sets DNS on every up network adapter to 1.1.1.1 / 1.0.0.1
        via Get-NetAdapter | Set-DnsClientServerAddress
    "Could not resolve Real directories" is a soft failure

Token stealing helpers (recovered strings):
    DuplicateTokenEx
    OpenProcessToken
    CreateProcessWithTokenW
    explorer.exe target for token elevation

Cryptographic stack

PrimitivePresentActually used for
Ed25519Yes (WinCNG + OpenSSL)Server response signing, launch token signing, manifest signing. Client device keypair for attestation.
SHA-256Yes (BCrypt + OpenSSL)Manifest integrity. HWID v4 SMBIOS hashing. Response signature payload hashing.
XOR (8 byte repeat)Yes (custom)Real.dll on disk encryption. Key 2A 27 94 7A E9 EA DA 78.
DPAPIYes (Windows crypt32)Wrapping account-vault key. Wrapping device private key in Credential Manager.
AESYes (BCrypt + OpenSSL)TLS. Exposed to Luau via crypt.encrypt / crypt.decrypt.
AES-GCMPresent in depsLocal vault (dev:v1:C prefix consistent with GCM).
ML-DSA 44 / 65 / 87Linked in OpenSSL 3Not observed in wire format. Future proofing.
SLH-DSA (SPHINCS+)LinkedNot observed. Future proofing.
X25519MLKEM768LinkedHybrid PQ + classical TLS group. Available if CDN offers it.
Trufo C2PA chainBundled root + intermediateC2PA / JUMBF verification. Not live.
Authenticode (WinTrust)YesChecked on incoming Update.exe before atomic swap.

Tooling

The stack that makes sense for a job like this. Reverser oriented, Windows leaning, packer aware.

Disassembly and decompilation

IDA Pro 9.0                Hex-Rays x64 decompiler for the outer wrapper
Binary Ninja 4.x           cross reference sanity on the aPLib stub and VM shape
Ghidra 11.1                second pass decompilation on Real.dll's Luau VM
Cutter (Rizin backend)     RTTI extraction on the RealVM namespace
retdec                     hand off findings to peers

Packer identification and unpacking

Detect It Easy (DIE) 3.10  first pass packer triangulation
PE-bear                    section characteristics review
CAPA (Mandiant)            behavioral rule matching on the unpacked code
MagicMida / Themidie       reference behavior for WinLicense stubs
TitanHide                  Themida VM entry survey
ScyllaHide                 anti anti debug layer for the Windows side runs

Windows debugging

x64dbg (nightly)           primary Windows debugger with ScyllaHide + xAnalyzer
WinDbg Preview             kernel side introspection and Time Travel Debugging
API Monitor v2 (rohitab)   WinHTTP + BCrypt + NCrypt call auditing on UI.exe
Process Hacker 2           live process view with KProcessHacker driver
System Informer            same, newer fork
PE-sieve                   detect reflectively loaded DLLs inside targets
HollowsHunter              detect process hollowing artifacts
Moneta                     memory anomaly scanning against the injected image

Dynamic instrumentation and emulation

Frida 16.x                 hooked WinHTTP and curl_easy_setopt on UI.exe
Unicorn Engine             x86-64 CPU emulation for isolated function replay
Qiling Framework           Windows userland emulation of the outer stub
Speakeasy (Mandiant)       packed sample emulation as a sanity check
Dumpulator                 minidump driven replay for the VM handler traces

Network capture and interception

Wireshark                  ambient packet capture during runs
mitmproxy                  TLS interception of UI to backend traffic
Burp Suite Professional    request replay and body fuzzing
Fiddler Classic + CertMaker Windows side TLS decryption reference
openssl s_client           cert chain confirmation on the Cloudflare fronts

Cryptography

CyberChef                  base64url and Ed25519 signature verify
Python cryptography 43.x   Ed25519 verify against the manifest signatures
YARA                       signature drafts (see IOC bundle)

Environment

QEMU + KVM (Ubuntu host)   Windows 11 guest for the injector's real target
VMware Workstation Pro     second isolated environment for sandbox runs
Windows Sandbox            short lived analysis snapshots
Sysinternals suite         Process Explorer, Autoruns, Handle, VMMap, ProcMon

Miscellaneous

010 Editor with PE template     hex level surgery
CFF Explorer                    quick PE structure edits
ImHex                           byte pattern visualisation
7-zip + innounp                 installer archive extraction

IOC bundle

File hashes

Injector.exeSHA-256949ed91c5395e407b3b29c7842bca3c50b61c0df62b9a891211f7297c79f148c
Real.dll (enc)SHA-2562756337a38a4525aa9b90b5dd2a843526d79f5b345258f56e8b093b51122b167
UI.exeSHA-25611ceebe9c8af83e2aea61c28da7d7e0902daa9eda124f7a946c85b958152bbab
Real.dll XOR key8 bytes2A 27 94 7A E9 EA DA 78

Cryptographic identity

Server Ed25519 pubkey (base64url)NzfoKTM_wDqKgse8D0-OWUt6Rmxt4vKj-t6rOTW0dm4
Server Ed25519 pubkey (hex)3737e829333fc03a8a82c7bc0f4f8e594b7a466c6de2f2a3fadeab3935b4766e
SHA-256 of pubkeyae5f80e44b8a29361ee38991d35a1a6a8c010c9e9424bcd581b2bf1c737ce635
Signature scheme identifierreal-response-v1
Launch token scheme identifierreal-launch-token-v3
HWID scheme labelRealExecClient-HWID-v4 (prefix HWIDv4_)

HTTP fingerprint

User-AgentRealExecutor/1.0
Session cookie namesaccount_session, account_csrf
CSRF headerX-CSRF-Token
HWID headerx-hwid
Response nonce headerX-Response-Nonce
Cookie store schema idsession-cookies-v1

Host artifacts

Install directory%LOCALAPPDATA%\Real\
Persistent updater%LOCALAPPDATA%\Real\bin\Update.exe
Registry autorunHKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\Real Startup
Scheduled taskReal\Launch (HIGHEST integrity)
URL scheme handlerHKCU\Software\Classes\projectreal
Local vault fileaccount-vault.dpapi
Vault key labelReal-UI account vault key v1R
Credential Manager entryservice=real-executor account=device-key
Injector log filelog.txt in cwd

Behavioral indicators (runtime)

Injector process comm             Injector.exe
Distinctive log lines             "Roblox not found"
                                  "Wrong Roblox Version"
                                  "Patching integrity at %X..."
                                  "Successfully Injected!"
                                  "Remapped"
                                  "manual mapping"
                                  "remapping done"
                                  "injected"
Distinctive string in memory      "WE LOVE HYPERION 69" x 256 sites
                                  "WE LOVE HYPERION 67" x 27 sites
                                  "JUSTMAGIC WE LOVE YOU" x 256 sites
Injection technique               manual DLL mapping (invisible to
                                  PEB.Ldr module walks)
API resolution                    dynamic via PEB walk + name hashing
                                  Nt* syscalls preferred over kernel32 wrappers

YARA landmark rule

rule Real_Injector_v118 {
    strings:
        $aplib_ep  = { E8 82 01 00 00 41 52 49 89 E2 41 52 49 8B 72 10 49 8B 7A 20 FC B2 80 }
        $seg_drv   = { B9 F0 01 00 00 83 E9 05 48 01 C1 53 48 89 CE 48 89 DF 8A 0E 48 FF C6 }
        $wl1       = "WinLicenseInstance" ascii
        $wl2       = "WinLicenseDriverVersion" ascii
        $wl3       = "/checkprotection" ascii
        $wl4       = "!ProcOUT" ascii
        $hy1       = "WE LOVE HYPERION 69" ascii
        $hy2       = "JUSTMAGIC WE LOVE YOU" ascii
        $section   = ".byfron" ascii
    condition:
        uint16(0) == 0x5A4D and $aplib_ep and $seg_drv
        and 3 of ($wl*) and all of ($hy*) and $section
}

Reporting

Coordinated via HackerOne #3809852 for the Roblox integration side. Reported to Roblox security. Not disclosed to Project Real prior to publication because Real is the vendor of the abuse infrastructure. The Ed25519 pubkey and endpoint set enables Roblox security and any EDR vendor to build robust detections against this family without needing the private client sample.

For third party downstream reporters. The api.lua.expert relationship should be surfaced to that service's operators. Any customer of Real who has ever called decompile() has shipped their target script's bytecode to that endpoint without a per session consent screen.

For Cloudflare abuse reporters. api.projectreal.live and download.projectreal.live are Cloudflare fronted. Report references should include the manifest sample and the Ed25519 pubkey. Do not include the per install token in reports, it is a per user credential.

Correspondence: research@clubhouseac.shop