리버싱 엔지니어링/리버싱 핵심 원리
7 스택 프레임
흰싸라기
2022. 3. 13. 23:36
(흐름상 5->6보다 5->7이 이해에 좋은 것 같아 업로드 순서 수정)
7.1 스택 프레임
- ESP(스택 포인터)가 아닌 EBP(베이스 포인터) 레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법
- ESP 값은 프로그램 내에서 수시로 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하기 어렵고 CPU가 정확한 위치를 참고하기 어렵다
- ESP 값을 EBP에 저장하고 함수 내에서 유지하면, 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있다.
- 스택 프레임의 구조
PUSH EBP ; 함수 시작(기존의 값을 스택에 저장)
MOV EBP, ESP ; 현재의 ESP를 EBP에 저장
...
MOV ESP, EBP ; ESP 정리(함수 시작했을 때 값으로 복원)
POP EBP ; 저장해 놓았던 원래 EBP 값으로 복원
RETN ; 함수 종료
- 최신 컴파일러는 최적화 옵션을 가지고 있어 간단한 함수의 경우 스택 프레임을 생성하지 않음
7.2 stackframe.exe
7.2.1 StackFrame.cpp
#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;
}
7.2.2 main() 함수 시작 & 스택 프레임 생성
int main(int argc, char* argv[])
{
1) main() 함수 살펴보기 (main 함수 찾기 : 2장 참고)
- main() 함수에 BP 설치 후 실행
2) 함수 시작 시 스택 상태
- ESP = 19FF2C, EBP =19FF70
- ESP에 저장된 값 4012A8은 main() 함수의 실행이 끝난 후 돌아갈 리턴 주소
3) PUSH EBP
- 의미 : EBP 값을 스택에 집어 넣어라
- 이전에 가지고 있던 값을 스택에 백업해두기 위한 용도
4) MOV EBP,ESP
- 의미 : ESP의 값을 EBP로 옮겨라
- 명령 후 EBP는 현재 ESP와 같은 값을 가지게 됨
- main() 함수가 종료될 때까지는 EBP 값 고정됨
5) EBP 위치 확인 (스택 창 마우스 우측 - Address - Relative to EBP)
- 현재 EBP, ESP 값이 동일함
- 19FF28 주소에는 19FF70(EBP 초기 값)이라는 값이 저장되어있음
7.2.3 로컬 변수 세팅
long a = 1, b = 2;
1) 메모리 공간 확보
- 의미 : ESP에서 8을 빼라
- 8을 빼는 이유? ‘a’와 ‘b’는 long 타입이므로 각각 4바이트 크기를 가짐. 총 8바이트의 메모리 공간을 확보하기 위해
2) 값 저장
- 의미 : [EBP-8]에는 1을 넣고, [EBP-4]에는 2를 넣어라
- 실행 후 스택 상태
7.2.4 add() 함수 파라미터 입력 및 add() 함수 호출
printf("%d\\n", add(a, b));
1) 함수 호출 과정
- [EBP-4] = b, [EBP-8] = a
- 입력 순서와 반대로 스택에 저장됨
- 변수 b가 먼저 들어가고 변수 a가 나중에 들어감
2) add() 함수 복귀주소
- 함수로 들어가기 전 CPU는 무조건 해당 함수가 종료될 때 복귀할 주소(return address)를 스택에 저장해야함
- 4010BC 주소의 CALL 명령어 실행 후 스택
- 4010C1은 add() 함수의 복귀 주소
- 4010C1은 add() 함수를 호출한 4010BC 다음 명령어
7.2.5 add() 함수 시작 & 스택 프레임 생성
long add(long a, long b)
{
1) 스택 프레임 생성
- add() 함수가 시작되면 자신만의 스택프레임 생성
- main() 함수와 동일한 코드 (7.2.2)
- 원래 EBP 값(main() 함수의 base pointer)을 저장하고 현재의 ESP 값을 EBP에 입력
7.2.6 add() 함수의 로컬 변수(x, y) 세팅
long x = a, y = b;
- 로컬 변수 x, y에 파라미터 a, b 대입
1) 로컬 변수 x, y 스택 메모리 영역(8바이트) 확보
2) 로컬 변수에 파라미터 값 대입
- 스택
7.2.7 ADD 연산
return (x + y);
1) add 연산
- 401092 명령 : 변수 x 값을 EAX에 넣음
- 401095 : EAX에 변수 y값을 더함
- ADD는 덧셈 연산 명령어
- 함수가 리턴하기 직전에 EAX에 넣은 값은 그대로 리턴 값이 된다
7.2.8 add() 함수의 스택 프레임 해제 & 함수 종료(리턴)
return (x + y);
}
1) 스택 프레임 해제 및 복원
- EBP 값을 ESP에 대입
- add() 함수 시작할 때의 ESP값을 EBP에 넣어두었다가 함수가 종료될 때 ESP를 원래대로 복원
- 스택
- 4010C1은 add() 함수의 복귀 주소
2) 복귀 주소로 리턴
- RETN 명령어 실행 시 스택에 저장된 복귀 주소로 리턴
- 스택 : (7.2.4 파라미터 입력 후와 동일)
7.2.9 add() 함수 파라미터 제거(스택 정리)
- ESP에 8을 더하는 이유?
- 함수에게 넘겨준 파라미터 a, b를 제거하기 위해
- 스택
7.2.10 printf() 함수 호출
printf("%d\\n", add(a, b));
1) 프린트 함수 호출 코드
- EAX에는 add() 함수 리턴 값이 저장되어 있음
- printf() 함수의 파라미터는 8비트, 따라서 ADD ESP, 8 명령으로 함수 파라미터 정리
7.2.11 리턴 값 세팅
return 0;
- XOR : 같은 값끼리 XOR하면 0됨
- MOV EAX, 0 명령어보다 실행 속도가 빨라 사용함
7.2.12 스택 프레임 해제 & main() 함수 종료
return 0;
}
- add() 함수와 마찬가지로 스택프레임 해제 후 리턴 (7.2.8)