반응형

 

 

CryptoHack – Home

A free, fun platform to learn about cryptography through solving challenges and cracking insecure code. Can you reach the top of the leaderboard?

cryptohack.org

 

이번 문제도 그냥 MITM이다. 

Overview

Alice와 Bob이 어떠한 값들을 주고받는지 살펴보자.

 

우선, nc로 연결해보면,

Intercepted from Alice: {"supported": ["DH1536", "DH1024", "DH512", "DH256", "DH128", "DH64"]}

이렇게 나오고, Bob은

Intercepted from Bob: {'chosen': 'DH1536'}

이러한 값을 Alice에게 전달하는 것을 볼 수 있다.

즉, Alice는 Bob에게 선택지를 보내고, Bob은 그 중 secret 값으로 사용할 숫자의 길이를 보내는 것 같다.

Exploit - Theory

그렇다면, Alice가 보내는 선택지를 공격자의 임의로 변경해서 보내면 되지않을까?

{'supported': ['DH64']}

로 바꿔서 보내보았다. 그랬더니,

Intercepted from Bob: {'chosen': 'DH64'}

로 답이 왔다.

길이가 64밖에 되지 않는다면, DLP를 풀 수 있을 것만 같다.

64 길이의 DLP 문제를 푸는 것은, 간단하게 SageMath를 이용해서 해결할 수 있다고 한다.

그런데, 내가 SageMath는 초보라서, 지피티와 함께 풀었다.

Exploit - Do it

우선, 이 문제에서도 이전 문제에서 사용했던 AES decrypt 코드를 이용한다고 하였다.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib

def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))

def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Decrypt flag
    ciphertext = bytes.fromhex(ciphertext)
    iv = bytes.fromhex(iv)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)

    if is_pkcs7_padded(plaintext):
        return unpad(plaintext, 16).decode('ascii')
    else:
        return plaintext.decode('ascii')
from pwn import *
import json

r = remote("socket.cryptohack.org", 13379)

def recv_json():
    ret = json.loads(r.recvuntil('}').decode())
    print(ret)
    return ret

def send_json(msg):
    to_send = json.dumps(msg).encode()
    r.sendline(to_send)

r.recvuntil('Intercepted from Alice: ')
Alice_1 = recv_json()
Alice_1 = {'supported': ['DH64']}

r.recvuntil('Send to Bob: ')
send_json(Alice_1)

r.recvuntil('Intercepted from Bob: ')
Bob_1 = recv_json()

r.recvuntil('Send to Alice: ')
send_json(Bob_1)

r.recvuntil('Intercepted from Alice: ')
Alice_2 = recv_json()
p = int(Alice_2['p'], 16)
g = int(Alice_2['g'], 16)
A = int(Alice_2['A'], 16)

r.recvuntil('Intercepted from Bob: ')
data = recv_json()
B = int(data['B'], 16)

이렇게 해서 Alice와 Bob의 p, g, A, B를 받아볼 수 있다.

하지만, 여기에서 사용된 p의 길이가 64밖에 되지 않기 때문에, 다른 취약점이 아닌, 계산을 통해 문제를 풀 수 있다.

이 아래 이와 같은 코드를 추가해주었다.

# sagemath에서 dlp를 계산하고 돌아옴
a = int(input())

잠시 멈추고, 위의 p, g, A, B를 이용해 a 값을 계산하여 이를 입력하는 과정이다.

$ sage
sage: g = Mod({g}, {p})
sage: A = {A}
sage: a = discrete_log(A, g)
sage: a

이렇게 하면 a값을 얻을 수 있다. (물론 '{}'로 씌워진 곳에는 문자가 아니라 숫자를 입력해야한다.)

이 다음 과정부터는 그냥 Diffie-Hellman 이용해서 계산하고, 보내주면 된다. 간단하다.

shared_secret = pow(B, a, p)

r.recvuntil('Intercepted from Alice: ')
data = recv_json()
iv = data['iv']
encrypted_flag = data['encrypted_flag']

print(decrypt_flag(shared_secret, iv, encrypted_flag))
#crypto{d0wn6r4d35_4r3_d4n63r0u5}

r.close()

 

끝~

반응형
반응형

 

 

CryptoHack – Home

A free, fun platform to learn about cryptography through solving challenges and cracking insecure code. Can you reach the top of the leaderboard?

cryptohack.org

Diffie-Hellman Key exchange

이 프로토콜은 DLP:Discrete Logarithm Problem을 바탕으로 만들어진 키 교환 프토로콜이다. 

$A = g^a mod\ p$ 

를 알고 있을 때, $(g, p, A)$를 모두 알고 있어도, $a$ 값은 알기 어렵다는 것을 기반으로 하고 있다.

 

좀 더 자세히 보면,

Alice와 Bob이 통신키(세션키)를 교환하고자 할 때 안전하게 교환하는 프로토콜이다.

1. Alice가 $a < p$인 secret 값 $a$ 를 정한다.

2. Alice -> Bob: $\{g, p, A=g^{a} mod\ p\}$ 를 전송한다.

3. Bob: $b < p$인 secret 값 $b$를 정한다.

4. Bob: $\{A^{b} mod\ p\}$ 를 계산한다. 이게 둘 사이의 shared secret이다. $A^b mod\ p = g^{ab} mod\ p$

5. Bob -> Alice: $\{B=g^{b} mod\ p\}$ 를 전송한다.

6. Alice: $B^a mod\ p$ 를 계산한다. ($B^{a} mod\ p = g^{ba} mod\ p$) 

이렇게 되면, Alice와 Bob은 안전하게 세션키를 교환할 수 있다.

Exploit - theori

그런데 여기서, 중간에 통신을 가로챌 수 있는 중간자가 있다면, 이 프로토콜을 깰 수 있다. :MITM: Man In The Middle attack

공격자 Carol을 가정하고, 위 프로토콜을 깨보자.

이 때, Carol은 본인만의 Malicious한 secret 값 $c$를 생성한다.

 

1. Alice가 $a < p$인 secret 값 $a$ 를 정한다.

2. Alice -> Bob: $\{g, p, A=g^a mod\ p\}$ 를 전송한다.

Carol이 위 데이터를 가로채서, $\{g, p, A=g^c mod\ p\}$ 를 Bob에게 전송한다.

3. Bob: $b < p$인 secret 값 $b$를 정한다.

4. Bob: $A^b mod\ p$ 를 계산한다. 이게 둘 사이의 shared secret이다. $A^b mod\ p = g^{cb} mod\ p$

5. Bob -> Alice: $\{B=g^b mod\ p\}$ 를 전송한다.

Carol이 위 데이터를 가로채서, $\{B=g^c mod\ p\}$를 Alice에게 전송한다.

6. Alice: $B^a mod\ p$ 를 계산한다. $(B^a mod\ p = g^{ca} mod\ p)$

 

이러한 과정을 거치면, Carol은 Alice와 Bob의 세션키를 모두 가지고있게 된다.

Alice의 통신키: $g^{ac} mod\ p$

Bob의 통신키: $g^{bc} mod\ p$

이를 통해, Carol은 Man In The Middle에서 둘 사이의 통신을 마음대로 쥐락펴락 할 수 있다.

 

 Exploit - do it

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib

def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))


def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Decrypt flag
    ciphertext = bytes.fromhex(ciphertext)
    iv = bytes.fromhex(iv)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)

    if is_pkcs7_padded(plaintext):
        return unpad(plaintext, 16).decode('ascii')
    else:
        return plaintext.decode('ascii')

# protocol
from pwn import *
import json
import os
from Crypto.Util.number import *

r = remote('socket.cryptohack.org', 13371)

r.recvuntil('Intercepted from Alice: ')
Alice = json.loads(r.recvline().decode())
p = int(Alice['p'], 16)
g = int(Alice['g'], 16)
A = int(Alice['A'], 16)

c = bytes_to_long(os.urandom(100))
payload = json.dumps({'p':hex(p),'g':hex(g),'A':hex(p)})
r.recvuntil('Send to Bob: ')
r.sendline(payload)

r.recvuntil('Intercepted from Bob: ')
Bob = json.loads(r.recvline().decode())
B = int(Bob['B'], 16)

r.recvuntil('Send to Alice: ')
C = pow(g, c, p)
payload = json.dumps({'B': hex(C)})
r.sendline(payload)

r.recvuntil('Intercepted from Alice: ')
data = json.loads(r.recvline().decode())
iv = data['iv']
encrypted_flag = data['encrypted_flag']

shared_secret = pow(A, c, p)

print(decrypt_flag(shared_secret, iv, encrypted_flag))
# crypto{n1c3_0n3_m4ll0ry!!!!!!!!}

r.close()
반응형
반응형

하다가 너무 헷갈려서 그냥 정리하는게 맞겠다~ 싶음.

 

정리해야 할 함수들

  • chr()
  • ord()
  • bytes.fromhex() / .hex()
  • base64.b64encode() / .b64decode
  • long_to_bytes() : long -> byte string
  • bytes_to_long() : byte string -> long

시작해보자.

 

1. chr() 함수

하나의 정수를 인자로 받고, 해당 정수에 해당하는 유니코드 문자를 반환한다.

chr(97) = 'a'

2. ord() 함수

하나의 문자를 인자로 받고, 해당 문자에 해당하는 유니코드 정수를 반환한다.

ord('a') = 97

 

이정도는 오케이. 그런데 그 다음부터 헷갈리더라.

3. bytes.fromhex()

hex 숫자를 인자로 받고, bytes 형식으로 변형함.

예) '\x00\x01\x02 ... '

code
result

 

4. bytes.hex()

바이트 형식 문자열을 16진수 hex로 바꿔줌.

code
result

일반 문자의 경우, 

code
result

그냥 hex 값으로 출력되는 것을 볼 수 있다.

 

정리

  • '\x'가 붙은 값: \x을 떼고 출력해줌.(당연함. hex값을 byte형식으로 나타낸거니까..)
  • 일반 문자: hex값으로 출력함.

5. long_to_bytes()

long형의 숫자를 bytes 형식의 string으로 나타낸다.

이렇게 코드를 짜보면

이런식으로 bytes string으로 출력되는 것을 볼 수 있다.

6. bytes_to_long()

위와 반대의 과정이다.

반응형
반응형

인코딩(encoding)과 디코딩(decoding)

파일에 저장된 정보의 형태나 형식을 변환하는 처리 / 처리방식을 말함.

이메일, 문자메시지 등의 전송, 동영상이나 이미지 영역에서 많이 사용됨.

인코딩의 반대는 디코딩

Base64

바이너리 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII문자로 표현하기 위해 만들어진 인코딩.

ASCII문자 하나가 64진법의 숫자 하나를 의미하기 때문에 base64라는 이름을 가졌다.

 

8비트짜리 바이트 3개를 6비트씩 4개로 쪼개어 base64 코드 4개로 바꾸어 표현한다. base64 코드를 바이너리로 디코딩하기 편하게 하기 위해 base64 코드를 무조건 네 글자 단위로 만들고, 빈 부분을 '=' 문자로 채워둔다.

encoding 예시

원문
E
1
L
바이트 값
0x45
0x31
0x4C
2진수
0
1
0
0
0
1
0
1
0
0
1
1
0
0
0
1
0
1
0
0
1
1
0
0
변환 값
17
19
5
12
결과
R
T
F
M

디코딩은 이 표와 거꾸로 하면된다.

Base64 변환 표

문자
 
문자
 
문자
 
문자
0
A
16
Q
32
g
48
w
1
B
17
R
33
h
49
x
2
C
18
S
34
i
50
y
3
D
19
T
35
j
51
z
4
E
20
U
36
k
52
0
5
F
21
V
37
l
53
1
6
G
22
W
38
m
54
2
7
H
23
X
39
n
55
3
8
I
24
Y
40
o
56
4
9
J
25
Z
41
p
57
5
10
K
26
a
42
q
58
6
11
L
27
b
43
r
59
7
12
M
28
c
44
s
60
8
13
N
29
d
45
t
61
9
14
O
30
e
46
u
62
+
15
P
31
f
47
v
63
/

출처 : https://namu.wiki/w/BASE64

 

BASE64 - 나무위키

'E1L'을 Base64로 인코딩하는 과정은 아래와 같다. 원문E1L바이트 값0x450x310x4C2진수010001010011000101001100변환 값1719512결과RTFM 결과는 'RTFM'. 디코딩은 이 표에 나온 과정을 거꾸로 하면 된다. 리눅스에서

namu.wiki

 

반응형
반응형

지난번 게시물에 이어서 확장 유클리드 알고리즘을 시작해보도록 해보아요...

 

 

지난 게시물에서, 유클리드 알고리즘에 의해

GCD(a, b) = GCD(b, r) 이 성립한다는 것을 알았음.

(※  GCD : greatest common divisor : 최대공약수, a % b = r)

 

확장 유클리드 알고리즘은 최대공약수 gcd를 구하는 것 뿐만아니라, 정수해를 가지는 부정방정식

ax + by = c가 주어질 때, a, b의 최대공약수를 구함과 동시에 이 방정식을 만족하는 x, y를 찾아주는 알고리즘이다.

당연히 식이 하나에 두 변수가 있으므로 unique한 해를 찾는다기보다는 무수히 많은 (x, y) 쌍에  대한 규칙을 찾는 것이라고 보면 될 것이다.

 

코드로 구조를 살짝 표현 해볼까?

function extended_gcd(a, b)
    s := 0; old_s := 1
    t := 1; old_t := 0
    r := b; old_r := a

    while r != 0
        quotient := old_r div r
        (old_r, r) := (r, old_r - quotient * r)
        (old_s, s) := (s, old_s - quotient * s)
        (old_t, t) := (t, old_t - quotient * t)

    output "coefficients : ", (old_s, old_t) //항등식의 계수
    output "GCD : ", old_r // a와 b의 최대공약수 (gcd)
    output "quotient by the GCD", (t, s) // 최대공약수로 나눈 몫

pseudo code 로 작성된 확장 유클리드 알고리즘.

r과 old_r을 각각 b와 a라고 두고(기존 항등식에서 사용하던 a,b와 같음), 나머지 변수들을 치워보면은 기존의 유클리드 알고리즘에서 사용하던 코드와 일치한다는 것을 알 수 있다.

이제, 여기에 s와 t가 추가되었는데, 이들은 초기값은 반드시 0, 1 / 1, 0으로 고정되고, 그 이후는 r. 즉, a, b와 비슷한 방식으로 처리되게 됨.

이걸 한번 시뮬레이션 해보자!

161x + 28y = 7을 계산해보자.

X_new는 이번 사이클에서 새롭게 구해지는 값을 말한다.

q는 이번 사이클에 구해지는 몫이다.

 

초기 세팅은 이러하다.

- q =  old_r / r

- r_new = old_r - r * q

- s_new = old_s - s * q

- t_new = old_t - t * q

q old_r r r_new old_s s s_new old_t t t_new
  161 28   1 0   0 1  

old_r = a, r = b로 세팅하고(그저 초기값일 뿐이랍니다), old_s, sm old_t, t는 1, 0, 0, 1로 각각 고정시켜놓는다.

q old_r r r_new old_s s s_new old_t t t_new
5 161 28 21 1 0 1 0 1 -5
  28 21   0 1   1 -5  

첫 스텝에서 old_r(a)을 r(b)로 나눈 몫 q = 5, r = 21이다.

이 예시에서는 a, b가 양수라서 별 상관이 없긴 하지만, 원래 확장유클리드 알고리즘은 음수에서도 작동을 해야한다. 그래서 r_new는 반드시 0 이상 abs(r) 미만의 정수가 되어야 한다(다들 아실거라고 믿고..)

예를 들어 7/(-3)을 한다고 하면 그 몫은 -2가 아니라 -3이라는 말이다. 7 - (-3 * -3)를 해야 나머지가 2. 즉, 0 이상 3 미만의 값으로 들어오기 떄문이다.

 

자 여기까지 왔으면, 나머지 s와 t를 구해준후

한칸씩 왼쪽으로 밀어서 두 번째 사이클을 준비해준다.

q old_r r r_new old_s s s_new old_t t t_new
5 161 28 21 1 0 1 0 1 -5
1 28 21 7 0 1 -1 1 -5 6
  21 7   1 -1   -5 6  

이런 작업을 r = 0이 될 떄까지 반복해주면 우리가 찾던 값이 등장하게 되는 것이다.

q old_r r r_new old_s s s_new old_t t t_new
5 161 28 21 1 0 1 0 1 -5
1 28 21 7 0 1 -1 1 -5 6
3 21 7 0 1 -1 4 -5 6 -23
  7(GCD) 0   -1 4   6 -23  

이러한 과정을 거친 결과 r = 0인 사이클에서, old_r = GCD인 것을 알 수 있고,

ax + by = c의 값을 구할 수 있는 것이다.

이 때,

x = old_s = -1

y = old_t = 6

c = GCD(161, 28) = 7임을 알 수 있다.

결과 : 161 * (-1) + 28 * 6 = 7

 

이렇게 확장 유클리드 알고리즘을 간단하게 알아보았다 ^^

s_new 와 t_new를 구하는 과정이 r_new를 구하는 과정과 같은 이유는

같은 연산을 적용해나가면서 값을 찾아주기 위함이다.

 

이걸 적용해볼만한 문제를 보게 된다면 아래 덧붙이도록 해보도록 노력해보도록... 해보겟슴미다.

 

반응형
반응형

유클리드 호제법

두 개의 자연수리 최대공약수를 "빠르게" 구하는 알고리즘.

여기서 빠르게 라는 말은 인간이 빠르게 구하려고 하는것도 물론 맞지만 컴퓨터가 빠르게 계산하기 위함인 것도 있다.

a와 b를 나눈 나머지를 r이라고 할 때, a와 b의 최대공약수는 b와 r의 최대공약수와 같다.

아 티스토리에서 라텍스 쓰는법 모르겠어요~~~

 

a % b = r이면 gcd(a, b) = gcd(b, r) (단, b > r > 0)

36 % 24 = 12 이고,

24 % 12 = 0 이므로, 0이 되기 한 단계 전인 12가 36과 24의 최대공약수임.

이걸 코드로 표현해보면,

#include <stdio.h>

int GCD(int a, int b)
{
    if (a % b == 0)
        return (b);
    return GCD(b, a % b);
}

int main(void)
{
    printf("%d\n", GCD(36, 24));
}

이렇게 C언어로 재귀적인 표현이 가능하다.

얍. 그냥 이렇다는 사실 정도만 알고 넘어가도록 하자.

다음 글에서 확장 유클리드 호제법을 공부해보도록 하자.

반응형

+ Recent posts