13 KiB
title | date | author | tags | toc | |||
---|---|---|---|---|---|---|---|
Decrypting cryptolocked partition | EXTerminated @ FCSC 2022 | 2022-05-08 19:00:00 | Juju |
|
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
Writeup
Overview
So we can see we are given an EXT4 partition.
$ 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.
$ 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.
$ 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.
$ 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:
$ ./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
$ 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;" >}}