How to protect your application against a keygen

This first article introduces a basic protection scheme based on IMSI or IMEI and how a cracker can defeat it.

Identifying a device

A mobile phone is typically identified by two unique numbers:
-  the IMEI, short for International Mobile Equipment Identity, which represents the device hardware itself. This number and its format is described in Retrieving the device IMEI code.
-  the IMSI, International Mobile Subscriber Identity which represents your subscription. The number contains two parts: the first identifies the GSM network operator with whom the subscriber has an account. The second part of the number is allocated by the network operator to identify uniquely the subscriber.

When registering your application, you can use one of these two codes - or a combination of both - to generate an lock/unlock code for your application. :
-  if you choose the IMEI, you bound the application to the device: the user will only be able to run the application on its current phone. He can change and move to another operator without problem but if he has a new phone, he will have to buy a new license.
-  if you choose the IMSI, you bound the application to the user. He can install the application on several devices but only the device he is currently using (i.e. which contains the proper SIM card) will be able to run it. This is useful when - like me - you have a single subscription but several devices.
-  you can be a lot more restrictive and use a combination of both and then lock your application on the IMSI and the IMEI.
-  you may also generate your own unique code based on other number (server generated, time based, etc....).

Note that, while there are some APIs to retrieve the IMEI on all Symbian OS phone (check Retrieving the device IMEI code), this is not always true for the IMSI (read How to retrive the IMSI number).

Let's suppose that you are a shareware author. You have written a nice little apps and decide to sell it through Handango, Symbos, or any other channel. It is very likely that you have designed a protection method based on one of these two codes, let's say the IMEI. The issue is that a cracker knows that too... And this is a first weakness in your application. As a matter of fact, with a little bit of knowledge of ARM assembly language and the proper tools, it is fairly easy to reverse-engineer part or whole of the application and identify where each system calls are made. A potential cracker will probably try to check whether you are calling MBasicGsmPhoneId::GetGsmPhoneId or PlpVariant::GetMachineIdL. The following is a real world example, based on a cracking tutorial written by a (bad!) guy called 18+2 which describes, how, from this entry point he has been able to break the protection of an application.

Finding an entry point: locating where the code is retrieved

In our example, the cracker has found that the second function was called there:

.text:10000284                 MOV     R0, SP          ; mov stack pointer in R0
.text:10000286                 BL      sub_10004B6C    ; get IMEI
.text:1000028A                 ADD     R0, R6, #0      ;
.text:1000028C                 BL      sub_100008DC    ;
.text:10000290                 ADD     R0, #0x2C       ; R0 = R0+0x2C
.text:10000292                 MOV     R1, SP          ; R1 = SP
.text:10000294                 BL      sub_10003DB0    ; copy IMEI which is in memory location SP to R0+0x2C

First the Stack Pointer is moved to R0. Than the IMEI is retrieved. We know now that the IMEI is on the Stack. After the next func the IMEI descriptor is copied to a specific memory location.

Finding the code calculation

Now we have to find the place where we enter our code and where the app calculates and compares the code. So he did a search for "ADD R0, #0x2C" because this is the place where the IMEI descriptor is copied. This instruction is used 4 times in the code. Here we have our code calc routine:

.text:1000240C                 PUSH    {R4-R7,LR}      ; Push registers
.text:1000240E                 ADD     R6, R0, #0      ; Rd = Op1 + Op2
.text:10002410                 ADD     R7, R1, #0      ; Rd = Op1 + Op2
.text:10002412                 STR     R7, [R6,#0x5C]  ; Store to Memory
.text:10002414                 MOV     R5, #0          ; Rd = Op2
.text:10002416                 MOV     R4, #0          ; Rd = Op2
.text:10002418
.text:10002418 loc_10002418                            ; CODE XREF: sub_1000240C+2Aj
.text:10002418                 ADD     R0, R6, #0      ; Rd = Op1 + Op2
.text:1000241A                 ADD     R0, #0x2C       ; Rd = Op1 + Op2
.text:1000241C                 ADD     R1, R4, #0      ; Rd = Op1 + Op2
.text:1000241E                 BL      sub_10003EF0    ; Branch with Link
.text:10002422                 LDRH    R0, [R0]        ; Load from Memory
.text:10002424                 SUB     R0, #7          ; Rd = Op1 - Op2
.text:10002426                 ADD     R2, R4, #1      ; Rd = Op1 + Op2
.text:10002428                 ADD     R1, R0, #0      ; Rd = Op1 + Op2
.text:1000242A                 MUL     R1, R2          ; Multiply
.text:1000242C                 LDR     R0, =0x16F      ; Load from Memory
.text:1000242E                 MUL     R0, R1          ; Multiply
.text:10002430                 ADD     R5, R5, R0      ; Rd = Op1 + Op2
.text:10002432                 ADD     R4, R2, #0      ; Rd = Op1 + Op2
.text:10002434                 CMP     R4, #4          ; Set cond. codes on Op1 - Op2
.text:10002436                 BLE     loc_10002418    ; Branch
.text:10002438                 MOV     R4, #0xA        ; Rd = Op2
.text:1000243A
.text:1000243A loc_1000243A                            ; CODE XREF: sub_1000240C+4Aj
.text:1000243A                 ADD     R0, R6, #0      ; Rd = Op1 + Op2
.text:1000243C                 ADD     R0, #0x2C       ; Rd = Op1 + Op2
.text:1000243E                 ADD     R1, R4, #0      ; Rd = Op1 + Op2
.text:10002440                 BL      sub_10003EF0    ; Branch with Link
.text:10002444                 LDRH    R0, [R0]        ; Load from Memory
.text:10002446                 SUB     R0, #7          ; Rd = Op1 - Op2
.text:10002448                 SUB     R1, R4, #4      ; Rd = Op1 - Op2
.text:1000244A                 MUL     R1, R0          ; Multiply
.text:1000244C                 LDR     R0, =0x16F      ; Load from Memory
.text:1000244E                 MUL     R0, R1          ; Multiply
.text:10002450                 ADD     R5, R5, R0      ; Rd = Op1 + Op2
.text:10002452                 ADD     R4, #1          ; Rd = Op1 + Op2
.text:10002454                 CMP     R4, #0xE        ; Set cond. codes on Op1 - Op2
.text:10002456                 BLE     loc_1000243A    ; Branch
.text:10002458                 LSL     R0, R5, #0x10   ; Logical Shift Left
.text:1000245A                 LSR     R0, R0, #0x10   ; Logical Shift Right
.text:1000245C                 CMP     R7, R0          ; Set cond. codes on Op1 - Op2
.text:1000245E                 BNE     loc_10002464    ; Branch
.text:10002460                 MOV     R0, #1          ; Rd = Op2
.text:10002462                 STR     R0, [R6,#0x60]  ; Store to Memory
.text:10002464
.text:10002464 loc_10002464                            ; CODE XREF: sub_1000240C+52j
.text:10002464                 POP     {R4-R7}         ; Pop registers
.text:10002466                 POP     {R0}            ; Pop registers
.text:10002468                 BX      R0              ; Branch to/from Thumb mode
.text:10002468 ; End of function sub_1000240C

This routine is called from the following code:

.text:10000D02                 LDR     R1, =0x16E8701B ; Enter reg code
.text:10000D04                 LDR     R2, [R0,#0x74]  ;
.text:10000D06                 ADD     R0, R4, #0      ;
.text:10000D08                 BL      sub_10003D20    ; Get the code
.text:10000D0C                 CMP     R0, #0          ;
.text:10000D0E                 BEQ     loc_10000D98    ; If Cancel was pressed b
.text:10000D10                 BL      sub_10003DD4    ;
.text:10000D14                 ADD     R0, R6, #0      ;
.text:10000D16                 BL      sub_100008DC    ;
.text:10000D1A                 LDR     R1, [SP]        ; Entered code moved to R1
.text:10000D1C                 BL      sub_1000240C    ; Call to code calc routine
.text:10000D20                 ADD     R0, R6, #0      ;
.text:10000D22                 BL      sub_100008DC    ;
.text:10000D26                 BL      sub_10002470    ; Load Memory location to R0 = return value of code calc func
.text:10000D2A                 ADD     R4, R0, #0      ; Put R0 -> R4
.text:10000D2C                 CMP     R4, #0          ; Compare R4 to 0
.text:10000D2E                 BNE     loc_10000D60    ; B if R4 is not 0 = reg code is right

First the resource ID is put in R1. Then a function is called to get the user input. After that the prog analyses which key was pressed. If it was Cancel it branches. If not it moves the number entered by the user in R1 (.text:10000D1A). Now the calc func is called. By looking at that function we see that the return value of the code calc func is stored to R6,#0x60 (.text:10002462). At .text:10000D26 the app loads that specific memory location to R0. Then R0 is moved to R4. And this is compared to 0. So here we have the first clue: The return value of the code calc func should not be 0! Let's search for that return value:

.text:1000245C                 CMP     R7, R0          ; compare R7 = R0
.text:1000245E                 BNE     loc_10002464    ; b if not equal
.text:10002460                 MOV     R0, #1          ; R0 = 1
.text:10002462                 STR     R0, [R6,#0x60]  ; store R0 to memory location

By looking at the calc func we can see that R7 has to be equal to R0 in order not to take the bne. Then the return value is 1 which is stored to memory! As already mentioned this is loaded after the return and compared to 0 (.text:10000D2C). Now it is interesting to find out what is in R7. Take a look at the beginning of the code

.text:10002410                 ADD     R7, R1, #0      ; Rd = Op1 + Op2

R1 is moved to R7. So what is in R1??? Before the calc func is called the content of SP is moved to R1:

.text:10000D1A                 LDR     R1, [SP]        ; Entered code moved to R1

Now we know that the entered code should be equal to the code that is calculated by the app. This calculated code has to be entered by the user. We just have to calc it for ourself...

Cracking the code

Here is the first part of the calc func:

.text:10002414                 MOV     R5, #0          ; R5 = 0
.text:10002416                 MOV     R4, #0          ; R4 = 0
.text:10002418
.text:10002418 loc_10002418                            ; CODE XREF: sub_1000240C+2Aj
.text:10002418                 ADD     R0, R6, #0      ;
.text:1000241A                 ADD     R0, #0x2C       ; R0 = Pointer to IMEI descriptor
.text:1000241C                 ADD     R1, R4, #0      ; R1 = R4 (which number from IMEI descriptor)
.text:1000241E                 BL      sub_10003EF0    ; Get ASCII value
.text:10002422                 LDRH    R0, [R0]        ; load value in R0
.text:10002424                 SUB     R0, #7          ; R0 = R0 - 7
.text:10002426                 ADD     R2, R4, #1      ; R2 = R4 + 1
.text:10002428                 ADD     R1, R0, #0      ; R1 = R0
.text:1000242A                 MUL     R1, R2          ; R1 = R2 * R1
.text:1000242C                 LDR     R0, =0x16F      ; R0 = 0x16F
.text:1000242E                 MUL     R0, R1          ; R0 = R1 * R0
.text:10002430                 ADD     R5, R5, R0      ; R5 = R5 + R0
.text:10002432                 ADD     R4, R2, #0      ; R4 = R2
.text:10002434                 CMP     R4, #4          ; is R4 = 4
.text:10002436                 BLE     loc_10002418    ; b if R4 is less or equal 4

So what is done here? First R5, R4 are set to 0. Then a pointer to the IMEI descriptor is moved in R0. R1 is the number in the IMEI. So at the first run of this loop R4=R1 is 0. So the app starts to get the ASCII value of the first number in the IMEI. If that is maybe 3 the value in R0 after the LDRH instruction would be 0x33 (which is the ASCII code for 3). From this value 7 is substracted. Which gives for our first run 0x2C in R0 after SUB. Then 1 is added to R4 and the result put in R2 (R2 is now 1). R0 is then moved (ADD) to R1. Now both are multiplied. Now it is easy to guess what the result would be: R2 = 1 * R1 = 0x2C. That would be 0x2C in R1. Then R1 will be multiplied with 0x16F in R0. Would make 0x3F14 which will be added to R5. Finally R2 is moved (ADD) to R4 (which is now 1) and compared to 4. Since R4 is now 1 the BLE is taken and we start again from .text:10002418. But now the second number of the IMEI is retrieved (R4 = 1). This is maybe 5. After the LDRH which value will be in R0? Of course, 0x35! Then we have the -7 and the multiply. But look out now: R2 contains now 2 since it adds 1 to R4. So (0x35-7) * 2 would be 0x5C. We see that a single number from the IMEI is multiplied with it's position in the IMEI. After the * with 0x16F it is added to R5 (.text:10002430). But in R5 is the value from the last loop. So you see all values are added up to 1 special value! After our second run this value should be 0xC2F8. Here is the code in plain c: code = ((ASCIIvalueofIMEI - 7) * PositionofNumberinIMEI) * 0x16F;

Then all codes are added... Ok! Now we have covered the first loop. Plz msg me and tell me the code after this loop when using 12345 and 00000 as the IMEI's first values.

After R4 is 5 the BLE is not taken and we continue here:

.text:10002438                 MOV     R4, #0xA        ; R4 = 0xA
.text:1000243A
.text:1000243A loc_1000243A                            ; CODE XREF: sub_1000240C+4Aj
.text:1000243A                 ADD     R0, R6, #0      ;
.text:1000243C                 ADD     R0, #0x2C       ; R0 = Pointer to IMEI descriptor
.text:1000243E                 ADD     R1, R4, #0      ; R1 = R4 (which number from IMEI descriptor)
.text:10002440                 BL      sub_10003EF0    ; Get ASCII value
.text:10002444                 LDRH    R0, [R0]        ; load value in R0
.text:10002446                 SUB     R0, #7          ; R0 = R0 - 7
.text:10002448                 SUB     R1, R4, #4      ; R1 = R4 - 4
.text:1000244A                 MUL     R1, R0          ; R1 = R0 * R1
.text:1000244C                 LDR     R0, =0x16F      ; R0 = 0x16F
.text:1000244E                 MUL     R0, R1          ; R0 = R1 * R0
.text:10002450                 ADD     R5, R5, R0      ; R5 = R5 + R0
.text:10002452                 ADD     R4, #1          ; Rd = Op1 + Op2
.text:10002454                 CMP     R4, #0xE        ; is R4 = 0xE
.text:10002456                 BLE     loc_1000243A    ; b if R4 is less or equal 0xE

You can see that 0xA is moved in R4. So some numbers of the IMEI are not included in calculation of the final code and that would be number 5 till 9. So basically the same is done in this loop. Get ASCII value from the IMEI number at a specific position. Substract 7. But here (text:10002448) is some difference: SUB R1, R4, #4. So 4 is substracted from R4 and put in R1. For the first run in this loop R4 is 0xA. And then R1 would be 0xA-4 = 0x6. So the app does edit the position number! Then we continue as normal by multiply and adding the result to R5. This loop is executed until we have reached the end of the IMEI that is the 0xE position (we started with 0x0 which counts as first position, 0x0 - 0xE = 0xF or 15 positions, that is the length of the IMEI) So in c code:

        code = ((ASCIIvalueofIMEI - 7) * PositionofNumberinIMEI - 4) * 0x16F;

And finally codes are added together. Last we have the final step in the calculation

.text:10002458                 LSL     R0, R5, #0x10   ; Logical Shift Left
.text:1000245A                 LSR     R0, R0, #0x10   ; Logical Shift Right

R5 is shifted left 0x10 times which just means it is multiplied by 65536 (R0 = R5 * 65536). Then it is devided by 65536 (shifted back, R0 = R0 / 65536). Now we have the final code in R0 which is compared to the user input in R7 (.text:1000245C)... Writing the proper keygen is now easy as the algorithm is known.

Conclusion

As you can see, designing a good protection scheme is not as simple as it looks and is definitely not as simple as writing a good ciphering code. You need to hide as much as possible your treatment and multiply the checks (and then the roads to explore for the cracker). Is the work worth it ? Is not a good freeware better than a badly protected shareware that will not sell ? You only have the answer....

Warning: installing a cracked application is always a potential risk for your device (ex. the Skulls trojan)...

> How to crack a symbian application: writing a keygen

Hi, Which disassembler produces this type of ARM asm listing? Asking this as haven't still found a ARM free disassembler. Thanks.
— Mayur.

> How to crack a symbian application: writing a keygen

This is a well known disassembler (which is not free). I would prefer not to comment on tools required to achieve this as the goal is not to help people cracking software....

> How to crack a symbian application: writing a keygen

How was the function that gets the IMEI identified as such? Are there system breakpoints or so possible? Or will the disassembler realise such things (it hasn't in my experience so far)?

> How to crack a symbian application: writing a keygen

It is a little bit more work than just using the default function of a dissasembler. However, just consider that any system call in your code can be found and identified as such. So try clever things to complicate the life of the cracker (multiply the calls and the indirection, base your protection on application data, ...)

> How to crack a symbian application: writing a keygen

Right, but as a developer, you want to check your own code. So now only the crackers have the good tools and the developers are left to guess :-)

> How to crack a symbian application: writing a keygen

Yeah, good point. What is the app?

> How to crack a symbian application: writing a keygen

IMHO if you dont know any tool that you could use for checking your app i mean here disassembling this tools are not for you.

> How to crack a symbian application: writing a keygen

Huh.. it is not a secret (anyone can find an ARM disassembler using google anyway) . I personally prefer IDA Pro (http://www.datarescue.com/idabase/).

> How to crack a symbian application: writing a keygen

Really, actually I've disassembled lots. IDA is the one I know and costs money. Is there a better one that is freely available and not $600 that the hackers have that us developers do not have?

> How to crack a symbian application: writing a keygen

What makes you think that the hackers pay for it? :-)

> How to crack a symbian application: writing a keygen

Lol Eric copy hacker tutorial... It's not his document, it's from K**S

:-)

> How to crack a symbian application: writing a keygen

I think he has quoted this in the article. Hasn't he ?

> How to crack a symbian application: writing a keygen

Yes, the last sentence in the first block of text is quite clear that someone else wrote the body of the article itself.

Eric is usually VERY careful about that kind of stuff.

> How to crack a symbian application: writing a keygen

My Opinion: Software is like sex, it's better when it's free!

> How to crack a symbian application: writing a keygen

Sex is free? Hmm. Yeah, and when you go to bars, the beers are free? :-)