본문 바로가기

모각코/[2023_하계] 모각코

모각코 3일차 결과 (2023.07.19)

x64 Assembly

기본 구조

명령어(Operation Code, Opcode)와 피연산자(Operand)

opcode operand1, operand2 ⇒ operand1에 operand2를 opcode해라 ex) mov eax, 3 ⇒ eax에 3을 대입해라

 

  • 명령어

명령 코드

데이터 이동 (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
  • 피연산자

피연산자는 총 3가지 종류가 있다.

  1. 상수 (Immediate Value)
  2. 레지스터 (Register)
  3. 메모리 (Memory) ⇒ 메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자 TYPE PTR이 올 수 있다. 타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있으며 각각 1, 2, 4, 8 바이트의 크기를 지정한다.

메모리 피연산자

QWORD PTR [0x8048000] 0x8048000의 데이터를 8바이트만큼 참조
DWORD PTR [0x8048000] 0x8048000의 데이터를 4바이트만큼 참조
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]

Code 1까지 실행했을 때, rax = 0xC0FFEE Code 2까지 실행했을 때, 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

Code 1까지 실행했을 때, rax = 0x31337 + 0x3 = 0x3133A Code 3까지 실행했을 때, rcx = 0x4, rax = 0 Code 4까지 실행했을 때, rax = 1

 

  • 논리 연산

논리 연산 명령어는 and, or, xor, not 등의 비트 연산을 한다. 이 연산은 비트 단위로 이루어 진다.

[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

[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 연산은 동일한 값으로 2번 연산하면 원래 값이 나온다. Code 3까지 실행했을 때, eax = 0xcafebabe # 만약 eax가 아닌 not rax를 했다면 답은 rax = 0xffffffffcafebabe 이다.

 

  • 비교 / 분기
[Code]
1: mov rax, 0xA
2: mov rbx, 0xA
3: cmp rax, rbx ; ZF=1

cmp는 두 피연산자를 빼서 대소를 비교한다. 위처럼 같은 수를 빼면 Zero Flag를 설정한다. 이후 CPU는 이 플래그를 보고 같은지를 판단한다.

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

test는 두 피연산자에 AND 비트연산을 한다. 위처럼 XOR 연산로 0으로 설정한 rax를 test 연산하면 결과가 0이므로 Zero Flag가 설정된다. 이후 CPU는 이 플래그를 보고 rax가 0이었는지를 판단한다.

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

[Code]
1: jmp 1 ; jump to 1
2: cmp rax, rbx ; rax == rbx
3: je 1 ; jump to 1
4: cmp rax, rbx ; rax > rbx
5: jg 1 ; jump to 1

Code 1은 주소 1로 rip를 이동시킨다. Code 3는 rax와 rbx가 같을 때 이동시킨다. Code 5는 rax가 rbx 보다 클 때 이동시킨다.

 

  • 스택 다루기

push val : val을 스택 최상단에 쌓는다.

rsp -= 8

[rsp] = val

; 예제
[Register]
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc400 | 0x0  <= rsp
0x7fffffffc408 | 0x0

[Code]
push 0x31337

; 결과
[Register]
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp 
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

 

pop reg : 스택 최상단의 값을 꺼내서 reg에 대입

rsp += 8

reg = [rsp-8]

; 예제
[Register]
rax = 0
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp 
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

[Code]
pop rax

; 결과
[Register]
rax = 0x31337
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc400 | 0x0 <= rsp 
0x7fffffffc408 | 0x0

프로시저(Procedure)란 특정 기능을 수행하는 코드 조각을 말한다.

call addr : addr에 위치한 프로시저 호출

push return_address

jmp addr

; 예제
[Register]
rip = 0x400000
rsp = 0x7fffffffc400 

[Stack]
0x7fffffffc3f8 | 0x0
0x7fffffffc400 | 0x0 <= rsp

[Code]
0x400000 | call 0x401000  <= rip
0x400005 | mov esi, eax
...
0x401000 | push rbp

; 결과
[Register]
rip = 0x401000
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x400005  <= rsp
0x7fffffffc400 | 0x0

[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax
...
0x401000 | push rbp  <= rip

 

leave : 스택프레임 정리

mov rsp, rbp

pop rbp

; 예제
[Register]
rsp = 0x7fffffffc400
rbp = 0x7fffffffc480

[Stack]
0x7fffffffc400 | 0x0 <= rsp
...
0x7fffffffc480 | 0x7fffffffc500 <= rbp
0x7fffffffc488 | 0x31337 

[Code]
leave

; 결과
[Register]
rsp = 0x7fffffffc488
rbp = 0x7fffffffc500

[Stack]
0x7fffffffc400 | 0x0
...
0x7fffffffc480 | 0x7fffffffc500
0x7fffffffc488 | 0x31337 <= rsp
...
0x7fffffffc500 | 0x7fffffffc550 <= rbp

 

ret : return address로 반환

pop rip

; 예제
[Register]
rip = 0x401008
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x400005    <= rsp
0x7fffffffc400 | 0

[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax
...
0x401000 | mov rbp, rsp  
...
0x401007 | leave
0x401008 | ret  <= rip

; 결과
[Register]
rip = 0x400005
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc3f8 | 0x400005
0x7fffffffc400 | 0x0    <= rsp

[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax   <= rip
...
0x401000 | mov rbp, rsp  
...
0x401007 | leave
0x401008 | ret

 

  • 시스템 콜 (Syscall)

유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용한다.

※ syscall

요청 : rax # syscall table 참고

인자 순서 : rdi → rsi → rdx → rcx → r8 → r9 → stack