Rating: 5.0

After connecting to the server, we are in a bash. Let's try `getflag`, as suggested by the challenge description:

```
bash: cannot set terminal process group (21183): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ getflag
There are still 13 locks locked. No flag for you.
bash-5.0$
```

But what exactly is `getflag` anyway? Let's find out:

```
bash-5.0$ type getflag
getflag is a shell builtin
bash-5.0$
```

This is interesting. They modified the bash and added a new builtin. We
need that bash binary to figure out what's going on, so let's just dump
it:

```
echo 'gzip -ck9 /bin/bash | base64' | nc ooobash.challenges.ooo 5000 > bash.gz.b64
```

After removing the first few lines of output as well as the bash prompt
at the end, and decompressing the whole thing, we have the bash binary.
Let's run it locally:

```
% ./ooobash
[error] token not found
```

Weird. This shell wants to read a token file? Where is it located? Let's
figure it out with `strace`:

```
...
openat(AT_FDCWD, "/etc/ooobash/token", O_RDONLY) = -1 ENOENT (No such file or directory)
...
```

I guess we need that file. Let's extract it with the same method:

```
% echo 'cat /etc/ooobash/token | base64 -w0' | nc ooobash.challenges.ooo 5000
bash: cannot set terminal process group (5001): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ cat /etc/ooobash/token | base64 -w0
cat: /etc/ooobash/token: Permission denied
bash-5.0$
```

Now that's interesting. But the bash *did* read it. Let's try again:

```
% echo 'cat /etc/ooobash/token | base64 -w0' | nc ooobash.challenges.ooo 5000
bash: cannot set terminal process group (21216): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ cat /etc/ooobash/token | base64
H1kOFBxMYsjJZKgLb4q+sgPEpIFIvPmjrVwNfXpdDDA=
bash-5.0$
```

Weird. There seems to be some race condition, and if we are lucky, we
can read the file. Good. But maybe there is something else in that
`/etc/ooobash` directory? Let's find out:

```
% echo 'ls /etc/ooobash' | nc ooobash.challenges.ooo 5000
bash: cannot set terminal process group (5022): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ ls /etc/ooobash
flag
state
token
bash-5.0$
```

So let's extract the other two files too.

The flag file (base64):

```
1bRuk/tqIIIoxPdyhBuerzktGaAkbZM/JRDfTOtNhVrP7bRkNL/b8lKVuPeoRcoqi9OfwJzlPsUL
6r54nK2lJE87O6HTqEDSjTxoDrG+3pk=
```

The state file (again base64):

```
EiH5VzrDVKDKUYRLGs1bnYws9DmkkP9MUr+WOsT2+qE=
```

Decoding those files only gives us seemingly random data, so how do we use
them? Clearly the ooobash binary knows how to do it, so let's reverse
engineer that `getflag` builtin. The code of the builtin:

```c
int getflag_builtin()
{
FILE* file;
size_t flaglen;
char* flag;
char zero[32];
char out[104];

memset(zero, 0, 32);
if(!memcmp(oootoken, zero, 32)) {
printf("[error] you need to execute this on the remote server\n");
} else if(leftnum <= 0) {
file = fopen("/etc/ooobash/flag", "rb");
if(!file) {
printf("[error]\n");
exit(1);
}
fseek(file, 0, 2);
flaglen = ftell(file);
fseek(file, 0, 0);
flag = (char *)malloc(flaglen + 1);
fread(flag, 1, flaglen, file);
fclose(file);
flag[flaglen] = 0;

out[aes_decrypt(flag + 16, flaglen - 16, ooostate, flag, out)] = 0;
printf("You are now a certified bash reverser! The flag is %s\n", out);
} else {
printf("There are still %d locks locked. No flag for you.\n", leftnum);
}
return 0;
}
```

This shell builtin checks if all locks are unlocked, and then reads the
flag, decrypts it with AES, and prints the result.

But what's that `ooostate`? Looking at the xrefs, we find another
function, which seems to initialize the ooostate:

```c
void init_ooostate()
{
FILE* f;
int i;

memset(ooostate, 0, 32);
f = fopen("/etc/ooobash/state", "rb");
if(!f) {
printf("[error] state not found\n");
exit(1);
}
fread(ooostate, 32, 1, f);
fclose(f);

for(i = 0; i < LOCKSNUM; i++) {
locks[i] = 1;
}
}
```

And directly next to that function, there is another one:

```c
void init_oootoken()
{
FILE* f;

memset(oootoken, 0, 32);
f = fopen("/etc/ooobash/token", "rb");
if(!f) {
printf("[error] token not found\n");
exit(1);
}
fread(oootoken, 32, 1, f);
fclose(f);
}
```

Those init functions read the token and the state. So all we have to do
now is look at the `aes_decrypt` function, which looks like this:

```c
int aes_decrypt(char* data, int len, char* key, char* iv, char* out)
{
EVP_CIPHER_CTX* ctx;
const EVP_CIPHER* type;
int outm;
int outl;

if(!(ctx = EVP_CIPHER_CTX_new()))
handleErrors();
type = EVP_aes_256_cbc();
if(EVP_DecryptInit_ex(ctx, type, 0, key, iv) != 1)
handleErrors();
if(EVP_DecryptUpdate(ctx, out, &outl, data, len) != 1)
handleErrors();
if(EVP_DecryptFinal_ex(ctx, &out[outl], &outm) != 1)
handleErrors();

EVP_CIPHER_CTX_free(ctx);
return outl + outm;
}
```

It's just a normal AES decryption routine, which decrypts the flag with
the `ooostate`.

So let's try to just make a simple C program which reads the three files
and calls `aes_decrypt`, without all the lock stuff. Turns out that
doesn't work and we get an error:

```
140595290311552:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:crypto/evp/evp_enc.c:583:
```

Maybe we overlooked something? Yeah, we overlooked the `oootoken` which
isn't used so far, and we didn't realize that the `ooostate` is modified
whenever a lock is unlocked.

```c
void update_ooostate(char* keyword, unsigned int idx)
{
size_t len;
int i;
char hash[32];
char buf[164];

assert(strlen(keyword) < 100);
assert(idx < LOCKSNUM);
if(locks[idx]) {
locks[idx] = 0;
printf("unlocking %s (%d)\n", keyword, idx);
len = strlen(keyword);
memcpy(buf, oootoken, 32);
memcpy(buf + 32, keyword, len + 1);
memcpy(buf + 32 + len, oootoken, 32);
SHA256(buf, len + 64, hash);
for(i = 0; i < 32; i++)
ooostate[i] ^= hash[i];
leftnum--;
} else {
printf("lock %d was already unlocked\n", idx);
}
}
```

Now we just need those keywords. We can easily find them by checking all
xrefs to `update_ooostate`. Those are the calls:

```c
update_ooostate("unlockbabylock", 0);
update_ooostate("badr3d1r", 1);
update_ooostate("verysneaky", 2);
update_ooostate("leetness", 3);
update_ooostate("vneooo", 4);
update_ooostate("eval", 5);
update_ooostate("ret", 6);
update_ooostate("n3t", 7);
update_ooostate("sig", 8);
update_ooostate("yo", 9);
update_ooostate("aro", 10);
update_ooostate("fnx", 11);
update_ooostate("ifonly", 12);
```

If we add this to our decoding program, after `init_ooostate` and
`init_oootoken`, but before reading/decoding of the flag, we finally get
the flag:

```
You are now a certified bash reverser! The flag is OOO{r3VEr51nG_b4sH_5Cr1P7s_I5_lAm3_bU7_R3vErs1Ng_b4SH_is_31337}
```

The complete program code is available here:
[solve.c](https://www.sigflag.at/assets/posts/ooobash/solve.c)

… and this is how you solve a pwn challenge without even interacting with the binary at all. This was
obviously not the intended solution, and it only worked because of the
race condition which allowed us to sometimes read the key files.

Original writeup (https://www.sigflag.at/blog/2020/defconq2020-ooobash/).