Skip to content

Commit

Permalink
Add rgbCTF writeups
Browse files Browse the repository at this point in the history
  • Loading branch information
Lindzy committed Jul 20, 2020
1 parent 4162cfb commit e2344f7
Show file tree
Hide file tree
Showing 98 changed files with 102,351 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Adequate Encryption Standard
## Challenge Description
I wrote my own AES! Can you break it?

hQWYogqLXUO+rePyWkNlBlaAX47/2dCeLFMLrmPKcYRLYZgFuqRC7EtwX4DRtG31XY4az+yOvJJ/pwWR0/J9gg==

File: adequate_encryption_standard.py

## Solution

The file `adequate_encryption_standard.py` contains an encryption method which resembles
AES. The encryption method contains a bug in the round function.

```
for _ in range(ROUNDS):
block = enc_sub(block)
block = enc_perm(block)
block = bytearray(block)
for i in range(len(block)):
block[i] ^= key[idx]
```

After every round of regular AES, the cipher state is XOR-ed with a 16-byte round key.
However, in this case `key[idx]` uses the wrong loop variable, and its value does
not change over the encryption of an entire block. Thus we can brute force the possible
values for `key[idx]` for each block of the provided ciphertext.

We write the corresponding decryption method for the cipher, test block decryption via
brute force for every block, and recover the flag.

[Solution](decode.py)

## Flag
```rgbCTF{brut3_f0rc3_is_4LW4YS_th3_4nsw3r(but_with_0ptimiz4ti0ns)}```

### Author
[keegan](https://twitter.com/inf_0_)
109 changes: 109 additions & 0 deletions rgbCTF2020/Cryptography/AdequateEncryptionStandard/decode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import base64
from adequate_encryption_standard import *


pbox_i = [pbox.index(i) for i in range(64)]

def dec_perm(in_bytes: bytes) -> bytes:
num = int.from_bytes(in_bytes, 'big')
binary = bin(num)[2:].rjust(BLOCK_SIZE * 8, '0')
permuted = ''.join([binary[pbox_i[i]] for i in range(BLOCK_SIZE * 8)])
out = bytes([int(permuted[i:i + 8], 2) for i in range(0, BLOCK_SIZE * 8, 8)])
return out

def dec_sub(in_bytes: bytes) -> bytes:
return bytes([sbox.index(b) for b in in_bytes])

def encrypt(plain: bytes, key: bytes) -> bytes:
blocks = to_blocks(plain)
out = bytearray()
key = expand_key(key, len(blocks))
for idx, block in enumerate(blocks):
block = pad(block)
assert len(block) == BLOCK_SIZE
for _ in range(ROUNDS):
block = enc_sub(block)
block = enc_perm(block)
block = bytearray(block)
for i in range(len(block)):
block[i] ^= key[idx]
out.extend(block)
return bytes(out)

def decrypt(cipher: bytes, key: bytes) -> bytes:
blocks = to_blocks(cipher)
out = bytearray()
key = expand_key(key, len(blocks))
for idx, block in enumerate(blocks):
for _ in range(ROUNDS):
block = bytearray(block)
for i in range(len(block)):
block[i] ^= key[idx]
block = bytes(block)
assert len(block) == BLOCK_SIZE
block = dec_perm(block)
block = dec_sub(block)
out.extend(block)
return bytes(out)

def decrypt_ks(cipher: bytes, keystream: bytes) -> bytes:
blocks = to_blocks(cipher)
out = bytearray()
key = keystream
for idx, block in enumerate(blocks):
for _ in range(ROUNDS):
block = bytearray(block)
for i in range(len(block)):
block[i] ^= key[idx]
block = bytes(block)
assert len(block) == BLOCK_SIZE
block = dec_perm(block)
block = dec_sub(block)
out.extend(block)
return bytes(out)

def score(pt):
score = 0
for p in pt:
if p < 128:
score += 1
return score

def hack_block(ct):
assert len(ct) == BLOCK_SIZE

ks = b"\xe4"
for i in range(256):
ks = bytes(bytearray([i]))

pt = decrypt_ks(ct, ks)
if score(pt) == 8:
print(hex(i), pt)

def hack(ct, i):
blocks = to_blocks(ct)

print(f"Block {i}")
hack_block(ct[i*BLOCK_SIZE:(i+1)*BLOCK_SIZE])
print()


def main():
ciphertext = "hQWYogqLXUO+rePyWkNlBlaAX47/2dCeLFMLrmPKcYRLYZgFuqRC7EtwX4DRtG31XY4az+yOvJJ/pwWR0/J9gg=="
ct = base64.b64decode(ciphertext)
hack(ct, 0)
hack(ct, 1)
hack(ct, 2)
hack(ct, 3)
hack(ct, 4)
hack(ct, 5)
hack(ct, 6)
hack(ct, 7)

ks = bytearray([0x9f, 0xc1, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01])
print(decrypt_ks(ct, ks))



if __name__ == "__main__":
main()
12,786 changes: 12,786 additions & 0 deletions rgbCTF2020/Cryptography/GrabYourJisho/grab your jisho.txt

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions rgbCTF2020/Cryptography/GrabYourJisho/grab_your_jisho.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Grab your jisho
## Challenge Description

これは文字化けか?それとも暗号…?

File: grab your jisho

## Solution

We are provided with a 2MB text file with characters from the Unicode CJK blocks.
The text is not recognized by web translation services, so we conclude the flag must be found some other way. We find the following string resembling a URL:

```昒鏽霱彊://鱓攫襵.扺譸醸叏褖𠆢夳鯁曵.蕐屩形/尤甠賿䵷/_/_/_/_/_____```

It seems like individual characters correspond directly to individual A-Z letters.
For example, "昒鏽霱彊://鱓攫襵." probably decodes to "http://www." This tells us we
are dealing with some sort of substitution cipher. However, it is not one-to-one
substitution, since otherwise we would expect each of the "t"s or "w"s to match the
others. This tells us that either we are working with a many-to-one substitution cipher
or perhaps a Vigenere cipher. With so much ciphertext, we can just treat it as a
many-to-one cipher, where one decoded Latin alphabet letter can be represented by
one of many CJK characters.

We start by identifying the most frequently appearing character and all of the words
it appears in. We use a dictionary of English words and their frequency to determine
the likelihood that the CJK character is an "A," "B," "C," and so on. For example, if we
are considering the character "躄," and we see start three letter words like "躄呵芀" or
"躄亟玊" or words with apostrophes like "齃劅魂'躄," we might speculate that the character
decodes to a "T" or a "S," since words with those letters in the same position are common.

If one candidate letter is significantly more likely than all the others, we make the
substitution in the text. We repeat until we have recovered a large portion of the text.
This reveals that the plaintext is taken from the book "Myths & Legends of Japan."
We find the flag added to this text, except there are some unique characters which only
appear once in the flag and cannot be decoded using frequency analysis.

```RGBCTF讞鸞鸚鱺YOMINIKUI鸝钁厵鬱```

"Yominikui" translates to "hard to read" or "illegible," so we know we are at least on the
right track. At this point, we look at the relationship between the CJK characters and Latin
characters we've decoded already. As a sample, observe the following:

```A:一 B:儿 C:亍 Z:髗```

The number of strokes in a character appears to correspond to the letter of the alphabet.
In fact, jisho.org helpfully reports the number of strokes used to write a character.
We input the 8 remaining characters to the site and find they take 27, 30, 28, 30, 30,
28, 30, and 29 strokes respectively. Since these values are greater than 26, which
corresponds to Z, we assume that these characters decode to the ASCII characters directly following z. This way the flag has the correct format, and we can understand the remaining characters in the flag.

## Flag
```rgbctf{~|~yominikui~|~}```

### Author
[keegan](https://twitter.com/inf_0_)
Loading

0 comments on commit e2344f7

Please sign in to comment.