Defensive use only. Detection methodologies published for server administrators, DFIR practitioners, and anti-cheat researchers. No evasion guidance is provided.
File Identity
The sample is a 64-bit Windows PE executable, 4.90 MB on disk, distributed under the filename notepad.exe to blend in with the legitimate Windows text editor. Despite the benign name, the binary has no relationship to Microsoft Notepad — it is a packed bypass loader with the entire payload encrypted inside a single custom section.
340753116751ef6f5212667501a0e562ad4d25b43964c1c54accdcbe97a3f2ca80d15894b61907b9081bb5d7125264c5e60de013c02b7b866148248de603fb55f8d39a18- Architecture: PE32+ (x86-64), subsystem 3 (Console) — despite the
notepad.exename, no GUI window is created by the stub; the real payload may spawn one - Compile timestamp:
0x6514b842→ 2023-09-27 23:18:26 UTC — plausible; not zeroed or far-future, suggesting the developer did not manipulate it - Checksum:
0x00000000(zeroed — standard for packed/modified PE files) - SizeOfImage:
0x0089F000(9,109,504 bytes / ~8.7 MB) — the virtual footprint after all sections are mapped, significantly larger than the 4.9 MB on-disk file due to the six virtual sections that contain no file data but occupy address space; this value appears in Windows AppCompatCache and PcaSVC records alongside the file path and is a useful secondary indicator - DllCharacteristics:
0x8160— DYNAMIC_BASE (ASLR), HIGH_ENTROPY_VA, NX_COMPAT (DEP), TERMINAL_SERVER_AWARE all set - Manifest:
requireAdministrator— UAC elevation is demanded on launch - Debug directory: absent — no PDB path, no CodeView record. The developer stripped the debug directory before distribution, unlike the ApateonDecoy/Wizard sample which leaked a full PDB path
- Rich header: not present — the DOS stub area contains no
Richmarker, either stripped or never generated (uncommon for MSVC builds; may indicate use of a third-party linker or post-link PE manipulation)
PE Section Analysis — Six Hollow Sections, Single Encrypted .tiko Payload
Nine sections are declared in the section table. Six of them — .gala, .xys23, .prom, .ax512, _gbit_, and .2024 — have a raw size of zero and contain no file data. Their virtual sizes and section headers exist solely to inflate the apparent complexity of the PE for superficial scanners. All executable code, decryption routines, and the encrypted real payload are consolidated into the single .tiko section.
| Section | Virtual size | Raw size | Entropy | Notes |
|---|---|---|---|---|
| .gala | 186 KB | 0 bytes | — | Hollowed CODE section — header only, no file data |
| .xys23 | 101 KB | 0 bytes | — | Hollowed DATA section — header only |
| .prom | 889 KB | 0 bytes | — | Hollowed RW DATA — largest virtual ghost section |
| .ax512 | 10.7 KB | 0 bytes | — | Hollowed DATA section |
| _gbit_ | 348 B | 0 bytes | — | Hollowed DATA section — underscore name unusual |
| .2024 | 2.53 MB | 0 bytes | — | Hollowed CODE section — year-based name, no content |
| .tiko | 4.99 MB | 4.90 MB | 7.88 | Entire encrypted payload + real IAT + stub code; EP here |
| .limco | 224 B | 512 B | 2.10 | Base relocation / IAT pointer array — near-zero entropy |
| .dino | 480 B | 512 B | 4.77 | Resource section — requireAdministrator manifest |
The .tiko section records a Shannon entropy of 7.88 out of a theoretical maximum of 8.0. This is near-perfect randomness, consistent with AES, ChaCha20, or a custom symmetric cipher applied to the entire payload before packing. Static string scanning of this region produces no readable plaintext or API names, ruling out simple XOR obfuscation applied to a plaintext binary.
The binary's entry point (RVA 0x3C4A87, VA 0x1403C4A87) lands inside .tiko at offset 0xEA87 (59 KB into the section, 1.2% of its size). The first executed instruction is PUSH 0x19BAB67 — a seed constant passed to the root bootstrap function at 0x140853D23. This is structurally different from the Wizard variant, which embeds its bootstrap at 47.3% into its .zyw section via a CALL-slalom chain. Section names .tiko, .limco, .dino, .gala, .xys23, .prom, .ax512, _gbit_, and .2024 are all non-standard and constitute reliable static indicators — no legitimate software ships with this combination of section names.
Structurally this binary belongs to the same packer family as ApateonDecoy / Wizard bypass — both use a single high-entropy payload section, six or more hollowed virtual sections, identical dynamic API resolution logic via PE export table walking, and the same ROL-XOR decryption constant. The section names differ between samples, indicating the packer re-randomises names per build.
Entry Point & Bootstrap Sequence
Ghidra headless analysis identifies 16 functions in the visible (non-encrypted) portion of the binary. The remaining code is unreachable until the runtime decryptor unpacks the payload into memory.
The entry point at 0x1403C4A87 executes exactly three instructions before jumping to the main decryption bootstrap:
The far jump (JMPF) after the call is deliberately placed but unreachable in practice —FUN_140853d23 does not return in the normal case. This is a common obfuscation tactic to confuse linear-sweep disassemblers into following the wrong execution path.
FUN_140853d23 (108 bytes) is the root of the bootstrap chain. It sets up the decryption key, calls into FUN_1407522af (the 1,279-byte main resolver), and orchestrates mapping the decrypted payload before transferring control. The argument 0x19bab67 pushed before the call is used internally as an initial state value for the cipher or as a hash seed — it appears nowhere else in the stub and is not a valid Windows API ordinal.
Decryption Engine — ROL-XOR Cipher with Key 0x32063cae
The core API-string and payload decryption cipher is implemented in FUN_1407522af (1,279 bytes) and reused verbatim in FUN_140752798 (373 bytes). Both functions contain the identical decryption loop at different offsets.
The cipher is a position-dependent ROL-XOR scheme:
The key 0x32063cae appears at two independent call sites — once in FUN_1407522af at 0x140752502 and again in FUN_140752798 at 0x1407527d9. Its presence at both sites confirms it is the primary decryption constant and not a coincidental immediate value.
This cipher decrypts API name strings on demand: rather than storing plaintext DLL and function names in readable memory, the stub stores them encrypted and decrypts each one immediately before calling LoadLibraryA or GetProcAddress. After the call resolves, the decrypted string may be re-encrypted or discarded. This prevents memory-scanning tools from locating hidden API names at rest.
The loop iterates up to 0x104 (260) bytes per string — the maximum Windows API name length — and terminates at a null decrypted byte. The decrypted output lands in a stack buffer at rsp+0x50 before being passed to GetModuleHandleA or LoadLibraryA.
Dynamic API Resolution — PE Export Table Walk + Hash Comparison
In addition to decrypting API names before passing them to GetProcAddress, the binary implements a manual PE export table walker in FUN_1407522af. This allows it to resolve exports from already-loaded modules without ever calling GetProcAddress — bypassing API-level hooks placed by EDR products.
The walker sequence, confirmed by disassembly:
The walker performs a binary search over the export name pointer array, comparing each candidate name against the decrypted target string one character at a time. When a match is found, it resolves the corresponding ordinal and function address. This technique completely avoids the hooked GetProcAddress entry point while still finding the correct function.
When a module is not already loaded, the stub falls back to LoadLibraryA via the decrypted name path (call site at 0x1406131A7), loads it, then re-enters the walk loop against the newly loaded DLL. The resolver recurses into itself (CALL 0x1407522af at 0x1407528D7) to handle nested dependencies.
Import Analysis — Stub IAT Reveals Intended Capabilities
The visible import table (located inside .tiko) contains 19 imports across five DLLs. This is the minimum surface needed to bootstrap the runtime — every other API used by the real payload is resolved dynamically and leaves no trace in the static import table.
Dynamic resolution bootstrap (KERNEL32.dll)
LoadLibraryA— load arbitrary DLLs by decrypted nameGetProcAddress— named export resolution fallbackGetModuleHandleA— locate already-loaded modules without LoadLibrary overhead
These three APIs together reconstruct the entire Windows API surface at runtime. All remaining capability is hidden behind these three entry points.
Anti-debug / anti-sandbox (ntdll.dll + KERNEL32.dll + USER32.dll)
ntdll!NtQuerySystemInformation— queried directly from ntdll to bypass higher-level KERNEL32 hooks; with classSystemKernelDebuggerInformation(0x23) this detects kernel debuggers; withSystemProcessInformationit enumerates running processes to check for sandbox agentsKERNEL32!GetSystemTimeAsFileTime— high-resolution timing used to detect time-acceleration in sandboxes; if elapsed time between two calls is implausibly small the binary haltsKERNEL32!Sleep— deliberate delay to outlast sandbox analysis windows; many automated sandboxes do not wait past 60–90 secondsUSER32!GetProcessWindowStation+USER32!GetUserObjectInformationW— checks whether the process is running inside an interactive desktop session; sandbox and headless environments typically return a non-interactive window station (WinSta0vs service window stations)
CPU affinity manipulation (KERNEL32.dll)
GetProcessAffinityMask— read the current process and system CPU affinity masksSetProcessAffinityMask— restrict the process to specific CPU coresSetThreadAffinityMask— restrict individual threads to specific cores
Affinity manipulation serves multiple goals: (1) single-core sandboxes often expose only one CPU bit in the affinity mask, which the binary can detect; (2) pinning decryption threads to specific cores can frustrate time-based analysis tools; (3) in the real payload, affinity control may be used to interfere with game anti-cheat threads that run on specific cores.
Cross-session messaging (WTSAPI32.dll)
WTSSendMessageW— sends a message box to a specific Terminal Services / Remote Desktop session. UnlikeMessageBoxWwhich targets the calling session,WTSSendMessageWcan target any active session by session ID, including Session 0 (services) or other logged-in users
This is unusual in a FiveM bypass context. Likely uses: displaying a status dialog in a specific session after injection completes, or probing which sessions are active as part of environment fingerprinting. Requires SeImpersonatePrivilege or matching session ownership.
Memory & process utilities (KERNEL32.dll + ADVAPI32.dll)
LocalAlloc/LocalFree— heap allocation for decrypted buffersGetModuleFileNameW— retrieves the binary's own on-disk path; used for self-deletion or path-based checks (confirming it is running from the expected location)FreeLibrary— unloads modules loaded during resolution (cleanup after dynamic loading)ExitProcess— clean process termination if anti-debug checks fireADVAPI32!RegCloseKey— registry key handle cleanup after reading from the registryKERNEL32!GetCurrentThreadId— obtains the calling thread ID; used in thread-local storage (TLS) callbacks or for timing measurements
Export Table Abuse — Encrypted Binary Blob as Export Name
The PE export directory declares exactly one export (ordinal 1, base 1). The function address RVA is 0x00000000 — a null pointer that would crash if actually called. The export exists purely to anchor the export name entry.
The “name” of this export, instead of a readable ASCII identifier, is a 3,100-byte binary blob with Shannon entropy of 7.94/8.0 — statistically indistinguishable from random data. This is encrypted content embedded where export name strings normally live. Tools that parse the export table looking for DLL name or function name strings will either crash, truncate at the first unexpected byte, or display garbage.
1 (ordinal base 1)0x00000000 (null — never called)3,100 bytes7.9425 / 8.0The 3,100-byte blob is almost certainly a configuration block, decryption table, or secondary payload fragment stored in the export name slot because it is a convenient location that many parsers overlook or mishandle. The loader accesses it by navigating the export directory directly via the PE walk routine — not via any normal name-resolution API.
Anti-Analysis Techniques — Full Breakdown
The stub layer implements multiple independent anti-analysis and anti-debug measures before the real payload is mapped. Each technique targets a different analysis environment.
1 — Kernel debugger detection via NtQuerySystemInformation
NtQuerySystemInformation is imported directly from ntdll.dll rather than going through IsDebuggerPresent or CheckRemoteDebuggerPresent in KERNEL32. This bypasses any hooks that security tools place on the higher-level KERNEL32 wrappers. Querying with class 0x23 (SystemKernelDebuggerInformation) checks the kernel debug flag at the lowest possible level.
2 — Window station / interactive desktop check
GetProcessWindowStation retrieves the process window station handle, then GetUserObjectInformationW with UOI_NAME reads its name. An interactive user session has station name WinSta0. Sandboxes running under service accounts or headless environments return different names (Service-0x0-xxx$, etc.). If the name does not match an expected interactive session, the binary exits cleanly via ExitProcess.
3 — CPU core count fingerprinting
GetProcessAffinityMask returns the process affinity mask. By counting set bits, the binary determines how many CPU cores are available. Most sandboxes expose only 1–2 cores. A typical gaming machine has 8–32 cores. If the popcount of the affinity mask falls below a threshold, the binary treats the environment as a sandbox and terminates.
4 — Timing / sleep evasion
GetSystemTimeAsFileTime is called before and after a Sleep of a known duration. The delta is compared against the expected elapsed wall-clock time. Sandboxes that accelerate time (by advancing system clocks) or that patch Sleep to return immediately will produce a suspiciously small delta, triggering abort.
5 — Registry query (ADVAPI32!RegCloseKey)
The presence of RegCloseKey in the stub IAT — but no RegOpenKey or RegQueryValue — indicates those open/query functions are resolved dynamically. The registry is likely queried for sandbox-specific keys (e.g., VirtualBox guest additions, VMware, Sandboxie registry artifacts) or for license/HWID validation.
6 — Export table obfuscation / parser confusion
The 3,100-byte encrypted blob stored as the export function name causes many PE parsers and automated tools to misrepresent or skip the export directory. This is a deliberate measure to degrade analysis quality in tools that are not hardened against malformed export names.
7 — No PDB / stripped debug directory
Unlike the ApateonDecoy/Wizard sample (which leaked DecoyLoader.pdb with the developer's username apx), this binary has no debug directory at all. The developer either configured /DEBUG:NONE at link time or stripped the debug directory post-build. This eliminates one of the most useful static attribution vectors.
Timestamp Analysis
The COFF header timestamp is 0x6514b842, which decodes to 2023-09-27 23:18:26 UTC. Unlike the ApateonDecoy/Wizard sample (timestamped 2026-01-01 — manipulated to a future date), this timestamp falls within a plausible range for a real build date. It is not zeroed, not in the distant future, and corresponds to a Wednesday evening in late September 2023.
- The timestamp alone does not prove the binary was compiled on that date — MSVC timestamps can be set arbitrarily — but its plausibility suggests less attention to anti-forensics in this dimension
- Assuming the timestamp is authentic, the binary predates the ApateonDecoy/Wizard sample by roughly 27 months, suggesting a shared packer codebase that has been reused and distributed over an extended period
SizeOfImage 0x89F000appears in Windows AppCompatCache/PcaSVC shimcache records paired with the executable path — this value combined with the SHA-256 hash provides a reliable historical detection artifact even after the file is deleted
Stub Function Map — All 16 Visible Functions
Ghidra headless analysis identifies 16 functions in the non-encrypted stub layer. The bulk of functionality lives in the encrypted .tiko payload and is only visible after runtime decryption. Functions are listed by address, size, and role.
| Address | Size | Name | Role |
|---|---|---|---|
| 0x1403C4A87 | 17 B | entry | Entry point — PUSH seed → CALL bootstrap → JMPF (dead code) |
| 0x1406252EB | 15 B | FUN_1406252eb | Final loader trampoline — CALL RAX (resolved fn ptr), stack cleanup, JMP to resolver |
| 0x1407522AF | 1279 B | FUN_1407522af | Main API resolver — MZ/PE validation, export table walk, binary search, ROL-XOR decryptor, LoadLibraryA fallback, recursive entry |
| 0x140752798 | 373 B | FUN_140752798 | GetProcAddress caller — second ROL-XOR loop (key 0x32063cae), ordinal-by-number fallback, self-recursive for nested resolution |
| 0x1407556AF | 87 B | FUN_1407556af | Stack frame switcher — saves RBP/RSP, aligns stack, JMPs to JMP-R8 trampoline |
| 0x1407557B7 | 5 B | thunk_FUN_1407bcf28 | Thunk — short JMP to FUN_1407bcf28 |
| 0x1407938F7 | 9 B | FUN_1407938f7 | NOT+ROL obfuscation stub — NOT ECX, ROL ECX,1, JMP to FUN_140860bb7 |
| 0x1407AC0B6 | 2 B | FUN_1407ac0b6 | PUSH RBP + RET — minimal stub, used as call target to capture return address |
| 0x1407BCF28 | 17 B | FUN_1407bcf28 | Stack bounds check — LEA R9,[RSP+0x140], CMP RBX R9, JMP to stack switcher |
| 0x140817C0F | 3 B | FUN_140817c0f | JMP R8 — indirect dispatcher |
| 0x140836B15 | 21 B | FUN_140836b15 | ADD R11,4 (advance ptr), CMC, XOR ECX R9D obfuscation, JMP to NOT+ROL stub |
| 0x1408370FA | 14 B | FUN_1408370fa | MOVSXD RCX, pointer arithmetic ADD R8 RCX, JMP thunk |
| 0x140853D23 | 108 B | FUN_140853d23 | Root bootstrap — called from entry point; sets up decryption context, drives loader chain |
| 0x140860BB7 | 41 B | FUN_140860bb7 | INC+BSWAP+XOR obfuscation trampoline, MOD stack, JMP to PUSH RBP stub |
| 0x14086CE7C | 179 B | FUN_14086ce7c | ROR/XOR/NEG arithmetic on stack value — appears to be a hash function or checksum stub |
| 0x140876C62 | 6 B | FUN_140876c62 | CLD + JMP — clears direction flag, dispatches to next stage |
The patterned obfuscation stubs (FUN_1407938f7, FUN_140860bb7, FUN_14086ce7c, etc.) exist to break up the control flow graph and prevent disassemblers from cleanly following execution. They interleave meaningless arithmetic operations (CMC, STC, CLC, BSWAP on partially-used registers) with the real logic. This is a known obfuscation technique called junk instruction insertion — the junk instructions have no net effect on program state but dramatically increase the number of nodes in the control flow graph.
Packer Family — Same Architecture as ApateonDecoy/Wizard
This binary and the ApateonDecoy/Wizard bypass sample share a common packer architecture. The following structural features match exactly:
- Single high-entropy payload section (7.82 in Wizard's
.zywvs 7.88 in this sample's.tiko) containing all executable code - Six or more hollowed virtual sections with no raw file data
- Identical ROL-XOR decryption cipher with key
0x32063caepresent in both samples at corresponding function positions - Manual PE export table walker for API resolution, bypassing hooked
GetProcAddress - Import stub limited to {LoadLibraryA, GetProcAddress, GetModuleHandleA} plus capability imports
requireAdministratorUAC manifest in both samples- Entry point inside the encrypted payload section
- Zeroed PE checksum
Section names differ between samples (.zyw / .3L9 / .64X vs .tiko / .limco / .dino / .gala, etc.), indicating the packer re-randomises or regenerates section names per build. The underlying packer code and decryption key, however, are consistent — suggesting either the same individual built both, or a shared packer-as-a-service tool is in use.
Primary Indicators of Compromise
b61907b9081bb5d7125264c5e60de013c02b7b866148248de603fb55f8d39a18ad4d25b43964c1c54accdcbe97a3f2ca80d15894340753116751ef6f5212667501a0e562notepad.exe (spoofed — not the real Windows notepad)5,142,528 bytes (4.90 MB)0x6514b842 → 2023-09-27 23:18:26 UTC0x0089F000 (AppCompatCache artifact)0x32063cae (ROL-XOR constant — shared with ApateonDecoy/Wizard)7.8848 / 8.03,100 bytes, entropy 7.9425 — encrypted payload fragment0x1403C4A87 (first instruction: PUSH 0x19bab67)- Behavioural: demands UAC elevation, enumerates CPU cores via affinity mask, queries window station name, calls
WTSSendMessageWcross-session, reads registry keys dynamically - Memory: at runtime the decrypted payload will be mapped into a newly allocated RWX region; look for unsigned executable pages not backed by a file on disk
- Network: not determinable from static analysis — the real payload is encrypted
YARA Detection Rule
The following rule targets both this specific sample and future builds from the same packer family. Two variants are provided — a tight single-sample rule and a broader family rule.
rule NotepadBypass_fa817dc1_Exact {
meta:
description = "Exact match: notepad.exe bypass (fa817dc1)"
hash_sha256 = "b61907b9081bb5d7125264c5e60de013c02b7b866148248de603fb55f8d39a18"
date = "2026-06-06"
strings:
// ROL-XOR decryption key constant (little-endian DWORD)
$key = { AE 3C 06 32 }
// Bootstrap seed pushed at entry point
$seed = { 68 67 AB B9 01 }
// Section name strings
$sec_tiko = ".tiko" ascii wide
$sec_limco = ".limco" ascii wide
$sec_gala = ".gala" ascii wide
condition:
uint16(0) == 0x5A4D and filesize < 8MB and
$key and $seed and ($sec_tiko or $sec_limco or $sec_gala)
}
rule NotepadBypass_PseudoPacker_Family {
meta:
description = "PseudoPacker family — covers fa817dc1 and ApateonDecoy/Wizard variants"
author = "Clubhouse AC Research"
date = "2026-06-06"
reference = "https://clubhouseac.shop/research/notepad-bypass-detection"
strings:
// Shared ROL-XOR key constant
$rolxor_key = { AE 3C 06 32 }
// Export table abuse: null function RVA in export directory
// Manual PE walk: MZ signature check pattern
$mz_check = { B8 4D 5A 00 00 66 3B 01 }
// PE sig check immediate
$pe_check = { 81 3C 08 50 45 00 00 }
// ApateonDecoy section names
$sec_zyw = ".zyw" ascii
$sec_3l9 = ".3L9" ascii
// This sample section names
$sec_tiko = ".tiko" ascii
$sec_limco = ".limco" ascii
condition:
uint16(0) == 0x5A4D and filesize < 25MB and
$rolxor_key and
($mz_check or $pe_check) and
($sec_zyw or $sec_3l9 or $sec_tiko or $sec_limco)
}Detection Notes — Shared Packer Intelligence
Because this binary and ApateonDecoy/Wizard share the decryption key 0x32063cae, a single YARA rule targeting the key constant catches both samples and likely all other builds from the same packer family. Adding the PE export table walk pattern (MZ signature check immediate + PE\0\0 check) narrows false positives without missing variants that change section names.
- AppCompatCache / ShimCache: the
SizeOfImagevalue0x89F000and the file path are cached by Windows PCA service even after the file is deleted. Querying SRUM, Prefetch (NOTEPAD.EXE-XXXXXXXX.pf), and AppCompatCache with path and size allows post-incident detection on endpoints where the file has been cleaned up - Memory forensics: the decrypted payload, once mapped at runtime, will appear as an unsigned PE image in memory. Tools like Volatility3
windows.malfindorwindows.dlllistwill identify the unsigned module - Behaviour: a call to
WTSSendMessageWfrom an unsigned process claiming to be Notepad is an extremely high-fidelity detection signal — no legitimate Notepad variant makes this call - Affinity manipulation: calls to
SetProcessAffinityMaskimmediately after startup (before any user interaction) from an alleged text editor should be flagged