xScope is a screen measuring tool that comes along with different other tools which are useful for anything design related. The application has surprising features, such as being able to measure iOS screens as well as color pickers using a loupe that allows you to select pixels and gives you the numeric representation of the color.
So we're back on the x64 OSX platform, and looking to demine the application using jumps only. Using a quick search for symbols, we find the method isRegistered
which is referenced in several places:
objc_sel_isRegistered: 0000000100093e10 dq 0x000000010005d29d ; XREF=0x100003d0e, 0x100010db2, 0x1000137e5, 0x100013e48, 0x10001e124, 0x10001fe28, ...
which has the following body:
====== B E G I N O F P R O C E D U R E ====== ; Basic Block Input Regs: rsp rdi - Killed Regs: rax rbp methImpl_RegistrationManager_isRegistered: 000000010001fe06 55 push rbp 000000010001fe07 4889E5 mov rbp, rsp 000000010001fe0a 488B057F8C0700 mov rax, qword [ds:_OBJC_IVAR_$_RegistrationManager.registrationState] 000000010001fe11 833C0702 cmp dword [ds:rdi+rax], 0x2 000000010001fe15 0F94C0 sete al 000000010001fe18 0FB6C0 movzx eax, al 000000010001fe1b 5D pop rbp 000000010001fe1c C3 ret ; endp
which returns a value based on a test performed by RegistrationManager
. We can go ahead and modify the return so that the test always returns true
. However, if we cannot modify the values themselves and want to stick to control flow, we have to go through the isRegistered
cases and reason about the jumps. There are exactly 13
calls to isRegistered
. Let us take the very first one in the method ALoupeView
:
0000000100003d0e 488B35FB000900 mov rsi, qword [ds:objc_sel_isRegistered] ; @selector(isRegistered) 0000000100003d15 4889C7 mov rdi, rax 0000000100003d18 FFD3 call rbx 0000000100003d1a 84C0 test al, al 0000000100003d1c 0F8557010000 jne 0x100003E79 ; Basic Block Input Regs: rbp - Killed Regs: xmm0 xmm1 0000000100003d22 F20F1005FE580600 movsd xmm0, qword [ds:0x100069628] 0000000100003d2a F20F108D60FFFFFF movsd xmm1, qword [ss:rbp-0xe0+var_64] 0000000100003d32 660F2EC1 ucomisd xmm0, xmm1 0000000100003d36 770A jnbe 0x100003D42 ; Basic Block Input Regs: <nothing> - Killed Regs: xmm0 0000000100003d38 F20F1005E0580600 movsd xmm0, qword [ds:0x100069620] 0000000100003d40 EB1E jmp 0x100003D60 ; Basic Block Input Regs: rax xmm1 - Killed Regs: rax rcx xmm0 0000000100003d42 F20F1005E6580600 movsd xmm0, qword [ds:0x100069630] ; XREF=0x100003d36 0000000100003d4a 660F2EC1 ucomisd xmm0, xmm1 0000000100003d4e 0F97C0 setnbe al 0000000100003d51 0FB6C0 movzx eax, al 0000000100003d54 488D0D15590600 lea rcx, qword [ds:0x100069670] ; "" 0000000100003d5b F20F1004C1 movsd xmm0, qword [ds:rcx+rax*8] ; Basic Block Input Regs: rax rbp xmm0 xmm1 xmm2 - Killed Regs: rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r12 r14 r15 xmm2 xmm3 xmm4 xmm5 . . . 0000000100003de5 488D0544C80900 lea rax, qword [ds:_registrationReminderText] 0000000100003dec 488B18 mov rbx, qword [ds:rax] 0000000100003def 488B3532000900 mov rsi, qword [ds:objc_sel_sizeWithAttributes_] ; @selector(sizeWithAttributes:) 0000000100003df6 4889DF mov rdi, rbx 0000000100003df9 4C89F2 mov rdx, r14
and notice the jump:
0000000100003d1c 0F8557010000 jne 0x100003E79
If this test succeeds, then the whole block after the jump is skipped. The block being something that leads to:
0000000100003de5 488D0544C80900 lea rax, qword [ds:_registrationReminderText]
Since we do not want the nag-text to appear, we replace the jne
by a jmp
so that the test al, al
becomes superfluous. This would have to be repeated 13
times for all the isRegistered
calls.
xScope
pops up reminders from time to time, another symbol check and we find the method needsLicenseReminder
:
====== B E G I N O F P R O C E D U R E ====== ; Basic Block Input Regs: rax rcx rdi - Killed Regs: rcx rsi rdi r14 methImpl_RegistrationManager_needsLicenseReminder: 000000010001feac 55 push rbp 000000010001fead 4889E5 mov rbp, rsp 000000010001feb0 4157 push r15 000000010001feb2 4156 push r14 000000010001feb4 53 push rbx 000000010001feb5 50 push rax 000000010001feb6 4989FE mov r14, rdi 000000010001feb9 488B35503F0700 mov rsi, qword [ds:objc_sel_isRegistered] ; @selector(isRegistered) 000000010001fec0 4C89F7 mov rdi, r14 000000010001fec3 FF1587D30500 call qword [ds:imp___got__objc_msgSend] 000000010001fec9 30C9 xor cl, cl 000000010001fecb 84C0 test al, al 000000010001fecd 7438 je 0x10001FF07 ; Basic Block Input Regs: rax rcx r14 - Killed Regs: rax rcx rbx rsi rdi r15 000000010001fecf 488B3542480700 mov rsi, qword [ds:objc_sel_licenseCount] ; @selector(licenseCount) 000000010001fed6 4C8B3D73D30500 mov r15, qword [ds:imp___got__objc_msgSend] 000000010001fedd 4C89F7 mov rdi, r14 000000010001fee0 41FFD7 call r15 000000010001fee3 4889C3 mov rbx, rax 000000010001fee6 488B059B8B0700 mov rax, qword [ds:_OBJC_IVAR_$_RegistrationManager.serial] 000000010001feed 48FFC3 inc rbx 000000010001fef0 498B3C06 mov rdi, qword [ds:r14+rax] 000000010001fef4 488B3525480700 mov rsi, qword [ds:objc_sel_usersWithLicense] ; @selector(usersWithLicense) 000000010001fefb 41FFD7 call r15 000000010001fefe 30C9 xor cl, cl 000000010001ff00 4839D8 cmp rax, rbx 000000010001ff03 7E02 jle 0x10001FF07 ; Basic Block Input Regs: <nothing> - Killed Regs: rcx 000000010001ff05 B101 mov cl, 0x1 ; Basic Block Input Regs: rcx - Killed Regs: rax rbx rsp rbp r14 r15 000000010001ff07 0FB6C1 movzx eax, cl ; XREF=0x10001fecd, 0x10001ff03 000000010001ff0a 4883C408 add rsp, 0x8 000000010001ff0e 5B pop rbx 000000010001ff0f 415E pop r14 000000010001ff11 415F pop r15 000000010001ff13 5D pop rbp 000000010001ff14 C3 ret ; endp
and since we do not need reminders, we can short circuit this method as well and step over the isRegistered
check by turning the je
at 000000010001fecd
into a jmp
as if we were registered, to 0x10001FF07
:
000000010001fecd E935000000 jmp 0x10001FF07
Another interesting symbol is:
cfstring___Remove_this_notice__purchase_now__: 000000010009d450 dq 0x0000000000000000, 0x00000000000007c8, 0x000000010005a385, 0x0000000000000024 ; XREF=0x10003ac8d, 0x100047a3f
which as only two references. Following the first one at 0x10003ac8d
, we find:
000000010003ac84 751A jne 0x10003ACA0 ; Basic Block Input Regs: rax r14 - Killed Regs: rdx rsi rdi r14 000000010003ac86 488B35EB930500 mov rsi, qword [ds:objc_sel_stringByAppendingString_] ; @selector(stringByAppendingString:) 000000010003ac8d 488D15BC270600 lea rdx, qword [ds:cfstring___Remove_this_notice__purchase_now__] ; @" (Remove this notice: purchase now!)" 000000010003ac94 4C89F7 mov rdi, r14 000000010003ac97 FF15B3250400 call qword [ds:imp___got__objc_msgSend] 000000010003ac9d 4989C6 mov r14, rax ; Basic Block Input Regs: rax rbp r8 r14 - Killed Regs: rax rcx rdx rbx rsi rdi r8 r15 000000010003aca0 488B3501960500 mov rsi, qword [ds:objc_sel_generalPasteboard] ; @selector(generalPasteboard) XREF=0x10003ac84
and since we do not want to purchase it, we remove the nagger by turning the jne
into a jmp
and paddign accordingly:
000000010003ac84 E917000000 jmp 0x10003ACA0 000000010003ac89 90 nop 000000010003ac8a 90 nop 000000010003ac8b 90 nop 000000010003ac8c 90 nop 000000010003ac8d 488D15BC270600 lea rdx, qword [ds:cfstring___Remove_this_notice__purchase_now__] ; @" (Remove this notice: purchase now!)" 000000010003ac94 4C89F7 mov rdi, r14 000000010003ac97 FF15B3250400 call qword [ds:imp___got__objc_msgSend] 000000010003ac9d 4989C6 mov r14, rax ; Basic Block Input Regs: rax rbp r8 r14 - Killed Regs: rax rcx rdx rbx rsi rdi r8 r15 000000010003aca0 488B3501960500 mov rsi, qword [ds:objc_sel_generalPasteboard] ; @selector(generalPasteboard) XREF=0x10003ac84
We do the same for the other reference at 0x100047a3f
.
The PurchaseWindowController_registrationStatus
checks whether the license is in order:
0000000100044409 488D0DA0510500 lea rcx, qword [ds:cfstring_] ; @"" XREF=0x1000444ef, 0x10004444b 0000000100044410 488B3519050500 mov rsi, qword [ds:objc_sel_localizedStringForKey_value_table_] ; @selector(localizedStringForKey:value:table:) 0000000100044417 4889C7 mov rdi, rax 000000010004441a 4531C0 xor r8d, r8d 000000010004441d 488B052C8E0300 mov rax, qword [ds:imp___got__objc_msgSend] 0000000100044424 4883C408 add rsp, 0x8 0000000100044428 5B pop rbx 0000000100044429 415E pop r14 000000010004442b 415F pop r15 000000010004442d 5D pop rbp 000000010004442e FFE0 jmp rax 0000000100044430 488B35C1030500 mov rsi, qword [ds:objc_sel_mainBundle] ; @selector(mainBundle) XREF=0x1000443e7 0000000100044437 488B3D6A220500 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] 000000010004443e FF150C8E0300 call qword [ds:imp___got__objc_msgSend] 0000000100044444 488D1565970500 lea rdx, qword [ds:cfstring_RegistrationInvalid] ; @"RegistrationInvalid" 000000010004444b EBBC jmp 0x100044409
it does that by checking the user and serial text fields and then jumping back until rax
contains the needed value to break out of the jmp 0x100044409
.
We can avoid this block and jump directly to the valid multiple licenses by changing:
00000001000443ec 745F je 0x10004444D
to a jump at address 0x10004444d
, where we nop
the next jump at 0x100044476
in order to have the program be thankful that we did not purchase any licenses at all:
That's it.