Security/System Hacking

Beginners: DH101 - 1 (컴퓨터 과학 기초)

arsenic-dev 2025. 2. 6. 20:34

DreamHack의 Beginners 로드맵을 기반으로 정리한 글입니다.

진법

진법은 임의의 숫자 혹은 문자를 사용하여 수를 표현하는 체계를 말한다. n진법은 한 자릿수를 n가지(0 ~ n-1)의 숫자 혹은 문자로 나타낼 수 있다. 10진법, 2진법, 16진법으로 나타낸 수는 각각 10진수(decimal), 2진수(binary), 16진수(hexadecimal)라고 한다.

 

10진법: 10개(0 ~ 9)의 숫자로 수를 표현

실생활에서는 손가락 10개로 숫자를 세고, 각 자릿수를 0 ~ 9까지의 수로 나타낸다. 사람들이 자연스럽게 사용하고 있는 수 체계가 바로 10진법이다.

 

2진법: 2개(0, 1)의 숫자로 수를 표현

컴퓨터는 사람과 다르게 10진법이 아닌 2진법을 사용한다. 흔히 컴퓨터를 두고 '0과 1로 이루어진 세상'이라고 비유하는데, 컴퓨터에서 가장 기본적인 전기적 신호의 ON/OFF 상태를 1과 0으로 표현하기 때문이다. 이러한 0과 1의 조합으로 이루어진 비트(bit)가 모여서 데이터를 표현한다. 2진수를 표현할 때는 보통 '0b'라는 접두어를 붙인다. 예를 들어 10진수 14를 2진수로 표현하면 '0b1110'으로 나타낸다.

 

16진법: 16개(0 ~ 9, A ~ F)의 문자로 수를 표현

2진법은 수가 조금만 커져도 표현할 때 많은 글자 수가 필요하여 비효율적이고, 사람이 읽고 쓰기 어렵다. 따라서 2진수를 좀 더 간편하고 효율적으로 표현하기 위해 다른 진법으로 표현할 필요가 있는데, 그 중에서 16진법이 가장 많이 사용된다. 2진수 4자리는 16진수 한 자리로 축약하여 표현할 수 있다. 16진수를 표현할 때는 보통 '0x'라는 접두어를 붙인다. 예를 들어 10진수 14를 16진수로 표현하면 '0xE'로 나타낸다. .

 

16진수는 컴퓨터 이곳저곳에서 활용되는 진법이다. 프로그램을 분석할 때 사용하는 디버깅 도구 같은 경우, 일반적으로 메모리의 위치를 나타내는 주소값을 16진수 형태로 출력한다. 또한 RGB 색상을 표현할 때도 16진수 형태로 표기한다.

10진수
2진수
16진수
0
0
0
1
1
1
2
10
2
3
11
3
4
100
4
5
101
5
6
110
6
7
111
7
8
1000
8
9
1001
9
10
1010
A
11
1011
B
12
1100
C
13
1101
D
14
1110
E
15
1111
F
16
10000
10

비트와 바이트

비트와 바이트는 컴퓨터의 데이터를 다루는 데 가장 기본이 되는 개념이다. 컴퓨터는 0과 1만으로 데이터를 표현하고 처리한다. 0과 1은 전기 신호와 관련이 있는데, 전기 신호가 통할 때는 1, 아닐 때는 0으로 나타낸다. 이때 0과 1을 나타내는, 컴퓨터에서 사용하는 데이터의 최소 단위를 1 비트(bit, binary digit)라고 한다. 8개의 비트로 구성된 더 큰 단위는 1 바이트(byte)라고 하고, 이는 메모리에 저장되는 최소 단위이다. 1 바이트는 2^8 = 256가지의 수를 표현할 수 있다. 10진수로 0 ~ 255, 2진수로 00000000 ~ 11111111, 16진수로 00 ~ FF까지 나타낸다.

▶ 10진수, 2진수, 16진수 표현 예시

 

16진수 한 자리를 2진수 네 자리로 변환하는 방식과 그 역은 진법 변환을 용이하게 한다.

최상위 비트(MSB), 최하위 비트(LSB)

여러 개의 비트로 구성된 이진 데이터에서 가장 왼쪽에 있는 비트를 Most Significant Bit (MSB, 최상위 비트)라고 하고, 가장 오른쪽에 있는 비트를 Least Significant Bit (LSB, 최하위 비트)라고 부른다. '중요하다(Significant)'는 표현을 붙이는 이유는 가장 왼쪽에 있는 비트가 숫자의 크기에 가장 큰 영향을 미치기 때문이고, 가장 오른쪽에 있는 비트는 숫자의 크기에 가장 작은 영향을 미치기 때문에 그러한 명명이 붙었다.

▶ '0b10010100'의 MSB는 1이고, LSB는 0이다.

부호 비트

부호가 있는 데이터의 경우, MSB는 부호의 의미를 가지게 된다. MSB가 0이면 양수, 1이면 음수를 나타낸다. 프로그래밍 언어에서 부호(+, -)를 가지는 데이터는 Signed 데이터 또는 부호가 있는 데이터라 부르고, 부호없이 양수(+)만 나타내는 데이터는 unsigned 데이터 또는 부호가 없는 데이터라 부른다.

예를 들어 '0b10010100'가 부호가 있는 데이터라면 MSB가 1이기 때문에 10진수로 표현하면 음수인 -108이 된다. 반면에 부호가 없는 데이터라면, MSB인 1은 부호를 의미하지 않고, 값을 의미하므로 양수인 148이 된다.

 

※ 2의 보수 = 1의 보수 + 1

바이트 오더링(Byte Ordering)

2 바이트 이상의 데이터는 메모리에 연속적으로 저장된다. 이때 각 바이트가 메모리에 정렬되는 방식을 바이트 오더링(Byte ordering)이라고 부른다. 바이트 오더링의 두 가지 방식으로 빅 엔디안(Big Endian)리틀 엔디안(Little Endian)이 있다. 어떤 바이트부터 낮은 주소에 저장되는지에 따라 두 방식을 구분한다.

 

※ 엔디안(Endian): 숫자를 구성하는 바이트를 컴퓨터가 정렬하는 방법이다.

 

예를 들어 0x01234567과 같은 데이터가 있을 대, 가장 왼쪽의 0x01이 가장 큰 바이트이고, 가장 오른쪽의 0x67이 가장 작은 바이트이다. 빅 엔디안은 큰(Big) 바이트부터 낮은 주소에 저장되며, 반대로 리틀 엔디안은 작은(Little) 바이트부터 낮은 주소에 저장된다.

▶ 빅 엔디안(Big-endian) 방식으로 4바이트 16진수 0x01234567을 메모리 주소 0x100에 저장한 결과

  • 가장 왼쪽에 있는(큰) 바이트부터 메모리의 낮은 주소에 저장
  • 네트워크 상에서 데이터를 전송할 때는 빅 엔디안 방식을 따름
  • 대표적으로 SPARC CPU에서 빅 엔디안 사용

▶ 리틀 엔디안(Little-endian) 방식으로 4바이트 16진수 0x01234567을 메모리 주소 0x100에 저장한 결과

  • 가장 오른쪽에 있는(작은) 바이트부터 메모리의 낮은 주소에 저장
  • 대표적으로 Intel의 x86, x86-64 CPU에서 리틀 엔디안 사용

※ 대다수의 개인용 컴퓨터 및 서버 환경에서 x86-64 CPU 아키텍처를 사용한다.

 

바이트 오더링 예시

정교함과 정확함이 필수인 해킹 분야에서 데이터가 어떤 바이트 오더링으로 메모리에 저장되었는지 고려하는 것은 기본이다.

 

예를 들어, 리버스 엔지니어링 분야에서 메모리에 저장된 값을 이용해 역연산을 수행하여 어떤 정답이 되는 값을 찾아내는 작업은 매우 흔하다. 이때 메모리에 저장된 값이 어떤 엔디안으로 저장되어 있는지를 고려하지 않으면, 전혀 다른 데이터로 혼동하여 올바른 역연산 결과를 얻을 수 없게 된다. 리틀 엔디안 방식으로 저장된 0x01234567을 빅 엔디안으로 생각하고 0x67452301이라는 숫자로 다뤄버리면, 결국 전혀 다른 데이터를 가지고 역연산을 수행하는 꼴이 되므로 올바른 결과를 얻을 수 없게 된다.

 

시스템 해킹을 예로 들어 공격을 위해 특정 메모리 주소에 원하는 값을 덮어씌워야 한다고 가정했을 때, 메모리의 낮은 주소부터 값을 쓰는 경우라면, 리틀 엔디안을 고려하여 저장하려는 값의 가장 오른쪽 바이트부터 써야 원하는 값이 제대로 저장된다. 또한 어떤 변수의 출력값을 알아내려고 한다면, 메모리의 변수 주소 영역에서 높은 주소부터 값을 읽어온다는 것을 고려해야 한다.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char * str = "ABCD"; // 16진수: 41424344
	puts(str);	// 문자열 출력 결과: ABCD

    unsigned int num = 0;
    num = 0x41424344;
    printf("%x\n", num);  // 16진수 출력 결과: 41424344
  
    return 0;
}

▶ 리틀 엔디안 방식으로 문자열과 16진수 정수를 메모리에 저장하는 C 프로그램의 소스 코드

 

※ ASCII 코드에 의해 chr "ABCD"는 hx "41424344"이다.

0x555555556004: 0x41    0x42    0x43    0x44

▶ 문자열

 

str 변수 주소의 메모리를 1바이트씩 4바이트 출력한 결과이다. 문자열을 메모리에 저장할 때는 바이트 오더링을 고려하지 않는다. 따라서 문자열 "ABCD"는 리틀 엔디안 방식을 따르지 않고, 문자 순서 그대로 메모리에 저장된다.

 

※ 엔디안은 2바이트 이상인 데이터(숫자)를 저장할 때 적용되는 개념인데, 문자열은 각 문자(1바이트) 단위로 저장되므로 엔디안을 고려할 필요가 없다. 예를 들어, 'A' -> 0x41 (16진수) -> 01000001 (2진수)는 1바이트이다.

0x7fffffffe314: 0x44    0x43    0x42    0x41

▶ 문자열이 아닌 데이터 - 16진수 정수

 

num 변수 주소의 메모리를 1바이트씩 4바이트 출력한 결과이다. 문자열이 아닌 데이터는 리틀 엔디안 방식으로 저장된다. 따라서 16진수 0x41424344는 가장 오른쪽의 바이트부터 메모리의 낮은 주소에 저장된 것을 볼 수 있다. num 변수 값을 출력하면 메모리에 저장된 4바이트에서 높은 주소부터 읽어 와서 원래 값 그대로 출력된다.

비트 연산

피연산자를 2진수로 표현하여 비트(bit) 단위로 연산하는 것을 비트 연산이라고 한다. 비트 단위로 논리 연산을 수행하거나, 비트를 특정 값만큼 이동하는 시프트(shift) 연산을 수행한다.

논리 연산자

참 또는 거짓 값으로 연산을 수행하고 결과로 참(1, true) 또는 거짓(0, false)을 반환한다.

논리 연산자
설명
x || y
둘 중 하나라도 참이면 결과는 참 (OR)
x && y
둘 다 참이면 결과는 참 (AND)
!x
참이면 결과는 거짓, 거짓이면 결과는 참 (NOT)

비트 연산자

비트 단위로 논리 연산을 수행한다. 1은 참, 0은 거짓을 나타낸다.

비트 연산자
설명
x | y
두 비트 중 하나라도 1이면 결과는 1 (OR)
x & y
두 비트 모두 1이면 결과는 1 (AND)
x ^ y
두 비트가 같으면 결과는 0, 다르면 결과는 1 (XOR)
~x
비트가 0이면 결과는 1, 1이면 결과는 0으로 모든 비트를 반전 (NOT)

시프트 연산자

비트를 특정 값만큼 왼쪽 혹은 오른쪽으로 이동한다. n만큼 시프트한 결과는 2^n으로 곱하거나 나눈 값과 같다.

시프트 연산자
설명
x << n (산술 시프트)
비트를 n만큼 왼쪽으로 이동
오른쪽 빈 칸은 모두 0으로 채움
== x * (2^n)
x >> n (산술 시프트)
비트를 n만큼 오른쪽으로 이동
왼쪽 빈 칸은 가장 왼쪽에 있던 비트(MSB)와 동일한 비트 값으로 채움
(양수는 양수, 음수는 음수로 부호가 유지됨)
== x / (2^n)
x >>> n (논리 시프트)
비트를 n만큼 오른쪽으로 이동
왼쪽 빈 칸은 모두 0으로 채움
(음수는 부호가 유지되지 않음)
시프트 연산자 예시
아래 표에서 산술 시프트를 가할 데이터는 1111 1111 0001 0001 입니다. 이는 10진수 음수로는 -239이고, 양수로는 65297이다. 우측 산술 시프트는 MSB가 1이면 1로 채우고, 0이면 0으로 채운다. 따라서 해당 데이터에 4비트만큼 우측 산술 시프트를 가하면, 1111 1111 1111 0001 이 된다. 이는 10진수 음수로는 -15이고, 양수로는 65521이다.
 
 
1
1
1
1
1
1
1
1
0
0
0
1
0
0
0
1
-239
>> 4
1
1
1
1
1
1
1
1
1
1
1
1
0
0
0
1
-15
 

▶ >>

아래 표는 1111 1111 0001 0001 에 4비트만큼 우측으로 논리 시프트를 가하는 모습이다. MSB에 상관없이 0으로 채우기 때문에 0000 1111 1111 0001 이 됩니다.
 
 
1
1
1
1
1
1
1
1
0
0
0
1
0
0
0
1
-239
>>> 4
0
0
0
0
1
1
1
1
1
1
1
1
0
0
0
1
4081
 

▶ >>>

비트 연산 활용

AND (&) 연산을 활용한 비트 마스킹

어떤 데이터가 존재할 때, 특정 위치의 비트만 표시하거나 가리는 연산을 비트 마스킹(Bit Masking)이라고 부른다. AND 연산은 대응하는 비트 중에 하나라도 0이면 0이 되는데, 이러한 AND 연산의 특징을 활용해 비트 마스킹을 할 수 있다.

 

예를 들어 2바이트 크기의 데이터는 0xABCD가 존재할 때, AND 연산을 활용하면 상위 8비트에 해당하는 0xAB는 없애고, 하위 8비트에 해당하는 0xCD만 남길 수 있다. 이를 위해서 다음과 같이 0xABCD에 0x00FF를 AND 연산하면 된다.

16진수 F(1111)는 특정 값과 AND 연산하면 기존 값에서 비트 값이 1이면 결과가 1, 0이면 결과가 0이기 때문에 기존 값과 동일한 결과가 나온다. 반대로 0(0000)은 특정 값과 AND 연산하면 모든 비트가 0이 되어 결과는 0이다. 따라서 상위 1바이트인 0xAB는 모두 0이 되고, 하위 1바이트인 0xCD는 1인 비트만 남아 결국 0xCD가 남는다.

 

AND (&) 연산과 시프트 연산을 활용하여 특정 비트/바이트 가져오기

또한 AND 연산과 시프트 연산을 함께 활용하면 특정 위치의 비트를 가져올 수 있다. 다음은 32비트 즉, 4바이트 크기의 데이터인 0x12345678 (0001 0010 0011 0100 0101 0110 0111 1000) 에서 특정 비트만 가져오는 예시이다.

0x12345678 & 0x000000FF = 0x00000078 (0000 0000 0000 0000 0000 0000 0111 1000)

▶ 하위 1 바이트만 가져오기: 0x000000FF와 AND 연산

0x12345678 >>> 24 = 0x00000012 (0000 0000 0000 0000 0000 0000 0001 0010)
0x12345678 >> 24 & 0x000000FF = 0x00000012 (0000 0000 0000 0000 0000 0000 0001 0010)

▶ 상위 1 바이트만 가져오기: 24번 우측으로 논리 시프트를 수행 or 24번 우측으로 산술 시프트한 후 0x000000FF와 AND 연산

0x12345678 >> 16 & 0x000000FF = 0x00000034 (0000 0000 0000 0000 0000 0000 0011 0100)

▶ 상위에서 두 번째 바이트 가져오기: 16번 우측으로 시프트 후 0x000000FF와 AND 연산

0x12345678 >> 4 & 0x0000000F = 0x00000007 (0000 0000 0000 0000 0000 0000 0000 0111)

하위 1바이트의 상위 4비트 가져오기: 4번 우측으로 시프트한 후 0x0000000F와 AND 연산

 

XOR 연산을 활용한 비교와 암호화

XOR 연산은 비트 값이 같으면 0을 반환한다. 즉, 자기 자신과 XOR 연산하면 결과는 0이다. 두 변수에 저장된 값끼리 XOR 연산하여 결과가 0인지 확인하면 두 값이 같은지 비교할 수 있다. 

더 나아가서, 같은 값에 어떤 값을 2번 XOR 하면 원래의 값과 동일해진다는 특성을 이용할 수도 있다. 예를 들어 x ^ y를 수행한 결과가 z일 때, z ^ y = x ^ y ^ y = x이다. 이러한 특성을 기반으로, y를 key로 설정하면 간단한 암호화, 복호화가 가능하다.

#include <stdio.h>

int main() {
    int A = 42;   // 원래 데이터
    int B = 123;  // 키
    
    int C = A ^ B;  // 암호화
    int D = C ^ B;  // 복호화
    
    printf("암호화된 값: %d\n", C); // 암호화된 값: 81
    printf("복호화된 값: %d\n", D); // 복호화된 값: 42
    
    return 0;
}

 

XOR 연산은 이러한 특성에 의해 어셈블리 코드나 암호 기법에 자주 등장한다.

 

시프트 연산을 활용한 곱셈, 나눗셈

시프트 연산은 2^n을 곱하거나 나눈 결과와 동일하다. 따라서 곱셈이나 나눗셈 연산자 대신 시프트 연산자를 사용하여 간단한 산술 연산을 수행할 수 있다. 시프트 연산자를 사용하면 비트 레벨에서 연산을 하므로 더 효율적이고 속도가 빠르다.

인코딩, 디코딩

인코딩(Encoding)은 데이터를 특정한 형식으로 변환하는 것을 말한다. 데이터의 크기를 줄이거나, 컴퓨터가 이해하기 쉽게 변환할 때 사용한다. 인코딩된 데이터는 디코딩(Decoding)하여 원래의 값을 구할 수 있다. 인코딩은 암호화와 유사하지만, 암호화는 데이터의 기밀성을 목적으로 남이 데이터를 알아보지 못하도록 변환하기 때문에 비밀키가 있어야 원문을 복구할 수 있지만, 인코딩은 누구나 표준화된 방식을 사용해서 디코딩하여 원문을 구할 수 있다는 점이 다르다. 실생활에서 볼 수 있는 인코딩의 예시로 압축이 있다.

 

인코딩은 컴퓨터가 데이터를 효율적으로 다루도록 중요한 역할을 수행하기에 웹, 시스템, 네트워크 등 모든 분야에서 인코딩이 중요하다. 어떤 인코딩된 데이터의 생김새를 보고 인코딩 방식을 유추하여 직접 디코딩해서 원문으로부터 단서를 얻어야 하는 경우도 있다.

아스키 코드

컴퓨터는 0과 1만을 이용하여 정보를 처리하고 표현한다. 우리가 입력하는 모든 글자가 0과 1로 변환되려면 숫자로 표현되어야 한다. 이를 위한 것이 아스키 코드이다.

 

아스키 코드(American Standard Code for Information Interchange, ASCII)는 정보 교환을 위한 미국 표준 코드로, 문자를 숫자로 변환하는 문자 인코딩(character encoding)의 표준이다. 아스키 코드를 사용하여 문자를 숫자로 인코딩하면 서로 다른 장치 간 데이터 전송을 더 쉽게 수행할 수 있다.

 

아스키 문자 1개는 1 바이트 크기로, 7 비트로 문자를 표현하고 1 비트는 오류 체크를 위해 사용한다. 따라서 2^7 = 128가지의 문자 표현이 가능하며, 각 문자는 0 ~ 127까지의 10진수 값을 가진다. 이 값을 문자의 아스키 값(ASCII value)이라고 하며, 아스키 테이블을 보면 아스키 값에 해당하는 문자를 알 수 있다.

 

아래 표는 아스키 값이 문자로 표현되는 예시이다. 일련의 비트 값 0b011000110110000101110100는 총 24 비트(3 바이트)이므로 아스키 문자 3개로 표현할 수 있다. 8 비트(1 바이트)씩 보면, 각각 10진수로 99, 97, 116이다. 아스키 테이블에서 각 10진수에 해당하는 문자는 99 = c, 97 = a, 116 = t이다. 즉, 해당 비트 값을 아스키 문자열로 나타내면 cat이다.

1100011
1100001
1110100
99
97
116
c
a
t

 

※ 0b1100011 = 0x63 = 6*16 + 3 = 99, 이때 4비트의 각 가중치는 8-4-2-1 이므로, 6 = 4 + 2, 3 = 2 + 1가 된 것이다.

유니코드

아스키 코드는 미국에서 만든 표준 코드인 만큼 알파벳 대소문자, 숫자, 특수 문자, 제어 문자만을 포함하고 있다. 초반에는 이러한 코드도 문제가 없었으나, 점차 다양한 언어와 문자가 나타나면서 새로운 형식이 필요해져 유니코드가 등장했다.

 

유니코드(Unicode)는 영어뿐만 아니라, 전세계 모든 언어의 문자에 고유한 번호를 부여하는 국제 표준 코드이다. 서로 다른 언어를 사용할 때 문자가 호환되지 않는 문제를 해결하기 위해 만들어졌다. 아스키 코드보다 용량을 크게 확장하여 최대 32 비트로 문자 1개를 표현하여, 현재 143000개 이상의 문자를 표현할 수 있다. 유니코드의 처음 128개 문자는 아스키 코드의 문자와 정확히 일치한다. 즉, 유니코드 안에 아스키 코드가 포함된다고 볼 수 있다. 유니코드 값은 U+ 뒤에 16진수를 붙여 나타낸다.

 

UTF-8, UTF-16, UTF-32 등 유니코드를 사용하는 다양한 인코딩 형식이 존재한다. 이는 컴퓨터가 어떤 문자를 어떻게 읽어야 하는지 미리 정해준다. UTF 뒤의 숫자는 비트를 의미한다. 가장 일반적으로 사용되는 UTF-8은 1~4 바이트의 가변적인 크기로 문자 1개를 표현하는 방식으로, 아스키 코드와 유사하다. 현재 컴퓨터로 볼 수 있는 글자는 대부분 UTF-8로 인코딩된 값이다.

URL 인코딩 (퍼센트 인코딩)

웹에서 사용되는 URL은 특정한 형식의 문자열만 허용한다. 알파벳 대소문자, 숫자, 그리고 일부 특수 문자만을 포함할 수 있다. 웹 브라우저로부터 받은 URL 문자열을 유효한 형식으로 변환하는 것을 URL 인코딩이라고 한다.

 

URL 인코딩을 통해 문자열을 인터넷으로 전송 가능한 형식으로 변환한다. 이를 통해 전송 중에 문자가 수정되거나 의도와 다르게 해석되는 것을 막을 수 있다. 허요되지 않는 문자, 즉 인코딩이 필요한 특수문자는 ' :/?#[]@!$&'()*+,;=%공백' 이다.

 

URL 인코딩은 % 기호 뒤에 문자의 아스키 코드 16진수 값을 붙여 나타낸다. 예를 들어, URL에 공백(space)이 포함되는 경우 %20으로 변환된다. 여기서 %20이 URL 인코딩 결과이다.

 

예를 들어, 문자열 Welcome, Dreamhack Beginners! :)를 URL 인코딩한 결과는 Welcome%2C%20Dreamhack%20Beginners%21%20%3A%29이다.

Base64 인코딩

Base64 인코딩은 이진 데이터를 아스키 문자로 구성된 텍스트로 변환하는 인코딩 방식이다. 총 64개의 아스키 문자가 인코딩에 사용되기 때문에 64진법(Base 64)이라는 의미에서 이러한 이름이 붙여졌다. 64개의 아스키 문자는 알파벳 대소문자(52자), 숫자(10자), +, / 이다.

 

Base64 인코딩은 이진 데이터를 그대로 포함할 수 없이 텍스트만 허용되는 환경에서 이진 데이터를 텍스트 형식으로 나타내기 위해 사용한다. 예를 들어, 이진 데이터인 이미지를 HTML 파일에 넣는 경우 base64로 인코딩해 넣을 수 있다.

 

Base64 인코딩 방식

  • 1. 원본 이진 데이터를 비트 나열로 표현하고, 이를 6 비트씩 끊어서 묶는다. 만약 비트의 개수가 6의 배수가 아닐 경우, 0을 뒤에 추가하여 6의 배수로 만든다.
  • 2. 각 6 비트 묶음을 수로 변환한 뒤, base64 테이블에서 해당하는 문자를 찾아 이로 치환한다.
  • 이렇게 치환 과정을 거친 뒤, 글자 수가 4의 배수가 되도록 문자 '='를 반복해 뒤에 추가한다. 이를 패딩(Padding)이라고 한다.

Base64 인코딩 시, 원본 데이터의 크기가 3 바이트(24 비트 = 6 * 4 비트)의 배수가 아닐 경우, 6비트씩 나누다 보면 남는 비트가 발생할 수 있다. 이 경우 디코딩을 하는 입장에서 뒤에 추가적인 내용이 있는데 오지 않은 것인지, 아니면 여기서 디코딩을 끝내는 것이 맞는지 알 수 없게된다. 때문에, 4의 배수 길이를 맞추기 위해 = 패딩을 추가하는 것이다.

▶ dream을 base64 인코딩한 예시 - 인코딩 결과: ZHJLYW0=

 

인코딩 도구: 문자열을 특정 형식으로 인코딩, 디코딩할 수 있는 도구들은 매우 다양하여, 해킹 문제를 풀거나 문자열을 원하는 형태로 변환하고 싶을 때 유용하다.

운영체제(OS)

사용자는 웹 브라우저, 엑셀, 메모장 등의 프로그램으로 원하는 작업을 수행하곤 한다. 사용자를 위해 특정한 기능을 수행하는 이러한 프로그램을 응용 프로그램이라고 한다. 이때 응용 프로그램의 동작을 수행하고, 응용 프로그램에게 시스템 자원을 할당하는 등의 복잡한 관리 작업은 사용자가 아니라 운영체제(Operating System, OS)라는 소프트웨어에 의해 이루어진다.

 

사용자 및 응용 프로그램은 컴퓨터 하드웨어(CPU, 메모리, 입출력 장치 등)에 직접 접근하지 않는다. 대신 운영체제가 하드웨어와 사용자/응용 프로그램 사이에서 중재자 역할을 한다.

운영체제가 하는 일

운영체제는 CPU, 메모리, 입출력 장치(키보드, 마우스, 디스크) 등의 하드웨어 자원을 효율적으로 사용할 수 있도록 분배, 할당하여 성능을 높인다. 만약 운영체제가 없다면 사용자가 하드웨어를 직접 관리해야 한다.

 

운영체제가 수행하는 대표적인 기능

  • 프로세스에 CPU를 번갈아 할당해야 하는데, 이때 어떤 프로세스에 CPU를 할당할지 결정 (CPU 스케줄링)
  • 메모리 공간을 각 프로세스에 분배하고 사용하는 과정 관리
  • 컴퓨터가 입출력 장치와 정보를 주고 받는 과정 관리

※ 프로세스 = 실행 중인 프로그램

 

운영체제는 사용자와 컴퓨터 사이 인터페이스 역할도 한다. 사용자가 컴퓨터에 명령을 내릴 수 있도록 하는데, 이는 의 기능이다.

 

커널과 셸

운영체제는 크게 커널과 셸로 나눌 수 있다. 먼저 커널(Kernel, 알맹이)은 운영체제의 핵심 기능인 하드웨어 관리를 실제로 수행하는 프로그램이다. 커널은 소프트웨어와 하드웨어 간의 커뮤니케이션을 관리하며, 시스템이 부팅될 때 메모리에 올라가서 꺼질 때까지 실행된다.

 

셸(Shell, 껍질)은 사용자와 운영체제의 커널 사이에서 사용자가 운영체제에 명령을 내릴 수 있도록 인터페이스 역할을 한다. 사용자가 셸에 명령을 입력하면, 셸이 명령어를 해석하여 커널에 요청한다. 커널은 명령을 수행하며 하드웨어를 조작하고, 수행 결과를 셸에 전송한다. 셸은 이 결과를 해석하여 사용자에게 출력해 준다. 즉, 셸은 명령어를 해석하는 역할을 하여 사용자와 운영체제가 소통할 수 있도록 한다. 셸을 획득하면 명령어를 통해 원하는 작업을 수행하고 시스템을 제어할 수 있게 된다. 따라서 일반적으로 셸을 획득하는 것을 시스템 해킹의 성공으로 여긴다.

 

※ 사용자, 응용 프로그램 -> 셸 -> 커널 -> 하드웨어 (CPU, 메모리, 입출력 장치 등)

운영체제 종류

운영체제는 크게 Windows 운영체제와 UNIX/Linux 계열 운영체제로 나눌 수 있다.

 

Windows는 마이크로소프트사에서 개발한 운영체제로, Graphical User Interface (GUI) 기능을 제공하여 사용자가 편리하게 사용할 수 있다. 2024년 통계에 의하면 대한민국 PC 운영체제 점유율의 90% 정도를 Windows가 차지하고 있다. 그만큼 많은 한국인들은 어렸을 때부터 윈도우를 사용해 왔으며, 윈도우 사용법에 친숙할 것이다.

 

UNIX는 벨 연구소에서 개발한 운영체제로, 대부분의 운영체제는 UNIX로부터 발전된 기술을 사용하고 있다. 따라서 현대 운영체제의 원형이라고 할 수 있다. 사용자가 키보드로 입력하는 명령에 의해 조작되는 Character User Interface (CUI) 기반의 대화식 운영체제이다.

 

UNIX가 개발된 이후, 리누스 토발즈(Linus Benedict Torvalds)가 UNIX 기반의 Linux 운영체제를 개발했다. Linux는 소스 코드를 공개하여 누구나 수정하고 재배포할 수 있는 오픈소스 프로그램으로, 많은 사람에 의해 계속해서 발전하고 있다. Linux 역시 CUI 기반의 운영체제이다. Linux를 기반으로 Ubuntu, CentOS 등의 다양한 버전이 존재한다.

 

UNIX 기반의 또 다른 운영체제로는 애플에서 개발한 GUI 기반 운영체제 macOS, 스마트폰을 많이 사용하는 현대인에게 친숙한 모바일 운영체제 AndroidIOS가 있다.

Wargame: 64se64

▶ 문제

 

소스 코드 확인을 위해 서버를 생성하여 서버를 생성하여 웹사이트에 들어가주면 다음과 같이 출력된다.

Welcome! 👋

F12를 눌러 소스 코드를 확인하니 다음과 같이 Base64로 인코딩된 텍스트를 확인할 수 있다.

value에 있는 인코딩된 텍스트를 디코딩 사이트를 통해 디코딩 하면,

다음과 같은 파이썬 코드를 얻을 수 있다. 얻은 파이썬 코드를 실행하면,

이렇게 플래그 값이 출력되는 것을 확인할 수 있다.