x86 Assembly : Essential Part (1)

·2023년 1월 16일
0

System Hacking

목록 보기
5/10

x86 어셈블리
: 간단한 어셈블리에 대한 이해

기계어 (Machine Code) - 컴퓨터 언어
어셈블리 언어 (Assembly Language) - 인간이 컴퓨터와 통역할 수 있게 함
어셈블러 (Assembler) - 어셈블리 언어를 기계어로 치환
역어셈블러 (Disassembler) - 기계어를 어셈블리 언어로 번역

x64 어셈블리 언어

동사, 명령어 (Operation Code, Opcode) 와 목적어, 피연산자 (Operand) 로 구성

mov eax, 3 // mov 는 Opcode, eax 와 3 은 Operand 에 해당한다
  1. 명령어 (Operation Code, Opcode)

    동작명령어
    데이터 이동 (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
  2. 피연산자 (Operand)

  • 상수
  • 레지스터
  • 메모리
    - 메모리 피연산자는 [ ] 으로 둘러싸인 것으로 표현
    - 앞에 크기 지정자 (Size Directive) TYPE
    - 타입에는 BYTE(1), WORD(2), DWORD(4), QWORD(8)가 올 수 있으며, 각각 1바이트, 2바이트, 4바이트, 8바이트의 크기를 지정
QWORD PTR [0x8048000] // 0x8048000의 데이터를 8byte만큼 참조
DWORD PTR [0x8048000] // 0x8048000의 데이터를 4byte만큼 참조
WORD PTR [rax] // rax가 가르키는 주소에서 데이터를 2byte만큼 참조

데이터 이동 ( mov, lea )

어떤 값을 레지스터나 메모리에 옮기도록 지시

  • 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, sub, inc, dec )

덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시

  • 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에 저장된 값은 0x3133A이다.
-> 0x2*8 은 10진수 16 이므로 16진수 0x10 와 같습니다
-> rax의 값은 rbx+0x10 = 0x555555554010 에 저장된 0x3 만큼 증가합니다
Code를 3까지 실행했을 때, rax에 저장된 값은 0이다.
-> rax의 값은 rbx+0x20에 저장된 0x3133A 만큼 감소합니다
Code를 4까지 실행했을 때, rax에 저장된 값은 1이다.
-> rax의 값은 1 증가합니다.

논리 연산 ( and, or, xor, neg )

and, or, xor, neg 등의 비트 연산을 지시
이 연산은 비트 단위로 이루어짐

  • and dst, src: dst와 src의 비트가 모두 1이면 1, 아니면 0
[Register]
eax = 0xffff0000
ebx = 0xcafebabe
[Code]
and eax, ebx
[Result]
eax = 0xcafe0000
  • or dst, src: dst와 src의 비트 중 하나라도 1이면 1, 아니면 0
[Register]
eax = 0xffff0000
ebx = 0xcafebabe
[Code]
or eax, ebx
[Result]
eax = 0xffffbabe
  • xor dst, src: dst와 src의 비트가 서로 다르면 1, 같으면 0
[Register]
eax = 0xffffffff
ebx = 0xcafebabe
[Code]
xor eax, ebx
[Result]
eax = 0x35014541
  • not op: op의 비트 전부 반전
[Register]
eax = 0xffffffff
[Code]
not eax
[Result]
eax = 0x00000000

📝 예제 - 논리 연산 : and, or

[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 이다.

📝 예제 - 논리 연산 : xor, not

[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연산을 동일한 값으로 두 번 실행할 경우, 원래 값으로 돌아갑니다
Code를 3까지 실행했을 때, rax에 저장되는 값은 0xcafebabe 이다.
-> 참고로 [Code]의 3번에서 rax가 아닌 eax를 not 하여도 괜찮은 이유는 eax가 rax의 하위 32비트를 가리키는 부분이기 때문입니다 만약 not rax를 수행했다면 답은 0xffffffffcafebabe가 됩니다

비교 ( cmp, test )

두 피연산자의 값을 비교하고, 플래그를 설정 (연산의 결과는 op1에 대입하지 않음)

  • cmp op1, op2: op1과 op2를 비교
[Code]
1: mov rax, 0xA
2: mov rbx, 0xA
3: cmp rax, rbx ; ZF=1

두 피연산자를 빼서 대소를 비교

  • test op1, op2: op1과 op2를 비교 (op1-op2)
[Code]
1: xor rax, rax
2: test rax, rax ; ZF=1

두 피연산자에 AND 비트연산을 취함 (op1&op2)

분기 ( jmp, je, jg )

분기 명령어는 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

0개의 댓글