Hology 2019 Final - Crypto

Sandiman v1

Diberikan dua buah file output dan encrypt.py.

Challenge

$ cat output
53 14 14 53 14 22 53 22 14 14 51 14 22 14 21 51 14 22 22 21 14 53 53 22 14 53 53 53 21 14 22 11 14 14 53 51 14 14 53 51 14 53 14 14 53 51 53 51 21 14 53 14 14 22 11 53
import string
import itertools

def adfgvx(val):
        val = val.lower()
        encode = ""
        num = ""
        table = ["8","p","3","d","1","n",
                 "l","t","4","o","a","h",
                 "7","k","b","c","5","z",
                 "j","u","6","w","g","m",
                 "x","s","v","i","r","2",
                 "9","e","y","0","f","q"]
        jumble = "adfgvx"
        for char in val:
            row = table.index(char) // 6
            col = table.index(char) %6
            num += str(row) + str(col)
        for i in range(len(num)):
            tmp = int(num[i])
            encode += jumble[tmp]
        return encode

def column(key,userval):
    try:
        assert key.isalpha()
        key = key.lower()
        userval = userval.lower()
        col=len(key)
        userval=userval.replace(' ','')
        if((len(userval)%col)!=0):
            userval+="x"*(len(userval)%col)
        o=[]
        for i in key:
            o.append(i)
        h=[]
        for i in range(col):
            h.append(userval[i:len(userval):col])
        dic=dict(zip(o,h))
        so=sorted(dic.keys())
        yoay = ''.join(dic[i]for i in so)
        return yoay
    except:
        print("Whoops, Wrong maneh. repeat char a?")

def check(key):
    key = key.lower()
    char = "abcdefghijlkmnopqrstuvwxyz"
    for i in char:
        count = key.count(i)
        if count > 1:
            return false

def encryptA(key,userval,counter=2):
        key = key.lower()
        userval = userval.lower()
        col=len(key)
        userval=userval.replace(' ','')
        if((len(userval)%col)!=0):
            userval+="x"*(len(userval)%col)
        o=[]
        for i in key:
            o.append(i)
        h=[]
        for i in range(col):
            h.append(userval[i:len(userval):col])
        dic=dict(zip(o,h))
        so=sorted(dic.keys())
        yeay = ''.join(dic[i]for i in so)
        return yeay

def chunker(seq, size):
    it = iter(seq)
    while True:
       chunk = tuple(itertools.islice(it, size))
       if not chunk:
           return
       yield chunk

def prepare_input(dirty):
    dirty = ''.join([c.upper() for c in dirty if c in string.ascii_letters])
    clean = ""
    if len(dirty) < 2:
        return dirty
    for i in range(len(dirty)-1):
        clean += dirty[i]
        if dirty[i] == dirty[i+1]:
            clean += 'X'
    clean += dirty[-1]
    if len(clean) & 1:
        clean += 'X'
    return clean

def generate_table(key):
    alphabet = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
    table = []
    for char in key.upper():
        if char not in table and char in alphabet:
            table.append(char)
    for char in alphabet:
        if char not in table:
            table.append(char)
    return table

def encryptC(plaintext, key):
    table = generate_table(key)
    plaintext = prepare_input(plaintext)
    ciphertext = ""
    for char1, char2 in chunker(plaintext, 2):
        row1, col1 = divmod(table.index(char1), 5)
        row2, col2 = divmod(table.index(char2), 5)
        if row1 == row2:
            ciphertext += table[row1*5+(col1+1)%5]
            ciphertext += table[row2*5+(col2+1)%5]
        elif col1 == col2:
            ciphertext += table[((row1+1)%5)*5+col1]
            ciphertext += table[((row2+1)%5)*5+col2]
        else:
            ciphertext += table[row1*5+col2]
            ciphertext += table[row2*5+col1]
    return ciphertext

def encryptB(plain):
    try:
        assert plain.isalpha()
        plain = plain.lower()
        for char in plain:
            row = int((ord(char) - ord('a')) / 5) + 1
            col = ((ord(char) - ord('a')) % 5) + 1
            if char == 'k':
                row = row - 1
                col = 5 - col + 1
            elif ord(char) >= ord('j'):
                if col == 1 :
                    col = 6
                    row = row - 1
                col = col - 1
            print(row, col, end =' ', sep ='')
        print("")
    except:
        print("Opps, Somethin Went Wrong")

if __name__ == "__main__":
    plain = input("plain: ")
    key = "Hology"
    keyC = "momogi"
    shift = 16
    A = encryptA(key,plain)
    C = encryptC(A,keyC)
    D = ""
    plain = plain.upper()
    print(plain)
    for i in range(len(plain)):
        tmp = ord(plain[i]) + (shift%26)
        if(tmp > 90):
            D += chr((tmp + 65) % 91)
        else:
            D += chr(tmp)
    keyE = "arek"
    E = adfgvx(D)
    E = column(keyE,E)
    encryptB(E)

Solution

Terdapat banyak fungsi pada kode program. Jujur saja, saya sempat melakukan trace dengan perlahan untuk memahami alur enkripsi. Namun karena banyaknya fungsi enkripsi (meskipun key diketahui), alhasil saya menyelesaikan challenge ini secara black-box.

Implementation

import string
import itertools

def adfgvx(val):
        val = val.lower()
        encode = ""
        num = ""
        table = ["8","p","3","d","1","n",
                 "l","t","4","o","a","h",
                 "7","k","b","c","5","z",
                 "j","u","6","w","g","m",
                 "x","s","v","i","r","2",
                 "9","e","y","0","f","q"]
        jumble = "adfgvx"
        for char in val:
            row = table.index(char) // 6
            col = table.index(char) %6
            num += str(row) + str(col)
        for i in range(len(num)):
            tmp = int(num[i])
            encode += jumble[tmp]
        return encode

def column(key,userval):
    try:
        assert key.isalpha()
        key = key.lower()
        userval = userval.lower()
        col=len(key)
        userval=userval.replace(' ','')
        if((len(userval)%col)!=0):
            userval+="x"*(len(userval)%col)
        o=[]
        for i in key:
            o.append(i)
        h=[]
        for i in range(col):
            h.append(userval[i:len(userval):col])
        dic=dict(zip(o,h))
        so=sorted(dic.keys())
        yoay = ''.join(dic[i]for i in so)
        return yoay
    except:
        print("Whoops, Wrong maneh. repeat char a?")

def check(key):
    key = key.lower()
    char = "abcdefghijlkmnopqrstuvwxyz"
    for i in char:
        count = key.count(i)
        if count > 1:
            return false

def encryptA(key,userval,counter=2):
        key = key.lower()
        userval = userval.lower()
        col=len(key)
        userval=userval.replace(' ','')
        if((len(userval)%col)!=0):
            userval+="x"*(len(userval)%col)
        o=[]
        for i in key:
            o.append(i)
        h=[]
        for i in range(col):
            h.append(userval[i:len(userval):col])
        dic=dict(zip(o,h))
        so=sorted(dic.keys())
        yeay = ''.join(dic[i]for i in so)
        return yeay

def chunker(seq, size):
    it = iter(seq)
    while True:
       chunk = tuple(itertools.islice(it, size))
       if not chunk:
           return
       yield chunk

def prepare_input(dirty):
    dirty = ''.join([c.upper() for c in dirty if c in string.ascii_letters])
    clean = ""
    if len(dirty) < 2:
        return dirty
    for i in range(len(dirty)-1):
        clean += dirty[i]
        if dirty[i] == dirty[i+1]:
            clean += 'X'
    clean += dirty[-1]
    if len(clean) & 1:
        clean += 'X'
    return clean

def generate_table(key):
    alphabet = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
    table = []
    for char in key.upper():
        if char not in table and char in alphabet:
            table.append(char)
    for char in alphabet:
        if char not in table:
            table.append(char)
    return table

def encryptC(plaintext, key):
    table = generate_table(key)
    plaintext = prepare_input(plaintext)
    ciphertext = ""
    for char1, char2 in chunker(plaintext, 2):
        row1, col1 = divmod(table.index(char1), 5)
        row2, col2 = divmod(table.index(char2), 5)
        if row1 == row2:
            ciphertext += table[row1*5+(col1+1)%5]
            ciphertext += table[row2*5+(col2+1)%5]
        elif col1 == col2:
            ciphertext += table[((row1+1)%5)*5+col1]
            ciphertext += table[((row2+1)%5)*5+col2]
        else:
            ciphertext += table[row1*5+col2]
            ciphertext += table[row2*5+col1]
    return ciphertext

def encryptB(plain):
    result = ''
    try:
        assert plain.isalpha()
        plain = plain.lower()
        for char in plain:
            row = int((ord(char) - ord('a')) / 5) + 1
            col = ((ord(char) - ord('a')) % 5) + 1
            if char == 'k':
                row = row - 1
                col = 5 - col + 1
            elif ord(char) >= ord('j'):
                if col == 1 :
                    col = 6
                    row = row - 1
                col = col - 1
            result += "{}{} ".format(row, col)
        return result.strip()
    except:
        print("Opps, Somethin Went Wrong")

def cek_similar(a, b):
    a = a.split()
    b = b.split()
    score = 0
    for i,j in zip(a,b):
        if i==j:
            score+=1
    return score

if __name__ == "__main__":
    ct = open('sandiman1.enc').read()
    print(ct)
    flag = ''
    while True:
        maxscore = 0
        benar = 'z'
        for ch in 'ABCDEFGHIKLMNOPQRSTUVWXYZ'.lower():
            pload = 'a'*(27 - len(flag)) + ch + flag
            plain = pload
            key = "Hology"
            keyC = "momogi"
            shift = 16
            A = encryptA(key,plain)
            C = encryptC(A,keyC)
            D = ""
            plain = plain.upper()
            for i in range(len(plain)):
                tmp = ord(plain[i]) + (shift%26)
                if(tmp > 90):
                    D += chr((tmp + 65) % 91)
                else:
                    D += chr(tmp)
            keyE = "arek"
            E = adfgvx(D)
            E = column(keyE,E)
            z = encryptB(E)
            score = cek_similar(ct, z)
            if score > maxscore:
                maxscore = score
                benar = ch
        if len(z) > len(ct):
            break
        flag += benar
        print(flag[::-1])
$ python solve-sandiman-v1.py
53 14 14 53 14 22 53 22 14 14 51 14 22 14 21 51 14 22 22 21 14 53 53 22 14 53 53 53 21 14 22 11 14 14 53 51 14 14 53 51 14 53 14 14 53 51 53 51 21 14 53 14 14 22 11 53

a
ra
ora
tora
ptora
yptora
ryptora
cryptora
ecryptora
decryptora
odecryptora
rodecryptora
prodecryptora
eprodecryptora
reprodecryptora
ireprodecryptora
uireprodecryptora
quireprodecryptora
equireprodecryptora
requireprodecryptora
trequireprodecryptora
ptrequireprodecryptora
yptrequireprodecryptora
ryptrequireprodecryptora
cryptrequireprodecryptora
dcryptrequireprodecryptora
ldcryptrequireprodecryptora
oldcryptrequireprodecryptora

Submit dan muncul alert flag salah. Hapus karakter terakhir, submit, dan flag benar.

Flag

oldcryptrequireprodecryptor

Sandiman v2

Challenge ini merupakan lanjutan dari seri Sandiman v1.

Challenge

Diberikan dua buah file output dan encrypt.py.

$ cat output
I think this is safe enough: sapmbe.ahlesulue.n..
secured flag: 4e8ac1f39c798068cd0190dab44146a2
from Crypto.Cipher import AES
import sys
import binascii
import random

IV = "4n0t3rkeybr0sist"

def encrypt(msg,phrase):
    aes = AES.new(phrase, AES.MODE_CBC,IV)
    return aes.encrypt(msg)

W = 5
perm = range(W)
random.shuffle(perm)
msg = open("rawkey.txt").read().strip()
raw = open("rawkey.txt").read().strip()
while len(msg) % (2*W):
    msg += "."

for i in xrange(100):
    msg = msg[1:] + msg[:1]
    msg = msg[0::2] + msg[1::2]
    msg = msg[1:] + msg[:1]
    res = ""
    for j in xrange(0, len(msg), W):
        for k in xrange(W):
            res += msg[j:j+W][perm[k]]
    msg = res

print "I think this is safe enough: " + msg
key = raw
data = binascii.hexlify(encrypt(sys.argv[1],key))
print "secured flag: " + data

Solution

Intinya, program tersebut memiliki alur enkripsi sebagai berikut:

  1. Membuka file rawkey.txt. File ini menjadi key di fungsi encrypt.
  2. Padding rawkey dengan karakter titik hingga panjang rawkey menjadi kelipatan 10. Dilihat dari output yang dihasilkan program, panjang rawkey adalah 20 karakter (16 karakter rawkey + 4 karakter hasil padding), lalu disimpan dalam variabel msg.
  3. Isi dari variabel msg ‘dihancurkan’ dengan loop sebanyak 100x.
  4. Encrypted msg ditampilkan di output.

How to solve?

  1. Generate dummy plaintext sebanyak 16 karakter + 4 karakter padding, simpan ke sebuah variabel, misal variabel pp.
  2. Generate seluruh kemungkinan perm, gunakan module permutations pada package itertools agar mempermudah pengerjaan.
  3. Untuk semua kemungkinan perm, ‘hancurkan’ variabel pp dengan loop 100x, lalu bandingkan posisi karakter paddingnya. Jika sesuai, maka didapat perm yang benar.
  4. Jika sudah ditemukan, recover isi dari rawkey dengan membandingkannya dengan string sapmbe.ahlesulue.n...
  5. Dekripsi ciphertext yang diberikan dengan key yang didapat dan IV yang diberikan.
  6. Enjoy.

Implementation

from Crypto.Cipher import AES
import itertools, string

IV = "4n0t3rkeybr0sist"

def ubah(a,b,c):
    res = ''
    for i in a:
        res += c[b.index(i)]
    return res

def decrypt(msg,phrase):
    aes = AES.new(phrase, AES.MODE_CBC,IV)
    return aes.decrypt(msg)

W = 5
pp = string.lowercase[:16] + '.'*4
c = 'sapmbe.ahlesulue.n..'
perms = list(itertools.permutations(range(W), W))

for perm in perms:
    msg = pp
    for i in xrange(100):
        msg = msg[1:] + msg[:1]
        msg = msg[0::2] + msg[1::2]
        msg = msg[1:] + msg[:1]
        res = ""
        for j in xrange(0, len(msg), W):
            for k in xrange(W):
                res += msg[j:j+W][perm[k]]
        msg = res
    if msg[6] == '.' and msg[16] == '.' and msg.endswith('..'):
        print 'perm = {}'.format(perm)
        print 'message = {}'.format(msg)
        key = ubah(pp, msg, c).strip('.')
        pt = decrypt('4e8ac1f39c798068cd0190dab44146a2'.decode('hex'), key)
        print 'plaintext = {}'.format(pt)

$ python solve-sandiman2.py
perm = (3, 2, 4, 1, 0)
message = jhldef.cpnkimgoa.b..
plaintext = 3asyKn0wnIVKeyzz

Flag

3asyKn0wnIVKeyzz