Assembly Language Basic

dmswl·2025년 10월 15일

System Security

목록 보기
8/15

80x86 system's CPU & Register

  • 80386
  • 이전 세대에 비해 크게 확장된 수치인 4GB 물리 메모리 사용
  • 32-bit register

Computer system architecture

저장 장치의 크기 순
1. HDD
2. RAM
3. Register

Instruction execution

Fetch and Execution cycle

  1. Fetch
  2. Decode
  3. Fetch Operands
  4. Execute
  5. Store Output
  • ALU: 산술 및 논리 연산 담당하는 연산 장치
  • PC: 현재/다음에 실행할 명령어의 주소를 저장하는 프로그램 카운터
  • IR: 현재 실행 중인 명령어를 잠시 저장하는 레지스터

실행 파일(binary file, 기계어)은 HDD와 같은 저장장치에 있다가 실행 명령을 받으면 운영체제에 의해 RAM에 적재된다.

Arithmetic and Logic Unit: ALU

  • CPU의 핵심 부분 중 하나, 산술과 논리 연산을 수행하는 연산 회로 집합으로 구성
  • 연산 장치의 구성 요소
  1. Adder: 덧셈 연산
  2. Complementer: 뺄셈 연산, 1의 보수나 2의 보수 이용
  3. Shifter: 비트를 오른쪽(x2)이나 왼쪽(x 1/2)으로 이동하여 나눗셈 곱셈 수행

Control unit

  • 입력, 출력, 기억, 연산 장치를 제어하고 감시, 주기억 장치에 저장된 명령을 차례로 해독하여 연산 장치로 보내 처리되도록 지시
  • 제어 장치의 구성 요소
  1. Instruction decoder
    • 명령 레지스터에 있는 명령을 해독하여 부호기로 전송
  2. Decoder
    • 명령 해독기가 전송한 명령을 신호로 만들어 각 장치 전송
  3. Address decoder
    • 명령 레지스터에 있는 주소를 해독하여 메모리의 실제 주소로 변환한 후, 이를 데이터 레지스터에 저장

Register

  • 처리 중인 데이터나 처리 결과를 임시 보관하는 CPU 내 기억 장치
  • 레지스터 크기별 분류
    • 64 bit: RAX, RBX, RCX, RDX (현재)
    • 32 bit: EAX, EBX, ECX, EDX
      • E: Extended
    • 16 bit: AX, BX, CX, DX
    • 8 bit
      • AH: 왼쪽 8비트 상위
      • AL: 오른쪽 8비트 하위

Flag Register

  • 32 bit, 컴퓨터의 다양한 상태를 나타내는 비트 포함
  • 상태 플래그(S), 제어 플래그(C), 시스템 플래그(X)로 구성

VScode에서 ZF를 ZR로 표시

Memory's architecture

16진수 1자리는 4-bit이고, 0xFFFFFFFF는 8자리이므로 8×4=328 \times 4 = 32 bit, 즉 4 byte이다.

Stack

  • 함수의 리턴 주소, 호출된 함수 내의 로컬변수 등이 저장
  • 상위 주소에서 하위 주소로 방향으로 증가
  • EBP: 현재 호출되어 사용되는 함수의 시작 주소
  • ESP: 스택의 가장 상단, 즉 현재 스택 영역에서 가장 하위 주소

Heap

  • 프로그램의 실행 중 필요한 기억 장소를 할당하기 위해 운영체제에 예약되어 있는 기억 장소
  • new나 malloc() 함수를 이용해 동적 메모리 할당 시 heap 영역 사용
    • e.g. char * p = new char[1000];
    • malloc()은 할당된 주소의 시작 주소 return
    • 포인터 변수는 데이터가 위치한 시작 주소를 가리킴, 32 bit - 4 byte, 64 bit - 8 byte

Data segment

  • 초기화된 데이터 세그먼트
  • 초기화된 외부 변수나 static 변수 등이 저장되는 영역
  • 보통 text segment와 data segment 영역을 합쳐 프로그램이라 함
    • e.g. static int a = 1;

전역 변수는 프로그램 전체에서 접근이 가능하여 의도치 않은 위험이 존재한다. 해당 함수 내에서만 접근하도록 static 변수를 이용한다.

  • 함수 내 선언: 지역 static, 함수 내에서만 접근 가능, 함수가 끝나도 값 유지
  • 함수 밖, 파일 선언: 접근 범위가 해당 파일로 제한

Block Started by Symbol segment

  • 초기화되지 않은 데이터 세그먼트
  • 프로그램이 실행될 때 0이나 NULL 포인터로 초기화
  • 외부 변수나 static 변수 중 초기화되지 않은 변수들이 정의될 때 저장
  • e.g. static int a;

Text segment

  • CPU에 의해 실행되는 머신 코드가 있는 영역
  • = Code segment
  • EIP: 다음에 실행하는 명령의 주소값 저장, 예전에는 PC라고 부르기도 함

2's complement

음수 표현 방법: 부호화 절대값, 1의 보수, 2의 보수

  • 부호화 절대값: 양수이면 MSB가 0, 음수이면 1
    • MSB: most significant bit
  • 1's complement
    • 0은 1로, 1은 0으로 변환하여 음수 표현
  • 2's complement
    • 1의 보수에 1을 더하여 음수 표현

Compile

CPU는 기계어로 돌아간다.

Assembly language

Assembly language

  • 기계어의 비트 형식을 니모닉 코드(mnemonic code)로 나타낸 것

Mnemonic code

  • 기계어의 비트 형식이 나타내는 의미를 symbol로 표현한 것으로, 프로그램을 이해하거나 작성하기 쉽다.
  • e.g. MOV A, #03H

Reasons for programming in assembly

  • 컴퓨터 하드웨어의 구성 요소들을 직접 액세스하려고 할 때
  • 컴파일러를 설계하거나 시스템 프로그램을 작성하려고 할 때
  • 빠른 수행이 필요한 프로그램을 작성하려고 할 때
  • 기억 장소를 적게 차지하거나 입출력 장치를 보다 효율적으로 사용하려는 경우

자바, 파이썬 등의 고급 언어에서는 하드웨어의 구성 요소(레지스터, 메모리)에 직접적으로 접근하기가 어렵거나 불가능한 경우가 많다.

Assembler

  • 어셈블리어를 기계어로 변환하는 프로그램
    • Source program: 어셈블리어 프로그램
    • Object program: 변환된 기계어 프로그램

Assembly langauge's data type

  • Byte: 1 byte(8 bit)
    • char 자료형은 보통 1 byte 크기로 저장된다.
  • Word: 2 byte(16 bit)
  • Doubleword: 4 byte(32 bit)

Byte Ordering Methods

Big Endian

  • 상위 바이트의 값(MSL)을 작은 번지수에 저장
  • RISC
  • IBN, Unix, (ARM)

정수 0x12345678

사람이 10진수를 읽고 쓰는 방식과 비슷해서, 숫자의 큰 자리부터 앞에 배치한다.

Little Endian

  • 하위 바이트의 값(LSB)을 작은 번지수에 저장
  • CISC
  • Intel, AMD, DEC, (ARM)

Assmbly langauge architecture

간접 메모리 주소 지정 방식에서 상수값을 저장하고자 할 경우, default 크기는 byte ptr이다.

  • 즉 mov [reg], 0x1234 =

1. Register Addressing

  • 레지스터의 주소 값을 직접 지정 복사, 처리 속도 가장 빠름
MOV EAX, EBX

Operator에서 l, b, w등이 함께 쓰이지 않았고, 값 앞에 $와 레지스터 앞에 %가 존재하지 않기 때문에, Intel 문법임을 알 수 있고, 레지스터가 E로 시작하기 때문에 32-bit 시스템이다.

  • Operator dest src: EBX가 가진 값을 EAX에 copy한다.

2. Direct Addressing

  • 가장 일반적인 주소 지정 방식
  • 세그먼트:[오프셋] 형식의 메모리에 직접 접근하는 방식
MOV EAX, DS:[0012ff78h]
  • DS에는 메모리 주소값이 저장되어 있고, 해당 주소에는 값으로 0이 저장되어 있다.
  • 80386 이후에는 더 이상 DS를 사용하지 않으나, [오프셋] 주소 지정방식에서 DS: 표기는 하여야 한다.

3. Register Indirect Addressing

  • [오프셋레지스터] 형식 사용
MOV EAX, [EBX] (=DS:EBX)
MOV EAX, [ESP] (=SS:ESP)
  • 기본 아닌 세그먼트를 강제로 지정(override)할 수 있다.
  • x86 메모리에서 주소를 지정할 때는 위 표처럼
    • EBX, ESI, EDI \rightarrow DS
    • ESP, EBP (스택 관련) \rightarrow SS 가 기본인데, 이를 강제로 지정할 수 있다.
MOV EAX, CS:[EBX]
MOV EAX, DS:[ESP] 

4. Index Addressing

  • 레지스터 간접 지정 방식에 변위가 더해진 메모리 주소 지정 방식
  • e.g. 20h만큼 더해 메모리를 참조한 명령
    • 20은 0x20으로 16진수 표기 \rightarrow 더 상위 주소값을 가리킨다.
MOV EAX, [EBX+20h] 
  • 다음과 같이 바꿔서 표현할 수 있다.
MOV EAX, 20h[EBX]

실제 물리 주소 = segment(DS) + offset
= 0 + 0x0012ff78 = 0x0012ff78
따라서 메모리 주소 0x0012ff78 위치의 값을 EAX로 copy한다는 뜻이다.

5. Base-Index Addressing

  • 베이스 레지스터(EBX, EBP)와 인덱스 레지스터(EDI, ESI)를 결합
MOV EAX, [EBX+ESI]
  • 다음과 같이 바꿔서 표현할 수 있다.
MOV EAX, [EBX][ESI]

메모리에서 배열이나 테이블과 같은 연속적인 데이터 구조를 효율적으로 접근하기 위해 만들어진 x86 어셈블리의 맞춤형 주소 지정 기법이다.

6. Base-Index with Displacement Addressing

  • Base-Index의 변형으로 베이스 레지스터, 인덱스 레지스터, 변위를 결합
MOV EAX, [EBX+ESI+20h]

Instruction format

Operator vs. Operand

  • Assembly 기준
  • 연산자 1개, 피연산자 2개

Instruction format

  • 각 연산자의 byte는 같지 않다.
  • 최대로 차지할 수 있는 byte 수: 4 + 3 + 1 + 1 + 4 + 4

E.g. ADD EAX, EBX \rightarrow 03 / r (or 01/r)

  • 03은 opcode로 16진수로 1 byte이다.
  • 각 어셈블리 명령어는 대응되는 기계어 opcode로 변환되어 실행된다.
  • r/m: register 또는 memory
    • 피연산자가 레지스터일 수도 있고 메모리일 수도 있다.
  • 32: 32 bit, 즉 4 byte
  • imm: Immediate값, 명령어 안에 상수로 직접 들어가는 값
  • ib: immediate byte, 1 byte
  • iw: immediate word, 2 byte
  • id: immediate double word, 4 byte
  • Op/En: Operand Encoding으로 I, M(첫 번째는 메모리)I(두 번째는 즉값), MR, RM type
    • MM type은 존재하지 않는다. 명령 하나에 두 개의 메모리 주소를 동시에 접근하는 명령은 불가하다.

Instruction format: Addressing mode

E.g. 03 C3

  • Opcode: 03
  • Mod R/M: 8 bit를 다시 3개의 필드로 쪼갠다.
    • 2 bit: Mod (주소/레지스터) 11
    • 3 bit: Reg/Opcode 000
    • 3 bit: R/M 011

표에서 000이면 32-bit 기준 EAX, R/M이 011이면 C3, 따라서 EBX임을 찾을 수 있다.

Syntax: arithmetic

ADD

  • 제1피연산자와 제2피연산자 값을 더한 결과 값을 제1피연산자에 저장

SUB

  • 제1피연산자에서 제2피연선자 값을 뺀 결과 값을 제1피연산자에 저장

CMP

  • 두 값을 데이터의 변경 없이 비교 시 사용
    • SUB 연산, 결과 저장 안 함, ZR 반영

CMP는 내부적으로 뺄셈 연산을 사용해 두 값을 비교한다. 계산 결과가 0이면 두 값이 같다는 뜻으로 ZF가 1이 되어 '같다'라는 의미가 된다. 계산된 실제 값은 바로 버려지며, 여러 상태의 플래그에만 반영된다.(Sign, Carry, Zero ...)

위 예시에서 MOV를 통해 값을 copy 했기 때문에, ZR = 1이 된다.

Etc..

Decimal number expression

Pack 10진수

  • 10진수 한 자리를 4 bit로 표현
  • 가장 우측 4bit는 부호 비트(양수: CF, 음수: D)
  • 실제 산술 연산 가능

Unpack 10진수

  • 10진수 한 자리를 8 bit로 표현
  • 마지막 bit의 왼쪽 4 bit는 부호 비트(양수: CF, 음수: D)
  • 입출력 처리는 가능하나, 연산 불가
  • 상위 4 bit는 F로 고정하는 것이 국제 표준

Syntax: data transfer

MOV

  • 데이터 이동할 때 사용
  • 제2 피연산자가 제1 피연산자로 복사된다.
  • [ESP]로 0x1234 복사, [ESP] 위치에 있던 값이 EAX로 복사
  1. 0x1234라는 값을 ESP가 가리키고 있는 주소 안에 copy
  2. ESP가 가리키고 있는 주소 안의 값은 위에서 copy한 0x1234, EAX에 0x1234를 copy
  3. EAX에 0x1234가 저장되고, 스택에 저장된 0x1234라는 값의 저장 위치를 EAX가 가리키는 것 아님
    • 값 자체를 복사해서 별도 공간에 복사한 것이다.

PUSH

  • 스택에 데이터를 삽입할 때 사용 스택은 커지고, 스택 포인터(ESP)는 데이터 크기만큼 감소

E.g. PUSH EBP: EBP 레지스터에 있는 값을 스택에 저장

스택에서 PUSH를 실행하면, 아래쪽(메모리 주소가 낮은)로 자라나기 때문에 ESP 값이 감소한다. 32-bit 시스템에서 PUSH할 때마다 ESP가 4 byte씩 작아져 새 데이터 영역을 확보한다.

POP

  • 스택에 있는 데이터를 삭제하고, 그 값을 [제1 피연산자]에 저장, 스택 포인터는 삭제하는 데이터 크기만큼 증가

E.g. POP EBP: 스택에 있는 값을 EBP 레지스터에 저장

POP EBP 명령을 실행해도, 메모리에 저장된 값 자체가 바로 지워지는 것이 아니다. 단지 ESP가 더 높은 주소로 이동하여 이전 값을 더 이상 'top'으로 간주하지 않기 때문에 유효하지 않은 데이터 영역이 된다.

POP은 PUSH와 반대로 데이터 크기인 4 byte만큼 증가한다.

LEA

  • Load Effective Address
  • 데이터의 값이 이동할 때 사용
  • MOV 명령과 유사하나, 제2피연산자의 주소 값 자체를 제1피연산자에 저장한다.

MOV는 데이터 복사, LEA는 주소(포인터) 복사로 복사하는 대상이 다르다.

Etc..

Syntax: logical

AND

  • 둘 다 1일 때만 결과가 1, 그 외의는 모두 0
  • bit 단위

OR

  • 하나만 1이어도 결과가 1, 둘 다 0인 경우에만 0

XOR

  • 두 개의 비트가 서로 다를 때만 1, 같으면 0

NOT

  • Invert
  • 피연산자의 1의 보수를 구하는 연산자로, 각 비트를 반전시킨다.

TEST

  • 특정 비트위치 값 확인 시, 데이터의 변경 없이 단순 비교
    • 제1 피연산자에 저장되지 않는다.

if, while, for문 등에서 단순히 조건만 비교할 때 컴파일러는 값 자체를 바꾸지 않고 TEST 명령어로 비트 조건 비교를 수행하도록 변환하는 경우가 많다.

TEST는 피연산자 2개를 AND 연산한다. AND 연산의 결과가 0이면 ZF를 1로, 아니면 0으로 set, 나머지 데이터에는 변화가 없다.

Etc..

Shift는 비교적 자주 쓰인다.

Syntax: String

REP

  • Repeat
  • ADD나 MOVS와 같은 연산자의 앞에 위치, ECX가 0이 될 때까지 뒤에 오는 스트링 명령 반복

MOVS

  • Move String
  • ESI가 지시한 문자열을 EDI가 문자열로 이동
  • 타입에 따라서 MOVSB, MOVSW, MOVSD

MOVS에서는 정의에서 ESI(소스), EDI(목적지)라는 레지스터 조합을 고정적으로 사용하게 설계되어 있다. 따라서 피연산자가 존재하지 않는 명령어이다.

  • LEA ES1, str: ESI에 소스 문자열 시작 주소 설정
  • REP MOVSB: 한 번에 1 byte씩 접근하여 ESI의 byte를 EDI로 복사한다. ESI와 EDI는 각각 1씩 증가하고, ECX 값은 1씩 감소한다. 이때, ECX가 0이 되면 자동 종료한다.

char str1[30] = char * str1

  • 배열의 이름은 메모리에서의 시작 주소 값과 같다.

Etc..

Syntax: Control transfer

JMP

  • Unconditional Jump
  • 점프할 주소 또는 라벨로 이동
  • JMP는 항상 메모리 주소값을 넣어줘야 한다.

Conditional JMP

CALL

  • 제 1피연산자에 라벨을 지정, 리턴 주소로 EIP 백업
  • PUSH EIP + JMP와 같은 의미
    • CALL은 현재 실행 중이던 주소(EIP)를 stack에 push
    • 그 뒤, 지정한 함수로 Jump

RET

  • Return from CALL
  • 호출한 곳으로 돌아갈 때 사용하는 명령어
  • POP EIP와 같은 의미
    • POP은 EIP에 직접적으로 제어 불가

Stack top에 저장된 값(이전 EIP)을 pop해서 EIP를 복구한다.
Register는 순간적으로 하나의 값만 가지기 때문에, stack에 해당 위치를 저장해야 하는 것이다.

LOOP

  • Loop ECX times
  • 문장들의 블록을 지정된 횟수만큼 반복
  • ECX는 자동적으로 카운터로 사용되며 루프 반복할 때마다 감소

단순히 1씩 증가시키는 것은 결과가 같기 때문에 INC AX가 INC EAX처럼 동작한다.

INT

  • Interrupt
  • 인터럽트가 호출되면 EIP와 플래그를 스택에 저장, 해당 인터럽트에 관련된 서브 루틴 실행

프로그램의 흐름이 완전히 커널로 들어가는 system call이다.

STC

  • Set Carry
  • EFLAGS 레지스터의 CF 값 세팅

NOP

  • No Operation
  • 아무 의미 없는 명령, 일종의 빈칸을 채우려고 사용한다.
  • 0x90, 1 byte

컴파일 코드의 특정 위치를 맞추기(alignment) 위해서 사용한다. 특정 attack에서 유용하게 사용되며, 기준이 전부 4 byte이다.

Etc..

0개의 댓글