juju.re/jujure/content/writeups/fcsc_2024/archiver.md
Julien CLEMENT 5ad8ea7bfa fix wus dates
Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
2024-04-15 14:45:05 +02:00

11 KiB

title date author tags toc
Black box reversing | Archiver @ FCSC 2024 2024-04-14 22:00:00 Juju
Reverse
Writeup
fcsc
true

Intro

This writeup is not a really serious one, if you are reading this as part of the FCSC writeup reviews, my submitted writeups are actually for svartalfheim and megalosaure. I still thought it would be funny to include this one as it is not that long and I think my solution is kind of unintended.

Basically we are given a Windows stripped binary, compiled from rust. The binary is an encrypted archive manager.

This challenge managed to put every single reverser red flag in a single binary.

I tried opening up the binary in binary ninja or IDA, but it was as expected: stripped rust.

So let's close this up, never touch it again and see what we can do without reading the code or debugging.

{{< image src="/archiver/meme.jpg" style="border-radius: 8px;" >}}

Challenge description

reverse | 490 pts 5 solves

Notre équipe SIGINT a intercepté un e-mail contenant une pièce jointe
result.fcsc. Vu l'extension, il doit s'agir d'une archive au format
propriétaire FCSC. Nous avons pu récupérer l'utilitaire dans sa version
Windows, archiver.exe.

Vu le contenu de l'e-mail, ça a l'air assez important. Est-ce qu'on a une
chance de savoir ce qu'il y a dedans ?

Votre prédécesseur, après avoir réussi une analyse similaire, a sombré
dans la folie et réside désormais dans un asile psychiatrique. Il
murmurait des mots étranges comme "TTD" et parlait sans arrêt de hardware
breakpoint.

Note : La chaîne que vous trouverez est à mettre entre FCSC{} pour avoir
le flag.

Authors: commial YoMama

Given files

archiver.exe

result.fcsc

Writeup

Overview

With the binary, we are also given a result.fcsc, which is an archive encrypted with the given binary.

This archive contains the flag and we must decrypt it.

$ xxd result.fcsc 
00000000: 0100 0000 0000 0000 21e2 ae0f b85f de7b  ........!...._.{
00000010: b246 ed90 194f 601e 041b 3c8a c6e9 37b1  .F...O`...<...7.
00000020: 878b d8e0 e796 a098 1800 0000 0000 0000  ................
00000030: d44a b96f ea76 8c55 73a0 7266 1a5e b5fd  .J.o.v.Us.rf.^..
00000040: c3bf cc29 8ed7 e925 1800 0000 0000 0000  ...)...%........
00000050: f96d be51 956d 9342 7e3d 1c0d bbef ad7a  .m.Q.m.B~=.....z
00000060: bc57 7bf0 f36a cb23                      .W{..j.#

Common fields

Sizes

First thing we can see are 3 uint64_t packed in little endian at offsets:

  • 0x0
  • 0x28
  • 0x48

Given how small these numbers are, they are probably representing some sizes.

If we count manually, we see that the last two uint64_t represent the size of the data that immediatly follows them.

The first one is still unknown but as it is 1, it probably just is the number of files in the archive.

sha256

Now let's try to create our own archive:

$ echo -n 'FCSC{test_flag}' > flag.txt
$ ./archiver.exe create password test.db flag.txt
$ xxd test.db 
00000000: 0100 0000 0000 0000 21e2 ae0f b85f de7b  ........!...._.{
00000010: b246 ed90 194f 601e 041b 3c8a c6e9 37b1  .F...O`...<...7.
00000020: 878b d8e0 e796 a098 1800 0000 0000 0000  ................
00000030: 8257 3ccc 2608 498d b6b7 5801 740f 2e4e  .W<.&.I...X.t..N
00000040: 8b36 0169 9273 2c91 1f00 0000 0000 0000  .6.i.s,.........
00000050: a278 0ee8 7308 548a 84af 1ad4 6c0c 0844  .x..s.T.....l..D
00000060: de13 a830 ea7f 4d37 19ea 7efe 14b5 c5    ...0..M7..~....

Two things are already weird:

  • Everything is identical to the given archive until offset 0x30
  • This archive is larger than the given one. Either the flag is really small or data is compressed.

Let's try to archive a really low entropy file to see if the archive is smaller:

$ echo -n 'aaaaaaaaaaaaaaa' > aaaaaaaa
$ ./archiver.exe create password low_entropy.db aaaaaaaa 
$ xxd low_entropy.db 
00000000: 0100 0000 0000 0000 1f3c e404 15a2 081f  .........<......
00000010: a3ee e75f c39f ff8e 56c2 2270 d1a9 78a7  ..._....V."p..x.
00000020: 249b 592d cebd 20b4 1800 0000 0000 0000  $.Y-.. .........
00000030: b2b6 ed4e 38bb 52b8 5cd7 12a7 7df6 261b  ...N8.R.\...}.&.
00000040: 2bbf f8df 77c4 dfe8 1f00 0000 0000 0000  +...w...........
00000050: b2b6 ed4e 38bb 52b8 8876 7671 b114 9799  ...N8.R..vvq....
00000060: cb38 24e6 02f9 26f9 25ec a7b8 bdb9 56    .8$...&.%.....V

I put exactly the same sizes in the file name and size data.

The resulting archive is exactly the same size so no compression is performed.

But wait ! Something changed !

In our first archive, the firsts 0x30 bytes where identical but now they differ starting at 0x8, they differ for 0x20 bytes before becoming the same again on the field we identified as a size.

Well, we changed two things: the filename and the file data.

It is unlikely that we guessed the file data on our first try.

But the filename however ? What if the the format stores a hash of the filename on 0x20 bytes at offset 0x8 ?

$ echo -n 'flag.txt' | sha256sum 
21e2ae0fb85fde7bb246ed90194f601e041b3c8ac6e937b1878bd8e0e796a098  -

Bingo ! It matches the bytes we have in our first custom archive and the one given.

So we know that the filename is flag.txt and that the archives stores the sha256 of the filename at offset 0x8.

Cipher texts

I wil now try to play with data sizes to identify what are the sizes in the binary refering to.

Let's create an archive with a filename 1 byte smaller, and data 1 byte larger:

$ echo -n 'aaaaaaaaaaaaaaaa' > aaaaaaa
$ ./archiver.exe create password sizes.db aaaaaaa
$ xxd sizes.db 
00000000: 0100 0000 0000 0000 e462 4071 4b5d b3a2  .........b@qK]..
00000010: 3eee 6047 9a62 3efb a4d6 33d2 7fe4 f03c  >.`G.b>...3....<
00000020: 904b 9e21 9a7f be60 1700 0000 0000 0000  .K.!...`........
00000030: 8ab9 acd8 126c cc48 064e 9843 2fa1 9492  .....l.H.N.C/...
00000040: 8503 ce34 399c 9820 0000 0000 0000 008a  ...49.. ........
00000050: b9ac d812 6ccc f370 a362 da68 de94 c7d2  ....l..p.b.h....
00000060: aa91 eb29 2be2 0aa3 a74f fd99 4dc0 ca    ...)+....O..M..

Notice that the first uint64_t went from 0x18 to 0x17 and the second one from 0x1f to 0x20

Thus the first size and data correspond to the encrypted file name, and the second one to the encrypted data.

We can also see that encrypted data is always exactly 0x10 bytes larger that the plaintext. So it probably just adds a 0x10 bytes IV in front of it.

Looking back at the original archive, we can thus see that the the filename is 0x8 bytes large (which matches the flag.txt we found) and the data is also 0x8 bytes large, thus confirming the really small flag. (see below for a reminder of the original archive)

$ xxd result.fcsc 
00000000: 0100 0000 0000 0000 21e2 ae0f b85f de7b  ........!...._.{
00000010: b246 ed90 194f 601e 041b 3c8a c6e9 37b1  .F...O`...<...7.
00000020: 878b d8e0 e796 a098 1800 0000 0000 0000  ................
00000030: d44a b96f ea76 8c55 73a0 7266 1a5e b5fd  .J.o.v.Us.rf.^..
00000040: c3bf cc29 8ed7 e925 1800 0000 0000 0000  ...)...%........
00000050: f96d be51 956d 9342 7e3d 1c0d bbef ad7a  .m.Q.m.B~=.....z
00000060: bc57 7bf0 f36a cb23                      .W{..j.#

Figuring out the crypto

Now I will try to get a file as close as possible as the original flag and see how the resulting archive behaves to small input mutations:

$ echo -n '12345678' > flag.txt
$ ./archiver.exe create password 12345678.db flag.txt 
$ echo -n '22345678' > flag.txt
$ ./archiver.exe create password 22345678.db flag.txt 
$ xxd 12345678.db 
00000000: 0100 0000 0000 0000 21e2 ae0f b85f de7b  ........!...._.{
00000010: b246 ed90 194f 601e 041b 3c8a c6e9 37b1  .F...O`...<...7.
00000020: 878b d8e0 e796 a098 1800 0000 0000 0000  ................
00000030: 8257 3ccc 2608 498d b6b7 5801 740f 2e4e  .W<.&.I...X.t..N
00000040: 8b36 0169 9273 2c91 1800 0000 0000 0000  .6.i.s,.........
00000050: d509 6e9f 3d4a 06c1 9daa bebe f6cb c23b  ..n.=J.........;
00000060: cf4e 3d32 2b68 09cf                      .N=2+h..
$ xxd 22345678.db 
00000000: 0100 0000 0000 0000 21e2 ae0f b85f de7b  ........!...._.{
00000010: b246 ed90 194f 601e 041b 3c8a c6e9 37b1  .F...O`...<...7.
00000020: 878b d8e0 e796 a098 1800 0000 0000 0000  ................
00000030: 8257 3ccc 2608 498d b6b7 5801 740f 2e4e  .W<.&.I...X.t..N
00000040: 8b36 0169 9273 2c91 1800 0000 0000 0000  .6.i.s,.........
00000050: d609 6e9f 3d4a 06c1 cc6e be60 dd5d 8214  ..n.=J...n.`.]..
00000060: 05bb cadc 0bf1 e4b8                      ........

Most part of the two archives are identical, as expected.

But maybe a little bit too much identical:

Look at the IV of the file data's cipher text (offset 0x50)

The first 8 bytes are almost identical, only the first one has been increased by one.

Could it be that the IV is generated with the clear text data ?

Let's try with an other data:

$ echo -n '32345678' > flag.txt
$ ./archiver.exe create password 32345678.db flag.txt 
$ xxd 32345678.db 
00000000: 0100 0000 0000 0000 21e2 ae0f b85f de7b  ........!...._.{
00000010: b246 ed90 194f 601e 041b 3c8a c6e9 37b1  .F...O`...<...7.
00000020: 878b d8e0 e796 a098 1800 0000 0000 0000  ................
00000030: 8257 3ccc 2608 498d b6b7 5801 740f 2e4e  .W<.&.I...X.t..N
00000040: 8b36 0169 9273 2c91 1800 0000 0000 0000  .6.i.s,.........
00000050: d709 6e9f 3d4a 06c1 fcd2 be2a c42f bdf1  ..n.=J.....*./..
00000060: 43e8 9879 eb86 bf95                      C..y....

Again, patching slightly the nth byte of the clear text only patched slightly the nth byte of the IV.

I played with some values and noticed that the operation performed is actually a xor:

>>> 0xd7 ^ ord('3')
228
>>> 0xd6 ^ ord('2')
228
>>> 0xd5 ^ ord('1')
228

So the clear text is xored with a key to generate the IV, but I do not know the said key, which seems to be derived from the archive password.

Known plaintext

Or do I ?

Remember that I know that filename is flag.txt, and that I have an associated ciphertext.

With a little bit of luck, the IV of the filename cipher text is generated with the same key:

>>> 0x82 ^ ord('f')
228

Looks like it does.

Since I have a known plaintext and example cipher text, I can simply, xor the plaintext with the filename IV to recover the xor key.

Then apply the same key to the file data IV to recover the plain text:

#!/usr/bin/env python3
from pwn import *
filename = b'flag.txt'
IV = b'\xd4\x4a\xb9\x6f\xea\x76\x8c\x55'
c = b'\xf9\x6d\xbe\x51\x95\x6d\x93\x42'
key = xor(IV, filename)
flag = xor(c, key)
print('FCSC{' + flag.decode() + '}')
$ ./solve.py 
FCSC{KKfYQogc}