Recently, I heard a certain game was giving away items to owners of a certain new device (The Motorola Droid Ultra and the Motorola Droid Maxx). Since I don’t live in the USA, and don’t particularly want a phone with a locked bootloader, or one labelled “a phone that doesn’t need to exist”, let’s break the APK apart and see what we can do. Let’s take a look at how they are checking for these devices.

public final class v
{
  public static DeviceInfo a()
  {
    SomeApplication localApplication = Application.a();
    return new w(0).a(Boolean.valueOf(b())).a(Settings.Secure.getString(localApplication.getContentResolver(), "android_id")).b(Build.BOARD).c(Build.BOOTLOADER).d(Build.BRAND).e(Build.DEVICE).f(Build.DISPLAY).g(Build.HARDWARE).h(Build.MANUFACTURER).i(Build.MODEL).j(Build.PRODUCT).k(Build.TAGS).l(Build.TYPE).m(Build.FINGERPRINT).a();
  }
 
  private static boolean b()
  {
    int i = 1;
    String str = Build.TAGS;
    if ((str != null) && (str.contains("test-keys")));
    while (true)
    {
      return i;
      try
      {
        boolean bool = new File("/system/app/Superuser.apk").exists();
        if (bool)
          continue;
        label38: i = 0;
      }
      catch (Throwable localThrowable)
      {
        break label38;
      }
    }
  }
}

Sure, a simple check for Superuser.apk to check if you are rooted, check a few build properties, and the unique device id (64-bit id randomly generated at boot), all wrapped up in it’s own class. There are a couple of solutions that can be taken here

  • Edit the build.prop
  • Modify the apk

While the former is technically easier to accomplish, one does still need root OR a custom recovery image installed to be able to modify the build.prop file (as it resides on /system, which is mounted as read-only 99.9% of the time). That and non-aosp roms may chuck a hissy fit and bootloop.

To begin, the Superuser.apk check. One does not really need any special permissions to check for it, and reporting as rooted does not affect our ability to use the application, but since we’re here, might as well rename the file path it checks to something a tad more unlikely.

const-string/jumbo v2, "/system/app/OhHaiThar.apk"

Now let’s take a look at how the w class that the device check returns looks like.

final class w
{
  private Boolean a = null;
  private String b = null;
  private String c = null;
  private String d = null;
  private String e = null;
  private String f = null;
  private String g = null;
  private String h = null;
  private String i = null;
  private String j = null;
  private String k = null;
  private String l = null;
  private String m = null;
  private String n = null;
 
  public final DeviceInfo a()
  {
    return new DeviceInfo(this.a, this.b, this.c, this.d, this.e, this.f, this.g, this.h, this.i, this.j, this.k, this.l, this.m, this.n);
  }
 
  public final w a(Boolean paramBoolean)
  {
    this.a = paramBoolean;
    return this;
  }
 
  public final w a(String paramString)
  {
    this.b = paramString;
    return this;
  }
 
  public final w b(String paramString)
  {
    this.c = paramString;
    return this;
  }
 
  public final w c(String paramString)
  {
    this.d = paramString;
    return this;
  }
 
  public final w d(String paramString)
  {
    this.e = paramString;
    return this;
  }
 
  public final w e(String paramString)
  {
    this.f = paramString;
    return this;
  }
 
  public final w f(String paramString)
  {
    this.g = paramString;
    return this;
  }
 
  public final w g(String paramString)
  {
    this.h = paramString;
    return this;
  }
 
  public final w h(String paramString)
  {
    this.i = paramString;
    return this;
  }
 
  public final w i(String paramString)
  {
    this.j = paramString;
    return this;
  }
 
  public final w j(String paramString)
  {
    this.k = paramString;
    return this;
  }
 
  public final w k(String paramString)
  {
    this.l = paramString;
    return this;
  }
 
  public final w l(String paramString)
  {
    this.m = paramString;
    return this;
  }
 
  public final w m(String paramString)
  {
    this.n = paramString;
    return this;
  }
}

Simple enough? Perhaps you did not see it, but based on the first snippet, we can determine that the boolean is storing if our device is rooted or not, and the rest of the strings are the various build properties. (You can see this via the parameter order) However, what we are seeing is merely the smali bytecode run through a Java decompiler. While useful for analysis, we obviously can’t modify it in that format.

As you saw above, the different properties are set using combined get/set methods which look like…

.method public final a(Ljava/lang/String;)Lcom/nianticproject/ingress/w;
    .locals 0
    .parameter
 
    .prologue
    .line 91
    iput-object p1, p0, Lcom/nianticproject/ingress/w;->b:Ljava/lang/String;
 
    .line 92
    return-object p0
.end method

In smali. (Obscure enough not to have markdown syntax highlighting!) With our terrible understanding of smali assembly, we turn it into:

.method public final a(Ljava/lang/String;)Lcom/nianticproject/ingress/w;
    .locals 0
    .parameter
     
    const-string/jumbo v1, "ffffffffffffffff"
 
    .prologue
    .line 91
    iput-object v1, p0, Lcom/nianticproject/ingress/w;->b:Ljava/lang/String;
      
 
    .line 92
    return-object p0
.end method

Tada, we now return “ffffffffffffff” for our unique device id. Rinse and repeat for the rest of the methods, and we are greeted with this upon our next login.

Login message detailing rewards