SMALL
함수 호출 규약?
Calling Convention의 우리말로 번역해 놓은 것이다. 이것은 '함수를 호출할 때 파라미터를 어떤 식으로 전달하는가?'에 대한 일종의 약속이다.
함수 호출 전에 파라미터를 스택을 통해서 전달하는데 스택이란 프로세스에서 정의된 메모리 공간이며 아래방향(그냥 주소가 줄어드는 방향이라고 하는게 편함)으로 자란다. 또한 PE헤더에 그 크기가 명시되어있다.
즉 프로세스가 실행될 때 스택 메모리의 크기가 결정된다(malloc/new 같은 동적 메모리할당과는 다름)
함수가 실행완료되었을때 스택에 들어잇던 파라미터는 어떻게 되냐?
스택 메모리는 이미 고정되어있기때문에 메모리를 해제할 수도 없고 할 필요도 없다. 그리고 어차피 다음번에 스택에 다른 값을 입력할 때 저절로 덮어쓰기에 그건 그냥 냅둔다 그 이유는 지우거나 하면 불필요하게 CPU자원을 소모한다.
함수 호출 규약은 ESP(스택 포인터)와 연관이 있는데 함수 호출 후에 ESP를 어떻게 정리하는지에 대한 약속이 바로 함수 호출 규약이다.
주요한 함수 호출 규약의 3가지
- cdecl
- stdcall
- fastcall
※ Caller와 Callee
Caller(호출자) - 함수를 호출한 쪽
Callee(피호출자) - 호출을 당한 함수
ex) main()에서 printf()를 호출했다면 Caller는 main(), Callee는 printf()
cdecl
주로 C언어에서 사용되는 방식, Caller에서 스택을 정리하는 특징을 가지고 있다.
#include <stdio.h>
int add(int a, int b) {
return (a + b);
}
int main(int argc , char* argv[]) {
return add(1 , 2);
}
다음과 같은 코드로 예시를 들자면
메인에서 add함수를 호출하고 나서 add esp,0x8를 통해 스택을 정리한다. 즉, Caller인 main()함수가 스택에 입력한 함수 파라미터를 직접 정리하는 방식이다.
※cdecl 방식의 장점 : C언어의 printf()함수와 같이 가변 길이 파라미터를 전달할 수 있다. 이러한 가변 길이 파라미터는 다른 Calling Convention에서는 구현이 어렵다.
stdcall
Callee(피호출자)인 add()함수 내부에서 스택을 정리하는 방식 => Win32 API에서 사용한다.
#include <stdio.h>
int _stdcall add(int a, int b) {
return (a + b);
}
int main(int argc , char* argv[]) {
return add(1 , 2);
}
- 장점
- 호출되는 함수(Callee)내부에 스택 정리 코드가 존재 (코드 크기가 작아짐)
- API를 직접 호출할 때 호환성이 좋게함
fastcall
stdcall 방식과 비슷
함수에 전달하는 파라미터 일부(2개까지) 스택 메모리가 아닌 레지스터를 이용하여 전달한다.
즉, 4개의 파라미터를 받았을시 먼저 ECX, EDX 파라미터를 통해 먼저 전달 받고 그 다음에 스택 메모리를 이용해 전달
장점 : 빠른 함수 호출 가능(레지스터에 접근하기 때문)
단점 : 레지스터를 관리하는 추가적인 오버헤드가 필요한 경우도 있다
추가적으로 더 공부해 보자면
Calling Conventions
32bit와 64bit의 함수 호출 규약의 차이점도 있다.
32bit Calling Conventions 는 3가지 함수 호출 규약 방식을 사용하고있다.
Calling Conventions |
인자 전달 방법 |
인자 정리 시점 |
_cdecl |
스택 |
함수 반환 후 인자 정리 |
_stdcall |
스택 |
함수 반환 전 인자 정리 |
_fastcall |
레지스터 EDX, ECX 이 이후는 스택 |
함수 반환 후 인자 정리 |
하지만 64bit는 32bit와 달리 fastcall 함수 호출 규약을 한층 더 강화하여 오로지 하나의 함수 호출 규약 방식을 사용하고 있다.
64bit Calling Conventions
Calling Conventions |
인자 전달 방법 |
인자 정리 시점 |
_fastcall 변형 ( Linux ELF) |
레지스터로 전달
정수 : EDI, ESI, EDX, ECX, R8, R9
실수 : XMM0 ~ XMM7 ( 이후 인자스택 )
|
함수 반환 전 인자정리 |
_fastcall 변형 ( Windows PE ) |
레지스터로 전달
정수 : ECX, EDX, R8, R9
실수 : XMM0 ~ XMM4 ( 이후의 인자는 스택 )
|
함수 반환 전 인자정리 |
64bit ELF 프로그램 내부의 함수를 예로 들었다. add(int n1, int n2)라는 함수가 있으면 n1는 정수형 int이기 때문에 EDI레지스터로 들어가고 그다음 ESI레지스터로 들어간다.
인자가 뒤에서 부터 들어가는데
int n2 -> EDI
int n1 -> ESI
※ 왜 64비트가 만들어 졌는가?
32비트
-2,147,483,647 ~ 2,147,483,647
Y38K(2038년도)문제
컴터 시간은 1970년 1월 1일 에 시작한다.
이것은 C / Unix의 아버지 데니스리치 가 편하게 쓰기위해서 였다.
대부분 32비트 컴퓨터는 시간을 표시할때 time_t 라는 비서에게 1970년 부터 지금 이시간 까지 초를 보관하라고 했다. 계속 기록중...
Unix Epoch Clock - https://www.epochconverter.com/clock
최종문제 발생하는 날짜
2038년 1월 19일 화요일 새벽 3시 14분 이후에 기호부호의 자리를 침범해 시간이 이상해진다.
(1970년 - 68.1년 = 1901년 12월로 가게된다.) -> 전 세계 셀 수없는 많은 전자기기에 이상이 생길수도 있다.
과거엔 Y2K(2000년도)문제가 있었다.
그래서 64비트로 넘어와야하는데 표현할수있는 자리수도 늘릴수 있고 이론적으로 약 2922억년 7천만년 까지 시간을 셀수있기도 한다.
'Reversing > 리버싱_핵심원리' 카테고리의 다른 글
11장 Lena's Reversing for Newbies (0) | 2019.08.14 |
---|---|
9장 Process Explorer - 최고의 작업 관리자 (0) | 2019.08.14 |
8장 abex` crackme #2(미완) (0) | 2019.08.14 |
7장 스택 프레임 (0) | 2019.08.07 |
6장 abex' crackme #1 분석 (0) | 2019.07.25 |