Taking a look at the binary in your favorite disassembler, we can notice that its compiled with AddressSanitizer, which makes the control-flow graph more complex.
In the main, we can see that it does the following :
* It asks for a category and a link* It reads the content of a file `src/key`* Same with `src/iv`* Same with `src/collection`, but it tries to parse its content this time.* it does some formatting using `sprintf`* then some AES-CBC encryption, probably using the key and iv read before.* and finally prints the encrypted result
The parsing consist of repeating the same thing 6 times :
It starts by finding the next space in the data, then finding the next newline, and splitting the current line on the space. Then it stores the two elements resulting of the splits, and go to the next line.We can deduce that the `src/collection` file must have 6 lines, each composed of two words.
The program then sort and format the strings it just read, encrypt the result and prints it.
We can summarize what its doing by the following python scripts (without the sorting stuff, which does not really matters):
```pythonfrom Crypto.Cipher import AES
with open("src/key", "rb") as keyfd, open("src/iv", "rb") as ivfd, open("src/collection", "rb") as collfd:    iv = ivfd.read(16)    key = keyfd.read(16)
    print("category ?")    cate = input()    print("link ?")    link = input()
    finalstr = cate + "{" + link + "}"    for line in collfd.read().decode().strip().split("\n"):        start,end = line.split(" ")        finalstr += " " + start + "{" + end + "}"
    finalstr += "\x00" * (16 - (len(finalstr) % 16))
    print("Encrypted : {}".format(AES.new(key, AES.MODE_CBC, iv).encrypt(finalstr.encode()).hex()))```
As our input is prepended to some string and then encrypted, that code is vulnerable to an "byte-at-a-time" attack, meaning we can take it as an encryption oracle (hence its name), and repeatedly call it with crafted categories and links to retrieve the content of the `src/collection` file on the server.
That type of attack is well described at many places on the net (its the same as the [ECB-byte-at-a-time on cryptopals](https://cryptopals.com/sets/2/challenges/12) for instance) plus its not that hard to understand, so i wont describe it any further.
The only thing to watch out for is that our category and link are prepended as "category{link}", so we have to make sure that the link is aligned on a 16 bytes boundary in the final string, meaning we have to input a category 15 bytes long.
Here is my implementation of the attack :
```python
from Crypto.Cipher import AESfrom pwn import *
context.log_level = 'error'
# Stuff to test for the attack locallyLOCAL = Falsefinalstr = ""with open("src/key", "rb") as keyfd, open("src/iv", "rb") as ivfd, open("src/collection", "rb") as collfd:    iv = ivfd.read(16)    key = keyfd.read(16)
    for line in collfd.read().decode().strip().split("\n"):        start,end = line.split(" ")        finalstr += " " + start + "{" + end + "}"
def local_oracle(cate, link):    plain = cate + "{" + link + "}" + finalstr    plain += "\x00" * (16 - (len(plain) % 16))
    enc = AES.new(key, AES.MODE_CBC, iv).encrypt(plain.encode()).hex()
    print("plain={}, len={}, enc={}".format(plain.encode().hex(), len(plain), enc))    return enc
def remote_oracle(cate, payload):
    r = remote("flu.xxx", 2010)    r.sendline(cate)    r.sendline(payload)
    data = r.recvline().decode().lstrip("category? link? encrypted: ").strip()    r.close()
    return data
def encryption_oracle(payload):
    category = "a"*15
    if LOCAL:        return local_oracle(category, payload)[32:]    else:        return remote_oracle(category, payload)[32:]
def attack(blocksize, known):    index = len(known) // blocksize    prefix = 'a' * (blocksize - len(known) % blocksize - 1)    lookup = {}
    substring = encryption_oracle(prefix)[32 * index:32 * (index + 1)]
    for char in range(0x20, 0x7f):        char = chr(char)        idx = encryption_oracle(prefix+known+char)[index * 32:(index + 1) * 32]
        if idx == substring:            return char
plain = ''while attack(16, plain) != None:    print(plain)    plain += attack(16, plain)
print("plain : ", plain)
```
It takes approximately ~45 minutes to retrieve the two first lines of the file (the flag is in the second line), and i didn't note it so i won't put it there :P.
