김승현

KISA Academy - 리버스 코드 엔지니어링 중급 (21~23강) 본문

교육/KisaGym

KISA Academy - 리버스 코드 엔지니어링 중급 (21~23강)

kshind 2024. 7. 12. 21:43

21강 [이론] 함수 호출규약 - 개념과 함수 호출규약의 종류

 

함수 호출 규약이란?

함수를 호출하는 caller와 호출당하는 callee간의 규칙

  • 파라미터 전달 방법
  • 파라미터 전달 순서
  • 파라미터 전달에 사용된 스택 영역의 해제 주체

x86 함수 호출 규약의 종류

ABI란? 

Application Binary Interface의 약자로 바이너리 기반 프로그램 모듈에서 스택 구성 방식임.

메모리 액세스 방식 등을 정의한 일종의 규칙으로 프로세서가 직접 액세스 할 수 있는 기본 데이터 유형의 크기, 레이아웃 및 정렬 등을 정의한 것

 

함수 호출규약이란 caller와 callee간 혼선이 발생하는 상황을 예방하기 위한 규칙으로도 요약 가능함

혼선의 예시 

  1. 매개변수 전달방법
    • caller는 파라미터를 스택을 통해 전달했는데, callee는 레지스터에서 파라미터를 찾는 경우
  2. 매개변수 전달방향
    • Right to Left     or     Left to Right
  3. 피호출자 함수가 호출자 함수를 위해 보존해 주어야 할 레지스터의 종류
    • eax, ebx, ecx 등 어떤 레지스터로 전달하고 이 레지스터 사이의 순서는 어떤지
  4. 매개변수 전달에 사용된 스택 영역 해제 주체
    • caller   or   callee = Caller Saved Register   or   Callee Saved Register

 

X86함수 호출 규약의 종류

IA-32, X86-64 아키텍처에서는 함수 호출 시 파라미터와 복귀 주소를 주로 스택에 저장

MIPS/ARM  아키텍처에선 레지스터에 저장

 

IntelArchitecture에선 자주 사용되는 호출 규약은 cdcel, stdcall, fastcall이 있음

호출 규약 이름 매개변수 전달 방향 매개변수 전달 사용 레지스터 스택영역 해제 주체
cdecl RTL X 호출자
stdcall RTL X 피호출자
fastcall RTL ECX, EDS 피호출자

 

*RTL이란? 

call funcB( p1, p2, p3)가 있을 때 stack에 p3, p2, p1순서로 저장됨

 

fastcall의 경우 전달 파리미터의 개수가 3개가 넘어가는 경우 다른 호출 규약처럼 스택으로 전달

 


22강 [이론] 함수호출규약 - IA32의 호출규약 유형별 특징

함수에 파라미터를 전달하는 방식은 크게 3개가 있음

  1. 스택 이용
  2. 레지스터 이용
  3. 스택 + 레지스터 혼합 이용

 

__cdecl, __stdcall 모두 스택을 이용하여 파라미터를 전달함

func B(p1, p2, p3)앞에 __stdcall이라는 게 앞에 붙으면 stdcall의 호출 규약을 따름을 의미함

 

파리미터 전달 순서에 관한 약속도 필요함

왼쪽에서 오른쪽 순서로, 혹은 오른쪽에서 왼쪽 순서로.

__cdecl, __stdcall모두 오른쪽에서 왼쪽 순서대로인 Rightmost to Leftmost 방식을 사용함

 

+추가 정보)

push 명령어 사용 목적

  1. 지역변수 공간 확보(sub 20 같은 느낌인듯)
  2. 파라미터 전달(호출 규약에서 스택으로 파라미터 전달할 때 그건듯)
  3. 백업 (이전 강의에서 나온 return address나 sfp 저장할 때 그런 백업 얘긴듯)

함수의 return값 전달 이슈

return의 값은 보통 스택을 통해 저장되고 스택을 통해 가져감 보통   - 아닌가

funcB( p1, p2, p3) {
	...
    sum = p1 + p2 + p3;
    
    return sum;

}

위와 같은 코드가 있으면 보통 eax라는 레지스터에 return 값이 저장됨 - 이것 또한 caller와 callee의 약속임

return 값의 size가 4바이트보다 크면 edx레지스터까지 추가적으로 사용함. 

 

  • Caller Saved Register(호출자가 관리 책임을 가지는 레지스터)
    • 피호출자가 자유롭게 사용할 수 있는 레지스터 
    • 함수를 호출하기 전과 함수에서 복귀한 후 EAX, ECX, EDX 레지스터의 값이 같아야 하면, 호출자가 피호출자를 호출하기 전에 레지스터들의 값을 백업해두어야 함                                                                                                  -> 그렇지 않으면 피호출자 호출 이후 레지스터를 자유롭게 사용하면서 원래의 값을 복구할 수 없기 때문
  • Callee Saved Register(피호출자가 관리 책임을 가지는 레지스터)
    • 피호출자가 자유롭게 사용할 수 없는 레지스터
    • 피호출자가 이 레지스터를 사용할 경우 값을 백업해두었다가 복귀하기 전에 복원해야 함                                        -> 그렇지 않으면 호출자가 사전에 해당 레지스터들에 담아뒀던 값들을 복구할 수 없기 때문

 


23강 [실습] 함수호출규약 - IA32의 호출규약 유형별 특징 분석 실습

 

사용툴

IDA, Ghidra - 디스어셈블러

xDbg - 디버거

 

 

실습에 사용할 소스 코드

소스코드를 확인해보면 각 호출규약에 따라 5+7 연산 결과 출력해주는 걸 볼 수 있음

ida를 통해 분석해보면 7부터 push하는 걸 볼 수 있음 (RTL방식)

따로 push하지 않고 함수를 call한다는 것으로 보아 해당 함수는 fastcall임을 알 수 있음 

 

함수 호출 이후 add esp, 8의 경우 두 번의 push로 올라가 있는 esp를 다시 내려주는 스택을 무효화하는 과정임

해당 함수는 add하는 게 없는 것으로 보아 callee가 스택을 정리하는 것을 알 수 있음

return 8이 있는데 이건 add 8을 의미 ( = 다른 함수와 과정이 동일 )

 

fastcall은 레지스터에 저장하기 때문에 add 8 같은 과정이 없음

 

Edit - Segments - Rebase program을 통해 base값을 수정할 수 있음

base 값은 xDbg의 memoryMap에서 확인 가능

 

우측 하단은 main함수의 스택 영역으로 주소값과 바로 위 두 개는 지역변수, 위 두 개는 파라미터의 값이다.

 

add esp, 8이 실행되면 상단의 두 개 5, 7을 참조하지 못하게 이동