프로그램
요즘사람들이 없으면 안되는물건이 뭔지아시나요? 바로 스마트폰과 컴퓨터입니다. 우리는 스마트폰과 컴퓨터로 방안에서 전세계에서 정보를 얻고, 사람들과 소통하며 업무를하고, 수업을듣고 여과생활을 할 수 있습니다. 이렇게 우리는 스마트폰과 컴퓨터에 있는 프로그램들을 실행함으로써 방안에서도 편리한 생활을 할 수 있습니다. 프로그램이 뭘까요?
프로그램이란 프로그래밍된 결과물을 뜻합니다. 프로그램의 범주 안에는 실행파일이있습니다.
윈도우에서는 .exe 파일이 실행파일이라 볼 수 있죠. #윈도우라는 가정 하에 예제를 설명하겠습니다
프로그램은 소프트웨어의 구성요소입니다.
일반적으로 프로그램은 소프트웨어와 비슷한 의미로 쓰입니다.
소프트웨어
한컴타자연습을 예로들어 소프트웨어를 설명해드리겠습니다.
우리가보고있는 배경화면, 텍스트, 버튼은 모두 데이터에불과합니다.
데이터는 혼자서 아무것도할수없는 소프트웨어이고
프로그램은 정해진규칙에따라 명령을수행하는 소프트웨어입니다.
데이터는 이처럼 정해진규칙에따라 프로그램에의해 수정되고, 출력되게됩니다.
한컴타자연습이 설치되어있는 경로입니다. 응용프로그램과 Data 폴더가 보이실껍니다. 여기있는 모든파일을 합해서
소프트웨어라 부를 수 있습니다.
#프로그램 + Data = 소프트웨어 #하지만 일반적으로 프로그램과 소프트웨어는 비슷한의미로 쓰입니다.
Data 폴더에는, 우리가 글자를 입력했을때 나오는 소리, 버튼, 배경화면 등이 들어있습니다.
프로그램은 데이터를 하드웨어 (스피커,모니터,키보드) 를 통해 사용자에게 화면을출력해주고, 글자를 입력받고, 효과음을 내는등 사용자와 상호작용을 합니다.
이제 프로그램을 한문장으로 정의할수있겠죠 ? 프로그램은 특정작업을하기위한 명령들의 집합체라 할 수 있습니다
프로그래밍
프로그램은 프로그래밍된 결과물이라했습니다. 프로그래밍은 컴퓨터프로그램을 작성하는일입니다.
컴퓨터프로그램은 프로그래밍언어의 명령들로 만들수있는데, 이때 명령들이 적힌 텍스트파일을 소스코드 라고합니다. 소스코드를 작성하는행위를 코딩이라하죠. 일반적으로 프로그래밍과 코딩은 같은의미로 쓰입니다. 의미도비슷하고요.
프로그래밍언어는 C,C++,Python, Ruby, java 등이있고, R 언어, Django 등 새로 추가되고있는 언어도있습니다.
예시로 한번 프로그램을 작성해보겠습니다.여러분도 지금부터 프로그래머가되는겁니다.
입문언어로 널리쓰이는 C언어로 작성해보겠습니다.
[ helloworld.c ]
#include<stdio.h>
int main(void){
printf("hello world!\n");
return 0;
}
프로그래밍언어를 처음배울때 화면에 "hello world" 를 출력하는 관습이있습니다.
관습대로 "hello world" 를 출력하는 소스코드를 짜보았습니다. 이걸 코딩이라하죠.
[소스분석]
//느낌만이해하고지나가도 충분합니다.
#include<stdio.h>
# 로 시작하는건 전처리문입니다. 컴파일러에게 stdio.h 헤더파일을 포함시킬꺼라는걸 알려줍니다.
stdio.h -> STanDardInputOutput.Header 해석하자면 표준입출력파일으로, printf 함수가 정의되어있습니다.
int main(void) {
메인함수를 선언합니다. c언어에서는 코드가 main에서시작해, main에서끝납니다.
main 함수에 명령을 입력하면, 실행될때 main 함수의명령들이실행되는거죠
printf("hello world!\n");
이게바로 명령입니다. printf 함수에 "hello world!\n" 라는 인자를 전달해서 hello world! 를 출력하라고 명령합니다.
C언어에서는 모든 명령 뒤에 ; (세미콜론) 이 붙어야합니다. 안그럼 오류가나죠.
return 0;
메인함수를 리턴한다. 0을 리턴해서 메인함수가 정상적으로 종료되었음을 알린다.
}
컴파일
이러한 .c 파일을 .exe 로 만들어 실행할수있게 만드려면 컴파일이란 과정을 거쳐야한다.
# 컴파일이란 ? 사람이 이해할수있는 언어를 컴퓨터가 이해 할 수 있는 언어로 바꾸어주는것이다.
컴파일 과정 -> 전처리 ( .i 파일 ) - 컴파일 ( .o 파일 ) - 링킹 ( .exe 파일)
전처리 :
# 으로시작하는 구문은 전처리구문입니다.
우리는 #include<stdio.h> 를 사용했죠?
stdio.h 헤더파일을찾아 내용을 삽입한 후 helloworld.c 파일을 helloworld.i 파일로 만들어줍니다.
컴파일 :
소스코드에서 잘못된 문법은 없는지 검사하고, helloworld.i 파일을 helloworld.o 파일로 만들어준다.
helloworld.i 파일에서 어셈블리라는 low level 언어로 바꿔 helloworld.s 파일을 생성하고,
helloworld.s 파일에서 컴퓨터가 이해할 수 있는 기계어로 바꾸어준다.
그게바로 helloworld.o 파일이다. 이 파일은 오브젝트파일이라부르고,
실행파일이 되기위한 최종단계파일이다.
링킹 :
우리는 소스코드를 여러개작성해서각각 .o (object 파일) 으로 만들수있다.
이러한 여러 object 파일 또는 하나의 object 파일과, <stdio.h> 같은 라이브러리 (헤더파일) 를
링크해서 실행가능한파일로 만들어준다.
실행파일 구조
이렇게 만들어진 실행파일은 구조를 이루고있다. 윈도우에서는 이 구조를 PE 구조라고한다.
DOS header :
MZ 라는 시그니쳐로 시작해서, 프로그램의 정보를 구조체형식으로 포함하고있다.
DOS stub :
필수는 아니다. 다만 16bit 환경에서 실행시 16bit 환경에서 구동될수없다는 문자열을 출력하고, 종료하는 어셈블리코드를 담고있다
NT header :
PE 라는 시그니쳐로 시작해서, IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER 으로 이루어져있다.
IMAGE_FILE_HEADER :
CPU별 고유값,섹션개수, opional_header 의 크기,파일 속성 등의 정보를 담고있다.
IMAGE_OPTIONAL_HEADER :
pe 파일의 시작주소(ImageBase), 프로세스 시작주소, pe헤더크기, 등의 정보를 담고있다.
Section header :
윈도우에서는, 코드, 데이터 등을 효율적으로 구분하기위해 섹션이라는 개념을 사용한다.
섹션헤더에서는, 각 자신이 가르키는 섹션의 크기, 속성값을 가지고있다.
Section :
섹션은 역할별로 실행권한이 나누어져있고, 종류도 다양하다. 위에있는 사진에있는 섹션이 다가 아니라는걸 알아두길바란다.
.text : 컴파일된 코드가 담겨져있는 섹션이다. 실행,읽기권한을 가진다.
.data : 초기화된 전역변수가 담겨져있다. 읽기, 쓰기권한을 가진다.
.rsrc : 리소스를 담고있다. ( ex : 실행아이콘 ), 읽기권한만을 가진다.
더있지만, 이번포스팅에서는 생략하겠습니다.
메모리상 구조
우리가 프로그램을 실행하면 4gb 의 메모리를 할당받아, 프로그램이 메모리에 올라가게된다.
(물론 4gb 에 다 올라가는게 아니라, 가상메모리, 페이징이라는 기법으로 필요한부분만 사용하므로
프로그램 4개정도 실행한다고, 메모리를 전부 사용하는건 아니다. )
그때의 메모리구조이다. 코드영역, 데이터영역, 힙영역, 스택영역으로 나뉜다.
Code :
우리가 작성한 코드가 들어가는 부분이다. 물론 컴파일후 기계어로변한 코드를 말하는거다.
텍스트영역이라고도 부른다.
Data :
전역변수 ( main 함수밖에 선언한 변수 ), 정적변수 가 저장되는 영역이다.
프로그램시작시 할당되고, 프로그램종료시 소멸된다.
Heap :
힙주소는, 프로그래머가 동적으로 할당하는 공간이다.
이 공간은 프로그래머가 관리하며, 프로그램 시작시 주소의 크기가 정해진다.
Stack :
스택은 임시 메모리 영역이라할 수 있다.
함수 실행시 (main함수같은), 사용되는 지역변수와 인자 등이 저장되었다가, 함수호출이 끝나면 사라진다.
Heap & Stack :
힙과 스택은 서로 같은 주소영역을 쓰고있다. 힙은 위에서부터, 스택은 아래서부터 주소를 사용한다.
힙공간을 많이사용할수록 스택영역이 줄어들고, 스택공간을 많이사용하면, 힙영역이 줄어든다.
그러다가, 서로의 영역을 침범하면 오류가나게되는데, 침범을 일으킨대상에따라
Heap overflow, Stack overflow 라고부른다.
취약점
스택에서 입력받을때는 낮은주소에서 높은주소로 입력받는다. 이때 높은주소에 저장된값들을 덮어쓸때 BOF(BUFFER OVER FLOW) 가 일어날때 우리가 원하는 동작을 하도록 조작할수있다.
이런걸 프로그램에서 일어날 수 있는 취약점이라부른다.
이 코드는 취약한코드이다. buf 의 길이는 10밖에 불과한데, scanf 나 gets 같은함수는 10보다 많이 입력을 받을수있다.
그러므로 정해진 범위밖까지 입력을 받았으므로, 오류가나게된다.
잠깐 조금 심화된 과정으로 들어가보도록하자.
[rbp-10]
[rbp]
[ret address]
우리가 입력을 받을때 스택은 저렇게되어있다.
우리는 rbp-10 에 입력을 받는다.
한글자씩 입력을 받을 수록, rbp-10, rbp-9, ... rbp-1 이렇게 순서대로 저장된다.
원래는 rbp-1 까지만 입력을 받아야하지만, 그 이상으로 입력을 받으면, rbp 에 우리가 입력한값이 들어가고,
ret address 에도 우리가 입력한 값이 들어간다. ret address 는 main 함수를 호출한곳의 주소를 담고있고,
main 함수에서 그 ret address 로 리턴해야 정상적으로 종료가 되지만,
ret address 에 우리가 입력한값으로 덮어씌워졌기때문에, 정상적으로 종료되지않는다.
우리는 이러한 취약점을 이용해 우리가 원하는곳으로 main 함수에서 리턴할수있다.
리눅스에서는 shell 이라는것이있다. 윈도우의 cmd 와 비슷한데, 명령어를 실행함으로써
우리가 원하는행위들을 할 수 있다. (ex : 파일읽기, 파일삭제 등 )
이렇게 ret address 를 조작해 shell 을 실행한다면, 다른사람의 서버에서 이 프로그램을 실행할때
리턴주소를 조작해 shell 을 실행한다면, 그사람의 서버에서 정보들을 훔치는 등 심각한 문제가 일어날수있다.
(하지만 이런일은 거의 없다.)
그렇기에 컴파일시 메모리보호기법을 적용해 컴파일하고, fgets 와 같은 안전한 함수로 입력을받고,
변수들의 크기를 잘 조정해줘야한다. 이토록, 우리모두 안전한 프로그래밍을 하자.
#참고 #참고자료는 참고자료일뿐
https://gracefulprograming.tistory.com/16
https://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8