A couple days ago I decided to increase my reverse engineering "skills". Opening up protected PE files, that are developed by multiple developers, who know what they are doing is kind of interesting and ofc seriously challenging.
I'll try to regularly update this thread and show my insights. Please feel free to discuss my findings here since it would be good to know, if I'm on the right path

I'm still learning a lot in RE and especially Windows Internals. This is personally for my own learning process and re-writing down my findings helps me a lot with this. I hope there are people out there that have way more experience than I have and can "error check" my findings. A little "You are on the right track" or a "You fell for a trap there" will help me for sure!
I did use files from the latest BF6 Beta. This will probably be a little outdated in the (near) future but internal structure e. g. manually loading DLLs might be still the same. I don't own any other games right now, that have Javelin as AC and BF6 Beta was for free.
So let the journey begin.
I opened DIE (Detect It Easy) and checked
EAAntiCheat.GameServiceLauncher.exe. After a few seconds I got the red message
(Heur)Packer: Compressed or packed data[High entropy + Section 0 (".text") compressed]. This was to be expected and also, this wont be any known packer. They will probably have their own mechanism. Next, we checked the Extractor findings of DIE and can see, that our .text-section is typed as GZip.
Also, the IMAGE_OPTIONAL_HEADER looks like that:
Code:
- SizeOfCode: 0x0074FA00 (7,665,152 bytes) // (7.3MB) Unusually large
- SizeOfImage: 0x00F23000 (15,872,000 bytes) // 15MB total size
- AddressOfEntryPoint: 0x006FE290 // Deep entry point
So, yeah. We know that people dont want us to dig around.
Lets go and load this EXE into IDA. Seeing what's happening.
Ofc IDA throws some error messages, but lets ignore them. We want to focus on the Import table and can immediately find interesting stuff: Only 1 imported function:
Code:
- 0000000140A647F8 1 TlsCallbacks preloader_l
This get's called when a thread is started. It's a function inside
preloader.dll.
This seems to be an important DLL.
Browsing the games directory, there it is... One drag&drop away from the IDA load up. Now it gets interesting.
First, lets check the import table:
Code:
- NtTerminateProcess
- ZwCreateUserProcess
- ZwQueryInformationProcess
- CallNextHookEx
- MessageBoxW
- MD5Init
- ...
The dopamine kicks since these imports look very good for a tool, that wants to protect a game. Leeeeets go and dig further.
Taking a look at the DllEntryPoint reveals this code:
Code:
- .rdata:0000000180003C00 ; BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
- .rdata:0000000180003C00 public DllEntryPoint
- .rdata:0000000180003C00 DllEntryPoint proc near ; DATA XREF: .rdata:0000000180003E5C↓o
- .rdata:0000000180003C00 ; .pdata:0000000180006090↓o
- .rdata:0000000180003C00 ; __unwind { // sub_1800024D0
- .rdata:0000000180003C00 ud2
- .rdata:0000000180003C00 DllEntryPoint endp
What. That looks strange. I didn't know "ud2" and googled it:
Code:
- The processor raises an invalid opcode exception if you execute an undefined instruction.
I see, they immediatly FORCE an exception here. But why?
Looking at the line above "ud2" shows __unwind. That's some exception meta-data! So, next step is, understanding the meta-data of the exception and we probably get some more executable code from there.
The __unwind links to
sub_1800024D0 and this is the code of it:
Code:
- sub_1800024D0 proc near
- mov rax, [r9+38h] ; Get address from dispatcher context
- mov eax, [rax] ; Dereference to get offset
- add rax, [r9+8] ; Add module base address
- mov [r8+0F8h], rax ; Set RIP (instruction pointer)
- xor eax, eax ; Return 0 (continue execution)
- retn
- sub_1800024D0 endp
and via IDA we XREF to the data structure here:
Code:
- .rdata:0000000180003BD0 stru_180003BD0 UNWIND_INFO_HDR <1, 1, 0, 0, 0, 0>
- .rdata:0000000180003BD0 ; DATA XREF: .pdata:0000000180006084↓o
- .rdata:0000000180003BD4 dd rva sub_1800024D0
- .rdata:0000000180003BD8 dd rva sub_180002330
As you already read, I added some comments to the
sub_1800024D0.
This handler dynamically calculates and sets the next instruction pointer, redirecting execution flow away from the ud2 instruction.
This leads us back to the
UNWIND_INFO_HDR . The next data stored the is
sub_180002330. Another function:
Code:
- sub_180002330 proc near
- ; Parameter preservation
- mov rsi, r8 ; Save r8 parameter
- mov edi, edx ; Save edx parameter
- mov rbx, rcx ; Save rcx parameter
-
- ; Call initialization
- call sub_180001520 ; Main loader function
-
- ; Setup for final jump
- lea r9, qword_180005040 ; Configuration data structure
- mov rax, cs:qword_180005070 ; Load function pointer
- or edi, 80000000h ; Set activation flag
-
- ; Jump to real implementation
- jmp rax ; Transfer control to actual code
- sub_180002330 endp
Nice! So this looks like a trampoline!
Let's investigate
sub_180001520.
Code:
- .text:0000000180001520 sub_180001520 proc near ; CODE XREF: sub_180002330+F↓p
- .text:0000000180001520 ; sub_180002440+F↓p
- .text:0000000180001520 ; DATA XREF: ...
- .text:0000000180001520
- .text:0000000180001520 ShareAccess = dword ptr -578h
- .text:0000000180001520 Length = dword ptr -568h
- .text:0000000180001520 ByteOffset = qword ptr -560h
- .text:0000000180001520 Key = qword ptr -558h
- .text:0000000180001520 AccessProtection= dword ptr -550h
- .text:0000000180001520 BaseAddress = qword ptr -540h
- .text:0000000180001520 Position = word ptr -532h
- .text:0000000180001520 var_530 = _LARGE_INTEGER ptr -530h
- .text:0000000180001520 SectionHandle = qword ptr -528h
- .text:0000000180001520 RegionSize = qword ptr -520h
- .text:0000000180001520 OldAccessProtection= dword ptr -514h
- .text:0000000180001520 var_510 = qword ptr -510h
- .text:0000000180001520 var_508 = qword ptr -508h
- .text:0000000180001520 Buffer = qword ptr -500h
- .text:0000000180001520 var_4F8 = qword ptr -4F8h
- .text:0000000180001520 FileHandle = qword ptr -4F0h
- .text:0000000180001520 var_4E8 = qword ptr -4E8h
- .text:0000000180001520 var_4E0 = qword ptr -4E0h
- .text:0000000180001520 ProcedureAddress= qword ptr -4D8h
- .text:0000000180001520 var_4C8 = xmmword ptr -4C8h
- .text:0000000180001520 Size = dword ptr -4B8h
- .text:0000000180001520 Name = _STRING ptr -4A8h
- .text:0000000180001520 var_498 = qword ptr -498h
- .text:0000000180001520 var_490 = dword ptr -490h
- .text:0000000180001520 var_488 = xmmword ptr -488h
- .text:0000000180001520 Module = qword ptr -478h
- .text:0000000180001520 NumberOfBytesToProtect= qword ptr -470h
- .text:0000000180001520 IoStatusBlock = _IO_STATUS_BLOCK ptr -468h
- .text:0000000180001520 BaseOfImage = qword ptr -458h
- .text:0000000180001520 var_448 = qword ptr -448h
- .text:0000000180001520 var_440 = word ptr -440h
- .text:0000000180001520 var_41C = dword ptr -41Ch
- .text:0000000180001520 var_408 = dword ptr -408h
- .text:0000000180001520 var_404 = dword ptr -404h
- .text:0000000180001520 var_58 = xmmword ptr -58h
- .text:0000000180001520
- .text:0000000180001520 push r15
- .text:0000000180001522 push r14
- .text:0000000180001524 push r13
- .text:0000000180001526 push r12
- .text:0000000180001528 push rsi
- .text:0000000180001529 push rdi
- .text:000000018000152A push rbp
- .text:000000018000152B push rbx
- .text:000000018000152C sub rsp, 558h
- .text:0000000180001533 movaps [rsp+598h+var_58], xmm6
- .text:000000018000153B cmp cs:BaseAddress, 0
- .text:0000000180001543 jnz short loc_18000159D
- .text:0000000180001545 mov [rsp+598h+BaseOfImage], 0
- .text:0000000180001551 mov rax, gs:30h
- .text:000000018000155A mov rax, [rax+60h]
- .text:000000018000155E mov rcx, [rax+18h] ; PcValue
- .text:0000000180001562 lea rdx, [rsp+598h+BaseOfImage] ; BaseOfImage
- .text:000000018000156A call cs:__imp_RtlPcToFileHeader
- .text:0000000180001570 mov rax, [rsp+598h+BaseOfImage]
- .text:0000000180001578 test rax, rax
- .text:000000018000157B jnz short loc_180001596
- .text:000000018000157D mov rcx, cs:__imp_RtlPcToFileHeader ; PcValue
- .text:0000000180001584 lea rdx, [rsp+598h+BaseOfImage] ; BaseOfImage
- .text:000000018000158C call rcx ; __imp_RtlPcToFileHeader
- .text:000000018000158E mov rax, [rsp+598h+BaseOfImage]
- .text:0000000180001596
- .text:0000000180001596 loc_180001596: ; CODE XREF: sub_180001520+5B↑j
- .text:0000000180001596 mov cs:BaseAddress, rax
- .text:000000018000159D
- .text:000000018000159D loc_18000159D: ; CODE XREF: sub_180001520+23↑j
- .text:000000018000159D mov rsi, cs:__imp_NtYieldExecution
- .text:00000001800015A4 mov di, 1
- .text:00000001800015A8 jmp short loc_1800015BF
- ... way to much code
This is some real [removed] here. I have to study this functions a bit more, since it's is really long. However, I found out the following already:
Code:
- lea rdx, aEaanticheatGam ; "EAAntiCheat.GameServiceLauncher.dll"
- call cs:__imp_RtlAppendUnicodeToString
Looks like they start manually mapping another dll to the game.
Code:
- call cs:__imp_NtOpenFile ; Open DLL file
- test eax, eax
- js loc_1800022F2 ; Jump if failed
DLL gets opened
Code:
- cmp word ptr [rsp+598h+BaseOfImage], 5A4Dh ; Check MZ signature
- jnz loc_18000205B
- mov eax, 0C000007Bh
- cmp dword ptr [rsp+rcx+598h+BaseOfImage], 4550h ; Check PE signature
- jnz loc_18000205B
some DLL PE validation
Code:
- ; Create memory section
- call cs:__imp_NtCreateSection
- test eax, eax
- js loc_1800022F2
-
- ; Map section into memory
- call cs:__imp_NtMapViewOfSection
- test eax, eax
- jns loc_180001DA4
Ahhh yeah, there we go. Mapping!
Code:
- mov eax, [rax+rcx+28h] ; Get AddressOfEntryPoint
- add rcx, rax ; Add base address
- mov cs:qword_180005070, rcx ; Store as function pointer
and it graps the entry point.
That's it for now. I'll be on vacation now till end of next week. Enjoy the read and I'm going to update this thread in the future.
If you want to go reverse this huge function on you own, this is the sig:
Code:
- 41 57 41 56 41 55 41 54 56 57 55 53 48 81 EC 58 05 00 00 0F 29 74 24 A0
or
Code:
- 48 81 EC 58 05 00 00 0F 29 74 24 A0 48 83 3D ?? ?? ?? ?? 00
02.10.2025 Update:
So I completely dissected the preloader.dll for the launcher now. My first view on the service looks very similar, however some "magic constants" changed there. I will focus on the launcher now.
As you already know, the preloader manually maps another AC DLL. I found out, that it also does some file version checking and can potentially reload from an embedded resource. I wont focus much on that now, because we want to get our hands on the REAL anti-cheat stuff.
What's still very interesting and 100% super important to the ongoing journey is this struct here, that gets passed to the manually mapped DLL entry point function:
Code:
- .text:00000001800022BB call cs:qword_180005070 ; the entry point of the loaded dll, params (NULL (RCX), 0x25665CD7 (RDX), NULL (R8), &EAACConfig (R9))
"My" preloader struct layout so far, that gets passes to the decryption lib/exe:
Code:
- struct TheiaDecryptionSetup {
-
- uint64_t magic1; // +0x00: 0xBC041F6C9783F67F
- uint64_t magic2; // +0x08: 0x019957B8303E7F01
- void* preloader_base; // +0x10: preloader_l.dll base
- void* mapped_dll_base; // +0x18: loaded DLL base
- void* external_module_base; // +0x20: additional module
- HANDLE section_handle; // +0x28: NtCreateSection handle
- void* dll_entry_point; // +0x30: calculated entry point
-
- uint8_t padding[0x20]; // +0x38-0x57: reserved/padding
- __m128 cleared_xmm; // +0x58: zeroed 16-byte region
-
- void* error_handler; // +0x68: report_and_handle_error
- void* virtual_call_thunk; // +0x70: COM dispatch
- void* com_window_proc; // +0x78: window handler
-
- uint32_t env_config_value; // +0x80: some env config value
- uint8_t function_ptr_flag; // +0x84: boolean (rbx != 0)
- uint8_t padding2[3]; // +0x85-0x87: alignment
-
- void* RtlQueryEnvironmentVariable_U; // +0x88
- void* RtlExpandEnvironmentStrings; // +0x90
- void* RtlDosPathNameToNtPathName; // +0x98
- void* RtlCreateProcessParametersEx; // +0xA0
- void* RtlCreateEnvironment; // +0xA8
-
- void* ZwCreateUserProcess; // +0xB0
- void* ZwSetInformationFile; // +0xB8
- void* ZwCreateFile; // +0xC0
- void* ZwCreateThreadEx; // +0xC8
- void* ZwQueryInformationFile; // +0xD0
- void* ZwQueryInformationProcess; // +0xD8
- void* ZwClose; // +0xE0
- void* ZwQueryDirectoryFile; // +0xE8
- void* ZwWaitForSingleObject; // +0xF0
- void* ZwDeleteFile; // +0xF8
-
- void* keyboard_input_filter; // +0x100: blocks injected keys, WH_KEYBOARD_LL
- void* mouse_input_filter; // +0x108: blocks injected mouse, WH_MOUSE_LL
- void* always_allow_stub; // +0x110: always true stub
- void* passthrough_monitor_hook; // +0x118: logging only
- };
So, the entry point of the manually mapped dll gets called with this struct data. magic1 & magic2 will be important IMO. Also the 2nd parameter to the called entry point function (0x25665CD7) will be interesting. magic1 and magic2 changed in the service / launcher preloader. The 2nd parameter to the entry point function is the same in both variants.
06.10.2025 Update:
Started
Theia String Decryptor
08.10.2025 Update:
As my string decryption is working quite good, we can process further with the dissect of the crucial functions. We habe some interesting strings here:
Code:
- 0x7FF684DF5006 ( 30): "dev-skyfall.dev.ac.ea.com:443"
- 0x7FF684DF5024 ( 34): "staging-skyfall.dev.ac.ea.com:443"
- 0x7FF684DF5046 ( 26): "eaanticheat.ac.ea.com:443"
We could check out, what kind of data they grep and send to their server from here
Code:
- 0x7FF684DF5B74 ( 19): "EAAntiCheatService"
- 0x7FF684DF5AD6 ( 32): "Failed retrieving service path."
- 0x7FF684DF5AB5 ( 33): "Game service executable missing."
Pretty sure this is relevant to start the service and we can check (optional) params maybe. Maybe disable the running checks...
Code:
- 0x7FF684DF5F88 ( 74): "Device Driver signing integrity checks must be enabled to start the game."
- 0x7FF684DF5FFC ( 55): "Test or debug mode must be disabled to start the game."
Maybe we can check this here to disable integrity checks. Who knows...
Code:
- 0x7FF684E1BB30 ( 37): "ea.ac.skyfall.pubs.ScreenshotRequest"
This here is important for a few of you! They for sure send screenshot. You could probably go from here and intercept it. Take care if you simply go for an ESP. Also Google for Javelin Screenshot function returns some AI scanning for screenshots. So yeah, this could ban alot of private hacks, if you dont take care of screenshots.
Code:
- 0x7FF684E147E6 ( 25): "NtQuerySystemInformation"
- 0x7FF684E147FF ( 25): "NtQueryInformationThread"
- 0x7FF684E14818 ( 21): "NtQueryVirtualMemory"
- 0x7FF684E1482D ( 24): "NtSetInformationProcess"
- 0x7FF684E14845 ( 26): "NtQueryInformationProcess"
- 0x7FF684E1485F ( 23): "NtQueryDirectoryObject"
- 0x7FF684E14876 ( 22): "NtOpenDirectoryObject"
- 0x7FF684E1488C ( 14): "NtQueryObject"
- 0x7FF684E1489A ( 21): "NtQueryDirectoryFile"
- 0x7FF684E148AF ( 23): "NtQueryInformationFile"
- 0x7FF684E148C6 ( 23): "NtSetSystemInformation"
- 0x7FF684E148DD ( 37): "NtEnumerateSystemEnvironmentValuesEx"
- 0x7FF684E14902 ( 17): "NtCreateThreadEx"
- 0x7FF684E14913 ( 25): "NtOpenSymbolicLinkObject"
- 0x7FF684E1492C ( 26): "NtQuerySymbolicLinkObject"
- 0x7FF684E14946 ( 22): "NtDeviceIoControlFile"
- 0x7FF684E149BD ( 11): "\ntdll.dll"
- 0x7FF684E1498D ( 27): "SetProcessMitigationPolicy"
Nt* Functions... awesome. Maybe we start somewhere here. IDK.
Code:
- 0x7FF684E19F84 ( 27): "DiscSoftLTD (Daemon Tools)"
- 0x7FF684E19E37 ( 12): "CheatEngine"
- 0x7FF684E19F42 ( 11): "AutoHotkey"
- 0x7FF684E19ED8 ( 18): "Windows10Injector"
- 0x7FF684E19EEA ( 21): "UnknowncheatsGeneric"
- 0x7FF684E19E4B ( 8): "ProcMon"
- 0x7FF684E19EFF ( 7): "DTrace"
- 0x7FF684E19FA9 ( 17): "MagnetRAMCapture"
- 0x7FF684E19EC8 ( 16): "ExtremeInjector"
- 0x7FF684E19F06 ( 14): "ProcessHacker"
- 0x7FF684E19F2E ( 13): "CheatToolSet"
- 0x7FF684E19F9F ( 10): "Proxifier"
- 0x7FF684E19F22 ( 4): "MHS"
- 0x7FF684E19FBA ( 14): "EasyAntiCheat"
- 0x7FF684E19FE0 ( 8): "Javelin"
- 0x7FF684E19FD9 ( 7): "FaceIT"
- 0x7FF684E19FC8 ( 17): "EasyAntiCheatEOS"
I dont know what this list is for right now. I dont think, it's for detection, more likely some reporting maybe. There are a lot of other tools blocked like IDA and x64dbg but I couldn't find strings for them.
Code:
- 0x7FF684DF6795 ( 16): "EAAntiCheat.cfg"
A config file that is actually a pure data DLL file. But there is like no data inside. So this will be maybe loaded from the internet. IDK now. Just guessing.