Did you know that you can navigate the posts by swiping left and right?
본 글은 이승원이 집필한 「리버싱 핵심원리」 를 읽고 공부(정리)한 내용을 바탕으로 쓰여졌습니다.
스택 프레임이란 ESP (스택 포인터)가 아닌 EBP (베이스 포인터) 레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법을 말한다.
함수를 호출할 때, 스택이라는 곳에 포인터가 저장이 된다. 스택은 우리가 흔히 알듯 LIFO(Last-In-First-Out) 방식이기 때문에 리턴하고 난 이후의 주소를 어떻게 설정해야할지 에 대한 의문점이 든다. 그것을 해결하는 방법이 바로 스택프레임이다.
다음의 소스코드로 이해해보자.
void function1() {
int first = 0; // 2
function2(); // 3
}
void function2() {
int second = 20; // 4
}
void main() {
function1; // 1
}
아주 간단한 소스코드이다. function1 함수와 function2 함수가 하는 일은 별로 없지만, 스택프레임의 작동 구조와 원리를 이해하기 위해서는 충분한 소스코드라고 생각한다. 위의 코드대로 주석으로 함수가 호출되는 순서를 모두 달아놨다.
위의 순서를 아래의 스택으로 표현해보자.
어셈블리(기계어)의 입장에서 고려해보자. 함수를 호출했을 때는 어셈블리 명령어로 JMP(또는 CALL 일지도..)로 표시될 것이다. 왜냐하면, 함수는 어떤 특정 메모리에 저장되어있을 것이고 그 함수의 시작포인터를 호출하는 것이기 때문이다. 그런데, 다시 돌아오는 것은 항상 RETN 함수이다. 함수가 엄청 여러개 중첩되어 있다면 리턴의 위치가 매우 엉켜버릴텐데 이것을 해결할 수 있는 방법이 스택 프레임이라는 기법이다.
스택 포인터가 한개라면 (만약 ESP 밖에 없다면) ESP는 프로그램 안에서 수시로 변경될 뿐만 아니라 CPU에서 정확한 메모리 주소를 참조하는 것에도 어려움이 있다. 그래서 함수의 리턴 주소를 EBP라는 레지스터에 저장하는 것이다. 그렇다면, ESP의 연산으로 리턴 주소를 알아낼 필요도 없고 (EBP의 값을 그대로 참조하면 되니깐) ESP이 수시로 변동한다고 하더라도 EBP는 유지되기 때문에 스택의 구조자체가 안정해진다.