CTF Challenge Description:
Hi there,
thanks for testing my new notes app. I know it’s not done yet, but the military-grade encryption is implemented, so let’s see if you can read my note.
Solution
If you install the app on your phone, the login screen looks just like below
Since this is an Android reverse engineering challenge, we use JEB decompiler to reverse and analyze the app. Below is full source code:
package com.example.firstjavaapplication; /* ... */ public class MainActivity extends AppCompatActivity { private Button buttonLogin; private EditText editTextPassword; private EditText editTextUsername; @Override // androidx.fragment.app.FragmentActivity protected void onCreate(Bundle bundle0) { super.onCreate(bundle0); this.setContentView(layout.activity_main); this.editTextUsername = (EditText)this.findViewById(id.editTextUsername); this.editTextPassword = (EditText)this.findViewById(id.editTextPassword); Button button0 = (Button)this.findViewById(id.buttonLogin); this.buttonLogin = button0; button0.setOnClickListener(new View.OnClickListener() { private boolean isValidCredentials(String s, String s1) { return (s.equals("admin")) && (s1.equals("fbG6pXd0wm1cz")); } @Override // android.view.View$OnClickListener public void onClick(View view0) { String s = MainActivity.this.editTextUsername.getText().toString().trim(); String s1 = MainActivity.this.editTextPassword.getText().toString().trim(); if(!s.isEmpty() && !s1.isEmpty()) { if(this.isValidCredentials(s, s1)) { Intent intent0 = new Intent(MainActivity.this, Part1.class); intent0.putExtra("password", s1); MainActivity.this.startActivity(intent0); return; } Toast.makeText(MainActivity.this, "Wrong Username or Password", 0).show(); return; } Toast.makeText(MainActivity.this, "Please enter username and password", 0).show(); } }); } }
The initial section of the wooden container contained a keypad and a wooden trapdoor that was closed. The provided code below outlines the authentication mechanism employed by the keypad in order to validate the user’s input by comparing it to the anticipated outcome.
6d47dd806bdf0677abfdca674206650aaa58fcb3669e76d1b60fb1fb91d50a54f4483f6a721cc3e8acc87f536f9419d782dcc8e7b443574530
uint64_t validate_code() int64_t var_38 __builtin_strcpy(dest: &var_38, src: ":1N50QU357") int64_t var_8 = 0x19 void var_140 void* var_10 = &var_140 strcpy(var_10, &code, 0x33555130354e313a) strcat(var_10, &var_38) size_t var_18 = 0x10 int64_t var_20 = 0xf md5String(var_10, &var_140 - 0x10) int128_t var_a0 __builtin_strncpy(dest: &var_a0, src: "33c12ea236cc81d7deb97e432aedc9d4", n: 0x21) void* x0_6 int64_t x1_2 x0_6, x1_2 = hexstr_to_char(&var_a0) void var_78 fastpbkdf2_hmac_sha512(&var_140 - 0x10, var_18, x0_6, x1_2, 0x64, &var_78, 0x40) void* x0_9 int64_t x1_5 x0_9, x1_5 = hexstr_to_char("6d47dd806bdf0677abfdca674206650a…") if (x1_5 == 0x40) int32_t x0_12 = memcmp(&var_78, x0_9, 0x40) free(x0_9) int64_t var_c0_1 = 0 free(x0_6) int64_t var_b0_1 = 0 return zx.q(x0_12) __assert_fail(assertion: "expected.length == PKBDF2_OUT_SI…", file: "firmware.c", line: 0x8d, function: "validate_code") noreturn
If you notice com.example.firstjavaapplication.MainActivity you will find that the correct credentials as admin
and fbG6pXd0wm1cz
which can be used to login as admin.
In the code, MainActivity
sends the password fbG6pXd0wm1cz
that is never being used besides methods such as generateFlag
and MD5
which are also not used at all.
if we display the content of storage.json we find the below:
└──> cat contents/assets/storage.json
{
"AES-Type": "256",
"note": "uS0D11dq3RM9QimRWfXcewwQdoxYwrZRNUGT205pDfQ="
}
This makes us re-examine this part of the code below:
@Override // android.view.View$OnClickListener public void onClick(View view0) { String s = Part1.this.editTextPassphrase.getText().toString().trim(); if(!s.isEmpty()) { try { ((TextView)Part1.this.findViewById(id.textViewFLAG2)).setText(this.decrypt(s1, s)); } catch(Exception exception0) { exception0.printStackTrace(); textView0.setText(s1); Toast.makeText(Part1.this, "Wrong passphrase", 0).show(); } return; } Toast.makeText(Part1.this, "Please enter a password", 0).show(); }
IIt calls decrypt
using the note uS0D11dq3RM9QimRWfXcewwQdoxYwrZRNUGT205pDfQ=
and the passphrase we enter. If we prevent exceptions from being raised in the code, we can assume that the note is successfully decrypted
Note gets decoded using Base64 and then it gets decrypted 313370+1 times using AES-256
. NoPadding
option is also used to avoid throwing exceptions during the multiple decryptions.
At the end, we use PKCS7Padding
to make sure decryption is correct.
To find the correct passphrase, we conduct a password dictionary attack using rockyou.txt.
The passphrase that we try must be of the “correct size” to avoid any error such as Unsupported key size: 4 bytes
exception.
Since we know that the encryption algorithm is AES-256
, we can safely deduce that the correct size is 32 characters long.
The next step is to filter out all passwords from the rockyou.txt wordlist which are not 32 bytes long.
So we proceed with this approach and get the correct passphrase (and the flag):
letsyouupdateyourfunNotesandmore
CTF Walkthrough Playlist