반응형
서론
- 지난번엔 산술연산, 논리연산, 비교, 분기의 어셈블리 명령어를 배웠다.
- 산술연산: ad, sub, inc, dec
- 논리연산: and, or, xor, not
- 비교: cmp, test
- 분기: jmp, je, jg
- 이 강의에선 운영체제의 핵심구조인, 스택, c언어의 함수에 대응되는 프로시저 그리고 시스템 해킹의 관점에서 매우 중요한 시스템콜과 관련된 어셈블리를 배운다.
- 스택: push, pop
- 프로시져: call, leave, ret
- 시스템콜: syscall
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: 프로시저 📜
- 프로시저(Procedure): 특정 기능을 수행하는 코드 조각이다.
- 프로시저를 사용하면 반복되는 연산을 프로시저 호출로 대체할 수 있어서 전체 코드의 크기를 줄일 수 있으며, 기능별로 코드 조각에 이름을 붙일 수 있게 되어 코드의 가독성을 크게 높일 수 있다.
- 호출(Call): 프로시저를 부르는 행위
(프로시저를 호출할 때는 프로시저를 실행하고 나서 원래의 실행 흐름으로 돌아와야 한다.) - 반환(Return): 프로시저에 돌아오는 것
(call 다음의 명령어 주소(Return Address, 반환 주소)를 스택에 저장하고 프로시저로 ip을 이동한다.) X64 어셈블리언어에는 프로시저의 호출과 반환을 위한 call, leave, ret 명령어가 있따.
- 프로시저(Procedure): 특정 기능을 수행하는 코드 조각이다.
- 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
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
💡스택프레임
- 스택: 함수별로 자신의 지역변수 또는 연산과정에서 부차적으로 생겨나는 임시 값들을 저장하는 영역이다.
- 스택영역을 아무런 구분없이 사용하게되면, 서로 다른 두 함수가 같음 메모리 영역을 사용할 수 있게 된다.
- 예를들어 A라는 함수가 B라는 함수를 호출하는데, 이 둘이 같은 스택 영역을 사용한다면, B에서 A의 지역변수를 모두 오염시킬 수 있다. 이경우, B에서 반환한 뒤 A는 정산적인 연산을 수행할 수 없따.
- 스택프레임: 따라서 함수별로 서로가 사용하는 스택의 영역을 명홧히 구분하기 위해 스택프레임이 사용된다.
- 대부분 Application binary interfacel(ABI)에서 함수는 호출될 떄 자신의 스택프레임을 만들고, 반환할떄 이를 정리한다.
- Opcode: 시스템 콜📟
- 윈도우, 리눅스, 맥등 현대 운영체제는 컴퓨터 자원의 효율적인 사용/사용자에게 편리한 경험을 제공하기 위해 복잡한 동작을 한다.
- 운영체제는 연결된 모든 하드웨어 및 소프트웨어에 접근할 수 있고, 제어할 수도 있따.
그리고 해킹으로 부터 권한을 보호하기 위해 커널 모드와, 유저 모드로 권한을 나눈다.
- 커널 모드:
- 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다.
- 모든 저수준의 작업(파일시스템, 입/출력, 네트워크 통신, 메모리 관리 )는커널 모드에서 진행된다.
시스템의 모든 부분을 제어할 수 있어서, 해커가 진입하면 시스템의 거의 무방비 상태가 된다.
- 유저모드::
- 운영체제가 사용자에게 부여하는 권한이다.
- 브라우저 사용, 유튜브 시청, 게임 모두 유저 모드에서 이루어진다.
리눅스에서 루트권한으로 사용자를 추가하고, 패키지를 내려 받는 행위도유저모드에서 이루어진다. - 유저모드에서 해킹이 발생해도, 해커는 유저모드 권한 밖에 획득하지 못해서 커널의 막강한 권한을 보호할 수 있다.
- 시스템 콜(system call, syscall)
- 유저모드에서 커널 모드의 시스템 소프트웨어에게 동작을 요청하기 위해 사용된다.
- 소프트웨어 대부분은 커널의 도움이 필요하다.
- 예를들어, 삳용자가 'cat flag'를 실행하면, cat은 flag라는 파일을 읽어서 사용자의 화면에 출력해줘야 한다.
flag는 파일 시스템에 존재하여 이를 읽으려면 파일시스템에 접근 할 수 있어야 한다.유저모드에선 이를 직접 할 수 없어서 커널이 도움을 줘야한다. 여기서 도움이 필요하다는 요청이 시스템 콜이라고 한다.
- 예를들어, 삳용자가 'cat flag'를 실행하면, cat은 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 테이블
결론
1. 스택
- push val: rsp를 8만큼 뺴고, 스택의 최상단에 val을 쌓습니다.
- pop reg: 스택 최상단의 값을 reg에 넣고, rsp를 8만큼 더한다.
2. 프로시저
- call addr: addr의 프로시저를 호출한다.
- leave: 스택 프레임을 정리한다.
- ret: 호출자의 실행 흐름으로 돌아간다.,
3. 시스템 콜
- syscall: 커널에게 필요한 동작을 요청한다.
끝
반응형
'hacking > pwnable' 카테고리의 다른 글
[Dream hack] Quiz: x86 Assembly 1-1 (0) | 2024.05.16 |
---|---|
[Dream hack] Exploit Tech: Shellcode (0) | 2024.05.16 |
[Dream hack] x86 Assembly🤖: Essential Part(1) (0) | 2024.05.01 |
[Dream hack] Quiz: Linux Memory Layout-7 (0) | 2024.05.01 |
[Dream hack] Quiz: Linux Memory Layout-6 (0) | 2024.05.01 |