Reversing Study - day 3(스택 프레임)
* 본 문서는 리버싱 핵심 원리책(일명 나뭇잎 책)과 인터넷 자료들을 바탕으로 학습한 기록입니다.
* 개인의 학습을 위한 자료이기 때문에 정확하지 않은 정보가 포함되어 있을 수 있습니다.
스택 프레임
메모리의 스택 영역은 함수 호출 시 지역 변수와 매개 변수가 저장되는 영역으로 함수 호출이 완료되면 함께 소멸한다. 함수가 호출되면 스택에는 함수의 매개변수, 지역변수 반환 주소 값 등이 저장되는데 이렇게 차례대로 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 한다.
- 참고: http://tcpschool.com/c/c_memory_stackframe
나는 스택 프레임을 함수가 호출되었을 때 그 함수가 가지는 스택 공간으로 이해했다.(이것은 개인적인 생각)
esp 레지스터는 베이스 포인터 역할을 하는데 프로그램 안에서 계속 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하기 어렵다. 그렇기 때문에 함수 시작 시 esp 값을 ebp에 저장 후 함수 내에서 유지한다면 ebp를 기준으로 변수, 파라미터, 복귀 주소에 접근이 가능하다.
스택 프레임 구조
push ebp //함수 시작, ebp 사용 전 초기 값을 스택에 저장
mov ebp, esp // esp 값을 ebp에 저장
...
mov esp, ebp //ebp 값을 esp에 저장, esp를 함수 시작 값으로 복원
pop ebp //리턴되기 전 저장해뒀던 원래 ebp 값으로 복원
retn ///함수 종료
실습
글로 된 설명만 읽어서는 이해가 어려우니 직접 리버싱 핵심 원리에 나오는 실습을 따라해보자.
#include<stdio.h>
long add(long a, long b) {
long x = a, y = b;
return (x + y);
}
int main(int argc, char* argv[]) {
long a = 1, b = 2;
printf("%d\n", add(a, b));
return 0;
}
언제나와 같이 main 함수를 찾아 먼저 break point를 걸어준 뒤 시작한다.
먼저 push ebp를 통해 스택 프레임을 생성한다. (ebp 값을 stack에 넣어라!)
- 스택에 들어간 것을 확인!
mov ebp, esp로 esp 값을 ebp에 저장한다.
- ESP와 EBP의 값이 같아진 것을 확인!
이번에는 main() 안의 지역 변수 a,b를 위한 공간을 생성하자.
sub esp, 8로 esp에서 8의 값을 뺀다. 이것은 a와 b가 long 타입이기때문에 각각 4bytes씩 공간을 확보하기 위한 작업이다.
mov dword ptr ss:[ebp-4], 1
mov dword ptr ss:[ebp-8], 2
ebp-4에 1을, ebp-8에 2를 저장한다. 이것은 각각 a와 b 변수에 1,2를 저장하라는 의미다.
- 스택에서 a, b 값이 저장된 것을 확인
a, b에 값을 할당해줬으니 이제 add 함수를 호출할 차례다. 위 사진에서 call stackframe.401000 명령을 볼 수 있다.
4010000을 찾아가보니 스택 프레임 구조를 확인할 수 있었다. 이 부분이 add()다.
여기서 기억할 내용은 파라미터가 C언어의 입력 순서와는 반대로 스택에 저장된다는 부분이다.(함수 파라미터의 역순 저장)
mov eax, dword ptr ss:[ebp-8]
push eax
mov ecx, dword ptr ss:[ebp-4]
push ecx
eax에 b 변수 값 2를 저장하고 stack에 push 해준다. 이후 ecx에 a 변수 값 1을 저장하고 stack에 push 해준다.
- 스택에서 b, a 값이 저장된 것을 확인
이제 call stackframe.401000을 따라 add 함수를 살펴보자.
call 명령어를 실행할 경우 복귀 주소 값(eip, 다음에 실행된 주소 값)을 stack에 저장해둔다.
(이번 실습의 경우 00401041 주소를 저장)
- 스택에서 반환 주소 값 확인
[main() 함수와 같은 부분은 생략]
return (x+y);
이 부분을 살펴보자.
mov eax, dword ptr ss:[ebp-8]
eax에 x변수 값 1을 저장한다.
- 레지스터에서 eax 값 확인
add eax, dword ptr ss:[ebp-4]
eax에 y변수 값 2을 더해준다.
- 레지스터에서 eax 값 확인
mov esp, ebp
pop ebp
ret
esp에 ebp 값을 저장해준 뒤 스택에서 ebp 값을 pop 해준 뒤 return을 해준다. 그 결과 함수 호출 전의 스택 상태로 돌아오게 된다.
이 후 할당된 변수 영역을 회수하고(add esp, 8) printf()함수에 대한 호출을 해준 뒤 또다시 할당된 영역을 회수해준다.
xor eax, eax
mov esp, ebp
pop ebp
ret
xor 연산으로 main() 함수 리턴값(0)을 셋팅해준 뒤 스택프레임을 해제하며 return해준다.