[Bug 40623] DOOM (2016) crashes on launch due to Denuvo copy protection

wine-bugs at winehq.org wine-bugs at winehq.org
Sat May 21 23:31:41 CDT 2016


https://bugs.winehq.org/show_bug.cgi?id=40623

--- Comment #19 from roman at hargrave.info <roman at hargrave.info> ---
(In reply to Michael Müller from comment #18)
> Created attachment 54540 [details]
> Hack to prevent crash
> 
> I debugged the crash some days ago. The problem with the Denuvo protection
> is that it contains a lot of intentional broken code that is called as soon
> as the system notices that something is going wrong. It is therefore
> possible that the crash happens on purpose and that the currently crashing
> code path should not be reached at all if everything works fine.
> Nevertheless, I analyzed the crash and attached a hack to to work around it.
> 
> The following assembler code leads to the crash. I added some annotations to
> explain the behavior.
> 
> ----
> 
> mov rax,qword ptr gs:[60]      ; TEB->PEB
> mov qword ptr ss:[rsp+C80],rbx
> mov qword ptr ss:[rsp+C68],rbp
> 
> mov rcx,qword ptr ds:[rax+18]  ; PEB->LdrData 
> 
> mov qword ptr ss:[rsp+C60],rsi
> mov qword ptr ss:[rsp+C58],rdi
> mov r9,qword ptr ds:[rcx+20]   ; LdrData->InMemoryOrderModuleList (list
> entry)
> 
> mov qword ptr ss:[rsp+C50],r12
> xor edi,edi
> sub r9,10 ; -> LDR_MODULE
> 
> mov qword ptr ss:[rsp+C48],r14
> mov qword ptr ss:[rsp+C40],r15
> cmp qword ptr ds:[r9+30],rdi ; LDR_MODULE->BaseAddress
> ; The comparison is a bit misleading, as the BaseAddress
> ; can never be zero. The idea is that if we reach the end
> ; of the list, we get a pointer to LdrData->InMemoryOrderModuleList
> ; again. The offset of LDR_MODULE->BaseAddress now matches the
> ; PEB_LDR_DATA->EntryInProgress offset. This value should be zero
> ; and wine never changes it anyway. The iteration therefore stops
> ; when we reach this entry again.
> 
> ; Reached end of list
> je doomx64.159E4907E ; --> Crash
> 
> ; BeginHash:
> mov r10,qword ptr ds:[r9+60] ; LDR_MODULE->BaseDllName.Buffer
> mov edx,edi ; edx will contain the hash, initialize with 0
> mov r8,rdi
> movzx ecx,word ptr ds:[r10] ; Check if BaseDllName is empty
> test cx,cx
> je doomx64.159E49075 ; --> NextEntry
> 
> ; ConverToLowerCase:
> lea eax,dword ptr ds:[rcx-41] ; - 'A'
> cmp ax,19
> ja doomx64.159E49055 ; --> HashChar
> add cx,20 ; tolower
> 
> ; HashChar:
> inc r8
> movzx eax,cx
> movzx ecx,word ptr ds:[r10+r8*2] ; ecx -> next char
> 
> ; The actual "hashing" of the name
> add edx,eax ; edx += char
> add edx,edx ; edx *= 2
> 
> ; Reached end of string?
> test cx,cx
> jne doomx64.159E49048 ; no --> ConverToLowerCase
> cmp edx,C8A08         ; check hash against 0xC8A08
> je doomx64.159E49178  ; --> found
> 
> ; NextEntry:
> mov r9,qword ptr ds:[r9] ; InLoadOrderModuleList.Flink
> cmp qword ptr ds:[r9+30],rdi ; LDR_MODULE->BaseAddress
> ; Reached end of list?
> jne doomx64.159E49036 ; no --> BeginHash
> 
> ; Crash:
> ; This function is meant to crash. It calls rsi (=rdi),
> ; which is already set to zero when the whole code path is
> ; reached. This is most probably just broken code to confuse us.
> mov rsi,rdi
> lea rax,qword ptr ss:[rsp+C98]
> lea rdx,qword ptr ds:[159DDCE20]
> mov r9d,20219
> xor r8d,r8d
> mov rcx,FFFFFFFF80000001
> mov qword ptr ss:[rsp+20],rax
> call rsi ; rsi = rdi = 0
> test eax,eax
> 
> ----
> 
> The whole algorithm just searches for a specific dll using a hash. The
> enumeration matches the following C code:
> 
> ----
> 
> TEB *teb = (void*) NtCurrentTeb();
> PEB *peb = teb->Peb;
> 
> PEB_LDR_DATA *ldr_data = peb->LdrData;
> LIST_ENTRY *entry = ldr_data->InMemoryOrderModuleList.Flink;
> LDR_MODULE *mod = (void*) ((char*) entry - 0x10);
> 
> while (mod->BaseAddress)
> {
> 	/* hash mod->BaseDllName.Buffer */
> 	mod = (void*)mod->InLoadOrderModuleList.Flink;
> }
> 
> ----
> 
> The assembler code searches for the hash 0xC8A08 which corresponds to
> advapi32.dll. The dll is loaded directly by doom through the PE import
> section. So why does it crash? Well, if you look at the enumeration you will
> see that it starts the search using the InMemoryOrderModuleList list but
> continues using the InLoadOrderModuleList list. The search only succeeds if
> the first entry in InMemoryOrderModuleList (i.e. the dll loaded at the
> lowest address) is loaded before advapi32. Otherwise the code intentionally
> crashes.
> 
> I currently don't have a windows machine for testing but it would be great
> if someone could attach a debugger on windows and take a look at the loaded
> libraries and their addresses. This way we can check if the whole code is
> garbage to confuse people or if the necessary conditions are met on windows.
> 
> I wrote a hack to pin advapi32.dll as first entry of the
> InMemoryOrderModuleList. This prevents the crash and Doom uses advapi32 to
> read some registry keys. It also loads the dbdata license file. When this is
> done nothing interesting happens any more. I guess that the license check
> fails, but I haven't debugged it any further since I don't have enough time
> at the moment. It would also make sense to check the theory on windows first
> to prevent wasting time on some intentionally broken code.
> 
> If someone wants to debug this any further, the attached hack should help. I
> wrote it against Staging (to use WINEDEBUG=+DOOMx64.exe:relay for generating
> relay logs without debugging Steam), but it should also apply on plain wine.

Thanks for the input Michael. If it's truly the case that WINE is simply not
similar enough to a Windows environment for Denuvo to be "OK", and not the case
that WINE is not implemented behaviour required for the basic functioning of
Denuvo in itself, that would mean that a simple set of patched could
potentially make this work. Unfortunately I can't do the suggested testing, but
I would like to note that debugging Denuvo (or running it in a VM) may lead to
unexpected results.

-- 
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.


More information about the wine-bugs mailing list