2023. 2. 23. 18:31ㆍ코딩/공부 [STUDY]
오늘은 스택프레임과 rip,rsp,rbp 등을 복습하였다.
우선 정리를 시작하기에 앞서, cpu가 연산하는 방법과 이에 대한 간단한 설명을 하고 넘어가겠다.
1. CPU 구조
cpu의 모습을 간단하게 그림으로 그려보았다.
cpu에는 연산들을 처리하는 동안 자료들을 임시 저장하는 작지만 매우 빠른 초고속 저장장치인 레지스터가 존재한다.
그 레지스터에도 종류가 다양하다.
그중 먼저 알아볼 것은 RIP레지스터이다.
1. RIP 레지스터
rip에서 ip는 무슨 뜻일까?
바로 (instruction pointer) 이다.
여기서 instruction이란, CPU가 처리하는 기계어 명령어를 의미한다.
CPU의 역할을 매우 단순화 시켜서 이해해보면
(명령어를 실행하고 그 다음 명령어를 실행한다) 정도 이다.
그럼, CPU는 다음에 어떤 명령어를 실행할지 어떻게 알고 있을까?
이에 대한 해답은 RIP에 존재한다.
RIP레지스터는 다음에 실행할 명령어의 주소를 가지고 있는데,
0: ADD RAX, 3 ;rax에 3을 더한다
4: SUB RAX, 1 ;rax에 3을 뺀다
이러한 코드가 있다고 가정을 해보자.
해당 코드를 실행할때 RIP에는 어떤 값이 들어있을까?
ADD RAX, 3을 실행할때의 RIP값에는 바로 4가 들어가있다.
자, 이제 RIP를 알게 되었으니, 스택프레임에 대해 알아보자.
2. 스택 프레임
스택의 가장 큰 특징 중 하나는 바로 높은 주소에서 낮은 주소로 즉, 거꾸로 자라는 것이다.
이때, 스택의 가장 끝 부분을 바로 RSP ( stack pointer ) 레지스터가 가지고 있다.
여기서 질문, 스택이 커지면 rsp 레지스터는 커질까 작아질까?
정답은 '커진다' 이다.
스택의 최상단 부분을 rsp가 가르키고 있으므로, 스택이 커질때마다 rsp는 더 커진다고 생각하면 된다.
스택프레임을 한마디로 정의하자면, 함수 호출에 관한 정보를 저장하기 위한 자료구조. 라고 생각하면 된다.
자, 그러면 스택 프레임이 이제 어떻게 형성 되는가? 에 대해서 알아보자.
CALL 0x400500
이러한 코드가 있다고 가정해보자.
위의 CALL 인스트럭션은 0x400500에 위치한 함수를 호출하는 명령어이다.
그리고 CALL 인스트럭션은 내부적으로 2개의 명령어를 실행한다고 볼 수 있다.
PUSH RIP, JMP 0x400500
CALL인스트럭션을 뜯어보면 이런 모습이다.
자, 그러면 여기서 아래와 같은 코드가 주어졌을때, 스택에 푸시 되는 값은 무엇일까?
0x1000: CALL 0x400500
0x1005: ADD EAX, 8
정답은 바로 0x1005 이다.
이유는 우리가 CALL로 호출한 함수가 종료된 후, 돌아갈 곳이 0x1005라는 것을 알 수 있기 때문이다.
스택 프레임을 본다면 이런 모습일 것이다.
이 상태에서 다음 실행 될 명령어의 주소를 담고 있는 RIP 레지스터는 어떤 값을 가지게 될까?
정답은 0x400500이다. 왜냐하면 코드의 흐름을 함수가 위치한 곳으로 바꿔야 하기 때문이다.
이제 함수로 이동해보겠다.
PUSH RBP
MOV RBP, RSP
이것은 함수가 호출되면 가장 먼저 호출되는 코드조각이다.
이것을 우리는 함수 프롤로그 라고 부른다.
현재 함수를 호출하고 나서 스택의 상태는 이런 모습이다.
그리고 위의 PUSH RBP를 수행하면 RBP 레지스터가 스택에 푸시 되는데,
이것을 우리는 SFP라고 부른다.
그리고 나서, 2번째 줄의 코드가 수행되면,
RBP레지스터의 값을 RSP레지스터의 값으로 설정한다.
즉, RBP 레지스터는 저 부분을 가리키게 된다.
이 과정을 거치고 난 다음에는 지역변수를 위한 공간을 확보해야 하는데,
이 공간을 할당하기 위해서는 RSP에서 할당한 만큼의 공간을 빼줘야 한다.
SUB RSP, size
위와 같은 방식으로 공간을 확보하게 된다.
하지만, 지역변수는 절대적인 주소를 가지는게 아니기 때문에, RBP를 기준으로 하여
MOV RAX, QWORD [RBP - 0x8]
이런 방법으로 읽고,
MOV QWORD [RBP - 0x8], RAX
이와 같은 방법으로 쓰게 된다.
즉, RBP 레지스터는 지역변수에 접근하는 기준이 되는 것이다.
다시 여기서 CALL 인스트럭션을 이용하여 새로운 함수가 호출된다고 해보자.
그럼 다음과 같이 새로운 RET이 생성이 된다.
이때, RBP레지스터를 호출이 끝나고 돌아갈 때 복구해야
caller가 기존 자신의 지역변수를 계속 참조 할 수 있으므로, 이를 위해 SFP라는 것을 푸시 하게 된다.
이것이 SFP이다.
이제 함수 에필로그를 알아보자.
MOV RSP, RBP
우선 RSP에 RBP의 값을 저장한다
이런 상태에서
이 상태가 되는 것이다.
우선적으로 지역 변수를 위해 할당한 공간을 해제한다.
물론, 데이터가 실제로 지워지는 것은 아니다.
논리적으로 해제되었다는 의미이다.
그리고 나서,
POP SFP
를 수행하게 된다.
그리하여 caller 의 RBP로 복구되게 된다.
이제 최종적으로 RET 인스트럭션을 수행하여 실행 흐름을 caller로 복귀시키는 것이다.
'코딩 > 공부 [STUDY]' 카테고리의 다른 글
[STUDY] 어셈블리어 - 5 (0) | 2023.02.28 |
---|---|
[STUDY] 어셈블리어 - 4 (0) | 2023.02.25 |
[STUDY] 어셈블리어 - 2 (1) | 2023.02.23 |
[STUDY] 어셈블리어 - 1 (0) | 2023.02.22 |
[STUDY] Web - javascript prototype pollution (0) | 2022.07.18 |