Ph0wn 2019 - Double Rainbow Level 2

Reverse engineering Windows 10 IoT Core applications

Description of the challenge

This time, Katy has not done the same mistake and she asked Britney to validate the challenge. Good Luck!

Same principle for this Level than for Level 1: We provide on USB keys a compressed image of the microSD card. Find the code using this image and validate your finding on the actual hardware to get the flag (same hardware than for Level 1).

Attachments:

  • A photo of the hardware (same than for Level 1)
  • The schema of the hardware used by the challenge (same than for Level 1)

Attachments

Photo of the hardware

Schema of the hardware

Note: The original schema had a mistake with the white LED.

Solution

Image decompression and mounting

We can start the same way we did for Level 1.

The image double-rainbow-level2.img.bz2 is compressed so the first thing to do is to decompress it:

bunzip2 double-rainbow-level2.img.bz2

We get a file double-rainbow-level2.img of 7.5 GiB.

The next thing to do is to identify the partitions in this disk image:

fdisk -l double-rainbow-level2.img
ainbow-level2.img: 7.41 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xae420040

Device                     Boot   Start      End  Sectors  Size Id Type
double-rainbow-level2.img1 *       4096   135167   131072   64M  c W95 FAT32 (LBA)
double-rainbow-level2.img2       147456  3074047  2926592  1.4G  7 HPFS/NTFS/exFAT
double-rainbow-level2.img3      3074048  3076095     2048    1M 70 DiskSecure Multi-Boot
double-rainbow-level2.img4      3076096 15523839 12447744    6G  5 Extended
double-rainbow-level2.img5      3080192 15523839 12443648    6G  7 HPFS/NTFS/exFAT

In order to mount the Data partition we type:

mkdir /mnt/data
mount -t ntfs -o loop,ro,offset=$((3080192*512)) double-rainbow-level1.img /mnt/data

Note: 3080192 is given by fdisk (Start column). 512 is the number of bytes per allocation unit.

/mnt/data/ now contains the Data partition:

total 307252
drwxrwxrwx 1 root root      4096 Oct 10 21:23  .
drwxr-xr-x 4 root root      4096 Dec 17 21:19  ..
-rwxrwxrwx 1 root root     21856 Oct 10 20:52 '$UGM'
drwxrwxrwx 1 root root         0 Oct 27  2018  CrashDump
-rwxrwxrwx 1 root root 314572800 Oct 27  2018  DedicatedDumpFile.sys
-rwxrwxrwx 1 root root         0 Oct 27  2018  FirstBoot.Complete
drwxrwxrwx 1 root root         0 Oct 27  2018  Logfiles
drwxrwxrwx 1 root root         0 Oct 27  2018  ProgramData
drwxrwxrwx 1 root root         0 Oct 27  2018  Programs
drwxrwxrwx 1 root root         0 Oct 27  2018  SharedData
drwxrwxrwx 1 root root         0 Oct 27  2018  SystemData
drwxrwxrwx 1 root root         0 Oct 10 20:03 'System Volume Information'
drwxrwxrwx 1 root root         0 Oct 27  2018  test
drwxrwxrwx 1 root root         0 Oct 27  2018  Users
drwxrwxrwx 1 root root         0 Oct 27  2018  Windows 

Find the application

So far, so good, it is almost the same than for Level 1 except some dates which are different.

find . -iname "double*"

Find gives a different result than for Level 1:

./ProgramData/Microsoft/Windows/AppRepository/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2.xml
./ProgramData/Microsoft/Windows/AppRepository/DoubleRainbow2-uwp_1.0.0.0_neutral_~_jnm4aweva4ye2.xml
./ProgramData/Microsoft/Windows/AppRepository/Packages/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2
./ProgramData/Microsoft/Windows/AppRepository/Packages/DoubleRainbow2-uwp_1.0.0.0_neutral_~_jnm4aweva4ye2
./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2
./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2/DoubleRainbow2-uwp.dll
./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2/DoubleRainbow2-uwp.exe
./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2/DoubleRainbow2.winmd
./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_neutral_~_jnm4aweva4ye2
./Users/DefaultAccount/AppData/Local/Packages/DoubleRainbow2-uwp_jnm4aweva4ye2

In Level 1, the application was deployed for debugging. It is not the case here, it is deployed as a regular package in ./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2:

Get access to the application binary

ls -la ./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2
ls: cannot access './Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2/clrcompression.dll': Input/output error
ls: cannot access './Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2/DoubleRainbow2-uwp.dll': Input/output error
ls: cannot access './Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2/DoubleRainbow2-uwp.exe': Input/output error
total 128
drwxrwxrwx 1 root root 16384 Oct 27  2018 .
drwxrwxrwx 1 root root 16384 Oct 27  2018 ..
-rwxrwxrwx 1 root root  2664 Oct 27  2018 AppxBlockMap.xml
-rwxrwxrwx 1 root root  4373 Oct 27  2018 AppxManifest.xml
drwxrwxrwx 1 root root     0 Oct 27  2018 AppxMetadata
-rwxrwxrwx 1 root root  1672 Oct 27  2018 AppxSignature.p7x
drwxrwxrwx 1 root root 16384 Oct 27  2018 Assets
-????????? ? ?    ?        ?            ? clrcompression.dll
-????????? ? ?    ?        ?            ? DoubleRainbow2-uwp.dll
-????????? ? ?    ?        ?            ? DoubleRainbow2-uwp.exe
-rwxrwxrwx 1 root root  3584 Oct 27  2018 DoubleRainbow2.winmd
drwxrwxrwx 1 root root     0 Oct 27  2018 microsoft.system.package.metadata
-rwxrwxrwx 1 root root  1928 Oct 27  2018 resources.pri

We get I/O errors! Is the image corrupted?

IMPORTANT: Depending of your operating system, we may not have these errors. This is because your operating system may already have required NTFS plugin. This is not the case of a fresh Kali Linux.

Let's try something else:

find . -ls
     1326     16 drwxrwxrwx   1 root     root        16384 Oct 27  2018 .
     1330     16 -rwxrwxrwx   1 root     root         2664 Oct 27  2018 ./AppxBlockMap.xml
     1329     16 -rwxrwxrwx   1 root     root         4373 Oct 27  2018 ./AppxManifest.xml
     1331      0 drwxrwxrwx   1 root     root            0 Oct 27  2018 ./AppxMetadata
     1332     16 -rwxrwxrwx   1 root     root         2313 Oct 27  2018 ./AppxMetadata/CodeIntegrity.cat
     1333     16 -rwxrwxrwx   1 root     root         1672 Oct 27  2018 ./AppxSignature.p7x
     1327     16 drwxrwxrwx   1 root     root        16384 Oct 27  2018 ./Assets
     1150     16 -rwxrwxrwx   2 root     root         1430 Oct 27  2018 ./Assets/LockScreenLogo.scale-200.png
     1151     16 -rwxrwxrwx   2 root     root         7700 Oct 27  2018 ./Assets/SplashScreen.scale-200.png
     1152     16 -rwxrwxrwx   2 root     root         2937 Oct 27  2018 ./Assets/Square150x150Logo.scale-200.png
     1153     16 -rwxrwxrwx   2 root     root         1647 Oct 27  2018 ./Assets/Square44x44Logo.scale-200.png
     1154     16 -rwxrwxrwx   2 root     root         1255 Oct 27  2018 ./Assets/Square44x44Logo.targetsize-24_altform-unplated.png
     1155     16 -rwxrwxrwx   2 root     root         1451 Oct 27  2018 ./Assets/StoreLogo.png
     1156     16 -rwxrwxrwx   2 root     root         3204 Oct 27  2018 ./Assets/Wide310x150Logo.scale-200.png
     1337      0 lrwxrwxrwx   1 root     root           25 Oct 27  2018 ./clrcompression.dll -> unsupported\ reparse\ point
     1334      0 lrwxrwxrwx   1 root     root           25 Oct 27  2018 ./DoubleRainbow2-uwp.dll -> unsupported\ reparse\ point
     1335      0 lrwxrwxrwx   1 root     root           25 Oct 27  2018 ./DoubleRainbow2-uwp.exe -> unsupported\ reparse\ point
     1336     16 -rwxrwxrwx   1 root     root         3584 Oct 27  2018 ./DoubleRainbow2.winmd
     1323      0 drwxrwxrwx   1 root     root            0 Oct 27  2018 ./microsoft.system.package.metadata
     1338     16 -rwxrwxrwx   1 root     root         1928 Oct 27  2018 ./resources.pri

This time, we have unsupported\ reparse\ point. What is a reparse point? It is a mechanism to extend NTFS.

Windows 10 NTFS Compression

When asking Mr. Google, you get (for example) on the NTFS-3G page of ArchLinux:

Troubleshooting

Compressed files

If you have a Windows 10 partition and when accessing files/directories,

  1. you see broken symbolic links to 'unsupported reparse point', or

  2. you see the error message "cannot access <filename>: Input/output error" (in this case you see in /var/log/messages "Could not load plugin /usr/lib64/ntfs-3g/ntfs-plugin-80000017.so: Success")</filename>

then install ntfs-3g-system-compression. This plugin handles system-compressed files.

There exists some other types of reparse points that ntfs-3g does not support by default. See this page for a list of available plugins.

And we have indeed a Windows 10 image and it makes sense to compress executables that are not supposed to change. Some details are available in the article How Windows 10 achieves its compact footprint.

To get a confirmation that it is our problem, we can check the NTFS attribute ntfs_reparse_data of the file:

getfattr -h -n system.ntfs_reparse_data -e hex DoubleRainbow2-uwp.dll
# file: DoubleRainbow2-uwp.dll
system.ntfs_reparse_data=0x170000801000000001000000020000000100000002000000

The beginning of the data (in reverse order) is the tag for compression: 0x80000017.

So how to read such NTFS files under Kali Linux? Fortunately, there is an implementation for Linux (32 and 64-bit) available here: https://jp-andre.pagesperso-orange.fr/advanced-ntfs-3g.html#download

Download and uncompress the file systcomp.zip. It contains the source code but also pre-compiled plugins. The installation on Kali Linus is trivial:

sudo mkdir -p /usr/lib/x86_64-linux-gnu/ntfs-3g/
sudo cp linux-64/ntfs-plugin-80000017.so /usr/lib/x86_64-linux-gnu/ntfs-3g/
sudo chmod 0555 /usr/lib/x86_64-linux-gnu/ntfs-3g/ntfs-plugin-80000017.so

There is no need to reboot the computer, the plugin is loaded when required. We can now try again to list the files:

ls -la ./Programs/WindowsApps/DoubleRainbow2-uwp_1.0.0.0_arm__jnm4aweva4ye2
total 484
drwxrwxrwx 1 root root  16384 Oct 27  2018 .
drwxrwxrwx 1 root root  16384 Oct 27  2018 ..
-rwxrwxrwx 1 root root   2664 Oct 27  2018 AppxBlockMap.xml
-rwxrwxrwx 1 root root   4373 Oct 27  2018 AppxManifest.xml
drwxrwxrwx 1 root root      0 Oct 27  2018 AppxMetadata
-rwxrwxrwx 1 root root   1672 Oct 27  2018 AppxSignature.p7x
drwxrwxrwx 1 root root  16384 Oct 27  2018 Assets
-r-xr-xr-x 1 root root  57088 Oct 27  2018 clrcompression.dll
-r-xr-xr-x 1 root root 651264 Oct 27  2018 DoubleRainbow2-uwp.dll
-r-xr-xr-x 1 root root  18944 Oct 27  2018 DoubleRainbow2-uwp.exe
-rwxrwxrwx 1 root root   3584 Oct 27  2018 DoubleRainbow2.winmd
drwxrwxrwx 1 root root      0 Oct 27  2018 microsoft.system.package.metadata
-rwxrwxrwx 1 root root   1928 Oct 27  2018 resources.pri

And no error this time.

Previous method failed

We can try to decompile the DLL as we do for Level 1 (with ILSpy for example), but it does not work this time. If we compare the two files:

file DoubleRainbow1Lib.dll DoubleRainbow2-uwp.dll
DoubleRainbow1Lib.dll:  PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
DoubleRainbow2-uwp.dll: PE32 executable (DLL) (GUI) ARMv7 Thumb, for MS Windows

we see a major difference: Level 1 was a .Net assembly, Level 2 is a ARMv7 Thumb binary. Even if it was written in C#/.NET, it was compiled and deployed as a native binary.

So we have to find something else.

Identify candidates

What are we looking for exactly? In Level 1, we have code similar to this:

private readonly int[] code1_ = new int[5] {0, 1, 0, 4, 3};

Let's assume Level 2 is similar. How is this code compiled into ARM? We can build a sample project, but it is also reasonable to think that these values are simply compiled as a sequence of 32-bit values. As we have a little endian executable, it means that 0 is compile as 00 00 00 00, 1 as 01 00 00 00, etc. So we can try to identify in the binary such values: a sequence of values in the form 0? 00 00 00 where ? is between 0 and 4 (as we have 5 colors). Let's also assume the length of the code is between 4 and 6.

A small Python 3 script will do the work:

# -*- coding: utf-8 -*-

# Use regex instead of re to use the 'overlapped' option.
# Install with 'pip install regex'.
import regex as re


def excluded(value):
    # Exclude long sequences of zeros
    return re.search(b"[\x00]{12,}", value)


def print_code(offset, value):
    code = [value[i * 4] for i in range(0, len(value) // 4)]
    binary = ' '.join(['{0:02x}'.format(value[i]) for i in range(0, len(value))])
    print(f"O = 0x{offset:x}, C = {code}, B = {binary}")


with open("DoubleRainbow2-uwp.dll", "rb") as stream:
    data = stream.read()
    result = re.finditer(b"([\x00-\x04]\x00\x00\x00){4,6}", data, overlapped=True)
    for r in result:
        if not excluded(r.group(0)):
            print_code(r.span(0)[0], r.group(0))

Note: When trying this script but without the excluded function, we get a lot of matches because executables contain a lot of sequence of zeros. So we exclude any match containing 12 or more sequences of zeros.

It gives the following result:

O = 0x1819c, C = [0, 0, 1, 0, 4, 3], B = 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 04 00 00 00 03 00 00 00
O = 0x181a0, C = [0, 1, 0, 4, 3, 4], B = 00 00 00 00 01 00 00 00 00 00 00 00 04 00 00 00 03 00 00 00 04 00 00 00
O = 0x181a4, C = [1, 0, 4, 3, 4, 0], B = 01 00 00 00 00 00 00 00 04 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00
O = 0x181a8, C = [0, 4, 3, 4, 0, 1], B = 00 00 00 00 04 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00 01 00 00 00
O = 0x181ac, C = [4, 3, 4, 0, 1, 3], B = 04 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00
O = 0x181b0, C = [3, 4, 0, 1, 3, 2], B = 03 00 00 00 04 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00 02 00 00 00
O = 0x181b4, C = [4, 0, 1, 3, 2], B = 04 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00 02 00 00 00
O = 0x181b8, C = [0, 1, 3, 2], B = 00 00 00 00 01 00 00 00 03 00 00 00 02 00 00 00
O = 0x1a7ec, C = [1, 2, 4, 4, 2], B = 01 00 00 00 02 00 00 00 04 00 00 00 04 00 00 00 02 00 00 00
O = 0x1a7f0, C = [2, 4, 4, 2], B = 02 00 00 00 04 00 00 00 04 00 00 00 02 00 00 00
O = 0x1a8bc, C = [3, 1, 0, 3, 2, 0], B = 03 00 00 00 01 00 00 00 00 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00
O = 0x1a8c0, C = [1, 0, 3, 2, 0, 4], B = 01 00 00 00 00 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00 04 00 00 00
O = 0x1a8c4, C = [0, 3, 2, 0, 4], B = 00 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00 04 00 00 00
O = 0x1a8c8, C = [3, 2, 0, 4], B = 03 00 00 00 02 00 00 00 00 00 00 00 04 00 00 00
O = 0x1ad14, C = [1, 0, 4, 1, 0], B = 01 00 00 00 00 00 00 00 04 00 00 00 01 00 00 00 00 00 00 00
O = 0x1ad18, C = [0, 4, 1, 0], B = 00 00 00 00 04 00 00 00 01 00 00 00 00 00 00 00
O = 0x1db24, C = [4, 0, 4, 4], B = 04 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00
O = 0x1dbac, C = [0, 0, 4, 4, 4], B = 00 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00 04 00 00 00
O = 0x1dbb0, C = [0, 4, 4, 4], B = 00 00 00 00 04 00 00 00 04 00 00 00 04 00 00 00
O = 0x1dbfc, C = [0, 0, 4, 4], B = 00 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00
O = 0x5a54c, C = [0, 0, 1, 0], B = 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
O = 0x5de18, C = [0, 3, 2, 1, 0, 0], B = 00 00 00 00 03 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
O = 0x5e54c, C = [0, 0, 4, 4], B = 00 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00
O = 0x5e7a8, C = [0, 0, 1, 0, 0], B = 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
O = 0x5e7ac, C = [0, 1, 0, 0], B = 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
O = 0x5e998, C = [0, 1, 2, 3, 0], B = 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00
O = 0x5e99c, C = [1, 2, 3, 0], B = 01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00
O = 0x5ea48, C = [0, 4, 0, 4], B = 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00

We have the offset (O) in the file, then the code (C) found and its binary (B) representation.

Doesn't the first two lines sound familiar: 0, 0, 1, 0, 4, 3 and 0, 1, 0, 4, 3, 4? The second code is in fact the same than the first one but shifted. It contains the solution of Level 1 (0, 1, 0, 4, 3). Let's try to disassemble the binary and see if we can identify where and how these code's candidates are used.

Disassembling with Ghidra

There are some tools to disassemble or decompile binary. One well known is IDA Starter or Pro. But there is today an alternative: Ghidra from the NSA. Ghidra has also the advantage to incorporate a decompiler for several architectures.

The installation of Ghidra is a bit out of the scope of this paper, but I summarizes the steps for Kali Linux:

sudo apt install -y openjdk-11-jdk

cd Downloads/
wget https://ghidra-sre.org/ghidra_9.1_PUBLIC_20191023.zip
unzip ghidra_9.1_PUBLIC_20191023.zip
mkdir -p ~/Applications
mv ghidra_9.1_PUBLIC ~/Applications/Ghidra
cd ~/Applications/Ghidra/
./ghidraRun

Once started, create a new project where you like. Then choose Import file and select DoubleRainbow2-uwp.dll. Leave the import options like they are. Once imported, double-click on DoubleRainbow2-uwp.dll. Answer yes when asking Do you want to analyze it now?.

There are several different ways to continue like determining the address of the 1st code (offset 0x1819c) when loaded. But let's be lazy: Copy the "Binary" value of the first code (00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 04 00 00 00 03 00 00 00) and click on Search | Memory | Next:

The first 0 32-bit value is not recognized by Ghidra (no cross-reference). This is expected since the first 0 is not part of the Level 1 code. But what is interesting is that all the following values are referenced by a single function FUN_004722ec:

Double-click on it and Ghidra will show the disassembly of the corresponding code and will try, on the right, to decompile it:

The interesting part is the following:

  FUN_00494b58();
  iVar1 = FUN_00494b4c();
  *(undefined4 *)(iVar1 + 8) = 0;
  *(undefined4 *)(iVar1 + 0xc) = 1;
  *(undefined4 *)(iVar1 + 0x10) = 0;
  *(undefined4 *)(iVar1 + 0x14) = 4;
  *(undefined4 *)(iVar1 + 0x18) = 3;
  FUN_00494b58();
  iVar1 = FUN_00494b4c();
  *(undefined4 *)(iVar1 + 8) = 4;
  *(undefined4 *)(iVar1 + 0xc) = 0;
  *(undefined4 *)(iVar1 + 0x10) = 1;
  *(undefined4 *)(iVar1 + 0x14) = 3;
  *(undefined4 *)(iVar1 + 0x18) = 2

Let's try to find what FUN_00494b4c is (double-click on it):

void FUN_00494b4c(void)
{
  RhpNewArray();
  return;
}

So FUN_00494b4c is simply a wrapper around RhpNewArray. Click on FUN_00494b4c, press L and rename the function. I usually add the prefix yyyy to distinguish symbols I have renamed manually from those deduced by the disassembler. Click in the left arrow in the tool bar to go back to the previous address.

So we have two arrays initialized with the code of Level 1 and what looks like another code: 4, 0, 1, 3, 2. We can directly try this code on the actual hardware, but can we confirm before that it is indeed the code of Level 2?

The decompilation of such code is often not very precise (or even wrong). It helps but in order to determine where those arrays are stored, we have to look at the disassembly:

We see the same code's structure for the two arrays with a difference (of course, the values of the code are also different): the first array is stored at offset 0x10, the second at offset 0x14. It will be important later.

Let's try to find the VerifyCode function. We know it displays Wrong Code. Click Search | For Strings. In Filter, type Wrongand it gives immediately the location of Wrong Code. Click on the line and close the window.

I usually rename the obvious data I see. For example, I rename PTR_DAT_0045abf0 to yyyyWrongCode and I do the same for the other strings. Double-click on the cross-reference (FUN_0048fabc). From the structure of the code, we can deduce that it is indeed VerifyCode and that it checks two codes: first the Level 1 code and then another code:

And do you remember what we found previously? The first code is at offset 0x10, the second at offset 0x14. So we can now be confident that the code for Level 1 is indeed: 4, 0, 1, 3, 2. When trying on the actual hardware, we get the second flag:

ph0wn{is-hard-to-find}

Note: Why Level 2 includes the code of Level 1? Because as there is a single hardware for both levels, it has to.

Conclusion

I hope you enjoyed this challenge. It is not so common to think about C#, .NET and Windows 10 when talking about IoT.

I faced many difficulties when building this challenge. Originally, the hardware used a RGB color detector but I was never able to get a reliable result, even when purchasing another detector. Probably something wrong in my setup but I was not able to find what. I also faced challenges to make Level 1 easy to solve. So, lot of work, but it was worth it.

Thank you a lot to the organizers of Ph0wn and especially to Cryptax. She has tested my challenge and has detected many defects.