-
[리버싱 핵심원리 study] 37장 x64 프로세서 이야기Reverse Engineering 2021. 2. 1. 19:11
1. x64에서 추가/변경된 사항
(1) 64비트
메모리 주소가 64bit(8byte)로 표현된다. 마찬가지로 레지스터와 스택의 기본 단위도 64bit로 증가하였다.
(2) 메모리
가상 메모리의 크기가 2^64 = 16TB로 증가하였으며, User 영역 및 kernel 영역이 각각 8TB의 크기를 갖는다. (이론상 그러하며 실제로 사용되는 영역은 이보다 적다.)
(3) 범용 레지스터
레지스터들이 64bit로 확장되었으며, R8 ~ R15 레지스터가 추가되었고, R로 시작한다. (ex. EAX->RAX로 변경, RAX의 하위 32bit가 EAX)
(4) CALL/JMP Instruction
똑같은 기능을 하는 다음 두 명령어를 살펴보자.
- x86
=> Addresss : 00401000 FF1500504000 CALL DWORD PTR DS:[00405000]
00405000 주소가 가리키는 값으로 점프하기 위해 FF15 다음에 00405000 값이 나타나며, 이 값은 절대 주소로 취급된다.
- x64
=> Address : 00000001`00401000 FF15FA3F0000 CALL QWORD PTR DS:[00000001`00405000]
00405000 주소가 가리키는 곳으로 점프하지만, FF15 다음에 나타나는 값의 의미는 x86과는 다르다. x64에서는 상대 주소로 취급되므로 target address = 00000001`00401000 + 00003FFA(op 다음 값) + 6(CALL 명령어 길이) = 00000001`00405000이 된다.
(5) 함수 호출 규약
함수 호출 규약에는 cdecl, stdcall, fastcall 등이 존재하는데, 64bit에서는 fastcall로 통일되었다. fastcall은 4개 이하의 argument에 대해서는 레지스터를 이용하는 특징이 있다.
Argument 정수형 실수형 1st RCX XMM0 2nd RDX XMM1 3rd R8 XMM2 4th R9 XMM3 argument가 4개를 넘어가는 경우 스택을 이용하며, 스택에 대한 정리는 Caller에서 담당한다. 또한 첫 4개의 argument는 레지스터로 전달되지만, 스택에 이 argument를 위한 공간 32byte를 예약해 놓는다는 특징이 있다.
(6) 스택 & 스택 프레임
(5)에서 설명한 내용 이외에도, 64bit 환경에서 스택 프레임을 구성할 때는 RBP를 이용하지 않고 RSP 레지스터를 직접 이용한다. 이러한 방식을 이용하면 스택 포인터를 정리할 필요가 없어 실행 속도를 향상 시킬 수 있다느 장점이 있다.
2. 실습 Stack32.exe & Stack64.exe
책에서는 다음과 같은 동일한 소스코드를 각각 x86, x64 환경에서 컴파일 한후, 각각 어떤 방식으로 코드가 전개되는지 디버깅한다.
-Stack.cpp
#include "stdio.h" #include "windows.h" void main() { HANDLE hFile = INVALID_HANDLE_VALUE; hFile = CreateFileA("c:\\work\\ReverseCore.txt", // 1st - (string) GENERIC_READ, // 2nd - 0x80000000 FILE_SHARE_READ, // 3rd - 0x00000001 NULL, // 4th - 0000000000 OPEN_EXISTING, // 5th - 0x00000003 FILE_ATTRIBUTE_NORMAL, // 6th - 0x00000080 NULL); // 7th - 0x00000000 if( hFile != INVALID_HANDLE_VALUE ) CloseHandle(hFile); }
코드 내용 자체는 간단히 CreateFile() API를 호출하는 코드다.
(1) Stack32.exe (x86)
x32dbg를 이용해 Stack32.exe 파일을 열어보자.
컴파일러 최적화 옵션에 의해 스택 프래임이 생략되었고, CreateFile() API, CloseHandle() API를 호출할 때 push 명령어로 stack을 통해 argument가 전달되는 것을 확인할 수 있다. 또한 Win32 API는 stdcall 방식을 사용하므로 스택 정리를 callee에서 하기 때문에 main 함수에서 스택 정리하는 부분을 찾아볼 수 없다. CreateFileA() API까지 F8을 눌러 Step over 한 후, F7(Step in)을 이용해 API 내부로 들어가 보자.
우선 API 내부 앞부분에서 1. push ebp / mov ebp, esp를 통해 스택 프레임을 생성하는 것을 확인할 수 있다. 2.에서는 내부적으로 CreateFileW() API를 호출하기 위해 스택을 통해 argument를 전달하는 모습을 확인할 수 있으며, 3.에서 leave 명령어(=mov esp, ebp / pop ebp)를 통해 스택을 정리한다.
(2) Stack64.exe (x64)
x64dbg를 이용해 Stack64.exe를 열어보자.
우선 스택 프레임 부분을 보면 코드 시작 전에 sub rsp, 48을 통해 스택을 늘리고, ret 전에 add rsp,48을 통해 스택을 해제하는 것을 볼 수 있다. 또한 argument를 넘길 때 레지스터를 이용하고 앞에서 4개를 넘어서는 경우 mov 연산을 통해 늘어난 스택에 넘겨주는 것을 확인할 수 있다. 이때 넘어가는 5번째 argument는 [rsp]가 아닌 [rsp+20]에 저장되는 것을 확인할 수 있는데, 앞의 4개의 argument를 위한 공간을 스택에 예약하는 특징이 있기 때문이다. CreateFileA() API 내부로 들어가도 마찬가지의 형태를 보인다.
3. Comment
리버싱 핵심원리 책 초반부에 stack frame에 대한 내용을 배우고 문제들을 풀면서 디버깅을 진행할 때 함수를 찾으려 push ebp / mov ebp, esp 코드를 찾으려 했지만 찾아지지 않았던 기억이 있다. 오늘 실습을 진행하면서 x64와 x32의 차이점을 배움으로써 이전의 궁금증을 해소할 수 있었다.
반응형'Reverse Engineering' 카테고리의 다른 글
[리버싱 핵심원리 study] 40장 64비트 디버깅 (0) 2021.02.08 [리버싱 핵심원리 study] 38장 PE32+ (0) 2021.02.02 [리버싱 핵심원리 study] 36장 64비트 컴퓨팅 (0) 2021.02.01 [리버싱 핵심원리 study] 34장 고급 글로벌 API 후킹 - IE 접속 제어 (0) 2021.01.29 [리버싱 핵심원리 study] 33장 '스텔스' 프로세스 (0) 2021.01.26