반응형

 

 

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()

위와 반대의 과정이다.

반응형
반응형

포너블을 하다가 system함수가 종종 버그가 터지곤 한다. 

 

이렇게 system함수로 잘 진입을 했는데

이런 이상한 함수로 /bin/sh를 넘겨주면서 system함수를 실행할 수 없게 되는 경우가 있다. 

이걸 좀 고쳐보려고 한~~참동안 궁리해본 결과 ....

 

execl함수를 쓰기로 했다!! 

평소에는 execve, system 이 두 함수만 생각하고있었어서 사용하기 복잡한 execve를 써야하나 ... 싶었는데

그냥 execl쓰면 된다.

 

사용법

execve( binsh 주소, binsh 주소, NULL);

이렇게 그냥 주면 된다.

어짜피 포너블에서 binsh이든, cat이든 사용하려면 문자열 주소는 알고있을테니까 그거 넣으면 되고, 마지막 인자로 p32(0)이나, p64(0) 넣으면 되는거니까, bof 크기만 충분하다면 사용해봐도 좋을 것 같다!

끝.

반응형
반응형

인코딩(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언어로 재귀적인 표현이 가능하다.

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

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

반응형
반응형

1. 정적분석 vs 동적분석

정적분석(Static Analysis)는 외적인 관찰만을 통해 정보를 알아내는 것을 의미하고, 

동적분석(Dynamic Analysis)은 실행을 통해 동작을 분석하는 것을 의미함.

둘 중 어느 방법이 더 좋다고 할 수 있는지 말하기 힘들며, 두 방법을 모두 적절히 혼용하여야 대상을 효과적으로 분석할 수 있음. 동적인 방법만을 고수하면 큰 구조를 놓칠 수 있고, 반대로 정적인 방법만을 고수하면 실행을 통해 직관적으로 알 수 있는 부분을 분석하느라 시간을 허비할 수 있음. 

 

2. 정적분석

2-1. 정적분석의 장점

정적분석을 사용하면 프로그램의 전체적인 구조를 파악하기 쉬움. 

정적 분석 도구들은 프로그램이 어떤 함수로 구성되어있는지, 어떤 함수로 구성되었는지, 서로 어떤 호출 관계를 가지고 있는지 등을 종합적으로 살펴볼 수 있음.

또한 분석 환경의 제약에서도 비교적 자유로움. 실행을 전제로 하는 동적분석은 운영체제와 같은 환경의 영향을 많이 받지만 정적분석은 도구만 잘 갖추면 그러한 환경의 제약을 거의 받지 않는다고 할 수 있음.

마지막으로, 바이러스와 같은 악성프로그램의 위협으로부터 안전함. 실제로 실행을 하는 것이 아닌, 구조를 살피는 것이기 때문.

 

2-2. 정적분석의 단점

정적분석의 대상이 되는 프로그램에 만약 난독화(Obfuscation)이 적용되면 분석이 매우 어려워짐. 난독화가 적용되면 프로그램의 코드가 심하게 변형되어서 이를 읽고 흐름을 파악하는 과정이 매우 힘들어짐.

 

2-3. 정적 분석의 예

IDA를 이용하여 프로그램을 구성하는 코드를 살펴볼 수 있음. 디스어셈블된 코드와 디컴파일되어있는 코드를 모두 살펴볼 수 있음. 이들은 기계어를 사람이 이해하기 쉬운 언어로 나타내어 분석을 더욱 쉽게 해줌. 

또한 상호 참조기능이나 함수의 실행 흐름을 보기 쉽게 해주는 제어흐름 그래프 등을 통해 분석가가 프로그램을 쉽게 이해할 수 있도록 도움.

 

3. 동적분석

3-1. 동적분석의 장점

동적분석을 활용하면 코드를 자세히 분석해보지 않고도 프로그램의 개략적인 동작을 파악할 수 있음. 

프로그램은 다양한 함수가 복잡하게 영향을 주고받고있기 때문에 프로그램을 정적분석만으로 완벽히 이해하기는 매우 어려움. But 동적분석은 어떤 입력에 대한 개별 함수 또는 프로그램의 출력을 빠르게 확인할 수 있으므로 출력값들을 기반으로 동작을 추론해볼 수 있음. 

 

3-2. 동적분석의 단점

분석환경을 구축하기가 매우 어려움, 일단 실행 하는 것을 전제로 하기 때문에 실행 환경을 구축하지 못하면 동적 분석을 진행할 수 없음. 이 과정이 매우 번거롭고 어렵기도 함.

또한 정적분석에서의 '난독화'처럼 동적 분석에 대해서도 '안티 디버깅'과 같은 방해 기술이 존재함. 

안티디버깅을 사용하면 자신이 디버깅 당하고 있는지 검사하고, 디버깅중이면 프로그램을 강제로 종료시킬 수 있음.

 

3-3. 동적분석의 예

윈도우의 대표적인 동적분석 도구로 디버거 중 하나인 x64dbg가 있음. 디버거란 프로그램의 버그를 찾아내고 제거하기 위해 사용되는 도구인데, 이를 이용하영실행중인 어셈블리코드, 레지스터 상태, 메모리와 스택의 값을 확인하며 분석을 진행할 수 있음. 

 

반응형

'security > Reversing' 카테고리의 다른 글

Reversing 기초 | DreamHack | Background: Binary  (0) 2022.06.01
반응형

1. C언어 코드의 바이너리 번역과정

code(.c)

--전처리--

preprocessed source file (.i)

--컴파일--

assembled file (.s)

--어셈블--

object file (.o)

--링크--

바이너리로 번역!

 

1-1. 전처리(preprocessing)

1. 주석제거

// 또는 /**/으로 구성되어있는 주석을 제거하는 과정

 

2. 매크로 치환

#define으로 정의된 매크로는 자주 쓰이는 코드나 상숫값을 단어로 정의한 것

#define HI 3  //HI를 3으로 치환하라는 명령어

 

3. 파일 병합

일반적인 프로그램은 여러개의 소스와 헤더파일로 구성. 컴파일러는 이런 것들을 컴파일 이후 합치기도 하지만 전처리단계에서 합친 이후에 컴파일하기도 함.

 

gcc에서는 -E 옵션을 사용하여 소스코드의 전처리 결과를 확인할 수 있음.(.c -> .i) 

$ gcc -E add.c > add.i

$ cat add.i

1-2. 컴파일(compile)

C로 작성된 소스코드를 어셈블리어로 번역하는 과정.

이 과정에서 소스코드의 문법을 검사하기도 함.

 

gcc에서는 -S 옵션을 사용하여 소스코드를 어셈블리 코드로 컴파일할 수 있음. (.i -> .s)

$ gcc -S add.i -o add.S

$ cat add.S

1-3. 어셈블(Assemble)

 

어셈블은 컴파일로 생성된 어셈블리 코드를 ELF형식의 목적파일(Obj)파일로 변환하는 과정임.

ELF는 리눅스의 실행파일 형식. PE는 윈도우의 실행파일 형식

 

gcc에서는 -c 옵션을 통해  어셈블리코드를 목적파일로 변활할 수 있음. (.s -> .o)

$ gcc -c add.S -o add.o

$ file add.o

$ hexdump -C add.o

1-4. 링크(Link)

여러 목적 파일들을 연결하여 실행 가능한 바이너리로 만드는 과정.

$ gcc add.o -o add -Xlinker --unresolved-symbols=ignore-in-object-files

$ file add

2. 디스어셈블

바이너리를 분석하기 위해서는 바이너리를 읽을 수 있어야 한다.

그런데 컴파일된 프로그램코드는 기계어로 이루어져있기 때문에 이를 그대로 이해하기는 매우 힘들다.

따라서 이를 어셈블리어로 재번역하는 과정이 디스어셈블(Disassemble) 이라고 함.

 

명령어 : $ objdump -d ./add -M intel

리버싱 과정

3. 디컴파일

디스어셈블러와 같은 개념. 어셈블리어로 된 파일을 고급언어로 바꾸어주는 역할을 한다.

하지만 100% 동일하게 바꾸어주지는 못한다. 

코드를 작성할 때 사용했던 변수나 함수의 이름은 컴파일 과정에서 사라지고 코드의 일부분은 최적화 등의 이유로 완전히 변형된다.

 

 

반응형

+ Recent posts