Home Buckeye CTF 2022 - soda - Writeup
Post
Cancel

Buckeye CTF 2022 - soda - Writeup

challenge-description

Soda workflow

The first we have to do is decompile the jar file. A great tool for that is Java decompiler jd-gui:

1
jd-gui soda.jar

Now that we can see the source code, it’s time to go over it and run the program a couple of times to get an idea of what’s happening. After some code analysis, here’s a typical workflow of the soda machine.

First, we purchase a drink and, unfortunately for us, it always gets stuck.

pic-3

Now, we can punch, kick, shake or shatter the vending machine, but this will not get us anywhere.

To help free our soda, we have to get rid of soda.Drink.DrinkStatus.STUCK status. We can do this by calling a tap function:

1
2
3
4
5
6
 public void tap() {
    for (byte b = 0; b < 12; b++) {
      if ((this.drinks[b]).status == soda.Drink.DrinkStatus.STUCK && (this.drinks[b]).stuck > 0)
        (this.drinks[b]).stuck--; 
    } 
  }

How many times do we have to tap? 3 times - we can get that info from class Drink property stuck:

1
2
3
4
5
6
7
8
class Drink {
  float cost = (float)(Math.random() * 6.0D);
  
  DrinkStatus status = (Math.random() > 0.75D) ? DrinkStatus.EMPTY : DrinkStatus.READY;
  
  int stuck = 3;

 // more code ...

pic-4

Okay, now that the drink has loosen up, we can try to reach for it and knock it down. However:

pic-5

There are people around and we have to wait. How long?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (paramArrayOfString[0].equalsIgnoreCase("wait")) {
      int i = 0;
      try {
        i = Integer.parseInt(paramArrayOfString[1]);
      } catch (Exception exception) {
        System.out.println(">> Not sure what you mean");
        return;
      } 
      pause(i);
      if (i >= 10) {
        bystanders = false;
        System.out.println(">> ...Looks like nobody's around...");
      } else {
        bystanders = true;
        System.out.println(">> People are walking down the street.");
      } 
      return;

At least 10 seconds.

pic-6

Now we can try to reach for our drink:

pic-7

Now that it had fallen, we can grab it:

pic-8

And … we don’t get the flag.

retrieve() my drink!

There is an interesting thing going on in the retrieve() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  public void retrieve() {
    byte b = -1;
    float f = -1.0F;
    for (byte b1 = 0; b1 < 12; b1++) {
      if ((this.drinks[b1]).status != soda.Drink.DrinkStatus.EMPTY && 
        (this.drinks[b1]).cost > f) {
        b = b1;
        f = (this.drinks[b1]).cost;
      } 
    } 
    if ((this.drinks[b]).status == soda.Drink.DrinkStatus.DROPPED) {
      soda.printFlag();
    } else {
      System.out.println(">> No flags in here... was the prophecy a lie...?");
    } 
  }

When we use grab command, retrieve() function is called. And contrary to what we would probably expect, it does not really retrieve the drink we ask for. Instead, it will iterate over all of the drinks, and choose the most expensive, not empty drink there is. Then, it will check if this drink is equal to the one we decided to purchase at the very beginning and then heroically rescue - only then will it give us the flag.

So basically what we have to do to get he flag is buy the most expensive drink there is. This can be tricky because all soda prices are random and can sometimes exceed 5$ budget we have. No problem - we just keep reconnecting to the server until the most expensive (and not empty) drink is under 5$. In the example below, soda number 4 meets the requirements for most expensive and not empty:

pic-9

Then, it is the matter of repeating the whole procedure and we get the flag:

pic-10

This post is licensed under CC BY 4.0 by the author.