# Mobile 2Description *(Forgot to copy, all based on memory)*```Here is a APK file. Need to get 5 keys to unlock/decrypt the flag```[Keybox.apk](Keybox.apk)
Lets try to unzip it:```bashunzip Keybox.apkArchive:  Keybox.apk  inflating: res/anim/design_snackbar_in.xml   extracting: META-INF/android.support.design_material.version   extracting: res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png    inflating: res/layout/design_text_input_password_icon.xml   extracting: res/drawable-xxhdpi-v4/abc_ic_star_half_black_16dp.png   extracting: META-INF/androidx.localbroadcastmanager_localbroadcastmanager.version   extracting: res/drawable-xhdpi-v4/notification_bg_low_pressed.9.png   extracting: res/drawable-xxxhdpi-v4/abc_btn_switch_to_on_mtrl_00012.9.png   extracting: res/mipmap-hdpi-v4/ic_greyflag_round.png    inflating: res/color-v23/abc_btn_colored_text_material.xml   extracting: res/mipmap-xhdpi-v4/ic_redkey.png  ...
lsAndroidManifest.xml  assets  classes2.dex  classes.dex  com  Keybox.apk  META-INF  res  resources.arsc```Then I used `dex2jar` from this [Github](https://github.com/DexPatcher/dex2jar) *(The offical one doesn't work)*
Run the script for both dex files:```bash../../../dex-tools-2.1-20190905-lanchon/d2j-dex2jar.sh classes.dex classes2.dex Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=truedex2jar classes.dex -> ./classes-dex2jar.jardex2jar classes2.dex -> ./classes2-dex2jar.jar```Then I used the [JD-GUI](http://java-decompiler.github.io/) to view the `jar` files
You can also choose to save all the sources in the file menu
## Static AnalysisIn the assets folder got many files looks suspicious:```flag  key_0  key_1  key_2  key_3  key_4  _main  _preload```Notice every key folder got a `ciphertext.js` (Encrypted javascript file) and a `key_%i.enc` (Encrypted key file) 
In the flag folder, notice a `code.js` implement an encryption looks like [RC4 Cipher](https://en.wikipedia.org/wiki/RC4)
Also got a encrypted flag file :`flag.enc`
According to the `FlagActivity` source code, we must find all 5 keys to decrypt the flag:```javapublic class FlagActivity extends AppCompatActivity {  protected void onCreate(Bundle paramBundle) {  	...	String str1 = null.getStringExtra("key0");	String str2 = null.getStringExtra("key1");	String str3 = null.getStringExtra("key2");	String str4 = null.getStringExtra("key3");	String str5 = null.getStringExtra("key4");	textView1 = (TextView)findViewById(2131230800);	TextView textView2 = (TextView)findViewById(2131230801);	try {	  null = getAssets().open("flag/flag.enc");	  byte[] arrayOfByte1 = new byte[null.available()];	  byte[] arrayOfByte2 = new byte[null.available()];	  arrayOfByte2 = new byte[null.available()];	  null.read(arrayOfByte1);	  null.close();	  Oracle oracle2 = new Oracle();	  this(str1.getBytes());	  Oracle oracle1 = new Oracle();	  try {	    this(str2.getBytes());	    Oracle oracle = new Oracle();	    try {	      this(str3.getBytes());	      Oracle oracle3 = new Oracle();	      this(str4.getBytes());	      Oracle oracle4 = new Oracle();	      try {	        this(str5.getBytes());	        byte[] arrayOfByte = oracle4.process(oracle3.process(oracle.process(oracle1.process(oracle2.process(arrayOfByte1)))));	        String str = new String();	        this(arrayOfByte);	        Log.d("TMCTF", String.format("PLAINTEXT FLAG: %s", new Object[] { str }));	        if (str.matches("TMCTF\\{.*\\}")) {	          textView1.setText(str);	          textView2.setText("Keybox Complete!");	        } 	        ...```In the `MainActivity`, we saw some lines that should lead us to the key:```javaString str1 = this.decrypter.decrypt_key(0);String str2 = this.decrypter.decrypt_key(1);String str3 = this.decrypter.decrypt_key(2);String str4 = this.decrypter.decrypt_key(3);String str5 = this.decrypter.decrypt_key(4);```And notice `decrypter` is `HippoLoader` class:```javaHippoLoader hippoLoader2 = new HippoLoader();this.decrypter = hippoLoader2;```Then in `HippoLoader` source found the `decrypt_key` function:```javapublic String decrypt_key(int paramInt) {    byte[] arrayOfByte;    Log.d("TMCTF", String.format("Calling decrypt_key(%d)", new Object[] { Integer.valueOf(paramInt) }));    String str = String.format("key_%d/key_%d.enc", new Object[] { Integer.valueOf(paramInt), Integer.valueOf(paramInt) });    AssetManager assetManager = this.androidContextObject.getAssets();    Context context = ContextFactory.getGlobal().enterContext();    context.setOptimizationLevel(-1);    ImporterTopLevel importerTopLevel = new ImporterTopLevel(context);    try {      null = assetManager.open(this.path_preload);      arrayOfByte = new byte[null.available()];      null.read(arrayOfByte);      null.close();      null = new String();      this(arrayOfByte);      ScriptableObject.putProperty(importerTopLevel, "androidContext", Context.javaToJS(this.androidContextObject, importerTopLevel));      context.evaluateString(importerTopLevel, null, this.path_preload, 1, null);      arrayOfByte = null;      null = assetManager.open(str);      byte[] arrayOfByte1 = new byte[null.available()];      null.read(arrayOfByte1);      ScriptableObject.putProperty(importerTopLevel, "ciphertext_bytes", Context.javaToJS(arrayOfByte1, importerTopLevel));      Object object = importerTopLevel.get("process", importerTopLevel);      Singleton singleton1 = this.singleton;      try {        String str1 = singleton1.keykey(paramInt);        if (object instanceof Function)          object1 = ((Function)object).call(context, importerTopLevel, importerTopLevel, new Object[] { str1, arrayOfByte1 });         Object object1 = (String)object1;        Context.exit();        return object1;      } catch (Exception null) {        if (arrayOfByte.getClass() == com.trendmicro.hippo.JavaScriptException.class) {          StringBuilder stringBuilder = new StringBuilder();          this();          stringBuilder.append("Javascript exception - ");          stringBuilder.append(arrayOfByte.getMessage());          Log(stringBuilder.toString());          Context.exit();          return null;        }         Log(String.format("Exception in decrypt_key()", new Object[0]));        arrayOfByte.printStackTrace();        Context.exit();        return null;      } finally {}    } catch (Exception null) {     } finally {}    if (arrayOfByte.getClass() == com.trendmicro.hippo.JavaScriptException.class) {      StringBuilder stringBuilder = new StringBuilder();      this();      stringBuilder.append("Javascript exception - ");      stringBuilder.append(arrayOfByte.getMessage());      Log(stringBuilder.toString());      Context.exit();      return null;    }     Log(String.format("Exception in decrypt_key()", new Object[0]));    arrayOfByte.printStackTrace();    Context.exit();    return null;  }```Notice the `ciphertext_bytes` and `process` mention in the function, that also in the `code.js` file we saw previously
The `process` function is the RC4 encryption function:```jsfunction process(key, arr, bool) {
    if(!key) {        throw "No password supplied";    }
	var s = [], j = 0, x, res = [];	for (var i = 0; i < 256; i++) {		s[i] = i;	}	for (i = 0; i < 256; i++) {		j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;		x = s[i];		s[i] = s[j];		s[j] = x;	}	i = 0;	j = 0;	for (var y = 0; y < arr.length; y++) {		i = (i + 1) % 256;		j = (j + s[i]) % 256;		x = s[i];		s[i] = s[j];		s[j] = x;		res.push(String.fromCharCode(arr[y] ^ s[(s[i] + s[j]) % 256]));	}
	if(bool) {	    return(bin2String(res));	} else {    	return(res);    }}```## First KeyWe need to know the `key` the decrypt the all of the flag key
Trace where is the key parameter:```java Object object = importerTopLevel.get("process", importerTopLevel);      Singleton singleton1 = this.singleton;      try {        String str1 = singleton1.keykey(paramInt);        if (object instanceof Function)          object1 = ((Function)object).call(context, importerTopLevel, importerTopLevel, new Object[] { str1, arrayOfByte1 }); ```It should be the `str1`, which is `singleton1.keykey(paramInt)`
Lets see the source of `keykey` function:```javapublic String keykey(int paramInt) {    String str = null;    if (paramInt != 0) {      if (paramInt != 1) {        if (paramInt != 2) {          if (paramInt != 3) {            if (paramInt == 4)              str = this.flagkey4_key;           } else {            str = this.flagkey3_key;          }         } else {          str = this.flagkey2_key;        }       } else {        str = this.flagkey1_key;      }     } else {      str = this.flagkey0_key;    }     return str;  }```Then I tried to find the where it initialize the `flagkey` by using `grep -r "flagkey0_key" ./`command
I found the `flagkey0_key` in `KEY0HintActivity` class:```javasingleton.flagkey0_key = singleton.hintkey0;```Therefore, we need to find the `hintkey0`
Found it at the Singleton constructor function:```javaprivate Singleton() {    try {      ContextFactoryFactory contextFactoryFactory = new ContextFactoryFactory();      this();      this.factory = contextFactoryFactory;      this.hintkey0 = contextFactoryFactory.CreateKey(0);      this.hintkeymain = this.factory.CreateKey(1);      this.key4_box = new String[] { "", "", "" };      this.box_index = 0;    } catch (Exception exception) {}}```Notice the `contextFactoryFactory` is in different module:```javaimport com.trendmicro.hippo.ContextFactoryFactory;```
Found the key at the `ContextFactoryFactory` file!!```javaprivate String hintkey0 = new String("TrendMicro");```Then I use Python to decrypt the first key:```pyfrom arc4 import ARC4enc = open("assets/key_0/key_0.enc",'rb').read()arc4 = ARC4('TrendMicro')print(arc4.encrypt(enc))```Result:```b'KEY0-7135446200'```Yeah!! We found the first key!
## Second KeyThen how we decrypt the `ciphertext.js`?
I tried the first key did not work, but the keykey:`TrendMicro` works!```jsfunction entrypoint(key, ciphertext_bytes) {   Log(path_ciphertext + ":entrypoint()");
    titleView.setText("Key Zero Hints");    textView.setText(    " First off -- good luck! As a reward for playing, Key Zero has been decrypted for you.\n\nNow for the hints:\n\n" +    "1. The 'Flag' is stored in this APK file. The flag is encrypted. When decrypted, it contains a string matching the regular expression '^TMCTF\{[A-Za-z0-9]*\}$'" + "\n\n" +    "2. There are five 'Flag Keys.' All five Flag Keys are used to decrypt the Flag. Keys match /^KEY[0-4]-.[0-9]+/" + "\n\n" +    "3. To decrypt the five Flag Keys you must solve five levels." + "\n\n" +    "4. Each level has its own hint, located in the following files. The hints are encrypted. The hints for Key Zero were decrypted for you. You must figure out the other four:" + "\n" +    "      key_0/ciphertext.js (already decrypted)" + "\n" +    "      key_1/ciphertext.js" + "\n" +    "      key_2/ciphertext.js" + "\n" +    "      key_3/ciphertext.js" + "\n" +    "      key_4/ciphertext.js" + "\n" +    "\n" +    /* the above ciphertext.js files are ALL encrypted using the same password */    "5. The file assets/_preload/code.js is included before every executed javascript file." + "\n\n" +    "6. There are a number of ways to decompile the APK file. You should do that." + "\n\n" +    "7. Android intents can unlock many of the hints and keys.\n\n" +    "8. Use the Android Studio Android Emulator to run the challenge. It's the simplest way." + "\n\n\n\n\n\n\n" +    "https://www.trendmicro.com/en_us/contact.html"    );
    return(true);}
function verify_unlocked() {    return(true);}```Saw the comment mention all `ciphertext.js` is encrypted with the same password!!
So we can decrypt all and save using Python:```pyfrom arc4 import ARC4
for i in range(5):	enc = open(f"assets/key_{i}/ciphertext.js",'rb').read()	arc4 = ARC4('TrendMicro')	open(f"hint{i}.js","wb").write(arc4.encrypt(enc))```The hint for second key is:```"To unlock Key 1, you must call Trend Micro"```I guess it means we need to make a phone call to the Trend Micro company
By searching the `flagkey1_key`, found it at `Unlocker` file:```javaelse if (str.equals("android.intent.action.PHONE_STATE")) {  str = intent.getStringExtra("incoming_number");  if (str != null)    singleton.flagkey1_key = str.replaceAll("[^\\d.]", "");   Bundle bundle = intent.getExtras();  if (bundle != null)    for (String str : bundle.keySet()) {      Object object = bundle.get(str);      if (str.equals("state") && object.toString().equals("IDLE")) {        Intent intent1 = new Intent();        intent1.setClassName("com.trendmicro.keybox", "com.trendmicro.keybox.KeyboxMainActivity");        intent1.setFlags(268435456);        paramContext.startActivity(intent1);      }     }  } ```The regular expression `[^\\d.]` find all characters except number, so it just delete all non-number characters
Therefore, the key of the second key should be one of the Trend Micro contact
It is Japan company then I first the [Japan number first](https://www.trendmicro.com/en_us/contact.html#t6):```pyenc = open("assets/key_1/key_1.enc",'rb').read()arc4 = ARC4('81353343618')print(arc4.encrypt(enc))```It works! We found the second key!!```b'KEY1-1047645455'```
## Third KeyThe hint for third key is:```To unlock KEY2, send the secret code.```Found the `flagkey2_key` at the Unlocker also:```javapublic void onReceive(Context paramContext, Intent paramIntent) {    Intent intent;    Singleton singleton = Singleton.getInstance();    str = paramIntent.getAction();    if (str.equals("android.provider.Telephony.SECRET_CODE")) {      null = paramIntent.getData().getHost();      if (null.equals("8736364276")) {        Singleton.getInstance(Boolean.valueOf(true));      } else {        singleton.flagkey2_key = null;      }       intent = new Intent();      intent.setClassName("com.trendmicro.keybox", "com.trendmicro.keybox.KeyboxMainActivity");      intent.setFlags(268435456);      paramContext.startActivity(intent);    } ```Can see the key should be `8736364276`?
But when I tried it did not work :(
Then I stuck for awhile..
I look thought the `AndroidManifest.xml` file, then I saw `android_secret_code` ,`8736364275`,`87363642769`
Searching online for the `SECRET_CODE` thing, the secret_code is stored in the AndroidManifest.xml 
[Android Secret Codes](https://simonmarquis.github.io/Android-SecretCodes/)
It maybe the password I guess
It works for `8736364275`!! Nice```b'KEY2-9517232028'```
## Forth KeyThe hint for forth key is:```Unlock KEY3 with the right text message```Found `flagkey3_key` at `Observer` class:```javapublic void onChange(boolean paramBoolean) {  if (this.context == null)    return;   try {    ContextFactoryFactory contextFactoryFactory = new ContextFactoryFactory();    this();    this.factory = contextFactoryFactory;    this.uri = Uri.parse(contextFactoryFactory.resolverFactory());  } catch (Exception exception) {}  super.onChange(paramBoolean);  try {    Cursor cursor1 = this.context.getContentResolver().query(this.uri, null, null, null, null);    this.cursor = cursor1;    cursor1.moveToFirst();    for (byte b = 0; b < this.cursor.getColumnCount(); b++) {      String.format("%s == %s", new Object[] { this.cursor.getColumnName(b), this.cursor.getString(b) });      if (this.cursor.getString(b) != null && this.cursor.getColumnName(b) != null && this.cursor.getString(b).equals(this.cursor.getColumnName(b)) && this.cursor.getInt(this.cursor.getColumnIndex("type")) == 1) {        String str1 = this.cursor.getString(b);        this.message = str1;        this.singleton.flagkey3_key = str1;        Intent intent = new Intent();        this();        intent.setClassName("com.trendmicro.keybox", "com.trendmicro.keybox.KeyboxMainActivity");        intent.setFlags(268435456);        this.context.startActivity(intent);      }       String str = new String();      this();      this.message = str;    }     this.cursor.close();  } catch (Exception exception) {    Log.d("TMCTF", String.format("SMS observer error: %s", new Object[] { exception.getLocalizedMessage() }));  } }```I know it should related to SMS, notice the `contextFactoryFactory.resolverFactory()` is `content://sms````javapublic String contextURL = new String("content://sms");public String resolverFactory() { return this.contextURL; }```And I search for the `Cursor` class in android, it just a table in a database
`cursor.getString(b)` is get the column value at index b, `cursor.getColumnName(b)` is get the column name at index b
Notice the statement in the if condition:```javathis.cursor.getString(b).equals(this.cursor.getColumnName(b))```It means the column name must equal to column value
Then it put the column value into the forth key:```javaString str1 = this.cursor.getString(b);this.message = str1;this.singleton.flagkey3_key = str1;```We can just brute force all the column name, can look the column name at the [Android Documentation](https://developer.android.com/reference/android/provider/Telephony.TextBasedSmsColumns)
Column name `body` is the key for the forth key!!```b'KEY3-2510789910'```
## Fifth KeyThe hint for fifth key was quite long:```js// These coordinates are considered the ''canonical'' GPS coordinates of the// Trend Micro offices for purposes of this competition// Please forgive me for converting some names (mostly Brazil) - it made things simpler// for your humble CTF creator :)
//var Locations = [  { location: "Amsterdam", latitude:52.3084375, longitude:4.9426875, hash: "2dce283e0e268a42d62e34467db274c9c38c358f" },  { location: "Aukland", latitude:-36.842294, longitude:174.756654, hash: "794a4f25478d31bf87ece956fc7c95466a27c06a" },  { location: "Austin", latitude:30.3981626, longitude:-97.7203696, hash: "5b06f1f08503b4e6346926667d318f0f9d7e9fd1" },  { location: "Bangalore", latitude:12.9735089, longitude:77.6164228, hash: "91f6fcb18482cc6fe303108f4283180b4fa20599" },  ...  ...  ...];
function entrypoint(key, ciphertext_bytes) {    Log(path_ciphertext + ":entrypoint()");
    var location_match = '';
    for(var index=0 ; index < Locations.length; index++) {        location = Locations[index];        var location_latitude = parseFloat(location.latitude);        var location_longitude = parseFloat(location.longitude);
        var singleton_latitude = parseFloat(singleton.latitude);        var singleton_longitude = parseFloat(singleton.longitude);
        // why actually calculate distance when you can just fake it ;)        if(Math.abs(location_latitude - singleton_latitude) < 0.001 && Math.abs(location_longitude - singleton_longitude) < 0.001 ) {            Log("Matched Location " + location.location);            var SHA1 = new Hashes.SHA1;            hash = SHA1.hex(location.location)            if( hash == location.hash) {                location_match = "Welcome to " + location.location;            } else {                location_match = "Welcome to " + location.location;                singleton.push(location.hash);            }            break;        } else {            location_match = ""        }    }
    titleView.setText("Key Four Hints");    textView.setText(    "Visit " + /* all three of */ "the headquarters to unlock Key 4" + "\n\n" +    location_match    );
    return(true)}```The main point of the hint is these lines:```jsvar SHA1 = new Hashes.SHA1;hash = SHA1.hex(location.location)if( hash == location.hash) {    location_match = "Welcome to " + location.location;} else {    location_match = "Welcome to " + location.location;    singleton.push(location.hash);}```Only if the hash is not equal the location hash, then it execute `singleton.push(location.hash)`
Check the method:```jspublic void push(String paramString) {  if (!paramString.equals(this.key4_box[2])) {    String[] arrayOfString = this.key4_box;    arrayOfString[0] = arrayOfString[1];    arrayOfString[1] = arrayOfString[2];    arrayOfString[2] = paramString;    this.flagkey4_key = String.join("", (CharSequence[])arrayOfString);  }   try {    paramString = String.format("key_%d/key_%d.enc", new Object[] { Integer.valueOf(4), Integer.valueOf(4) });    InputStream inputStream = this.androidContextObject.getAssets().open(paramString);    byte[] arrayOfByte = new byte[inputStream.available()];    inputStream.read(arrayOfByte);    Oracle oracle = new Oracle();    this(this.flagkey4_key.getBytes());    String str = new String();    this(oracle.process(arrayOfByte));    if (str.matches("KEY4-.*")) {      Intent intent = new Intent();      this();      intent.setClassName("com.trendmicro.keybox", "com.trendmicro.keybox.KeyboxMainActivity");      intent.setFlags(268435456);      this.androidContextObject.startActivity(intent);    }   } catch (Exception exception) {}}```The main code is here:```javaif (!paramString.equals(this.key4_box[2])) {    String[] arrayOfString = this.key4_box;    arrayOfString[0] = arrayOfString[1];    arrayOfString[1] = arrayOfString[2];    arrayOfString[2] = paramString;    this.flagkey4_key = String.join("", (CharSequence[])arrayOfString);} ```Therefore, we need find three hashes that are incorrect, join them together is the key of fifth key
I used Python to find the three hashes:```pycity = {"Amsterdam" : "2dce283e0e268a42d62e34467db274c9c38c358f" ,"Aukland" : "794a4f25478d31bf87ece956fc7c95466a27c06a" ,"Austin" : "5b06f1f08503b4e6346926667d318f0f9d7e9fd1" ,"Bangalore" : "91f6fcb18482cc6fe303108f4283180b4fa20599" ,......... }key4 = "" for k,v in city.items():    if hashlib.sha1(k.encode()).hexdigest() != v:        key4 = v + key4enc = open("assets/key_4/key_4.enc",'rb').read()arc4 = ARC4(key4)print(arc4.encrypt(enc))```And we found the fifth key!! We can decrypt the flag now!!```b'KEY4-4721296569'```## FlagAccording to the `FlagActivity` and the hint is the flag folder:```jsfunction decrypt_flag() {     Log("DECRYPT THE FLAG()");     // flag=bin2String(process(key0,process(key1,process(key2,process(key3,process(key4, flag_ciphertext))))));     Log("KEY0" + key0);     Log("KEY1" + key1);     Log("KEY2" + key2);     Log("KEY3" + key3);     Log("KEY4" + key4);     Log("FLAG " + flag);     // return(flag); } function verify_unlocked() {    return(true); }```We need to decrypt the flag recursively:```pyflag = open("assets/flag/flag.enc","rb").read()for i in range(5):  arc4 = ARC4(key[i])  flag = arc4.encrypt(flag)print(flag)```Thats it!! Longest analysis for a CTF challenge!```TMCTF{pzDbkfWGcE}```[Full Python Script](solve.py)