본문 바로가기
Reversing

Reversing 1주차 - 함수 호출 규약 (32bit / 64bit) + 코드 C로 변환

by 몰라몰라개복치 2020. 9. 11.

함수 호출 규약이란?

 

 

- 함수를 호출할 때 파라미터를 어떤 식으로 전달하는가에 대한 약속

- 함수 호출 후에 ESP(스택 포인터)를 어떻게 정리하는지에 대한 약속

 

 

* Caller : 호출자 / 함수를 호출한 대상

* Callee : 피호출자 / 호출을 당한 함수

만약, main() 함수에서 printf() 함수를 호출했다면 Caller는 main(), Callee는 printf()가 된다.

 

 

 

 

 

(1) cdecl 방식

주로 C언어에서 사용되는 방식이며 Caller에서 스택을 정리하는 특징을 가짐

 

 

_cdecl 의 가장 큰 특징은 메인 함수에서 함수를 call 하고 add 등으로 스택을 다시 채워줌으로써 보정하는 특징을 가지고 있는데 이 보정하는 스택의 크기로 파라미터의 개수까지 알 수 있음

 

ex)

 

0040101C 주소를 보면 ESP에 8을 더해서 Caller인 main()함수가 자신이 스택에 입력한 함수 파라미터를 직접 정리

장점은 가변 길이 파라미터를 전달할 수 있다는 것

 

 

 

 

 

 

(2) stdcall 방식

 

tdcall 방식은 주로 Win32 API에서 사용되며, cdecl와 반대로 Callee에서 스택을 정리

C언어는 기본적으로 cdecl 방식이므로 stdcall 방식으로 컴파일을 하고 싶을 때는 함수명 앞에 '_stdcall' 키워드를 붙여주면 됨

 

이 방식 역시 스택에 파라미터를 push 하여 함수 호출시 전달하는 것은 동일하고, Callee인 add() 함수에서 스택을 정리하는데 방식이 약간 다르다.

cdecl 방식에서는 main() 함수에서 add() 함수 호출 후에 ADD ESP, 8 명령으로 스택을 정리 했다면, stdcall 방식에서는 add() 함수의 RETN 명령에 정리할 스택의 크기를 포함하여 RETN 8(RETN + POP 8) 명령으로 스택을 정리한다. 또한 스택을 정리하는 코드가 없기 때문에 cdecl 방식에 비해 코드의 크기가 줄어든다는 장점이 있다.

 

 

 

 

 

 

 

(3) fastcall 방식

fastcall 방식은 기본적으로 stdcall 방식과 같다. 하지만 함수에 전달하는 파라미터 일부(2개까지)를 스택이 아닌 레지스터를 이용하여 전달한다는 것이 특징이다. ECX, EDX 레지스터를 사용한다.

방식 이름 그대로 멀리 있는 메모리보다 CPU와 붙어 있는 레지스터에 접근하는 것이 빠르므로 좀 더 빠른 함수 호출이 가능하다는 것이 장점이지만, 레지스터를 사용함으로써 ECX, EDX에 중요한 값이 저장되어 있다면 그 값들을 백업시켜 놓아야 하거나 복잡한 함수의 경우 ECX, EDX 레지스터를 다른 용도로 사용해야 할 때도 값을 다른 곳에 저장해 놓아야 한다는 단점이 존재한다.

 

 

 

 

 

 

 

(4) thiscall 방식

 

__thiscall은 주로 C++ 클래스에서 이용되는 방법이다. 특징으로는 현재 객체의 포인터를 ecx에 전달한다는 것이 있다. Class로 생성한 각각의 객체를 구분해주기 위해, C++에서는 this 포인터를 사용하는데, 이 때 ecx로 전달되는 값이 this 포인터가 된다. 그리고 해당 클래스에서 사용하고 있는 멤버 변수나 각종 값은 다음과 같이 ecx 포인터에 오프셋 몇 번지를 더하는 식으로 사용할 수 있다

 

 

 

 

 

 

 

 

 

+ 아래 코드 C로 변환해 주기

 

흐름을 살펴보자.

 

먼저 main 함수부터 해석해보면

 

push rbp, mov rbp, rsp: 스택 프레임을 생성하는 부분

sub rsp, 16: rsp가 가리키는 주소를 16만큼을 빼며 스택 사용 준비(스택에 사용할 공간 할당)

mov DWORD PTR [rbp-4], 1: rbp가 가리키는 주소로부터 4 떨어진 곳에 1을 저장

mov eax, DWORD PTR [rbp-4]: eax 레지스터에 rbp-4 주소에 저장된 데이터를 복사

mov edi, eax: edi 레지스터에 eax에 저장된 데이터를 복사

mov eax, 0: 0을 eax 레지스터에 저장

call sequare: square 함수 호출

 

 

square 함수가 호출되었으므로 해석해보면

push rbp, mov rbp, rsp: 스택 프레임을 생성하는 부분

mov DWORD PTR [rbp-4], edi: edi 레지스터에 저장된 값을 rbp-4 주소에 저장

mov eax, DWORD PTR [rbp-4]: eax 레지스터에 rbp-4 주소에 저장된 데이터를 복사

imul eax, eax: eax에 저장된 값을 제곱을 해서 eax에 저장

pop rbp: 함수 종료

ret: main 함수로 다시 돌아가기

 

mov DWORD PTR[rbp-8], eax: rbp가 가리키는 주소로부터 8 떨어진 곳에 eax 레지스터에 저장된 데이터 복사

mov eax, 0: 0을 eax에 저장 

 

 

 

이를 간단하게 c코드로 바꿔보면 다음과 같은 식이 나올 것으로 예상된다

 

 

 

 

 

출처: 

ggn0.tistory.com/48

 

[리버싱] 함수의 에필로그, 함수 호출 규약

리버싱을 하려고 디버거에 바이너리를 올려놓고 제일 먼저 해야할것은 각 함수가 뭘하는지. 역할을 파악하는 것이다 그러고 난 뒤엔, 어떤식의 함수인지 인자는 몇개를 전달하는지에 대한정보�

ggn0.tistory.com

koreanfoodie.me/25

 

Reversecore chap 10 - 함수 호출 규약

'리버싱 핵심 원리'의 내용 및 이슈들과 해결책을 다룹니다. 함수 호출 규약 챕터 10은 함수 호출 규약(Calling Convention)에 대해 다룬다. 이는 '함수를 호출할 때 파라미터를 어떤 식으로 전달하는지

koreanfoodie.me