[Assembly] Assembly - 2

EUGENE·2023년 4월 18일
0

⭐ 이번 시간은 저번 시간보다 중요한 명령어를 배운다!

스택 Stack

push

val을 스택 최상단에 쌓음

📌 rsp란?

현재 스택의 최상단 주소를 갖고있는 레지스터

1CAC095C-4B9F-4343-88A3-1B2D1FAF26DE.jpeg

rsp -= 8 ; 왜 감소인가? 메모리 방향때문 어쩌구 저쩌
[rsp] = val
;[Register]
rsp = 0x7fffffffc400

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

;[Code]
push 0x31337
;[Register]
rsp = 0x7fffffffc3f8

;[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp 
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

pop

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

rsp += 8 ; 스택 주소는 반대임 그냥 받아들일 것
reg = [rsp-8]
;[Register]
rax = 0
rsp = 0x7fffffffc3f8

;[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp 
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

;[Code]
pop rax
;[Register]
rax = 0x31337
rsp = 0x7fffffffc400

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

프로시저(Procedure)

📌 프로시저란?

특정 기능을 수행하는 코드 조각, 어셈블리어에 함수같은 거라고 생각하자

  • 호출 Call : 프로시저를 부르는 행위
  • 반환 Return : 프로시저에서 돌아오는 것

⭐ 호출할때는 프로시저 실행 후 원래 실행 흐름으로 돌아와야 하므로 call 다음에 명령어 주소(return address, 반환주소)를 스택에 저장하고 프로시저로 rip를 이동

call

addr에 위치한 프로시져 호출

push return_address
jmp addr
;[Register]
rip = 0x400000
rsp = 0x7fffffffc400 

;[Stack]
0x7fffffffc3f8 | 0x0
0x7fffffffc400 | 0x0 <= rsp

;[Code]
0x400000 | call 0x401000  <= rip
0x400005 | mov esi, eax
...
0x401000 | push rbp
;[Register]
rip = 0x401000
rsp = 0x7fffffffc3f8

;[Stack]
0x7fffffffc3f8 | 0x400005  <= rsp ; 다시 돌아갈 주소가 스택에 쌓임
0x7fffffffc400 | 0x0
[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax
...
0x401000 | push rbp  <= rip

leave

스택 프레임 정리

  • 📌 스택프레임이란? 구구절절한 설명 고고 스택은 함수별로 자신의 지역변수 또는 연산과정에서 부차적으로 생겨나는 임시 값들을 저장하는 영역입니다. 만약 이 스택 영역을 아무런 구분 없이 사용하게 된다면, 서로 다른 두 함수가 같은 메모리 영역을 사용할 수 있게 됩니다. 예를 들어 A라는 함수가 B라는 함수를 호출하는데, 이 둘이 같은 스택 영역을 사용한다면, B에서 A의 지역변수를 모두 오염시킬 수 있습니다. 이 경우, B에서 반환한 뒤 A는 정상적인 연산을 수행할 수 없습니다. 따라서 함수별로 서로가 사용하는 스택의 영역을 명확히 구분하기 위해 스택프레임이 사용됩니다. 우분투 18.04에서 함수는 호출될 때 자신의 스택프레임을 만들고, 반환할 때 이를 정리합니다.

📌 rbp란?

스택의 시작점, 바닥을 가리키고 있는 레지스터

rsp은 꼭대기이므로 반대

mov rsp, rbp
pop rbp
;[Register]
rsp = 0x7fffffffc400
rbp = 0x7fffffffc480

;[Stack]
0x7fffffffc400 | 0x0 <= rsp
...
0x7fffffffc480 | 0x7fffffffc500 <= rbp
0x7fffffffc488 | 0x31337 

;[Code]
leave
;[Register]
rsp = 0x7fffffffc488
rbp = 0x7fffffffc500

;[Stack]
0x7fffffffc400 | 0x0
...
0x7fffffffc480 | 0x7fffffffc500
0x7fffffffc488 | 0x31337 <= rsp
...
0x7fffffffc500 | 0x7fffffffc550 <= rbp

red

return address로 반환

pop rip
;[Register]
rip = 0x401008
rsp = 0x7fffffffc3f8
[Stack]
0x7fffffffc3f8 | 0x400005    <= rsp
0x7fffffffc400 | 0

;[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax
...
0x401000 | mov rbp, rsp  
...
0x401007 | leave
0x401008 | ret  <= rip
;[Register]
rip = 0x400005
rsp = 0x7fffffffc400
[Stack]
0x7fffffffc3f8 | 0x400005
0x7fffffffc400 | 0x0    <= rsp

;[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax   <= rip
...
0x401000 | mov rbp, rsp  
...
0x401007 | leave
0x401008 | ret

스택 프레임의 할당과 해제

  • 아주 좋은 예시 스크린샷 2023-04-06 오전 11.36.21.png 스크린샷 2023-04-06 오전 11.37.13.png 스크린샷 2023-04-06 오전 11.37.35.png 스크린샷 2023-04-06 오전 11.37.49.png 스크린샷 2023-04-06 오전 11.38.06.png 스크린샷 2023-04-06 오전 11.38.21.png 스크린샷 2023-04-06 오전 11.38.32.png 스크린샷 2023-04-06 오전 11.38.45.png 스크린샷 2023-04-06 오전 11.38.59.png

시스템 콜 System Call

배경

  • 구구절절한 설명 윈도우, 리눅스, 맥 등의 현대 운영체제는 컴퓨터 자원의 효율적인 사용을 위해, 그리고 사용자에게 편리한 경험을 제공하기 위해, 내부적으로 매우 복잡한 동작을 합니다. 운영체제는 연결된 모든 하드웨어 및 소프트웨어에 접근할 수 있으며, 이들을 제어할 수도 있습니다. 그리고 해킹으로부터 이 막강한 권한을 보호하기 위해 커널 모드와 유저 모드로 권한을 나눕니다. 커널 모드는 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한입니다. 파일시스템, 입력/출력, 네트워크 통신, 메모리 관리 등 모든 저수준의 작업은 사용자 모르게 커널 모드에서 진행됩니다. 커널 모드에서는 시스템의 모든 부분을 제어할 수 있기 때문에, 해커가 커널 모드까지 진입하게 되면 시스템은 거의 무방비 상태가 됩니다. 이에 대해서는 나중에 Linux Kernel Exploit 커리큘럼에서 자세히 배울 수 있습니다. 유저 모드는 운영체제가 사용자에게 부여하는 권한입니다. 브라우저를 이용하여 드림핵을 보거나, 유튜브를 시청하는 것, 게임을 하고 프로그래밍을 하는 것 등은 모두 유저 모드에서 이루어집니다. 리눅스에서 루트 권한으로 사용자를 추가하고, 패키지를 내려 받는 행위 등도 마찬가지입니다. 유저 모드에서는 해킹이 발생해도, 해커가 유저 모드의 권한까지 밖에 획득하지 못하기 때문에 해커로 부터 커널의 막강한 권한을 보호할 수 있습니다. 시스템 콜(system call, syscall)은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용됩니다. 소프트웨어 대부분은 커널의 도움이 필요합니다. 예를 들어, 사용자가 cat flag를 실행하면, cat은 flag라는 파일을 읽어서 사용자의 화면에 출력해 줘야 합니다. 그런데 flag는 파일 시스템에 존재하므로 이를 읽으려면 파일시스템에 접근할 수 있어야 합니다. 유저 모드에서는 이를 직접 할 수 없으므로 커널이 도움을 줘야 합니다. 여기서, 도움이 필요하다는 요청을 시스템 콜이라고 합니다. 유저 모드의 소프트웨어가 필요한 도움을 요청하면, 커널이 요청한 동작을 수행하여 유저에게 결과를 반환합니다. x64아키텍쳐에서는 시스템콜을 위해 syscall 명령어가 있습니다. 스크린샷 2023-04-06 오전 11.42.22.png
  1. 커널 모드 Kernel mode
  2. 유저 모드 User mode

시스템 콜 System Call

유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용

요청: rax

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

  • 구구절절한 해석 오른쪽의 syscall table을 보면, rax가 0x1일 때, 커널에 write 시스템콜을 요청합니다. 이때 rdi, rsi, rdx가 0x1, 0x401000, 0xb 이므로 커널은 write(0x1, 0x401000, 0xb)를 수행하게 됩니다. write함수의 각 인자는 출력 스트림, 출력 버퍼, 출력 길이를 나타냅니다. 여기서 0x1은 stdout이며, 이는 일반적으로 화면을 의미합니다. 0x401000에는 Hello World가 저장되어 있고, 길이는 0xb로 지정되어 있으므로, 화면에 Hello World가 출력됩니다.
;[Register]
rax = 0x1   
rdi = 0x1   
rsi = 0x401000  
rdx = 0xb   

;[Memory]
0x401000 | "Hello Wo"   
0x401008 | "rld"    
[Code]  
syscall

;-----------------;
;output
Hello World
profile
한 줄로 소개하기엔 여백이 좁아 적지 않겠습니다.

0개의 댓글