STUDY/Reverse Engineering

Assembly

Dalseung 2025. 1. 1. 21:02

개요

지난 시간 우리는 아키텍쳐에 대해서 배운바가 있다.

오늘은 고수준의 언어에서 기계어로 가기위한 중간 단계인 assembly에 대해서 알아보도록 하자.

아키텍쳐

프로세서에서 사용한 명령어 집합 구조들을 통칭하는 말로, 아키텍쳐에 따라 레지스터와 명령어(Assembly)가 나뉜다.

x86을 예를 들어 보자면 32bit / 64bit는 컴퓨터가 한번에 처리할 수 있는 단위를 말한다.

이를 우리는 1WORD라 정의한다.

레지스터

CPU의 기억장치로 명령을 처리하는데 필요한 데이터를 일시적으로 저장한다.

크기는 작지만 기억장치 중에 속도가 가장 빠르다.

종류

  • 범용레지스터
    • 산술 연산 레지스터
    • 인덱스 레지스터
    • 포인터 레지스터
  • 세그먼트 레지스터
  • 플래그 레지스터

범용레지스터

이름 주 용도
RAX 함수의 반환값
RBX 메모리 주소
RCX 반복문의 반복 및 각종연산의 시행 횟수
RDX 부호 확장 및 곱셈, 나눗셈
RSI 데이터를 옮길 때 원본을 가리키는 포인터
RDI 데이터를 옮길 때 목적지를 가리키는 포인터
RSP 사용중인 스택의 위치를 가리키는 포인터
RBP 스택의 바닥을 가리키는 포인터
RIP 현재 실행중인 명령어의 위치를 가리킴

대체적으로 이렇게 쓰일뿐 이런 상황에서 무조건 쓰인다는 내용은 아니다.

함수 호출 규약에 따라 레지스터는 사용되어진다.

세그먼트 레지스터

각 섹션의 주소를 가리킴

이름 주 용도
cs 코드 섹션 레지스터
ss 스택 메모리 레지스터
ds 데이터 섹션 레지스터
es 운영체제마다 범용적인 용도로 사용
fs 운영체제마다 범용적인 용도로 사용
gs 운영체제마다 범용적인 용도로 사용

플래그 레지스터

이름 주 용도
CF(Carry Flag) Unsigned의 연산 결과가 Overflow 되었을 때 1로 설정
ZF(Zero Flag) 연산 결과가 0일 경우 1로 설정
SF(Sign Flag) 연산 결과가 음수일 경우 1로 설정
OF(Overflow Flag) Signed의 연산결과가 Overflow 되었을 때 1로 설정

image

전체가 플래그 레지스터이고, 이것이 잘게 1비트씩 쪼개어져서 각각의 플래그를 나타내고 있는 사진이다.

레지스터 호환성

EAX는 32bit 운영체제에서 동작하는 레지스터이고, AX 16bit가 확장된 것이다.

그림상으로 보면 AX도 AH와 AL로 각각 8bit 씩 구성된 것으로 볼 수 있다.

image

image

데이터 단위

이름 크기
bit 1bit
byte 8bit
WORD 2byte = 16bit
DWORD 2WORD(Double WORD) = 4byte = 32bit
QWORD 4WORD(Quater WORD) = 8byte = 64bit

ASSEMBLY

어셈블리어는 연산자를 뜻하는 Opcode와 피연산자인 Operand로 이루어져있다.

가령 MOV RAX, 0x41이라는 명령어가 있다면, Opcode는 MOV, Operand는 RAX와 0x41이다.

용도 코드
데이터 이동 mov, lea
산술 연산 inc, dec, add, sub
논리 연산 and, or, xor
비교 cmp, test
분기 jmp, je, jg, jne, jng
기타 push, pop, call, ret, leave, syscall

명령어

  • mov operand1, operand2 : 피연산자 2를 피연산자 1에 대입
  • lea operand1, operand2 : 피연산자2의 주소를 피연산자 1에 대입
    • 또는, lea rsi, [rax x 3 + 2]의 경우 rax x 3 + 2의 값을 rsi에 대입
  • add : 더하기
  • sub : 빼기
  • inc : 1 더하기
  • dec : 1 빼기
  • cmp rax, rbx : rax와 rbx를 뺀 값을 확인하여 비교
    • ZF = 1일 시 두 값은 같은 값이 된다.
  • test rax, rbx : rax와 rbx를 and 연산하고 두 값에서 같은 비트가 있는 지 확인할 수 있다.
    • test rax, rax와 같은 명령어는 실질적으로 rax가 0인지 아닌지 확인하는 작업이다.
    • cmp는 두 값을 뺄셈 연산을 통해 비교하는 반면 test는 단순히 and 연산만 수행하기 때문에 계산 오버헤드가 낮고 저장을 하지 않기 때문에 기존 값을 변경하지 않으면서, 플래그만 업데이트를 한다.
  • jmp 0x00001001: 해당 주소로 점프
  • je(jmp equal) : 비교 했을 때, 두 값이 같으면 해당 주소로 점프
  • jg(jmp greater) : 두 값일 비교했을 때, op1이 더 크면 해당 주소로 점프
  • jl(jmp less) : op1이 더 작으면 해당 주소로 점프

operand

피연산자에는 상수, 레지스터, 메모리 총 3가지 종류가 존재한다.

특히 메모리일 경우 mov rax BYTE PTR [0x501000]과 같이 [ ]로 둘러싸져있고, 앞부분에 참조할 크기를 지정해주기도 한다.

이 경우 0x501000의 주소에서 1byte만 참조해서 rax에 대입하겠다는 뜻이다.

'STUDY > Reverse Engineering' 카테고리의 다른 글

[Dreamhack] Embedded Hacking  (0) 2024.03.18