2018년 3월 20일 화요일

함수 호출시 Stack 구조 (EBP,ESP,EIP)

2010. 8. 3. 12:53



함수 호출시 Stack 구조 (EBP,ESP,EIP)


프로그램의 진행은 stack pointer의 이동이다.

l  ESP : 함수가 진행하고 있을 때  stack의 제일 아래 부분,현재 진행 stack 지점, Stack Pointer
          stack 메모리는 아래로 성장하기 때문에 제일 아래가 제일 마지막이 된다.
l  EBP : 스택의 가장 윗 부분(기준점), Base Pointer
l  EIP : 실행할 명령의 주소, Instruction Pointer
l  E가 붙는 것은 16비트에서 32비트 시스템으로 오면서 Extended 된 개념, 64비트에서는 R이 붙음
l  32bit 레지스터의 기본 설명은 http://www.reversecore.com/tag/EIP 참고
l  Context Switching :  Multi-Tasking상황에서 현재 작업을 진행하고 있는 CPU의 각 register들의 정보가 저장되고 이전 것이 다시 불려지면서 교환되는 상황.

아래 코드를 보자(code sample A-1)

char szTmp1[10];
char szTmp2[10];

sprintf(szTmp2,"%s","123456789abcdefg");
printf("%s\n",szTmp2);
printf("%s\n",szTmp1);

위 코드는 지정된 지역변수 공간을 침해 했음에도 AV에서 오류로 검출되지 못한다. AV에서는 Heap할당 영역의 침범 경우에만 검출을 하기 때문에(디버그 모드에서 heap일 경우 메모리 가드가 있어 검출 가능) 위 경우는 실제 운영상에 매우 곤란한 문제를 야기하는 경우가 많이 있다.

위 코드의 결과는 아래와 같다.
123456789abcdefg
defg

★ 왜 이런 결과가 발생한 것 일까 ?


 
위 상황을 이해하려면 함수 호출시 스택의 구조에 대해 이해를 해야 한다.


stack minus 방향
으로 진행
두 번째 함수 인자
ebp + 12
ebp + 8
ebp + 4
ebp
ebp – 4
ebp – 8
첫 번째 함수 인자
함수 복귀 주소 (복귀할 EIP저장)
Base Pointer 저장 (이전 EBP저장)
첫 번째 지역 변수
두 번째 지역 변수

n  함수인자와 지역변수는 시스템의 int 크기만큼 사이즈가 결정된다.

아래와 같은 코드가 있을 때 이것을 역어셈블 해 보자.
void test()
{
    int a;
    a=10;
    printf("a=%d" , a);
}

아래 코드를 보자 (VS 6.0의 debug 모드에서 본 내용, ALT+8 하면 disassembly mode 전환)
5:    void test()
6:    {
EIP                                     # ESP = 0013FF2C EBP = 0013FF80
00401010   push        ebp             # 현재의 BP(이전 함수의 BP) 저장해 두고
함수 종료 후 스택 상태를 되돌리기 위해 )
                                         # ESP = 0013FF28 EBP = 0013FF80 , ESP 4 bytes 감소 ( 아래로 진행 )
00401011   mov         ebp,esp        # 이 함수의 SP BP에 넣으면서 , 함수의 BP를 확보
                                         # ESP = 0013FF28 EBP = 0013FF28
00401013   sub         esp,44h         # 지역 변수 또는 기타(?) 여유 공간 확보(debug일 때만),
stack는 아래로 성장그래서 sub를 한다.
                                         # ESP = 0013FEE4 EBP = 0013FF28
00401016   push        ebx
                                         # ESP = 0013FEE0 EBP = 0013FF28
00401017   push        esi
                                         # ESP = 0013FEDC EBP = 0013FF28
00401018   push        edi
                                         # ESP = 0013FED8 EBP = 0013FF28
00401019   lea         edi,[ebp-44h]      # ebp-44h를 수행 뒤 그 값을 edi에 옮긴다
                                                      # ebp-44h의 주소를 edi에 저장한다. 
                                         # ESP = 0013FED8 EBP = 0013FF28
0040101C   mov       ecx,11h
                                         # ESP = 0013FED8 EBP = 0013FF28
00401021   mov         eax,0CCCCCCCCh  # eax에는 일반적으로 리턴값 저장,
                                         # ESP = 0013FED8 EBP = 0013FF28
00401026   rep stos    dword ptr [edi]
# 44h = 68, 11h = 17  -> 4 * 17 = 68
# edi 주소부터 11h번 동안 4바이트씩 eax 값을 넣는다# debug mode에서 stack이 침범되었는지 확인을 위해 ? ( 저도 공부가 좀 필요 ^^ )
7:        int a;
8:
9:        a=10;
00401028   mov         dword ptr [ebp-4],0Ah
10:
11:       printf("a=%d",a);
0040102F   mov         eax,dword ptr [ebp-4]
00401032   push        eax
00401033   push        offset string "a=%d" (00422f78)

이 상태에서 printf 함수를 호출하면 현재 esp-4 printf ebp가 되기 때문에 첫번째 파라메터는 ebp+8이 된다. ( ebp+4는 복귀주소가 들어 가기 때문에 )
00401038   call         printf (0040dd70)  # call할때 자동으로 복귀 주소 push하고 printf 함수호출
0040103D   add         esp,8            # push를 두번해서 뒤로 간 스택을 다시 앞으로
12:   }
00401040   pop         edi
00401041   pop         esi
00401042   pop         ebx
00401043   add         esp,44h          # SP를 다시 원래 위치로 돌린다.
                                          # ESP = 0013FF28 EBP = 0013FF28
00401046   cmp         ebp,esp
00401048   call        __chkesp (004013d0)
0040104D   mov         esp,ebp
0040104F   pop         ebp
                                          # ESP = 0013FF2C EBP = 0013FF80
00401050   ret


이상의 내용을 검토했을 때 이전의 예제 (code sample A-1) 코드에서
123456789abcdefg
defg
결과가 나온 이유는 명백해 진다.


 
l  그런데 왜 bcdefg가 아니고 defg 일까 ?
아래 코드는 WinDbg에서 역어셈블한 내용이다.
debug 파일이 아니고 release파일을 역어셈블 했다.

0:000> u 004123a0 004123e2
Timetest!errTestMemViolation2 [G:\byun\test_code\100716_umdh_test\Timetest.cpp @ 72]:
004123a0 55              push    ebp
004123a1 8bec            mov     ebp,esp
004123a3 83ec18          sub     esp,18h # 14h(20)이 아니고 18h (24)
004123a6 68c0154200      push    offset Timetest!`string' (004215c0)
004123ab 8d45f4          lea     eax,[ebp-0Ch]
004123ae 6898154200      push    offset Timetest!`string' (00421598)
004123b3 50              push    eax
004123b4 e8370c0000      call    Timetest!sprintf (00412ff0)
004123b9 83c40c          add     esp,0Ch
004123bc 8d4df4          lea     ecx,[ebp-0Ch]
004123bf 51              push    ecx
004123c0 68bc154200      push    offset Timetest!`string' (004215bc)
004123c5 e864180000      call    Timetest!printf (00413c2e)
004123ca 83c408          add     esp,8
004123cd 8d55e8          lea     edx,[ebp-18h]
004123d0 52              push    edx
004123d1 68bc154200      push    offset Timetest!`string' (004215bc)
004123d6 e853180000      call    Timetest!printf (00413c2e)
004123db 83c408          add     esp,8
004123de cc              int     3
004123df 8be5            mov     esp,ebp
004123e1 5d              pop     ebp

sprintf(szTmp2,"%s","123456789abcdefg"); 일 때 0Ch  12번째 자리부터 값을 넣는 것을 알 수 있다.

그런데 이상한 점이 있다.
각자 찾아 보기 바란다 !

댓글 없음:

댓글 쓰기