The following article demonstrates the crack procedure for ShareMouse version 3 (currently at 3.0.29
). There are not many differences in procedure between version 3 and the previous instructions for 2.x.
A persistent mistake that the ShareMouse developers make lies within the startDemoTimer
function that is responsible for starting the timer that checks for expiry because it is a function coded in such a way that it can easily be disabled.
In order to do so, we nop
the first jump from the demoTimerEnabled
conditional:
startDemoTimer]: 0000000100011d2f push rbp ; Objective C Implementation defined at 0x1000932d0 (instance) 0000000100011d30 mov rbp, rsp 0000000100011d33 push r14 0000000100011d35 push rbx 0000000100011d36 mov rbx, rdi 0000000100011d39 mov rax, qword [ds:objc_ivar_offset_AppController_demoTimerEnabled] ; objc_ivar_offset_AppController_demoTimerEnabled 0000000100011d40 lea rcx, qword [ds:0x1000a3be4] ; 0x1000a3be4 0000000100011d47 mov cl, byte [ds:rcx] 0000000100011d49 or cl, byte [ds:rbx+rax] ; jump starts here 0000000100011d4c nop ; don't jump, just ignore 0000000100011d4d nop 0000000100011d4e pop rbx 0000000100011d4f pop r14 0000000100011d51 pop rbp 0000000100011d52 ret ; and return ; the next part starts the timer
The string DEMO
is stored in cfstring_str_demoaddstr
which gets appended to the controls once the configuration is loaded. For example, the method optsMenuClick
is paved with references to cfstring_str_demoaddstr
which can easily be knocked out.
There are two ways to do this:
DEMO
string to the controls manually.
Since the second method is too much of a bother, we will have to turn all the jumps into unconditional jumps, slaloming across the code such that we do not enter any section that contains a reference to cfstring_str_demoaddstr
. We locate optsMenuClick
and follow the jumps.
The first jump is:
; ... ; preamble of optsMenuClick method ; ... 000000010001fed4 mov rax, qword [ds:objc_ivar_offset_AppController_forumBtnPrefs] ; objc_ivar_offset_AppController_forumBtnPrefs, XREF=-[AppController optsMenuClick:]+46 000000010001fedb mov rdi, qword [ds:rbx+rax] ; argument "instance" for method imp___got__objc_msgSend 000000010001fedf lea rax, qword [ds:0x1000a3be4] ; 0x1000a3be4 000000010001fee6 cmp byte [ds:rax], 0x0 000000010001fee9 sete al 000000010001feec mov rsi, qword [ds:0x10009f2f0] ; @selector(setHidden:), argument "selector" for method imp___got__objc_msgSend 000000010001fef3 movzx edx, al 000000010001fef6 call qword [ds:imp___got__objc_msgSend] 000000010001fefc mov rax, qword [ds:objc_ivar_offset_AppController_versionEdition] ; objc_ivar_offset_AppController_versionEdition 000000010001ff03 mov eax, dword [ds:rbx+rax] 000000010001ff06 cmp eax, 0x2 ; we need to turn this jump into an unconditional jmp to avoid crossing into the next section that contains the DEMO string 000000010001ff09 jne 0x1000202da
Turning the last jne
into a jmp
and following the jump brings us into the next check (we jumped to 0x1000202da
):
00000001000202da cmp eax, 0x3 ; XREF=-[AppController optsMenuClick:]+165 00000001000202dd jne 0x100020675
Now, this jne
has to be entirely removed instead of jumping like we did previously. The reason for this is that the section following this jne
does not contain any reference to cfstring_str_demoaddstr
and at the end of the section the code already jump to the next section. So just remove the jne
:
00000001000202da cmp eax, 0x3 ; XREF=-[AppController optsMenuClick:]+165 00000001000202dd nop 00000001000202de nop 00000001000202df nop 00000001000202e0 nop 00000001000202e1 nop 00000001000202e2 nop ; rest follows and goes up to: 000000010002066d mov rdi, r13 0000000100020670 jmp 0x100020eae
which is fine.
All the DEMO
strings should now be removed.
This is a funny one - we have not done this previously with 2.x
since we just wanted to manipulate flow control instead of data but let's try attacking the application's isRegistered
method. The original method looks as follows:
-[AppController isRegistered]: 0000000100008360 push rbp ; Objective C Implementation defined at 0x100092850 (instance) 0000000100008361 mov rbp, rsp 0000000100008364 push r14 0000000100008366 push rbx 0000000100008367 mov rbx, rdi 000000010000836a mov r14, qword [ds:objc_ivar_offset_AppController_licenseKey] ; objc_ivar_offset_AppController_licenseKey 0000000100008371 mov rdi, qword [ds:rbx+r14] ; argument "instance" for method imp___got__objc_msgSend 0000000100008375 mov rsi, qword [ds:0x10009ef38] ; @selector(isEqualToString:), argument "selector" for method imp___got__objc_msgSend 000000010000837c lea rdx, qword [ds:cfstring_] ; @"" 0000000100008383 call qword [ds:imp___got__objc_msgSend] 0000000100008389 test al, al 000000010000838b jne 0x1000083e1 000000010000838d mov rdi, qword [ds:rbx+r14] ; argument "instance" for method imp___got__objc_msgSend 0000000100008391 mov rsi, qword [ds:0x10009ef38] ; @selector(isEqualToString:), argument "selector" for method imp___got__objc_msgSend 0000000100008398 lea rdx, qword [ds:cfstring_DEMO] ; @"DEMO" 000000010000839f call qword [ds:imp___got__objc_msgSend] 00000001000083a5 test al, al 00000001000083a7 jne 0x1000083e1 00000001000083a9 mov rdi, qword [ds:rbx+r14] ; argument "instance" for method imp___got__objc_msgSend 00000001000083ad mov rsi, qword [ds:0x10009ef38] ; @selector(isEqualToString:), argument "selector" for method imp___got__objc_msgSend 00000001000083b4 lea rdx, qword [ds:cfstring_NONE] ; @"NONE" 00000001000083bb call qword [ds:imp___got__objc_msgSend] 00000001000083c1 test al, al 00000001000083c3 jne 0x1000083e1 00000001000083c5 mov rdi, qword [ds:rbx+r14] ; argument "instance" for method imp___got__objc_msgSend 00000001000083c9 mov rsi, qword [ds:0x10009ef38] ; @selector(isEqualToString:), argument "selector" for method imp___got__objc_msgSend 00000001000083d0 lea rdx, qword [ds:cfstring_0] ; @"0" 00000001000083d7 call qword [ds:imp___got__objc_msgSend] 00000001000083dd test al, al 00000001000083df je 0x1000083eb 00000001000083e1 xor eax, eax ; XREF=-[AppController isRegistered]+43, -[AppController isRegistered]+71, -[AppController isRegistered]+99 00000001000083e3 movzx eax, al ; XREF=-[AppController isRegistered]+153 00000001000083e6 pop rbx 00000001000083e7 pop r14 00000001000083e9 pop rbp 00000001000083ea ret 00000001000083eb mov rax, qword [ds:objc_ivar_offset_AppController_isIllegal] ; objc_ivar_offset_AppController_isIllegal, XREF=-[AppController isRegistered]+127 00000001000083f2 cmp byte [ds:rbx+rax], 0x0 00000001000083f6 sete al 00000001000083f9 jmp 0x1000083e3
What you notice is that regardless of the jne
s along the way the application will end up at 0x1000083e1
where it executes the instruction:
xor eax, eax
That instruction simply sets the return value stored in eax
to zero. In other words, it is semantically equivalent to:
mov eax, 0
In hex, the instructions encoding will be the sequence 0x31 0xC0
. Now we are going to make this function return 1
regardless because the application is, of course, registered.
In order to do that, lets first force a jump to the xor
instruction by turning the jne
into a jmp
:
Registered]: 0000000100008360 push rbp ; Objective C Implementation defined at 0x100092850 (instance) 0000000100008361 mov rbp, rsp 0000000100008364 push r14 0000000100008366 push rbx 0000000100008367 mov rbx, rdi 000000010000836a mov r14, qword [ds:objc_ivar_offset_AppController_licenseKey] ; objc_ivar_offset_AppController_licenseKey 0000000100008371 mov rdi, qword [ds:rbx+r14] ; argument "instance" for method imp___got__objc_msgSend 0000000100008375 mov rsi, qword [ds:0x10009ef38] ; @selector(isEqualToString:), argument "selector" for method imp___got__objc_msgSend 000000010000837c lea rdx, qword [ds:cfstring_] ; @"" 0000000100008383 call qword [ds:imp___got__objc_msgSend] 0000000100008389 test al, al 000000010000838b jmp 0x1000083e1
Next, we manipulate the xor eax, eax
by overwriting it with mov eax, 1
:
00000001000083e1 mov eax, 0x1 ; XREF=-[AppController isRegistered]+43, -[AppController isRegistered]+71, -[AppController isRegistered]+99 00000001000083e6 pop rbx 00000001000083e7 pop r14 00000001000083e9 pop rbp 00000001000083ea ret
such that the function returns 1
instead of 0
.
The effects thereof is that the Register
menu item disappears, and potentially other checks along the way are knocked out.