[SUA] 리버싱 스터디 - IA-32 Register & Stack Frame & 함수호출규약

2020. 5. 3. 12:32정보보안/리버싱

728x90

1. IA-32 Register (Intel Architecture)

Basic Program Execution Register는 4개의 그룹으로 나눌 수 있습니다.

  • 범용 레지스터 - 8개
  • 세그먼트 레지스터 - 6개 
  • 프로그램 상태 & 제어 레지스터 - 1개
  • 명령 포인터 - 1개

 

1) General Purpose Register (범용 레지스터) - 8개

 

 

  • EAX :  Accumulator ( retrun 값 저장)
  • EBX : Pointer to Data in DS Segment
  • ECX : Counter for loop (반복문의 Counter)
  • EDX : I/O pointer 

 

  • EBP : Base Stack pointer
  • ESP : Stack pointer (in SS segment)
  • ESI : 메모리 복사에 사용
  • EDI : Destination pointer (메모리 복사시 목적지 주소)

 

 

여기서 EㅁX로 생긴애들은 특정 구역만 잘라서 쓰기 위해 따로 명칭되어 있는 것이 있습니다.

  • EAX : (0~31) 32 bit
  • AX : (0~15) 하위 16 bit
  • AH : (8~15) AX의 상위 8 bit
  • AL : (0~7) AX의 하위 8 bit
E(Extended)는 과거 IA-16 16bit 시절의 크기를 32bit로 확장시켜서 Extended가 되었습니다

64bit 에서는 R로 시작합니다 (eg. RAX)

 

2) Segment Register (세그먼트 레지스터) - 6개

세그먼트는 메모리 보호 기법으로 사용 됩니다. 페이징 기법과 함꼐 VA를 PA로 변경할 때 사용됩니다.

세그먼트 메모리는 SDT(Segment Descriptor Table)이라는 곳에 기술되어 있습니다.

세그먼트 레지스터는 SDT의 index를 가리킵니다.

 

 

  • CS : Code Segment
  • SS : Stack Segment
  • DS : Data Segment
  • ES : Extra Data Segment
  • FS : Data Segment
  • GS : Data Segment

 

 

 

3) 프로그램 상태 & 제어 레지스터 - 1개

컴퓨터 구조에서 Status & Control Register에 해당되며 여러가지 Flag가 있습니다.

EFLAGS에서 자주 쓰이는 Flag만 간략히 소개하겠습니다.

 

< EFLAGS >

 

C : Carry A : Aux Carry S : Sign I : Interrupt  
P : Parity Z: Zero T : Trap   O: Overflow

 

 

4) Instruction Pointer (명령 포인터) - 1개

  • EIP : 명령어의 주소를 나타냅니다.

 

 

2. Stack Frame & 함수 프롤로그 & 함수에필로그

Stack은 FILO 구조로 되어있습니다. 왜 FILO로 되어있냐면 그러한 구조가 {변수/주소 저장 및 호출}에 용이하기 때문입니다.

스택은 거꾸로 자랍니다. 위(High Addr)에서 아래(Low Addr)로요.

 

먼저 「콜 스택」(Call Stack) 을 통해 스택 프레임이 어떻게 쌓이는지를 보면서 동작성을 파악해보도록 합시다.

 

< Call Stack 동작성 >

좌측과 같은 C 코드가 있다고 할때, main은 foo_1를 호출하고, foo_1은 foo_2를 호출한뒤, 함수의 실행이 종료되면 Call Stack에 쌓인 Stack Frame을 제거하는 과정을 거칩니다. 

< Stack Frame >

여기서 Stack Frame은 함수가 호출 될 때 생성 되죠. 그럼 왜 이렇게 할까요 ?

함수를 호출 했을 때, 함수 내에서 사용되는 지역변수를 빠르게 찾기 위해 사용합니다.

실제동작은 위에서 배운 IA-32 아키텍쳐의 EBP」 (Stack Base Pointer) ,ESP (Stack Pointer) 에 의해 발생하게 되는데요, 이 EBP와 ESP을 Stack Frame을 쌓음과 동시에 움직이게 됩니다.

 

왜 EBP와 ESP를 움직이냐고요? 여러분이 함수를 사용해서 함수 내부로 들어갔잖아요. 그럼 그 함수 내부에 있는 변수들을 사용하려고 정의해놨죠? 함수에 진입했고, 내부 변수를 빠르게 사용하려면 거기에 사용되는 변수들만 모아서 관리하는게 편하니까요 !

 

그리고 이러한 스택프레임」이 쌓이고 제거되는 원리에 따라 여러분이 배우신 C언어에서의 「지역변수는 함수내에서만 사용된다」라는 결과가 도출됩니다.

   

이와같이 스택 프레임이 생성되고 소멸되는 과정은 코드로 구현이 되어있는데요.

생성되는 코드를 함수 프롤로그 라고 하며, 제거되는 코드는 함수 에필로그 라고 합니다.

 

함수 프롤로그 코드와 , 함수 에필로그 코드를 살펴보겠습니다.

 

1) 함수 프롤로그

PUSH EBP // 스택 프레임 생성 (Call)
MOV EBP, ESP // ESP의 위치를 EBP에 저장

 

1-1) 함수 CALL

함수 CALL 시 함수가 종료되었을 때

복귀할 주소」 를 Stack에 저장합니다.

Stack의 최상단을 가리키는 ESP는 자동으로 RET을 넣은 뒤를 가리키게 됩니다.

 

 

 

 

 

 

1-2) PUSH EBP

이전 스택 프레임의 EBP를 저장합니다.

이 저장된 값을 SFP(Stack Frame Pointer)라고 부릅니다.

왜 저장하냐고요?

함수 호출이 종료되고 난 뒤에 원래 함수로 돌아갈 때, 원래 함수에 있던 EBP를 알기 위해서입니다.

 

스택 프레임이란 개념은 EBP를 움직이면서 값을 쉽게 찾는거니까요 !

 

1-3) MOV EBP, ESP

EBP의 위치를 ESP로 옮겨주면서, 우리가 새로 생성한 Stack Frame의 공간이 설정 되었습니다!

지금부터 자유롭게 함수내부의 변수들이 마음껏 뛰놀게 공간을 움직여 줄 수 있어요.

 

 

 

 

 

 

 

 

이제 함수 에필로그 코드를 살펴보겠습니다.

 

2) 함수 에필로그

 

함수 에필로그는, 함수 프롤로그의 역순이에요 ! 설명도 위와 동일하니 그림만 보도록 하겠습니다.

MOV ESP, EBP // 스택 프레임 제거
POP EBP
RET

다른점 1개는, RET 시에 뽑아낸 ESP 값을 EIP에 저장합니다.

 

 

3. 함수 호출 규약

호출된 함수를 정리하는 방식을 말합니다. 위에서 본 생성된 Stack Frame을 누가 정리하느냐에 따라 구분이 되는데요,

스택을 정리하는 방식」인 함수호출규약을 3가지를 살펴보겠습니다.

 

  • _cdecl 방식 : Caller(호출한 쪽) 에서 정리. C언어 사용 방식
  • _stdcall 방식 : Callee(호출 당한 쪽)에서 정리. Win32API 사용 방식 (cdecl에 비해 코드 크기가 작아짐)
  • _fastcall 방식 : IA-32의 Register인 ECX, EDX 를 사용하여 정리

1) _cdecl방식

add esp, 8

호출한 쪽에서 정리하는 방식으로, ESP를 8칸 올려 {RET, SFP}를 정리

 

2) _stdcall 방식

ret 8

호출당한 쪽에서, push로 늘어난 스택을 8칸 올려서 정리

 

3) fastcall 방식

Stack을 사용하지 않고, Register를 사용하여 연산한다.

따라서 스택을 정리가 필요가 없게 된다.