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:
Note: The original schema had a mistake with the white LED.
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
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
:
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.
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,
you see broken symbolic links to 'unsupported reparse point', or
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.
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.
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.
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 Wrong
and 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.
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.