Friday, June 10, 2011

cracking verizon's v cast apps drm

verizon has a new app store. it has an amazon-store like drm that's fairly simple to crack.

here's how it works. in the launching activity a thread is started that calls checkLicense(). this was in com/cp/app/MainActivity$2.smali:
.method public run()V
    .locals 2

    .prologue
    .line 100
    invoke-static {}, Landroid/os/Looper;->prepare()V

    .line 102
    :try_start_0
    iget-object v0, p0, Lcom/cp/app/MainActivity$2;->this$0:Lcom/cp/app/MainActivity;

    iget-object v0, v0, Lcom/cp/app/MainActivity;->licenseAuthenticator:Lcom/verizon/vcast/apps/LicenseAuthenticator;

    sget-object v1, Lcom/cp/app/MainActivity;->verizonAppKeyword:Ljava/lang/String;

    # call checkLicense and store result in v0
    invoke-virtual {v0, v1}, Lcom/verizon/vcast/apps/LicenseAuthenticator;->checkLicense(Ljava/lang/String;)I
    move-result v0

    .line 103
    iget-object v1, p0, Lcom/cp/app/MainActivity$2;->this$0:Lcom/cp/app/MainActivity;

    # send result of checkLicense (v0) to isDRMDone()
    invoke-virtual {v1, v0}, Lcom/cp/app/MainActivity;->isDRMDone(I)Z
    move-result v0

    # isDRMDone() handles error messages and returns true when all is good
    if-eqz v0, :cond_0

    # ... rest of code

if you look at isDRMDone() you'll see that it's basically a big switch. input of 0 or 1 counts as valid. everything else is some kind of error. so we just need to make sure checkLicense returns 1 and doesn't call anything else that may have side effects (timeouts, checking to see if verizon app store is installed, etc..).

checkLicense() is defined in com/verizon/vcast/apps/LicenseAuthenticator.smali. after modification it looks like:
.method public declared-synchronized checkLicense(Ljava/lang/String;)I
    .locals 10
    .parameter "keyword"

    .prologue
    
    # just set v0 to true and return
    # the rest of the code never runs
    const/4 v0, 0x1
    return v0

    const/16 v9, 0x64

    const-string v7, "checkLicense() finished.  Trying to shutDownLicenseService()"

    const-string v7, "LicenseAuthenticator"

    .line 256
    monitor-enter p0

    :try_start_0
    const-string v7, "LicenseAuthenticator"

    const-string v8, "begin checkLicense()"

    # ... rest of code

if you're a developer, depending on how they implement the insertion of their drm, it may still be possible to use classical protection / anti-tampering techniques. i'd like to know. but really though, don't waste your time on protection. i'm not blasting verizon or amazon or google for weak security. real effort should be spent improving the program, not slowing down (because you can't stop) crackers. if you want money, use ads.

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();