x64 어셈블리 언어
어셈블리 언어 : 컴퓨터의 기계어와 치환되는 언어
동사에 해당하는 명령어(Operation Code, Opcode)와 목적어에 해당하는 피연산자(Operand)로 구성된다.

명령 코드
|
|
데이터 이동(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
|
피연산자
상수(Immediate Value), 레지스터(Register), 메모리(Memory)가 올 수 있다.
메모리 피연산자 크기 지정자 타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있으며, 각각 1바이트, 2바이트, 4바이트, 8바이트의 크기를 지정한다.
ex) QWORD PTR [0x8048000] : 0x8048000의 데이터를 8바이트만큼 참조
ex) WORD PTR [rax] : rax가 가르키는 주소에서 데이터를 2바이트 만큼 참조
데이터 이동
어떤 값을 레지스터나 메모리에 옮기도록 지시
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]
Q1. Code를 1까지 실행했을 때, rax에 저장된 값은 0xC0FFEE 이다.
// rbx(0x401A40)+8 = 0x401a48, 0x401a48의 값을 rax에 대입해야 한다. 답은 0x0000000000C0FFEE
Q2. Code를 2까지 실행했을 때, rax에 들어있는 값은 0x401A48 이다.
// rbx(0x401A40)+8 = 0x401a48, 이 값을 rax에 대입한다. 답은 0x401a48
산술연산
덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시
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
Q1. Code를 1까지 실행했을 때, rax에 저장된 값은 0x3133A 이다.
rax의 값은 rbx+0x10(0x555555554010) 에 저장된 0x3 만큼 증가한다. 0x31337 + 0x3 = 0x3133A
Q2. Code를 3까지 실행했을 때, rax에 저장된 값은 0 이다.
rax의 값은 rbx+0x20에 저장된 0x3133A 만큼 감소한다. 0x3133A - 0x3133A = 0
Q3. Code를 4까지 실행했을 때, rax에 저장된 값은 1 이다.
rax의 값은 1 증가한다. 0 + 1 = 1
논리연산
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]
and eax, ebx
[Result]
eax = 0xcafe0000
예제) 레지스터, 메모리 및 코드가 다음과 같을 때, 아래에서 적절한 값을 채우시오.
[Register]
rax = 0xffffffff00000000
rbx = 0x00000000ffffffff
rcx = 0x123456789abcdef0
==================================
[Code]
1: and rax, rcx
2: and rbx, rcx
3: or rax, rbx
Q1. Code를 1까지 실행했을 때, rax에 저장된 값은 0x1234567800000000 이다.
//0xffffffff00000000 = 1111 1111 1111 1111 1111 1111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000
//0x123456789abcdef0 = 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0000
Q2. Code를 2까지 실행했을 때, rbx에 저장된 값은 0x000000009abcdef0 이다.
//0x00000000ffffffff = 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111
//0x123456789abcdef0 = 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0000
Q3. Code를 3까지 실행했을 때, rax에 저장된 값은 0x123456789abcdef0 이다.
//0x1234567800000000 = 0001 0010 0011 0100 0101 0110 0111 1000 0000 0000 0000 0000 0000 0000 0000 0000
//0x000000009abcdef0 = 0000 0000 0000 0000 0000 0000 0000 0000 1001 1010 1011 1100 1101 1110 1111 0000
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
예제) 레지스터, 메모리 및 코드가 다음과 같을 때, 아래에서 적절한 값을 채우시오.
[Register]
rax = 0x35014541
rbx = 0xdeadbeef
==================================
[Code]
1: xor rax, rbx
2: xor rax, rbx
3: not eax
Q1. Code를 1까지 실행했을 때, rax에 저장되는 값은 0xebacfbae 이다.
//0x35014541 = 0011 0101 0000 0001 0100 0101 0100 0001
//0xdeadbeef = 1101 1110 1010 1101 1011 1110 1110 1111
//0xebacfbae = 1110 1011 1010 1100 1111 1010 1010 1110
Q2. Code를 2까지 실행했을 때, rax에 저장되는 값은 0x35014541 이다.
//xor연산을 동일한 값으로 두 번 실행할 경우, 원래 값으로 돌아간다.
Q3. Code를 3까지 실행했을 때, rax에 저장되는 값은 0xcafebabe 이다.
//0x35014541 = 0011 0101 0000 0001 0100 0101 0100 0001
//0xcafebabe = 1100 1010 1111 1110 1011 1010 1011 1110
비교
두 피연산자의 값을 비교하고, 플래그를 설정
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를 비교
|
[Code]
1: xor rax, rax
2: test rax, rax ; ZF=1
분기
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
Q1. end로 점프하면 프로그램이 종료된다고 가정하자. 프로그램이 종료됐을 때, 0x400000 부터 0x400019까지의 데이터를 대응되는 아스키 문자로 변환하면 어느 문자열이 나오는가?
[Register]
rcx = 0
rdx = 0
rsi = 0x400000
=======================
[Memory]
0x400000 | 0x67 0x55 0x5c 0x53 0x5f 0x5d 0x55 0x10
0x400008 | 0x44 0x5f 0x10 0x51 0x43 0x43 0x55 0x5d
0x400010 | 0x52 0x5c 0x49 0x10 0x47 0x5f 0x42 0x5c
0x400018 | 0x54 0x11 0x00 0x00 0x00 0x00 0x00 0x00
=======================
[code]
1: mov dl, BYTE PTR[rsi+rcx]
2: xor dl, 0x30
3: mov BYTE PTR[rsi+rcx], dl
4: inc rcx
5: cmp rcx, 0x19
6: jg end
7: jmp 1
1: mov dl, BYTE PTR[rsi+rcx]
[rsi+rcx]의 값을 1바이트만큼 가져와서 dl에 대입하면 된다.
0x400000 + 0 = 0x400000, [0x400000] = 0x67
dl = 0x67
2: xor dl, 0x30
0x67 = 0110 0111
0x30 = 0011 0000
0x57 = 0101 0111 = dl
3: mov BYTE PTR[rsi+rcx], dl
dl값을 [rsi+rcx]에 대입하면 된다.
0x400000 + 0 = 0x400000
[0x400000] = 0x57
4: inc rcx
rcx +=1
rcx = 0x1
5: cmp rcx, 0x19
rcx(0x01)과 0x19를 비교한 뒤 플래그를 설정한다.
0x01<0x19이므로 SF(Sign Flag)를 설정한다.
6: jg end
직전에 비교한 두 연산자 중 전자가 더 크면 점프하는데, 이 경우에는 후자가 더 크기 때문에 점프하지 않는다.
7: jmp 1
1로 돌아간다.
0x400000 | 0x57 0x55 0x5c 0x53 0x5f 0x5d 0x55 0x10
0x400008 | 0x44 0x5f 0x10 0x51 0x43 0x43 0x55 0x5d
0x400010 | 0x52 0x5c 0x49 0x10 0x47 0x5f 0x42 0x5c
0x400018 | 0x54 0x11 0x00 0x00 0x00 0x00 0x00 0x00
1번 했을 때의 메모리이다.
rcx>0x19가 될 때까지 반복되므로 위에서 1번 한 것을 포함해 총 20번 실행된다.
최종적으로 저장된 메모리를 살펴보면
0x400000 | 0x57 0x65 0x6c 0x63 0x6f 0x6d 0x65 0x20
0x400008 | 0x74 0x6f 0x20 0x61 0x73 0x73 0x65 0x6d
0x400010 | 0x62 0x6c 0x79 0x20 0x77 0x6f 0x72 0x6c
0x400018 | 0x64 0x21 0x00 0x00 0x00 0x00 0x00 0x00
이렇게 된다.
