Antirdroid
Last updated
Last updated
Antirdroid was a fun Android reverse engineering challenge split in 3 steps. I am not that familiar with Android RE and especially with the specific tools for dynamic analysis and debugging, hence why after desperately and unsuccessfully trying to patch the application and make it work on an emulator I decided to give up. I only believe in one god, and its name is static analysis.
In this challenge, you need to find three flags.
Each flag starts with ECW_ and will be displayed in the Android logcat together with a tag indicating the flag number.
In order to decompile the APK, I used Bytecode Viewer.
In the assets/
directory, we can see a file mnist.tflite
and 12 files mnist-letter.tflite
with letter
ranging from a
to l
.
In the com/example/ecw
directory lie two classes named MainActivity.class
and FinishActivity.class
.
MainActivity
contains a few interesting methods:
public void onActivityResult(int var1, int var2, Intent var3) {
super.onActivityResult(var1, var2, var3);
if (var1 == 12 && var2 == 10) {
String var7;
label15: {
LinearLayout var4 = (LinearLayout)this.findViewById(id.base);
TextView var5 = new TextView(this);
var5.setText("Congratulation: the final flag is:");
var4.addView(var5);
if (var3 != null) {
var7 = var3.getStringExtra("end_flag");
if (var7 != null) {
break label15;
}
}
var7 = "ERROR, this is not the flag";
}
LinearLayout var6 = (LinearLayout)this.findViewById(id.base);
TextView var8 = new TextView(this);
var8.setText(var7);
var6.addView(var8);
Log.i("FLAG 3", var7);
}
}
This seems to log the final flag for the third step, so we'll save this for later.
public void onCreate(Bundle var1) {
super.onCreate(var1);
this.setContentView(2131361821);
ClassLoaderSharing.INSTANCE.setLoader(this.getClassLoader());
Iterator var69 = CollectionsKt__CollectionsKt.listOf(new String[]{"step_1", "step_2", "step_3"}).iterator();
while(var69.hasNext()) {
String var2 = (String)var69.next();
Field var3;
FileOutputStream var70;
boolean var10001;
try {
var3 = c.class.getField(var2);
StringBuilder var4 = new StringBuilder();
var4.append(var2);
var4.append(".dex");
var70 = this.openFileOutput(var4.toString(), 0);
} catch (Exception var68) {
var10001 = false;
continue;
}
[...]
var71 = this.getResources().openRawResource(var3.getInt((Object)null));
[...]
This seems to read files called step_1.dex
, step_2.dex
and step_3.dex
from raw resources. Speaking of which, if you unzip the apk and check in the res/raw/
folder, you will find these files, but they do not look like valid dex files. Perhaps are they encrypted?
Let's take a look at FinishActivity
now:
public final Object invoke() {
Class var1 = this.b.getClass();
String var2 = this.b.getSharedPreferences("flag", 0).getString("a", (String)null);
if (var2 == null) {
var2 = "fail";
}
IvParameterSpec var3 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
SecretKeySpec var5 = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(new PBEKeySpec(var2.toCharArray(), new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256)).getEncoded(), "AES");
Cipher var4 = Cipher.getInstance("AES/CBC/PKCS7Padding");
var4.init(2, var5, var3);
ClassLoader var6 = ClassLoaderSharing.INSTANCE.getLoader();
Class var7 = var1;
if (var6 != null) {
Class var8 = var6.loadClass(new String(var4.doFinal(Base64.decode("raTLFVkpCb4yP1YXsMdvqr2TjJSxtpiYA0yJLQ2UTPs=", 0)), Charsets.UTF_8));
var7 = var1;
if (var8 != null) {
var7 = var8;
}
}
return var7.getConstructor(Activity.class).newInstance(this.b);
}
Definitely some interesting stuff going on here, we know for sure there's crypto involved now. Some base64 string is decoded then decrypted using AES CBC, but the key seems derived from a certain variable, that is the a
field in a shared preferences object called flag
. Shared preferences allow to read and save key/value pairs on device storage, which can also be used to keep a global state in the application. We understand that we'll probably get to this bit later in the challenge.
In the d/c/a/d/
folder, there is an interesting b.class
file:
public Object invoke(Object var1) {
Cursor var288 = (Cursor)var1;
IntRef var2 = this.c;
int var3 = var2.element++;
boolean var4 = false;
if (var3 == 4) {
if (this.b == null) {
throw null;
}
Companion var289;
label2364:
var289 = Result.Companion;
var1 = Result.constructor-impl(var288.getString(var288.getColumnIndex("data2")));
Object var291 = var1;
if (Result.isFailure-impl(var1)) {
var291 = null;
}
String var290 = (String)var291;
if (var290 != null) {
MessageDigest var294 = MessageDigest.getInstance("MD5");
var294.update(var290.getBytes(Charsets.UTF_8));
Unit var301;
if (Intrinsics.areEqual((new BigInteger(1, var294.digest())).toString(16), "b71985397688d6f1820685dde534981b")) {
label2357: {
Exception var10000;
label2372: {
[...]
Cipher var7;
FileInputStream var8;
FileOutputStream var303;
MainActivity var306;
File var307;
IvParameterSpec var299 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
SecretKeyFactory var300 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
char[] var292 = var290.toCharArray();
PBEKeySpec var6 = new PBEKeySpec(var292, new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256);
SecretKey var302 = var300.generateSecret(var6);
SecretKeySpec var293 = new SecretKeySpec(var302.getEncoded(), "AES");
Cipher var304 = Cipher.getInstance("AES/CBC/PKCS7Padding");
var304.init(2, var293, var299);
this.b.p = var304;
var306 = this.b;
var7 = this.b.p;
var290 = UUID.randomUUID().toString();
var307 = new File(var306.getFilesDir(), var290);
var8 = var306.openFileInput("step_1.dex");
var303 = var306.openFileOutput(var290, 0);
byte[] var9;
var9 = new byte[4096];
while(true) {
var3 = var8.read(var9);
if (var3 > 0) {
byte[] var295;
if (var3 == 4096) {
var295 = var7.update(var9);
} else {
var295 = var7.doFinal(var9, 0, var3);
}
var303.write(var295);
}
}
[...]
label2376: {
int var10;
String var305;
byte[] var312;
Method[] var313;
try {
CloseableKt.closeFinally(var303, (Throwable)null);
if (!var307.exists()) {
break label2376;
}
PathClassLoader var308 = new PathClassLoader(var307.getAbsolutePath(), var306.getClassLoader());
byte[] var310 = this.b.p.doFinal(Base64.decode("j04vGcW35ZUg23JsqQ+/YA==", 0));
var305 = new String(var310, Charsets.UTF_8);
var307.getClass().getMethod(var305).invoke(var307);
byte[] var309 = this.b.p.doFinal(Base64.decode("WOtre8ObMy2nnFbqn2Kb6w==", 0));
String var311 = new String(var309, Charsets.UTF_8);
var291 = var308.loadClass(var311).newInstance();
var312 = this.b.p.doFinal(Base64.decode("J9vFCBjTjE6YoMI1wVDwjg==", 0));
var290 = new String(var312, Charsets.UTF_8);
var313 = var291.getClass().getDeclaredMethods();
var10 = var313.length;
} catch (Exception var273) {
var10000 = var273;
var10001 = false;
break label2372;
}
for(var3 = 0; var3 < var10; ++var3) {
Method var315 = var313[var3];
boolean var11;
label2295: {
label2294: {
if (Intrinsics.areEqual(var315.getName(), var290) && Arrays.equals(var315.getParameterTypes(), new Class[]{Activity.class})) {
break label2294;
}
var11 = false;
break label2295;
}
var11 = true;
}
if (var11) {
try {
var315.invoke(var291, this.b);
Editor var314 = this.b.getSharedPreferences("flag", 0).edit();
var312 = this.b.p.doFinal(Base64.decode("bjmQcWsAN3k8NxmaYYWvy6L+SDvu3ZlDFMSFvepIycxwZLgw5qGRB5ggJLHpDvW3", 0));
var305 = new String(var312, Charsets.UTF_8);
var314.putString("a", var305).apply();
((TextView)this.b.q.getValue()).setVisibility(8);
break label2357;
} catch (Exception var271) {
var10000 = var271;
var10001 = false;
break label2372;
}
}
}
try {
NoSuchElementException var316 = new NoSuchElementException("Array contains no element matching the predicate.");
throw var316;
} catch (Exception var266) {
var10000 = var266;
var10001 = false;
break label2372;
}
}
try {
FileNotFoundException var318 = new FileNotFoundException();
throw var318;
} catch (Exception var265) {
var10000 = var265;
var10001 = false;
}
}
Exception var317 = var10000;
var317.printStackTrace();
Toast.makeText(this.b, "Nice try", 0).show();
}
}
var301 = Unit.INSTANCE;
}
var4 = true;
}
return var4;
}
I greatly pruned the code because there were a lot of try/catches and stuff that heavily impacted readability.
Here's what we can understand from this piece:
Some string var290
is hashed with MD5 and compared to b71985397688d6f1820685dde534981b
var290
is used to derived an AES key
The file step_1.dex
is decrypted with this key
A few ciphertexts are decrypted too (in base64: j04vGcW35ZUg23JsqQ+/YA==
, WOtre8ObMy2nnFbqn2Kb6w==
and J9vFCBjTjE6YoMI1wVDwjg==
)
A longer ciphertext (bjmQcWsAN3k8NxmaYYWvy6L+SDvu3ZlDFMSFvepIycxwZLgw5qGRB5ggJLHpDvW3
) is decrypted and put in the a
field of the flag
shared preferences object
All of this is quite approximative, but it doesn't matter; it's enough to make progress.
The md5 reverses to "jean". I found an implementation of PBKDF2WithHmacSHA256 in Python, which I used to decrypt all the ciphertexts:
from hashlib import pbkdf2_hmac
from Crypto.Cipher import AES
from base64 import b64decode
salt = [56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110]
salt = list(map(lambda u: u % 256, salt))
salt = bytes(salt)
iv = [-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93]
iv = list(map(lambda u: u % 256, iv))
iv = bytes(iv)
def decrypt(blob, passwd):
key = pbkdf2_hmac(
hash_name='sha256',
password=passwd,
salt=salt,
iterations=65536,
dklen=32,
)
aes = AES.new(key, AES.MODE_CBC, iv)
return aes.decrypt(blob)
C = """j04vGcW35ZUg23JsqQ+/YA==
WOtre8ObMy2nnFbqn2Kb6w==
J9vFCBjTjE6YoMI1wVDwjg==
bjmQcWsAN3k8NxmaYYWvy6L+SDvu3ZlDFMSFvepIycxwZLgw5qGRB5ggJLHpDvW3""".split('\n')
C = list(map(b64decode, C))
for c in C:
print(decrypt(c, b'jean'))
open('step_1_decoded.dex', 'wb').write(
decrypt(open('step_1.dex', 'rb'), b'jean')
)
Result:
b'delete\n\n\n\n\n\n\n\n\n\n'
b'a.a.a.c\t\t\t\t\t\t\t\t\t'
b'a\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
b'LuKXSGlN5(%:Vk=alEbl9khIEPBo=mXu;hR7Ez7E\x08\x08\x08\x08\x08\x08\x08\x08'
Okay, so the first three plaintexts are not that interesting. Maybe the second one will be the path to the class in the next step. On the other hand, the fourth plaintext looks very interesting. It looks like some kind of key.
Now let's decompile the newly decrypted .dex file!
public final Thread a(@NotNull Activity var1) {
SharedPreferences var2 = var1.getSharedPreferences("save", 0);
var1.getSharedPreferences("flag", 0).edit().putString("w", "k").apply();
Toast.makeText(var1, "You made it to step 1", 0).show();
String var3 = var2.getString("pass1", (String)null);
LinearLayout var4 = (LinearLayout)var1.findViewById(id.base);
View var5 = View.inflate(var1, layout.check, (ViewGroup)null);
Button var6 = (Button)var5.findViewById(id.validation);
EditText var7 = (EditText)var5.findViewById(id.password);
var7.setText(var3);
var4.addView(var5);
var6.setOnClickListener(new b(var7, var1, var2, this, var1));
return ThreadsKt.thread$default(false, false, (ClassLoader)null, (String)null, 0, new a.a.a.c.c(var1), 31, (Object)null);
}
Cool, we made it to step1. We can see some weird stuff going on with the flag
shared preferences object: the value "k" is affected to the key "w"...
In a/a/a/d.class
, we can see a few potential new ciphertexts:
public static final String a = "ZnoETjqJ0h3VUtdPQnzkWsqrDFtvsK4BQ+1NJGx38YHXq9QxUEmztU9CsN4vCTbI";
public static final String b = "tvEf77LVcQcHX2FtkIoSBQ==";
public static final String c = "TbQSB6aY7Ye++tVv84UPIA==";
public static final String d = "biPW3PPcH5wQHBNdE6eP2Pg4K9UAZT8guUhpNLV44RzWdYVT91LcP8WgtY+9QrUUKWfW0FIyKHVg3P7AKS9vIQ==";
public static final String e = "n/CG6W9Ilu8muE8UGJM29S/2JV4hw2O/IX8IPBartj7qvWP0MasL7ZujCyHYH1ERYd+NP+IzVaTuRwT+TbCoSA==";
public static final String f = "7wwCGcbnGp/EAusByZQYcYsxSfBxiEHP4GZPjsAHjGLYVryk6yS9xTo6GmF1J6Z6rDvp8XnuBCZ97DmURQx+lvAvrebYDXPEbiVOcSANTk4=";
public static final String g = "EIW2q6l3m0ZvO1G6+QgXDVqiFcGj5tDV9tEtCRHJ6ALV2bwYxBzUvY4S5LuERqdrqm4RGDU3xHXOJr6+buDwIg==";
In a/a/a/e/c.class
, there's a new interesting piece of code:
if (b.a.a()) {
String var28 = var1.getSharedPreferences("flag", 0).getString("a", "");
if (var28 == null) {
Intrinsics.throwNpe();
}
IvParameterSpec var25 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
SecretKeySpec var34 = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(new PBEKeySpec(var28.toCharArray(), new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256)).getEncoded(), "AES");
Cipher var29 = Cipher.getInstance("AES/CBC/PKCS7Padding");
var29.init(2, var34, var25);
b.a.a(var29);
}
List var30 = StringsKt.split$default(new String(b.a.a().doFinal(Base64.decode("7wwCGcbnGp/EAusByZQYcYsxSfBxiEHP4GZPjsAHjGLYVryk6yS9xTo6GmF1J6Z6rDvp8XnuBCZ97DmURQx+lvAvrebYDXPEbiVOcSANTk4=", 0)), Charsets.UTF_8), new String[]{"!"}, false, 0, 6, (Object)null);
ArrayList var26 = new ArrayList();
Iterator var31 = var30.iterator();
Looks like it's the same crypto as before, but with a different key which is the string contained in the a
field of flag
. Luckily, we might know what this key is. Let's try it out:
C = """ZnoETjqJ0h3VUtdPQnzkWsqrDFtvsK4BQ+1NJGx38YHXq9QxUEmztU9CsN4vCTbI
tvEf77LVcQcHX2FtkIoSBQ==
TbQSB6aY7Ye++tVv84UPIA==
biPW3PPcH5wQHBNdE6eP2Pg4K9UAZT8guUhpNLV44RzWdYVT91LcP8WgtY+9QrUUKWfW0FIyKHVg3P7AKS9vIQ==
n/CG6W9Ilu8muE8UGJM29S/2JV4hw2O/IX8IPBartj7qvWP0MasL7ZujCyHYH1ERYd+NP+IzVaTuRwT+TbCoSA==
7wwCGcbnGp/EAusByZQYcYsxSfBxiEHP4GZPjsAHjGLYVryk6yS9xTo6GmF1J6Z6rDvp8XnuBCZ97DmURQx+lvAvrebYDXPEbiVOcSANTk4=
EIW2q6l3m0ZvO1G6+QgXDVqiFcGj5tDV9tEtCRHJ6ALV2bwYxBzUvY4S5LuERqdrqm4RGDU3xHXOJr6+buDwIg==""".split('\n')
C = list(map(b64decode, C))
for c in C:
print(decrypt(c, b'LuKXSGlN5(%:Vk=alEbl9khIEPBo=mXu;hR7Ez7E'))
Result:
b'\xf9\xd8\xbe\xe0O\x8bD\xdcL\x84\xb0X|\xd0\xac\xcc\x19\xcd\xd3V\xa6\xbd}\xc3"\x81\x8e\x08\xc0\xab8\xc7i?\x18\xadV\xff\xb3(6Tf\xf8?\xe3\xac\xdb'
b'\x01\xd8\x96X=\xd0\xb01_\x9dN\xc3\x16&\x0e\xa4'
b'\xe6\xe2\xf7KR\x18\xe5$kN\x802\xbf4\x1d('
b'45:*!3:s!42:b!43:j!31:1!7:d!44:M!28:9!0:p!5:o!18:_!24:5!50:O!\x03\x03\x03'
b'19:i!38:V!49:b!34:b!4:w!23:y!1:a!41:%!16:p!14:t!6:r!13:s!12:_!\x02\x02'
b"8:_!15:e!47:R!35:Z!46:'!51:7!25:B!11:r!26:<!48:C!10:o!27:S!33:r!\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"
b'22:p!40:V!20:s!2:s!17:1!21::!32:W!29:a!37:.!39:t!30:T!36:U!9:f!\x01'
The first three ciphertexts translated to garbage with incorrect PKCS padding, but the four last did yield quite interesting plaintexts.
I instantly had the intuition to split them on "!" and sort the "k:b" pairs by the "k" value:
q = """45:*!3:s!42:b!43:j!31:1!7:d!44:M!28:9!0:p!5:o!18:_!24:5!50:O!19:i!38:V!49:b!34:b!4:w!23:y!1:a!41:%!16:p!14:t!6:r!13:s!12:_!8:_!15:e!47:R!35:Z!46:'!51:7!25:B!11:r!26:<!48:C!10:o!27:S!33:r!22:p!40:V!20:s!2:s!17:1!21::!32:W!29:a!37:.!39:t!30:T!36:U!9:f"""
q = q.split('!')
Q = [0] * 100
for qq in q:
if qq.count(':') == 2:
ch = ':'
offset = qq.split(':')[0]
else:
offset, ch = qq.split(':')
Q[int(offset)] = ord(ch)
print(bytes(Q))
Result : password_for_step1_is:py5B<S9aT1WrbZU.VtV%bjM*'RCbO7
Really cool, what if we try this password as a key for the remaining ciphertexts that we weren't able to decrypt earlier?
C = """ZnoETjqJ0h3VUtdPQnzkWsqrDFtvsK4BQ+1NJGx38YHXq9QxUEmztU9CsN4vCTbI
tvEf77LVcQcHX2FtkIoSBQ==
TbQSB6aY7Ye++tVv84UPIA==""".split('\n')
C = list(map(b64decode, C))
K = b'py5B<S9aT1WrbZU.VtV%bjM*\'RCbO7'
for c in C:
print(decrypt(c, K))
And here we have our first flag!
b'ECW_oe8%jXffkWul&#!V@tqB(:V%WP?JUKm@I(2KqIfv\x04\x04\x04\x04'
b'a.a.a.c\t\t\t\t\t\t\t\t\t'
b'a\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
If we try to decrypt step_2.dex
with the last key (py5B<S9aT1WrbZU.VtV%bjM*'RCbO7
), we do get a valid dex file again. Let's decompile it.
The a/a/a/d.class
file contains three new ciphertexts, we're used to it at that point.
public static final String a = "5sxJURBMWadPV+Qfj2g/WFVWcaLbXoUxyXeiIvpa4pu1SjSj0nqneJeN0tNkKbJx";
public static final String b = "gkZ6pGuoDU6Lz5bc23Y/5ZfI9XPcJd/r1PRrsE1epqc=";
public static final String c = "rbfA5lkSHq0eL4dmwH4gHg==";
The a/a/a/e.class
is the interesting part.
public final boolean a(@NotNull String var1) {
boolean var2 = false;
boolean var3 = var2;
if (f.a(var1, 4, 0, 2, (Object)null) * f.a(var1, 6, 0, 2, (Object)null) == 4840) {
var3 = var2;
if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 217) {
var3 = var2;
if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 8, 0, 2, (Object)null) == 9559) {
var3 = var2;
if ((char)(f.a(var1, 8, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 141) {
var3 = var2;
if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 7, 0, 2, (Object)null) == 10494) {
var3 = var2;
if (f.a(var1, 1, 0, 2, (Object)null) * f.a(var1, 2, 0, 2, (Object)null) == 5346) {
var3 = var2;
if (f.a(var1, 4, 0, 2, (Object)null) * f.a(var1, 0, 0, 2, (Object)null) == 3360) {
var3 = var2;
if ((char)(f.a(var1, 10, 0, 2, (Object)null) + f.a(var1, 2, 0, 2, (Object)null)) == 167) {
var3 = var2;
if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 6138) {
var3 = var2;
if ((char)(f.a(var1, 12, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 193) {
var3 = var2;
if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 3, 0, 2, (Object)null) == 13794) {
var3 = var2;
if (f.a(var1, 3, 0, 2, (Object)null) * f.a(var1, 10, 0, 2, (Object)null) == 9804) {
var3 = var2;
if (f.a(var1, 7, 0, 2, (Object)null) * f.a(var1, 0, 0, 2, (Object)null) == 8904) {
var3 = var2;
if ((char)(f.a(var1, 7, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 224) {
var3 = var2;
if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 161) {
var3 = var2;
if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 11682) {
var3 = var2;
if ((char)(f.a(var1, 10, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 148) {
var3 = var2;
if ((char)(f.a(var1, 14, 0, 2, (Object)null) + f.a(var1, 5, 0, 2, (Object)null)) == 216) {
var3 = var2;
if ((char)(f.a(var1, 4, 0, 2, (Object)null) + f.a(var1, 6, 0, 2, (Object)null)) == 161) {
var3 = var2;
if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 2, 0, 2, (Object)null)) == 202) {
var3 = var2;
if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 8, 0, 2, (Object)null) == 7821) {
var3 = var2;
if (f.a(var1, 14, 0, 2, (Object)null) * f.a(var1, 5, 0, 2, (Object)null) == 11564) {
var3 = var2;
if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 4, 0, 2, (Object)null) == 3960) {
var3 = var2;
if ((char)(f.a(var1, 4, 0, 2, (Object)null) + f.a(var1, 8, 0, 2, (Object)null)) == 'w') {
var3 = var2;
if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 3, 0, 2, (Object)null)) == 235) {
var3 = var2;
if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 2, 0, 2, (Object)null) == 9801) {
var3 = var2;
if ((char)(f.a(var1, 0, 0, 2, (Object)null) + f.a(var1, 10, 0, 2, (Object)null)) == 170) {
var3 = var2;
if (f.a(var1, 7, 0, 2, (Object)null) * f.a(var1, 10, 0, 2, (Object)null) == 9116) {
var3 = var2;
if ((char)(f.a(var1, 7, 0, 2, (Object)null) + f.a(var1, 10, 0, 2, (Object)null)) == 192) {
var3 = var2;
if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 8, 0, 2, (Object)null)) == 200) {
var3 = var2;
if (f.a(var1, 11, 0, 2, (Object)null) * f.a(var1, 1, 0, 2, (Object)null) == 6468) {
var3 = var2;
if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 8, 0, 2, (Object)null)) == 178) {
var3 = var2;
if ((char)(f.a(var1, 2, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 199) {
var3 = var2;
if ((char)(f.a(var1, 7, 0, 2, (Object)null) + f.a(var1, 0, 0, 2, (Object)null)) == 190) {
var3 = var2;
if (f.a(var1, 8, 0, 2, (Object)null) * f.a(var1, 5, 0, 2, (Object)null) == 7742) {
var3 = var2;
if (f.a(var1, 15, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 7316) {
var3 = var2;
if (f.a(var1, 10, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 5332) {
var3 = var2;
if (f.a(var1, 8, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 4898) {
var3 = var2;
if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 239) {
var3 = var2;
if ((char)(f.a(var1, 8, 0, 2, (Object)null) + f.a(var1, 5, 0, 2, (Object)null)) == 177) {
var3 = var2;
if (f.a(var1, 1, 0, 2, (Object)null) * f.a(var1, 4, 0, 2, (Object)null) == 2640) {
var3 = var2;
if ((char)(f.a(var1, 0, 0, 2, (Object)null) + f.a(var1, 3, 0, 2, (Object)null)) == 198) {
var3 = var2;
if ((char)(f.a(var1, 11, 0, 2, (Object)null) + f.a(var1, 1, 0, 2, (Object)null)) == 164) {
var3 = var2;
if (f.a(var1, 10, 0, 2, (Object)null) * f.a(var1, 2, 0, 2, (Object)null) == 6966) {
var3 = var2;
if (f.a(var1, 0, 0, 2, (Object)null) * f.a(var1, 3, 0, 2, (Object)null) == 9576) {
var3 = var2;
if (f.a(var1, 12, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 8850) {
var3 = var2;
if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 14278) {
var3 = var2;
if (f.a(var1, 0, 0, 2, (Object)null) * f.a(var1, 10, 0, 2, (Object)null) == 7224) {
var3 = var2;
if (f.a(var1, 2, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 9558) {
var3 = var2;
if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 7, 0, 2, (Object)null)) == 205) {
var3 = var2;
if ((char)(f.a(var1, 8, 0, 2, (Object)null) + f.a(var1, 0, 0, 2, (Object)null)) == 163) {
var3 = var2;
if ((char)(f.a(var1, 15, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 180) {
var3 = var2;
if ((char)(f.a(var1, 1, 0, 2, (Object)null) + f.a(var1, 4, 0, 2, (Object)null)) == 'j') {
var3 = var2;
if (f.a(var1, 8, 0, 2, (Object)null) * f.a(var1, 0, 0, 2, (Object)null) == 6636) {
var3 = var2;
if (f.a(var1, 4, 0, 2, (Object)null) * f.a(var1, 8, 0, 2, (Object)null) == 3160) {
var3 = var2;
if ((char)(f.a(var1, 4, 0, 2, (Object)null) + f.a(var1, 0, 0, 2, (Object)null)) == '|') {
var3 = var2;
if (f.a(var1, 7, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 12508) {
var3 = var2;
if ((char)(f.a(var1, 3, 0, 2, (Object)null) + f.a(var1, 10, 0, 2, (Object)null)) == 200) {
var3 = var2;
if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 4, 0, 2, (Object)null)) == 139) {
var3 = var2;
if ((char)(f.a(var1, 1, 0, 2, (Object)null) + f.a(var1, 2, 0, 2, (Object)null)) == 147) {
var3 = true;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return var3;
}
A huge pyramid of conditions on some string called var1
!
Let's take a look at the first one:
if (f.a(var1, 4, 0, 2) * f.a(var1, 6, 0, 2) == 4840)
I removed the "(Object)null" arguments which are probably useless decompilation artifacts. What does this f.a
function do now? Let's take a look at the f.class
file:
public static final int a(@NotNull String var0, int var1, int var2) {
Character var3 = StringsKt.getOrNull(var0, var1);
if (var3 != null) {
var2 = var3;
}
return var2;
}
The Kotlin documentation says: getOrNull
returns a character at the given index or null if the index is out of bounds of this char sequence.
Not sure about the extra arguments, but what's highly likely is we are isolating var1[4]
, var1[6]
and multiplying them.
I extracted the pyramid of if's in a text file, wrote a script to parse it and directly feed it into z3:
from z3 import *
dump = open('dump.txt', 'r').read().split('\n')[::2]
variables = [Int('k%s' % i) for i in range(16)]
V = []
V += [variables[i] >= 0 for i in range(16)]
V += [variables[i] < 256 for i in range(16)]
for line in dump:
i1 = int(line.split(',')[1].replace(' ', ''))
i2 = int(line.split(',')[5].replace(' ', ''))
z = line.split('== ')[1].split(')')[0]
if "'" in z:
z = ord(z.replace("'",