System Hacking

함수 프롤로그(prolog) / 함수 에필로그(eplilog)

jir4vvit 2020. 9. 17. 14:58

jiravvit.tistory.com/entry/dreamhack-pwnable-offbyone000-%ED%92%80%EC%9D%B4

 

[dreamhack : pwnable] off_by_one_000 풀이

 

jiravvit.tistory.com

이 문제 풀이를 작성하다가 한가지 의문이 들었다.

 

EBP의 마지막 한 바이트를 건드리는데 왜 EIP control이 가능한걸까??

이것의 해답을 찾기 위해 함수 에필로그를 찾아보았고, 덤으로 프롤로그도 찾아보았다.

 

 

아 참, 함수 에필로그와 프롤로그의 개념을 알기 전에 스택프레임이란 것을 알아야 한다.

jiravvit.tistory.com/entry/%EC%8A%A4%ED%83%9D-%ED%94%84%EB%A0%88%EC%9E%84-Stack-Frame

 

스택 프레임 (Stack Frame)

출처 : 동빈나님 유튜브 youtu.be/TxWOaKE5w_s 유튜브를 보며 공부하던 중 두고두고 복습이 필요할 것 같아 정리를 하려고 한다.. 스택 프레임(Stack Frame)을 이해하기 위해서 간단한 예제를 만들어보겠�

jiravvit.tistory.com

한 마디로,, 스택프레임이란 함수 자기 자신만이 갖는 공간이다.


함수 프롤로그(prolog)

push ebp
mov ebp, esp

함수 프롤로그란 함수가 호출되면 그 함수의 영역을 설정하기 위한 것으로 위의 명령어로 이루어져 있다. 

 

처음에 ebp라는 것을 push해준다. 이는 스택이 여기서부터 쌓일 것이라는 것을 알려주는 베이스 포인터이다. 여기 위에 버퍼가 자리잡게 된다. 이 ebp를 기준으로 ret나 여러 지역변수들의 위치를 가늠할 수 있다. 

 

근데 ebp의 값은 뭐지???? 뭔데...? 뭘 push 해 줘????

바로 현재 지금 자신을 호출한 이전 함수의 ebp 주소이다. 이 주소가 스택에 가장 먼저 쌓이게 된다.

 

ebp를 스택 가장 아래 넣어주면 이제 esp는 ebp와 동일한 위치를 가리키게 된다.  현재 이 함수의 ebp를 설정해주는 것이다. 

 

그림으로 표현하면 이런느낌이랄까..? 이제 변수가 들어온다면 ESP가 -되어 스택이 커지게 될 것이다.

 

이것이 바로 함수의 스택프레임을 형성하는 그 시작이고, 함수 프롤로그라고 한다.


함수 에필로그(eplilog)

(dreamhack off_by_one_000 문제를 풀기위한 핵심이다)

함수 에필로그는 아래와 같은 두 개의 명령으로 이루어져 있다. 

leave
ret

 

그리고 내부적으로 각각 아래와 같은 명령어를 수행한다.

leave

mov esp, ebp
pop ebp

ret

pop eip
jmp eip

 

함수 에필로그는 현재 지금 자신이 종료되어 나를 호출한 함수로 돌아갈 때 스택을 정리하는 과정이다. 

 

이를 위해 pop 명령에 대해 더 자세히 알아야 한다.

pop 명령어는 현재 esp가 있는 곳에서 4byte를 복사하여 피연산자에 담고 esp + 4 를 한다.

 

 

mov esp, ebp
pop ebp
pop eip
jmp eip

위에서 부터 차근차근 보자.

 

처음에 ebp를 esp로 복사하여 결국 둘이 같아진다.

그리고 ebp의 값을 pop하게 된다. 풀어서 쓰자면 현재 esp가 있는 곳에서 4byte를 복사하여 ebp에 담는다.

현재 esp가 있는 곳이 어디일까? 바로 SFP, 즉 이전함수의 ebp이다. 나를 호출한 함수의 ebp 말이다.

그리고 esp + 4 하게 된다. 아래로 내려간다(?)는 뜻

 

한편, SFP 아래는 RET이 존재하게 된다. 그럼 esp는 현재 어디일까?

맞다. RET이다. (아래 그림 참고)

 

 

여기서 pop eip 명령을 수행하게 되면 esp가 있는 곳에서 4byte를 복사해 eip에 담게 된다. 더 풀어서 쓰자면 eip에는 RET에 담긴 스택 주소가 들어가게 된다.

그리고 esp + 4를 한다.

 

마지막으로 jmp eip 명령을 수행하여 eip에 저장된 주소, 즉 리턴어드레스(RET)로 이동하며 함수 에필로그가 진행된다.


jiravvit.tistory.com/entry/dreamhack-pwnable-offbyone000-%ED%92%80%EC%9D%B4

 

[dreamhack : pwnable] off_by_one_000 풀이

 

jiravvit.tistory.com

여기서 이 문제를 다시 생각해보자.

 

그래서... EBP의 마지막 한 바이트를 건드리는데 왜 EIP control이 가능한걸까??

 

함수 에필로그(leave와 ret)가 두번 일어나는 것에 주의하자.

 

main에서 cpy 함수를 호출하고 cpy함수가 종료될 때 함수 에필로그 한번, 에필로그 후 main으로 복귀하여 main이 종료되면서 함수 에필로그 한번.. 총 두 번 일어난다.

 

입력할 때 256바이트를 꽉꽉 채워서 입력하면 cpy 함수 내의 strcpy 함수 덕분에(?) ebp의 가장 마지막 한 바이트가 NULL 바이트로 채워지게 된다. 여기서 ebp 변조가 일어나고, 메모리 주소는 낮아져서 우리가 입력한 영역으로 저장되게 된다.

 

cpy 함수 에필로그

cpy함수가 종료되면서 우리가 입력한 영역이 그대로 ebp 레지스터에 들어가게 된다.

 

 

그리고 이 상태에서 main이 함수 에필로그 를 할 때 문제가 발생한다.

leave가 호출되는 경우..

 

mov esp, 변조된 ebp
pop 변조된 ebp

esp는 바뀌게 된다. 바로 우리가 입력한 영역으로 가게 된다. pop을 할 떄는 esp를 기준으로 한다.

 

pop eip
jmp eip

esp는 현재 우리가 입력한 영역에 머물러 있고... 우리가 입력한 영역은 256바이트이기 때문에 +4를 해봤자 계속 우리가 입력한 영역 안이다.

 

만약 우리가 입력한 영역을 get_shell 주소로 도배했다면?? 

eip에도 get_shell 주소가 들어가게 된다.

 

 

 

 

 

 

자 이래서 EBP의 마지막 한 바이트를 NULL로 overwrite했을 뿐인데 EIP control이 가능한 것이다.

 

 

 

 

 

후.. 

그냥 넘어갈 뻔 했는데... 스승님 감사합니다;;ㅠㅠ