<리버싱 핵심 원리>로 공부하였다.
07 스택 프레임
목표
- 스택 프레임의 동작 원리를 이해
- 간단한 프로그램을 만들고 디버거를 이용해서 스택 프레임을 확인
- 간단한 어셈블리 명령어의 상세설명
7.1. 스택 프레임 :
ESP가 아닌 EBP를 이용해 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법ESP : 스택 포인터, EBP : 베이스 포인터ESP 값은 수시로 변경되기 때문에 스택에 저장된 변수나 파라미터에 접근하려고 할 때 ESP 값을 기준으로 하면 프로그램을 만들기 힘들고, CPU가 정확한 위치를 참고할 때 어려움이 있다. 이를 보완하기 위해 어떤 기준 시점의 ESP 값을 EBP에 저장하고 유지한다면 ESP가 변하더라도 EBP를 기준으로 안전하게 변수, 파라미터, 복귀 주소에 접근 가능하다.어셈블리 코드로는 다음과 같은 형식으로 이루어져 있다.
PUSH EBP //함수 시작
MOV EBP, ESP //현재의 ESP를 EBP에 저장
... //함수들, 여기서 ESP가 변경되어도 EBP로 안전하게 로컬 변수와 파라미터 액세스 가능
MOV ESP, EBP //ESP를 정리
POP EBP //리턴되기 전에 저장해 놓았던 원래 EBP 값으로 복원
RETN //함수 종료
스택 프레임을 이용해 함수 호출을 관리하면 함수 호출 깊이가 깊고 복잡해져도 스택을 완벽하게 관리할 수 있다.
7.2. 실습 예제 - stackframe.exe
OllyDbg에서 stackframe.exe를 실행한 뒤 401000 주소로 간다.
401020에 BP를 설치한 후 실행한다. 그럼 메인함수의 BP에서 멈출 것이고, 이 시점의 스택 상태는 위와 같다.
ESP : 18FF44, EBP : 18FF88이고, 스택에서 18FF44를 보면 401250으로 복귀 주소가 저장되어있다.
~메인 함수 내부~
401020 : EBP 값을 스택에 집어넣어라. (원래의 EBP 값을 스택에 백업해두기 위한 용도)
401021 : ESP 값을 EBP로 옮겨라. (EBP = ESP, 스택에 저장된 함수 파라미터와 로컬 변수들은 EBP를 통해 접근하겠다!)
==>이 두 줄의 코드로 인해 메인 함수에 대한 스택 프레임이 생성되었다.
우클릭 - Addressing - Relative to EBP를 선택하면 EBP 위치를 스택창에서 확인할 수 있다.
401023 : ESP 값에서 8 빼기(현재의 ESP는 18FF40, 메인 함수의 로컬 변수는 a와 b이다. 이 변수들은 long 타입이므로 각각 4바이트를 가지기 때문에 스택에 저장하기 위해 총 8바이트의 메모리 공간을 만들어야 하기 때문에 8을 빼는 것!)
401026 : LOCAL.1(=local 'a')의 주소에 1을 넣으라는 의미.
40102D : LOCAL.2(=local 'b')의 주소에 2를 넣으라는 의미.
EBP-4와 LOCAL.1 이렇게 다른 형식으로 보이는 것은 버전 차이이다.
관련 정보 링크 : https://ezbeat.tistory.com/249,
https://hackerfordream.tistory.com/entry/Dbg-%EB%B6%84%EC%84%9D
여기까지 실행하면 스택이 다음과 같은 상태가 된다.
스택값으로 1과 2가 쌓인 것을 볼 수 있다.
401034~40103B : add 함수에서 이용될 파라미터 a,b를 위해 변수 a, b를 스택에 넣고 있다.
여기서 중요한 부분은 스택에 함수 파라미터의 역순 저장이 이용된다는 점이다. b->a 순서로!
그럼 위와 같이 스택에 파라미터로 사용될 내용도 저장이 된 것을 확인할 수 있다.
이제 401000으로, 즉 add 함수 내부를 살펴보자.
~add 함수 내부~
401000~401001 : 메인 함수와 동일한 내용(ebp값 스택에 저장 후 ebp에 esp 값 저장)
401003 : 로컬 변수 x, y에 대한 스택 메모리 영역(8byte) 확보
401006~40100F : ARG.1, ARG.2는 각각 파라미터 a, b를, LOCAL.2, LOCAL.1는 각각 add 함수의 로컬 변수 x, y를 의미함.
401012 : 변수 x의 값을 EAX에 넣는다.
401015 : 변수 y의 값을 EAX와 더해서 EAX에 저장한다.
401018 : ESP에 EBP 값을 대입한다. (함수가 종료될 때 ESP를 원래대로 복원시키는 목적으로 사용하는 것.)
40101A : EBP 값 복원
40101B : 스택에 저장된 복귀 주소로 리턴
여기까지 하면 add 함수 호출하기 전 스택 상태로 다시 돌아오는 것을 확인할 수 있다.
이제 다시 메인 함수로 돌아왔다.
~메인 함수 내부~
401041 : ESP에 8 더하기(파라미터가 더 이상 필요 없기 때문에 스택 정리하기 위해)
401044 : add 함수의 리턴 값이 EAX에 들어 있으므로 스택에 쌓는다.
401045~40104F : printf 함수 호출(40104F 주소에서는 스택 속 함수 파라미터 정리)
401052 : MOV EAX, 0과 동일. 보다 실행속도가 빨라서 레지스터를 초기화시킬 때 XOR을 많이 사용함.
401054~401056 : 메인 함수의 스택 프레임 해제.
여기까지 하면 스택이 위와 같은 상황이 된다. 복귀 주소 하나만 남은 상태이다.
401057 : 복귀 주소로 리턴한다. 그리고 프로그램 종료.
이렇게 간단한 프로그램이 진행되면서 스택 구조가 어떻게 변하는지 자세히 알아보았다.
7.3. OllyDbg 옵션 변경
1. Disasm 옵션 : Option Dialog - Disasm - Show default segments/Always show size of memory operands 선택해제
세그먼트 표시와 메모리 크기 표시가 사라진다.(DWORD PTR 같은)
2. Analysis1 옵션 : Option Dialog - Analysis1 - Show ARGs and LOCALs in procedures 항목 체크
EBP로 표시되던 함수의 로컬 변수와 파라미터가 LOCAL.1, ARG.1 형식으로 표시(이 부분은 이미 되어 있었음)