anti-tampering with crc check

one way an app will try to detect if it has been tampered with is to look at classes.dex inside the apk. just so you know, java code is compiled to java .class files, which is then transformed by dx into classes.dex. this one file contains all the compiled code of an app. once the code is finished and the app is ready to be published, properties of the file such as the size or crc (cyclic redundancy check) can be determined and then stored inside the resources of the app.

when the app runs, it can compare the stored values with the actual values of the classes.dex file. if they do not match, then the code was likely tampered with.

note that we're using zipentry here, but we could also use jarentry and jarfile. you can't simply look for getCrc() and feel safe either, because the method could be called with reflection.

here's what a crc check may look like in java:
private void crcTest() throws IOException {  boolean modified = false;   // required dex crc value stored as a text string.  // it could be any invisible layout element  long dexCrc = Long.parseLong(Main.MyContext.getString(R.string.dex_crc));   ZipFile zf = new ZipFile(Main.MyContext.getPackageCodePath());  ZipEntry ze = zf.getEntry("classes.dex");   if ( ze.getCrc() != dexCrc ) {   // dex has been modified   modified = true;  }  else {   // dex not tampered with   modified = false;  } }

and here's the above code translated into smali:
.method private crcTest()V     .locals 7     .annotation system Ldalvik/annotation/Throws;         value = {             Ljava/io/IOException;         }     .end annotation      .prologue     .line 599     const/4 v2, 0x0      .line 602     # modified will be set to true if classes.dex crc is not what it should be     .local v2, modified:Z     sget-object v5, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;      # get the crc value from string resources     const v6, 0x7f040002     invoke-virtual {v5, v6}, Landroid/content/Context;->getString(I)Ljava/lang/String;     move-result-object v5      # convert it to a long since ZipEntry.getCrc gives us long     invoke-static {v5}, Ljava/lang/Long;->parseLong(Ljava/lang/String;)J     move-result-wide v0      .line 604     .local v0, dexCrc:J     new-instance v4, Ljava/util/zip/ZipFile;      sget-object v5, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;      # get the path to the apk on the system     invoke-virtual {v5}, Landroid/content/Context;->getPackageCodePath()Ljava/lang/String;     move-result-object v5      invoke-direct {v4, v5}, Ljava/util/zip/ZipFile;->(Ljava/lang/String;)V      .line 605     .local v4, zf:Ljava/util/zip/ZipFile;     # get classes.dex entry from our apk     const-string v5, "classes.dex"     invoke-virtual {v4, v5}, Ljava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;     move-result-object v3      .line 607     .local v3, ze:Ljava/util/zip/ZipEntry;     # you could crack here by providing v5 with the correct     # long value. this may be easier if later logic is convoluted     # or if the result is stored in a class variable and acted     # on later. you can write your own java jadwal to get the
    # correct value. 
    invoke-virtual {v3}, Ljava/util/zip/ZipEntry;->getCrc()J     move-result-wide v5      # compare v5 (actual crc) with v0 (stored crc)     cmp-long v5, v5, v0      # if v5 is 0, meaning cmp-long reports values are NOT the same     # goto :cond_0. this is where this could be cracked.     # could simply remove this line, in this case.     if-eqz v5, :cond_0      .line 609     # otherwise store true in v2.     # normally there will be code to act on the value of v2.     const/4 v2, 0x1      .line 615     :goto_0     return-void      .line 613     :cond_0     # store false in v2.     const/4 v2, 0x0      goto :goto_0 .end method

Komentar