본문 바로가기

High Level Technique/Reversing

Stack, Calling Conventions

스택은 프로그래밍을 한다면 누구나 들었을 말입니다.


스택은 LIFO (Last In First Out)입니다. 즉, 나중에 들어온 값이 먼저 나간다라는 말이죠.


스택에 넣을때는 PUSH라고 하며, 빼낼때는 POP이라고 합니다. 그리고 스택은 항상 "높은주소에서 낮은주소로 자랍니다." 즉, "거꾸로 자란다."라는 말이죠.

이 이유는 다른 영역에 침범하지 않기 위해서 그렇습니다.




먼저 간단히 해당 영역이 어떤 역할을 하는지 알아보도록 하겠습니다.


Text 영역 : 컴파일 된 프로그램 소스가 기계어 형태로 있는 영역입니다.

Data 영역: 프로그램에서 초기화 된 데이터들이 위치합니다.

BSS 영역: 초기화되지 않은 변수들이 위치해있고, 항상 0으로 초기화 됩니다.

Stack 영역: 지역변수가 위치합니다.

Heap 영역: 동적으로 할당된 변수들이 위치합니다.




자 그렇다면 리버싱을 하면서 Stack이 왜 중요한지 알아보도록 할께요.



1. 함수 내 지역변수 저장.

2. 함수 호출시 파리미터가 들어가는 방향 (Calling Conventions)

3. 리턴 주소 저장


이 3가지 때문에 중요합니다.!!!






- 함수 내 지역변수 저장


스택은 지역변수를 저장한다고 말했죠.


여러 디버거를 사용하다보면은 다음과 같은 어셈블리어를 자주 보게 됩니다.



push ebp

mov ebp, esp

sub esp, 50h


위의 어셈블리어를 해석하면 

push ebp: ebp 값을 스택에 넣어둡니다.

mov ebp, esp: ebp에 esp 값을 넣습니다.

sub esp, 50h: esp에서 50-h만큼 뺍니다. 50h 만큼 지역변수를 사용한다는 말입니다.







Intel CPU에서 EBP, ESP, SFP, EIP 등을 이용해서 스택을 구현합니다. 


현재까지 실행된 곳의 주소는 EBP에 저장하고, 스택 포인터는 ESP가 됩니다. 


SFP는 스택의 top을 가리키는 고정된 주소를 가지고 있고 EIP는 다음으로 실행할 명령어의 주소를 가리키게 됩니다.







- 함수 호출시 파라미터가 들어가는 방향 (Calling Conventions)


콜링컨벤션은 함수 호출시 파라미터(전달 값)이 들어가는 방향(순서)를 말합니다.


함수 호출에는 4가지 방식이 있습니다.



1. cdecl

c, c++에서 사용하는 방식.

파라미터 전달시에는 오른쪽에서 왼쪽으로 전달합니다.

호출한 함수에서 스택을 정리합니다.




#해당 소스코드와 프로그램은 http://reversecore.com/ 실습파일을 참고했습니다.





위의 어셈블리어는 다음과 같은 간단한 C 소스코드 입니다.


 #include <stdio.h>


int add(int a, int b)

{

  return (a+b);

}


int main()

{

  return add(1,2);

}


어셈블리어를 확인해보면 main에서는 add(1,2)를 호출하고 있습니다. 그런데 자세히 보면 1과 2를 인자로 넘겨주는데 2가 먼저 스택에 쌓인다는 것이 중요합니다.

또 add()가 호출이 완료된 다음 main()으로 돌아와서 ADD ESP, 8을 하게되는데 함수를 부른 main에서 스택을 정리한다는 것이 중요합니다..





2. stdcall

win32API에서 사용하는 방식.

파라미터 전달시에는 오른쪽에서 왼쪽으로 전달합니다.

호출 당한 함수에서 스택을 정리합니다.

cdecl 보다 코드의 크기가 줄어듭니다.





cdecl과 다르게 호출당한 함수에서 스택을 정리합니다. return 8을 통해서 스택을 정리하게 됩니다.


 #include <stdio.h>


int _stdcall add(int a, int b)

{

  return (a+b);

}


int main()

{

  return add(1,2);

}





3. fastcall

파라미터 전달시에 처음 2개는 ECX와 EDX를 (레지스터)사용합니다. 그 이상의 파라미터는 오른쪽에서 왼쪽으로 저장합니다.

레지스터를 사용하므로써 CPU와 가까워 빠른 호출이 가능합니다. 하지만, 함수를 호출하거나 ECX, EDX를 사용해야 하는 경우 들어있던 파라미터 값을 다른곳에 저장해둬야 하는 불편함이 있습니다.







- 리턴주소 저장



stdcall의 어셈블리어를 가지고 설명하도록 하겠습니다.




CALL 00401000 명령어가 실행되면 add()가 불러지게 됩니다. 그러면 add함수가 실행이 끝나고나면 main으로 돌아와서 그 다음 소스를 실행해야 하죠.

그런데 어떻게 다시 돌아오냐가 문제입니다. 





스택 구조를 보겠습니다.




위의 스택구조를 보면 리턴주소를 스택에 저장하고 있습니다. 인자값도 저정되어있구요.


이렇게 리턴주소를 스택에 저장해서 add함수가 호출이 끝나면 main으로 돌아올 지점을 알려주도록 합니다.






4. thiscall


thiscall은 주로 C++ 클래스에서 사용되는 방법입니다. 현재 객체의 포인터를 ecx에 전달합니다.


좀 더 풀어말하자면 하나의 클래스만 정의해 두면 여러 개의 독립적인 객체를 만들 수 있습니다. 독립적으로 만들게되면 모두 다른 주소에 위치하죠.


이 주소를 각각 구분해줘야 하는데 C++에서 this라는 포인터를 사용합니다. 바로 이 값을 전달받는 레지스터가 ecx 입니다.


이 ecx 레지스터에 offset를 더해서 구분합니다.



'High Level Technique > Reversing' 카테고리의 다른 글

분석방법  (0) 2015.06.26
주요 단어  (0) 2015.06.26
Byte Ordering  (0) 2015.05.18
Back To User Mode  (0) 2015.05.18
UPX Unpacking  (0) 2015.05.18