if an app requires an unlocker key app, it's likely there will be protection hidden in the key. perhaps the key performs a checksum on the main app, or the key stores pre-computed checksum values for the main app. this is easy to add if you're a developer and is somewhat tricky to handle as a cracker. the reason is the cracker must know how to calculate the checksums himself and inject those values into the app.
calculating checksums is a way to determine if your apk has been modified. there are at least four easy methods to do this. they are: md5, sha1, crc32 and adler32. once you run these guys on your apk you will have either a long number or a byte array. of course, you can't store the checksum in your main app since you wont know the checksum until the app is finished. for this reason, either the calculations or just the checksum values must be stored in another app signed by the same certificate, such as a key / unlocker app.
here's some example code of what the protection may look like in java:
and here's what the above java looks like in smali, so you have some idea what to look for. cracking this will require you to write an app that simulates how the app calculates the checksum. then you'll have to come up with a clever way to get the value into the smali. if it's a long number, this is fairly easy, but if it's a byte array, you may need to use base64 encoding or some other method to make a byte array safe for literal strings. the keywords to look for, of course, are md5, sha1, crc32 and adler32. they may not appear at all if the developer is using reflection to make the method calls.
calculating checksums is a way to determine if your apk has been modified. there are at least four easy methods to do this. they are: md5, sha1, crc32 and adler32. once you run these guys on your apk you will have either a long number or a byte array. of course, you can't store the checksum in your main app since you wont know the checksum until the app is finished. for this reason, either the calculations or just the checksum values must be stored in another app signed by the same certificate, such as a key / unlocker app.
here's some example code of what the protection may look like in java:
// Copied directly and made private from R.java of Key App // After creating strings.. private static final int tt_crc32=0x7f040002; private static final int tt_md5=0x7f040004; public static TestResult checkCRC32ChkSum() { // Get path to our apk on the system. String apkPath = Main.MyContext.getPackageCodePath(); Long chksum = null; try { // Open the file and build a CRC32 checksum. // You could also use Adler32 in place of CRC32. FileInputStream fis = new FileInputStream(new File(apkPath)); CRC32 chk = new CRC32(); CheckedInputStream cis = new CheckedInputStream(fis, chk); byte[] buff = new byte[80]; while ( cis.read(buff) >= 0 ) ; chksum = chk.getValue(); } catch (Exception e) { e.printStackTrace(); } // After creating your apk, calculate the crc32 checksum // and store it as a string value in the KEY / unlock App. String keyStrVal = getKeyString(tt_crc32); // Key is not installed. if ( keyStrVal.equals("") ) { // Key not installed. Validation failure. } Long chksumCmp = Long.parseLong(keyStrVal); if ( chksum.equals(chksumCmp) ) { // Success. Checksum stored in key matches the // checksum we just created. We can assume APK // is not modified. Kinda. } else { // Validation failure! Apk has been modified. } } public static TestResult checkMD5ChkSum() { // Do pretty much the exact same thing here, // except instead of a CRC32 checksum, we'll be // using an MD5 digest. You could also use SHA1. // Any cracker worth his salt will immediately recognize // CRC32 and MD5 keywords and know them to be checksum // attempts. Using Adler32 or SHA1 may put them off. String apkPath = Main.MyContext.getPackageCodePath(); MessageDigest msgDigest = null; byte[] digest = null; try { msgDigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } byte[] bytes = new byte[8192]; int byteCount; FileInputStream fis = null; try { fis = new FileInputStream(new File(apkPath)); while ((byteCount = fis.read(bytes)) > 0) msgDigest.update(bytes, 0, byteCount); digest = msgDigest.digest(); } catch (Exception e) { e.printStackTrace(); } String keyStrVal = getKeyString(tt_md5); // Key is not installed. if ( keyStrVal.equals("") ) { // Key not installed. Validation failure. } // Using Base64 encoding is just a lazy way to store byte arrays. // You -could- also embed the values in the code of the Apk // Read more here: // http://stackoverflow.com/questions/2721386/android-how-to-share-code-between-projects-signed-with-the-same-certificate if ( Arrays.equals(Base64.decode(keyStrVal, Base64.DEFAULT), digest) ) // Validated else // Apk has been modified } private static String getKeyString(int resId) { // You will need this to retrieve the stored checksums from the KEY App. String result = ""; try { Context c = Main.MyContext.createPackageContext("your.package.name.key", Context.CONTEXT_IGNORE_SECURITY); result = c.getString(resId); } catch (Exception e) { Console.log("Error while getting key string:\n" + e); e.printStackTrace(); result = ""; } return result; }
and here's what the above java looks like in smali, so you have some idea what to look for. cracking this will require you to write an app that simulates how the app calculates the checksum. then you'll have to come up with a clever way to get the value into the smali. if it's a long number, this is fairly easy, but if it's a byte array, you may need to use base64 encoding or some other method to make a byte array safe for literal strings. the keywords to look for, of course, are md5, sha1, crc32 and adler32. they may not appear at all if the developer is using reflection to make the method calls.
.method public static checkCRC32ChkSum()V .locals 11 .prologue .line 934 sget-object v9, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context; invoke-virtual {v9}, Landroid/content/Context;->getPackageCodePath()Ljava/lang/String; move-result-object v0 .line 935 .local v0, apkPath:Ljava/lang/String; const/4 v3, 0x0 .line 939 .local v3, chksum:Ljava/lang/Long; :try_start_0 new-instance v7, Ljava/io/FileInputStream; new-instance v9, Ljava/io/File; invoke-direct {v9, v0}, Ljava/io/File;->(Ljava/lang/String;)V invoke-direct {v7, v9}, Ljava/io/FileInputStream;-> (Ljava/io/File;)V .line 940 .local v7, fis:Ljava/io/FileInputStream; new-instance v2, Ljava/util/zip/CRC32; invoke-direct {v2}, Ljava/util/zip/CRC32;-> ()V .line 942 .local v2, chk:Ljava/util/zip/CRC32; new-instance v5, Ljava/util/zip/CheckedInputStream; invoke-direct {v5, v7, v2}, Ljava/util/zip/CheckedInputStream;-> (Ljava/io/InputStream;Ljava/util/zip/Checksum;)V .line 943 .local v5, cis:Ljava/util/zip/CheckedInputStream; const/16 v9, 0x50 new-array v1, v9, [B .line 944 .local v1, buff:[B :cond_0 invoke-virtual {v5, v1}, Ljava/util/zip/CheckedInputStream;->read([B)I move-result v9 if-gez v9, :cond_0 .line 946 invoke-virtual {v2}, Ljava/util/zip/CRC32;->getValue()J move-result-wide v9 invoke-static {v9, v10}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long; :try_end_0 .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 move-result-object v3 .line 953 .end local v1 #buff:[B .end local v2 #chk:Ljava/util/zip/CRC32; .end local v5 #cis:Ljava/util/zip/CheckedInputStream; .end local v7 #fis:Ljava/io/FileInputStream; :goto_0 const v9, 0x7f040002 invoke-static {v9}, Lcom/lohan/testtarget/PerformTestsTask;->getKeyString(I)Ljava/lang/String; move-result-object v8 .line 956 .local v8, keyStrVal:Ljava/lang/String; const-string v9, "" invoke-virtual {v8, v9}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z .line 960 invoke-static {v8}, Ljava/lang/Long;->parseLong(Ljava/lang/String;)J move-result-wide v9 invoke-static {v9, v10}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long; move-result-object v4 .line 962 .local v4, chksumCmp:Ljava/lang/Long; invoke-virtual {v3, v4}, Ljava/lang/Long;->equals(Ljava/lang/Object;)Z .line 970 return-void .line 947 .end local v4 #chksumCmp:Ljava/lang/Long; .end local v8 #keyStrVal:Ljava/lang/String; :catch_0 move-exception v9 move-object v6, v9 .line 948 .local v6, e:Ljava/lang/Exception; invoke-virtual {v6}, Ljava/lang/Exception;->printStackTrace()V goto :goto_0 .end method .method public static checkMD5ChkSum()V .locals 12 .prologue const/4 v11, 0x0 .line 981 sget-object v10, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context; invoke-virtual {v10}, Landroid/content/Context;->getPackageCodePath()Ljava/lang/String; move-result-object v0 .line 982 .local v0, apkPath:Ljava/lang/String; const/4 v9, 0x0 .line 983 .local v9, msgDigest:Ljava/security/MessageDigest; const/4 v3, 0x0 check-cast v3, [B .line 985 .local v3, digest:[B :try_start_0 const-string v10, "MD5" invoke-static {v10}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest; :try_end_0 .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0 move-result-object v9 .line 990 :goto_0 const/16 v10, 0x2000 new-array v2, v10, [B .line 992 .local v2, bytes:[B const/4 v6, 0x0 .line 995 .local v6, fis:Ljava/io/FileInputStream; :try_start_1 new-instance v7, Ljava/io/FileInputStream; new-instance v10, Ljava/io/File; invoke-direct {v10, v0}, Ljava/io/File;-> (Ljava/lang/String;)V invoke-direct {v7, v10}, Ljava/io/FileInputStream;-> (Ljava/io/File;)V :try_end_1 .catch Ljava/lang/Exception; {:try_start_1 .. :try_end_1} :catch_2 .line 997 .end local v6 #fis:Ljava/io/FileInputStream; .local v7, fis:Ljava/io/FileInputStream; :goto_1 :try_start_2 invoke-virtual {v7, v2}, Ljava/io/FileInputStream;->read([B)I move-result v1 .local v1, byteCount:I if-gtz v1, :cond_0 .line 1000 invoke-virtual {v9}, Ljava/security/MessageDigest;->digest()[B :try_end_2 .catch Ljava/lang/Exception; {:try_start_2 .. :try_end_2} :catch_1 move-result-object v3 move-object v6, v7 .line 1005 .end local v1 #byteCount:I .end local v7 #fis:Ljava/io/FileInputStream; .restart local v6 #fis:Ljava/io/FileInputStream; :goto_2 const v10, 0x7f040004 invoke-static {v10}, Lcom/lohan/testtarget/PerformTestsTask;->getKeyString(I)Ljava/lang/String; move-result-object v8 .line 1007 .local v8, keyStrVal:Ljava/lang/String; const-string v10, "" invoke-virtual {v8, v10}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z .line 1015 invoke-static {v8, v11}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B move-result-object v10 invoke-static {v10, v3}, Ljava/util/Arrays;->equals([B[B)Z .line 1020 return-void .line 986 .end local v2 #bytes:[B .end local v6 #fis:Ljava/io/FileInputStream; .end local v8 #keyStrVal:Ljava/lang/String; :catch_0 move-exception v10 move-object v5, v10 .line 987 .local v5, e1:Ljava/security/NoSuchAlgorithmException; invoke-virtual {v5}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V goto :goto_0 .line 998 .end local v5 #e1:Ljava/security/NoSuchAlgorithmException; .restart local v1 #byteCount:I .restart local v2 #bytes:[B .restart local v7 #fis:Ljava/io/FileInputStream; :cond_0 const/4 v10, 0x0 :try_start_3 invoke-virtual {v9, v2, v10, v1}, Ljava/security/MessageDigest;->update([BII)V :try_end_3 .catch Ljava/lang/Exception; {:try_start_3 .. :try_end_3} :catch_1 goto :goto_1 .line 1001 .end local v1 #byteCount:I :catch_1 move-exception v10 move-object v4, v10 move-object v6, v7 .line 1002 .end local v7 #fis:Ljava/io/FileInputStream; .local v4, e:Ljava/lang/Exception; .restart local v6 #fis:Ljava/io/FileInputStream; :goto_3 invoke-virtual {v4}, Ljava/lang/Exception;->printStackTrace()V goto :goto_2 .line 1001 .end local v4 #e:Ljava/lang/Exception; :catch_2 move-exception v10 move-object v4, v10 goto :goto_3 .end method .method private static asdfgetKeyString(I)Ljava/lang/String; .locals 6 .parameter "resId" .prologue .line 1025 const-string v2, "" .line 1028 .local v2, result:Ljava/lang/String; :try_start_0 sget-object v3, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context; const-string v4, "your.package.name.key" const/4 v5, 0x2 invoke-virtual {v3, v4, v5}, Landroid/content/Context;->createPackageContext(Ljava/lang/String;I)Landroid/content/Context; move-result-object v0 .line 1029 .local v0, c:Landroid/content/Context; invoke-virtual {v0, p0}, Landroid/content/Context;->getString(I)Ljava/lang/String; :try_end_0 .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 move-result-object v2 .line 1036 .end local v0 #c:Landroid/content/Context; :goto_0 return-object v2 .line 1030 :catch_0 move-exception v3 move-object v1, v3 .line 1031 .local v1, e:Ljava/lang/Exception; new-instance v3, Ljava/lang/StringBuilder; const-string v4, "Error while getting key string:\n" invoke-direct {v3, v4}, Ljava/lang/StringBuilder;-> (Ljava/lang/String;)V invoke-virtual {v3, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder; move-result-object v3 invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v3 invoke-static {v3}, Lcom/lohan/testtarget/Console;->log(Ljava/lang/String;)V .line 1032 invoke-virtual {v1}, Ljava/lang/Exception;->printStackTrace()V .line 1033 const-string v2, "" goto :goto_0 .end method
Hi lohan+, is there any way to crack lite app to full app ?
ReplyDeletesometimes. :D (what kind of answer did you expect?)
ReplyDeleteI want instruction for changing smali file or something else :D Some lite apps have full feature but they disable user from using full feature, i want to learn to unlock this !
ReplyDeleteThanks !
there is no _one_ way to crack an app just as there is no _one_ way to program one or _one_ way to convince someone that eating apples is good. you will have to analyze and understand each app individually and determine if the lite version is just a crippled full version or if it is lacking certain assets or programming that make it impossible to "unlock".
ReplyDeleteHi, lohan+! The thing is, it always breaks down to a single instruction: the instruction on line 36: chksum.equals(chksumCmp) can simply set to a constant which is true in bytecode. Do you have a suggestion concerning this single point of failure?
ReplyDelete-Adi
hello Adi, not sure what you mean by "suggestion". how to crack or how to protect against? to crack, yes, you can just replace the equals and move-result with something like const/4 v#, 0x1 to make it always equal. sometimes reflection is employed, along with encrypted strings, to make the equals invocation look different. does this answer your question?
ReplyDeleteThanks for your fast reply! I meant to protect against cracking. The only way I can see is to compare two computed public fields in multiple locations of the execution flow.
ReplyDelete-Adi
Wonderful article .worth reading .Thank you for sharing.
ReplyDeleteDownloaded files not working on android
Thanks for this wonderful post. The information in this article is very helpful to me.
ReplyDelete**SELLING SSN+DOB FULLZ**
ReplyDeleteCONTACT
Telegram > @leadsupplier
ICQ > 752822040
Email > leads.sellers1212@gmail.com
>>1$ each without DL/ID number
>>2$ each with DL
>>5$ each for premium (also included relative info)
*Will reduce price if buying in bulk
*Hope for a long term business
FORMAT OF LEADS/FULLZ/PROS
->FULL NAME
->SSN
->DATE OF BIRTH
->DRIVING LICENSE NUMBER WITH EXPIRY DATE
->COMPLETE ADDRESS
->PHONE NUMBER, EMAIL, I.P ADDRESS
->EMPLOYMENT DETAILS
->REALTIONSHIP DETAILS
->MORTGAGE INFO
->BANK ACCOUNT DETAILS
>Fresh Leads for tax returns & w-2 form filling
>Payment mode BTC, ETH, LTC, PayPal, USDT & PERFECT MONEY
''OTHER GADGETS PROVIDING''
>SSN+DOB Fullz
>CC with CVV
>Photo ID's
>Dead Fullz
>Spamming Tutorials
>Carding Tutorials
>Hacking Tutorials
>SMTP Linux Root
>DUMPS with pins track 1 and 2
>Sock Tools
>Server I.P's
>HQ Emails with passwords
Email > leads.sellers1212@gmail.com
Telegram > @leadsupplier
ICQ > 752822040
THANK YOU
The Best Apps and Games For Android ·https://apkmodule.com/project-qt-mod-apk/
ReplyDelete