Degradation as DRM
by Nikolaos Tsapakis
Information available is for educational purposes only, views expressed are my own and do not necessarily reflect those of my employer.
Long time ago, while I was searching for interesting Digital Rights Management (DRM) systems on games, I came across FADE. 1
The protection allows the player to use the game normally. Then it gradually degrades certain game features over time, like decreasing the accuracy of the player's weapons, eventually rendering it unplayable.
It seemed to me like an interesting exercise to introduce a similar custom protection as a binary patch in a game.
I selected LZDoom. 2 After downloading, you need to place a WAD3 file inside the main game directory in order to start the game.
The idea is to drop the game frame rate on a computer which is different than the game owner's computer based on an event.
That event is the player typing the idkfa cheat string during game play.
A different computer is been detected by the CPUID 4 instruction. That hardware check is not very strict, but I believe it is fine for the purposes of the current article.
In order to discover which game code triggers on player cheat string input, I started the game and entered "idkfa", then noticed the string "Very Happy Ammo Added" on top of the screen.
Break pointing for read access on that particular string pauses game execution on code which gets executed in an event of a player cheat string input. For dropping the game frame rate, I found the game's main loop and introduced a delay. Delay may be introduced in different places inside the game's main loop.
The file for binary patching is lzdoom.exe having an MD5 value of: 61a2cd931fd3aaaae976e4131c512728
Binary analysis and patching was done using x64dbg. 5
Running tests between two different computers was done using a physical and a virtual machine on VirtualBox. Following are the patches, description, and file raw offsets to patch.
You may use any hex editor to apply the patches.
patch_1:
; file offset 0x281FD6 ; goto patch_2 player cheat string input event check E9290F4500 jmp lzdoom_____prot.patch_2 90 nop continue_2: <original game instructions>patch_2:
; file offset 0x6D2F04 ; save original registers 50 push rax 66:9C pushf ; rax points to cheat string ; compare string with "Very" 813856657279 cmp dword ptr ds:[rax],0x79726556 ; if no string matches then continue game 75 0F jne lzdoom_____prot.continue_1 ; if string matches then get delta E8 00000000 call lzdoom_____prot.delta delta: 58 pop rax ; rax points at the end of data section 48:05 DC745700 add rax, 0x5774DC ; set flag for later h/w check C600 01 mov byte ptr ds:[rax],1 ; restore original registers continue_1: 66:9D popf 58 pop rax ; execute stolen code due to patch_1 48:8BF8 mov rdi,rax 48:85FF test rdi,rdi ; continue game E9 AFF0BAFF jmp lzdoom_____prot.continue_2patch_3:
; file offset 0x253C0 ; go to patch_4 hardware check E9 67DB6A00 jmp lzdoom_____prot.patch_4 90 nop 90 nop 90 nop 90 nop continue_4: <original game instructions>patch_4:
; file offset 0x6D2F2C ; save original registers 66:9C pushf 50 push rax 53 push rbx 51 push rcx 52 push rdx E8 00000000 call lzdoom_____prot.delta_2 delta_2: 58 pop rax 48:05 B9745700 add rax,0x5774B9 ; check if flag set by cheat string check ; If not set then continue game else check h/w ; processor info and feature bits 8038 01 cmp byte ptr ds:[rax],1 75 17 jne lzdoom_____prot.continue_3 48:33C0 xor rax,rax 48:FFC0 inc rax 0FA2 cpuid ; If h/w check fails introduce frame delay 81F9 0322989E cmp ecx,0x9E982203 74 07 je lzdoom_____prot.continue_3 B9 00101101 mov ecx,0x1111000 frame_drop: E2 FE loop lzdoom_____prot.frame_drop ; restore original registers, execute stolen ; code due to patch_3 and continue game continue_3: 5A pop rdx 59 pop rcx 5B pop rbx 58 pop rax 66:9D popf 57 push rdi ; stolen code 48:81EC 80000000 sub rsp0x80 ; stolen code E9 5C2495FF jmp lzdoom_____prot.continue_4References