공부해요/리버싱_핵심원리

7장. 스택 프레임

yenas0 2024. 9. 18. 01:12
반응형

목표

- 스택 프레임의 동작 원리 이해

- 간단한 프로그램을 만들고 디버거를 이용해서 스택 프레임 확인

- 간단한 어셈블리 명령어의 상세 설명

 

스택 프레임

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 

 

GitHub - reversecore/book: 리버싱 핵심원리 - 소스 코드 및 실습 예제

리버싱 핵심원리 - 소스 코드 및 실습 예제. Contribute to reversecore/book development by creating an account on GitHub.

github.com

 

 

 

이걸로 실습 진행

 

[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