Instruction Format
Instruction
- Instruction == Operation Code + Operand
Opcode (Operation Code)
- 명령 코드, 명령어에서 어떤 동작(산술 연산, 복사 등)할지 나타내는 부분
- 기계 코드와 동일
- 바이너리(컴파일 결과물)를 구성한다.
- CPU가 수행하는 작업을 숫자로 나타낸 것
Assembly Code
- 명령 코드를 사람이 이해할 수 있는 문자로 작성한 코드
- 명령 코드와 어셈블리 코드는 1:1 대응된다.
55 push rbp
48 89 e5 mov rbp,rsp
48 8d 3d 9f 00 00 00 lea rdi,[rip+0x9f]
e8 c6 fe ff ff call 510 <puts@plt>
b8 00 00 00 00 mov eax,0x0
5d pop rbp
c3 ret
Operand
- 명령 코드의 연산 대상
- Intel 방식의 어셈블리는, 명령코드 연산 결과를 왼쪽의 피연산자에 저장한다.
- 피연산자는 상수, 레지스터, 레지스터가 가리키는 주소가 될 수 있다.
mov rbp,rsp ; rbp = rsp
mov eax,0x0 ; eax = 0x0
add rcx,0x8 ; rcx = rcx+0x8
dec rcx ; rcx = rcx-1
상수값(Immediate)
mov rcx, 0xbeef ; rcx = 0xbeef
add rcx, 0x1337 ; rcx = rcx + 0x1337
연산 후 rcx에는 0xbeef + 0x1337인 0xD226이 들어있다.
레지스터 (Register)
mov rcx, rbx ; rcx = rbx
sub rcx, rax ; rcx = rcx - rax
rbx = 0xdead, rax = 0xc0de이면, rcx에는 0x1DCF가 들어있다.
Addressing Modes
- 피연산자가 레지스터에 저장된 주소를 참조한 값인 경우
[reg]
mov [rcx], rax ; *rax = rax
- rcx 레지스터가 참조하는 주소의 메모리에 rax 레지스터의 값을 저장한다.
mov byte ptr [rcx], al ; *rcx = al
- byte ptr : Pointer Directive
- al : rax 레지스터의 하위 8bit (1byte) least significant byte of AX (8-bit register)
- rax 레지스터가 저장하는 값의 하위 8bit(1byte)를 rcx 레지스터가 가리키는 주소에 저장한다.
[reg+d]
mov dword ptr [rbp-1Ch], eax
- rax 레지스터의 값 중 하위 DWORD 사이즈인 32bit(4byte)을 rbp 메모리 주소로부터 -0x1C 떨어진 주소에 저장한다.
[reg1+reg2]
- reg1 레지스터와 reg2 레지스터 값을 더한 결과가 참조할 메모리 주소이다.
[reg1+reg2*i+d]
mov byte ptr [rdi+rcx*4+3], 0FFh
- rdi 레지스터 값 기준으로, rcx 레지스터 값 단위로 4단위 떨어진 곳에서 3 오프셋 더한 주소가 참조할 메모리 주소이다.
- 0xFF (1byte 사이즈)가 참조하는 메모리 주소에 저장된다.
Instructions 어셈블리 명령어들
Data Movement
mov dst, src
- src에 dst 값을 옮긴다.
- dst = src
mov rax, [rbx+8] ; rax = *(rbx+8)
- rax에 rbx+8 주소가 가지는 값을 저장한다.
lea dst, addr
- lea = load effective address
- dst에 addr를 저장한다.
- dst = addr
lea rax, [rbx+8]
- rax에 rbx가 가지는 주소에서 +8한 값을 저장한다.
Arithmetic Operations
Unary Instructions
inc dst
dec dst
neg dst
- dst 값의 부호를 바꾼다.
- dst = -dst
not dst
- dst 값의 비트를 반전한다.
- dst = ~dst
Binary Instructions
add dst, src
- dst 값에 src 값을 더해서 dst에 저장한다.
- dst = dst + src
sub dst, src
- dst 값에서 src 값을 빼서 dst에 저장한다.
- dst = dst - src
imul dst, src
- dst 값에 src를 곱해서 dst에 저장한다.
- dst = dst * src
and dst, src
- dst 값과 src 값 간에 AND 연산 결과를 dst에 저장한다.
- dst = dst & src
or dst, src
- dst 값과 src 값 간에 OR 연산한 결과를 dst에 저장한다.
- dst = dst | src
xor dst, src
- dst 값과 src 값 간에 XOR 연산한 결과를 dst에 저장한다.
- dst = dst ^ src
Shift Instructions
shl dst, k
- dst 값에서 k만큼 왼쪽으로 logical shift한다.
- dst << k
shr dst, k
- dst 값에서 k만큼 오른쪽으로 logical shift한다.
- 빈 bit 자리에는 0이 채워진다.
- dst >> k
sal dst, k
- dst 값을 k만큼 왼쪽으로 arithmetic shift한다.
- shift 이후에도 부호가 보전된다.
- dst << k
sar dst, k
- dst 값을 k만큼 오른쪽으로 arithmetic shift한다.
- shift 이후에도 최상위 비트가 보전된다.
- dst >> k
Conditional Operations
test dst, src
- dst 값과 src 값 사이에 AND 연산 결과가 ZF, SF 플래그에만 영향을 미친다.
- 연산 결과가 음수이면, 즉 최상위비트 == 1이면 SF = 1, ZF = 1
- 연산 결과가 0이면, ZF = 1
- 레지스터에 들어있는 값이 음수인지, 0인지 확인하는데 쓰인다.
test rax,rax ; ZF = 1 if rax = 0 ; SF = 1 if rax < 0
test rax,rax
; ZF = 1 if rax = 0
; SF = 1 if rax < 0
cmp dst, src
- dst - src 연산 결과가 CF, ZF 플래그에만 영향을 미친다.
- dst = src이면 ZF = 1, CF = 0
- dst < src이면 ZF = 0, CF = 1
- CF(Carry Flag)를 borrow (자리 내림)하기 위한 borrow flag로도 사용한다.
- dst < src이면 음수로 unsigned 기준 범위를 벗어나는데, OF 기준이 signed 이기 때문에 OF가 체크되지 않는다. 따라서 CF로 체크한다.
- 1 - 2 = 0b00000001 - 0b00000010 = -1 = 0b11111111
- msb(최상위비트) 위치에 0b100000000을 더해야 자리 내림을 진행할 수 있다.
- 0b100000001 - 0b00000010 = 0b11111111
- dsf > src아면 ZF - 0, CF = 0
cmp rax, rdi ; ZF = 1 if rax == rdi ; ZF = 0 if rax != rdi ; CF = 1 if rax < rdi ; CF = 0 if rax > rdi
cmp rax, rdi
; ZF = 1 if rax == rdi
; ZF = 0 if rax != rdi
; CF = 1 if rax < rdi
; CF = 0 if rax > rdi
jmp, jcc
jmp location
- location이 가리키는 곳으로 무조건 점프한다.
jcc location
- 산술연산이나 test, cmp 수행 결과 바뀐 FLAGS 레지스터 바탕으로 location이 가리키는 곳으로 점프 수행 여부를 결정한다.
je location
jne location
jg location
- if greater
- location > signed
jge location
- if greater or equal
- location ≥ signed
jl location
- if less
- location < signed
jle location
- if less or equal, jmp to location
- location ≤ signed
ja location
jb location
js location
jns location
cmp dword ptr [rbp-0x2c], 0x47
jle 400a31
- cmp 연산 결과 바탕으로 jle 명령을 수행한다.
- rbp-0x2c 위치의 하위 32bit와 0x47를 비교하여 0x47보다 작거나 같으면(less or equal) 0x400a31 주소로 점프한다.
test rax,rax
je 4006c5
- rax & rax 연산 결과가 0과 같으면 (ZF = 1이면) 0x4006c5로 점프한다.
- 주어진 조건을 만족하지 않으면 jcc 명령어 실행하지 않고 다음 명령어로 넘어간다.
Stack Operations
Stack
- rsp
- x86_64의 stack pointer
- 스택의 맨 위쪽을 가리키고 있다.
- 지역변수(Local Variable)들은 스택(Stack)에 저장된다.
- LIFO 방식: 마지막으로 담은 데이터 위에 새로운 데이터가 쌓이는 방식으로 길어진다.
- Intel x86_64 아키텍처에서는 스택은 낮은 주소를 향해 자란다.
Function Prologue/Epilogue
Function Prologue
- 함수가 시작되면, 메모리에서 스택이 준비된다.
- rsp 레지스터에 들어있는 주소에서 값을 빼서(낮은 주소로, 스택은 낮은 주소로 자라기 때문) 지역변수 공간을 확보한다.
Function Epilogue
- 함수가 종료되면, 프롤로그에서 빼준 오프셋만큼 rsp에 더해서 스택을 정리한다.
push, pop
push
- 스택에 새로운 데이터를 넣는다.
- rsp 레지스터는 들어간 데이터 크기만큼 공간 확보 후 데이터를 rsp로 복사한다.
- rsp는 항상 스택의 top을 가리키고 있으므로 새로 들어온 데이터를 담은 주소를 가리키도록 갱신해준다.
push rdi
; sub rsp, 8
; mov [rsp], rdi
pop
- 스택 top의 데이터를 스택에서 빼는 명령어
- rsp가 가지는 값을 rdi로 옮긴 후, rsp 값을 데이터 크기만큼 더해준다.
pop rdi
; mov rdi, [rsp]
; add rsp, 8
Procedure Call Instructions
call location
- location으로 실행할 함수 주소를 받아, location 주소의 함수를 실행한다.
- Return Address
- call로 호출한 함수 종료 후 다음 실행할 명령어로 돌아와야 하기 때문에 함수 종류 이후 돌아와야 하는 주소
- call 명령어는 push retaddr ; jmp location 과 동일하다.
ret
- 호출된 함수가 종료 시 Return Address로 돌아가기 위해 사용하는 명령어
- pop rip ; jmp rip 와 동일하다.
- 스택의 Return Address를 pop 하여 rip 레지스터에 넣은 후, jmp rip로 Return Address로 점프한다.
- ret 명령어 전에 함수 에필로그 과정을 가져 스택을 정리한 상태로, rsp는 함수 호출 전 스택에 넣은 값, 즉 call 전 스택에 넣은 Return Address를 가리키고 있다. (call은 push retaddr ; jmp location이므로 push retaddr) 따라서 pop rip를 하면 return address를 가져오게 되는 것이다.