Tags: burrowswheeler 

Rating:

# ASIS CTF Finals 2016 : oracle-354

**Category:** Reverse
**Points:** 354
**Solves:** 0
**Description:**

The resident oracle of our temple has risen his prices too high. If only we could understand his [inner workings](oracle.txz), we could get our answers for free.

Hint: [Burrows–Wheeler transform](https://en.wikipedia.org/wiki/Burrows%E2%80%93Wheeler_transform)

## Write-up

by [0xf4b](https://github.com/0xf4b)

The task has been solved after the end of the CTF.

The binary is a static 64-bits ELF.

```
$ file oracle
oracle: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=b986a272dac0f10f199e3114a358c03aef13f458, stripped
```

### main

The main function asks for the flag, and uses the last character as the number of rounds.

Here is the algorithm used:
```
for i in xrange(rounds):
flag = burrows_wheeler_bits(flag)
flag = xor(flag, 0x8f ^ i)
```

The Burrows-wheeler transform is not directly performed on the flag, but on its binary representation (string composed of "0" and "1" characters).

```
burrows_wheeler_bits("x}") == burrows_wheeler("111100001111101") == "100101111111100"
```

The final flag is hex-encoded and compared to:
```
8f02030966689a88a70e46d87260834943327b43956802b72ef9937f94e34c6bceac062454f1
```

### Inversion

The main problem is to invert the Burrows-wheeler transform without having a terminating character in the input string.

Inverting the matrix is a simple problem, however finding which row is the input is not possible without any knowledge on the input.

We know two conditions:
- the input for each round has its leading character set to "1" (because the transform strips leading 0's)
- the input for each round (except first) is a Burrows-Wheeler output from previous round, xored with the round-key (0x8f^round_index), so its trailing character should be "0" xored with the previous round key

These two conditions gives us 4 times less rows as candidates.

The remaing rows should each be tested as candidates of a Burrows-Wheeler output, until the inversion fails (yes, inversion can fail!).

A fail can be detected if all the rows of the matrix are not rotations of the first row.

All these conditions allow to perform a bruteforce, which retrieves the flag after ~2150s using pypy on a single core.

```
$ time pypy invert_oracle.py 8f02030966689a88a70e46d87260834943327b43956802b72ef9937f94e34c6bceac062454f1
FLAG ASIS{0b9323e43f1b0888e8bb7d270434113a}
pypy invert_oracle.py 2155,71s user 1,19s system 99% cpu 35:58,71 total
```

Script :
```
#!/usr/bin/python

import sys

fu = sys.argv[1].decode("hex")
xz = len(fu)

start = ord('}')-1

def rol(s,j):
return s[j:] + s[:j]

def invert_burrows(my_input, i):

xored = "".join(chr( ord(x) ^ 0x8f ^ i) for x in my_input)
xored_int = int(xored.encode("hex"),16)
xored_bin = bin(xored_int)[2:]

str_len = len(xored_bin)

### Retrieve matrix
final_col = [ x for x in xored_bin ]
first_col = [ x for x in xored_bin ]
first_col.sort()

tmp_matrix = first_col[:]
for j in xrange(1, str_len, 1):
bigrams=[]
for k in xrange(len(final_col)):
bigrams.append(final_col[k]+tmp_matrix[k])
bigrams.sort()
tmp_matrix = bigrams[:]

### Check if matrix is ok
matrix_ok = True

#compute permutations of bigrams[0]
perms=[]
for k in xrange(len(bigrams)):
rotation=rol(bigrams[0],k)
perms.append(rotation)

for k in bigrams:
if k not in perms:
matrix_ok = False
break

### Recursive Burrows-Wheeler, or stop if last round
if matrix_ok:
if i == 0:
# Last round
if len(bigrams[0]) == (xz*8-1): # Leading bit should be 0 (printable char)
for row in bigrams:
if row.startswith("1000001010100110100100101010011"): # ASIS
print "FLAG",hex(int(row,2))[2:-1].decode("hex")
sys.exit(0)
else:
# Go for previous round
for k in bigrams:
if k.startswith("1"): # Filter only leading "1"
if (k.endswith("0") and ( ((i-1)^0x8f) & 0x1) == 0) or (k.endswith("1") and ( ((i-1)^0x8f) & 0x1) == 1): # Filter only trailing "0"

new_input = "%x" % int(k,2)
if len(new_input)%2 != 0:
new_input = "0"+new_input
new_input = new_input.decode("hex")

invert_burrows(new_input,i-1)

invert_burrows(fu,start)

```

## Other write-ups and resources

* https://github.com/ctfs/write-ups-2016/tree/master/asis-ctf-2016/reverse/oracle-354

Original writeup (https://github.com/ctfs/write-ups-2016/tree/master/asis-ctf-2016/reverse/oracle-354).