Showing posts with label anti-cracking. Show all posts
Showing posts with label anti-cracking. Show all posts

Saturday, September 20, 2014

Unpacking APKProtect, Bangcle, LIAPP, Qihoo

Android packers getting more common, and if you're doing any significant amount of reversing, you're bound to come across one. While any serious reverser should 1.) familiarize themselves with how they work,  2.) unpack a few by hand, and 3.) write their own tool, it's always nice to kick back and pull something off the shelf. Or, at least, learn how someone else is doing it.

For this reason, I'd like to share with you the provocatively named android-unpacker from strazzere. Nothing says "i write c code" like a blunt and unambiguous project title!
https://github.com/strazzere/android-unpacker/tree/master/native-unpacker

From the readme, it already supports a few popular unpackers:
  • Bangcle (SecNeo)
  • APKProtect
  • LIAPP (preprelease demo)
  • Qihoo

    Monday, October 17, 2011

    protection using checksums and key / unlocker apps

    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:
    // 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

    Wednesday, June 8, 2011

    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 program 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

    Monday, June 6, 2011

    protection by checking for debuggers

    one way to figure out what an app is doing is to use a debugger so you can step through line by line. apktool makes it possible to debug apps to which you do not have the source, and you also have to setup a few other things covered in lesson 1 of the way of the android crack tutorials.

    some apps try to protect against this and there are two techniques of doing so. the first is to check the android manifest to see if the app is set to debuggable. the java code would look something like this:

    boolean isDebuggable = (0 != (getApplcationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE));
    if ( isDebuggable )
      invalidLicense(); // you get the idea 

    in smali, specifically from the testtarget app distributed with antilvl, it looks like:
    # get app info object
    invoke-virtual {p0}, Lcom/lohan/testtarget/Main;->getApplicationInfo()Landroid/content/pm/ApplicationInfo;
    move-result-object v1
    
    # get flags mask
    iget v2, v1, Landroid/content/pm/ApplicationInfo;->flags:I
    
    # 0x2 is ApplicationInfo.FLAG_DEBUGGABLE
    and-int/lit8 v2, v2, 0x2
    
    iput v2, v1, Landroid/content/pm/ApplicationInfo;->flags:I
    
    # if v2 is 0x0 (the debuggable flag is not set), goto :cond_0
    if-eqz v2, :cond_0


    the second method is to check if a debugger is currently connected. the java looks like:
    boolean debugConn = Debug.isDebuggerConnected();

    Thursday, March 31, 2011

    spoof getinstallerpackagename with adb

    as i wrote back in another post about anti-cracking technique examples, one method that is often used is getinstallerpackagename(). if the apk is installed from adb, it will be null, but if it's installed from the market it will be com.google.android.feedback. antilvl is well aware of this already, but there is an easier solution for when you're in a hurry. i learned it reading this post at tim's fantastic blog on reversing. he's not affiliated with me and for all i know he's an upstanding white hat who just loves hacking android.

    all you need is adb. just give it this command either in a shell or as:
    adb install -i com.google.android.feedback com.protected.app
     this will setup com.google.android.feedback as the installer for the com.protected.app. if you're not sure what the app name is for a given apk, just use aapt, from the android-sdk. ex: aapt d --values badging someapk.apk

    Sunday, January 23, 2011

    anti-cracking example

    there are only a handful of anti-cracking techniques for android, at least that i know of. this seems to be because of how android apps must behave. they must operate inside their own virtual machine and are not allowed to store permenent data. when they are uninstalled, they must remove all traces of themselves. this makes things like timed trials difficult to implement because a simple reinstall will reset timers.

    if an app wants to prevent cracking, it only has a few options. it can check file properties of the apk such as file size, last modified date and signature. it could also check the installer package name to test if it was installed via market. a cracker or pirate would install via adb, not market. antilvl can already hook and subvert signature checks. file size, last modified and installer package hooking are in the works. :D

    this is what a installer package check may look like in java:
    private boolean InstalledFromMarketEasy() {
      String pname = this.getPackageName();
      PackageManager pm = this.getPackageManager();
      String installPM = pm.getInstallerPackageName(pname);
     
      if ( installPM == null ) {
        // Definitely not installed from Android Market
        return false;
      }
      else if ( installPM.equals("com.google.android.feedback") ) {
        // Installed from the Android Market
        return true;
      }
    
      return false;
    }


    but there's another way to perform any check in a more clever way that makes detection more difficult... by using java reflection. instead of calling the method directly, the app could use reflection to store the method in a variable and call it later indirectly. this makes searching for the direct method call difficult. it also allows the method name to be obfuscated since it is just a strong. let me show you in code what it may look like:
    const-class v1, Landroid/content/pm/PackageManager;
    
    # do not assume this will not always be a plain text
    # it could be put together in a much more complicated way such as building character by character using ascii codes
    # you'll have to hook reflection methods to test the name or figure it out manually
    const-string v2, "getInstallerPackageName"
    
    # make array of size 1
    const/4 v3, 0x1
    new-array v3, v3, [Ljava/lang/Class;
    
    const/4 v4, 0x0
    const-class v5, Ljava/lang/String;
    
    # store String class in array v3, at index v4 (0)
    aput-object v5, v3, v4
    
    # get method of name v2 ("getInstallerPackageName") and move to v0
    # http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Class.html#getMethod(java.lang.String, java.lang.Class...)
    invoke-virtual {v1, v2, v3}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
    move-result-object v0
    
    # do something with v0, like store in class variable
    # ....
    # and then sometime later, somewhere else in code
    
    # get the package manager and store in v1
    invoke-virtual {p0}, Lcom/clever/app/Main;->getPackageManager()Landroid/content/pm/PackageManager;
    move-result-object v1
    
    const/4 v2, 0x1
    new-array v2, v2, [Ljava/lang/Object;
    
    const/4 v3, 0x0
    invoke-virtual {p0}, Lcom/clever/app/Main;->getPackageName()Ljava/lang/String;
    move-result-object v4
    
    # put package name in array v2 at index v3 (0)
    aput-object v4, v2, v3
    
    # actually call "getInstallerPackageName" with the array we built as parameters
    # which contains this package name
    # http://download.oracle.com/javase/1.5.0/docs/api/java/lang/reflect/Method.html#invoke%28java.lang.Object,(java.lang.Object...)
    invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
    move-result-object v0
    check-cast v0, Ljava/lang/String;
    
    # clever to not use any type of full string, just "goog"
    # remember the full string is expected to be "com.google.android.feedback"
    const-string v1, "goog"
    invoke-virtual {v0, v1}, Ljava/lang/String;->indexOf(Ljava/lang/String;)I
    move-result v0
    
    # check to see if v0 has the correct value. if not... error :D

    the next lesson in way of the android cracker will cover all anti-cracking techniques i know in detail, and antilvl will support these types of checks soon, but this is enough for anyone to figure it out. :D

    for some more information on reflection:
    Using Java Reflection
    Java programming dynamics, Part 2: Introducing reflection