목표
- 스택 프레임의 동작 원리 이해
- 간단한 프로그램을 만들고 디버거를 이용해서 스택 프레임 확인
- 간단한 어셈블리 명령어의 상세 설명
스택 프레임
EBP(베이스 포인터) 레지스터 이용해서 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법
ESP 레지스터 값은 프로그램 안에서 수시로 변경됨 -> 파라미터 접근하려할 때 ESP 기준으로 하면 프로그램 만들기 힘듦 -> EBP 기주능로 하면 안전하게 함수의 변수, 파라미터, 복귀 주소에 접근 가능
스택 프레임의 구조
PUSH EBP | 함수 시작(EBP를 사용하기 전에 기존의 값을 스택의 저장) |
MOV EBP, ESP | 현재의 ESP(스택포인터)를 EBP에 저장 |
... | 함수 본체 여기서 ESP가 변경되더라고 EBP가 변경되지 않으므로 안전하게 로컬 변수와 파라미터를 액세스할 수 있음 |
MOV ESP, EBP | ESP를 정리(함수 시작했을 때의 값으로 복원) |
POP EBP | 리턴되기 전에 저장해 놓았던 원래 EBP 값으로 복원 |
RETN | 함수 종료 |
실습 예제 - stackframe.exe
https://github.com/reversecore/book
이걸로 실습 진행
[Ctrl + G]로 해당 주소로 이동
main()함수 시작 & 스택 프레임 생성
main에 BP[F2]설치 후 실행[F9]
스택값 확인 가능
main() 함수는 시작하자마자 스택 프레임 생성
= EBP 값을 스택에 넣어라
MOV 명령은 데이터 옮기는 명령
= ESP 값을 EBP로 옮겨라
= EBP는 현재 ESP와 같은 값을 가지게 됨, main()함수 끝날 시 까지 EBP 값은 고정
= 스택에 저장된 함수 파라미터와 로컬 변수들은 EBP를 통해 접근한다.
로컬 변수 세팅
long a = 1, b =2;
스택에 main() 함수의 로컬 변수(a, b)를 위한 공간 할당 후, 변수 초기화
= ESP 값에서 8을 마이너스
왜 8을 뺄까?
: 위에 두 변수를 저장하기 위해서는 각각 4바이트씩 총 8바이트가 필요하기 때문
DWORD PTR SS:[EBP-4]
어셈블리 언어 | C 언어 | Type casting |
DWORD PTR SS:[EBP-4] | *(DWORD*)(EBP-4) | DWORD (4 바이트) |
WORD PTR SS:[EBP-4] | *(WORD*)(EBP-4) | WORD (2 바이트) |
BYTE PTR SS:[EBP-4] | *(BYTE*)(EBP-4) | BYTE |
= [EBP-4]에는 1, [EBP-8]에는 2
add()함수 파라미터 입력 및 add()함수 호출
printf("%d\n", add(a, b));
40103C 주소에서 401000함수 호출 = add()함수
복귀 주소
CALL 명령어 실행 후 함수 들어가기 전에 CPU는 해당함수 종료될 때 복귀할 주소(return address)를 스택에 저장함.
40103C 주소에서 add() 함수를 호출
add()함수 실행 완료 시 다음 주소인 401041로 돌아옴
EBP값을 스택에 저장 후 현재의 ESP를 EBP에 입력함 = add() 함수의 스택 프레임 생성
add() 함수의 로컬 변수(x, y)세팅
long x = a, y = b;
로컬 변수 x, y에 대한 스택 메모리 영역(8바이트) 확보
add()함수에서 새롭게 스택 프레임 생성 및 EBP 값 변화
[EBP+8] == a
[EBP+C] == b
[EBP-8] == x
[EBP-4] == y
ADD연산
return (x + y);
[EBP-8] = 1을 EAX에 넣음
[EBP-4] = 2의 값을 EAX와 더함
EAX는 3
스택 변화 없음
add() 함수의 스택 프레임 해제 & 함수 종료(리턴)
현재의 EBP값을 ESP에 대입
add()함수 시작할 때의 ESP값(12FF28)을 EBP에 넣어 두었다가 함수가 종료될 때 ESP를 원래대로 복원시키는 목적으로 사용
add() 함수가 시작되면서 스택에 백업한 EBP 값을 복원
앞에서 실행된 401000 주소의 PUSH EBP 명령에 대응
복원된 EBP 값은 12FF40, 이 값은 main() 함수의 EBP값.
add()함수의 스택 프레임은 해제
add()함수 호출 전 상태로 스택 상태 돌림
add() 함수 파라미터 제거(스택 정리)
main()함수 코드로 돌아와 'ADD' 명령으로 ESP에 8 더함 = a, b 필요가 없기 때문에 스택 정리
printf()함수 호출
printf("%d\n", add(a, b));
- 00401044 | 50 | PUSH EAX
- PUSH 명령어는 EAX 레지스터의 값을 스택에 저장
- 00401045 | 68 84B34000 | PUSH 0040B384
- 이 PUSH 명령어는 즉시 값(0x0040B384)을 스택에 저장
- 0040104A | 6A 10 | PUSH 10
- PUSH 명령어가 이번엔 즉시 값 0x10 (10진수로 16)을 스택에 저장
- 0040104C | E8 18000000 | CALL 00401067
- CALL 명령어는 서브루틴(함수)인 주소 0x00401067을 호출
- 00401051 | 83C4 08 | ADD ESP, 8
- ADD ESP, 8 명령어는 스택 포인터인 ESP의 값을 8만큼 증가시켜 스택을 정리
리턴 값 세팅
return 0;
스택 프레임 해제 & main() 함수 종료
return 0;
}
'공부해요 > 리버싱_핵심원리' 카테고리의 다른 글
9장. Process Explorer - 최고의 작업 관리자 (0) | 2024.09.22 |
---|---|
8장. abex' crackme #2 (0) | 2024.09.18 |
5장. 스택 (0) | 2024.09.17 |
4장. IA-32 Register 기본 설명 (0) | 2024.09.11 |
3장. 리틀 엔디언 표기법 (0) | 2024.09.11 |