avatar
Cyscom
Cybersecurity Student Community of VIT Chennai
  • CTF EVENTS
  • CATEGORIES
  • TAGS
  • ARCHIVES
  • POSTS
  • ABOUT
Home Ciphercase 2026 Corelight Museum Archive
Writeup
Cancel

Corelight Museum Archive

Corelight Museum Archive

Author: Sagnik

This challenge is a combination of steganography, PNG file analysis, reverse engineering, and cryptography.

We are given a single PNG image which appears to be an old archival photograph. The image contains multiple independent artifacts. We need to recover three clues from the image, find the encrypted payload hidden in the PNG, and then reverse engineer an appended executable to understand how the clues are turned into the encryption key.

The overall solve path is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PNG Image
   |
   +--> Metadata
   |
   +--> LSB steganography
   |
   +--> Visual clue
   |
   +--> Custom PNG chunk
   |
   +--> Appended ELF
             |
             v
      Reverse engineering
             |
             v
       FNV-1a key derivation
             |
             v
          ChaCha20
             |
             v
            FLAG

Firstly, inspect the image metadata

The challenge description says:

“As with every restoration, begin by identifying the creator.”

This strongly suggests checking the image metadata.

We can use:

1
exiftool archive.png

Among the PNG metadata, the Artist field contains:

1
see_hidden_notes

This is not a normal artist name and is clearly intended as a clue.

So our first recovered value is:

1
EXIF = see_hidden_notes

The metadata is therefore one of the inputs to the later key derivation.

Next, inspect the image for hidden data

The image also contains information hidden using LSB steganography.

A tool such as zsteg can be used to inspect the different bit planes:

1
zsteg archive.png

The relevant information is hidden in the least significant bits of the image.

Extracting the embedded message gives:

1
beyond_last_pixel

Therefore our second clue is:

1
LSB = beyond_last_pixel

The important point is that this message is not visible when the PNG is simply opened normally.

Inspect the photograph itself

The challenge description also says that the archive is self-contained and that we should begin by identifying the creator. After checking the metadata and hidden data, we should also inspect the photograph itself.

A visual reference is present in the image:

1
REF-0217

This gives us the third clue:

1
VIS = REF-0217

At this point we have:

1
2
3
EXIF = see_hidden_notes
LSB  = beyond_last_pixel
VIS  = REF-0217

However, we still need to determine how these values are used.

Inspect the PNG structure

A PNG consists of a sequence of chunks, so we should inspect the actual file structure rather than treating it only as an image.

Using:

1
pngcheck -v archive.png

reveals an unusual custom chunk:

1
2
chunk coRe at offset 0x382464, length 29
    unknown private, ancillary, safe-to-copy chunk

The coRe chunk is suspicious because it is not one of the normal PNG chunks.

A PNG chunk has the structure:

1
2
3
4
Length  4 bytes
Type    4 bytes
Data    N bytes
CRC     4 bytes

The reported offset points to the beginning of the length field. Therefore, the actual chunk data begins 8 bytes after the reported offset.

The chunk contains 29 bytes of data, which is the encrypted payload.

We can extract it with:

1
dd if=archive.png of=core.bin bs=1 skip=3679340 count=29

Then inspect the result:

1
xxd core.bin

The output is binary data and does not resemble plaintext.

This tells us that we still need to recover the encryption method and key.

Check for data after the PNG

pngcheck also reports:

1
additional data after IEND chunk

This is another important clue.

A valid PNG normally ends at its IEND chunk. If there is additional data after IEND, something else has been appended to the file.

We can use:

1
binwalk archive.png

This identifies an ELF executable after the PNG.

The ELF begins at:

1
3679381

We can extract it using:

1
dd if=archive.png of=archive_encoder bs=1 skip=3679381

Then verify it:

1
file archive_encoder

It is a Linux x86-64 ELF executable.

Make it executable if desired:

1
chmod +x archive_encoder

The appended executable is effectively the missing restoration software referenced by the challenge.

Reverse engineer the executable

We can load archive_encoder into Ghidra.

We can also start with:

1
strings -a archive_encoder

Some useful strings include:

1
2
3
4
5
6
Corelight Restoration Encoder
[record] assembling capture record from scene-tag, hidden-note, visual-ref
[record] record layout: EXIF:<scene-tag>|LSB:<hidden-note>|VIS:<visual-ref>
[signature] deriving archive signature from capture record
[transform] preparing archive transform state
[protect] applying payload protection

The most important string is:

1
EXIF:<scene-tag>|LSB:<hidden-note>|VIS:<visual-ref>

This tells us exactly how the three clues are combined.

Using the values recovered earlier, the record becomes:

1
EXIF:see_hidden_notes|LSB:beyond_last_pixel|VIS:REF-0217

The order and separators are important.

Identify the hash function

The encoder contains a function that initializes a 32-bit value with:

1
0x811C9DC5

and repeatedly XORs each byte before multiplying by:

1
0x01000193

These are the standard constants for FNV-1a.

The equivalent Python implementation is:

1
2
3
4
5
6
7
8
def fnv1a32(data):
    h = 0x811C9DC5

    for b in data:
        h ^= b
        h = (h * 0x01000193) & 0xFFFFFFFF

    return h

The first hash is therefore:

1
2
3
H1 = FNV-1a32(
    "EXIF:see_hidden_notes|LSB:beyond_last_pixel|VIS:REF-0217"
)

The encoder then hashes three calibration strings:

1
2
3
CHUNK_A
CHUNK_B
CHUNK_C

and chains them with XOR:

1
2
3
4
5
6
7
H1 = FNV1a32(record)

H2 = FNV1a32("CHUNK_A") XOR H1

H3 = FNV1a32("CHUNK_B") XOR H2

H4 = FNV1a32("CHUNK_C") XOR H3

These four 32-bit values form the archive signature.

Expand the signature into the key

The executable then expands the four 32-bit signature values into a 32-byte key.

The relevant logic is equivalent to:

1
2
3
4
5
6
7
8
for (int i = 0; i < 8; i++) {
    uint32_t shift = (i % 4) * 8;

    key[i]      = (sig[0] >> shift) & 0xff;
    key[i + 8]  = (sig[1] >> shift) & 0xff;
    key[i + 16] = (sig[2] >> shift) & 0xff;
    key[i + 24] = (sig[3] >> shift) & 0xff;
}

So each signature value contributes eight bytes to the final key, with its four little-endian bytes repeated twice.

The resulting key can be reproduced from the four signature values rather than guessed.

Identify the encryption algorithm

The executable contains a custom implementation of ChaCha20.

The initialization constants are:

1
2
3
4
0x61707865
0x3320646e
0x79622d32
0x6b206574

These correspond to:

1
expand 32-byte k

which is the standard ChaCha20 constant.

The quarter-round also uses the standard ChaCha20 rotations:

1
2
3
4
16
12
8
7

Therefore the payload is protected using:

1
ChaCha20

The parameters recovered from the executable are:

1
2
3
Cipher:  ChaCha20
Nonce:   000000000000000000000000
Counter: 1

The nonce is 12 zero bytes and the initial counter is 1.

Decrypt the coRe payload

We now have everything needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
EXIF clue
    +
LSB clue
    +
visual reference
    |
    v
capture record
    |
    v
FNV-1a + chained XOR
    |
    v
32-byte key
    |
    v
ChaCha20
    |
    v
coRe ciphertext
    |
    v
plaintext

The repository also contains a decryption script that reproduces the recovered algorithm.

The important part is:

1
2
3
4
5
6
7
8
9
10
key = derive_key()

cipher = ChaCha20.new(
    key=key,
    nonce=b"\x00" * 12
)

cipher.seek(64)

plaintext = cipher.decrypt(ciphertext)

The seek(64) advances to counter 1 because one ChaCha20 block is 64 bytes.

Running the decryption produces:

1
CYS{restoration_record_0217}

Complete solve chain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
                         archive.png
                              |
              +---------------+---------------+
              |               |               |
              v               v               v
           EXIF            RGB LSB        Photograph
              |               |               |
              v               v               v
   see_hidden_notes   beyond_last_pixel     REF-0217
              |               |               |
              +---------------+---------------+
                              |
                              v
       EXIF:see_hidden_notes|LSB:beyond_last_pixel|VIS:REF-0217
                              |
                              v
                           FNV-1a
                              |
                              v
                    CHUNK_A / CHUNK_B / CHUNK_C
                              |
                              v
                       4 x 32-bit values
                              |
                              v
                         32-byte key
                              |
                              v
                          ChaCha20
                              |
                              v
                     coRe encrypted chunk
                              |
                              v
                             FLAG

There is also a second important path through the file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
archive.png
    |
    +--> PNG image
    |
    +--> coRe chunk
    |       |
    |       +--> encrypted payload
    |
    +--> IEND
    |
    +--> appended ELF
            |
            +--> key derivation
            +--> cipher implementation

The PNG provides the clues and ciphertext, while the appended executable provides the algorithm required to turn those clues into the decryption key.

Final Flag

1
CYS{restoration_record_0217}

Flag

CYS{restoration_record_0217}
Edit on GitHub
Trending Tags
Admin Bot AES-GCM Algorithm Confusion authentication Broken Access Control CRT ECC ECDH ELF Ghidra

© 2026 Cyscom. Some rights reserved.

Using the Jekyll theme Chirpy.

A new version of content is available.