[Stack] Frame Pointer Overflow, FPO에 대하여

두비니

·

2020. 7. 30. 09:40

 

 

 

 

 

 

오늘은 FPO, Frame Pointer Overflow에 대해서 알아볼 것입니다. SFP Overflow라고도 불립니다.

FPO에 대한 풀이 글들을 보면 계속 leave가 ebp+4에 접근할 수 있다, sfp를 조작했으니 ebp를 조작할 수 있다이런 말들이 되게 많은데 이걸 단계별로 제대로 이해하지 못하고 넘어가면 이게 뭔 개소리지??? 하다가 포기하게 되니까 천천히 이해해봅시다.

 

 

일단 FPO가 뭔지 알아봅시다.

 

일단 FPO를 이해하기 위해서는 기본적인 stack구조가 어떻게 굴러가는지 알아야하며, 더불어 stack bufferoverflow가 어떻게 일어나는지 배경지식으로 알고 있어야 합니다. 그에 대해서 잘 모른다면 먼저 stack bufferoverflow에 대해서 공부를 해보고, 이 글은 bufferoverflow까지는 기본적으로 안다는 전제 하에 진행하겠습니다.

 

기존의 단순한 stack buffer overflow는 ret의 주소를 변경해 내가 원하는 주소에 접근하는 방법이였다면 Frame Pointer Overflow는 ret위의 sfp를 변경시켜 결론적으로 IP(Instruction Pointer), 즉 eip 레지스터를 변경시키는 기법입니다. 1바이트만으로도 공격이 가능하기 때문에 매우 강력한 공격기법이고, 함수의 에필로그때문에 이 공격은 가능해집니다.

 

 

아아 잠깐 아직 포기하지 마십쇼

자 무슨말인지 와닿지 않을거 아니깐 일단 밑에서 차근차근 설명할테니 조금만 더 읽어보세요.

 

 

그리고 FPO의 발생 조건에 대해서 알아봅시다.

 

1. SFP영역에서 최소 1바이트의 overflow발생

2. 메인 함수 이외의 서브 함수가 필요

 

 

일단 마음에 와닿지 않아도 그냥 그렇구나 하고 넘어갑시다. 이제 하나하나 자세히 설명해볼거에요.

 

 

일단 FPO의 발생 조건이 메인 함수 이외의 서브 함수가 필요하다고 했죠? 서브함수가 있을 때 stack상황을 먼저 봅시다.

다음은 main함수 이외에 서브함수가 있는 코드입니다.

 

     1  /*
     2          The Lord of the BOF : The Fellowship of the BOF
     3          - darkknight
     4          - FPO
     5  */

     6  #include <stdio.h>
     7  #include <stdlib.h>

     8  void problem_child(char *src)
     9  {
    10          char buffer[40];
    11          strncpy(buffer, src, 41);
    12          printf("%s\n", buffer);
    13  }

    14  main(int argc, char *argv[])
    15  {
    16          if(argc<2){
    17                  printf("argv error\n");
    18                  exit(0);
    19          }

    20          problem_child(argv[1]);
    21  }

 

코드는 LOB golem >> darkknight의 코드를 가져왔습니다.

20번 줄에서 problem_child로 넘어간 뒤, 12번 줄에서 printf까지 실행한 후 다시 main함수로 돌아오기 전 상황에 breakpoint를 걸었다고 가정하고 그 때의 stack상황을 봅시다.

 

 

정말 대략적으로 그린 상황입니다. 굳이 따지자면 problem_child() 함수는 argv[1]을 매개변수로 받고있기 때문에 ret밑에 argv[1]의 주소가 매개변수 *src로 들어가있겠죠.

근데 아무튼 이 그림은 그냥 서브함수가 어떻게 쌓이는지 보여주기 위함입니다. 

이런식으로 서브함수가 스택에 쌓이면, 이 함수가 할 일을 다 했을 때 원래 진행중이던 main함수의 주소로 돌아와야만 프로그램이 정상적으로 진행될 수 있습니다.(당연한 얘기지만,,) 이걸 저장하는 위치가 바로 sfp, Stack Frame Pointer입니다. (참고로 Saved Frame Pointer라고도 합니다. 둘다 쓰입니다.) 그리고 problem_child()함수의 ret는 main함수로 돌아간 후 실행할 main코드의 주소이구요.

 

 

일단 여기까지 읽었을 때 알고있어야 하는 점

 

1. stack에서 main함수 이외의 서브함수가 있을 경우 main함수의 스택 위에 쌓이고, 그 후 서브함수를 실행한다.

2. 서브함수가 실행된 후 main함수에 돌아와야하는데 그 "돌아가야하는 주소"sfp가 기록하고 있다.

 

 

 

 

자, 그럼 다음으로 넘어가도록 하겠습니다.

이제 함수의 에필로그라는걸 볼 필요가 있습니다. 일단 이것도 설명한 링크를 첨부하는데, 아직 에필로그가 정상적으로 진행될 때에 대해서 잘 모르는 분들은 글을 한번씩 읽고 옵시다.

 

함수의 에필로그: https://dokhakdubini.tistory.com/227?category=809542

 

[stack] 함수의 에필로그(epilogue)

함수의 에필로그(epilogue)에 대하여 오늘은 함수의 에필로그라는걸 배울 것입니다. 보통 함수의 프롤로그와 에필로그를 묶어서 설명하는데, 모든 함수의 처음(프롤로그)과 끝(에필로그)에 공통��

dokhakdubini.tistory.com

 

결론적으로 함수의 에필로그의 핵심은 다음과 같습니다.


Function Epilog


leave
ret

 


Internal of "Leave"


move esp, ebp
pop ebp

 


Internal of "Ret"


pop eip
jmp eip

 

 

위에 첨부한 함수의 에필로그를 참고하여 FPO취약점을 이용해 공격하는 과정을 그려보도록 하겠습니다.

일단 이해가 안되더라도 따라서 읽어보고, 결과를 본 뒤 왜 해당하는 부분에 그 값을 넣었는지 한번 더 봅시다.

다음은 공격할 때의 stack상황이니 위에 링크로 첨부한 글의 정상적인 함수 에필로그와도 비교하면 더 쉽게 이해할 수 있으니 비교하면서 보시길 바랍니다.

 

 

일단 주소는 이해를 위해 임의적으로 삽입했습니다. 코드를 보면 strncpy로 40바이트인 buffer에 41바이트까지 overflow가 가능하기때문에 sfp의 마지막 바이트를 \xa0로 바꾼걸 볼 수 있습니다. 일단 "왜 \xa4도 아니도 굳이 \xa0이냐" 대해서는 일단 넘어갑시다.

그리고 이 상태에서 leave의 mov esp, ebp가 실행됩니다.

 

+) \xbf의 값이 바뀌어야 하는거 아닙니까?

주소는 리틀엔디안 방식으로 입력되기때문에 주소의 마지막바이트가 overwrite됩니다.

 

++) 작성하고보니깐 오해의 소지가 있을 수 있어서 써놓는데, 스택 부분이 회색이라고해서 값이 초기화된다거나 날라가지 않습니다. 그냥 이 상황에서 따로 볼 필요가 없어서 회색으로 표시한 것 뿐입니다. 오해하지 마시길.

 

 

다음은 pop ebp입니다. pop ebp를 하면 stack에서 가장 위에있는 값이 ebp안으로 들어가기 때문에 sfp의 값이 ebp로 들어가게 됩니다. 그리고 pop했으니 esp는 ret의 주소를 가리키고 있겠죠?

원래는 sfp에는 서브함수가 실행되기 전 main의 ebp주소가 입력되어있지만, buffer overflow로 인해 sfp의 마지막 바이트를 조정해 0xbffffaa0로 바꾸어주었기 때문에 main함수의 stack으로 돌아가는게 아니라 0xbffffaa0로 ebp가 바뀌게 됩니다.

 

 

다음은 ret과정입니다. ret안에 있던 값은 변하지 않았기 때문에 main함수로 돌아갑니다. ret안에 있는 주소값은 임의로 설정한 값으로, main함수에서의 problem_child()가 실행된 후 그다음 instruction인것만 알면 됩니다. 이 이후에는 eip에 main함수 관련 instruction들이 있을테니 main함수의 명령어들을 실행하겠죠? 이건 생략하겠습니다.

 

 

 

 

다음 상황은 main함수의 나머지 명령어들을 모두 실행한 뒤 함수 에필로그가 실행되는 상황입니다. 여기서부터 중요하니 잘 보세요.

 

 

leave의 mov esp, ebp가 실행 된 상황입니다. ebp의 값이 복사되면서, esp도 같이 0xbffffaa0로 올라가버립니다. esp의 값은 어차피 ebp의 값으로 바뀌기때문에 이전에 esp가 어디있었는지는 중요하지 않습니다.

 

leave의 다음 명령어인 pop ebp입니다. pop ebp를 수행하면서 ebp에는 0xbffffaa0의 주소 안에 있던 값이 ebp안으로 들어가게 됩니다. 주소 0xbffffaa0안의 값은 따로 설정 안해놨으니 그냥 그림 상에서는 없앴습니다. 그리고 당연히 pop을 했으니 esp는 이제 buffer의 시작주소인 0xbffffaa4를 가리키게 됩니다. 왜 buffer의 시작주소에서 -4를 한 주소를 넣었는지 이해가 가나요?

 

 

자, 그럼 pop eip와 jmp eip를 하는 ret의 차례인데, 결론적으로 esp의 주소 안에 있는 값으로 접근하게 됩니다. (eip에 pop한 뒤 jmp할거니깐)

그러면 권한을 얻으려면 0xbffffaa4라는 주소 안에 shellcode의 주소가 있으면 권한을 얻을 수 있겠죠?

 

 

이게 FPO의 큰 흐름입니다. 

글의 맨 처음에 설명했던 FPO의 발생조건 2가지가 이해가 가시나요?

 

1. SFP영역에서 최소 1바이트의 overflow발생        << SFP Overflow가 필요하기 때문   

2. 메인 함수 이외의 서브 함수가 필요                 << Leave/Ret이 2번필요하기 때문

 

 

FPO뿐만아니라 대부분의 해킹기법은 이렇게 구조를 하나하나 꿰뚫고 있어야 이해할 수 있습니다. 처음하면 당연히 어려운 것이니, 조금만 참고 여러번 읽어보시길 바랍니다.

가장 중요한건 이 단계에서 "주소"에 대한 이야기를 하는건지, "값"에 대한 이야기를 하는건지 잘 구분해야합니다. 저도 처음 공부할때 이거때문에 한참 헤맸구요.

 

FPO는 괜찮은 기법인데 문제가 별로 없어서 그런지 참고할만한 글도 그닥 없네요. 

그나마 이걸 활용한 대표적인 문제는 이미 본문에서도 예시로 쓴 LOB의 12단계 golem이 있습니다. 이왕 글을 읽은 겸 LOB도 풀어봅시다.

 

참고로 이와 비슷한 개념인 fake ebp라는 기법도 있지만, 이건 다음에 알아보도록 합시다.

긴 글 읽느라 수고하셨습니다. 끝!

 

진짜 열심히 썼다.. 불태웠어

 

 

::참고::

 

https://bob3rdnewbie.tistory.com/188

https://idkwim.tistory.com/48

https://d4m0n.tistory.com/76