초보 해커를 위한 C언어와 동작 원리/Ch1. Theory

컴파일러와 인터프리터

anonkorea4869 2025. 1. 27. 15:04

학습 목표

! 본 내용은 검토 전 포스트 입니다. 부정학한 내용이 있을 수 있으니 양해바랍니다.

  • 고급언어와 저급언어의 정의를 이해한다.
  • 컴파일러와 인터프리터의 차이점을 이해한다.
  • 해커가 어떻게 프로그램을 분석하는지 이해한다.

1. 고급언어와 저급언어

우리가 C언어, Python 등으로 짠 소스코드는 사람이 읽기 쉬운 고급 언어이고 컴퓨터가 해석하는 어셈블리어(Assembly language), 기계어은 저급 언어이다. 단어의 의미를 혼동하기 쉬운데 고급 언어는 고급스러운 언어가 아닌 High-level language이기 때문에 컴퓨터와 사람과의 관계에서 인간 친화적이다는 뜻이다. 컴퓨터는 고급언어를 인식하지 못하기 때문에 어셈블리어로 변환 후 기계어로 해석하는 과정을 거친다.

#include 

int main(void) {
    printf("hello world");

    return 0;
}
endbr64 
push   rbp
mov    rbp,rsp
lea    rax,[rip+0xeac]
mov    rdi,rax
mov    eax,0x0
call   printf@plt
mov    eax,0x0
pop    rbp
ret  

 

2. 컴파일러와 인터프리터

컴파일러(Compiler)인터프리터(Interpreter)는 우리가 짠 소스코드를 컴퓨터가 해석할 수 있게 도와주는 프로그램이고, 그 과정을 각각 컴파일(Compile) 인터프리팅(Interpreting)이라고 한다.

다컴파일과 인터프리팅은 공통적으로 고급언어에서 저급언어로 번역하는 것이지만 컴파일은 한 번에 저급언어로 변화하여 실행파일(Executable file)에 저장하고, 인터프리팅은 실행시 코드를 한 줄씩 저급언어로 변환한다. 우리가 이해하기 쉬운  한국어는 고급언어하고 비교적 이해하기 힘든 영어를 저급언어라고 가정하자. 컴파일 방식은 번역가(컴파일러)가 한글을 통째로 번역하여 책으로 만드는 것이고. 인터프리팅 방식은 통역가(인터프리터)가 사용자 요청에 그때마다 한글을 한 줄씩 영어로 번역하는 방식이다.

 

컴파일 : 소스코드를 저급언어로 번역하여 실행파일로 저장

인터프리터 : 소스코드 실행시 한 줄씩 저급언어로 번역

2.1. 컴파일과 인터프리터의 장단점

컴파일

장점 : 모든 소스코드가 저급언어로 번역되어있는 상태이기 때문에 실행 시 속도가 굉장히 빠르다.

단점 : 최초 소스코드를 변경하는데 시간이 오래 걸린다.

인터프리터

장점 : 소스코드 중 일부만 실행시키는데 속도가 빠르다.

단점 : 실행시 한줄 씩 번역하기 때문에 속도가 느리다.

2.2. 컴파일 과정

 C코드는 전처리기, 컴파일러, 어셈블러, 링커를 통해 실행 파일로 만들어진 후, 로더를 통해 프로그램을 실행할 시 사용할 수 있게 해 준 다. 흔히 C코드를 목적파일로 만드는 과정을 컴파일, C코드를 실행 파일로 만드는 과정을 빌드(Build)라고 한다. 즉, 컴파일은 빌드의 한 과정이지만, 빌드와 컴파일을 혼용하여 부르기도 한다.

 

전처리기(Preprocessor) : 매크로(#define), 조건부 컴파일(#if, #ifdef, …)등을 적용하여 C코드 재해석한다.

컴파일러(Compiler) : C코드를 해석하여 어셈블리어로 변환한다.

어셈블러(Assembler) : 어셈블리어를 기계어로 바꾼다.

목적 파일(Object file) : 기계어 파일로 코드에 대한 전반적인 정보를 가지고 있다. 그 자체로는 프로그램이라고 볼 수 없다.

링커(Linker) : 목적 파일을 연결(Llinking)하여 실행 파일(바이너리 파일)로 만들어준다. 쉽게 프로그램에서 사용하는 기능들을 연결해 주는 것이라고 할 수 있다.

로더(Loader) : 실행파일을 실행할 수 있도록 메모리에 올려준다.

파일은 크게 텍스트 파일(Text file)과 이진 파일(Binary file)로 구분할 수 있다. 파일은 사람이 읽을 수 있게 되어있는 반면 바이너리 파일은 사람이 읽을 수 없고 컴퓨터만 해석할 수 있게 되어있다.

출처 : https://code-piggy.tistory.com/entry/컴퓨터-구조-C언어-컴파일-과정전처리-컴파일-어셈블러링커

2.3. 컴파일 TMI

컴파일을 시켜주는 컴파일러는 사용자가 작성한 소스코드 그대로 직역해주지 않는다. 가장 큰 이유는 최적화와 보안이다. 

사용자가 작성한 소스코드는 그 자체로 위험하다. 보안 지식이 없는 개발자가 작성한 코드라면 전문적인 지식을 가지고 있는 헤커가 서버 자체를 장악할 수 있다. 따라서 컴파일러는 기본적으로 보호 기법들을 적용하여 컴파일한다(사용자가 수동으로 옵션을 제거 가능). 여러 가지 보안 기능을 자동으로 넣기 때문에 보안적으로 안전하지만 저급언어가 길어지고 난해해진다.

아래는 C 코드를 저급 언어로 전환하였을 경우 보호 기법을 켜었을 때(오른쪽)와 껐을 때(왼쪽)의 차이를 보여준다.

#include <stdio.h>
#include <string.h>

int main(void) {
    char src[10] = "Hello";
    char dst[10];

    strcpy(dst, src);

    return 0;
}

 

endbr64 
push   rbp
mov    rbp,rsp
sub    rsp,0x20
movabs rax,0x6f6c6c6548
mov    QWORD PTR [rbp-0xa],rax
mov    WORD PTR [rbp-0x2],0x0
lea    rdx,[rbp-0xa]
lea    rax,[rbp-0x14]
mov    rsi,rdx
mov    rdi,rax
call   strcpy@plt
mov    eax,0x0
leave  
ret
endbr64 
push   rbp
mov    rbp,rsp
sub    rsp,0x20
mov    rax,QWORD PTR fs:0x28
mov    QWORD PTR [rbp-0x8],rax
xor    eax,eax
movabs rax,0x6f6c6c6548
mov    QWORD PTR [rbp-0x1c],rax
mov    WORD PTR [rbp-0x14],0x0
lea    rdx,[rbp-0x1c]
lea    rax,[rbp-0x12]
mov    rsi,rdx
mov    rdi,rax
call   strcpy@plt
mov    eax,0x0
mov    rdx,QWORD PTR [rbp-0x8]
sub    rdx,QWORD PTR fs:0x28
je     0x11c4 <main+91>
call   __stack_chk_fail@plt
leave  
ret

 

3. 소스코드 취약점 분석

헤커가 소스코드를 읽고 취약점을 찾는 행위를 오디팅(Auditing)이라고 한다. C코드처럼 읽을 수 있는 파일을 분석하는 행위를 소스코드 오디팅(Source code auditing)이라고 하며, 파일을 분석하는 행위를 리버싱(Reverse engineering)이라고 한다. 상용 프로그램(금액을 지불하고 구매하는 프로그램)의 경우 코드를 공개하지 않기 때문에 소스코드 오디팅이 불가하다. 주로 코드가 공개되어 있는 오픈소스를 분석할 때 사용한다. 반대로 소스코드가 공개되어있지 않는 실행파일은 리버싱으로 분석한다.

출처 : https://ggn0.tistory.com/47

3.1. 리버스 엔지니어링(Reverse engineering)

리버싱(리버스 엔지니어링)역공학으로 불리며 물건, 장치를 분석하여 구조, 기능, 동작을 역으로 따라가며 분석하고 그 원리를 이해해 부족한 부분을 보완하며 새로운 기능 등을 추가하는 작업이다. 컴퓨터에서의 바이너리 파일을 역으로 따라가며 동작 원리를 이해해 최적화 등 부족한 기능을 추가하는 작업이다. 리버싱을 통해 취약점을 찾아내고 게임 핵과 같은 기능을 추가할 수 있지만 최적화 외 허가가 없는 프로그램을 분석하는 것은 불법이다.

바이너리 파일은 컴퓨터만 읽을 수 있다고 해서 그 뜻을 사람이 해석하지 못하는 것은 아니다. 바이너리 파일을 디스어셈블리(Disassmebly)하여 어셈블리어로 복구할 수 있다. 훌륭한 리버서(Reverser)는 어셈블리어를 보고서 빠르고 직관적으로 원본 소스코드를 유추할 수 있다. 또한 IDA와 같은 프로그램을 통해 어셈블리어를 디어셈블리하여 기존 소스코드와 유사한 소스코드로 변환시킬 수 있다.

출처 : https://redscreen.tistory.com/3