Lecture
x86-64 어셈블리 명령어 Pt.2
Opcode: 스택
- x64 아키텍처에서는 다음의 명령어로 스택을 조작할 수 있다.
push val: val을 스택 최상단에 쌓음 |
연산 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: 스택 최상단의 값을 꺼내서 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
Opcode: 프로시저
- 특정 기능을 수행하는 코드 조각을 말한다.
- 반복되는 연산을 프로시저 호출로 대체할 수 있다.
- 기능별로 코드 조각에 이름을 붙일 수 있게 되어 가독성이 높아진다.
- 프로시저를 부르는 행위를 호출(Call), 프로시저에서 돌아오는 것을 반환(Return)이라 부른다.
- 프로시저를 호출할 때는 프로시저를 실행하고 나서 원래의 실행 흐름으로 돌아와야 하기 때문에 call 다음의 명령어 주소(return address, 반환 주소)를 스택에 저장하고 프로시저로 rip를 이동시킨다.
- x64어셈블리언어에는 프로시저의 호출과 반환을 위해 call, leave, ret 명령어가 있다.
call addr: 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: 스택 프레임 정리 |
연산 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
스택프레임이란?
- 함수별로 서로가 사용하는 스택의 영역을 명확히 구분하기 위하여 스택프레임이 사용된다.
- 우분투 18.04에서 함수는 호출될 때 자신의 스택프레임을 만들고, 반환할 때 이를 정리한다.
ret: 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
Opcode: 시스템 콜
- 운영체제는 연결된 모든 하드웨어 및 소프트웨어에 접근, 제어할 수 있는 권한이 있다.
- 이 권한을 해킹으로부터 보호하기 위해 커널모드와 유저모드로 권한을 나눈다.
커널모드
- 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다.
- 파일시스템, 입력/출력, 네트워크 통신, 메모리 관리 등 모든 저수준의 작업은 커널모드에서 진행된다.
- 해커가 커널모드까지 진입하게 되면 시스템은 거의 무방비 상태가 된다.
유저모드
- 운영체제가 사용자에게 부여하는 권한이다.
- 브라우저를 이용해 웹서핑을 하거나, 게임을 하고 프로그래밍을 하는 것 모두 유저모드에서 이루어진다.
- 유저모드에서는 해커가 진입해도 커널의 막강한 권한을 보호할 수 있다.
시스템 콜
- 유저모드에서 커널모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용된다.
- 예를 들어, cat flag라는 명령어를 실행하면, cat은 flag라는 파일을 읽어서 사용자의 화면에 출력해줘야 한다. 이때 flag는 파일 시스템에 존재하므로 이를 읽으려면 파일시스템에 접근할 수 있어야하는데, 유저모드에서는 할 수 ㅇ벗으므로 커널이 도움을 줘야한다. 여기서 도움이 필요하다는 요청을 하는것이 시스템 콜 이다.
- x64아키텍처에는 시스템콜을 위해 syscall 명령어가 있다.
syscall |
요청: rax 인자 순서: rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> stack |
- 예제)
[Register]
rax = 0x1
rdi = 0x1
rsi = 0x401000
rdx = 0xb
[Memory]
0x401000 | "Hello Wo"
0x401008 | "rld"
[Code]
syscall
- 결과)
Hello World
- 해석)
위의 syscall table을 보면, rax가 0x1일 때, 커널에 write 시스템콜을 요청한다. 이때 rdi, rsi, rdx가 0x1, 0x401000, 0xb이므로 커널은 write(0x1, 0x401000, 0xb)를 수행한다.
write함수의 각 인자는 출력 스트림, 출력 버퍼, 출력 길이를 나타낸다. 여기서 0x1은 stdout이며, 이는 일반적으로 화면을 의미한다. 0x401000에는 Hello World가 저장되어있고, 길이는 0xb로 지정되어있으므로, 화면에 Hello World가 출력된다.
x64 syscall 테이블
- 시스템 콜 테이블의 일부이다.
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
read | 0x00 | unsigned int fd | char *buf | size_t count |
write | 0x01 | unsigned int fd | const char *buf | size_t count |
open | 0x02 | const char *filename | int flags | umode_t mode |
close | 0x03 | unsigned int fd | ||
mprotect | 0x0a | unsigned long start | size_t len | unsigned long prot |
connect | 0x2a | int sockfd | struct sockaddr * addr | int addrlen |
execve | 0x3b | const char *filename | const char *const *argv | const char *const *envp |
Note
요약 | |
스택 | - push val: rsp를 8만큼 빼고, 스택의 최상단에 val을 쌓는다. - pop reg: 스택 최상단의 값을 reg에 넣고, rsp를 8만큼 더한다. |
프로시저 | - call addr: addr의 프로시저를 호출한다. - leave: 스택 프레임을 정리한다. - ret: 호출자의 실행 흐름으로 돌아간다. |
시스템 콜 | - syscall: 커널에게 필요한 동작을 요청한다. |
'문제 풀이 > [DreamHack]' 카테고리의 다른 글
[DreamHack] System Hacking Stage2 - Quiz: x86 Assembly 2 (0) | 2022.12.20 |
---|---|
[DreamHack] System Hacking Stage2 - Quiz: x86 Assembly 1 (0) | 2022.12.20 |
[DreamHack] SystemHacking Stage2 - x86 Assembly: Essential Part 1 (0) | 2022.12.20 |
[DreamHack] SystemHacking Stage2 - Quiz: Computer Architecture (0) | 2022.12.19 |
[DreamHack] SystemHacking Stage2 - Background: Computer Architecture (0) | 2022.12.19 |