The Perfect Photo Suite is an addon for Adobe Photoshop CS6. In demo-mode it runs for 15 days, after which it starts nagging. It seems that the whole nag system is easily dismantled by disabling the showDemoOrActivationIfNecessary
in the Perfect Photo Suite
application. However that is not clean enough if other applications from the suite are launched individually.
Cracking the Perfect Photo Suite
is redundant and the best way is to crack the onOneToolbox
helper application instead. We have left this section here because it represented the first logical step. You can jump directly to the "Framework Protection" section.
The Perfect Photo Suite application can be easily made to shut-up by short-circuiting the showDemoOrActivationIfNecessary
method. This is a self-standing check that is implemented in the Perfect Photo Suite
binary. Later on, we will move to the framework checks.
First, since activation and showing demo screens is not really necessary, we go to the showDemoOrActivationIfNecessary
method and jmp
from the start to the end of the method. This will only ensure that the popups will not show for just the Perfect Photo Suite
application which implements its own checks.
====== B E G I N O F P R O C E D U R E ====== ; Basic Block Input Regs: rax rsi rdi - Killed Regs: rbx rsi rdi __ZN9PLToolbox31showDemoOrActivationIfNecessaryEv_10004b644: // PLToolbox::showDemoOrActivationIfNecessary() 000000010004b644 55 push rbp ; XREF=0x10002294c 000000010004b645 4889E5 mov rbp, rsp 000000010004b648 53 push rbx 000000010004b649 4883EC08 sub rsp, 0x8 000000010004b64d 4889FB mov rbx, rdi 000000010004b650 488B7F10 mov rdi, qword [ds:rdi+0x10] 000000010004b654 31F6 xor esi, esi 000000010004b656 E8B92E0200 call imp___symbol_stub1__onOneActivationToolboxRegister 000000010004b65b 894318 mov dword [ds:rbx+0x18], eax 000000010004b65e 3D204E0000 cmp eax, 0x4E20 000000010004b663 7410 je 0x10004B675 ; Basic Block Input Regs: rax - Killed Regs: <nothing> 000000010004b665 3D224E0000 cmp eax, 0x4E22 000000010004b66a 7409 je 0x10004B675 ; Basic Block Input Regs: rax rbx - Killed Regs: rbx 000000010004b66c 31DB xor ebx, ebx 000000010004b66e 3D214E0000 cmp eax, 0x4E21 000000010004b673 7525 jne 0x10004B69A ; Basic Block Input Regs: rax - Killed Regs: rbx 000000010004b675 89C3 mov ebx, eax ; XREF=0x10004b663, 0x10004b66a 000000010004b677 85C0 test eax, eax 000000010004b679 741F je 0x10004B69A ; Basic Block Input Regs: rax - Killed Regs: rax rcx rdx rsi rdi 000000010004b67b 488D0D86522300 lea rcx, qword [ds:cfstring_PLToolbox__showDemoOrActivationIfNecessary__] ; @"PLToolbox::showDemoOrActivationIfNecessary()" 000000010004b682 89C2 mov edx, eax 000000010004b684 488D0505672300 lea rax, qword [ds:_kPerfectLayersStr_100281d90] ; "" 000000010004b68b 488B30 mov rsi, qword [ds:rax] 000000010004b68e BF01000000 mov edi, 0x1 000000010004b693 31C0 xor eax, eax 000000010004b695 E842150200 call imp___symbol_stub1__ONLogWithParams ; Basic Block Input Regs: rbx - Killed Regs: rax rbx rsp 000000010004b69a 89D8 mov eax, ebx ; XREF=0x10004b673, 0x10004b679 000000010004b69c 4883C408 add rsp, 0x8 000000010004b6a0 5B pop rbx 000000010004b6a1 C9 leave 000000010004b6a2 C3 ret ; endp 000000010004b6a3 90 nop
We jump directly from 0x10004b64d
to the end of the method at 000000010004b69a
.
====== B E G I N O F P R O C E D U R E ====== ; Basic Block Input Regs: <nothing> - Killed Regs: <nothing> __ZN9PLToolbox31showDemoOrActivationIfNecessaryEv_10004b644: // PLToolbox::showDemoOrActivationIfNecessary() 000000010004b644 55 push rbp ; XREF=0x10002294c 000000010004b645 4889E5 mov rbp, rsp 000000010004b648 53 push rbx 000000010004b649 4883EC08 sub rsp, 0x8 000000010004b64d E948000000 jmp 0x10004B69A 000000010004b652 90 nop 000000010004b653 90 nop 000000010004b654 31F6 xor esi, esi 000000010004b656 E8B92E0200 call imp___symbol_stub1__onOneActivationToolboxRegister 000000010004b65b 894318 mov dword [ds:rbx+0x18], eax 000000010004b65e 3D204E0000 cmp eax, 0x4E20 000000010004b663 7410 je 0x10004B675 000000010004b665 3D224E0000 cmp eax, 0x4E22 000000010004b66a 7409 je 0x10004B675 000000010004b66c 31DB xor ebx, ebx 000000010004b66e 3D214E0000 cmp eax, 0x4E21 000000010004b673 7525 jne 0x10004B69A 000000010004b675 89C3 mov ebx, eax ; XREF=0x10004b663, 0x10004b66a 000000010004b677 85C0 test eax, eax 000000010004b679 741F je 0x10004B69A 000000010004b67b 488D0D86522300 lea rcx, qword [ds:cfstring_PLToolbox__showDemoOrActivationIfNecessary__] ; @"PLToolbox::showDemoOrActivationIfNecessary()" 000000010004b682 89C2 mov edx, eax 000000010004b684 488D0505672300 lea rax, qword [ds:_kPerfectLayersStr_100281d90] ; "" 000000010004b68b 488B30 mov rsi, qword [ds:rax] 000000010004b68e BF01000000 mov edi, 0x1 000000010004b693 31C0 xor eax, eax 000000010004b695 E842150200 call imp___symbol_stub1__ONLogWithParams ; Basic Block Input Regs: rbx - Killed Regs: rax rbx rsp 000000010004b69a 89D8 mov eax, ebx ; XREF=0x10004b64d, 0x10004b673, 0x10004b679 000000010004b69c 4883C408 add rsp, 0x8 000000010004b6a0 5B pop rbx 000000010004b6a1 C9 leave 000000010004b6a2 C3 ret ; endp 000000010004b6a3 90 nop
Each plugin uses the onOneToolbox
binary to show the demo dialog using the method showDemoDialog
. There are two onOneToolbox
binaries located at:
/Applications/Perfect Photo Suite 6/Perfect Photo Suite.app/Contents/Frameworks/onOneToolbox.framework/Versions/Current
for the suite./Library/Frameworks/onOneToolbox.framework/Versions/A/
for standalone applications.They are the same file, intuitively it is part of the framework for the application and placed in two different locations so that the suite can run in stand-alone mode by running just an individual application.
There are two types of windows that pop-up. One of them is the regular demo window (showDemoDialog
) and the other is the demo expired window (showDemoExpiredDialog
).
A simple one, following the reasoning, we follow the jump and skip over the window display. Here is the original version:
====== B E G I N O F P R O C E D U R E ====== ; Basic Block Input Regs: rdi - Killed Regs: rax r12 r13 methImpl_OTBDemoDialogController_showDemoDialog: 00000000000099d6 55 push rbp 00000000000099d7 4889E5 mov rbp, rsp 00000000000099da 4155 push r13 00000000000099dc 4154 push r12 00000000000099de 53 push rbx 00000000000099df 4883EC18 sub rsp, 0x18 00000000000099e3 4989FC mov r12, rdi 00000000000099e6 4C8D2DB3BC0000 lea r13, qword [ds:_OBJC_IVAR_$_OTBDialogController.myWindow] 00000000000099ed 498B4500 mov rax, qword [ds:r13+0x0] 00000000000099f1 48833C0700 cmp qword [ds:rdi+rax], 0x0 00000000000099f6 754F jne 0x9A47 ; Basic Block Input Regs: rdi - Killed Regs: rax 00000000000099f8 488B0591B80000 mov rax, qword [ds:_OBJC_IVAR_$_OTBDemoDialogController.mDemoOnly] 00000000000099ff 803C0700 cmp byte [ds:rdi+rax], 0x0 0000000000009a03 7413 je 0x9A18 ; Basic Block Input Regs: r12 - Killed Regs: rcx rdx rdi 0000000000009a05 488B3DF4A30000 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] 0000000000009a0c 4C89E1 mov rcx, r12 0000000000009a0f 488D1572910000 lea rdx, qword [ds:cfstring_DemoOnly] ; @"DemoOnly" 0000000000009a16 EB11 jmp 0x9A29 ; Basic Block Input Regs: r12 - Killed Regs: rcx rdx rdi 0000000000009a18 488B3DE1A30000 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] ; XREF=0x9a03 0000000000009a1f 4C89E1 mov rcx, r12 0000000000009a22 488D157F910000 lea rdx, qword [ds:cfstring_Demo] ; @"Demo" ; Basic Block Input Regs: rbx r12 r13 - Killed Regs: rax rbx rsi 0000000000009a29 488D35B89F0000 lea rsi, qword [ds:objc_msg_loadNibNamed_owner_] ; @selector(loadNibNamed:owner:) XREF=0x9a16 0000000000009a30 FF15B29F0000 call qword [ds:objc_msg_loadNibNamed_owner_] ; @selector(loadNibNamed:owner:) 0000000000009a36 498B4500 mov rax, qword [ds:r13+0x0] 0000000000009a3a 31DB xor ebx, ebx 0000000000009a3c 49833C0400 cmp qword [ds:r12+rax], 0x0 0000000000009a41 0F84C7000000 je 0x9B0E ; Basic Block Input Regs: r12 r13 - Killed Regs: rax rcx rdx rbx rbp rsi rdi 0000000000009a47 4C8965D0 mov qword [ss:rbp-0x30+var_0], r12 ; XREF=0x99f6 0000000000009a4b 488B05FEA40000 mov rax, qword [ds:0x13F50] 0000000000009a52 488945D8 mov qword [ss:rbp-0x30+var_8], rax 0000000000009a56 488D7DD0 lea rdi, qword [ss:rbp-0x30+var_0] 0000000000009a5a 488D35F7A00000 lea rsi, qword [ds:objc_msg_setupWindow] ; @selector(setupWindow)
The procedure is as follows:
nop
the first jne
since it jumps directly to the window display.0x9a41
turn the je
into a jmp
, skipping over the dialog display.The result will be:
methImpl_OTBDemoDialogController_showDemoDialog: 00000000000099d6 55 push rbp 00000000000099d7 4889E5 mov rbp, rsp 00000000000099da 4155 push r13 00000000000099dc 4154 push r12 00000000000099de 53 push rbx 00000000000099df 4883EC18 sub rsp, 0x18 00000000000099e3 4989FC mov r12, rdi 00000000000099e6 4C8D2DB3BC0000 lea r13, qword [ds:_OBJC_IVAR_$_OTBDialogController.myWindow] 00000000000099ed 498B4500 mov rax, qword [ds:r13+0x0] 00000000000099f1 48833C0700 cmp qword [ds:rdi+rax], 0x0 00000000000099f6 90 nop 00000000000099f7 90 nop 00000000000099f8 488B0591B80000 mov rax, qword [ds:_OBJC_IVAR_$_OTBDemoDialogController.mDemoOnly] 00000000000099ff 803C0700 cmp byte [ds:rdi+rax], 0x0 0000000000009a03 7413 je 0x9A18 0000000000009a05 488B3DF4A30000 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] 0000000000009a0c 4C89E1 mov rcx, r12 0000000000009a0f 488D1572910000 lea rdx, qword [ds:cfstring_DemoOnly] ; @"DemoOnly" 0000000000009a16 EB11 jmp 0x9A29 0000000000009a18 488B3DE1A30000 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] ; XREF=0x9a03 0000000000009a1f 4C89E1 mov rcx, r12 0000000000009a22 488D157F910000 lea rdx, qword [ds:cfstring_Demo] ; @"Demo" 0000000000009a29 488D35B89F0000 lea rsi, qword [ds:objc_msg_loadNibNamed_owner_] ; @selector(loadNibNamed:owner:) XREF=0x9a16 0000000000009a30 FF15B29F0000 call qword [ds:objc_msg_loadNibNamed_owner_] ; @selector(loadNibNamed:owner:) 0000000000009a36 498B4500 mov rax, qword [ds:r13+0x0] 0000000000009a3a 31DB xor ebx, ebx 0000000000009a3c 49833C0400 cmp qword [ds:r12+rax], 0x0 0000000000009a41 E9C8000000 jmp 0x9B0E
Exactly the same procedure applies to the showDemoExpiredDialog
. Fist nop
the first jne
and at 0x9d69
turn the je
into a jmp
. The final version is:
methImpl_OTBDemoExpiredDialogController_showDemoExpiredDialog: 0000000000009cf6 55 push rbp 0000000000009cf7 4889E5 mov rbp, rsp 0000000000009cfa 48895DE8 mov qword [ss:rbp+0xFFFFFFFFFFFFFFE8], rbx 0000000000009cfe 4C8965F0 mov qword [ss:rbp+0xFFFFFFFFFFFFFFF0], r12 0000000000009d02 4C896DF8 mov qword [ss:rbp+0xFFFFFFFFFFFFFFF8], r13 0000000000009d06 4883EC30 sub rsp, 0x30 0000000000009d0a 4889FB mov rbx, rdi 0000000000009d0d 4C8D2D8CB90000 lea r13, qword [ds:_OBJC_IVAR_$_OTBDialogController.myWindow] 0000000000009d14 498B4500 mov rax, qword [ds:r13+0x0] 0000000000009d18 48833C0700 cmp qword [ds:rdi+rax], 0x0 0000000000009d1d 90 nop 0000000000009d1e 90 nop 0000000000009d1f 488B05AAB60000 mov rax, qword [ds:_OBJC_IVAR_$_OTBDemoExpiredDialogController.mDemoOnly] 0000000000009d26 803C0700 cmp byte [ds:rdi+rax], 0x0 0000000000009d2a 7413 je 0x9D3F 0000000000009d2c 488B3DCDA00000 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] 0000000000009d33 4889D9 mov rcx, rbx 0000000000009d36 488D158B8E0000 lea rdx, qword [ds:cfstring_DemoOnlyExpired] ; @"DemoOnlyExpired" 0000000000009d3d EB11 jmp 0x9D50 0000000000009d3f 488B3DBAA00000 mov rdi, qword [ds:bind__OBJC_CLASS_$_NSBundle] ; XREF=0x9d2a 0000000000009d46 4889D9 mov rcx, rbx 0000000000009d49 488D15988E0000 lea rdx, qword [ds:cfstring_DemoExpired] ; @"DemoExpired" 0000000000009d50 488D35919C0000 lea rsi, qword [ds:objc_msg_loadNibNamed_owner_] ; @selector(loadNibNamed:owner:) XREF=0x9d3d 0000000000009d57 FF158B9C0000 call qword [ds:objc_msg_loadNibNamed_owner_] ; @selector(loadNibNamed:owner:) 0000000000009d5d 498B4500 mov rax, qword [ds:r13+0x0] 0000000000009d61 4531E4 xor r12d, r12d 0000000000009d64 48833C0300 cmp qword [ds:rbx+rax], 0x0 0000000000009d69 E954000000 jmp 0x9DC2
After hiding the demo window, the license window will pop up. Just hiding the license window will not cut it. We need to fiddle with the ZN17ActivationToolbox12ActivationUIE11LicenseMode_8982
method in order to remove:
0000000000008a88 E86D1F0000 call _doLicenseEntryDialog_a9fa
that is responsible for showing the license window in the first place. We do that by nop
ing the doLicenseEntryDialog
call and then turning the je 0x8D24
into a jmp
:
0000000000008a73 4C89F7 mov rdi, r14 ; XREF=0x8d04, 0x8b47 0000000000008a76 E817060000 call __ZN28ActivationToolboxStringUtils19format18DigitStringERSs_9092 ; ActivationToolboxStringUtils::format18DigitString(std::string&) 0000000000008a7b 4C89F2 mov rdx, r14 0000000000008a7e 488BB538FFFFFF mov rsi, qword [ss:rbp+0xFFFFFFFFFFFFFF38] 0000000000008a85 4C89E7 mov rdi, r12 0000000000008a88 90 nop 0000000000008a89 90 nop 0000000000008a8a 90 nop 0000000000008a8b 90 nop 0000000000008a8c 90 nop 0000000000008a8d 89C3 mov ebx, eax 0000000000008a8f 83F804 cmp eax, 0x4 0000000000008a92 E98D020000 jmp 0x8D24 0000000000008a97 90 nop
That is it.