[Dreamhack] Background: 3 - x86 Assembly - 1

securitykss·2022년 10월 21일
0

이 글은 https://dreamhack.io/lecture/courses/37, https://dreamhack.io/lecture/courses/63 토대로 작성한 글입니다.

1. Introduction

컴퓨터는 컴퓨터의 언어인 기계어(Machine Code)로 소통을 하고 여러 상호작용을 한다.

하지만 기계어는 0과 1로 구성되어 있어서 사람이 읽고 작성하고 이해하기에는 너무 어렵다.

그래서 컴퓨터 과학자 중 한 명인 David Wheeler는 EDSAC을 개발하면서 어셈블리 언어(Assembly Language)와 어셈블러(Assembler)라는 것을 고안했습니다.

어셈블리어(Assembly Language)는 기계어와 1 대 1 대응이 되어 있고, 어셈블리어(Assembly Language)는 어셈블러(Assembler)를 통해서 기계어로 치환이 된다.

어셈블리어(Assembly Language)는 0과 1로 이루어져 있는 기계어(Machine Code)보다 사람이 읽기 더 편하다.

이번 시간에는 어셈블리어(Assembly Language)에 대해 더 자세하게 알아보겠다.

2. Assembly Language

이전 Backgruond: Computer Architecture (https://velog.io/@securitykss/Dreamhack-1.-Background-2-Computer-Architecture)에서 CPU에 사용되는 ISA는 IA-32, x86-64, ARM, MIPS 등 다양한 졸류가 있다고 언급한 적이 있다.

x86-64에는 x86-64가 사용하는 어셈블리어가 있고,
ARM에는 ARM이 사용하는 어셈블리어가 존재한다.

우리는 x86-64 어셈블리어를 알아보자.

3. x86-64 Assembly Language

3.1 구조

명령어(operation, opcode)와 피연산자(operand)로 구성되어 있다.

피연산자(operand)는 3가지 종류가 올 수 있다.

상수(Immediate Value)

상수는 위의 그림에서 3처럼 숫자를 사용한다.

레지스터(Regitster)

레지스터는 위의 그림에서 eax처럼 레지스터들을 사용한다.

메모리(Memory)

메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있습니다. 여기서 타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있으며, 각각 1바이트, 2바이트, 4바이트, 8바이트의 크기를 지정합니다.

ex) mov rax, QWORD PTR[0X40393]
해석: 0x40393 주소 안의 데이터를 8바이트 만큼 참조해서 rax에 대입

데이터 이동, 산술 연산, 논리 연산, 비교, 분기에 관한 명령어에 대해 알아 보겠다.

3.2 데이터 이동

mov, lea 명령어

mov 명령어

mov dst, src : src에 들어있는 값을 dst에 대입

ex1) mov rax, rdi : rdi의 값을 rax에 대입

ex2) mov rsi, QWORDPTR[rdi] : rdi가 가리키는 주소의 값을 8바이트 만큼 참조해서 rsi에 대입

lea 명령어

lea dst, src : src의 유효 주소(Effective Address, EA)를 dst에 저장

ex) lea rsi, [rbx + 8*rcx] : rbx + 8*rcx를 rsi에 대입

3.3 산술 연산

add, sub, inc, dec 연산 명령어

add 연산

add dst, src : dst에 src의 값을 더함

ex) add eax, 3 : eax += 3

sub 연산

sub dst, src: dst에서 src의 값을 뺍니다.

ex) sub eax, 3 : eax -= 3

inc 연산

inc op: op의 값을 1 증가시킴

ex) inc eax : eax += 1

dec 연산

dec op: op의 값을 1 감소 시킴

ex) dec eax : eax -= 1

3.4 논리 연산

and, or, xor, not 연산 명령어

and 연산

and dst, src: dst와 src의 비트가 모두 1이면 1, 아니면 0

ex) and eax, ebx

[Reg] : eax = 0x14ff(0001 0100 1111 1111)(2비트), ebx = 0x1234(0001 0010 0011 0100)(2비트)
[Code] : and eax, ebx
[연산 방법] : 모두 1인 경우 1, 아니면 0

   0001 0100 1111 1111 (0x14ff)
& 0001 0010 0011 0100 (0x1234)


= 0001 0000 0011 0100 (0x1034)

[Result] : eax = 0x1034

or 연산

or dst, src: dst와 src의 비트 중 하나라도 1이면 1, 아니면 0

ex) or eax, ebx

[Reg] : eax = 0x14ff(0001 0100 1111 1111)(2비트), ebx = 0x1234(0001 0010 0011 0100)(2비트)
[Code] : or eax, ebx
[연산 방법] : 하나라도 1이면 1, 두 개 다 0인 경우 0

    0001 0100 1111 1111 (0x14ff)
|   0001 0010 0011 0100 (0x1234)


= 0001 0110 1111 1111 (0x14ff)

[Result] : eax = 0x14ff

xor 연산

xor dst, src: dst와 src의 비트가 서로 다르면 1, 같으면 0

ex) xor eax, ebx

[Reg] : eax = 0x14ff(0001 0100 1111 1111)(2비트), ebx = 0x1234(0001 0010 0011 0100)(2비트)
[Code] : xor eax, ebx
[연산 방법] : 서로 다르면 1, 같으면 0

   0001 0100 1111 1111 (0x14ff)
^ 0001 0010 0011 0100 (0x1234)


= 0000 0110 1100 1011 (0x06cb)

[Result] : eax = 0x06cb

not 연산

not op: op의 비트 전부 반전

ex) not eax

[Reg] : eax = 0x14ff(0001 0100 1111 1111)(2비트)
[Code] : not eax
[연산 방법] : 비트 반전, 0 -> 1, 1 -> 0 으로 바꿈

!(0x14ff)
! (0001 0100 1111 1111)


= 1110 1011 0000 0000 (0xeb00)

[Result] : eax = 0xeb00

3.5 비교

cmp 명령어

cmp op1, op2: op1과 op2를 비교

ex)
mov rax, 0xA
mov rbx, 0xA
cmp rax, rbx ; ZF=1(rax - rbx = 0이므로 Zero Flag가 0으로 설정)

test 명령어

test op1, op2: op1과 op2를 비교

ex)
xor rax, rax (rax를 0으로 만듦)
test rax, rax ; ZF = 1 (rax가 0이므로)

cmp 명령어와 test 명령어의 차이

cmp는 두 피연산자의 차를 이용한 비교이다.

test는 두 피연산자를 and 연산자를 통해서 비교한다.

3.6 분기

분기 명령어는 rip(현재 프로그램이 실행되고 있는 위치)를 이동 시켜 실행 흐름을 바꾼다.

분기문은 여기 소개된 것 이외에도 많은 수가 존재한다.

jmp 명령어

jmp addr: addr로 rip를 이동시킵니다.
jmp 명령어는 조건이 어떻든 간에 무조건 가리키는 주소로 분기한다.(무조건 분기)

ex)
xor rax, rax (rax == 0)
jmp 1 ; jump to 1

조건 상관없이 무조건 1로 분기

je 명령어

je addr: 직전에 비교한 두 피연산자가 같으면 점프 (jump if equal)

ex)
mov rax, 4
mov rbx ,4
cmp rax, rbx (rax == rbx)
je 1 ; jump to 1

rax와 rbx는 같으므로 je 조건에 충족, 1로 분기 가능

jg 명령어

jg addr: 직전에 비교한 두 연산자 중 전자가 더 크면 점프 (jump if greater)

ex)
mov rax, 5
mov rbx, 3
cmp rax, rbx ; rax > rbx
jg 1 ; jump to 1

rax가 rbx보다 더 크므로 jg 조건에 충족, 1로 분기 가능

4. Conclusion

CPU의 ISA에 따라 어셈블리어가 다 다르다.

그 중에서 x86-64 어셈블리어를 간략하게 알아 보았다.

마치며

다음 시간에는 Background: x86 Assembly - 2를 알아보자.

Reference

https://learn.dreamhack.io/37#5 (그림 출처)
https://dreamhack.io/lecture/courses/37, https://dreamhack.io/lecture/courses/63

profile
보안 공부를 하는 학생입니다.

0개의 댓글