LoginSignup
3
4

More than 5 years have passed since last update.

「katagaitai CTF勉強会 - 関東|hard」の復習(脆弱なAES暗号の攻撃法)

Last updated at Posted at 2015-12-20

脆弱性"&'<<>\ Advent Calendar 2015 20日目の記事です。

katagaitai CTF勉強会 - 関東|hardに参加してきた。午前中の暗号の問題の手抜き版の解き方。

問題はここに。ラウンド数2段のAESっぽい秘密鍵暗号。暗号化は、鍵とのxor、バイトごとに表を引くx=Table[x]という操作(Substitution)、ビットの並び替え(Permutation)を2段だけ繰り返し、最後に鍵を足す。

C(P) = perm(sub(xor(perm(sub(xor(P, k1))), k2))) + k3

勉強会の内容を理解し切れてはいないけど、要は2段では少なすぎて充分に攪拌されないので、2段目の入力の1bitが変化した場合の2段目の出力をリストに持って、2段目の出力の変化が1bitになるような1段目の入力を探索して~ということができるらしい。3段目の足し算については、xorと足し算の結果が異なるのは繰り上がりがあった場合だけだから、ありえないものを弾くことができると。

足し算がどうこうとかを手抜きして、1段目も2段目も入力の内容を変えながら、差分のビット数が小さいものを選べば解けた。試行回数を増やさないと精度は悪いと思う。

1段目の1文字目は正解がKでこんな感じ。[]の中が各試行で右端がその合計。

73 I [31, 11, 27, 16, 17, 30, 7] 139
74 J [27, 17, 18, 18, 11, 13, 26] 130
75 K [9, 11, 14, 15, 6, 21, 16] 92
76 L [20, 16, 21, 15, 28, 13, 19] 132
77 M [19, 25, 19, 24, 26, 21, 23] 157

2段目の1文字目はこんな感じ。

99 c [10, 11, 5, 13, 17, 3, 10] 69
100 d [17, 6, 7, 8, 4, 5, 10] 57
101 e [1, 2, 3, 2, 3, 3, 5] 19
102 f [11, 6, 5, 12, 3, 6, 14] 57
103 g [14, 4, 12, 4, 6, 13, 2] 55

1段目。C=perm(sub(xor(perm(P), k2))) + k3C'=perm(sub(xor(perm(P'), k2))) + k3を考える。CC'の差分が少なくて8bitくらいならば、PP'の差分が1bitである可能性が高い。なぜなら、permとxorでは差分のビット数は変化せず、subによる差分の拡大は高々8bitで、足し算もだいたいxorと同じ結果になるから。あとは勉強会の内容と同じで、aを0, 1, 2…と変えながら、P=sub⁻¹(0)^aP'=sub⁻¹(1)^aとした場合の差分を見れば良い。差分が少ないということは、PP'の差が1bitである可能性が高いということで、aが鍵である可能性が高い。

2段目も同様に。足し算さえ無ければ、暗号文の差分は1bitになるので、足し算を通してもそんなに差分は増えない。

3段目は、0をStep2とStep1の復号処理に通してから暗号化すれば、k3となる。

step2(step1(step1⁻¹(step2⁻¹(0)))) + k3 = k3
# coding: utf-8

from socket import *
from itertools import *
from hashlib import *
from struct import *
from time import *
import subme

# 配列を送信してサーバーで暗号化
def encrypt(v):
    s = socket(AF_INET, SOCK_STREAM)
    s.connect(("katagaitai.orz.hm", 9999))

    # SHA1問題を解く
    q = s.recv(9999).split()[-1]
    for x in product("abcdefghijklmnopqrstuvwxz", repeat=5):
        a = q+"".join(x)
        if sha1(a).hexdigest().endswith("ffff"):
            break
    s.sendall(a)

    # 暗号化
    s.sendall(pack("<H",len(v)) + "".join(map(chr, v)))
    sleep(3)
    return map(ord, eval(s.recv(999999)[20:]))

# 探索空間
A = range(0x20, 0x7f)
# 1文字あたりの試行回数
m = 7

# 1ステップ戻す
def unStep(v, key):
    v = v[:]
    v = map(ord, subme.unPermute("".join(map(chr, v))))
    v = subme.unSubStr(v)
    v = [a^k for a,k in zip(v,key)]
    return v

# Step 1, Step 2の探索
# Step 2を探索する場合はk1にStep 1の鍵を指定する
def solveStep(step, k1):
    key = []

    for i in range(8):
        print "i:", i

        p = []
        for a in A:
            p += [0]*i + [subme.sinv[0]^a] + [0]*(7-i)
            for j in range(m):
                p += [0]*i + [subme.sinv[1<<j]^a] + [0]*(7-i)

        if step==2:
            for j in range(0,len(p),8):
                p[j:j+8] = unStep(p[j:j+8], k1)

        c = encrypt(p)

        ansd = 9999
        for t,a in enumerate(A):
            # 差分を求める
            x = []
            for j in range(m+1):
                x += [c[:8]]
                c = c[8:]
            diff = []
            for j in range(m):
                d = 0
                for k in range(8):
                    d += bin(x[0][k]^x[j+1][k]).count("1")
                diff += [d]
            print a, chr(a), diff, sum(diff)

            # 差分が少なければ鍵
            if sum(diff)<ansd:
                ansd = sum(diff)
                ans = a

        print "key[%s] = %s" % (i, ans)
        key += [ans]

    return key

k1 = solveStep(1, [])
k2 = solveStep(2, k1)

# Step 3
k3 = encrypt(unStep(unStep([0]*8, k2), k1))[::-1]

print "k1:", "".join(map(chr, k1))
print "k2:", "".join(map(chr, k2))
print "k3:", "".join(map(chr, k3))
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4