patching winrar license

0x0 intro

Some of you might remember the WinRAR RCE vulnerability (dropped around 2019) ever since it seems like WinRAR gets updated more frequently, It got me curious & decided to challenge myself to try and analyse WinRAR, more precisely - get rid of the annoying and infinite ‘Expired Notification’ and ‘Please purchase WinRAR license’ windows.

The first thing I usually do when working on any binary is to identify what type of binary, architecture, compiler information and generally viewing pe information etc..
my go-to tool for this is ‘detect it easy’ (DIE for short) which has dozens of signatures to detect various packers, crypters and protectors moreover the ability to run scripts and plugins and its open source, I will also be using IDA for static analysis.

looks like our binary is 64-bit compiled with MSVC

finding our main function is relatively easy, each PE file has a field called Subsystem inside IMAGE_OPTIONAL_HEADER structure, the subsystem determines how the kernel should prepare the environment for the program based on the entry-point function the application compiled with.

  • IMAGE_SUBSYSTEM_WINDOWS_GUI (0x2) - means our program is GUI application defined by using WinMain
  • IMAGE_SUBSYSTEM_WINDOWS_CUI (0x3) - defined by using main() function usually this is standard C++ entry point, the OS will attach console window to this process

before we start diving into our analysis it is worth to mention that on 64-bit binaries will use __fastcall calling convention by default meaning arguments are passed in this order
rcx - first arg
rdx - second arg
r8 - third arg
r9 - fourth arg
the rest are passed through the stack

0x1 analysis

using DIE we see our subsystem value is 0x2 so inside our entrypoint we search for a function call with 4 arguments whereas rcx is 0x140000000

0x1400FCF5F - mov     r8, rax         ; lpCmdLine
0x1400FCF62 - mov     r9d, ebx        ; nShowCmd
0x1400FCF65 - xor     edx, edx        ; hPrevInstance
0x1400FCF67 - lea     rcx, cs:140000000h ; hInstance
0x1400FCF6E - call    WinMain         ; IDA has already detected & renamed it

inside our main function we have 5 calls to RegisterClassW, it initialises WndClass struct at the beginning but each call it sets different lpfnWndProc (function-pointer/callback which is responsible for most of the behaviour of the window), lpszClassName (string which specifies the window class name) and hbrBackground (handle to class background brush).

judging by the strings used for lpszClassName (uhm “RarReminder”) we can assume the same strings are used for CreateWindow api, cross reference “RarReminder” string leads to

0x1400A9788 - mov     rcx, cs:hInstance
0x1400A978F - lea     r8, WindowName              ; "WinRAR"
0x1400A9796 - mov     [rsp+10A8h+lpParam], r15    ;
0x1400A979B - lea     rdx, aRarreminder           ; "RarReminder"
0x1400A97A2 - mov     [rsp+10A8h+hInstance], rcx  ; hInstance
0x1400A97A7 - mov     r9d, esi                    ; dwStyle
0x1400A97AA - mov     [rsp+10A8h+hMenu], r15      ; hMenu
0x1400A97AF - xor     ecx, ecx                    ; dwExStyle
0x1400A97B1 - mov     [rsp+10A8h+hWndParent], r15 ; hWndParent
0x1400A97B6 - mov     [rsp+10A8h+nHeight], r14d   ; nHeight
0x1400A97BB - mov     [rsp+10A8h+nWidth], ebp     ; nWidth
0x1400A97BF - mov     [rsp+10A8h+Y], edi          ; Y
0x1400A97C3 - mov     [rsp+10A8h+X], ebx          ; X
0x1400A97C7 - call    cs:CreateWindowExW

We could NOP (refers to replacing the assembly with ‘no operation’ opcode which as the name suggest - does nothing) the call above ‘CreateWindowExW’ by using any hex editor simply find the RVA offset or search by pattern and replace the hex bytes with 0x90. Launching our patched winrar it seems ‘expired notification’ window is gone but it’s not convenient to NOP window creation but preferably finding the subroutine that checks for registration.

Few lines below we have this call

0x1400A981F - mov     rcx, cs:hInst     ; hInstance
0x1400A9826 - lea     r9, sub_1400E27F0 ; lpDialogFunc
0x1400A982D - mov     r8, rax           ; hWndParent
0x1400A9830 - mov     qword ptr [rsp+10A8h+X], r15 ; dwInitParam
0x1400A9835 - lea     rdx, aReminder    ; "REMINDER"
0x1400A983C - call    cs:DialogBoxParamW

DialogBoxParamW is basically a wrapper for looping messages and window creation, it will call CreateWindow to create the dialog box, sub_1400E27F0 is the dialog procedure inside it contains strings such as “HELPLicenseAndRegistering”, “REMINDER”, “order.htm” which might be related to our ‘Please purchase WinRAR license’ window. DialogBoxParamW is called only if dil (in x64 dil is lowest 8 bits of rdi register) is non-zero value which is determined by sub_1400620E8 return value- if it returns zero it sets dil to one and vice versa.

0x1400A938B - xor     r15d, r15d
0x1400A93A6 - mov     r12d, 1
...
0x1400A9495 - call    sub_1400620E8
0x1400A949A - test    al, al     // al is the low 8 bits of rax
0x1400A949C - jnz     short loc_1400A94AA
...
0x1400A94A5 - mov     dil, r12b  // set dil to 1 if sub_1400620E8 returns non-zero
...
0x1400A94AA - mov     dil, r15b  // set dil to 0 if sub_1400620E8 returns zero
...
0x1400A980D - test    dil, dil

sub_1400620E8 simply returns global variable

sub_1400620E8:
	0x1400620E8 - mov     al, cs:byte_1401954F0
	0x1400620EE - ret

and referenced three times in our function, here it’s being called and if the return value (AL) is zero don’t take the jump, in other words continue calling functions below while pushing “Interface\Misc”, “RemShown” strings as arguments with different r8

0x1400A951C - lea     rsi, aInterfaceMisc ; "Interface\\Misc"
...
0x1400A952B - call    sub_1400620E8
0x1400A9530 - test    al, al
0x1400A9532 - jnz     short loc_1400A957D ; dont jump is al == zero
0x1400A9534 - xor     r8d, r8d
0x1400A9537 - lea     rdx, aRemshown      ; "RemShown"
0x1400A953E - mov     rcx, rsi
0x1400A9541 - call    sub_1400A789C       ; read registry
0x1400A9546 - cmp     eax, cs:dword_14015DFA8
0x1400A954C - jnb     short loc_1400A957D ; 
0x1400A954E - lea     r8d, [rax+1]
0x1400A9552 - mov     rcx, rsi
0x1400A9555 - lea     rdx, aRemshown      ; "RemShown"
0x1400A955C - call    sub_1400A84D8       ; write registry

these are wrapper functions used for reading and writing registry keys, their implementation is pretty simple and straightforward it will append “Interface\Misc” to “Software\WinRAR” which is the key path and uses RegQueryValueExW to read and RegSetValueExW to write.
I won’t go into full detail of explaining the registry, all you need to know is that it is database structured in a tree format, to run registry editor simply open a run prompt (windows key + R) and type “regedit”, navigate to “HKEY_CURRENT_USER\Software\WinRAR\Interface\Misc\” RemShown is set at 1 if we modify the value to 0 and relaunch winrar this window pops up

after this window is shown registry shows RemShown value has changed to 1.
so in essence we have a subroutine- that if returns non-zero value will skip creating dialog related to purchasing and reading registry keys to determine if ‘reminder’ window has shown, it is safe to presume this subroutine could indicate if winrar has registered.
we can change sub_1400620E8 to always return true by changing the assembly from mov al, cs:byte_1401954F0 to mov al, 0x1 or even change the global variable value using any memory editor (cheat engine for example) base_address + 0x1954F0

and it appears WinRAR title no longer contains “evaluation copy” string.
cross reference this global variable (base_address + 0x1954F0) shows it is being used twice, in the second subroutine it moves cl (lowest 8 bit of rcx register) which is the function parameter to our global variable

sub_140062268:
	0x140062268 - mov     cs:byte_1401954F0, cl
	0x14006226E - ret

basically sub_140062268 sets winrar registration state according to the byte it receives.

to sum up this writeup it does not cover generating keys for winrar but rather my approach on doing static analysis and modifying winrar memory and how I found the registration routine. this analysis was done on 5.91 winrar version(MD5 715065F9ADF100230AFCB91D99316050)