If you are not strong enough to beat the boss, you need to find another way to win the game!

Category: Misc

Solver: t0b1

## Writeup#

In this challenge we get a binary. As the description says, we `need to find another way to win the game!`. Running the binary shows that its a little game. At first we can choose between two game modes. Obviously the `Izi!` mode is not available ;).

Choosing the hard mode shows some statistics about our character and another menu. This time we can create a profile, play the game, claim or prize or exit. Claiming the prize looks interesting, but yields `No prize for you! Try again!` right now.

At this point we fire up Ghidra on the binary and try to figure out what determines our price. We find the following `prize` method.

``````void prize(void)

{
long lVar1;
long in_FS_OFFSET;
int local_14;

lVar1 = *(long *)(in_FS_OFFSET + 0x28);
bf = bf + -1;
check_bf();
local_14 = 0;
while (local_14 < 8) {
flag = (uint)(*(char *)((long)&temp_pass + (long)local_14) != passw[local_14 + 0xf8]);
local_14 = local_14 + 1;
}
if (flag != 0) {
fwrite(&DAT_00101728,1,0x3d,stdout);
system("cat flag.txt");
/* WARNING: Subroutine does not return */
exit(0x122);
}
fwrite("No prize for you!\nTry again!\n",1,0x1d,stderr);
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
``````

The `system("cat flag.txt");` is where we want to get to. To get there, the `flag` variable has to be 1. In the lines above we see that the flag variable is set. A loop is iterating over some `temp_pass` array and checking if it is equal to the last 8 characters in the `passw` array. As `flag` is always reassigned, only the last run of the loop is interesting to us. For the `flag` to be 1, the last character of the `temp_pass` has to be different to the last character of the `passw`.

We need to find out, what the `temp_pass` and `passw` array are and how they get modified. The `temp_pass` array is 8 bytes long, the `passw` array is 256 bytes long.

Looking through the `main` function, we find that the `intro` function is called in the beginning. It contains the following lines.

``````__fd = open("/dev/urandom",0);
if (__fd == -1) {
fwrite("Could not open \"/dev/urandom, exiting..\"\n",1,0x29,stderr);
/* WARNING: Subroutine does not return */
exit(0x100);
}
local_20 = 0;
while (local_20 < 8) {
*(undefined1 *)((long)&temp_pass + (long)local_20) = passw[local_20 + 0xf8];
local_20 = local_20 + 1;
}
``````

The program reads 8 bytes from `/dev/urandom` into `passw + 0xf8` which is essentially the last 8 bytes of `passw`. Afterwards those bytes are stored in the `temp_pass` array as well. The `temp_pass` is never written again. So it is used as some sort of sanity check to prevent us from getting the flag.

Now we need to know where we can influence the content of `passw`. This can only be done in the `create_profile` method which looks as follows.

``````void create_profile(void) {
long lVar1;
int iVar2;
long lVar3;
char *__src;
long in_FS_OFFSET;
char *local_30;
size_t local_28;

lVar1 = *(long *)(in_FS_OFFSET + 0x28);
iVar2 = strncmp(passw,"Super-",6);
if (iVar2 == 0) {
local_30 = passw + 5;
while (*local_30 == '-') {
local_30 = local_30 + 1;
}
*local_30 = '\0';
fwrite(&DAT_00101528,1,0x4f,stdout);
if (lVar3 == 2) {
fwrite("\nHow many Attack points you want to add? Max 120 pts!\n> ",1,0x38,stdout);
attack = attack + (int)local_28;
}
else {
if (lVar3 == 3) {
fwrite("\nHow many Agility points you want to add? Max 120 pts!\n> ",1,0x39,stdout);
speed = speed + (int)local_28;
}
else {
if (lVar3 != 1) {
fwrite("\nInvalid Option!\n",1,0x11,stderr);
goto LAB_00100eac;
}
fwrite("\nHow many Health points you want to add? Max 120 pts!\n> ",1,0x38,stdout);
hp = hp + (int)local_28;
}
}
if (((long)local_28 < 0x79) && (0 < (long)local_28)) {
fwrite("\nInsert a catch-phrase for your character!\n> ",1,0x2d,stdout);
__src = (char *)malloc(local_28);
strncat(passw,__src,0x100);
}
else {
fwrite("\nYou do not have enough points!\n",1,0x20,stderr);
}
}
else {
fwrite("\nInvalid name!\n\n",1,0x10,stderr);
}
LAB_00100eac:
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
``````

First we can enter our super name. This can be 13 characters long and must start with `Super-` as checked by `strncmp` to not get the `Invalid name!` output. The name is saved in the beginning of the `passw` array. The program checks our name for `-` and sets the byte after our last `-` to the null byte. Inputting the name `Super--------` will thus results in the `passw` array to start with `Super--------\0`. The loop that iterates over the `-` is not bound by any length. It simply follows all `-` and places a `\0` after the end. This might be unsafe.

We can now choose whether we want to increase our attack, health or agility points and by what amount we want them to increase. If that amount is lower than `121` we are allowed to enter a catch phrase for our super character. The catch phrase we enter can be as long as our amount of points we chose before. The phrase we entered will be appended to the `passw` as well using `strncat`. The strange thing here is, that `strncat` is called with `0x100` which means that it will read up to 256 byte from the `__src` and appends it to the `passw` array.

Sadly this won’t help us, as memory allocated by malloc always has some padding which is mostly 0 and `strncat` would stop there.

An important thing to notice is the following, extracted from the manpage of `strncat`:

The strcat() function appends the src string to the dest string, overwriting the terminating null byte (’\0’) at the end of dest, and then adds a terminating null byte.

`strncat` will always overwrite the terminating null byte of our `passw` and append another null byte after the string. That means, if we enter the catch phrase `abcde`, the `passw` array will look like `Super--------abcde\0`.

After entering our catch phrase, we are taken back to the main menu and a credit is subtracted. We can now choose to create a profile again. Furthermore, the `passw` array is not resetted, the program just overwrites our contents.

In the second run of `create_profile`, our `passw` array already contains the string `Super--------abcde\0`. Entering our super name again results in `Super--------abcde\0`. Now the while loop iterates over all `-` and write the null byte at the end, we get `Super--------\0bcde\0`. If we now enter a catch phrase, it will again be added to the array after the first null byte.

However, if our catch phrase entered previously only consists of `-` instead of `abcde`, the while loop will iterate over all of them and append the null byte at the end. Our `passw` array looks like `Super-------------\0`. The `strncat` function will now append our catch phrase way more inside the array than before!

As we have four credits, we can run the `create_profile` method a maximum of four times. Each time we can append up to 120 `-` to our `passw` array. The `passw` array is 256 bytes long. That is enough to overwrite the last byte!

Our super name is already 13 bytes long. That means we need to overwrite 243 bytes in order to change the last byte. That means if we create a new profile three times, always enter our super name `Super--------` and a catch phrase of 81 `-` we must have overwritten the last byte and are able to claim our price! Afterwards we simple choose to claim our prize and we get the flag!

We can automate that idea using python and the pwntools library. The following script solves the task for us.

``````#!/usr/bin/env python

from pwn import *

local = False
proc = process('./arcade') if local else remote('docker.hackthebox.eu', 31960)

print(proc.recvuntil('> '))
proc.send('2\n') # mode
print(proc.recvuntil('> '))
for i in range(3): # 3 * 81 = 243
proc.send('1\n') # create profile 1
And we get the flag: `HTB{1ts_4_m3_fl4gioo!}`