어셈블리어
기계어와 일대일 대응이 되는 저급 언어이다.
사람이 읽기 쉬운 C언어와 읽기 어려운 기계어의 중간 단계이고
기계어를 조금 더 쉽게 읽기 위해서 일대일로 문자화 해서 만들어졌다.
기계어와 어셈블리어는 서로 대응하기 때문에 명령어 집합 구조(ISA)의 종류에 따라 어셈블리어도 달라진다.
명령어 집합 구조 (ISA: Instruction Set Architecture): CPU가 해석하고 실행할 수 있는 모든 기계어 명령어들을 포함한다. 설계 방식에 따라 CISC와 RISC로 나뉘는데 CISC는 x64, x86등이 있고, RISC는 ARM,MIPS등이 있다.
x64
CISC방식의 ISA(명령어 집합 구조) 중 하나이다.
반도체 회사인 Intel과 AMD 등이 사용하며, x86과 함께 압도적인 CPU의 점유율를 보유하고 있다.
x64는 x86의 확장 버전, 즉 상위 버전이기 때문에 x86을 완벽하게 지원하지만 반대로 x86은 x64보다 하위 버전이므로 지원하지 못한다. 그리고 x64는 x86-64, AMD64, Intel64등 여러 개의 명칭으로 불리는데 거의 다 x64를 의미하고 앞에 두 회사에서 각각 수정하며 이름을 바꾼 것이다.
x64 레지스터
레지스터(Register): 메모리 계층 제일 위에 있는 메모리이며, CPU안에 들어있다.
용량이 매우 작지만 연산속도가 매우 빠르기 때문에 일반적으로 현재 연산중인 값, 명령어의 주소, 스택 프레임의 위치같이 이 제일 필요한 값들이 저장된다.
<레지스터의 크기>
x64: - 64bit = 8Byte (8B)
- R로 시작함 (ex: RAX, RBX, R8..)
- R8~R15가 있음 (x86에는 없음)
- 최소 단위 QWORD (Quad WORD)
x86: - 32bit = 4Byte (4B)
- E로 시작함 (ex: EAX, EBX, ECX..)
- 최소단위 DWORD (Double WORD)
- EAX~EDX (4B)를 반으로 나눈 AX, BX, CX, DX(2B)
- 반으로 나눈 AX~DX 중 상위(high) : AH~DH(1B)
하위(low) : AL~DL(1B)
(EAX가 AX를 포함, 그 AX가 AH, AL을 포함하고 있는 형태, 시스템 호환을 위해 설계된 형태이다.)
WORD: 16bit 컴뷰터가 레지스터에 옮겨 놓을 수 있는 데이터 최소 단위이다.
WORD(16bit) -> DWORD(32bit) -> QWORD(64bit)
<레지스터의 종류>
범용 레지스터: 범용적으로 사용되는 레지스터이다. 권고되는 역할이 있지만 참고만 해도 되고 아무렇게나 써도 된다.
RAX | Accumulator | -산술/논리 연산을 수행 -함수의 return값이 저장됨 |
RBX | Base | 메모리 주소를 저장하기 위한 용도로 사용 |
RCX | Count | 반복문에서 카운터 변수로 사용 고급언어 for문의 i, j와 같은 역할 |
RDX | Data | 다른 레지스터 서포트하는 여분 레지스터 큰 수의 곱셈/나눗셈에서 RAX랑 사용 |
R8 ~ R15 | 다용도인 여분 레지스터 |
인덱스 레지스터: 데이터가 저장되어 있는 메모리 주소를 가리키는 포인터로 사용되는 레지스터이다.
RSI | Source | 데이터를 복사할 때 복사할 데이터(원본)출발지 주소 저장됨 |
RDI | Destination | 데이터를 복사할 때 복사된 데이터 목적지 주소가 저장됨 |
포인터 레지스터: 프로그램 실행 과정에서 사용하는 특정 메모리 위치 주소를 가리키는 레지스터이다.
RSP | Stack Pointer |
현재 사용중인 스택에 끝 지점 주소,
즉 맨 위(top)를 가리킨다. |
RBP | Base Pointer | 현재 사용중인 스택의 시작 지점 주소, 즉 맨 아래(스택 복귀 주소)를 가리킨다. |
RIP | Instruction Pointer | 프로그램카운터의 역할을 한다. 프로그램 카운터는 다음에 실행될 명령어가 위치한 주소를 가리킨다. |
함수의 매개변수로서의 레지스터: 프로그램이 실행될 때 여러 가지 함수들의 인자 전달을 하는 레지스터이다.
매개변수(parameter): 호출된 함수에서 넘어온 값
인자(argument) : 함수가 호출될 때 넘기는 변숫값
호출 규약: 함수 호출 시 일어나는 행동에 대한 규칙을 의미하며 함수 인자들에 대해 어떤 순서로 스택에 쌓을 것 인지, 인자를 레지스터로 이용할 것 인지 그리고 함수 종료 후 스택을 누가 정리할 것 인지에 대해 정해놓은 약속이다.
(ex: __cdecl, __stdcall, __fastcall, __thiscall)
x64 순서대로 지정된 레지스터에 들어감, r9까지 들어가면 나머지는 스택에 쌓음 |
int function(int a, int b, int c, int d, int e, int f, int g, int h) | | | | | | | rdi rsi rdx rcx r8 r9 STACK |
x86 레지스터 사용 X 모두 스택에 쌓음 |
int function(int a, int b, int c, int d, int e, int f, int g, int h) | STACK <------------------------------------------------------------ STACK ^ int a | int b | int c | int d | int e | int f | int g | int h | |
플래그 레지스터 : 일반적인 레지스터와 다르게 여러 가지의 특정한 상태 값을 저장하는 레지스터이다. 이 레지스터 안에는 하나하나의 비트가 각각 서로 다른 의미를 갖고 있어서 각각 독립적으로 사용된다. 0(거짓) or 1(참)의 상태를 가진다.
CF | Carry Flag | 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우(자리올림) 1이 됨 |
ZF | Zero Flag | 연산 결과가 0일 경우 1이 됨 |
SF | Sign Flag | 부호 있는 수의 연산 결과가 음수일 경우 1이 됨 (양수면 0) |
OF | Overflow Flag | 부호 있는 수의 연산결과가 비트의 범위를 넘을 경우(=오버플로우) 1이 됨 |
OF: 오버플로우 원리로 최대 숫자(01111111)에 +1을 더하면 최소 숫자(10000000, 맨 앞에 부호 비트가 건드려져 음수)가 됨)
스택 프레임
<메모리의 구조>

스택(Stack): - 함수의 지역변수(함수 안에서 정의된 변수여서 함수 밖에서는 못씀)
- 높은 주소 -> 낮은 주소 방향(힙과 반대방향)
- 후입 선출(LIFO)의 특성을 가진 자료구조이며, 푸시(PUSH) 연산과 팝(POP) 연산을 이용

<함수의 스택 프레임>
스택 프레임(Stack Frame): - 함수가 호출될 때, 그 함수만의 스택 영역을 구분하기 위해 생성되는 공간
- 이 공간에는 함수와 관계되는 지역변수, 매개변수가 저장됨,
- 함수 호출 시 할당되고, 함수 종료 시 소멸됨
- 이 영역을 표현하기 위해 함수 프롤로그(Prolog)와 함수 에필로그(Epilog)를 수행

포인터 레지스터 부분에 있었던 RBP는 현재 사용 중인 프레임의 맨 아래, RSP는 맨 위를 가리킨다.
함수 프롤로그(Prolog)

1. 현재 실행 중인 main이 func를 호출하면
2. func의 스택 프레임을 만들기 위해 RSP에 RBP를 올리고(main의 맨 아래에 있었던 RBP가 main의 맨 위로 가서 func의 맨 아래를 가리킴)
3. RSP를 func의 프레임 크기만큼 올려서 RBP와 RSP 사이의 공간, 즉 스택 프레임이 만들어진다.
함수 에필로그(Epilog)

1. func 함수의 실행이 끝났을 때
2. 더 이상 필요 없어진 func 함수 프레임을 소멸시키기 위해서 RSP가 다시 main 맨 위로 내려오고
3. RBP는 main의 맨 아래로 돌아가서 main 함수 프레임의 시작 부분(경계)을 가리킴
*그런데 RBP가 main함수의 경계위치로 돌아가는 것이 어려움
-> 스택 프레임의 구조를 보면 어떻게 돌아갈 위치를 찾는지 알 수 있음
<스택 프레임의 구조>
SFP : main 함수 프레임의 맨 아래(경계)의 주소가 저장됨 -> 다시 돌아갈 때 SFP에 있는 주소를 RBP가 꺼내서 넣기 때문에 돌아갈 수 있음
RET: main에서 func으로 이동하기 전에, 실행되고 있던 main함수가 몇 번째 명령어까지 실행하고 있었는지의 주소가 저장됨, 즉 RIP의 값이 만들어짐( func 함수가 다 실행되면 RET에 저장된 주소가 main함수로 돌아갔을 때 다음으로 이어서 실행해야 할 명령어의 주소를 의미함)
어셈블리어 명령어
<산술 명령어>
and rax, rdx -> rax = rax & rdx
or rax, rdx -> rax = rax | rdx
xor rax, rdx -> rax = rax ^ rdx
cmp rax, rdx -> rax – rdx의 결과를 플래그 레지스터에 저장
test rax, rdx -> rax & rdx의 결과로 ZF 설정(1 or 0)
<데이터 이동 명령어>
movA, B -> B의 값을 A에 복사
lea A, B -> B의 주소를 A에 복사(* [ ] = 주소에 있는 값에 접근)
좌변엔 레지스터, 우변엔 주소(상수 X)
push A -> 스택 맨 위에 A를 올림(RSP가 한 칸 올라감)
pop B -> 스택 맨 위 값을 B에 넣고 스택에서 삭제(RSP 한 칸 내려감)
<흐름 제어 명령어>
call func -> func 함수 호출
ret(return) -> 호출된 함수에서 호출한 함수로 복귀
jmp -> 조건에 따라 특정한 곳으로 분기(점프)할지 결정
jmp 명령어
'Layer7' 카테고리의 다른 글
리버싱 4차시 과제(C언어 파일 리버싱, prob1) (0) | 2022.07.27 |
---|---|
Pwndbg 명령어 (0) | 2022.07.25 |
리버싱 2차시 과제(실행파일이 만들어지는 과정) (0) | 2022.07.20 |
하드웨어 3, 4차시 과제(SoC, 하드웨어 인터페이스, 펌웨어, 논리회로, 펌웨어 획득, 펌웨어 이미지 분석) (0) | 2022.06.19 |
하드웨어 2차시 과제(파이프라이닝, 비순차적 실행, 분기 예측, 추측 실행) (0) | 2022.06.12 |