All Articles

Cyber Security Rumble CTF 2023 Writeup

This page has been machine-translated from the original page.

I participated in Cyber Security Rumble CTF 2023, which started on 7/8, as part of 0nePadding, and we finished 35th out of 622 teams.

There did not seem to be many Japanese participants this time, so somehow we ended up as the top-ranked team from Japan, which was a surprise haha.

As usual, I will go through the writeups.

SHELLCODE-CEPTION(Rev)

I think I lost my flag in some kind of inception. Can you help me find my flag?

Decompiling the challenge binary produced the following output.

image-20230709011605412

The program XORs data embedded in the binary, stores it in memory, and then uses mprotect to set attributes such as PROT_EXEC on that region.

Reference: mprotect(2) - Linux manual page

From this, I inferred that the program had functionality to execute shellcode decoded at runtime.

So I used the following script to extract the shellcode into a file.

data = b'\x3c\x21\xe0\x8c\x21\xd1\x6b\x7b\x7a\x53\x58\x1c\x4b\x43\x21\xd3\x41\x46\x4f\x77\x19\x46\x4b\x1b\x21\xe0\x2c\xb9\x21\xe0\x3c\xb1\x21\xd1\x58\x5c\x19\x18\x46\x77\x4b\x1c\x21\xd3\x46\x77\x4a\x4d\x77\x1c\x46\x46\x21\xe0\x2c\x89\x21\xe0\x3c\x81\xae\x2c\x99\x47\x51\x19\x46\x0f\xae\x2c\x9d\x4f\x55\xaf\x2c\x9f\x69\xae\x2c\x95\x69\x69\x69\x69\x82\x75\xe2\x2c\x95\x21\xf1\x66\xdf\x2d\x6c\xb9\xea\x99\x28\xe0\xab\xe2\x2c\x95\x21\xf1\xe1\x3d\x6c\xb9\xea\x2c\x95\x68\xea\x14\x95\x4c\x17\xb7\xae\x2c\x91\x69\x69\x69\x69\x82\x67\xe2\x2c\x91\x21\xf1\xaf\x2d\x6c\xb9\x69\xea\x2c\x91\x68\xea\x14\x91\x4c\x17\x85\xf9\x34\xaa'
dist = b''
for d in data:
    dist += (d^0x69).to_bytes(1,'big')
with open("shellcode", "wb") as f:
    f.write(dist)

Further analyzing the extracted shellcode in Ghidra produced the following result.

image-20230709011545535

The logic itself was simple, so I wrote the following solver and recovered the flag.

import struct

a = 0x2a2275313a131202
b = 0x72222f701e262f28
c = 0x75221e2f71703531
d = 0x2f2f751e24231e2f
e = 0x2f70382e
f = 0x3c26

# https://docs.python.org/ja/3.9/library/struct.html
data = struct.pack("<qqqqih", a, b, c, d,e,f)
for d in data:
    print(chr(d^0x41),end="")

# CSR{p4cking_1nc3pt10n_c4n_be_4nnoy1ng}

LIGHTBULB(Rev)

I have a very nice light bulb at home, and I found out I can switch with my phone using the app I wrote.

But the app only works on my phone, so good luck switching my light!

The challenge binary is provided as an APK file.

Running it in an emulator showed that it was an app where you log in with a password and then toggle a light switch on and off.

First, after unpacking the APK with apktool and looking through the files, I found that 583908295080, which is used as the login password, was stored in plaintext.

Then, to trace what happened after login, I used Smali2Java to decompile the Smali files into Java and inspected the result.

This showed that LightSwitchingActivity performs the following processing.

String string = sharedPreferences.getString("secret_key", "");
Log.d("LIGHTSWITCH", "the sk was " + string);
Intrinsics.checkNotNull(string);
byte[] bytes = string.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
APIKeyHash aPIKeyHash = new APIKeyHash(bytes);
StringBuilder sb = new StringBuilder("CSR{");
byte[] bytes2 = "APIKEY".getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes2, "this as java.lang.String).getBytes(charset)");
this.apiKey = sb.append(LightSwitchingActivityKt.toHex(aPIKeyHash.hash(bytes2))).append('}').toString();
RequestQueue newRequestQueue = Volley.newRequestQueue((Context) this);
Intrinsics.checkNotNullExpressionValue(newRequestQueue, "newRequestQueue(this)");
this.requestQueue = newRequestQueue;
SwitchCompat findViewById = findViewById(2131231147);
Intrinsics.checkNotNullExpressionValue(findViewById, "findViewById(R.id.switch_light_bulb)");
findViewById.setOnCheckedChangeListener(new LightSwitchingActivity$.ExternalSyntheticLambda0(this));

Here, it first converts the secret_key string—which is also the login password—into a byte array and then constructs an APIKeyHash object.

After that, it converts the string APIKEY into a byte array and passes it to the hash method of APIKeyHash.

I found that feeding the resulting byte array returned by hash into LightSwitchingActivityKt.toHex() produced the flag.

As before, I used Smali2Java to decompile APIKeyHash and LightSwitchingActivityKt into Java so I could inspect their implementations.

// LightSwitchingActivityKt 
package club.redrocket.lightbulb;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
/* compiled from: LightSwitchingActivity.kt */
@Metadata(d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0019\n\u0000\n\u0002\u0010\u000e\n\u0002\u0010\u0012\n\u0000\u001a\n\u0010\u0002\u001a\u00020\u0003*\u00020\u0004\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0005"}, d2 = {"HEX_CHARS", "", "toHex", "", "", "app_release"}, k = 2, mv = {1, 8, 0}, xi = 48)
/* loaded from: /tmp/jadx-9359415826287627730.dex */
public final class LightSwitchingActivityKt {
    private static final char[] HEX_CHARS;

    static {
        char[] charArray = "0123456789ABCDEF".toCharArray();
        Intrinsics.checkNotNullExpressionValue(charArray, "this as java.lang.String).toCharArray()");
        HEX_CHARS = charArray;
    }

    public static final String toHex(byte[] bArr4) {
        Intrinsics.checkNotNullParameter(bArr4, "<this>");
        StringBuffer stringBuffer = new StringBuffer();
        for (byte b : bArr4) {
            char[] cArr = HEX_CHARS;
            stringBuffer.append(cArr[(b & 240) >>> 4]);
            stringBuffer.append(cArr[b & 15]);
        }
        String stringBuffer2 = stringBuffer.toString();
        Intrinsics.checkNotNullExpressionValue(stringBuffer2, "result.toString()");
        return stringBuffer2;
    }
}

// APIKeyHash 
package club.redrocket.lightbulb;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
/* compiled from: APIKeyHash.kt */
@Metadata(d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0012\n\u0002\b\u0005\n\u0002\u0010\u0002\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u000f\b\u0000\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\u000e\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0007\u001a\u00020\u0003J\b\u0010\b\u001a\u00020\tH\u0002J\u0006\u0010\n\u001a\u00020\tR\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u000e\u0010\u0005\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u000b"}, d2 = {"Lclub/redrocket/lightbulb/APIKeyHash;", "", "key", "", "([B)V", "s", "hash", "plaintext", "initializeS", "", "reset", "app_release"}, k = 1, mv = {1, 8, 0}, xi = 48)
/* loaded from: /tmp/jadx-2851593408582218842.dex */
public final class APIKeyHash {
    private final byte[] key;
    private final byte[] s;

    public APIKeyHash(byte[] bArr) {
        Intrinsics.checkNotNullParameter(bArr, "key");
        this.key = bArr;
        this.s = new byte[256];
        if ((bArr.length == 0) || bArr.length > 256) {
            throw new IllegalArgumentException("key length must be between 1 and 256");
        }
        initializeS();
    }