컴퓨터 공학 및 프로그래밍 #1 - Data alignment, 포인터, Buffer Overflow
Data alignment
프로세서가 메모리에서 값을 가져올 때 특정 배수단위로 주소를 기반으로 가져 오기 때문에 성능 최적화를 위해서는 메모리에 저장되는 각 타입(short
, int
, double
)의 주소도 메모리상에 배수형태(2, 4, 8)로 저장되어야 함.
이를 위해 struct type
과 같은 경우 내부에 혹은 마지막에 추가적인 바이트를 넣어서 struct
내부의 값 혹은 element
를 struct
로 갖는 array
의 포인터가 4의 배수를 갖도록 컴파일 단계에서 특정 로직을 수행함.
예) int, char, int를 갖는 struct *xp의 경우 9바이트의 크기를 갖게 될 경우 내부의 2번째 int는 xp+5로 할당됨. 이 경우 data alignment를 위해 char뒤에 3바이트를 추가하여 총 12바이트의 struct를 만들고 2번째 int를 xp+8로 불러올 수 있도록 컴파일링이 수행됨. (struct 포인터인 xp도 4의 배수로 할당됨)
포인터에 대한 이해
- 모든 포인터는 연관된 타입이 존재한다: 포인터가 가리키는 값의 타입 (정수, 문자열 등)
- 모든 포인터는 값이 존재한다: 포인터의 값 = 메모리상의 주소
- 포인터는 & 연산자에 의해 만들어진다: 정수 값으로 정의된
x
(int x
)에 대하여&x
는 해당 값을 저장하고 있는 메모리 상의 주소(=포인터)가 된다. - 포인터는 * 연산자에 의해 역으로 가리켜진다 (dereference).
- 배열과 포인터는 밀접하게 관련되어 있다: 배열의 이름이 포인터 처럼 사용될 수 있음.
- 특정타입의 포인터를 다른 타입으로 변경하면(=캐스팅) 타입만 바뀔뿐 값은 동일하게 유지된다.
- 포인터는 함수도 가리킬 수 있다. (함수의 첫번째 instruction 메모리 주소를 가리킴)
Buffer Overflow
C에서 gets()
함수를 잘못 사용하게 되면 스택에 저장되는 stack pointer, base pointer, return address에 다른 값이 대신 덮어씌워지는 현상이 일어난다. 이런 문제가 발생시 caller
함수가 가지는 로컬변수를 정상적으로 접근할 수 없거나 return시에 아예 엉뚱한 곳에 있는 코드가 실행되게 된다.
버퍼 오버플로우를 이용하여 악성코드가 장치를 공격하는 방법중에 관련함수에 문자열 형태의 실행 스크립트(exploit code) 넣어서 이곳으로 리턴되도록 하는 방식이 있다.
이러한 버퍼 오버플로우 공격을 막기 위해 stack randomization방식이 존재한다. 과거에는 어떠한 프로그램을 동일한 운영체제 및 버젼에서 실행시키면 여러장치에서도 그 스택의 주소가 변하지 않고 동일해서 이것만 알아내면 공격이 가능했다. 하지만 이제는 특정 프로그램 실행시에 0~n 사이 크기를 스택 메모리에 랜덤으로 할당하여 스택 포인터의 값이 프로그램 실행때마다 항상 랜덤으로 결정되도록 하여 완벽하지는 않지만 어느 정도는 공격을 무마시키거나 시간을 벌 수 있다.
참고자료
- Computer Systems - A Programmer’s Perspective 2nd edition by Bryant & O’Hallaron