289 lines
13 KiB
Markdown
289 lines
13 KiB
Markdown
|
---
|
||
|
title: "Decrypting cryptolocked partition | EXTerminated @ FCSC 2022"
|
||
|
date: "2022-05-08 19:00:00"
|
||
|
author: "Juju"
|
||
|
tags: ["Reverse", "Writeup", "fcsc"]
|
||
|
toc: true
|
||
|
---
|
||
|
|
||
|
# Intro
|
||
|
|
||
|
EXTerminated is a malware reversing challenge. You are handed an EXT4 partition
|
||
|
that has been encrypted by a malware, your goal is to recover the original
|
||
|
unencrypted files.
|
||
|
|
||
|
The description of the challenge tells us that the malware does not use any
|
||
|
known cryptosystem to encrypt data, let's find this out.
|
||
|
|
||
|
{{< image src="/EXTerminated/panik.png" style="border-radius: 8px;" >}}
|
||
|
|
||
|
## Challenge description
|
||
|
`reverse` | `472 pts` `12 solves` `:star::star:`
|
||
|
```
|
||
|
Un client a détecté un seveur compromis sur son parc. Ce serveur semble avoir
|
||
|
perdu l'ensemble de ses données suite à une infection. Il nous indique que les
|
||
|
attaquants exigent une rançon et affirment pouvoir récupérer les fichiers
|
||
|
disparus.
|
||
|
|
||
|
Une analyse rapide du virus indique que ce dernier ne disposerait à première
|
||
|
vue d'aucun algorithme cryptographique connu.
|
||
|
|
||
|
On vous demande d'analyser ce disque, et de récupérer les fichiers originaux.
|
||
|
|
||
|
SHA256(disk.img) = a9e7891224868af43e2aa134152beaa2a83f43cde21af8038d138001377157dc.
|
||
|
```
|
||
|
|
||
|
Author: `Nofix`
|
||
|
|
||
|
## Given files
|
||
|
|
||
|
[disk.img](/EXTerminated/disk.img)
|
||
|
|
||
|
# Writeup
|
||
|
|
||
|
## Overview
|
||
|
|
||
|
So we can see we are given an EXT4 partition.
|
||
|
|
||
|
```console
|
||
|
$ file disk.img
|
||
|
disk.img: Linux rev 1.0 ext4 filesystem data,
|
||
|
UUID=c26167b3-e0b9-441d-ab4d-a5f4b5b1fcd0 (extents) (64bit) (large files)
|
||
|
(huge files)
|
||
|
```
|
||
|
|
||
|
Let's try to mount it.
|
||
|
|
||
|
We can see that there are multiple pictures, a pdf and an executable called
|
||
|
`wannaweep`, which is probably our malware.
|
||
|
|
||
|
```console
|
||
|
$ sudo mount -o loop ./disk.img mnt/
|
||
|
$ tree mnt/
|
||
|
mnt/
|
||
|
├── Documents
|
||
|
│ └── anssi-guide-ransomware_attacks_all_concerned-v1.0.pdf
|
||
|
├── Images
|
||
|
│ ├── accident.png
|
||
|
│ ├── disk.jpg
|
||
|
│ ├── flag.jpg
|
||
|
│ ├── martine.jpg
|
||
|
│ ├── smile.png
|
||
|
│ ├── tintin.jpeg
|
||
|
│ └── valide.png
|
||
|
├── lost+found [error opening dir]
|
||
|
└── wannaweep
|
||
|
|
||
|
3 directories, 9 files
|
||
|
```
|
||
|
|
||
|
By further investigating we can see that all data of the files have been erased
|
||
|
except for wannaweep, which definitely is the malware, an x64 stripped and
|
||
|
dinamically linked ELF.
|
||
|
|
||
|
```console
|
||
|
$ file mnt/Images/flag.jpg
|
||
|
mnt/Images/flag.jpg: data
|
||
|
$ xxd mnt/Images/flag.jpg | head
|
||
|
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
|
$ file mnt/wannaweep
|
||
|
mnt/wannaweep: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
|
||
|
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
|
||
|
BuildID[sha1]=590a9f68cde37ffceb8e7441db343742e4032f47, for GNU/Linux
|
||
|
3.2.0, stripped
|
||
|
```
|
||
|
|
||
|
We can see that the binary links to `libext2fs.so.2`, a library for EXT file
|
||
|
system parsing and manipulation. Since it is dinamically linked we will still
|
||
|
have the corresponding symbols so I'm guessing I won't have too much trouble
|
||
|
understanding what the malware does even though I do not know the EXT
|
||
|
specification.
|
||
|
|
||
|
```console
|
||
|
$ ldd mnt/wannaweep
|
||
|
linux-vdso.so.1 (0x00007ffccb2b3000)
|
||
|
libext2fs.so.2 => /usr/lib/libext2fs.so.2 (0x00007f8059117000)
|
||
|
libcom_err.so.2 => /usr/lib/libcom_err.so.2 (0x00007f8059111000)
|
||
|
libc.so.6 => /usr/lib/libc.so.6 (0x00007f8058f07000)
|
||
|
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f80591ad000)
|
||
|
```
|
||
|
|
||
|
## Main
|
||
|
|
||
|
It is now the perfect time for me to absolutely not launch this program, I
|
||
|
could have setup a sandboxed environment but it turned out it wasn't necessary,
|
||
|
the code was really straight forward.
|
||
|
|
||
|
So I start my favorite decompiler and I download the source code of libext2fs.
|
||
|
|
||
|
A small fast forward in time as the main function is really straight forward,
|
||
|
|
||
|
The program takes the path to the device to encrypt as argument, calls a bunch
|
||
|
of function that are mostly wrappers around libext2fs functions to initialize
|
||
|
some global variables holding structures of the EXT filesystem. It alsos checks
|
||
|
that some flags are set in the structures of libext2fs, I do not know if these
|
||
|
flags are implementation specific or are standard EXT flags but I did not
|
||
|
bother too much with this.
|
||
|
|
||
|
The intersting stuff is at the end, I can see that it calls a function that
|
||
|
I called `encrypt_folder` after reversing it. It then flushes the filesystem to
|
||
|
disk and write the inode bitmap.
|
||
|
|
||
|
{{< code file="/static/EXTerminated/main.c" language="c" >}}
|
||
|
|
||
|
## Encrypt folder
|
||
|
|
||
|
Alright so let's take a look at the `encrypt_folder` to understand why I called
|
||
|
it this way.
|
||
|
|
||
|
I could clearly see from `main` that function was called with the string `.`
|
||
|
to reference the current directory, and a function pointer that was still
|
||
|
unkown to me at the time but that I renamed `encrypt_file`.
|
||
|
|
||
|
How I knew that the function was encrypting folders and that the parameter was
|
||
|
a callback to encrypt files is really simple, you can clearly see the libc
|
||
|
symbols calling `opendir` on the path given as argument (`.`), reading all the
|
||
|
entries of the directory and calling the callback if the entry is a file.
|
||
|
|
||
|
If it is not a file, it will `chdir` in the said directory, before recursively
|
||
|
calling itself with the same arguments.
|
||
|
|
||
|
{{< code file="/static/EXTerminated/encrypt_folder.c" language="c" >}}
|
||
|
|
||
|
|
||
|
## Encrypt file
|
||
|
|
||
|
Let's now look at the `encrypt_file` we guessed. Again, mostly wrapper
|
||
|
functions so I did not bother to show you why I named them this way, the code
|
||
|
basically reads the content of the file to encrypt, encrypts it in a dedicated
|
||
|
block, puts it in the file system before deleting the original content of the
|
||
|
file.
|
||
|
|
||
|
{{< code file="/static/EXTerminated/encrypt_file.c" language="c" >}}
|
||
|
|
||
|
## Encrypt block
|
||
|
|
||
|
Well, I was scared of custom cryptography but this seems simple enough for me.
|
||
|
To encrypt a plain text block, the malware starts at the last byte of the block
|
||
|
and xor it with the previous one until it reaches the start.
|
||
|
Actually this function does a heap buffer underflow when xoring the first byte
|
||
|
of the block, since it will xor it with the byte right before the block in the
|
||
|
heap.
|
||
|
|
||
|
Reversing the encryption is trivial, the only undefined behaviours is with the
|
||
|
underflow because we cannot know the value of the byte preceding the block.
|
||
|
However, I assumed it would really likely be 0.
|
||
|
|
||
|
{{< code file="/static/EXTerminated/encrypt_block.c" language="c" >}}
|
||
|
|
||
|
## Decrypting
|
||
|
|
||
|
So to decrypt a block, we simply need to know the value of the byte preceding
|
||
|
the buffer. Let's assume it is 0 since it is the most likely.
|
||
|
|
||
|
I will xor this byte with the first byte of cipher text and that will give
|
||
|
me the first byte of plain text, I then repeat the operation, xoring the
|
||
|
first byte of plain text with the second byte of cipher text and I do this
|
||
|
for the whole block to recover the entire block.
|
||
|
|
||
|
|
||
|
## Solve
|
||
|
|
||
|
So know I know how to decrypt a block, let's decrypt the whole filesystem.
|
||
|
|
||
|
I could do something really smart and overengineered to recover a valid
|
||
|
decrypted EXT filesystem that I could mount to recover the original files.
|
||
|
|
||
|
However I'm not familiar with the EXT specification and their are still small
|
||
|
shadow zones in the malware code for me, so I try a naive solution.
|
||
|
|
||
|
I'm guessing that data blocks of files or all alligned on `0x1000`, it would be
|
||
|
kind of weird otherwise for me. So if I just cut the whole filesystem in
|
||
|
`0x1000` sized blocks without any consideration of the semantic of those blocks
|
||
|
in the EXT structure, I could just decrypt each of these blocks individually,
|
||
|
and prey that I recover some files whose blocks were contiguous in the EXT
|
||
|
structure.
|
||
|
|
||
|
Obviously that will corrupt all the EXT metadata but it doesn't really matter
|
||
|
as long as I can recover the files.
|
||
|
|
||
|
{{< code file="/static/EXTerminated/decrypt.py" language="py" >}}
|
||
|
|
||
|
Now let's try to output the result and see if we find any file signatures:
|
||
|
|
||
|
```console
|
||
|
$ ./decrypt.py > decrypted.img
|
||
|
$ binwalk decrypted.img
|
||
|
|
||
|
DECIMAL HEXADECIMAL DESCRIPTION
|
||
|
--------------------------------------------------------------------------------
|
||
|
9289728 0x8DC000 PNG image, 473 x 306, 8-bit/color RGB, non-interlaced
|
||
|
9531392 0x917000 JPEG image data, JFIF standard 1.01
|
||
|
9625600 0x92E000 PNG image, 500 x 564, 8-bit/color RGBA, non-interlaced
|
||
|
10125312 0x9A8000 JPEG image data, JFIF standard 1.01
|
||
|
10212833 0x9BD5E1 bix header, header size: 64 bytes, header CRC: 0x6003083D, created: 2004-01-10 13:39:36, image size: 2097152 bytes, Data Address: 0x22120000, Entry Point: 0x1E040003, data CRC: 0xD2100008, CPU: IA64, image name: ""
|
||
|
10231808 0x9C2000 JPEG image data, EXIF standard
|
||
|
10231820 0x9C200C TIFF image data, big-endian, offset of first image directory: 8
|
||
|
10330112 0x9DA000 JPEG image data, EXIF standard
|
||
|
10330124 0x9DA00C TIFF image data, big-endian, offset of first image directory: 8
|
||
|
10403840 0x9EC000 PNG image, 482 x 367, 8-bit/color RGB, non-interlaced
|
||
|
10403902 0x9EC03E Zlib compressed data, default compression
|
||
|
34263365 0x20AD145 Cisco IOS experimental microcode, for ""
|
||
|
46640306 0x2C7ACB2 MySQL ISAM compressed data file Version 9
|
||
|
46691658 0x2C8754A GIF image data 15531 x
|
||
|
```
|
||
|
|
||
|
We do! So let's try to extract them
|
||
|
|
||
|
```console
|
||
|
$ binwalk --dd='.*' decrypted.img
|
||
|
|
||
|
DECIMAL HEXADECIMAL DESCRIPTION
|
||
|
--------------------------------------------------------------------------------
|
||
|
9289728 0x8DC000 PNG image, 473 x 306, 8-bit/color RGB, non-interlaced
|
||
|
9531392 0x917000 JPEG image data, JFIF standard 1.01
|
||
|
9625600 0x92E000 PNG image, 500 x 564, 8-bit/color RGBA, non-interlaced
|
||
|
10125312 0x9A8000 JPEG image data, JFIF standard 1.01
|
||
|
10212833 0x9BD5E1 bix header, header size: 64 bytes, header CRC: 0x6003083D, created: 2004-01-10 13:39:36, image size: 2097152 bytes, Data Address: 0x22120000, Entry Point: 0x1E040003, data CRC: 0xD2100008, CPU: IA64, image name: ""
|
||
|
10231808 0x9C2000 JPEG image data, EXIF standard
|
||
|
10231820 0x9C200C TIFF image data, big-endian, offset of first image directory: 8
|
||
|
10330112 0x9DA000 JPEG image data, EXIF standard
|
||
|
10330124 0x9DA00C TIFF image data, big-endian, offset of first image directory: 8
|
||
|
10403840 0x9EC000 PNG image, 482 x 367, 8-bit/color RGB, non-interlaced
|
||
|
10403902 0x9EC03E Zlib compressed data, default compression
|
||
|
34263365 0x20AD145 Cisco IOS experimental microcode, for ""
|
||
|
46640306 0x2C7ACB2 MySQL ISAM compressed data file Version 9
|
||
|
46691658 0x2C8754A GIF image data 15531 x
|
||
|
|
||
|
$ file _decrypted.img.extracted/*
|
||
|
_decrypted.img.extracted/2C7ACB2: MySQL ISAM compressed data file Version 9
|
||
|
_decrypted.img.extracted/2C8754A: GIF image data 15531 x
|
||
|
_decrypted.img.extracted/8DC000: PNG image data, 473 x 306, 8-bit/color RGB, non-interlaced
|
||
|
_decrypted.img.extracted/9A8000: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, progressive, precision 8, 660x424, components 3
|
||
|
_decrypted.img.extracted/9BD5E1: data
|
||
|
_decrypted.img.extracted/9C200C: TIFF image data, big-endian, direntries=0
|
||
|
_decrypted.img.extracted/9C2000: JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=0], comment: "CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90", baseline, precision 8, 481x600, components 3
|
||
|
_decrypted.img.extracted/9DA000: JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=0], baseline, precision 8, 683x500, components 3
|
||
|
_decrypted.img.extracted/9DA00C: TIFF image data, big-endian, direntries=0
|
||
|
_decrypted.img.extracted/9EC000: PNG image data, 482 x 367, 8-bit/color RGB, non-interlaced
|
||
|
_decrypted.img.extracted/9EC03E: empty
|
||
|
_decrypted.img.extracted/9EC03E-0: zlib compressed data
|
||
|
_decrypted.img.extracted/20AD145: cisco IOS experimental microcode for ''
|
||
|
_decrypted.img.extracted/92E000: PNG image data, 500 x 564, 8-bit/color RGBA, non-interlaced
|
||
|
_decrypted.img.extracted/917000: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 495x669, components 3
|
||
|
```
|
||
|
|
||
|
Displaying the images we just extracted, we find the following one, giving us
|
||
|
the flag.
|
||
|
|
||
|
{{< image src="/EXTerminated/flag.png" style="border-radius: 8px;" >}}
|