> For the complete documentation index, see [llms.txt](https://maso-soup.gitbook.io/sec/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://maso-soup.gitbook.io/sec/cryptography.md).

# Cryptography

## Quick Checks for a CTF

### What is Known/Unknown

* Cipher (e.g. AES)
* Mode (e.g. CTR)
* Plaintext (or partial plaintext)
* Ciphertext
* Initialization Vector (IV) or Nonce

## AES-CTR Encrypted File with Key

Given an encrypted PNG file and the below snipped of code, we can recover the original PNG file.

```python
from Crypto.Cipher import AES

open('flag.png.enc', 'wb').write(
    AES.new(b'sup3rrr s3cr3ttt', AES.MODE_CTR).encrypt(open('flag.png', 'rb').read())
)
```

From [Wikipedia](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_\(CTR\)) we can visually understand how Counter Mode works for both encryption and decryption.

![](/files/Ra0C24rHZ3618dShYxJ3)

Further research tells us that a counter block is exactly as long as the cipher block size (16 bytes for AES). It consists of the concatenation of two pieces:

1. A fixed nonce, which is set at initialization
2. A variable counter, with gets increased by 1 for any subsequent counter block. The counter is big endian encoded.

![](/files/1jhSOGdKa0dhT5K9Ux1q)

The above information from [pycryptodome](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html#ctr-mode) tells us that the CTR mode takes a block cipher (the insecure ECB mode) and turns it into a stream cipher. Each byte of plain text is XOR-er with a byte of the key stream built with the sequence of counter blocks mentioned above.

Now let's step through the above processes. First let's talk encryption:

1. We will start with a random 8 byte nonce (sometimes called IV) concatenated with an 8 byte counter. These form a 16 bit AES ECB block.
2. Each block is then combined with the known key to have the AES object that will produce the key stream.
3. Each byte of plaintext is XOR-ed with a key stream resulting in cipher text.

Now let's talk decryption.

1. In order to decrypt the same item from above, we would need the same nonce value, the same key value, and to have the cipher text.
2. The ECB blocks containing the nonce and counters are once again combined with the key to create the AES object that will produce the key stream.
3. With the known cipher text and key stream, we can XOR those bytes to determine the plaintext, as shown above.

Now let's discuss the problem at hand. We do not know the nonce value nor the plaintext value. By definition we cannot know the key stream without one of these, even if we do know the key and type of encryption in use.

We need Key + Nonce to get the key stream to XOR with cipher text to find the plaintext.

Or alternatively we need Plaintext + Cipher text to get the key stream to get the nonce. But if that were the case we would already have the plaintext and wouldn't need it.

However, there is a third scenario. What if we could get the nonce somehow, even from just one block? After all, it is a fixed nonce and repeated each block through the key stream. If only we could even get a little piece of plaintext, maybe we could obtain part of the key stream and use that and the key to reveal one block with nonce and counter?

It turns out, there has been a piece of "plaintext" hiding the whole time. If we look at the file we're given, it reveals a piece of information, even if very small. The original file is a PNG file. Every file has what is called "magic bytes." These can vary in size, but are known bytes of every file of specific types when viewed as hex. We can attempt to use this as our "plaintext" of the PNG file.

The PNG "magic bytes" signature (89 50 4E 47 0D 0A 1A 0A) is only 8 bytes. We will need 16 total known bytes to match the AES block size for later decryption. We check Wikipedia for other byte data that would be common to PNG files generally, without including bytes that may start to be too specific to details of unique PNG files. We see that commonly a part of PNG image files is the IHDR header (00 00 00 0D 49 48 44 52 00 00 00 01 00 00 00 01 08 02 00 00 00 90 77 53 DE) which is 25 total bytes. Since we only need 8 more bytes to match the block size, we can just use the first 8 bytes of the IHDR header (00 00 00 0D 49 48 44 52).

Now that we have all of this information and understanding we can create our own Python script to decrypt the encrypted file.

First we will import some crypto libraries.

```python
from Crypto.Cipher import AES  
from Crypto.Util.strxor import strxor
```

We then create two variables for the cipher text and key we were given.

```python
ciphertext = open('flag.png.enc', 'rb').read()
key = b'sup3rrr s3cr3ttt'
```

We assign those 8 PNG header bytes and beginning 8 IHDR header bytes to a variable as our known plaintext.

```python
known_plaintext = bytes.fromhex("89504E470D0A1A0A0000000D49484452")
```

Next we can XOR known plaintext and cipher text variables to get the key stream

```python
keystream = strxor(known_plaintext, ciphertext[:16])
```

We then create an AES ECB cipher object with the known key.

```python
aes_ecb = AES.new(key, AES.MODE_ECB)
```

We can then use the AES ECB encryption object to decrypt the key stream we got earlier. Or rather the first 16 byte block the counter block sequence that eventually becomes the full key stream.

```python
counter_block = aes_ecb.decrypt(keystream)
```

Now that we have the first counter block we can grab the first 8 bytes of it knowing that they are the fixed nonce.

```python
nonce = counter_block[:8]
```

Now that we have the nonce and key, et's create another AES encryption object, this time in CTR mode.

```python
aes_ctr = AES.new(key, AES.MODE_CTR, nonce=nonce)
```

With that we can decrypt the PNG file and write the decrypted file to disk so we can open it!

```python
decrypted_png = aes_ctr.decrypt(ciphertext)
open('flag.png','wb').write(decrypted_png)
```

The final code:

```python
from Crypto.Cipher import AES  
from Crypto.Util.strxor import strxor

ciphertext = open('flag.png.enc', 'rb').read()
key = b'sup3rrr s3cr3ttt'
known_plaintext = bytes.fromhex("89504E470D0A1A0A0000000D49484452")
keystream = strxor(known_plaintext, ciphertext[:16])
aes_ecb = AES.new(key, AES.MODE_ECB)
counter_block = aes_ecb.decrypt(keystream)
nonce = counter_block[:8]
aes_ctr = AES.new(key, AES.MODE_CTR, nonce=nonce)
decrypted_png = aes_ctr.decrypt(ciphertext)
open('flag.png','wb').write(decrypted_png)
```

## AES-OFB Encrypted File with Key

Visualization of AES OFB

![](/files/Nu1H6g8LsMdc4DkQ7ORg)

Similar situation, except we don't care to recover the original nonce or IV. That is because in OFB mode we know that the next IV is going to be this next 16 byte block recovered from XOR-ing our "known plaintext" magic bytes and cipher text.

This would work for something encrypted with AES-OFB mode:

```python
from Crypto.Cipher import AES  
from Crypto.Util.strxor import strxor

key = b'sup3rrr s3cr3ttt'
ciphertext = open("flag.png.enc", "rb").read()
magic_bytes = bytes.fromhex("89504E470D0A1A0A0000000D49484452")

iv1 = strxor(ciphertext[0:16], magic_bytes)

ciphertext = ciphertext[16:]

cipher = AES.new(key, AES.MODE_CTR, nonce=iv1)
plaintext = cipher.decrypt(ciphertext)
open('output.png', 'wb').write(magic_bytes + plaintext)
```

## One Time Pad and Key Reuse

Any time there is mention of a One Time Pad and key reuse, I think of Crib Dragging. This is a method where you can guess the key of encrypted messages when you have two or more plaintext samples. It helps when the key is known phrases or excerpts.

[This tool](https://toolbox.lotusfa.com/crib_drag/) (or a similar one) might come in handy.

```
We intercepted some communications by an enemy cyber actor, and we believe it's possible to break their encryption scheme. We know they're using some form of a one-time pad, but fatally, they're reusing the key across all their messages. We know the group likes classical books, with one of the actors recently being into Charles Dickens, but we don't have much else to go off of. Please break these communications to get the password they've sent each other!

Encrypted message 1 (hex):
e0261e3f44788a669ff87710eb149eba87a9b7ce2abb92b45e862de035485ad02aff59e980e5b0db1657332f202bebb0abe591b6137d93854cddd82ad670caf50431b73fa787741a348e8166791c39ef396c658c1313dac4ac82ff5a948211c9503bb9318376d1a61f45c277ac6ac62e4cdfbf3a40753206b661e4bf4e99575d4253c8feaa6b43eb5b8fc415fa2173c497f0e2573671613aa04d72b5daa5aafcb7fd45850739053965c49016276c733b724e67f976b9ee61a633f95b1286435dc491cec07baf506949f97a56c20870c0b6cfe0e81a96bec40628e407e25d573a35f7c3f9f1af533a363e65b87e7b3948f0d138d2f9e8678e9fc38c83e4202cf00570c386d5fc334314936148efc4fc27db06315d9131fa0455d31e4349a7343ed87f152ace3a1f62552ba00bc36228ef5da754239273453634bdab2a4a6f3c12e3c94b91bbf7c521ca22ff84cb7aac3083a2a252fab727c4f1fcef43d7c32b4f2ba70b32eeefabd1da10980932d3af79c0f8c069ed09f73ef69793ddf7256b6c9473be6ca7907b065e0a0965418fc2daf03abf308c55639d827ea41b4a9a1874b41bfc2cf809083593bda5de46e306e142f9c65716eb1f1670bb8a4367b1920fb5490387b64c9ad105365dd9e812a044a70bfcc5fa6d01ae325aff2db252307c01d54f2d6c8764651a90ab69eb8146ead1c318a952fcd4648764e450f84e7cca18ddb8f0bd0f405a320a3ed8ce79bf0767ea1ed2915664082ad3d7671b59b79b7d9d5a7948952923fc2537fd14a40ac98344fe927adfbf805b39974fb525aeb7dceabcf58c363c04232ede54cfc0f02169ac06293e322d1ccceb1927f3a1c70328226b4dd812ee33df05a1728769167a25e245b58eb5ada6b26b71b0d7cdae8115d950a1824b445fb197e20318d61b69385ff5cc10600419209239735105f3feb09dfcad6b3a42aba213cb997e5cce2e04df6299fefaea04d9fd17819fd24477cf15d1fd0644cafed146175753a166a9344be3a8163fe855c71017b041ef49e2eff087d9de753b4f1518ca989b5f326407f82b0f98ecbdc9b900c075bed589c348d0e0b623658a31ddec507abce8518add4d9b00a847354f000da85303f704e722277102296f2bc62e6a4374d1f9f0ea56923ab84cade47ac393be38adfc67a3db39d29d08e6bc86ab987266618476daba54c678ae48658dc44e37974732347e95448d4b3fd88473df9875304d941f4009accace8bb696c6f266faf681a77b214a35312584c0ef835a0fea60e8eb0b3155908f97601a8596ba846f88ad63c197b266e2730d0b58e3c43262303528e1dbef86

Encrypted message 2 (hex):
ee205b29512bc37692fc7706e1479fe98defe3d22aa5d79f3df464ff70461bd765ab54e2c3e0a6d91103722c2a2bf0bfe6ef97e8137788c856c9c563c179dbbc0a38a133e88f3b1a7d94912e671070eb3d23619e0951c2c9e9d6f11d9acc11d8502abe3f9b3fd5ab5652d424f42bc33105c6b96901633516e225b4990d92165c0407cff7aa225fe2028fcc0fa37661d2d4f6ef46367d7f75b74d37facbeab1feb0af429406751f24728bd55f2724617a592141b178fce424ae33b6595d874218fad8ddc06eed506153a02d58d7086bdda1cff6e31897a8da5222e607cb5956223af5d5f9f1af533a362471b82a2f3348f0cb25d0fff573c792c9c9cbff3f69be577cd6cd8ebd320a07937057b8e4b237d1547a5c9866be0e48c65f455dab79309d3c0429cf680971532cab16df7266ee0fac542b9972006120bda77e58667013ecca4b92b1a3de27c920ab8ec772a46287e0b854a2b73bc4a8abf854c7902f0f67e51929a7c8eb9c9e0a9f1e3487fb65ceaca865f858e170bcde8addf9724f708824be79e790700c43431d654283c49fe721bf338c4963bbd673af0a1894097933ff4831fe40087d91aaa28107f00ba40be7835117e15b506ea88a1734aede08b75e0dcb9600939d513c5dd9eb05ab53ab0befc5f36706e73c1ea964b61a37614dc3003760d36323558cad3aae9940ef82da11bb48af95629c63e50eb153618818dda2ebbe03410e341c77c3c93ab71f34be14d68c186d4778d9da67525db79e35ce1c6252d06e2ffc2f63f708f01f8ad117f4c45a93e7c85d22d053a960e7b089edb6f28c33680f306b8959d9c2a23063ac1a2a70356205d4ef4b3ee1bdda54283e6314857d9406c501a772cb675d2814aa41b595bdb0b2e5736aa09f84bc8134dd56a7a43f6845a5c5b20f2392087d2e4da1c7191f441867a57b22411ec0fce883b4f8782409a7cb5bc9d52a46ce6f13df3094f1fca14bd0a826cf8cd10560c45bd1fa014ed6bad13b22760ca14ea92c47e3bb163ce9448f4302f15ffa1beaecf099d0c8217a431e57c0988344777501bd7c0998ecf8dab90bc46fbf9488c70bd3a3e46c72c530bfd9205deeba718c980c9a02a848674d011ba1194d991ee72627770d6c7864c72f3e4168c0aabfed479222bf1fe0fe36c093ba32adb567ae9a2687910efabcc8ead6057a618461ddb100d878b05f36d9c34d37934e24600d824a975a6dc79a658c896f2308814a5b1afec38fca979bdaf264fbfcd5b37d3e51703661c194f7904b5cfb66f5a50f2d1b8d93d86b0ccb87a08023d8bb75c7d0
```

### Solution

It's a crib dragging problem. Searching Charles Dickens opening lines for A Tale of Two Cities does the trick. Putting into the "guess text" box on the website they provided works.

![](/files/VTFQhTR1JZcBjHXPU9EU)

These Crib Words

```
It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way – in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only. 
There were a king with a large jaw and a queen with a plain face, on the throne of England; there were a king with a large jaw and a queen with a fair face, on the throne of France. In both countries it was clearer than crystal to the lords of the State preserves of loaves and fishes, that things in general were settled for ever.
```

Make sure for above you hit enter after the space after the period for the next paragraph.

Get this output

```
Great idea to use this XOR key to encrypt all of our communications, now it's impossible to know what we're talking about! I can tell you any secret in the world but since the XOR key is longer than any of our encrypted messages, it must be impossible to break, right? I do know one of my cybersecurity friends was telling me something about key reuse, but I'm sure that it's fine. Anyway, let's get to why I encrypt§ ÷this message in the first place. I'll be sending you a zip file with our evil plans soon, it's super critical that no one reads the contents, so I thought it would be wise to send the password sOparately. The password is MetaCTF{cr1b_dr4gg1ng_7h3_b00k_c1ph3r}. I'll reach back out to you soon! PS: I sent the start of that book in the other message for you PPS: Oh and I couldn't forget to put one of my favorite quotes: "Who controls the past controls the future. Who controls the present controls the past"
```

![](/files/fAMDkA20LnTnrZTrZhzR)

## Hash Brute-Forcing

### Question

We got our hands on [a dump](https://metaproblems.com/07af9c63cb1767cddf8b0a7220cbf2ef/bigcorp.sql) of Bigcorp's user database, with passwords in it! Looks like they're hashed, though. Good news is, we also have the hashing function they used:

```
md5("PASS" . hash('sha256', $pwd) . "THESALT" . hash('sha256', $salt) . "ANDPEPPER" . hash('sha256', "12345678")) 
```

Can you figure this out? We know that exactly one of their users used a password that can be found in `rockyou.txt`. The flag will be that password (it will *not* be in flag format). Good luck!

### Solution

Opening the SQL dump in VS Code we can see a list of users, their password hashes, and their associated salts.

![](/files/hJli2OcKTpcDNTpLN5wz)

Seeing as there are 50 users, hashes, and salts, I pasted that line into Gemini and asked it to format them into two lists, one of hashes and one of salts.

Known hashes:

```python
known_hashes_list = [
    'aa2c7fca38895cec18223aae5782d50f',
    'ad573b6cbe3ef40128c866e04e80128e',
    '40e0e6cb2f197c45102ee9a3d1485884',
    '7b02b29678ea4d3864c4d7d9f574a9ed',
    '5df2ee218ae1a48e2b97cf15faeea70d',
    '1be433927e4ae99a6614f3675639ee39',
    '5f99bc6bee49fe1e2ec021541459e81f',
    '4cbe3ab502c676e8bc692a652f72ff0e',
    'eb97dc96e0e3e553f506376984372d05',
    '7bf54cee518180da950b547c75e61c61',
    '599fb9ff25d7f229fa499493321e83ec',
    '00e974374e2f4f42638b1cc6511e0fca',
    '461e74cd4aee197ee25f582fc6a6a6c3',
    '4ab64a0b21e5e608c1c33c283a734d87',
    '8170888219671e8a475ebf2ea389250f',
    'df5c870197ced4dd035b724c1726960c',
    'e8d76ee086b41a18deedcd9e982a5670',
    '629d329c08115eeb2be272ecebae3eec',
    '386ddc0e1f2f882430064e97548238d9',
    '2eee8916ce6492c6e39dd307c7b1f5f7',
    '2c101f9e944f0e197954bce2c07f6876',
    '1e35fee860b72ecf5d3d633d9d9d655f',
    '56035ba57c68a5d6298bac2928b31abc',
    '4a4415f218123d5d3a5cd3377fa4c4fa',
    'd1c2dfd6e400281af0d51482b6292ac7',
    'a1b1ce40b4d26bf5427c04c315e84e65',
    '9f9d653c6a8cd5548d1667cce1602439',
    'fc37175d9df7c63818e7dcd38d740b96',
    'a1ad941b606e6dd7e608271b778cd2a6',
    '1f00e1db67f6e659e9efe2071bc76558',
    '1472a898aac46ab40f3d2731d630b096',
    '8b7fc076e8a85d563cc22f3a4c781b72',
    'd809ab64ddd9a727fa07ba4b02eeb42c',
    'a79079c52c495448dad8279f1eca935b',
    '8dac3b3508f2ea66604dde84b42bdeb5',
    '2a3cf5b313adcb95e52faf6ee8f1ddfa',
    '9782fb92e46dd895f773faa07cf06fbf',
    'e036463380e3b50a6d153d50fd930589',
    '60a633cbe65ed3897cda728c460de1ef',
    '91e5ba6408bd8528234ca6df528efcd3',
    'b323ca64caa1c573964363cf590e1933',
    '600e55cfb77df932f3297e6f6bf7ce5d',
    'c35cbdc998f5d4dffe895533b2a45a1a',
    '9df5df7768f28357acf42efc6dbd9b45',
    '3c21b5c8212f0a884bd94dfe76e708ae',
    '17c8855ffb9469c5f37578d702b63b8b',
    '0027365e3c239554f9674d599375f10c',
    '4032429d812e9b0a076d0d2539db9f42',
    '02f0845b3973563659a5f20fc3c24c2e',
    '5bfe7124490dd9a835edea3571a8256b'
    ]
```

Known salts (keep in mind these are parallel lists, 1:1, user:salt):

```python
salt_values = [
    'b9c294366eab9644b4a9d117d2f30169',
    'be324d466e9ed65b1f5bf0ccb769cb1a',
    '91a646fa333fbcc9963d3fa66aba3b9a',
    '848e57db0af826a0fd4015f93c726cb5',
    '039cedcb1916dc6d13cf4155b00f8513',
    'c60b4ab78618298c3722a776b2fe730c',
    'd6297ff7310551b3f325cbac71138655',
    'ec856e063a88d154bae9cd08b1c8eb5b',
    'd2ac6f089b850d28ffc05140df18dc58',
    '6094da29e26e34f0bbac2edd368292f8',
    'b361e0ddda23c367dab5a7a6483ef674',
    '4972eed63ec816aab8d5be9085fc76da',
    'da7d43e8e28caacbc38c71657728e86b',
    '9dc2ad748f19034bcda4c18d4fc2b9f6',
    '24f62c412ed8ea07a50f0c32d0e6e46e',
    '5d1bbb59be1a61ca409973d6125813cc',
    '0f333ca4d47dfd4e94a4eb07139cab71',
    'c842fff45395bd19b36fceecd5a3584e',
    'b1c21543413f44ef1d5bd62743c66293',
    '291e47b4d90d72c1e9a3b3217a791493',
    '3f28bd11a2388e4db4439670f04f3d5f',
    'c7c12e5e2c48ac6a0e46e3c9ac3fa0e1',
    '13c0002926ca4cd992f9b870fa1ec30f',
    '4f689bac81818f82aaa75a5c69501abd',
    'd2e734c2bce9698ea4cffa6dc156a426',
    '6aa408edd967247b70237c0ae3f065b7',
    '45446bf86edf5ad7627d9324e015e558',
    '00b5d3b406e6dff9438ca3794b1baf4a',
    '5dcdab6b72c6e2ca1c39acf5ab9d2bd1',
    '69396bc3f4aa40d1f49c202ac277128d',
    'cae5bceb044fb3cfc5d54783464e1f4b',
    'a931ec4de5caeca605d8df2b78c9ce90',
    'b9e454b84a9ac734949245c37223016f',
    'f7378c440e101d3b2c5fb8a705927232',
    'f74affbcd75a84599bda778490057dfd',
    '1329d3bcf6c195377b4cc43d14cc4352',
    '35d4460afb5df8f8939670a997ea5000',
    '103ecc55f3965acea7bd5caaf27034c6',
    '1c583ab7a1a44b00f96276e6587677b1',
    'ca3bb4bce2e277675ca0fa95bd57abf5',
    'a8dcad922fb6542b5832ae01ad214f43',
    '6612fd5d0c18d64dca96601b429b3ecc',
    '756c9af9bc12855b1ab0e3892c0d8cd6',
    '60ba7fee820fcec77e08b30fa2ad5c78',
    '5db6432c4d30c92df0123aa8f95c7c4a',
    'ebb9cb2840d6ca82cede895cc6048d00',
    'dc664139914a296dd210016f41c29f41',
    '921978d46563a20441551b9add75094d',
    '356d13ab6e00ce4c17653630e0dabfc1',
    '5db80705c809fe2b0e0ad983c463d24d'
    ]
```

Now that we have list of hashes and salts, let's break down the known hashing algorithm given to us:

```
md5("PASS" . hash('sha256', $pwd) . "THESALT" . hash('sha256', $salt) . "ANDPEPPER" . hash('sha256', "12345678")) 
```

After running through Gemini to get some help on the syntax of this, we can break it down as such:

* 3 literal strings alternate concatenation with 3 SHA256 hashing operations of different inputs. One of the password variable, one of the salt variable, and one of a fixed "pepper" string.
* After all of these are concatenated (the periods are indicative of PHP concatenation or something related) they are md5 hashed

Since we know exactly one of the passwords is in `rockyou.txt` and we know the hashing function, hashes, and salts, we can develop an approach to figure out what that one password is.

We cannot reverse a hash, but, since we have the `rockyou.txt` list, the salts and hashes per user, and the hash function, we can insert each of these variables for each user and each word in `rockyou.txt`. This will be an expensive and long process, but works.

The script below implements the custom hashing function in Python. It then, essentially, opens a loop using the salts list (as that is an input for the hash function, and will need to be in each one), then opens a loop with the contents of the `rockyou.txt` file, passing both the salt and a password guess from the file into the hashing function. Once it is in the hashing function, the has is calculated with that exact salt and password combination, and is compared to check if that hash is in the list of 50 known hashes. Eventually this results in a correct guess.

Note: Using `rockyou.txt` in this way may throw errors, as the typical version of it shipped with Kali is not UTF-8 encoded. Opening it in VS Code and resaving it ensuring UTF-8 encoding fixed the issue.

Script:

```python
import hashlib

def hash_function(password, salt):

    # Calculate SHA256 of the password
    sha256_pwd = hashlib.sha256(password.encode('utf-8')).hexdigest()

    # Calculate SHA256 of the provided salt
    sha256_salt = hashlib.sha256(salt.encode('utf-8')).hexdigest()

    # Calculate SHA256 of the fixed string "12345678"
    sha256_fixed = hashlib.sha256("12345678".encode('utf-8')).hexdigest()

    # Concatenate all parts as specified
    concatenated_string = "PASS" + sha256_pwd + "THESALT" + sha256_salt + "ANDPEPPER" + sha256_fixed

    # Calculate the MD5 hash of the concatenated string
    md5_hash = hashlib.md5(concatenated_string.encode('utf-8')).hexdigest()
    
    known_hashes_list = [
    'aa2c7fca38895cec18223aae5782d50f',
    'ad573b6cbe3ef40128c866e04e80128e',
    '40e0e6cb2f197c45102ee9a3d1485884',
    '7b02b29678ea4d3864c4d7d9f574a9ed',
    '5df2ee218ae1a48e2b97cf15faeea70d',
    '1be433927e4ae99a6614f3675639ee39',
    '5f99bc6bee49fe1e2ec021541459e81f',
    '4cbe3ab502c676e8bc692a652f72ff0e',
    'eb97dc96e0e3e553f506376984372d05',
    '7bf54cee518180da950b547c75e61c61',
    '599fb9ff25d7f229fa499493321e83ec',
    '00e974374e2f4f42638b1cc6511e0fca',
    '461e74cd4aee197ee25f582fc6a6a6c3',
    '4ab64a0b21e5e608c1c33c283a734d87',
    '8170888219671e8a475ebf2ea389250f',
    'df5c870197ced4dd035b724c1726960c',
    'e8d76ee086b41a18deedcd9e982a5670',
    '629d329c08115eeb2be272ecebae3eec',
    '386ddc0e1f2f882430064e97548238d9',
    '2eee8916ce6492c6e39dd307c7b1f5f7',
    '2c101f9e944f0e197954bce2c07f6876',
    '1e35fee860b72ecf5d3d633d9d9d655f',
    '56035ba57c68a5d6298bac2928b31abc',
    '4a4415f218123d5d3a5cd3377fa4c4fa',
    'd1c2dfd6e400281af0d51482b6292ac7',
    'a1b1ce40b4d26bf5427c04c315e84e65',
    '9f9d653c6a8cd5548d1667cce1602439',
    'fc37175d9df7c63818e7dcd38d740b96',
    'a1ad941b606e6dd7e608271b778cd2a6',
    '1f00e1db67f6e659e9efe2071bc76558',
    '1472a898aac46ab40f3d2731d630b096',
    '8b7fc076e8a85d563cc22f3a4c781b72',
    'd809ab64ddd9a727fa07ba4b02eeb42c',
    'a79079c52c495448dad8279f1eca935b',
    '8dac3b3508f2ea66604dde84b42bdeb5',
    '2a3cf5b313adcb95e52faf6ee8f1ddfa',
    '9782fb92e46dd895f773faa07cf06fbf',
    'e036463380e3b50a6d153d50fd930589',
    '60a633cbe65ed3897cda728c460de1ef',
    '91e5ba6408bd8528234ca6df528efcd3',
    'b323ca64caa1c573964363cf590e1933',
    '600e55cfb77df932f3297e6f6bf7ce5d',
    'c35cbdc998f5d4dffe895533b2a45a1a',
    '9df5df7768f28357acf42efc6dbd9b45',
    '3c21b5c8212f0a884bd94dfe76e708ae',
    '17c8855ffb9469c5f37578d702b63b8b',
    '0027365e3c239554f9674d599375f10c',
    '4032429d812e9b0a076d0d2539db9f42',
    '02f0845b3973563659a5f20fc3c24c2e',
    '5bfe7124490dd9a835edea3571a8256b'
    ]
    
    if md5_hash in known_hashes_list :
        print("Success")
        print(password)
    
if __name__ == "__main__":

    wordlist_filename = "/usr/share/wordlists/rockyou.txt"
    salt_values = [
    'b9c294366eab9644b4a9d117d2f30169',
    'be324d466e9ed65b1f5bf0ccb769cb1a',
    '91a646fa333fbcc9963d3fa66aba3b9a',
    '848e57db0af826a0fd4015f93c726cb5',
    '039cedcb1916dc6d13cf4155b00f8513',
    'c60b4ab78618298c3722a776b2fe730c',
    'd6297ff7310551b3f325cbac71138655',
    'ec856e063a88d154bae9cd08b1c8eb5b',
    'd2ac6f089b850d28ffc05140df18dc58',
    '6094da29e26e34f0bbac2edd368292f8',
    'b361e0ddda23c367dab5a7a6483ef674',
    '4972eed63ec816aab8d5be9085fc76da',
    'da7d43e8e28caacbc38c71657728e86b',
    '9dc2ad748f19034bcda4c18d4fc2b9f6',
    '24f62c412ed8ea07a50f0c32d0e6e46e',
    '5d1bbb59be1a61ca409973d6125813cc',
    '0f333ca4d47dfd4e94a4eb07139cab71',
    'c842fff45395bd19b36fceecd5a3584e',
    'b1c21543413f44ef1d5bd62743c66293',
    '291e47b4d90d72c1e9a3b3217a791493',
    '3f28bd11a2388e4db4439670f04f3d5f',
    'c7c12e5e2c48ac6a0e46e3c9ac3fa0e1',
    '13c0002926ca4cd992f9b870fa1ec30f',
    '4f689bac81818f82aaa75a5c69501abd',
    'd2e734c2bce9698ea4cffa6dc156a426',
    '6aa408edd967247b70237c0ae3f065b7',
    '45446bf86edf5ad7627d9324e015e558',
    '00b5d3b406e6dff9438ca3794b1baf4a',
    '5dcdab6b72c6e2ca1c39acf5ab9d2bd1',
    '69396bc3f4aa40d1f49c202ac277128d',
    'cae5bceb044fb3cfc5d54783464e1f4b',
    'a931ec4de5caeca605d8df2b78c9ce90',
    'b9e454b84a9ac734949245c37223016f',
    'f7378c440e101d3b2c5fb8a705927232',
    'f74affbcd75a84599bda778490057dfd',
    '1329d3bcf6c195377b4cc43d14cc4352',
    '35d4460afb5df8f8939670a997ea5000',
    '103ecc55f3965acea7bd5caaf27034c6',
    '1c583ab7a1a44b00f96276e6587677b1',
    'ca3bb4bce2e277675ca0fa95bd57abf5',
    'a8dcad922fb6542b5832ae01ad214f43',
    '6612fd5d0c18d64dca96601b429b3ecc',
    '756c9af9bc12855b1ab0e3892c0d8cd6',
    '60ba7fee820fcec77e08b30fa2ad5c78',
    '5db6432c4d30c92df0123aa8f95c7c4a',
    'ebb9cb2840d6ca82cede895cc6048d00',
    'dc664139914a296dd210016f41c29f41',
    '921978d46563a20441551b9add75094d',
    '356d13ab6e00ce4c17653630e0dabfc1',
    '5db80705c809fe2b0e0ad983c463d24d'
    ]
    
    for salt in salt_values :
        with open(wordlist_filename, 'r', encoding='utf-8') as infile:
            for line in infile:
                word = line.strip() # Remove leading/trailing whitespace, including newlines
                if word: # Ensure the line isn't empty after stripping
                    hashed_value = hash_function(word, salt)
```

## Predictable Passwords

### Question

We intercepted this sysadmin emailing himself password csv backups, and it seems like his master passwords are predictably weak. Our analysts were able to crack all but the latest dump, can you crack the first 5, find a pattern, then crack the 6th?

Download all 6 password dumps [here.](https://metaproblems.com/66110f0800cf839f6a782c7356322781/password_dumps.zip)

### Solution

Using bash loop and `zip2john` convert first 5 zip files to text files containing a `john` crackable hash.

```
for i in {1..5}; do zip2john passwords${i}.zip >> passwords.hashes; done 
```

Use `john` and `rockyou.txt` to crack these so we can view the pattern the syadmin is following.

```
john passwords.hashes --wordlist=/usr/share/wordlists/rockyou.txt
```

This reveals the following passwords:

```
naruto427
Deathnote666
dragonball369
sailormoon121
onepiece123
```

The pattern seems to be lowercase or uppercase first letter, the title of an anime, and 3 numbers. We can use `john` to make a custom rule to account for those variables, and put it in `/etc/john/john.conf`.

```
[List.Rules:Custom]
cAz"[0-9][0-9][0-9]"
Az"[0-9][0-9][0-9]"
```

As for getting a list of anime titles, this can be done with Gemini (or any LLM). At first I simply asked for a list of top 50/100/200 famous anime titles and told it to format into a list I could paste as the wordlist for `john`. However, that did that not work. After talking to the judge, and getting the hint "it's come out in the last 10 years," adding that to the prompt yielded better results.

Putting that list into a text file and referencing it as the wordlist and including the new custom rule, running `john` cracks the password.

```
john passwords6.txt --wordlist=/home/kali/password_dumps/anime3.txt --rules=Custom
```

Password6, the flag, is revealed:

```
Chainsawman715
```
