일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- data distribution
- open office xml
- svn update
- MySQL
- idapro
- TensorFlow
- Rat
- hex-rays
- x64
- Ransomware
- ecma
- error
- error fix
- 포인터 매핑
- pytest
- NumPy Unicode Error
- ida pro
- idapython
- debugging
- why error
- Python
- Injection
- Analysis
- ida
- commandline
- mock.patch
- h5py.File
- javascript
- idb2pat
- malware
- Today
- Total
13 Security Lab
What "Calling Convention" means? 본문
Calling Convention ?
; 함수 호출 규약
호출자(caller)와 피호출자(callee) 간의 함수의 인자를 전달하는 방식에 대한 규약을 정의
- 함수 호출 규약은 아키텍처마다 다를 수 있음 (예: x86, ARM, x86-64, MIPS 등)
- 같은 아키텍처 내에서도 다양한 이유로 다양한 호출 규약을 가질 수 있음
- 프로그래밍 언어에 따라, 컴파일러의 구현에 따라 함수 호출 규약이 다르게 정의되거나 구현
Calling Convention 종류
함수 호출 규약은 4가지 기준으로 그 종류가 나뉜다
- Parameter 전달 방법
스택 프레임 사용해서 parameter 전달, 레지스터 사용해서 parameter 전달 - Parameter 전달 순서
함수명( param1, param2, param3, ....) 에서 어떤 parameter부터 먼저 전달 할 것인가? - 함수 리턴 값 전달 방법
함수 리턴 값을 어디에 저장해서, 돌려줄 것인가? - 함수 호출간 사용했던 stack frame을 정리하는 방법
함수 사용이 끝난후에, 사용했던 stack frame을 공간을 누가 정리할 것인가?
e.g.
void __stdcall add( int a, int a );
int __cdcl add(int a, int b);
호출 규약 | 스택 정리 | 인수전달 | 이름규칙 |
__cdecl | 호출원 | 오른쪽 먼저 | _함수명 |
__stdcall | 함수 | 오른쪽 먼저 | _함수명@인수크기 |
__fastcall | 함수 | 오른쪽 먼저 (ECX, EDX가장 먼저) | @함수명@인수크기 |
thiscall | 함수 | 오른쪽 먼저 (this 포인터는 ecx레지스터로 전달) | C++이름 규칙을 따름 |
__cdecl
c언어는 기본적으로 cdecl 방식
리턴 값 : eax에 리턴값 저장
(리턴 값이 4 < x < 8byte 경우 : 상위 4바이트 edx / 하위 4바이트는 eax)
stack frame 정리 방법
caller(호출한 함수)가 callee(호출된 함수)의 stack frame 공간을 정리함
add esp, (total parameter size)
※ cdecl에서 callee의 스택프레임을 caller가 정리 이유
: 가변인자 함수(파라메터 개수 제한 x, e.g. printf( ) ) 때문
printf( "a=%d, b=%d, c=%d, d=%d", n1,n2,n3,n4 )
printf("a=%d, b=%d", n1,n2)
__stdcall
C lang은 기본적으로 cdecl 방식, stdcall 방식으로 컴파일 시 함수명 앞에 '_stdcall' 키워드를 붙인다
( 윈도우에서 사용하고 있는 표준 호출 규약. windows API는 stdcall을 따른다 )
e.g ret 8 (RETN + POP 8)
리턴 값 : eax에 리턴값 저장
(리턴 값이 4 < x < 8byte 경우 : 상위 4바이트 edx / 하위 4바이트는 eax)
stack frame 정리 방법
callee(호출된 함수)가 자신의 stack frame 공간을 정리함
# stack frame 정리하는 어셈 코드 : ret 커맨드 사용
Name Decoration
func --> _func@파라메터 총 합
__fastcall
( critical한 성능을 요구하는 일부 함수에서 사용. 윈도우 커널의 함수)
Parameter 전달 방법
레지스터 사용해서 파라메터 전달
: 처음 2개의 파라메터는 스택을 사용하지 않고, ecx와 edx 를
사용해서 전달.
리턴 값: eax에 리턴값 저장
(리턴 값이 4 < x < 8byte 경우 : 상위 4바이트 edx / 하위 4바이트는 eax)
stack frame 정리 방법: callee(호출된 함수)가 자신의 stack frame 공간을 직접 정리함
예외사항 (Saved-Register)
아래의 디스어셈블리 코드에서, ESI 레지스터에는 ShoWindow와 UpdateWindow에 파라메터로 전달되어질 hWnd 값이 들어가 있다.
push eax ; nCmdShow
push esi ; hWnd
call ds:ShowWindow
push esi
call ds:UpdateWindow
mov eax, 1
pop esi
retn
이 때 만약 첫 번째로 호출되어지는 ShowWindow 함수 내부에서 ESI 레지스터의 내용을 바꾸어 놓았다면
이 ESI 레지스터를 사용하고자 했던 UpdateWindow 함수에서는 전혀 엉뚱한 결과를 얻게 될 것이다
(엉뚱한 hWnd 값이 UpdateWindow에 전달되었으므로)
우리가 사용하는 Windows의 컴파일러들은 이러한 문제를 피하기 위해 Saved- Register 를 사용하여
함수들간의 레지스터 사용의 충돌을 피하고 있다. 아래 표는, 32 bit Windows를 기준으로 한 값이다.
Caller saved register, Callee saved register 는 중간에 변경될 수 없다.
모든 함수는 main( ) 에서 호출되기 때문에, callee saved register를 백업해야 하고,
main( )도 main_crc_Startup( ) 에 의해서 호출되기 때문에 callee가 된다.
(함수 호출규약의 caller, callee는 함수 호출 시점에 따라 변경되지만, 여기서의 caller, callee의 의미는 다르다)
정리를 하면, main( )를 비롯한 프로그래머가 작성한 모든 함수는 callee savaed register를 백업하고,
시스템 함수는 caller saved register를 백업함.
Caller saved register
(Caller가 save해야 하는 레지스터)
EAX, ECX, EDX
(eax, ecx, edx는 caller가 백업 받아 놓고, callee가 마음대로 eax, ecx, edx
를 사용할 수 있다)
Callee saved register
(Callee가 save 해야 하는 레지스터)
EBX, ESI, EDI, EBP
(ebx, esi, edi, ebp는 callee가 백업 받아 놓고, caller가 마음대로 ebx, esi,
edi, ebp를 사용할 수 있다)
- ebp는 함수 prolog 과정을 통해 SFP(Saved Frame Pointer)라는 값으
로 스택프레임이 저장이 된다.
* Callee는 Callee saved register를 사용하려 할 경우 반드시 백업을 하고
사용해야 하며 함수 복귀 전 다시 값을 복원해야 함
(Callee 입장에서는 Caller가 해당 레지스터를 사용했는지 알지 못하므로
무조건 백업을 수행 후 사용해야 함)
레퍼런스
hxxps://blog.naver.com/PostView.naver?blogId=tjdghkgkdl&logNo=10117639381
hxxps://blog.naver.com/PostView.naver?blogId=tjdghkgkdl&logNo=10117777106