Stack Frame

study 2009/03/03 05:55




abstract
 
프로그램에서 선언되는 로컬 변수함수 호출에 사용되는 스택 프레임(Stack Frame)에 대해 알아봅니다.



goal

  • Stack Frame 개념의 이해
  • 간단한 프로그램을 만들고 디버거를 이용해서 stack frame 을 확인



Stack

프로세스에서 스택(stack) 메모리의 역할은 아래와 같습니다.

1. 함수 내의 로컬 변수 임시 저장
2. 함수 호출 시 파라미터 전달
3. 복귀 주소(return address)를 저장


위와 같은 역할을 수행하기에는 스택의 FILO(First In Last Out) 구조가 아주 유용합니다.

그리고 스택에 대해서 주의하실 점은 아래와 같습니다.

1. 32/64 bit 컴퓨팅 환경에서 스택 크기는 각각 32/64 bit (4/8 byte) 이다.
2. 스택은 아래로 자란다.
    - 스택에 값을 입력하면 (PUSH 명령) 스택 포인터는 4 byte 만큼 줄어든다.
    - 스택에서 값을 가져오면 (POP 명령) 스택 포인터는 4 byte 만큼 늘어난다




Stack Frame

stack frame 이란 쉽게 말해서 EBP (베이스 포인터) 레지스터를 사용하여
스택 내의 로컬 변수, 파라미터,  복귀 주소에 접근하는 기법을 말합니다.

IA-32 Register 기본 설명 에서 ESP 레지스터가 stack pointer 로써 스택의 top 을 가리키고,
EBP 레지스터는 base pointer 역할을 한다고 설명했었습니다.

ESP 레지스터의 값은 프로그램 안에서 수시로 변경되기 때문에
스택에 저장된 변수, 파라미터에 접근하고자 할때 ESP 값을 기준으로 하면
프로그램을 만들기 힘들고, CPU 가 정확한 위치를 참고할때 어려움이 있습니다.

따라서 어떤 기준 시점(함수 시작)의 ESP 값을 EBP 에 저장하고 이를 함수 내에서 유지해주면,
ESP 값이 아무리 변하더라도 EBP 를 기준(base)으로 안전하게 변수, 파라미터, 복귀 주소에 접근할 수 있습니다.

이것이 바로 EBP 레지스터의 base pointer 로써의 역할입니다.

assembly 코드로 보면 이런 형식입니다.

PUSH EBP                  ; 함수 시작 (EBP 를 사용하기 전에 기존의 값을 스택에 저장)
MOV EBP, ESP              ; 현재의 ESP(스택포인터) 를 EBP 에 저장

...                       ; 함수 본체
                          ; 여기서 ESP 가 변경되더라도 EBP 가 변경되지 않으므로
                          ; 안전하게 로컬변수와 파라미터를 access 할 수 있음

MOV ESP, EBP              ; ESP 를 정리 (함수 시작했을 때 값으로 복원시킴)
POP EBP                   ; 리턴 되기 전에 저장해 놓았던 원래 EBP 값을 복원
RETN                      ; 함수 종료

stack frame 을 이용해서 함수 호출을 관리하면,
아무리 함수 호출 depth 가 깊어지고 복잡해져도 스택을 완벽하게 관리할 수 있습니다.

* 최신 컴파일러는 최적화(Optimization) 옵션을 가지고 있어서
   간단한 함수 같은 경우에 stack frame 을 생성하지 않을 수 도 있습니다.

* stack 에 복귀주소가 저장된다는 점이 보안 취약점으로 작용할 수 있습니다.
   buffer overflow 기법을 사용하여 복귀주소가 저장된 스택 메모리를 의도적으로 다른 값으로 변경이 가능합니다.
   (향후 buffer overflow 에 대해서 정리해보는 시간을 갖도록 하겠습니다.)



간단한 예제 - stackframe.exe

stack frame 설명을 위해서 아주 간단한 프로그램을 만들어 보겠습니다.

#include "stdio.h"

int add(long, long);

int main(int argc, char* argv[])
{
    long a = 1, b = 2;
    add(a, b);
    return 0;
}

int add(long a, long b)
{
    long x = a, y = b;
    return (x + y);
}


VC++ 의 '최적화(Optimization)' 옵션을 끄고(disabled) 빌드하면 (/Od 옵션), 아래와 같은 바이너리 코드가 생성됩니다.
(VC++ 9.0 의 main() 함수 찾는 법은 "Hello World 디버깅" 을 참고하세요.)


<Fig. 1>


위 코드를 상세하게 살펴보도록 하겠습니다.

00401000    PUSH EBP                        ; main() 함수 시작
00401001    MOV EBP,ESP                     ; ESP -> EBP (스택 프레임 생성)
00401003    SUB ESP,8                       ; local 변수 a, b 를 위한 공간을 확보한다.
00401006    MOV DWORD PTR SS:[EBP-4],1      ; EBP-4 = a = 1
0040100D    MOV DWORD PTR SS:[EBP-8],2      ; EBP-8 = b = 2
00401014    MOV EAX,DWORD PTR SS:[EBP-8]    ; b
00401017    PUSH EAX                        ; add() 함수의 두번째 파라미터 b 를 스택에 입력       
00401018    MOV ECX,DWORD PTR SS:[EBP-4]    ; a       
0040101B    PUSH ECX                        ; add() 함수의 첫번째 파라미터 a 를 스택에 입력
0040101C    CALL stackfra.00401030          ; add() 함수 호출       

눈여겨 보실 부분은 main() 함수의 로컬 변수 a, b 를 가리킬 때 ESP 가 아닌 EBP 를 사용한다는 점입니다.
(a = EBP-4, b = EBP-8)

그리고 0040101C 주소의 CALL 에 의해서 자동으로 스택에 복귀주소가 입력됩니다.

스택의 모양은 이렇게 됩니다. (ESP = 12FF68)

         address     value       comment
------------------------------------------
EBP-14   0012FF68    00401021    ; 복귀주소 -> add() 함수의 실행이 종료되면 이 주소로 돌아온다.
EBP-10   0012FF6C    00000001    ; add() 함수의 첫번째 파라미터 a
EBP-C    0012FF70    00000002    ; add() 함수의 두번째 파라미터 b
EBP-8    0012FF74    00000002    ; main() 함수의 변수 b
EBP-4    0012FF78    00000001    ; main() 함수의 변수 a
EBP ==>  0012FF7C    0012FFC0    ; main() 함수 stack frame 의 EBP 값 = 12FF7C


add() 함수 내부로 디버깅을 계속합니다.

00401030    PUSH EBP                         ; add() 함수 호출

00401031    MOV EBP,ESP                      ; ESP -> EBP (add() 함수의 스택 프레임 생성)
00401033    SUB ESP,8                        ; local 변수 x, y 를 위한 공간을 확보한다.
00401036    MOV EAX,DWORD PTR SS:[EBP+8]     ; EBP+8 = a
00401039    MOV DWORD PTR SS:[EBP-8],EAX     ; EBP-8 = x
0040103C    MOV ECX,DWORD PTR SS:[EBP+C]     ; EBP+C = b
0040103F    MOV DWORD PTR SS:[EBP-4],ECX     ; EBP-4 = y

add() 함수를 위한 스택 프레임이 생성됩니다.
이제부터 EBP 는 add() 함수의 스택 프레임을 구성하는 베이스 포인터가 됩니다.
(main() 함수에서 사용되던 베이스 포인터는 401030 주소의 PUSH 명령에 의해 스택에 저장됨)

여기까지 오면 스택의 모양은 이렇게 됩니다. (ESP = 12FF5C)

         address     value       comment
------------------------------------------
EBP-8    0012FF5C    0000000A    ; add() 함수의 변수 x
EBP-4    0012FF60    00000014    ; add() 함수의 변수 y
EBP ==>  0012FF64    0012FF7C    ; add() 함수 stack frame 의 EBP 값 = 12FF64
EBP+4    0012FF68    00401021    ; 복귀주소 -> add() 함수의 실행이 종료되면 이 주소로 돌아온다.
EBP+8    0012FF6C    00000001    ; add() 함수의 첫번째 파라미터 a
EBP+C    0012FF70    00000002    ; add() 함수의 두번째 파라미터 b
EBP+10   0012FF74    00000002    ; main() 함수의 변수 b
EBP+14   0012FF78    00000001    ; main() 함수의 변수 a
EBP+18   0012FF7C    0012FFC0    ; main() 함수 stack frame 의 EBP 값 = 12FF7C


스택이 상당히 복잡하게 보입니다만, 위에서부터 설명을 차근차근 읽어 내려오면 이해할 수 있으실 겁니다.
(address 를 기준으로 main() 함수의 스택과 비교해 보세요.)

00401042    MOV EAX,DWORD PTR SS:[EBP-8]     ; EBP-8 = x
00401045    ADD EAX,DWORD PTR SS:[EBP-4]     ; EBP-4 = y
00401048    MOV ESP,EBP                      ; ESP 복원 (12FF64) -> add() 스택 제거
0040104A    POP EBP                          ; EBP 복원 (12FF7C) -> main() 스택 프레임 EBP
0040104B    RETN                             ; add() 함수 종료

add() 함수가 종료되면서 스택 프레임을 제거하고 EBP 값을 원래대로 복원시킵니다.
(main() 함수의 스택 프레임 EBP 로 변경됩니다.)

RETN 명령에 의해서 12FF68 에 저장되어 있던 복귀주소가 스택에서 빠지면서 그 복귀주소로 되돌아 갑니다.

00401021    ADD ESP,8                        ; add() 함수 호출을 위해 스택에 입력한 파라미터를 제거함
00401024    XOR EAX,EAX
00401026    MOV ESP,EBP                      ; ESP 복원 -> main() 스택 제거
00401028    POP EBP                          ; EBP 복원 (12FFC0)
00401029    RETN                             ; main() 함수 종료

여기서 또하나 눈여겨 보실 부분은 00401021 주소의 ADD ESP, 8 명령입니다.
이 명령의 의미는 add() 함수의 파라미터 크기 만큼 스택을 보정(정리)시키는 역할을 합니다.

이 스택 보정(정리) 명령을 수행하지 않으면 함수를 호출 할 때마다 스택에 파라미터들이 쌓여서
결국 스택 메모리가 다 차버리고 말 것입니다.

이렇게 함수 호출시 스택에 파라미터를 전달하고 함수가 리턴될 때
스택을 정리하는 방식에 대한 규약을 Calling Convention 이라고 합니다.

* Calling Convention 또한 리버싱에서 필수적으로 배워야 할 내용이며 다음 포스트에서 설명하도록 하겠습니다.



Epilogue

지금까지 스택 프레임에 대해서 알아보았습니다.

수시로 변경되는 ESP 레지스터 대신 EBP (베이스 포인터) 레지스터를 사용하여
로컬 변수, 파라미터, 복귀주소 등을 관리하는 방법입니다.

위 간단한 예제를 직접 디버깅 하면서 스택의 변동사항을 확인하면,
스택 프레임에 대해서 쉽게 이해할 수 있으실 겁니다.


 

Trackback Address :: http://www.reversecore.com/trackback/11 관련글 쓰기

  1. Tracked from www.reversecore.com 2009/03/11 05:42 삭제

    Subject: Calling Convention

    cdecl.exe stdcall.exe abstract Calling Convention (함수 호출 규약) 에 대해서 알아 보겠습니다. Calling Convention 우리 말로 '함수 호출 규약' 이라고 합니다. "함수를 호출할 때 파라미터를 어떤 식으로 전달하고, 스택을 어떻게 정리하는가" 에 대한 일종의 약속입니다. 우린 이미 함수 호출전에 파라미터를 스택을 통해서 전달한다는 것을 알았습니다. 스택이란 프로세스에서 정의된 메모리 공간이며 아..
  1. 도레비 2009/11/06 12:09 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요. reversecore님
    알찬 강좌 잘 보고 있답니다.

    이번 강좌를 보다가 궁금한게 생겼는데요.
    예제 stackframe.exe를 디버깅 하는 과정에서
    MOV DWORD PTR SS : [EBP-4], 1
    가 의미하는 바를 잘 모르겠습니다.

    이것을
    MOV EBP-4, 1이라고 해주면 안되는것인지요?
    DWORD PTR SS : [ ]는 무슨 의미를 가지고 있는 것인지요?

    • reversecore 2009/11/06 18:21 댓글주소 | 수정 | 삭제

      도레비님, 안녕하세요.

      문의하신 MOV DWORD PTR SS : [EBP-4], 1 명령은 본문의 main() 함수에서 a = 1; 명령과 같은것입니다.

      [] 설명은 아래 표로 설명드릴께요.

      address value comment
      ------------------------------------------
      EBP-14 0012FF68 00401021 ; 복귀주소 -> add() 함수의 실행이 종료되면 이 주소로 돌아온다.
      EBP-10 0012FF6C 00000001 ; add() 함수의 첫번째 파라미터 a
      EBP-C 0012FF70 00000002 ; add() 함수의 두번째 파라미터 b
      EBP-8 0012FF74 00000002 ; main() 함수의 변수 b
      EBP-4 0012FF78 00000001 ; main() 함수의 변수 a
      EBP ==> 0012FF7C 0012FFC0 ; main() 함수 stack frame 의 EBP 값 = 12FF7C

      indentation 이 좀 깨졌는데요. (본문 내용을 참고하셔도 됩니다.)

      DWORD PTR SS:[EBP-4] 를 풀어쓰면, EBP 가 12FF7C 이므로 EBP-4 는 12FF78 이 되구요... 위 식은 DWORD PTR SS:[12FF78] 이 됩니다.

      뜻은 "12FF78 주소에서 4 byte (DWORD) 읽어와라" 입니다.
      즉, 1 이죠. 마치 C 언어의 포인터 개념과 같습니다. (4 byte 가 중요합니다.)

      만약 WORD PTR SS:[EBP-4] 라면 같은 1 입니다만 이때는 WORD(2 byte) 입니다.

      참고가 되셨나요?

      다른 질문 있으시면 다시 댓글 달아주세요~

    • 도레비 2009/11/09 00:42 댓글주소 | 수정 | 삭제

      친절한 설명에 감사드립니다. reversecore님 :)

      설명을 읽고 나니 궁금한게 해결이 되었네요.

      앞으로도 강좌 열심히 보겠습니다.

      감사합니다.

  2. 베리굿 2009/11/08 16:44 댓글주소 | 수정 | 삭제 | 댓글

    이번강좌는 좀 어렵네요 좋은강좌 감사합니다.

  3. 뿡뿡이 2009/11/12 00:47 댓글주소 | 수정 | 삭제 | 댓글

    어울리는 표현인지는 모르겠습니다만..

    설명 정말 간지나네요...ㅎㅎ

    잼있게 잘읽었습니다...^^

  4. 검은콘다 2009/11/23 00:15 댓글주소 | 수정 | 삭제 | 댓글

    알찬 강의 덕분에 많이 배우고 있습니다.


    이번 강의를 보다가 한 가지 질문이 생겼습니다.


    main함수코드에서는

    int a = 1, int b = 2;

    위처럼 선언되어 스택에 a, b 순서로 들어갔는데

    add함수에서

    int x = a, int y =b;

    라고 선언되었음에도 스택에 들어가는 순서가 y, x인 이유가 무엇인가요?

    • reversecore 2009/11/23 11:46 댓글주소 | 수정 | 삭제

      검은콘다님, 안녕하세요.

      함수의 파라미터를 전달할 때 스택을 사용하게 되는데요.

      main() 함수에서 add(a, b); 라고 호출되면 Intel 계열 CPU (IA-32/64)는 마지막 파라미터부터 (역순으로) 스택에 집어 넣습니다.
      즉, 첫번째 파라미터가 스택의 위쪽으로 오게 되는 것입니다.

      파라미터를 역순으로 저장하는 기술적인 이유는 잘 모르겠네요.
      예전에 한번 들어본 것도 같은데, 기억이 안나는군요. ^^
      나중에 검색을 좀 해봐야 겠습니다.

      그리고 멤버 변수인 경우도 스택에 저장이 되는데요.
      이건 제한 사항이 없습니다.
      선언 순서대로 저장하던지, 선언 역순으로 하던지 컴파일러 별로 틀리고, 최적화 옵션에 따라서 틀려집니다.

      감사합니다.

    • reversecore 2009/11/23 11:46 댓글주소 | 수정 | 삭제

      검은콘다님, 안녕하세요.

      함수의 파라미터를 전달할 때 스택을 사용하게 되는데요.

      main() 함수에서 add(a, b); 라고 호출되면 Intel 계열 CPU (IA-32/64)는 마지막 파라미터부터 (역순으로) 스택에 집어 넣습니다.
      즉, 첫번째 파라미터가 스택의 위쪽으로 오게 되는 것입니다.

      파라미터를 역순으로 저장하는 기술적인 이유는 잘 모르겠네요.
      예전에 한번 들어본 것도 같은데, 기억이 안나는군요. ^^
      나중에 검색을 좀 해봐야 겠습니다.

      그리고 멤버 변수인 경우도 스택에 저장이 되는데요.
      이건 제한 사항이 없습니다.
      선언 순서대로 저장하던지, 선언 역순으로 하던지 컴파일러 별로 틀리고, 최적화 옵션에 따라서 틀려집니다.

      감사합니다.

    • RED_BIT 2009/12/26 10:38 댓글주소 | 수정 | 삭제

      역순 저장의 이유는 인자값을 참조하기 쉽게하기 때문입니다...!!

      이것은 가변인자에서 가장 강력한 위력을 발휘하게 되는데요,

      예로써 printf( "%s", a, b, c, d ); 이런 식의 문구라면 역순으로 집어 넣게된다면 "%s"의 문자열은 ebp+8 의 위치에서 참조하면되지만,
      역순이 아니라면... "%s"의 위치를 알아내기가 좀 많이 힘들어지게됩니다. 가변인자 이기때문에, 위치를 알수가 없는것이죠..
      이러한 문제점을 해결하기 위한게 역순으로 스택에 집어 넣는것입니다.

    • ReverseCore 2009/12/28 01:13 댓글주소 | 수정 | 삭제

      RED_BIT님, 안녕하세요.

      가변인자는 C 언어의 특징인데요.

      Delphi 나 VB 처럼 가변인자를 사용하지 않는 언어에서도 왜 함수 파라미터를 역순으로 저장하는 걸까요?

      분명 이유가 있었던것 같은데 생각도 안나고 검색도 잘 안되는군요. ^^

      --------

      과거 CPU 설계 단계에서 어떤 이유로든 함수파라미터를 스택에 역순으로 전달하게 되었고, 이것이 지금까지 그대로 이어진 것이겠지요.

      또한 어떤 프로그래밍 언어든지 그 컴파일러는 asm 또는 C 로 만들테니 그 언어 또한 역순 저장을 쓸테고 말이죠.

      최초의 그 이유는 과연 무엇이었을까요? 궁금궁금 ^^

  5. 퓨리에 2009/12/26 06:22 댓글주소 | 수정 | 삭제 | 댓글

    어셈으로 번역햇을때 최초의 명령어가 push ebp 인데요
    그럼 최초의 ebp 레지스터에는 어떤값이 저장되있는지 궁금하네요.
    그리고 mov dword ptr ss: [ebp-4],1 에서요. dword ptr ss: 가 전부 하나의 예약어 역할을 하는건가요? 그냥 mow dword [ebp-4],1 라고 안하는 이유가 궁금하네요. 그냥 약속인건지..아니면 ptr과 ss도 각각 다른 의미가 잇는건지..

    • RED_BIT 2009/12/26 10:35 댓글주소 | 수정 | 삭제

      mov dword ptr ss:[ebp-4],1 과 mov dword [ebp-4],1 은 완전히 동일한 구문입니다.
      ptr 은 포인터라는 의미이고요, ss 은 ss 세그먼트 셀렉터를 사용하겠다는 의미 입니다. ( 사실상 windows에서의 세그먼트 셀렉터값들은 모두 같은 값들을 가지고 있습니다. 0~0xFFFFFFFF ) 따라서 다른 셀렉터가 들어가도 같은 데이터를 가지고 올것입니다. 하지만 기본적으로 모든 메모리참조에는 세그먼트셀렉터가있어야됩니다. ebp, esp는 명시적으로 세그먼트셀렉터를 지정해주지 않는다면 자연스럽게 ss 세그먼트 셀렉터를 선택하는것입니다. ( 따라서 위 문구는 같다고 볼 수 있습니다. ) 또한 ptr이란것은 masm32에서만 존재하는걸로 알고 있습니다.
      dword ptr [ebp-4] 와 dword [ebp-4] 를 그대로 해석하면 dword의 포인터인 ebp-4에 있는 4바이트값,
      ebp-4에서 4바이트를 읽어라. 결국은 같은 값이죠...
      제대로적은것지 모르겠네요 ㅎㅎ;; 주인장님께서... 다음답변을..ㅎ

    • ReverseCore 2009/12/28 00:54 댓글주소 | 수정 | 삭제

      퓨리에님, 안녕하세요.

      EBP 는 베이스 포인터이므로 보통 스택내의 주소를 가집니다.

      dword ptr ss 의 의미에 대해서는 RED_BIT님께서 아주 잘 설명해 주셨네요.

      감사합니다. ^^

    • ReverseCore 2009/12/28 00:54 댓글주소 | 수정 | 삭제

      RED_BIT님, 안녕하세요.

      훌륭한 답변 감사합니다. ^^

  6. 책벌레 2009/12/27 18:44 댓글주소 | 수정 | 삭제 | 댓글

    오타수정부탁드립니다...
    '스택의 FILO 구조'를 '스택의 LIFO 구조'로 변경해야 할 것 같네요...
    (물론 결과적으로는 동일하지만...)

    • ReverseCore 2009/12/28 01:00 댓글주소 | 수정 | 삭제

      책벌레님, 안녕하세요.

      FILO 와 LIFO 는 같은말로써
      기호에 따라 선택해서 쓰이는 걸로 알고 있었는데요...

      제가 잘 못 알고 있었나요? @@~

  7. 극한의고독 2010/03/20 19:51 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 감사합니다.....
    저가 가지고 있던 의문점을 말끔히 씻어주는 댓글도 이 글을 더 유익하게 하네요...

  8. Acaicat 2010/08/06 21:35 댓글주소 | 수정 | 삭제 | 댓글

    제가 기억하기로는 스택 포인터는
    마지막으로 스택에 쌓인 메모리의 다음 주소를 가리키고 있는 다고 알고 있었는데요.
    위에서 보면 -4 -8를 사용하는 것으로 보아 그렇지 않은 것 처럼 보이는데요.
    아닌가요? 제가 잘못 알고 있는 건가요? 잘못 이해한 건가요?

    • reversecore 2010/08/09 11:05 댓글주소 | 수정 | 삭제

      안녕하세요.

      어떤 자료구조 프로그래밍 책을 보면 스택의 top pointer 가 말씀하신대로 마지막 추가된 다음 주소를 가리키는 경우가 있지요.

      스택을 그런식으로 구현해도 결국에는 마찬가지입니다.

      하지만 Windows OS 프로세스의 스택은 마지막 입력된 스택 주소를 가리켜요. 저도 이런 방식이 편하다고 생각합니다. (약간 취향 차이 겠지만요... ^^)


      감사합니다.

  9. KR 2010/08/11 11:47 댓글주소 | 수정 | 삭제 | 댓글

    언제나 공부하는데 많은 도움을 받습니다
    감사히 보고 갑니다 꾸벅

  10. Zero 2010/08/19 22:06 댓글주소 | 수정 | 삭제 | 댓글

    허허 .. 책을 한권 쓰셔도 될거같습니다 . .이렇게 친절하고 배려심 깊은 책을 찾지못해서 많이 헤맸는데 ..
    확실한 개념을 잡아주시네요 .. 정말 감사합니다 ^^

  11. BanYa 2010/10/26 17:09 댓글주소 | 수정 | 삭제 | 댓글

    좋은 글 정말 잘 보고 있습니다. 여길 왜 이제야 발견했는지 모르겠네요.
    이해도 잘 되고 어지간한 책, 학원보다 좋은 것 같습니다.
    감사합니다. (_ _)

    질문이 있는데요, EBP, ESP 같은 레지스터들은 스택이 몇 개고 함수가 몇 개고 해도 CPU당 하나씩 있는 것 아닌가요?
    EBP에 처음엔 main의 stack frame값을 넣고, add()가 호출될 땐 add의 stack frame 값을 넣었죠. 그리고 함수가 끝나면 pop EBP를 해서 main의 stack frame으로 돌아간다고 했는데, EBP는 하나뿐인데 그럼 그 전에 저장된 값이 어딘가에 기록이 되어 있는 건가요? POP 하면 데이터가 없어지는 게 아닌가요?

    • reversecore 2010/10/26 20:11 댓글주소 | 수정 | 삭제

      안녕하세요.
      칭찬 감사합니다. ^^

      함수 시작부분에서 스택 프레임을 생성하는데요. 아래와 같은 코드로 되어 있습니다.

      PUSH EBP ; 이전 EBP 값을 스택에 저장
      MOV EBP, ESP ; 현재 함수에 대한 스택 포인터를 EBP 에 저장

      그리고 함수를 리턴하기 전에 아래 명령으로 현재 함수에 대한 스택 프레임을 해제하지요.

      MOV ESP, EBP ; 스택 포인터를 원래대로 복원
      POP EBP ; EBP 를 원래대로 복원

      스택을 이용해서 적절하게 값을 저장/복원 시키기 때문에 안전하게 동작할 수 있습니다.

      저 위의 디버깅을 한번 따라해 보시고요. 질문있으시면 다시 올려주세요~

      감사합니다.

  12. reversecingK 2011/01/01 23:48 댓글주소 | 수정 | 삭제 | 댓글

    reverscecore 님 매번 글을 읽을때마.. 감사하고있습니다.

    첨에는 아무리 봐도 모르겠는데 계속 따라하고 그값을 찾을려고 하니..

    아주 미약하지만 조금씩 보일려고 하네용 ㅎㅎ

    좋은 강좌 감사합니다.!! 그리고 새 복 많이 받으세용 ^^

  13. mjxaone 2011/01/21 09:06 댓글주소 | 수정 | 삭제 | 댓글

    원리부터 차근차근 쉽게 잘 설명 되있어서 잘 이해가 됩니다.
    감사합니다.

  14. chimei 2011/01/25 01:35 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 잘보고 있습니다 . 그런데 main 찾는법을 잘 모르겠습니다. Hello World 경우엔 string, API 호출 등등으로 찾기가 수월한데. 예제에는 찾기가 힘드네요 . 예제를 수정하여 API를 추가하거나 메시지를 추가해서 찾을수도 있지만, 수정이 안될시에는 어떻게 찾아야 할까요?
    혹시 00401000 이 항상 main 주소인가요?

    • reversecore 2011/01/25 23:03 댓글주소 | 수정 | 삭제

      안녕하세요.

      main() 의 주소는 프로그래머/개발도구에 따라서 달라집니다.

      저의 경우는 각 버전별로 VC++ 를 설치하고 하나씩 빌드 해가면서 각 파일의 특징을 살펴보았습니다.

      main() 에는 쉽게 찾을 수 있도록 MessageBox() 등의 호출 코드를 넣었지요.

      그리고 EP(EntryPoint) 코드부터 디버깅 해나가는 연습을 하였습니다. 처음에는 무척 헤매게 되지요. 하지만 그런 과정을 조금 반복하면 금방 익숙해 지고 스텁 코드가 눈에 들어오게 됩니다.

      감사합니다.

    • chimei 2011/01/26 17:47 댓글주소 | 수정 | 삭제

      답변 감사드립니다. ^ ^

      연습이 많이 필요하겠네요 . 많이 보면서 스텁코드를 눈에 익혀겠어요 !

      // Debug Option - Event - Make first pause at:

  15. 빵뎅이 2011/05/31 22:43 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 질문이 있는데요..
    제가 main()함수를 찾으려고 코드 처음부터 F7 눌러서 스텁코드엔 어느정도 익숙해졌습니다.
    그런데 쭉 가다보면 MOV DWORD PTR SS:[ESP+4],EAX ; stackfra.<ModuleEntryPoint> 란 곳에서 F7을 누르면 그냥 끝까지 실행이 되버립니다. 그래서 메인 함수를 찾을 수가 없었습니다.
    어떻게 해야할까요..?

  16. 리버싱생초보 2011/06/25 21:18 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세여 강의 잘 보고 있습니다.
    이번 강의에서 궁금한게 잇는데여
    첨부파일로 올려주신파일을 Olldbg로 디버깅하려는데
    401000부터 하고싶은데 4012FD부터 되네요.. 401000부턴 못하나요??

  17. 헤헤 2011/10/30 18:58 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다

    많은 도움이 되었어요!!

    자세해서

    감동이예요


◀ PREV : [1] : ... [78] : [79] : [80] : [81] : [82] : [83] : [84] : [85] : [86] : ... [91] : NEXT ▶