Reverse Engineering - X86 Assembly

장진혁·2025년 7월 6일

Reverse Engineering

목록 보기
6/6

어셈블리어와 x86-64

어셈블리어


컴퓨터의 기계어와 치환되는 언어

어셈블리어는 컴퓨터 아키텍처별로 다른 문법을 가지고 있음 (Intel, AT&T)

x86-64 어셈블리어


문장의 동사에 해당하는 명령어(Operation Code, Opcode)와 목적어에 해당하는 피연산자(Operand)로 구성, 피연산자는 연산의 입력 값(레지스터, 상수, 메모리 주소)

명령어


피연산자


피연산자에는 3가지 종류가 올 수 있음

  • 상수(Immediate Value)
  • 레지스터(Register)
  • 메모리(Memory)
    메모리 피연산자는 []로 둘러 싸인 것으로 표현, 앞에 크기 지정자가 올 수 있음
mov [rdi], eax  ; (X) 오류 발생 가능: 크기 지정이 명확하지 않음
mov DWORD PTR [rdi], eax  ; (O) 명확한 크기 지정

피연산자 표기법의 일부

산술 연산과 논리 연산

add, sub, mul, div


add : 덧셈
sub : 뺄셈
mul, imul : 부호 있는 곱셈 / 부호 없는 곱셈
div, idive : 부호 있는 나눗셈 / 부호 없는 나눗셈

mul

암시적으로 RAX 레지스터롤 첫번째 피연산자로 사용

  • 8비트 x 8비트
    AL 레지스터의 값과 src 피연산자의 값을 곱함, 두 숫자의 연산 결과는 최대 16비트 이기 때문에 AX(16비트) 레지스터에 보관
  • 16비트 x 16비트
    AX 레지스터의 값과 src 피연산자의 값을 곱함, 두 숫자의 연산 결과는 최대 32비트 이기 때문에 상위 16비트는 DX 레지스터, 하위 16비트는 AX 레지스터에 보관
  • 32비트 x 32비트
    EAX 레지스터의 값과 src 피연산자의 값을 곱함, 상위 32비트는 EDX 레지스터, 하위 16비트는 EAX 레지스터에 보관
  • 64비트 x 64비트
    RAX 레지스터의 값과 src 피연산자의 값을 곱함, 상위 64비트는 RDX 레지스터, 하위 64비트는 RAX 레지스터에 보관

imul

피연산자가 3개까지 올 수 있음

imul <source> ; 피연산자가 1개
// mul가 유사하게 동작
imul <destination>, <source> ; 피연산자가 2개
// destination에 source를 곱한 결과를 destination에 대입
imul <destination>, <source>, <immediate> ; 피연산자가 3개
// sorce와 immediate를 곱합 결과를 destination에 대입

연산 결과가 목적지 피연산자의 크기를 초과하여 데이터 손실이 발생하면, 캐리 플래그(CF)와 오버플로우 플래그(OF)를 1로 설정하여 이를 알림

div/idiv

div/idiv 제수

나눗셈을 수행할 피제수의 크기에 따라 AX, DX:AX, EDX:EAX, RDX:RAX에 저장, 결과인 몫과 나머지를 크기에 따라 AL:AH, AX:DX, RAX:RDX에 저장

lea


메모리에 실제 접근을 하지 않고, 유효 주소(Effective Address, EA)를 저장하기 위해 사용

lea <destination>, <source>

cmp, test


두 피연산자의 값을 비교하고 플래그 설정

cmp <destination>, <source>

내부적으로 두 피연산자를 빼본 후 플래그 갱신

test <destination>, <source>

AND 연산을 수행하고 결과를 버리고 플래그만 갱신

어셈블리어에서의 함수 선언


  1. 함수 시작 위치를 나타낼 라벨(Label) 정의
  2. 스택 프레임이 필요한 경우, 함수 프롤로그를 통해 스택 프레임 구성
  3. 함수 내부에서 실제 동작을 구현
  4. 함수 마지막에 함수 에필로그를 통해 스택 프레임을 해제하고, ret 명령어로 종료

함수 호출 과정


x86

함수에 전달한 인자를 스택에 pushcall 명령어로 함수 호출
함수가 끝난 후 호출자 측에서 스택 정리 해줘야 함

section .text
global _start

add:
    push ebp
    mov ebp, esp
    mov eax, [ebp + 8]
    add eax, [ebp + 12]
    leave
    ret

_start:
    push dword 20
    push dword 10
    call add
    add esp, 8
    mov ebx, eax
    mov eax, 1
    int 0x80

x64

함수를 부르기 전, mov rdi, 10, mov rsi, 20으로 인자 설정 후 call add 실행
함수 내부에서는 rdirsi를 더한 결과가 rax에 담겨, ret으로 복귀시 _start로 돌아와 rax에 있던 합 사용 가능

section .text
global _start

add:
    push rbp
    mov rbp, rsp
    mov rax, rdi
    add rax, rsi
    pop rbp
    ret

_start:
    mov rdi, 10
    mov rsi, 20
    call add
    mov rdi, rax
    mov rax, 60
    syscall

Opcode 시스템 콜


현대 운영체제는 사용자 모드(User Mode)커널 모드(Kernel Mode)로 나뉘어 동작

커널 모드

운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한
모든 메모리 영역 접근, 하드웨어에 직접적으로 접근, 시스템의 모든 부분 제어 가능

사용자 모드

접근할 수 있는 메모리 영역과 권한이 한정, 하드웨어에 직접적으로 접근 불가

시스템 콜

유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용
도움이 필요하다는 요청이 시스템 콜

profile
jnhhyuk

0개의 댓글