x64 Assembly
기본 구조
명령어(Operation Code, Opcode)와 피연산자(Operand)
opcode operand1, operand2 ⇒ operand1에 operand2를 opcode해라 ex) mov eax, 3 ⇒ eax에 3을 대입해라
- 명령어
명령 코드
데이터 이동 (Data Transfer) | mov, lea |
산술 연산 (Arithmetic) | inc, dec, add, sub |
논리 연산 (Logical) | and, or, xor, not |
비교 (Comparison) | cmp, test |
분기 (Branch) | jmp, je, jg |
스택 (Stack) | push, pop |
프로시져 (Procedure) | call, ret, leave |
시스템 콜 (System Call) | syscall |
- 피연산자
피연산자는 총 3가지 종류가 있다.
- 상수 (Immediate Value)
- 레지스터 (Register)
- 메모리 (Memory) ⇒ 메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자 TYPE PTR이 올 수 있다. 타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있으며 각각 1, 2, 4, 8 바이트의 크기를 지정한다.
메모리 피연산자
QWORD PTR [0x8048000] | 0x8048000의 데이터를 8바이트만큼 참조 |
DWORD PTR [0x8048000] | 0x8048000의 데이터를 4바이트만큼 참조 |
WORD PTR [rax] | rax가 가리키는 주소에서 데이터를 2바이트만큼 참조 |
명령어 예시
- 데이터 이동
mov dst, src : src에 들어있는 값을 dst에 대입
mov rdi, rsi | rsi의 값을 rdi에 대입 |
mov QWORD PTR[rdi], rsi | rsi의 값을 rdi가 가리키는 주소에 대입 |
mov QWORD PTR[rdi+8*rcx], rsi | rsi의 값을 rdi+8*rcx가 가리키는 주소에 대입 |
lea dst, src : src의 유효 주소(Effective Address, EA)를 dst에 저장
lea rsi, [rbx+8*rcx] | rbx+8*rcx 를 rsi에 대입 |
[Register]
rbx = 0x401A40
=================================
[Memory]
0x401a40 | 0x0000000012345678
0x401a48 | 0x0000000000C0FFEE
0x401a50 | 0x00000000DEADBEEF
0x401a58 | 0x00000000CAFEBABE
0x401a60 | 0x0000000087654321
=================================
[Code]
1: mov rax, [rbx+8]
2: lea rax, [rbx+8]
Code 1까지 실행했을 때, rax = 0xC0FFEE Code 2까지 실행했을 때, rax = 0x401A48
- 산술 연산
add dst, src : dst에 src의 값을 더한다.
add eax, 3 | eax += 3 |
add ax, WORD PTR[rdi] | ax += *(WORD *)rdi |
sub dst, src : dst에 src의 값을 뺀다.
sub eax, 3 | eax -= 3 |
sub ax, WORD PTR[rdi] | ax -= *(WORD *)rdi |
inc op : op의 값을 1 증가
inc eax | eax += 1 |
dec op : op의 값을 1 감소
dec eax | eax -= 1 |
[Register]
rax = 0x31337
rbx = 0x555555554000
rcx = 0x2
=================================
[Memory]
0x555555554000| 0x0000000000000000
0x555555554008| 0x0000000000000001
0x555555554010| 0x0000000000000003
0x555555554018| 0x0000000000000005
0x555555554020| 0x000000000003133A
==================================
[Code]
1: add rax, [rbx+rcx*8]
2: add rcx, 2
3: sub rax, [rbx+rcx*8]
4: inc rax
Code 1까지 실행했을 때, rax = 0x31337 + 0x3 = 0x3133A Code 3까지 실행했을 때, rcx = 0x4, rax = 0 Code 4까지 실행했을 때, rax = 1
- 논리 연산
논리 연산 명령어는 and, or, xor, not 등의 비트 연산을 한다. 이 연산은 비트 단위로 이루어 진다.
[Register]
rax = 0xffffffff00000000
rbx = 0x00000000ffffffff
rcx = 0x123456789abcdef0
==================================
[Code]
1: and rax, rcx
2: and rbx, rcx
3: or rax, rbx
Code 1까지 실행했을 때, rax = 0x1234567800000000 Code 2까지 실행했을 때, rbx = 0x000000009abcdef0 Code 3까지 실행했을 때, rax = 0x123456789abcdef0
[Register]
rax = 0x35014541
rbx = 0xdeadbeef
==================================
[Code]
1: xor rax, rbx
2: xor rax, rbx
3: not eax
Code 1까지 실행했을 때, rax = 0xebacfbae Code 2까지 실행했을 때, rax = 0x35014541
# XOR 연산은 동일한 값으로 2번 연산하면 원래 값이 나온다. Code 3까지 실행했을 때, eax = 0xcafebabe # 만약 eax가 아닌 not rax를 했다면 답은 rax = 0xffffffffcafebabe 이다.
- 비교 / 분기
[Code]
1: mov rax, 0xA
2: mov rbx, 0xA
3: cmp rax, rbx ; ZF=1
cmp는 두 피연산자를 빼서 대소를 비교한다. 위처럼 같은 수를 빼면 Zero Flag를 설정한다. 이후 CPU는 이 플래그를 보고 같은지를 판단한다.
[Code]
1: xor rax, rax
2: test rax, rax ; ZF=1
test는 두 피연산자에 AND 비트연산을 한다. 위처럼 XOR 연산로 0으로 설정한 rax를 test 연산하면 결과가 0이므로 Zero Flag가 설정된다. 이후 CPU는 이 플래그를 보고 rax가 0이었는지를 판단한다.
분기 명령어는 rip를 이동시켜 실행 흐름 바꾼다.
[Code]
1: jmp 1 ; jump to 1
2: cmp rax, rbx ; rax == rbx
3: je 1 ; jump to 1
4: cmp rax, rbx ; rax > rbx
5: jg 1 ; jump to 1
Code 1은 주소 1로 rip를 이동시킨다. Code 3는 rax와 rbx가 같을 때 이동시킨다. Code 5는 rax가 rbx 보다 클 때 이동시킨다.
- 스택 다루기
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
프로시저(Procedure)란 특정 기능을 수행하는 코드 조각을 말한다.
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
- 시스템 콜 (Syscall)
유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용한다.
※ syscall
요청 : rax # syscall table 참고
인자 순서 : rdi → rsi → rdx → rcx → r8 → r9 → stack
'모각코 > [2023_하계] 모각코' 카테고리의 다른 글
모각코 4일차 결과 (2023.07.26) (0) | 2023.07.26 |
---|---|
모각코 4일차 계획 (2023.07.26) (0) | 2023.07.26 |
모각코 3일차 계획 (2023.07.19) (0) | 2023.07.24 |
모각코 2일차 결과 (2023.07.12) (0) | 2023.07.12 |
모각코 2일차 계획 (2023.07.12) (0) | 2023.07.12 |