상세 컨텐츠

본문 제목

어셈블리 명령어 포맷, 명령어 종류 (Instruction Format, Instructions)

REVERSING/Reversing Study

by koharin 2021. 3. 26. 23:23

본문

728x90
반응형

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

  • dst 값을 1 증가시킨다.
  • ++dst

dec dst

  • dst 값을 1 감소시킨다.
  • —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

  • if equal
  • ZF = 1 (equal)

jne location

  • if not equal
  • ZF = 0

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

  • location > unsigned

jb location

  • location < unsigned

js location

  • negative (SF = 1)

jns location

  • not negative (SF = 0)
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를 가져오게 되는 것이다.
728x90
반응형

관련글 더보기