Tags: reversing android 


# Water Color

Well we think this puzzle is hard!

* [watercolor.apk](./watercolor.apk)

## Solution
Used Bluestacks to emulate and jadx-gui to decompile the apk.

This is the obfuscated MainActivity of the app.
public class bj9704hd extends Fragment {
String sf8352oh;
String tq9643uf;

public native void extraInfo();

public native byte[] initFromJNI();

static {

public void onCreate(Bundle bundle) {

public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle bundle) {
View inflate = layoutInflater.inflate(R.layout.fragment_login, viewGroup, false);
final EditText editText = (EditText) inflate.findViewById(R.id.usernametext);
final EditText editText2 = (EditText) inflate.findViewById(R.id.passwordtext);
((Button) inflate.findViewById(R.id.loginbtn)).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
bj9704hd.this.sf8352oh = editText.getText().toString();
bj9704hd.this.tq9643uf = editText2.getText().toString();
if (bj9704hd.this.sf8352oh.matches("") || bj9704hd.this.tq9643uf.matches("")) {
Toast.makeText(bj9704hd.this.getActivity().getApplicationContext(), "Username and Password are required", 0).show();
} else if (new dh7645oi().wr9452tb(bj9704hd.this.tq9643uf)) {
SharedPreferences.Editor edit = bj9704hd.this.getActivity().getSharedPreferences("watercolor", 0).edit();
edit.putString("password", bj9704hd.this.tq9643uf);
bj9704hd.this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new nt9923ng()).addToBackStack("main").commit();
} else {
Toast.makeText(bj9704hd.this.getActivity().getApplicationContext(), "Incorrect Username or Password", 0).show();
return inflate;

class dh7645oi {
Cipher uh7608nh;

public dh7645oi() {
try {
MessageDigest instance = MessageDigest.getInstance("SHA-256");
byte[] initFromJNI = bj9704hd.this.initFromJNI();
instance.update(initFromJNI, 0, initFromJNI.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(instance.digest(), "AES");
Cipher instance2 = Cipher.getInstance("AES");
this.uh7608nh = instance2;
instance2.init(1, secretKeySpec);
} catch (NoSuchAlgorithmException e) {
} catch (InvalidKeyException e2) {
} catch (NoSuchPaddingException e3) {

public String fg8461gs(String str) {
try {
return Base64.encodeToString(this.uh7608nh.doFinal(str.getBytes()), 2);
} catch (BadPaddingException e) {
return "";
} catch (IllegalBlockSizeException e2) {
return "";

public boolean wr9452tb(String str) {
try {
String readLine = new BufferedReader(new InputStreamReader(bj9704hd.this.getActivity().getAssets().open("uw7289nv"))).readLine();
if (readLine == null || !readLine.equals(fg8461gs(str))) {
return false;
return true;
} catch (IOException e) {
return false;

It checks only the password when we try to login. The check is as follows...
- `initFromJNI` from native library gives a string. The SHA-256 digest of the string is used as a key for AES ECB (No IV was used)
- The password is encryped using the above AES instance
- The encrypted password is base64 encoded and checked with asset `uw7289nv` which is...

`r6EszdHWf8TkNrC+BLfiXILGelpBPHw7koCOeLpBv8w=` --> `afa12ccdd1d67fc4e436b0be04b7e25c82c67a5a413c7c3b92808e78ba41bfcc`


I have Bluestacks rooted and ADB with Frida Server... So I used JNItrace to log the JNI and tried to login...

Key is `MfVLoHWWiAyeaTl` SHA-256 --> `770f97bc76546a3508b6be54adac963af6e86f67551993904f56feccb0ce2388`

AES decrypting it gives the password `Th!$_!$_$uPp0s3d_t0_63_$3cur3`

There is another function that is called from native library when we succesfully login `extraInfo`. I used IDA to decompile the native library, at the end of the function we see this...

__android_log_print(4LL, "watercolor", "valid username is: %s", v3);
So instead of reversing the function I used ADB logcat to see the logs, then logged in the apk with the valid password.


04-26 19:35:23.335 1520 1577 I audio_hw_primary: choose pcmC0D0p for 0
04-26 19:35:23.337 5105 5105 I watercolor: valid username is: [email protected]@t3RC^l3r&$3cr3t!U53R#
04-26 19:35:23.352 5105 5114 I art : Do partial code cache collection, code=24KB, data=29KB
04-26 19:35:23.354 5105 5114 I art : After code cache collection, code=24KB, data=29KB
04-26 19:35:23.354 5105 5114 I art : Increasing code cache capacity to 128KB
04-26 19:35:23.362 1846 1877 D InputMethodManagerService: packageName=io.peykar.watercolor, activityName=.kg8923um

So username is `[email protected]@t3RC^l3r&$3cr3t!U53R#`

Now we got the username and password but where is flag??? There is a `solvedPuzzleUI` function in another class...

### solvedPuzzleUI
private void solvedPuzzleUI() {
LinearLayout linearLayout = (LinearLayout) this.hd9863mm.findViewById(R.id.pauseContainer);
getLayoutInflater().inflate(R.layout.puzzle_solved_ui, linearLayout, true);
((Button) this.hd9863mm.findViewById(R.id.okBtn)).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
new Thread(new Runnable() {
public void run() {
try {
URI uri = new URI(kj0762ey.this.ss2176jv.getResources().getString(R.string.hostname));
OkHttpClient unused = kj0762ey.this.fs9572kw = se5498vr.ab3702ns(uri.getHost(), kj0762ey.this.ss2176jv.getResources().getString(R.string.fingerprint));
String string = kj0762ey.this.gs0973mt.getString("password", "");
JSONObject jSONObject = new JSONObject();
try {
jSONObject.put("username", "find a valid username to capture the flag");
jSONObject.put("password", string);
kj0762ey.this.fs9572kw.newCall(new Request.Builder().url(uri.toURL()).post(RequestBody.create(kj0762ey.this.yh5921pa, jSONObject.toString())).build()).enqueue(new Callback() {
public void onFailure(Call call, IOException iOException) {
Log.i("watercolor", "Failure in sending request");

public void onResponse(Call call, Response response) {
Log.i("watercolor", "Response received successfully");
} catch (JSONException e) {
} catch (Exception e2) {
kj0762ey.this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new bj9704hd()).addToBackStack("login").commit();


On solving the puzzle it sends a JSON with username and password to the address in `hostname`.

In `Resources/resources.arsc/res/values/strings.xml` we get the hostname `https://water-color.peykar.io`

I made a post request with curl to get the flag
curl --header "Content-Type: application/json" \
--request POST \
--data '{"username":"[email protected]@t3RC^l3r&$3cr3t!U53R#","password":"Th!$_!$_$uPp0s3d_t0_63_$3cur3"}' \


## Flag
> `S4CTF{W4t3r_C0l0r_is_Beaut1fu1_But_Danger0u5_B3_C4r3ful!!}`

Original writeup (https://github.com/1GN1tE/CTF_Writeups/tree/main/Writeups/S4CTF_2021/Water%20Color).