본문 바로가기
시스템해킹

Syetem Hacking - [02] Computer Science - (3-1) x86 Assembly

by minpiter 2022. 12. 1.
반응형

CONTENTS

 

1. 어셈블리어와 x86-64
 1-1. 어셈블리언어
 1-2. x86-64 어셈블리
 1-3. 피연산자

2. x86-64 어셈블리 명령어
 2-1. Opcode : 데이터 이동
 2-2. Opcode : 산술 연산
 2-3. Opcode : 논리 연산 - and, or
 2-4. Opcode : 논리 연산 - xor, not
 2-5. Opcode : 비교/분기

3. 요약 정리

 

 

 

 


 

 

 

 

 

1. 어셈블리어와 x86-64

 

 1-1. 어셈블리언어

  - 어셈블리 언어는 컴퓨터의 기계어와 치환되는 언어

 


 

 1-2. x86-64 어셈블리

 

  1) 기본 구조

   - 동사에 해당하는 명령어(Operation Code, Opcode)와 목적어에 해당하는 피연산자(Operand)로 구성

 

x86-64 어셈블리어 문법 구조

 

 

 

  2) 명령어

명령 코드
데이터 이동(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

 


 

 1-3. 피연산자

 

  - 메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있다

  - TYPE에는 BYTE, WORD, DWORD, QWORD가 있으며, 각각 1바이트, 2바이트, 4바이트, 8바이트의 크기를 지정

 

  1) 종류

  • 상수(Immediate Value)
  • 레지스터(Register)
  • 메모리(Memory)

 

[예시]

메모리 피 연산자
QWORD PTR [0x8048000] 0x8048000의 데이터를 8바이트만큼 참조
DWORD PTR [0x8048000] 0x8048000의 데이터를 4바이트만큼 참조
WORD PTR [rax] rax가 가르키는 주소에서 데이터를 2바이트만큼 참조

 

 

 


 

 

 

 

2. x86-64 어셈블리 명령어

 

 2-1. Opcode : 데이터 이동

 

  - 데이터 이동 명령어는 어떤 값을 레지스터나 메모리에 옮기도록 지시

 

mov dst, src : src에 들어있는 값을 dst에 대입
mov rdi, rsi rsi의 값을 rdi에 대입
mov QWORD PTR[rdi], rsi rsi의 값을 rdi가 가리키는 주소에 대입
mov QWROD PTR[rdi+8*rcx], rsi rsi의 값을 rdi+8*rcx가 가리키는 주소에 대입
lea dst.src : src의 유효 주소(Effective Addres, 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]

 

[문제]

 

1. Code를 1까지 실행했을 때, rax에 저장된 값은 (                          )이다.

더보기

0xCOFFEE

 

2. Code를 2까지 실행했을 때, rax에 들어있는 값은 (                         )이다.

더보기

0x401A48

 


 

 

 2-2. Opcode : 산술 연산

  - 산술 연산 명령어는 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시

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

 

[문제]

1. Code를 1까지 실행했을 때, rax에 저장된 값은 (                           )이다.

더보기

0x3133A

 

해설 : rax의 값은 rbx+0x10인 0x555555554010에 저장된 0x3만큼 증가

 

2. Code를 3까지 실행했을 때, rax에 저장된 값은 (                        )이다.

더보기

0

 

해설 : rax의 값은 rbx+0x20에 저장된 0x3133A 만큼 감소

 

3. Code를 4까지 실행했으 때, rax에 저장된 값은 (                          )이다.

더보기

1

 

rax의 값은 1 증가

 

 


 

 2-3. Opcode : 논리 연산 - and, or

  - 논리 연산 명령어는 and, or, xor, neg 등의 비트 연산을 지시

  - 비트 단위로 연산

 

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

[Register]
eax = 0xffff0000
ebx = 0xcafebabe

[Code]
and eax, dbx

[Result]
eax = 0xcafe0000

 

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

[Register]
eax = 0xffff0000
ebx = 0xcafebabe

[Code]
or eax, ebx

[Result]
eax = 0xffffbabe

 


 

[예시] - 논리 연산 and, or

 - 레지스터, 메모리 및 코드가 다음과 같을 때, 아래에서 적절한 값을 채우시오

[Register]
rax = 0xffffffff00000000
rbx = 0x00000000ffffffff
rcx = 0x123456789abcdef0

=================================

[Code]
1: and rax, rcx
2: and rbx, rcx
3: or rax, rbx

 

[문제]

1. Code를 1까지 실행했을 때, rax에 저장된 값은 (                     )이다.

더보기

0x1234567800000000

 

2. Code를 2까지 실행했을 때, rbx에 저장된 값은 (                     )이다.

더보기

0x000000009abcdef0

 

3. Code를 3까지 실행했을 때, rax에 저장된 값은 (                     )이다.

더보기

0x123456789abcdef0

 

 


 

 

 2-4. Opcode : 논리 연산 - xor, not

 

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

[Register]
eax = 0xffffffff
ebx = 0xcafebabe

[Code]
xor eax, ebx

[Result]
eax = 0x35014541

 

  2) not op: op의 비트 전부 반전

[Register]
eax = 0xffffffff

[Code]
not eax

[Result]
eax = 0x00000000

 


 

[예시] - 논리 연산 xor, not

 - 레지스터, 메모리 및 코드가 다음과 같을 때, 아래에서 적절한 값을 채우시오

[Register]
rax = 0x35014541
rbx = 0xdeadbeef

=============================

[Code]
1: xor rax, rbx
2: xor rax, rbx
3: not eax

 

[문제]

 

1. Code를 1까지 실행했을 때, rax에 저장되는 값은 (                         )이다.

더보기

0xa96c79ae

 

2. Code를 2까지 실행했을 때, rax에 저장되는 값은 (                        )이다.

더보기

0x35014541

 

해설 : xor 연산을 동일한 값으로 두번 실행할 경우, 원래 값으로 돌아간다

(이를 기반으로 개발된 암호 사이트 → https://en.wikipedia.org/wiki/XOR_cipher)

 

3. Code를 3까지 실행했을 때, rax에 저장되는 값은 (                        )이다.

더보기

0xcafebabe

 

해설 : 3번에서 rax가 아닌 eax를 not 하여도 괜찮은 이유는 eax가 rax의 하위 32비트를 가리키는 부분이기 때문

          만약 not rax를 수행했다면 답은 0xffffffffcafebabe

 


 

 

 2-5. Opcode : 비교/분기

 

  1) 비교

   - 비교 명령어는 두 피연산자의 값을 비교하고, 플래그를 설정

 

  ① cmp op1, op2: op1과 op2를 비교

   - cmp는 두 피연산자를 빼서 대소를 비교합니다. 연산의결과는 op1에 대입하지 않는다.

   - 예를 들어, 서로 같은 두 수를 빼면 결과가 0이 되어 ZF플래그가 설정되는데,

     이후에 CPU는 ZF플래그를 보고 두 값이 같았는지 판단할 수 있다.

[Code]
1: mov rax, 0xA
2: mov rbx, 0xA
3: cmp rax, rbx ; ZF=1

 

  ② test op1, op2: op1과 op2를 비교

   - test는 두 피연산자에 AND 비트연산을 취한다. 연산의 결과는 op1에 대입하지 않는다.

   - 예를 들어, 아래 코드에서 처럼 0이 된 rax를 op1과 op2로 삼아 test를 수행하면, 결과가 0인 ZF플래그가 설정된다.

      이후에 CPU는 이 ZF플래그를 보고 rax가 0이었는지 판단할 수 있다.

[Code]
1: xor rax, rax
2: test rax, rax ; ZF=1

 

 

  2) 분기

   - 분기 명령어는 rip를 이동시켜 실행 흐름을 바꾼다.

 

  ① jmp addr: addr로 rip를 이동시킨다.

[Code]
1: xor rax, rax
2: jmp 1 ; jump to 1

 

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

[Code]
1: mov rax, 0xcafebabe
2: mov rbx, 0xcafebabe
3: cmp rax, rbx ; rax == rbx
4: je 1 ; jump to 1

 

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

[Code]
1: mov rax, 0x31337
2: mov rbx, 0x13337
3: cmp rax, rbx ; rax > rbx
4: jg 1 ; jump to 1

 

 

 

 


 

 

 

 

3. 요약 정리

 

 1) 데이터 이동 연산자

  • mov dst, src: src의 값을 dst에 대입
  • lea dst, src: src의 유효 주소를 dst에 대입

  2) 산술 연산

  • add dst, src: src의 값을 dst에 더함
  • sub dst, src: src의 값을 dst에서 뺌
  • inc op: op의 값을 1 더함
  • dec op: op의 값을 1 뺌

 3) 논리 연산

  • and dst, src: dst와 src가 모두 1이면 1, 아니면 0
  • or dst, src: dst와 src중 한 쪽이라도 1이면 1, 아니면 0
  • xor dst, src: dst와 src가 다르면 1, 같으면 0
  • not op: op의 비트를 모두 반전

 4) 비교

  • cmp op1, op2: op1에서 op2를 빼고 플래그를 설정
  • test op1, op2: op1과 op2에 AND 연산을 하고, 플래그를 설정

 5) 분기

  • jmp addr: addr로 rip 이동
  • je addr: 직전 비교에서 두 피연산자의 값이 같을 경우 addr로 rip 이동
  • jg addr: 직전 비교에서 두 피연산자 중 전자의 값이 더 클 경우 addr로 rip 이동

 

 

 

 

 

 

 

※ 이 게시글은 DREAMHACK 강의를 참조하여 작성되었습니다.

반응형