본문 바로가기

Wargame/Krypton

[ Docker ] Krypton Wargame 만들기 - 6번 문제 ( 8 / 8 )

1. Krypton6 목표

Hopefully by now its obvious that encryption using repeating keys is a bad idea.
Frequency analysis can destroy repeating/fixed key substitution crypto.

A feature of good crypto is random ciphertext.
A good cipher must not reveal any clues about the plaintext.
Since natural language plaintext (in this case, English) contains patterns,
it is left up to the encryption key or the encryption algorithm to add the ‘randomness’.

Modern ciphers are similar to older plain substitution ciphers, but improve the ‘random’ nature of the key.

An example of an older cipher using a complex, random, large key is a vigniere using a key of the same size of the plaintext.
For example, imagine you and your confident have agreed on a key using the book ‘A Tale of Two Cities’ as your key, in 256 byte blocks.

The cipher works as such:

Each plaintext message is broken into 256 byte blocks.
For each block of plaintext, a corresponding 256 byte block from the book is used as the key,
starting from the first chapter, and progressing.
No part of the book is ever re-used as key.
The use of a key of the same length as the plaintext, and only using it once is called a “One Time Pad”.

Look in the krypton6 directory.
You will find a file called ‘plain1’, a 256 byte block.
You will also see a file ‘key1’, the first 256 bytes of ‘A Tale of Two Cities’.
The file ‘cipher1’ is the cipher text of plain1.
As you can see (and try) it is very difficult to break the cipher without the key knowledge.

If the encryption is truly random letters, and only used once, then it is impossible to break.
A truly random “One Time Pad” key cannot be broken.
Consider intercepting a ciphertext message of 1000 bytes.
One could brute force for the key, but due to the random key nature,
you would produce every single valid 1000 letter plaintext as well.
Who is to know which is the real plaintext?!?

Choosing keys that are the same size as the plaintext is impractical.
Therefore, other methods must be used to obscure ciphertext against frequency analysis in a simple substitution cipher.
The impracticality of an ‘infinite’ key means that the randomness, or entropy, of the encryption is introduced via the method.

We have seen the method of ‘substitution’.
Even in modern crypto, substitution is a valid technique.
Another technique is ‘transposition’, or swapping of bytes.

Modern ciphers break into two types; symmetric and asymmetric.

Symmetric ciphers come in two flavours: block and stream.

Until now, we have been playing with classical ciphers, approximating ‘block’ ciphers.
A block cipher is done in fixed size blocks (suprise!).
For example, in the previous paragraphs we discussed breaking text and keys into 256 byte blocks, and working on those blocks.
Block ciphers use a fixed key to perform substituion and transposition ciphers on each block discretely.

Its time to employ a stream cipher.
A stream cipher attempts to create an on-the-fly ‘random’ keystream to encrypt the incoming plaintext one byte at a time.
Typically, the ‘random’ key byte is xor’d with the plaintext to produce the ciphertext.
If the random keystream can be replicated at the recieving end, then a further xor will produce the plaintext once again.

From this example forward, we will be working with bytes, not ASCII text, so a hex editor/dumper like hexdump is a necessity. Now is the right time to start to learn to use tools like cryptool.

In this example, the keyfile is in your directory, however it is not readable by you.
The binary ‘encrypt6’ is also available.
It will read the keyfile and encrypt any message you desire, using the key AND a ‘random’ number.
You get to perform a ‘known ciphertext’ attack by introducing plaintext of your choice.
The challenge here is not simple, but the ‘random’ number generator is weak.

As stated, it is now that we suggest you begin to use public tools, like cryptool, to help in your analysis.
You will most likely need a hint to get going.
See ‘HINT1’ if you need a kicktstart.

If you have further difficulty, there is a hint in ‘HINT2’.

The password for level 7 (krypton7) is encrypted with ‘encrypt6’.

Good Luck!

 

2. Krypton6 구현

# 비밀번호 root 입력 접속
ssh -oStrictHostKeyChecking=no root@localhost -p 2231

mkdir -p /krypton/krypton6/onetime

cat <<'EOF' > /krypton/krypton6/HINT1
The 'random' generator has a limited number of bits, and is periodic.
Entropy analysis and a good look at the bytes in a hex editor will help.

There is a pattern!
EOF

cat <<EOF > /krypton/krypton6/onetime/plain1
SINGOGODDESSTHEANGEROFACHILLESSONOFPELEUSTHATBROUGHTCOUNTLESSILLSUPONTHEACHAEANSMANYABRAVESOULDIDITSENDHURRYINGDOWNTOHADESANDMANYAHERODIDITYIELDAPREYTODOGSANDVULTURESFORSOWERETHECOUNSELSOFJOVEFULFILLEDFROMTHEDAYONWHICHTHESONOFATREUSKINGOFMENANDGREATACHILL
EOF

cat <<EOF > /krypton/krypton6/onetime/key1
ITWASTHEBESTOFTIMESITWASTHEWORSTOFTIMESITWASTHEAGEOFWISDOMITWASTHEAGEOFFOOLISHNESSITWASTHEEPOCHOFBELIEFITWASTHEEPOCHOFINCREDULITYITWASTHESEASONOFLIGHTITWASTHESEASONOFDARKNESSITWASTHESPRINGOFHOPEITWASTHEWINTEROFDESPAIRWEHADEVERYTHINGBEFOREUSWEHADNOTHINGBEF
EOF

cat <<EOF | base64 -d > /krypton/krypton6/onetime/cipher1
QUJKR0daVkhFSUtMSE1YSVpLV1pIQkFVQVBQSFNKS0hCVFlYUVBXQ0xQSFNNSVZPQUtWWVlXTVFI
WE1MT0lERVpZUFVSSE1KT1FTSVdIQVdFU1ZSV0JKVENJV0RJTktXSUpYRE1SSVBOTlJRQlVLSERL
UEFDTUlRR0pFUVhYSUdXSUFBUkdXUEhBWFlBU1lSRkFaS0ZNV1dLR0tUVUhOWUxMSUVTWElPSUNC
QVdKTU1ERVVIQlJLVENBQkxYVENTVVlUWUVMRFhLSk5XWk1MVlJGQlNGTEhRVERYT0VWU0lTV1lN
WU1IWUxNU1VGSkdXSkVVREpFU1RBSVBOSlBR
EOF

echo 8 bit LFSR > /krypton/krypton6/HINT2

echo WEAKRANDOM > /krypton/krypton6/keyfile.dat

echo QkVMT1MgWg== | base64 -d > /krypton/krypton5/krypton7

cat <<'EOF' > /krypton/krypton6/README
Hopefully by now its obvious that encryption using repeating keys
is a bad idea.  Frequency analysis can destroy repeating/fixed key
substitution crypto.

A feature of good crypto is random ciphertext.  A good cipher must
not reveal any clues about the plaintext.  Since natural language
plaintext (in this case, English) contains patterns, it is left up
to the encryption key or the encryption algorithm to add the
'randomness'.

Modern ciphers are similar to older plain substitution
ciphers, but improve the 'random' nature of the key.

An example of an older cipher using a complex, random, large key
is a vigniere using a key of the same size of the plaintext.  For
example, imagine you and your confident have agreed on a key using
the book 'A Tale of Two Cities' as your key, in 256 byte blocks.

The cipher works as such:

Each plaintext message is broken into 256 byte blocks.  For each
block of plaintext, a corresponding 256 byte block from the book
is used as the key, starting from the first chapter, and progressing.
No part of the book is ever re-used as key.  The use of a key of the
same length as the plaintext, and only using it once is called a "One Time Pad".

Look in the krypton6/onetime  directory.  You will find a file called 'plain1', a 256
byte block.  You will also see a file 'key1', the first 256 bytes of
'A Tale of Two Cities'.  The file 'cipher1' is the cipher text of
plain1.  As you can see (and try) it is very difficult to break
the cipher without the key knowledge.

(NOTE - it is possible though.  Using plain language as a one time pad
key has a weakness.  As a secondary challenge, open README in that directory)

If the encryption is truly random letters, and only used once, then it
is impossible to break.  A truly random "One Time Pad" key cannot be
broken.  Consider intercepting a ciphertext message of 1000 bytes.  One
could brute force for the key, but due to the random key nature, you would
produce every single valid 1000 letter plaintext as well.  Who is to know
which is the real plaintext?!?

Choosing keys that are the same size as the plaintext is impractical.
Therefore, other methods must be used to obscure ciphertext against
frequency analysis in a simple substitution cipher.  The
impracticality of an 'infinite' key means that the randomness, or
entropy, of the encryption is introduced via the method.

We have seen the method of 'substitution'.  Even in modern crypto,
substitution is a valid technique.  Another technique is 'transposition',
or swapping of bytes.

Modern ciphers break into two types; symmetric and asymmetric.

Symmetric ciphers come in two flavours: block and stream.

Until now, we have been playing with classical ciphers, approximating
'block' ciphers.  A block cipher is done in fixed size blocks (suprise!).
For example, in the previous paragraphs we discussed breaking text and keys
into 256 byte blocks, and working on those blocks.  Block ciphers use a
fixed key to perform substituion and transposition ciphers on each
block discretely.

Its time to employ a stream cipher.  A stream cipher attempts to create
an on-the-fly 'random' keystream to encrypt the incoming plaintext one
byte at a time.  Typically, the 'random' key byte is xor'd with the
plaintext to produce the ciphertext.  If the random keystream can be
replicated at the recieving end, then a further xor will produce the
plaintext once again.

From this example forward, we will be working with bytes, not ASCII
text, so a hex editor/dumper like hexdump is a necessity.  Now is the
right time to start to learn to use tools like cryptool.

In this example, the keyfile is in your directory, however it is
not readable by you.  The binary 'encrypt6' is also available.
It will read the keyfile and encrypt any message you desire, using
the key AND a 'random' number.  You get to perform a 'known ciphertext'
attack by introducing plaintext of your choice.  The challenge here is
not simple, but the 'random' number generator is weak.

As stated, it is now that we suggest you begin to use public tools, like cryptool,
to help in your analysis.  You will most likely need a hint to get going.
See 'HINT1' if you need a kicktstart.

If you have further difficulty, there is a hint in 'HINT2'.

The password for level 7 (krypton7) is encrypted with 'encrypt6'.

Good Luck!
EOF

echo UE5VS0xZTFdSUUtHS0JF | base64 -d > /krypton/krypton6/krypton7

chown krypton6:krypton6 /krypton/krypton6/*

chown root:root /krypton/krypton6/onetime

chmod 755 /krypton/krypton6/onetime

chown krypton6:krypton6 /krypton/krypton6/onetime/*

chmod 640 /krypton/krypton6/*

chmod 640 /krypton/krypton6/onetime/*

# krypton7 유저를 생성하여 준다.
useradd krypton7 && echo -e "LFSRISNOTRANDOM\nLFSRISNOTRANDOM" | passwd krypton7

chown krypton7:krypton7 /krypton/krypton6/keyfile.dat

chmod 640 /krypton/krypton6/keyfile.dat

echo 'LFSRISNOTRANDOM' > /etc/krypton_pass/krypton7

chown krypton7:krypton7 /etc/krypton_pass/krypton7

chmod 400 /etc/krypton_pass/krypton7

mkdir -p /krypton/krypton7

echo Congratulations on beating Krypton! > /krypton/krypton7/README

chown krypton7:krypton7 /krypton/krypton7/*

chmod 640 /krypton/krypton7/*

cat <<'EOF' > /tmp/encrypt6.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>

#define KEY_LENGTH 30

#define VIGENERE_KEY "IECJMGLFKNOPHDB"
#define VIGENERE_KEY_LENGTH 15

void vigenere_encrypt(char *input) 
{
    for (int i = 0; i < KEY_LENGTH; i++) 
    {
        char c = input[i];
        char k = VIGENERE_KEY[i % VIGENERE_KEY_LENGTH];

        int c_index = c - 'A';
        int k_index = k - 'A';

        input[i] = ((c_index + k_index) % 26) + 'A';
    }
}

char vigenere_encrypt_single(char c, char k) 
{
    int c_index = c - 'A';
    int k_index = k - 'A';

    return ((c_index + k_index) % 26) + 'A';
}

int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        puts("usage: encrypt6 foo bar \nWhere: foo is the file containing the plaintext and bar is the destination ciphertext file.");
        exit(-1);
    }

    FILE *plainFile, *cipherFile, *keyFile;
    char *plainPath = argv[1];
    char *cipherPath = argv[2];
    char *keyFilePath = "keyfile.dat";
    char key[KEY_LENGTH];
    int index = 0;
    int c;

    keyFile = fopen(keyFilePath, "r");
    if(keyFile == NULL)
    {
        puts("failed to open keyfile");
        exit(-1);
    }

    plainFile = fopen(plainPath, "r");
    if(plainFile == NULL)
    {
        puts("failed to open plaintext");
        exit(-1);
    }

    while (!feof(keyFile))
    {
        c = fgetc(keyFile);
        if (c != EOF && c != '\n')
        {
            key[index++] = c;
        }
        
        if(index >= KEY_LENGTH)
        {
            break;
        }
    }
    fclose(keyFile);

    int originalLength = index;
    for (int i = 0; index < KEY_LENGTH; i++)
    {
        key[index++] = key[i % originalLength];
    }

    vigenere_encrypt(key);

    seteuid(getuid());

    cipherFile = fopen(cipherPath, "w");
    if(cipherFile == NULL)
    {
        puts("failed to create cipher file");
        exit(-1);
    }

    index = 0;

    while(!feof(plainFile))
    {
        c = toupper(fgetc(plainFile));

        if (c == EOF)
        {
            break;
        }

        if(isalpha(c))
        {
            fputc(vigenere_encrypt_single(c, key[index++ % KEY_LENGTH]), cipherFile);
        }
    }

    fclose(plainFile);
    fclose(cipherFile);

    return 0;
}
EOF

gcc -o /krypton/krypton6/encrypt6 /tmp/encrypt6.c

chown krypton7:krypton6 /krypton/krypton6/encrypt6

chmod 4750 /krypton/krypton6/encrypt6

 

3. Krypton6 문제풀의

# 비밀번호 : RANDOM
# ssh krypton6@krypton.labs.overthewire.org -p 2231
ssh krypton6@localhost -p 2231

TMP_DIR=$(mktemp -d)

cd $TMP_DIR

chmod 777 $TMP_DIR

ln -s /krypton/krypton6/keyfile.dat

# 비즈네리 암호화가 사용된 것을 확인
cat /krypton/krypton6/onetime/cipher1 | awk -v key="ITWASTHEBESTOFTIMESITWASTHEWORSTOFTIMESITWASTHEAGEOFWISDOMITWASTHEAGEOFFOOLISHNESSITWASTHEEPOCHOFBELIEFITWASTHEEPOCHOFINCREDULITYITWASTHESEASONOFLIGHTITWASTHESEASONOFDARKNESSITWASTHESPRINGOFHOPEITWASTHEWINTEROFDESPAIRWEHADEVERYTHINGBEFOREUSWEHADNOTHINGBEF" '{
    for (i=1; i<=length($0); i++) {
        c=substr($0, i, 1);
        k=substr(key, (i-1) % length(key) + 1, 1);
        c_index = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(c)) - 1;
        k_index = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(k)) - 1;
        printf "%c", ((c_index - k_index + 26) % 26) + 65;
    }
    printf "\n";
}'

# 비즈네리 암호문 해독을 위한 임시 파일을 생성
awk 'BEGIN { for (i = 1; i <= 100; i++) printf "a"; printf "\n" }' > $TMP_DIR/a.txt

# 비즈네리 암호는 (원문 + 키) mod 26 공식으로 암호문을 생성
# 원문이 A(0) 일경우 키 mod 26은 키가 되므로
# 키가 반복되는 암호문이 생성됨
/krypton/krypton6/encrypt6 $TMP_DIR/a.txt $TMP_DIR/chiper_a.txt

# 키 확인
# 키 : EICTDGYIYZKTHNSIRFXYCPFUEOCKRN
cat $TMP_DIR/chiper_a.txt | awk '
{
    str_len = length($0);

    for (pat_len = 2; pat_len <= str_len / 2; pat_len++)
    {
        pattern = substr($0, 1, pat_len);
        match_count = 0;

        for (i = 1; i <= str_len - pat_len + 1; i += pat_len)
        {
            if (substr($0, i, pat_len) == pattern)
            {
                match_count++;
            }
            else
            {
                break;
            }
        }

        if (match_count >= 2)
        {
            print pattern;
            exit;
        }
    }
}'

# 복호화 확인
cat $TMP_DIR/chiper_a.txt | awk -v key="EICTDGYIYZKTHNSIRFXYCPFUEOCKRN" '{
    for (i=1; i<=length($0); i++) {
        c=substr($0, i, 1);
        k=substr(key, (i-1) % length(key) + 1, 1);
        c_index = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(c)) - 1;
        k_index = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(k)) - 1;
        printf "%c", ((c_index - k_index + 26) % 26) + 65;
    }
    printf "\n";
}'

# password : LFSRISNOTRANDOM
cat /krypton/krypton6/krypton7 | awk -v key="EICTDGYIYZKTHNSIRFXYCPFUEOCKRN" '{
    for (i=1; i<=length($0); i++) {
        c=substr($0, i, 1);
        k=substr(key, (i-1) % length(key) + 1, 1);
        c_index = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(c)) - 1;
        k_index = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(k)) - 1;
        printf "%c", ((c_index - k_index + 26) % 26) + 65;
    }
    printf "\n";
}'