Reverse Engineering

x86 Assembly: Essential Part(1)

박연준 2023. 7. 2. 15:03

  • 컴퓨터 과학자 중 한 명인 David Wheeler는 EDSAC을 개발하면서 어셈블리 언어와 어셈블러라는 것을 고안했다.
  • 소프트웨어를 역분석하는 사람들은 역발상을 더해, 기계어를 어셈블리 언어로 번역하는 역어셈블러를 개발했다.

x86-64 어셈블리 언어

기본 구조

  • x64 어셈블리 언어는 우리가 사용하는 한국어보다는 훨씬 단순한 문법 구조를 지닌다.
  • 이들의 문장은 동사에 해당하는 명령어(Operation Code, Opcode)와 목적어에 해당하는 피연산자(Operand)로 구성된다.

명령어

  • 인텔의 x64에는 매우 많은 명령어가 존재한다.
  • 이 중 중요한 21개의 명령어는 다음과 같다.
명령 코드
데이터 이동(Data Transfer)movlea
산술 연산(Arithmetic)incdecaddsub
논리 연산(Logical)andorxornot
비교(Comparison)cmptest
분기(Branch)jmpjejg
스택(Stack)pushpop
프로시져(Procedure)callretleave
시스템 콜(System call)syscall

피연산자

  • 피연산자에는 총 3가지 종류가 올 수 있다.
    • 상수
    • 레지스터
    • 메모리
  • 메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자(Size Directive) 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바이트 만큼 참조

x86-64 어셈블리 명령어

데이터 이동

  • 데이터 이동 명령어는 어떤 값을 레지스터나 메모리에 옮기도록 지시한다.
mov dst, src : src에 들어있는 값을 dst에 대입
mov rdi, rsirsi의 값을 rdi에 대입
mov QWORD PTR[rdi], rsirsi의 값을 rdi가 가리키는 주소에 대입
mov QWORD PTR[rdi+8*rcx], rsirsi의 값을 rdi+8*rcx가 가리키는 주소에 대입
lea dst, src : src의 유효 주소(Effective Address, EA)를 dst에 저장합니다.
lea rsi, [rbx+8*rcx]rbx+8*rcx 를 rsi에 대입

산술 연산

  • 산술 연산 명령어는 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시한다.
add dst, src : dst에 src의 값을 더합니다.
add eax, 3eax += 3
add ax, WORD PTR[rdi]ax += *(WORD *)rdi
sub dst, src: dst에서 src의 값을 뺍니다.
sub eax, 3eax -= 3
sub ax, WORD PTR[rdi]ax -= *(WORD *)rdi
inc op: op의 값을 1 증가시킴
inc eaxeax += 1
dec op: op의 값을 1 감소 시킴
dec eaxeax -= 1

논리 연산

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

    and dst, src: dst와 src의 비트가 모두 1이면 1, 아니면 0

    [Register]
    eax = 0xffff0000
    ebx = 0xcafebabe
    [Code]
    and eax, ebx
    [Result]
    eax = 0xcafe0000

    or dst, src: dst와 src의 비트 중 하나라도 1이면 1, 아니면 0

    [Register]
    eax = 0xffff0000
    ebx = 0xcafebabe
    [Code]
    or eax, ebx
    [Result]
    eax = 0xffffbabe

    xor dst, src: dst와 src의 비트가 서로 다르면 1, 같으면 0

    [Register]
    eax = 0xffffffff
    ebx = 0xcafebabe
    [Code]
    xor eax, ebx
    [Result]
    eax = 0x35014541

    not op: op의 비트 전부 반전

    [Register]
    eax = 0xffffffff
    [Code]
    not eax
    [Result]
    eax = 0x00000000

비교

  • 비교 명령어는 두 피연산자의 값을 비교하고, 플래그를 설정한다.

    cmp op1, op2: op1과 op2를 비교

    [Code]
    1: mov rax, 0xA
    2: mov rbx, 0xA
    3: cmp rax, rbx ; ZF=1

    test op1, op2: op1과 op2를 비교

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

분기

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

    jmp addr: addr로 rip를 이동시킵니다.

    [Code]
    1: xor rax, rax
    2: jmp 1 ; jump to 1

    je addr: 직전에 비교한 두 피연산자가 같으면 점프 (jump if equal)

    [Code]
    1: mov rax, 0xcafebabe
    2: mov rbx, 0xcafebabe
    3: cmp rax, rbx ; rax == rbx
    4: je 1 ; jump to 1

    jg addr: 직전에 비교한 두 연산자 중 전자가 더 크면 점프 (jump if greater)

    [Code]
    1: mov rax, 0x31337
    2: mov rbx, 0x13337
    3: cmp rax, rbx ; rax > rbx
    4: jg 1  ; jump to 1

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

Tools: IDA  (0) 2023.07.02
x86 Assembly: Essential Part(2)  (0) 2023.07.02
Background: Windows Memory Layout  (0) 2023.07.02
Background: Computer Architecture  (0) 2023.07.02
Static Analysis vs. Dynamic Analysis  (0) 2023.07.02