Information Security
C언어로 만들어진 프로그램의 실행과정 이해 본문
C언어 프로그램 실행과정 이해하기
CPU가 프로그램을 실행하는 과정 중에 취약점이 발생하기 때문에, 소스코드가 실행 파일로 만들어지는 과정을 알아야 한다.
IDE를 사용하지 않고 C언어로 작성된 소스 코드를 직접 컴파일, 어셈블하면서 과정을 파악해볼 것이다.
※ 우리가 이때까지 C언어를 배우면서 사용한 Visual Studio, 자바를 배우면서 사용한 이클립스는 컴파일러가 아니고 IDE(Integrated Development Environment: 통합 개발 환경)이다.
IDE는 컴파일러, 편집기, 디버거를 하나로 묶은 소프트웨어이다.
일단 vi 편집기로 Hello World를 출력하는 소스를 작성한다.
gcc 명령어로 해당 파일을 컴파일 한다.
명령어 실행 후 아무것도 출력되지 않으면 에러 없이 컴파일에 성공한 것이다.
컴파일이 완료되면 a.out이라는 실행 파일이 생성된다.
a.out은 기본 실행 파일명이다.
-o 옵션을 사용하면 실행 파일의 이름을 지정할 수 있다.
실행 시키면 Hello World가 출력된다.
컴파일 과정
-v 옵션을 사용하여 컴파일을 하면 컴파일 과정이 출력된다.
이 과정은 총 4개의 단계로 이루어져 있다.
1) 전처리 단계: cpp (C PreProcess)
-매크로, 헤더 파일을 처리해주는 단계이다.
/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=91 -D__ELF__ -Dunix -Di386 -D__i386__ -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(posix) -Asystem(unix) -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ hello.c /tmp/ccPRxee4.i
옵션들이 굉장히 많다. 여기서 /tmp/ccPPxee4.i 임시 파일에 주목해야 한다.
임시 파일은 컴파일이 끝나면 자동으로 삭제되어서 확인할 수 없다.
-save-temps 옵션을 사용하면 컴파일 전 과정의 임시 파일이 삭제되지 않고 저장된다.
컴파일 후 파일 목록을 확인해보면 .i .o .s 파일이 생성되어 있다.
여기서 .i 파일은 전처리가 완료된 파일이다.
전처리 파일을 이해하기 쉽게 확인하기 위해서 #include를 #define으로 수정했다.
다시 컴파일을 해주고 전처리 파일(hello.i)을 확인한다.
매크로를 통해 NUMBER의 값이 1000으로 치환된 것을 확인할 수 있다.
2) 컴파일 단계: cc1
-소스 파일을 어셈블리어로 변환시켜 주는 단계이다.
/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cc1 /tmp/ccPRxee4.i -quiet -dumpbase hello.c -version -o /tmp/ccQg0qFW.s
.i 파일을 .s로 변환
hello.s 파일 확인
소스 파일이 어셈블리어로 변환되었다.
※ 어셈블리어
컴퓨터는 기계어만 이해할 수 있다.
그런데 기계어 1,0으로만 프로그래밍을 하면 너무 힘들기 때문에 기계어 명령과 1:1 매칭되는 기호(mnemonic)인 어셈블리어가 만들어 졌다.
소스 파일 -(컴파일)-> 어셈블리어 -(어셈블)-> 기계어
-> 이 과정은 소프트웨어 공학의 일부분이다.
시스템 해킹을 공부하는 우리는 거꾸로 기계어를 어셈블리어로 변환하고 그 변환된 어셈블리어를 통해 원본 소스 파일을 추측해내야 한다. 이게 바로 역 공학, 리버스 엔지니어링, 리버싱(Reversing)이다.
3) 어셈블 단계: as
-어셈블리어를 기계어로 변환시켜주는 단계이다.
as -V -Qy -o /tmp/ccHwM0wP.o /tmp/ccQg0qFW.s
.s 파일을 목적 파일(.o)로 변환
목적 파일은 바이너리 파일이므로 xxd 명령어로 확인할 수 있다.
4) 링크 단계: collect2, ld
-목적 파일을 실행하려면 실행에 필요한 라이브러리를 링크해야 한다.
/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib /tmp/ccHwM0wP.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o
목적 파일을 라이브러리와 링크하여 실행 파일로 만든다.
결론적으로 소스 코드가 실행 파일로 만들어지는 과정은 다음과 같다.
원본 소스 코드(hello.c) -(전처리)-> 전 처리 파일(hello.i) -(컴파일)-> 어셈블리어 파일(hello.s) -(어셈블)-> 바이너리 파일(hello.o) -(링크)-> 실행 파일
'System Hacking' 카테고리의 다른 글
함수 (0) | 2018.01.23 |
---|---|
관계 연산(cmp) + 제어문 (0) | 2018.01.19 |
레지스터(Register)와 사칙연산 (0) | 2018.01.18 |
주소 vs 메모리 (0) | 2018.01.16 |
System Hacking 실습 환경 구성 (0) | 2018.01.10 |