미역줄기의 이모저모
[ Dreamhack ] 어셈블리어와 x86-64(2) 본문
Opcode: 스택
push val: val을 스택 최상단에 쌓음
#예제
[Register]
rsp = 0x7fffffffc400
[Stack]
0x7fffffffc400 | 0x0 <- rsp
0x7fffffffc408 | 0x0
[Code]
push 0x31337
#결과
[Register]
rsp = 0x7fffffffc3f8
[Stack]
0x7fffffffc3f8 | 0x31337 <- rsp
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0
rsp는 스택 포인터이다. 스택 포인터는 프로그램이 현재 스택 프레임에서 사용 중인 스택의 최상단을 가리키는 레지스터이다. 0x7fffffffc400는 rsp레지스터의 현재 값인 메모리 주소를 나타낸다.
push 0x31337 명령어로 0x31337이라는 16진수 값이 스택에 푸쉬되었으며 rsp가 새로운 값의 위치를 가리키게 된다.
0x7fffffffc3f8 | 0x31337 여기서 ' 0x7fffffffc3f8 ' 이 부분은 메모리 주소를 나타내고 ' 0x31337 '은 메모리 주소에 저장된 16진수 값이다.
따라서 ' 0x7fffffffc3f8 ' 주소에 있는 메모리 위치에 ' 0x31337 '이라는 값이 저장되어 있는 것이다.
pop reg: 스택 최상단의 값을 꺼내서 reg에 대입
#예제
[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
초기 스택에는 ' 0x31337 '이라는 값이 있고, rsp는 해당 값의 위치를 가리키고 있다.
명령어 ' pop rax ' 는 스택에서 최상단의 값을 pop하며 레지스터 ' rax '에 저장하는 역할을 한다. 따라서 스택에서 값 ' 0x31337 '을 pop하여 ' rax '레지스터에 저장한다.
결과를 보면 ' rax '는 스택의 최상단 값이었던 ' 0x31337 '이 저장되었고, 스텍에선 pop되어서 rsp는 스택의 새로운 맨 위를 가리키게 된다.
Opcode: 프로시저
프로시저란 특정 기능을 수행하는 코드 조각이다. 프로시저를 사용하면 반복되는 연산을 프로시저 호출로 대체할 수 있어서 전체 코드의 크기를 줄일 수 있다.
프로시저를 부르는 행위를 호출(call)이라고 부르며, 프로시저에서 돌아오는 것을 반환(return)이라고 부른다. 이때 call 다음의 명령어 주소(return address, 반환 주소)를 스택에 저장하고 프로시저로 rip를 이동시킨다.
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
초기에 ' rip '는 ' 0x400000 '을, ' rsp '는 ' 0x7fffffffc400 '을 가리키고 있다. 스택에는 아무런 값도 없다.
0x400000 | call 0x401000: 이 명령어는 현재 rip의 값(0x400000)을 스택에 푸시하고, 그 다음에 주어진 주소(0x401000)로 점프한다. 현재 ' rip '인 ' 0x400000 '이 스택에 푸쉬되고, ' rip '은 ' 0x401000 '으로 업데이트 된다.
' 0x400005 '에서 ' mov esi, eax '명령어를 실행하고, 코드는 계속 진행된다. ' 0x401000 '에 도달하면서, ' rip '이 ' 0x401000 '을 카리키게 된다.
' rip '는 ' 0x401000 ' 을 가리키게 되고, 스택에는 이전에 푸쉬한 ' 0x400000 '이 남아있다. 따라서 ' 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
rsp가 스택의 맨 위(0x7fffffffc400)를 가리키고 있고, rbp가 스택의 일정한 위치(0x7fffffffc480)를 가리키고 있다. 스택에는 일부 데이터가 존재하고, 0x7fffffffc488 위치에는 0x31337이라는 값이 있다.
leave 명령어는 보통 함수의 프롤로그 부분에서 사용되어, 스택 프레임을 정리하는 데에 쓰인다. mov rsp, rbp는 현재 스택 프레임의 rbp 값을 rsp에 복사하고, pop rbp는 스택에서 값을 팝하여 rbp 레지스터에 저장한다.
이제 rsp는 현재 스택 프레임의 맨 위를 가리키게 되고, rbp는 스택에서 팝한 값으로 업데이트 된다. 스택 프레임이 제거되면서 이전 스택 프레임으로 복귀한다.
종합하면, leave 명령어를 통해 스택 프레임이 종료되고, 이전 스택 프레임으로 복귀하면서 rsp와 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
' pop rip ' 명령어는 스택에서 값을 팝하여 rip 레지스터에 저장하는 역할을 한다. 스택에서 값 0x400005가 팝되어 rip 레지스터에 저장된다. 이제 rip은 0x400005를 가리키게 된다.
ret 명령어가 실행되면, 스택에서 값을 팝하여 그 값을 다음 실행할 명령어의 주소로 설정하고 해당 주소로 점프한다.
Opcode: 시스템 콜
커널 모드는 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다. 파일 시스템, 입력/출력, 네트워크 통신, 메모리 관리 등 모든 저수준의 작업이 진행된다. 시스템의 모든 부분을 제어할 수 있기 때문에, 해커가 커널 모드까지 진입하게 되면 시스템은 무방비 상태가 된다.
유저모드는 운영체제가 사용자에게 부여하는 권한이다. 유튜브를 시청하는 것 등이 유저 모드에서 이뤄진다.
시스템 콜은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용된다. 유저 모드에서 직접할 수 없는 일을 커널이 도와줘야 하고 도움이 필요하다는 요청을 시스템 콜이라고 한다.
x64아키텍처에서는 시스템콜을 위해 syscall 명령어가 있습니다.
syscall
요청: rax 인자 순서: rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> stack |
'Hacking > DreamHack 시스템 해킹 강의' 카테고리의 다른 글
[ Dreamhack ] Return Address Overwrite (0) | 2024.02.06 |
---|---|
[ Dreamhack ] Tool: gdb (1) | 2024.02.05 |
[ Dreamhack ] 어셈블리어와 x86-64(1) (0) | 2024.02.05 |
[Dreamhack]Linux Memory Layout (0) | 2024.02.03 |
[Dreamhack] Computer Architecture (0) | 2024.02.03 |