My name is Kenneth and I write cool Mac and iPhone software. This is my personal weblog where I post about stuff I find interesting. I usually write about Mac development, the business of shareware and the Mac community in general.
read more →Update: I rewrote this post with better language and better explanations. Please read the newer version first.
In this post, I will describe how to hack a mac shareware app.
The reason for this is to push the developers to create stronger protection, and to show common weaknesses in licensing code.
Before each hack is published, I get the concerned developer’s approval, and send them a note describing the hack, and suggesting ways to improve their protection. I also leave them some time to patch their app before I publish the hack.
I have no intention of promoting piracy, and this not meant to be used as a guide for would-be pirates to get those for free. What follows if of highly technical nature, and is intended for fellow developers.
Today is up: Direct Mail, a great app from e3 software useful for anyone doing mailing lists or press releases.
I did this hack on version 1.8.3, because it isn’t the latest.
First thing to do, is to class-dump the executable, which results in an interesting find:
@end
Now, there’s different ways to hack this. You could just edit the ivar _registered upon launch. Other option would be to hack registered or isValidCode:forName: to always return true.
I’m going to go with a slightly more complicated way, and hack the methods which call isValidCode:forName:.
Let’s set a breakpoint when isValidCode is called. Disassemble the whole thing, and check where isValidCode:forName: is called. For me, it returns at 0x00008fdc. Run. It hits the breakpoint immediately. Continue once, to let it finish its setting up etc. Now, using the app, go to the register menu and try to register (with a properly-formatted email-address). It will hit the breakpoint again. Do a nexti. You now are in “0x0002ffb7 in -[CRegisterPanelController doOK:] ()”
Let’s disassemble this method:
0x0002ff50 <-[CRegisterPanelController doOK:]+0>: push %ebp
0x0002ff51 <-[CRegisterPanelController doOK:]+1>: mov %esp,%ebp
0x0002ff53 <-[CRegisterPanelController doOK:]+3>: push %edi
0x0002ff54 <-[CRegisterPanelController doOK:]+4>: push %esi
0x0002ff55 <-[CRegisterPanelController doOK:]+5>: push %ebx
0x0002ff56 <-[CRegisterPanelController doOK:]+6>: sub $0x1c,%esp
0x0002ff59 <-[CRegisterPanelController doOK:]+9>: mov 0x8(%ebp),%edi
0x0002ff5c <-[CRegisterPanelController doOK:]+12>: mov 0x22091c,%eax
0x0002ff61 <-[CRegisterPanelController doOK:]+17>: mov %eax,0x4(%esp)
0x0002ff65 <-[CRegisterPanelController doOK:]+21>: mov 0x221fa4,%eax
0x0002ff6a <-[CRegisterPanelController doOK:]+26>: mov %eax,(%esp)
0x0002ff6d <-[CRegisterPanelController doOK:]+29>: call 0x21f395 <dyld_stub_objc_msgSend>
0x0002ff72 <-[CRegisterPanelController doOK:]+34>: mov %eax,%esi
0x0002ff74 <-[CRegisterPanelController doOK:]+36>: mov 0xc(%edi),%edx
0x0002ff77 <-[CRegisterPanelController doOK:]+39>: mov 0x221428,%eax
0x0002ff7c <-[CRegisterPanelController doOK:]+44>: mov %eax,0x4(%esp)
0x0002ff80 <-[CRegisterPanelController doOK:]+48>: mov %edx,(%esp)
0x0002ff83 <-[CRegisterPanelController doOK:]+51>: call 0x21f395 <dyld_stub_objc_msgSend>
0x0002ff88 <-[CRegisterPanelController doOK:]+56>: mov %eax,%ebx
0x0002ff8a <-[CRegisterPanelController doOK:]+58>: mov 0x8(%edi),%edx
0x0002ff8d <-[CRegisterPanelController doOK:]+61>: mov 0x221428,%eax
0x0002ff92 <-[CRegisterPanelController doOK:]+66>: mov %eax,0x4(%esp)
0x0002ff96 <-[CRegisterPanelController doOK:]+70>: mov %edx,(%esp)
0x0002ff99 <-[CRegisterPanelController doOK:]+73>: call 0x21f395 <dyld_stub_objc_msgSend>
0x0002ff9e <-[CRegisterPanelController doOK:]+78>: mov %ebx,0xc(%esp)
0x0002ffa2 <-[CRegisterPanelController doOK:]+82>: mov %eax,0x8(%esp)
0x0002ffa6 <-[CRegisterPanelController doOK:]+86>: mov 0x22084c,%eax
0x0002ffab <-[CRegisterPanelController doOK:]+91>: mov %eax,0x4(%esp)
0x0002ffaf <-[CRegisterPanelController doOK:]+95>: mov %esi,(%esp)
0x0002ffb2 <-[CRegisterPanelController doOK:]+98>: call 0x21f395 <dyld_stub_objc_msgSend>
0x0002ffb7 <-[CRegisterPanelController doOK:]+103>: test %al,%al
0x0002ffb9 <-[CRegisterPanelController doOK:]+105>: jne 0x2ffc7 <-[CRegisterPanelController doOK:]+119>
0x0002ffbb <-[CRegisterPanelController doOK:]+107>: add $0x1c,%esp
0x0002ffbe <-[CRegisterPanelController doOK:]+110>: pop %ebx
0x0002ffbf <-[CRegisterPanelController doOK:]+111>: pop %esi
0x0002ffc0 <-[CRegisterPanelController doOK:]+112>: pop %edi
0x0002ffc1 <-[CRegisterPanelController doOK:]+113>: pop %ebp
0x0002ffc2 <-[CRegisterPanelController doOK:]+114>: jmp 0x21f444 <dyld_stub_NSBeep>
0x0002ffc7 <-[CRegisterPanelController doOK:]+119>: mov 0x22091c,%eax
0x0002ffcc <-[CRegisterPanelController doOK:]+124>: mov %eax,0x4(%esp)
0x0002ffd0 <-[CRegisterPanelController doOK:]+128>: mov 0x221fa4,%eax
0x0002ffd5 <-[CRegisterPanelController doOK:]+133>: mov %eax,(%esp)
0x0002ffd8 <-[CRegisterPanelController doOK:]+136>: call 0x21f395 <dyld_stub_objc_msgSend>
0x0002ffdd <-[CRegisterPanelController doOK:]+141>: mov %eax,%esi
0x0002ffdf <-[CRegisterPanelController doOK:]+143>: mov 0xc(%edi),%edx
0x0002ffe2 <-[CRegisterPanelController doOK:]+146>: mov 0x221428,%eax
0x0002ffe7 <-[CRegisterPanelController doOK:]+151>: mov %eax,0x4(%esp)
0x0002ffeb <-[CRegisterPanelController doOK:]+155>: mov %edx,(%esp)
0x0002ffee <-[CRegisterPanelController doOK:]+158>: call 0x21f395 <dyld_stub_objc_msgSend>
0x0002fff3 <-[CRegisterPanelController doOK:]+163>: mov %eax,%ebx
0x0002fff5 <-[CRegisterPanelController doOK:]+165>: mov 0x8(%edi),%edx
0x0002fff8 <-[CRegisterPanelController doOK:]+168>: mov 0x221428,%eax
0x0002fffd <-[CRegisterPanelController doOK:]+173>: mov %eax,0x4(%esp)
0x00030001 <-[CRegisterPanelController doOK:]+177>: mov %edx,(%esp)
0x00030004 <-[CRegisterPanelController doOK:]+180>: call 0x21f395 <dyld_stub_objc_msgSend>
0x00030009 <-[CRegisterPanelController doOK:]+185>: mov %ebx,0xc(%esp)
0x0003000d <-[CRegisterPanelController doOK:]+189>: mov %eax,0x8(%esp)
0x00030011 <-[CRegisterPanelController doOK:]+193>: mov 0x220788,%eax
0x00030016 <-[CRegisterPanelController doOK:]+198>: mov %eax,0x4(%esp)
0x0003001a <-[CRegisterPanelController doOK:]+202>: mov %esi,(%esp)
0x0003001d <-[CRegisterPanelController doOK:]+205>: call 0x21f395 <dyld_stub_objc_msgSend>
0x00030022 <-[CRegisterPanelController doOK:]+210>: movl $0x219530,0x8(%esp)
0x0003002a <-[CRegisterPanelController doOK:]+218>: mov 0x220784,%eax
0x0003002f <-[CRegisterPanelController doOK:]+223>: mov %eax,0x4(%esp)
0x00030033 <-[CRegisterPanelController doOK:]+227>: mov 0x221fb4,%eax
0x00030038 <-[CRegisterPanelController doOK:]+232>: mov %eax,(%esp)
0x0003003b <-[CRegisterPanelController doOK:]+235>: call 0x21f395 <dyld_stub_objc_msgSend>
0x00030040 <-[CRegisterPanelController doOK:]+240>: mov 0x220780,%edx
0x00030046 <-[CRegisterPanelController doOK:]+246>: mov %edx,0x4(%esp)
0x0003004a <-[CRegisterPanelController doOK:]+250>: mov %eax,(%esp)
0x0003004d <-[CRegisterPanelController doOK:]+253>: call 0x21f395 <dyld_stub_objc_msgSend>
0x00030052 <-[CRegisterPanelController doOK:]+258>: mov 0x4(%edi),%edx
0x00030055 <-[CRegisterPanelController doOK:]+261>: mov 0x220900,%eax
0x0003005a <-[CRegisterPanelController doOK:]+266>: mov %eax,0xc(%ebp)
0x0003005d <-[CRegisterPanelController doOK:]+269>: mov %edx,0x8(%ebp)
0x00030060 <-[CRegisterPanelController doOK:]+272>: add $0x1c,%esp
0x00030063 <-[CRegisterPanelController doOK:]+275>: pop %ebx
0x00030064 <-[CRegisterPanelController doOK:]+276>: pop %esi
0x00030065 <-[CRegisterPanelController doOK:]+277>: pop %edi
0x00030066 <-[CRegisterPanelController doOK:]+278>: pop %ebp
0x00030067 <-[CRegisterPanelController doOK:]+279>: jmp 0x21f395 <dyld_stub_objc_msgSend>
We now are at this line: “0x0002ffb7 <-[CRegisterPanelController doOK:]+103>: test %al,%al”
That, in the right, is assembly code. This is basically some kind of “if” statement. (a TEST followed by a JNE (jump if not equal)).
What interests me is the next line: “0x0002ffb9 <-[CRegisterPanelController doOK:]+105>: jne 0x2ffc7 <-[CRegisterPanelController doOK:]+119>”
If we just reverse this test (turn the JNE into a JE (jump if equal)), any invalid code will be considered valid, and vice-versa. Let’s examine the memory for this statement.
(gdb) x/x 0x0002ffb9
0x2ffb9 <-[CRegisterPanelController doOK:]+105>: 0xc4830c75
Now, I’m working on a intel machine. And for some dumb reason, every block of four bytes is inverted. What this means, is that the byte that interests me is the rightmost one: 0x75. This is what a JNE looks like. Do some more tests by setting breakpoints until you find a JE, and read the memory for it: you will find that a JE is 0x74.
Let’s test if our theory is correct by editing the memory live, before we edit it in the binary. Do the following:
(gdb) set {char}0x0002ffb9=0x74
(gdb) x/x 0x0002ffb9
0x2ffb9 <-[CRegisterPanelController doOK:]+105>: 0xc4830c74
(gdb) disassemble 0x0002ffb7
Dump of assembler code for function -[CRegisterPanelController doOK:]:
[...edited out...]
0x0002ff99 <-[CRegisterPanelController doOK:]+73>: call 0x21f395
0x0002ff9e <-[CRegisterPanelController doOK:]+78>: mov %ebx,0xc(%esp)
0x0002ffa2 <-[CRegisterPanelController doOK:]+82>: mov %eax,0x8(%esp)
0x0002ffa6 <-[CRegisterPanelController doOK:]+86>: mov 0x22084c,%eax
0x0002ffab <-[CRegisterPanelController doOK:]+91>: mov %eax,0x4(%esp)
0x0002ffaf <-[CRegisterPanelController doOK:]+95>: mov %esi,(%esp)
0x0002ffb2 <-[CRegisterPanelController doOK:]+98>: call 0x21f395
0x0002ffb7 <-[CRegisterPanelController doOK:]+103>: test %al,%al
0x0002ffb9 <-[CRegisterPanelController doOK:]+105>: je 0x2ffc7 <-[CRegisterPanelController doOK:]+119>
0x0002ffbb <-[CRegisterPanelController doOK:]+107>: add $0x1c,%esp
0x0002ffbe <-[CRegisterPanelController doOK:]+110>: pop %ebx
0x0002ffbf <-[CRegisterPanelController doOK:]+111>: pop %esi
0x0002ffc0 <-[CRegisterPanelController doOK:]+112>: pop %edi
0x0002ffc1 <-[CRegisterPanelController doOK:]+113>: pop %ebp
0x0002ffc2 <-[CRegisterPanelController doOK:]+114>: jmp 0x21f444
0x0002ffc7 <-[CRegisterPanelController doOK:]+119>: mov 0x22091c,%eax
[...edited out...]
End of assembler dump.
(gdb)
Here we change the byte for the JNE, then test if we changed it correctly by re-reading it.
Then we disassemble the whole method again to see if the JNE was changed correctly. And yes — tah-da — - it now says JE. Perfect. Continue. You are now registered. To make this change permanent: do “x/8x 0x0002ffb9”. You will get 24 bytes of data. Open the binary in your favorite hex editor and find the bytes outputted by gdb. If you’re on intel, don’t forget you have to reverse all the blocks of four bytes before searching. When you find it, edit the 0x75 into 0x74. Bravo! You have now made the change permanent.
We are not finished yet. As you will now notice if you run the program, it makes your code valid, but you get an error message each launch, and you have to re-do the entering a code process every time. This is because the first check at launch doesn’t happen in doOk:.
Continue and quit normally (using cmd-Q in Direct Mail). Launch it again by doing run. But this time, don’t continue after hitting the breakpoint at launch. Do nexti. Ok, so now we are in loadRegistration. Disassemble this method. Similarly, there’s a JE this time, just after the call to isValidCode:forName: Turn this into a JNE by changing the 0x74 into a 0x75 at this location in memory. If you disassemble the method again, you can see that the JE turned into a JNE. Like before, change this in the binary.
Well Done! You have now fully hacked Direct Mail 1.8.3.
By now, the developer has probably fixed this security flaw, so you can’t use this to get this app for free. If you like it, buy it. It’s a great piece of software! Think about the poor developers who have to feed their family.
You can download the trial version here
This entry was posted on Wednesday, December 19th, 2007 at 1:02 am and is filed under Apple, Cocoa, English, Hacking. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Add your thoughts!
When you say, “If you’re on intel, don’t forget you have to reverse all the blocks of four bytes before searching”, I assume you mean that when you disassemble the PPC code, which should be different (e.g. you’re not dealing with 0x75/0x74 at all, but something completely different), that whatever it is that you’ll get from gdb you do not reverse?
Yeah: this is due to Intel being Little-endian, while PPCs are Big-endian. See http://en.wikipedia.org/wiki/Endianness#Little-endian for a more detailed explanation.
Well yes that makes sense, but I’m still a little confused… specifically when you said: “If you’re on intel, don’t forget you have to reverse all the blocks of four bytes before searching.”
In your example gdb gave you 0xc4830c75 for the instruction (right?), based on my somewhat limited knowledge I’m guessing that the 0x75 is the opcode that you want to change. So what is the value that’s searched for in the binary? Does gdb always spit out big endian instructions…?
From my limited understanding of it yes. Basically, if you’re on intel, you’d read this in gdb: 0xc4830c75 and you’d have to search for this in the binary: 0x750c83c4. On PPC you’ll search for whatever you get in gdb. Note I don’t really know the full theory behind it, I just apply it in practice and this is what works for me.
Thanks! I find this all fascinating and wish I had more time to study it (don’t worry, not because I want to steal any money from poor independent developers). Great post!
Aren’t the “reverse” bytes actually in that format on the Intel because of the way variables are stored in memory? Isn’t it supposed to be that it reads them backwards in assembly code?
e.g. 1234h : 34h 12h. and 0000:0010 translates into 0001:0000.
Cause I am pretty sure that’s how the memory is read in assembly.
Have something to say?