[Dreamhack] Background: 3 - x86 Assembly - 2

securitykss·2022년 10월 21일
0
post-thumbnail

이 글은 https://dreamhack.io/lecture/courses/37, https://dreamhack.io/lecture/courses/63 토대로 작성한 글입니다.

1. Introduction

지난 시간 x86 Assembly - 1에서 데이터 이동, 산술 연산, 논리 연산, 비교, 분기 명령어에 대해서 알아보았다.

이번 시간에는 스택, 프로시저, 시스템콜에 관련된 명령어에 관하여 알아보겠다.

2. x86-64 Assembly Language - (con't)

2.1 스택

설명

스택과 관련된 명령어로는 대표적으로 push, pop가 있다.

push와 pop은 함수의 Prologue, Epilogue 에서 자주 사용된다.

소개

push, pop

push 명령어

push val : val을 스택 최상단에 쌓음

push val은

rsp -= 8
[rsp] = val 를 합친 것이다.


ex)
[Register]
rsp = 0x7fffffffc400

[Stack]     
0x7fffffffc400 | 0x0 <= rsp
0x7fffffffc408 | 0x0

[Code]
push 0x31337


result)
[Register]
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp      ; push 0x31337로 인해서 rsp값이 -8이 되고, rsp가 가리키는 주소 안에 0x31337(val)이 들어간 모습을 확인 할 수 있다.
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

pop 명령어

pop reg : 스택 최상단의 값을 꺼내서 reg에 대입

pop reg는

rsp += 8
reg = [rsp - 8] 를 합친 것이다.


ex)
[Register]
rax = 0
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp      ; 현재 스택 위치
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

[Code]
pop rax


result)
[Register]
rax = 0x31337                         ; 이전 스택 위치인 0x7fffffffc3f8 안의 값인 0x31337을 pop rax를 통해서 rax에 0x31337이 들어 간 모습을 확인 할 수 있다.
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc400 | 0x0 <= rsp      ; 스택 위치가 +8이 된 모습을 확인 할 수 있다.
0x7fffffffc408 | 0x0

push는 '스택을 늘리고 스택 최상단에 값을 넣는다' 라고 생각하면 되고,

pop은 '스택을 줄이고 스택 최상단의 값을 꺼낸다' 라고 생각하면 된다.

2.2 프로시저

설명

프로시저(Procedure)란 컴퓨터 과학에서 특정 기능을 수행하는 코드 조각을 말한다.

프로시저를 부르는 행위를 호출(Call), 프로시저에서 돌아오는 것을 반환(Return)이라고 부른다.

그리고 프로시저를 호출할 때는 프로시저를 실행하고 나서 원래의 실행 흐름으로 돌아와야 하므로,

call 다음의 명령어 주소(return address, 반환 주소)를 스택에 저장하고 rip를 이동시킨다.

x64 어셈블리언어에서는 프로시저의 호출과 반환을 위해 대표적으로 call, leave, ret 명령어가 있다.

소개

call, leave, ret

call

call addr : addr에 위치한 프로시져 호출

call addr은

push return_address
jmp addr 를 합친 것이다.

return_address는 call로 addr에 위치한 프로시져를 호출 할 때, call로 호출한 위치(현 rip)에서 그 다음 명령어 주소를 스택에 넣는다.

leave

leave: 스택프레임 정리

leave는

mov rsp, rbp
pop rbp 를 합친 것이다.

ret

ret : return address로 반환

ret은
pop rip 와 같다.

예제를 들면서 설명 하겠습니다

1) call로 hello 함수 호출

현재 call rip가 call함수에 위치해 있다.

현재 스택 상태: rbp = 0x7fffffffdd40, rsp = 7fffffffdd30

2) call로 hello 함수 호출 후 상황

성공적으로 rip가 hello함수의 시작 주소로 옮겨간 모습을 확일 할 수 있다.

그리고 또한 rsp = 0x7fffffffdd28(이전 rsp보다 -8)이 되고, rsp 주소 안에 main+34(return address)가 저장된 모습을 확인 할 수 있다. ([<main+29> call hello]에서 그 다음 명령어 [<main+34> mov eax,0]의 주소가 return address이다)

3) push rbp

현재 rip가 [push rbp]에 위치해 있다.

4) push rbp 결과

[push rbp]를 통해 rsp = 0x7ffffffdd20(이전 rsp보다 -8)이 되고, 현재 rsp 주소 안에 rbp를 넣는다.

여기서 질문! 왜 rbp를 스택에 넣는 것일까?

왜냐하면 기존의 스택 프레임인 main에서의 스택 프레임을 까먹지 않기 위해서 스택에 저장한다.(스택 프레임에 대해서는 나중에 다뤄 보겠다.)

현재 rip가 위치한 [mov rbp, rsp]의 결과를 보겠다.

5) mov rbp, rsp 결과

[mov rbp, rsp]를 통해서 rbp = rsp를 만들어 준다.

이렇게 만들어 주는 이유는 hello함수의 스택 프레임을 만들기 위해서이다.

[sub rsp, 0x10]의 결과를 보자.

6) sub rsp, 0x10 결과

hello 함수에서의 스택 프레임의 공간을 확장하기 위해서 [sub rsp, 0x10]을 통해 스택의 공간을 넓혀준 모습이다.

7) hello에서 다시 main으로

hello 함수에서 할일을 마치고 leave 명령어를 통해 main으로 돌아갈 준비를 한다.

8) leave 명령어 결과

leave 는 [mov rsp, rbp]와

               [pop rbp]를 합친 것과 같다고 언급했었다.

따라서 rsp = rbp = 0x7fffffffdd20으로 만든 후,

pop rbp를 통해서 rsp = rsp -8 -> rsp = 0x7fffffffdd28로 만들고,

0x7ffffffdd28에 있던 기존의 스택 프레임 값(main의 스택프레임)인 0x7fffffffdd40을 가져와서 rbp에 다시 넣어 준다.

이제 ret의 결과를 보자

9) ret 결과

ret을 통해서

1. rsp = 0x7fffffffdd28 -> (main+34)를 rsp = rsp + 8을 해서 rsp = 0x7fffffffdd30로 만들어 기존 main에서의 스택 프레임을 복구 해 주고

2. 0x7fffffffdd28(이전 rsp)에 있던 값인 main+34(return address)를 rip에 넣어서 현재 프로그램 실행 위치를 다시 main stream으로 오게 끔 해준다.(stream은 프로그램의 흐름)

여기까지 프로시저에 대한 설명이다.(개인적으로 가장 기본이자 가장 중요한 부분이라 생각한다)

이제 시스템 콜(syscall)에 대해 알아보자.

2.4 시스템 콜

설명

운영체제는 해킹으로부터 시스템 권한을 보호하기 위해 커널 모드(Kernel mode)와 유저 모드(User mode)로 권한을 나눈다.

커널 모드(Kernel mode)는 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다.

파일시스템, 입/출력, 네트워크 통신, 메모리 관리 등 저수준의 작업은 커널 모드에서 진행된다.

이러한 작업들을 해커에 의해 제어 당한다면, 개인 정보 유출 등 막심한 피해를 당하게 된다.

그렇기에 운영체제는 따로 분리해서 관리를 한다.

유저 모드(User mode)는 운영체제가 사용자에게 부여하는 권한이다.

유튜브, 게임, 프로그래밍 등 모두 유저모드에서 진행된다.

유저 모드에서 해킹이 발생해도 해커로부터 커널의 권한들을 보호할 수 있다.

시스템 콜(system call, syscall)은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어던 동작을 요청하기 위해 사용된다.

쉽게 말해, 유저 모드에서 커널의 권한을 잠시 빌린다고 생각하면 된다.

소개

syscall

요첨: rax

인자 순서: rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> stack

간략한 설명: syscall을 요청할 때 rax 등 레지스터로 인자를 넘겨 준 뒤에 작업을 실행 한다.

아래 테이블을 보면 읽기, 쓰기, 파일 열기 등 syscall 함수의 여러 작업들을 확인 할 수 있다.

syscall에 대한 작업은 300개 정도에 달한다. 그리고 여기서 이해가 잘 안되어도 공부를 하다보면 점점 익숙해 진다.

(저도 물론 처음엔 외우려고 하다가, 점점 문제를 풀다 보면서 익숙해졌답니다. ㅎㅎ)

3. Conclusion

스택, 프로시저, syscall에 대해 알아보는 시간이였다.

스택(push,pop), 프로시저(call, leave, ret), syscall(read, write, open, execve 등)

마치며

다음 시간에는, gdb와 pwntool을 사용하는 법을 알아보자.

Reference

https://dreamhack.io/lecture/courses/37, https://dreamhack.io/lecture/courses/63
https://learn.dreamhack.io/63#8 (그림 출처)

profile
보안 공부를 하는 학생입니다.

0개의 댓글