안녕하십니까 코딩 파인애플입니다
이번 시간에는 레지스터와 어셈블리어 기초 산술 연산에 관해 알아보겠습니다.
레지스터(register)는 중앙처리장치(CPU) 내부에 있는 저장장치를 의미합니다
CPU가 연산을 수행하려고 하면 필요한 데이터를 먼저 레지스터에 저장해야 합니다.

다음은 x86-64에서 사용하는 주요 레지스터입니다
| 레지스터 | 주요 역할 |
|---|---|
| rax | 연산 결과나 함수의 반환값 저장 |
| rbx | 일반적인 데이터 임시 저장 |
| rcx | 반복 횟수나 특정 연산의 횟수 저장 |
| rdx | 곱셈·나눗셈 또는 함수 인자 전달에 사용 |
| rsi | 데이터를 옮길 때 원본 주소를 가리킴 |
| rdi | 데이터를 옮길 때 목적지 주소를 가리킴 |
| rsp | 현재 스택의 가장 위쪽 위치를 가리킴 |
| rbp | 현재 함수가 사용하는 스택 영역의 기준 위치 |
| rip | 현재 또는 다음에 실행할 명령어의 위치를 가리킴 |
(천천히 외워도 좋습니다)
세그먼트 레지스터는 메모리 영역을 구분하기 위해 사용됩니다
| 레지스터 | 의미 |
|---|---|
| cs | 코드가 저장된 영역 |
| ss | 스택이 저장된 영역 |
| ds | 일반 데이터가 저장된 영역 |
플래그 레지스터는 연산 결과의 상태를 기록합니다
예를 들어 두 숫자를 더한 결과값이 0인지, 음수인지, 범위 초과인지 등을 저장합니다.
| 플래그 | 의미 |
|---|---|
| CF | 부호 없는 숫자에서 자리 올림 또는 범위 초과가 발생하면 1. |
| ZF | 연산 결과가 0이면 1. |
| SF | 연산 결과의 최상위 비트가 1이면 1, 음수인지 확인하는 데 쓰임. |
| OF | 부호 있는 숫자의 연산 결과가 표현 범위를 넘으면 1. |
CF와 OF는 부호가 있냐 없냐에 따라 범위 초과를 알립니다
예를 들어 8비트 부호 없는 숫자는 0~255까지 표현할 수 있습니다
여기서 255+1이 되면 256이 되는데
8비트로 부호 없는 숫자로 256를 표현할 수 없으므로 CF가 설정됩니다
반면 8비트 부호 있는 숫자는 -128~127까지 표현할 수 있습니다
여기서 127+1이 되면 128이 되는데
8비트 부호 있는 숫자로 128을 표현할 수 없으므로 OF가 설정됩니다
어셈블리어의 기본 연산은 크게 다음과 같이 나눌 수 있습니다
1️⃣ 데이터 이동 연산
2️⃣ 산술 연산
3️⃣ 논리 연산
4️⃣ 비교 연산
5️⃣ 분기 연산
데이터 이동 연산은 값을 레지스터나 메모리로 옮기는 연산을 의미합니다
C언어로 비유하면 '='이 있겠습니다
대표적인 명령어는 mov, push, pop 등이 있습니다
mov는 값을 복사하여 다른 위치에 저장하는 명령어입니다
기본 구조는 다음과 같습니다
mov 목적지, 원본
만일 mov rax, 10일 경우 rax = 10이 되겠습니다
또한 mov rax, rbx라면 rax = rbx 정도로도 볼 수 있겠습니다.
+ 리버싱을 하다 보면 lea이라는 것도 자주 볼 수 있는데 이는 주소 계산을 수행하는 mov라 보시면 편합니다
push는 값을 스택에 저장하는 명령어입니다
push rax라면 "rax라는 값을 스택에 저장한다"가 되겠습니다
pop는 스택의 가장 위에 있는 값을 꺼내는 명령어입니다
pop rax라면 "스택의 가장 위에 있는 값을 꺼내 rax에 저장한다" 가 되겠습니다
산술 연산은 숫자를 더하거나 빼고, 곱하거나 나누는 연산을 의미합니다
대표적인 명령어로는 add, sub, inc, dec, mul, imul, div, idiv가 있습니다

add는 두 값을 더하는 명령어입니다
기본 구조는 다음과 같습니다
add 목적지, 원본
만일 mov rax, 10 add rax, 5라고 할 경우
rax=10;
rax+=5; 연산과 같다고 볼 수 있겠습니다
또한 다른 레지스터의 값을 더할 수도 있습니다
mov rax, 10
mov rbx, 20
add rax, rbx
sub는 첫 번째 값에서 두 번째 값을 빼는 명령어입니다.
sub 목적지, 원본
예...그냥 -(마이너스) 입니다 마이너스.
inc는 값을 1 증가시킵니다
mov rax, 5
inc rax
위 예시 처럼 rax=5라는 값을 inc을 통해 1 증가시켜 rax는 6이 됩니다
c언어로 비유하자면 rax++; 정도 되겠습니다
dec는 값을 1 감소시킵니다
mov rax, 5
dec rax
마찬가지로 rax=5라는 값을 dec을 통해 1 감소시켜 rax는 4가 됩니다
c언어로 비유하자면 rax--; 정도 되겠습니다
mul은 부호 없는 숫자의 곱셈을 진행해주는 명령어입니다
mov rax, 5
mov rbx, 3
mul rbx
을 진행하여 rax는 15의 값을 가집니다
(참고로 mul은 항상 rax을 피연산자로 사용합니다)
imul은 부호 있는 숫자의 곱셈을 진행해주는 명령어입니다
mov rax, -5
imul rax, 3
을 진행하여 rax는 -15의 값을 가집니다
div는 부호 없는 수자의 나눗셈을 진행해주는 명령어입니다
나눗셈에서는 일반적으로 rdx와 rax를 함께 사용합니다
mov rax,10
mov rdx,0
mov rbx,3
div rbx
보편적으로
rax = 몫, rdx = 나머지 으로 사용됩니다
따라서 결과는 다음과 같아집니다
rax = 3, rdx = 1
idiv는 부호 있는 숫자의 나눗셈을 진행해주는 명령어입니다
div와 사용 방법은 비슷하지만 음수를 처리할 수 있습니다
논리 연산은 데이터를 비트 단위로 계산하는 연산입니다
대표적인 명령어로는 and, or, xor, not이 있습니다

and는 두 비트가 모두 1일 때만 결과값을 1로 만듭니다
반대로 한 비트라도 0이면 결과값은 0이 됩니다
0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1
만일 rax = 1101, rbx = 1011 일 경우.
and rax, rbx을 하게 된다면
rax = 1001이 됩니다.
또한 and는 특정 비트만 남기고 나머지 비트를 제거할 때 자주 사용됩니다
or는 두 비트 중 하나라도 1이면 결과값을 1로 만듭니다
반대로 두 비트 전부 0이면 결과값은 0이 됩니다
0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1
만일 rax = 1100, rbx = 0011 일 경우.
or rax, rbx을 하게 된다면
rax = 1111이 됩니다
xor는 두 비트가 서로 다르면 1, 같으면 0이 됩니다
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
만일 rax = 1100, rbx = 1010 일 경우.
xor rax, rbx을 하게 된다면
rax = 0110이 됩니다
또한 레지스터를 0으로 초기화할 때 자주 사용합니다
not은 모든 비트를 반대로 바꿉니다
C-MOON
rax = 0101이 됩니다
비교 연산은 두 값을 비교하여 플래그 레지스터를 설정하는 연산입니다
대표적인 명령어는 cmp와 test가 있습니다

cmp는 두 값을 비교하는 명령어입니다
cmp 값1, 값2
내부적으로는 다음과 같은 계산을 수행합니다
값1 - 값2
하지만 계산 결과를 저장하지 않고 플래그만 설정합니다
만일 mov rax, 10 cmp rax, 10 일 때
내부에서는 10 - 10 = 0 을 계산하여 결과가 0이므로 ZF는 1로 설정되고 참이 되는 겁니다
하지만 만일 ZF가 1이 아니라면 그 결과는 거짓이 됩니다
참고로 cmp는 이후에 나오는 조건 분기 명령어와 함께 사용됩니다
test는 두 값을 AND 연산한 뒤 결과를 저장하지 않고 마찬가지로 플래그만 설정합니다
test rax, rax라고 할 경우 rax 와 자기 자신을 and 연산합니다
만일 rax가 0이라면 결과도 0이므로 ZF가 1이 되고
따라서 다음과 같이 사용할 수 있습니다
test rax, rax
jz zero
cmp와 test 겉보기엔 둘 다 피연산자를 변경하지 않고 플래그만 남긴다는 공통점이 있습니다
하지만 cmp는 연산을 뺄셈으로 진행하므로 주로 두 값의 대소/일치 비교에 쓰이고
test의 경우 and 기반이기에 (a & b) 특정 비트가 켜졌는지 혹은 값이 0인지 확인하기 위해 쓰이는 차이점이 존재합니다.
분기 연산은 어셈블리어에서 프로그램의 흐름을 제어하는 연산입니다
대표적인 명령어는 jmp,je,jne,jg,jl이 있습니다.

jmp는 무조건 분기하는 명령어입니다 어떠한 경우에도 따지지 않고 지정된 주소로 이동합니다
jmp 1 ; 1번 코드로 점프
C언어로 비유하면 return 1이 있겠습니다
je는 두 값이 같으면 점프합니다
cmp rax, rbx
je 1 ; rax와 rbx의 값이 같으면 점프
jne는 je와 반대로 직전 비교한 값이 다르면 점프합니다
cmp rax, rbx
jne 1 ; rax와 rbx의 값이 다르면 점프
jg는 두 피연산자 중 첫 번째 값이 더 크면 점프합니다
C언어로 비유하자면 if (rax > rbx) 와 같은 의미가 되겠습니다
jl는 jg와 반대로 첫 번째 값이 더 작으면 점프합니다
if (rax < rbx) 와 같은 의미입니다
그 외의 분기문 명령어들
- jnz (jump if not zero) : 결과가 0이 아니면 점프
- jz (jump if zero) : 결과가 0이면 점프
- jle (jump if less or equal) : 작거나 같으면 점프
- jge (jump if greator or equal) : 크거나 같으면 점프
- js (jump if sign) : 음수이면 점프
- jns (jump if not sign) 음수가 아니면 점프
간단하게 3문제 정도 테스트해보도록 하겠습니다
(정답은 댓글에)
1. 조건 분기
0x401000: b8 05 00 00 00 mov eax, 0x5
0x401005: 83 f8 03 cmp eax, 0x3
0x401008: 7e 05 jle 0x40100f
0x40100a: b8 01 00 00 00 mov eax, 0x1
0x40100f: c3 ret
최종 eax 값은? (여기서 ret은 c언어에서 return과 같다)_
2. lea 과 mov
0x401000: 48 8d 47 08 lea rax, [rdi + 0x8]
0x401004: 48 8b 0f mov rcx, [rdi]
0x401007: 48 8b 57 08 mov rdx, [rdi + 0x8]
rax,rcx의 값은? (단 rdi = 0x404000, 그 주소의 8바이트 내용은 0x00000000deadbeef.)
3. 연산
0x401000: b8 0a 00 00 00 mov eax, 0xa
0x401005: bb 03 00 00 00 mov ebx, 0x3
0x40100a: 01 d8 add eax, ebx
0x40100c: 83 c0 05 add eax, 0x5
0x40100f: 29 d8 sub eax, ebx
0x401011: c3 ret
ret 직전의 eax 값은?
이상으로 글을 마치겠습니다 감사합니다

