함수 인자와 Return 값 레지스터 (feat.32, 64 bit)
#include <stdio.h>
int tmp(int a, int b, int c, int d, int e, int f)
{
printf("%d %d %d %d %d %d\n", a, b, c, d, e, f);
return 1;
}
int main()
{
printf("%d\n", tmp(1, 2, 3, 4, 5, 6));
}
위의 코드에서 32비트와 64비트의 Function Argument와 Function Return 값 저장 레지스터를 알아보자.
32 BITS
pwndbg> disas main
Dump of assembler code for function main:
0x0804844a <+0>: lea ecx,[esp+0x4]
0x0804844e <+4>: and esp,0xfffffff0
0x08048451 <+7>: push DWORD PTR [ecx-0x4]
0x08048454 <+10>: push ebp
0x08048455 <+11>: mov ebp,esp
0x08048457 <+13>: push ecx
0x08048458 <+14>: sub esp,0x4
0x0804845b <+17>: sub esp,0x8
0x0804845e <+20>: push 0x6
0x08048460 <+22>: push 0x5
0x08048462 <+24>: push 0x4
0x08048464 <+26>: push 0x3
0x08048466 <+28>: push 0x2
0x08048468 <+30>: push 0x1
0x0804846a <+32>: call 0x804841b <tmp>
0x0804846f <+37>: add esp,0x20
0x08048472 <+40>: sub esp,0x8
0x08048475 <+43>: push eax
0x08048476 <+44>: push 0x8048533
0x0804847b <+49>: call 0x80482f0 <printf@plt>
0x08048480 <+54>: add esp,0x10
0x08048483 <+57>: nop
0x08048484 <+58>: mov ecx,DWORD PTR [ebp-0x4]
0x08048487 <+61>: leave
0x08048488 <+62>: lea esp,[ecx-0x4]
0x0804848b <+65>: ret
End of assembler dump.
pwndbg> disas tmp
Dump of assembler code for function tmp:
0x0804841b <+0>: push ebp
0x0804841c <+1>: mov ebp,esp
0x0804841e <+3>: sub esp,0x8
0x08048421 <+6>: sub esp,0x4
0x08048424 <+9>: push DWORD PTR [ebp+0x1c]
0x08048427 <+12>: push DWORD PTR [ebp+0x18]
0x0804842a <+15>: push DWORD PTR [ebp+0x14]
0x0804842d <+18>: push DWORD PTR [ebp+0x10]
0x08048430 <+21>: push DWORD PTR [ebp+0xc]
0x08048433 <+24>: push DWORD PTR [ebp+0x8]
0x08048436 <+27>: push 0x8048520
0x0804843b <+32>: call 0x80482f0 <printf@plt>
0x08048440 <+37>: add esp,0x20
0x08048443 <+40>: mov eax,0x1
0x08048448 <+45>: leave
0x08048449 <+46>: ret
End of assembler dump.
32비트에서 함수 호출 할 때에는 push 명령어를 통해 인자를 stack에 저장한 후에 DWORD PTR [ebp+인자위치] 에서 값을 들고 온다. 레지스터에 저장을 안하네? 뭐지? syscall 인자로 사용해야 해서 그런가? 아시는 분은 댓글 좀 달아주세요 ㅠㅠ 해결! 아래 주소에서 그 이유를 설명해준다.
What are the calling conventions for UNIX & Linux system calls on i386 and x86-64
Following links explain x86-32 system call conventions for both UNIX (BSD flavor) & Linux: http://www.int80h.org/bsdasm/#system-calls http://www.freebsd.org/doc/en/books/developers-handbook/x86-
stackoverflow.com
그리고 tmp 함수의 return 값은 EAX에 저장되어 main으로 전달되는 것을 main+43(push eax)에서 printf의 인자 전달 부분에서 볼 수 있다.
ebp+인자위치 에서 값을 가져오는 이유
이때 첫번째 인자가 왜 ebp+0x8인지는 다음과 같이 설명할 수 있다.
main+32 -> call tmp
pwndbg> x/wx $ebp
0xfff535a8: 0x00000000
... code execute ...
tmp+1 -> mov ebp,esp
pwndbg> x/16wx $esp
0xfff53578: 0xfff535a8 0x0804846f 0x00000001 0x00000002
0xfff53588: 0x00000003 0x00000004 0x00000005 0x00000006
0xfff53598: 0xfff5365c 0xf761619d 0xf778d3c4 0xfff535c0
0xfff535a8: 0x00000000 0xf75fcad3 0x08048490 0x00000000
pwndbg> x/wx 0xff86ee38
0xfff535a8: 0x00000000
pwndbg> x/wx 0x0804846f
0x804846f <main+37>: 0x8320c483
pwndbg> x/wx $ebp
0xfff53578: 0xfff535a8
pwndbg> x/wx $ebp+8
0xfff53580: 0x00000001
tmp+1 까지 실행될 경우 stack은 main 함수의 ebp, main 함수에서 tmp 함수가 끝나고 실행할 명령어 주소 이 두 주소가 저장되어 있으므로 실질적인 인자는 tmp 함수의 ebp+8 에 존재한다.
64 BITS
pwndbg> disas main
Dump of assembler code for function main:
0x000000000040058c <+0>: push rbp
0x000000000040058d <+1>: mov rbp,rsp
0x0000000000400590 <+4>: mov r9d,0x6
0x0000000000400596 <+10>: mov r8d,0x5
0x000000000040059c <+16>: mov ecx,0x4
0x00000000004005a1 <+21>: mov edx,0x3
0x00000000004005a6 <+26>: mov esi,0x2
0x00000000004005ab <+31>: mov edi,0x1
0x00000000004005b0 <+36>: call 0x400536 <tmp>
0x00000000004005b5 <+41>: mov esi,eax
0x00000000004005b7 <+43>: mov edi,0x400667
0x00000000004005bc <+48>: mov eax,0x0
0x00000000004005c1 <+53>: call 0x400410 <printf@plt>
0x00000000004005c6 <+58>: nop
0x00000000004005c7 <+59>: pop rbp
0x00000000004005c8 <+60>: ret
End of assembler dump.
pwndbg> disas tmp
Dump of assembler code for function tmp:
0x0000000000400536 <+0>: push rbp
0x0000000000400537 <+1>: mov rbp,rsp
0x000000000040053a <+4>: sub rsp,0x20
0x000000000040053e <+8>: mov DWORD PTR [rbp-0x4],edi
0x0000000000400541 <+11>: mov DWORD PTR [rbp-0x8],esi
0x0000000000400544 <+14>: mov DWORD PTR [rbp-0xc],edx
0x0000000000400547 <+17>: mov DWORD PTR [rbp-0x10],ecx
0x000000000040054a <+20>: mov DWORD PTR [rbp-0x14],r8d
0x000000000040054e <+24>: mov DWORD PTR [rbp-0x18],r9d
0x0000000000400552 <+28>: mov r8d,DWORD PTR [rbp-0x14]
0x0000000000400556 <+32>: mov edi,DWORD PTR [rbp-0x10]
0x0000000000400559 <+35>: mov ecx,DWORD PTR [rbp-0xc]
0x000000000040055c <+38>: mov edx,DWORD PTR [rbp-0x8]
0x000000000040055f <+41>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000400562 <+44>: sub rsp,0x8
0x0000000000400566 <+48>: mov esi,DWORD PTR [rbp-0x18]
0x0000000000400569 <+51>: push rsi
0x000000000040056a <+52>: mov r9d,r8d
0x000000000040056d <+55>: mov r8d,edi
0x0000000000400570 <+58>: mov esi,eax
0x0000000000400572 <+60>: mov edi,0x400654
0x0000000000400577 <+65>: mov eax,0x0
0x000000000040057c <+70>: call 0x400410 <printf@plt>
0x0000000000400581 <+75>: add rsp,0x10
0x0000000000400585 <+79>: mov eax,0x1
0x000000000040058a <+84>: leave
0x000000000040058b <+85>: ret
End of assembler dump.
64비트에서 호출할 때에는 EDI, ESI, EDX, ECX, R8, R9에 차곡차곡 저장한 후에 stack에 옮긴 뒤, 다시 레지스터에 그 포인터 값을 넣는다.
그리고 tmp 함수의 return 값은 EAX에 저장되어 main으로 전달되는 것을 main+41(mov esi,eax)에서 printf의 인자 전달 부분에서 볼 수 있다.
Function Argument
32bits | 64bits |
In Stack & use [ebp+@] | RDI, RSI, RDX, RCX, R8, R9 |
Function Return Value
32bits | 64bits |
EAX | EAX |
참고문헌
https://tribal1012.tistory.com/13
32bit와 64bit의 차이
32bit와 64bit의 차이 CPU 레지스터(Registers) Calling Conventions(함수 호출 규약) 1. CPU Registers CPU의 레지스터란 처리의 지연 시간을 줄이기 위해 사용하고 있는 CPU 내부의 임시 기억장치이다. 컴퓨터가..
tribal1012.tistory.com
https://docs.microsoft.com/ko-kr/cpp/build/x64-calling-convention?view=vs-2019
x64 호출 규칙
ABI 기본 x64 호출 규칙의 세부 정보입니다.
docs.microsoft.com
https://www.lazenca.net/pages/viewpage.action?pageId=16810038&src=contextnavpagetreemode
03.RTL(Return to libc) - TechNote - Lazenca.0x0
페이지 … TechNote 02.TechNote 06.Exploit tech 배너의 맨 끝으로 배너의 맨 처음으로 03.RTL(Return to libc) 메타 데이터의 끝으로 건너뛰기 Lazenca.0x0님이 작성, 4월 11, 2018에 최종 변경 메타 데이터의 시작으로 이동