저번 시간에는 기계어와 어셈블리 기본구조, 산술연산, 논리연산, 비교, 분기를 공부하였다.
다시 공부하고 싶다면 밑에 링크를 참고하면 된다.
이어서 이번 시간에는 스택, 프로시저, 시스템콜에 관련된 어셈블리를 공부할 것이다.
1. Opcode : 스택
스택을 조작할 수 있는 명령어로 1)push, 2)pop이 있다.
1) push
push val : val을 스택 최상단에 쌓는다.
연산 rsp -= 8 [rsp] = val |
2) pop
pop reg : 스택 최상단의 값을 꺼내서 reg에 대입한다.
연산 rsp += 8 reg = [rsp-8] |
2. Opcode : 프로시저
프로시저는 특정 기능을 수행하는 코드 조각이다. 반복되는 연산을 프로시저 호출로 대체하여 코드 크기를 줄이며, 기능별로 코드 조각에 이름을 붙여 코드의 가독성을 높일 수 있다. 프로시저는 호출(Call)과 반환(Return)이 있다.
- 호출(Call) : 프로시저를 부르는 행위
- 반환(Return) : 프로시저에서 돌아오는 행위
프로시저를 호출할 때는 프로시저를 실행하고 나서 원래의 실행 흐름으로 돌아와야 하므로, call 다음의 명령어 주소(반환 주소)를 스택에 저장하고 프로시저로 rip를 이동시킨다. x64어셈블리언어는 프로시저의 호출과 반환을 위한 1)call, 2)leave, 3)ret 명령어가 있다.
1) call
call addr : addr에 위치한 프로시져 호출
연산 push return_address jmp addr |
2) leave
leave : 스택프레임 정리
연산 mov rsp, rbp pop rbp |
3) ret
ret : return address로 반환
연산 pop rip |
3. Opcode : 시스템 콜
운영체제는 컴퓨터 자원의 효율적인 사용 및 사용자에게 편리한 경험을 제공하기 위해 내부적으로 복잡한 동작을 한다. 또한 하드웨어, 소프트웨어에 접근하여 이들을 제어할 수 있다. 그리고 해킹으로 부터 보호하기 위해 1)커널 모드와 2)유저 모드로 권한을 나눈다.
1) 커널 모드
운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다. 파일 시스템, 네트워크 통신, 메모리 관리 등 사용자 모르게 커널 모드에서 진행된다. 커널 모드에서 시스템의 모든 부분을 제어하기 때문에 해커가 커널 모드까지 집입하게 되면 시스템은 무방비 상태가 된다.
2) 유저 모드
운영체제가 사용자에게 부여하는 권한이다. 프로그래밍하거나 브라우저를 이용해 유튜브 시청등 유저 모드에서 이루어 진다. 유저 모드는 해킹이 발생해도 유저 모드 권한까지 밖에 획득하지 못하므로 커널의 권한을 보호할 수 있다.
시스템 콜(syscall)은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 동작을 요청하기 위해 사용된다.
1) syscall
필요한 기능과 인자에 대한 정보를 레지스터로 전달하고 커널이 이를 읽어 요청을 처리한다.
요청 : rax
인자 순서 : rdi - rsi - rdx - rcx - r8 - r9 - stack
2) 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 |
<시스템 콜 예제>
[Register]
rax = 0x1
rdi = 0x1
rsi = 0x401000
rdx = 0xb
[Memory]
0x401000 | "Hello Wo"
0x401008 | "rld"
[Code]
syscall
[result]
Hello World
<시스템 콜 예제 설명>
syscall table을 참고하여 rax가 0x01이면 커널에 write 시스템 콜을 요청한다. 그리고 인자 rdi = 0x01, rsi = 0x401000, rdx = 0xb 이므로 write(0x01, 0x401000, 0xb)를 수행한다. write 함수의 인자는 각각 출력 스트림, 출력 버퍼, 출력 길이이다. 0x01은 stdout, 0x401000은 Hello World, 길이는 0xb이다. 결과로 Hello World가 출력된다.
이번 시간에는 스택, 프로시저, 시스템콜에 관련된 어셈블리를 공부하였다.
다음 시간에는 리눅스 대표적인 디버거 중 하나인 gdb 기능들과 사용법을 공부할 것이다.
'개념정리 > System Hacking' 카테고리의 다른 글
6. pwntools (0) | 2022.08.01 |
---|---|
5. gdb 디버거 (0) | 2022.07.21 |
4-1. 어셈블리(Assembly) - 산술연산, 논리연산, 비교, 분기 (2) | 2022.07.06 |
3. 리눅스 프로세스의 메모리 구조 (0) | 2022.07.04 |
2. x86-64 아키텍처 (0) | 2022.07.04 |