^ The idea was to give this code flexibility. If the game updates, I won't know again where the data is
![Razz :P](./images/smilies/icon_razz.gif)
The x64dbg script I sent to you by PM works in a similar way, but not for scanning, just for determining which is the section with the highest virtual size. The reason I look for that section is personal observations (multiple test subjects) on how the Denuvo envelope works. Reminds me somewhat of HASP Sentinel
So:
Code: Select all
mov $ModuleBase, mod.main()
mov $FileHeader, $ModuleBase + dword:[$ModuleBase+0x3C] // +0x3C = e_lfanew
mov $NumberOfSections, 2:[$FileHeader+0x6]
mov $SizeOfOptionalHeader, 2:[$FileHeader+0x14]
mov $FirstSection, $FileHeader + 0x18 + $SizeOfOptionalHeader
mov $LastSection, $FirstSection + ($NumberOfSections - 1) * 0x28
mov $ExecStart, $ModuleBase + dword:[$FirstSection + 0xC]
mov $AddressOfEntryPoint, $ModuleBase + dword:[$FileHeader + 0x28]
// load all section sizes into an array (sizes are DWORDs)
mov $i, 0
alloc $NumberOfSections * 4
mov $array, $result
a0:
cmp i, $NumberOfSections
je a1
mov dword:[$array + $i * 4], dword:[$FirstSection + $i * 0x28 + 0x10]
mov $i, $i + 1
jmp a0
a1:
// time to get to the Denuvo section via the largest raw size
// we'll store the first max found, then iterate NumberOfSections times the rest
mov $i, 0
mov $j, $NumberOfSections
mov $u, 0
mov $v, 0
mov $max, 0
a2:
cmp dword:[$array + $i * 4], 0 // while not having parkoured the entire sizes_array
je a3
mov $u, dword:[$array + $i * 4] // get i-iterated stored size value
a4:
cmp $j, 0 // start by checking NumberOfSections, if 0
je a5
mov $v, dword:[$array + ($NumberOfSections - $j) * 4] // get j-iterated stored size value
cmp $u, $v
jg a6 // if u < v
cmp $max, $v
jge a6 // and max <= v
mov $max, $v // store max
mov $ExecDenuvo, $ModuleBase + dword:[$FirstSection + ($NumberOfSections - $j) * 0x28 + 0xC] // and its section address
a6:
mov $j, $j - 1 // next j-iteration
jmp a4 // that's why the cmp at line #32, because we loop
a5:
mov $j, $NumberOfSections // restore j iterator
mov $i, $i + 1 // next i-iteration
jmp a2 // and loop till we compare each size with all sizes in the array and the max
a3:
free $array // clean up
log "First exec section starts at: {$ModuleBase + dword:[$FirstSection + 0xC]}"
log "Denuvo exec section starts at: {$ExecDenuvo}"
ret
This is to be executed in x64dbg, Script pane. Just save the above as a .txt and load it up. Then open the game .exe in x64dbg (doesn't have to be running; the script expects the process to be paused to run), load script .txt and run it. You'll see what happens next
![Razz :P](./images/smilies/icon_razz.gif)
And that's how I know which section the executable code was relocated in by Denuvo.
Of course.. some of the executable code will still be in the (usually) first section. Like I said, Denuvo doesn't relocate ALL of it; just certain sized-one. Example: MOV AL,1|RET will not be relocated.
Ignore the target in the screenshot above. Red Dead Redemption 2 doesn't use Denuvo, so the script is useless in that context.
BR,
Sun
P.S.#1: What Denuvo does when enveloping a target is to scan the executable code of the original PE32/64, determine the # of functions to relocate based on function size, allocate enough physical space in a section of its own, relocate the executable code and replace the function prologues with JMPs to the relocated code, generate the encrypted version of the whole Denuvo section and envelope the target. Moving the code-to-be-protected to their own section is the safest way for them to control the decryption process (the relocated code is decrypted with the license key). They don't care about the rest, as you would not obtain a working executable anyway without the key
What I don't understand is why it takes a shitload of time for the reversing teams to properly copy-paste (and offset-fix) the code back to its original location. Unless there's some of it that runs virtualized that I don't know about...
P.S.#2: I can turn the above into a Lua script that would allow it to be run whenever you like (e.g.: at table open) and will calculate the location of the Denuvo section for you. Then it's up to you to determine if the AOB you scan for is in then first (or the original executable) section or Denuvo's relocated executable section
![Razz :P](./images/smilies/icon_razz.gif)
Or even better.. scan for it either of them. The advantage is if the code won't be found in the first section, the others will be skipped (scanner should jump directly into Denuvo's). It won't probably faster than mgr.inz.Player's code, but in-between normal and his
![Razz :P](./images/smilies/icon_razz.gif)
Lemme know