728x90
반응형

 

공격자는 비지박스(busybox)라는 리눅스 명령어 패키지를 이용해서 wget 명령으로 악성코드를 다운로드 해 실행

 

x86-64 아키텍처

인텔의 32비트 CPU 아키텍처, 64: CPU가 한번에 처리할 수 있는 데이터의 크기
- 인텔의 x86 아키텍처와 호환되는 64bit 아키텍처 IA-64 발표

Intel 회사에서 개발한 컴퓨터의 중앙처리장치(CPU)
ISA(Instruction Set Architecture)

 

범용 레지스터(General Register)

이름 주용도
rax (accumulator register) 함수의 반환 값
rbx (base register) x64에서는 주된 용도 없음
rcx (counter register) 반복문의 반복 횟수, 각종 연산의 시행 횟수
rdx (data register) x64에서는 주된 용도 없음
rsi (source index) 데이터를 옮길 때 원본을 가리키는 포인터
rdi (destination index) 데이터를 옮길 때 목적지를 가리키는 포인터
rsp (stack pointer) 사용중인 스택의 위치를 가리키는 포인터
rbp (stack base pointer) 스택의 바닥을 가리키는 포인터

세그먼트 레지스터(Segment Register)

x64 아키텍처에는 cs, ss, ds, es, fs, gs 총 6가지 세그먼트 레지스터 존재

arm, mips, mpsl, x86

 

Q1. rax = 0x0123456789abcedf일 때, eax의 값은?

0x89abcdef

 

Q2. rax = 0x0123456789abcdef일 때, al의 값은?

0xef

 

Q3. rax에서 rbx를 뺐을 때, ZF가 설정되었다. rax와 rbx의 대소를 비교하시오.

==

 

Q4. rax = 0x0123456789abcdef일 때, ax의 값은?

0xcdef

 

Q5. rax = 0x0123456789abcdef일 때, ah의 값은?

0xcd

 

프로세스 메모리 구조

섹션

윈도우의 PE 파일은 PE 헤더와 1개 이상의 섹션으로 구성됨

".text" 섹션: PE코드, 실행 가능한 기계 코드가 위치하는 영역

".data" 섹션: PE가 실행중에 참조하는 데이터, 컴파일 시점에 값이 정해진 전역 변수들이 위치, 읽기/쓰기 권한 부여

".rdata" 섹션: 컴파일 시점에 값이 정해진 전역 상수와 참조할 DLL 및 외부 함수들의 정보 저장, 읽기 권한 부여(쓰기 불가능), str_ptr: "readonly" 문자열

섹션이 아닌 메모리

윈도우의 가상 메모리 공간에는 섹션 + 스택/힙 등이 적재됨

스택: 지역 변수나 함수의 리턴 주소가 저장됨, 읽기/쓰기 권한 부여 (기존 주소보다 낮은 주소로 확장됨)

어셈블리어와 x86-64

어셈블리 언어

컴퓨터의 기계어와 치환되는 언어, 명령어 집합구조(Instruction Set Architecture, ISA)

x64 어셈블리 언어

명령어(Operation Code, Opcode)와 목적어에 해당하는 피연산자(Operand)

x86-64 어셈블리어 문법 구조

mov eax, 3

(opcode: 대입해라, operand1: eax에, operand2: 3을)

명령어

인텔의 x64에는 매우 많은 명령어가 존재함, 본 커리큘럼에서는 이 코스와 다음 코스에 걸쳐, 이 중 중요한 21개의 명령어를 자세히 학습할 것.

명령 코드  
데이터 이동(Data Transfer) mov, lea
산술 연산(Arithmetic) inc, dec, add, sub
논리 연산(Logical) and, or, xor, not
비교(Comparison) cmp, test
분기(Branch) jmp, je, jg
스택(Stack) push, pop
프로시져(Procedure) call, ret, leave
시스템 콜(System call) syscall

피연산자

상수(Immediate Value), 레지스터(Register), 메모리(Memory)

크기 지정자(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바이트 만큼 참조

자료형 WORD의 크기가 2바이트인 이유

초기에 인텔은 WORD의 크기가 16비트인 IA-16 아키텍처를 개발했음
CPU의 WORD가 16비트였기 때문에, 어셈블리어에서도 WORD를 16비트 자료형으로 정의하는 것이 자연스러웠음

이후에 개발된 IA-32, x86-64 아키텍처는 CPU의 WORD가 32비트, 64비트로 확장됨
이 둘의 아키텍처에서는 WORD 자료형이 32비트, 64비트의 크기를 지정하는 것이 당연할 것

하지만 인텔은 WORD 자료형의 크기를 16비트로 유지함 (새로운 아키텍처 호환 여부)

DWORD(Double Word, 32bit), QWORD(Quad Word, 64bit) 자료형을 추가로 만듦

논리 연산 - and & or

# add
[Register]
eax = 0xffff0000
ebx = 0xcafebabe

[Code]
and eax, ebx

[Result]
eax = 0xcafe0000
# or
[Register]
eax = 0xffff0000
ebx = 0xcafebabe

[Code]
or eax, ebx

[Result]
eax = 0xffffbabe

논리 연산 - xor & not

[Register]
eax = 0xffffffff
ebx = 0xcafebabe

[Code]
xor eax, ebx

[Result]
eax = 0x35014541

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

- f0 e1 d2 c3 b4 a5

[Register]
eax = 0xffffffff

[Code]
not eax

[Result]
eax = 0x00000000

 

정리

종류 연산자 의미
데이터 이동 연산자 mov dst, src src의 값을 dst에 대입
데이터 이동 연산자 lea dst, src src의 유효 주소를 dst에 대입
산술 연산 add dst, src src의 값을 dst에 더함
산술 연산 sub dst, src src의 값을 dst에서 뺌
산술 연산 inc op op의 값을 1 더함
산술 연산 dec op op의 값을 1 뺌
논리 연산 and dst, src dst와 src가 모두 1이면 1, 아니면 0
논리 연산 or dst, src dst와 src 중 한 쪽이라도 1이면 1, 아니면 0
논리 연산 xor dst, src dst와 src가 다르면 1, 같으면 0
논리 연산 not op op의 비트를 모두 반전
비교 cmp op1, op2 op1에서 op2를 빼고 플래그를 설정
비교 test op1, op2 op1과 op2에 AND 연산을 하고, 플래그를 설정
분기 jmp addr addf로 rip 이동
분기 je addr 직전 비교에서 두 피연산자의 값이 같을 경우 addr로 rip 이동
분기 jg addr 직전 비교에서 두 피연산자 중 전자의 값이 더 클 경우 addr로 rip 이동

 

728x90
728x90
728x90
반응형
목차
1. char 자료형
  - limits.h 헤더 파일

2. scanf 오류
3. int 자료형
4. 이진탐색(Binary Search) 시 발생하는 오버플로우 대안

char 자료형

C언어에서는 정수 자료형인 char를 이용하여 문자 한 개를 저장함

이 때, char 자료형의 표현 범위는 -128~127이며, char에 문자를 저장할 때는 문자 자체를 저장하는 것이 아니라 문자에 해당하는 정수 값을 저장(ASCII)

(00000000~1111111까지 256가지 중 127까지 양수, 128부터 음수로 표현)

따라서, char로 선언한 변수에 ASCII 코드값 128을 저장한다면, 이에 overflow가 발생하여 -128이 됨

#include <stdio.h>
#include <limits.h>

int main() {
    printf("char min: %d, char max: %d\n", CHAR_MIN, CHAR_MAX);
    // char min: -128, char max: 127
    return 0;
}


scanf 오류가 발생하는 이유

보안 문제, 프로젝트에서 SDL(Security Development Lifecycle) 자동 검사를 수행하기 때문

문제: 문자열이나 파일에 대한 버퍼나 스택 등 메모리에 문제가 생길 수 있음

해결 방법

1) 코드 최상단에 #define _CRT_SECURE_NO_WARNINGS 추가

  - scanf 보안 경고로 인한 컴파일 에러 방지

2) scanf → scanf_s로 변경(visual studio에서만 호환 가능)

  - 메모리의 사이즈를 요구하는 함수(_s), C언어 표준 함수는 아님

3) scanf_s("%s", sentence, sizeof(sentence))라 명시(마지막에 sentence 사이즈)

(e.g. scanf_s(“%d”, &store, 10); → [10]개의 사이즈를 받는다고 명시)

 

[참고]

sscanf()란? 입력 대상: 표준 입력(X), 매개변수로 전달되는 문자열 버퍼(O)

fscanf()란? 입력 대상: 파일(f) 스트림에서 포맷 스트링에 맞게 데이터를 읽어들임


int 자료형

int 자료형의 표현 범위는 -2147483648~2147483647이며,

2,147,483,628 = 2³¹

따라서 부호 있는 32bit 정수가 보유할 수 있는 최대값은 2³¹-1 (int 범위: -2³¹ ~  2³¹-1)

#include <stdio.h>
#include <limits.h>

int main() {
    printf("int min: %d, int max: %d\n", INT_MIN, INT_MAX);
    // int min: -2147483648, int max: 2147483647
    return 0;
}


이진탐색(Binary Search)  시 발생하는 오버플로우 대안

first = 0;
last = n - 1;
middle = (first + last)/2;

부호 있는 32bit 정수가 보유할 수 있는 최대값이 2³¹-1임

이 때, 검색할 요소가 2³¹-2 (마지막에서 두번째 요소)라고 가정해 보자

 

[0, 1, 2, 3, … , 2³¹/2, …, (2³¹-3), (2³¹-2), (2³¹-1)]

 

첫 번째 반복 시 middle = (0 + (2³¹-1))/2

target은 해당 배열의 우측에 위치하므로,

first = middle + 1 = (2³¹-1)/2 + 1 = 2³⁰ + 1/2

(*)두 번째 반복 시, middle = (first + last)/2 = {(2³⁰ + 1/2) + (2³¹ - 1)}/2 = {(2³⁰ + 2³¹ - 1/2}/2 = 2²⁹+2³⁰-1/4 = 2²⁹(1+2) – 1/4 = 2³⁰ + 2²⁹ – 1/4 (= 2²⁹ * 3 – 1/4)

이 때 first = 2²⁹ * 3 – 1/4 + 1 = 2²⁹ * 3 + 3/4

세 번째 반복 시, middle = (first + last)/2 = {(2²⁹ + 2³⁰ + 3/4) +(2³¹ - 1)}/2 = 2³⁰ + 2²⁹ + 2²⁸ - 1/8

 

해당 함수를 n 번 반복 시, n번 째 middle = (2³⁰ + 2²⁹ + 2²⁸ + … + 2^(30-n)) - 1/2^(n)

원하는 값(2³¹-2)을 구하기까지 middle은 최대 2³¹을 넘지 않으므로 문제되지 않음

(∵ (2³⁰ + 2²⁹ + 2²⁸ + … + 2¹) = 2³¹ - 1 < 2³¹)

 

하지만 두 번째 연산 시,(*) first와 last의 합이 INT_MAX인 2³¹ - 1을 넘어가게 되어 오버플로우 발생

➡️ middle이 음수가 됨

 

따라서, middle 값을 다음과 같이 변경하여 문제 해결 가능

문제: first와 last의 합이 2³¹을 초과하게 되어 overflow 발생

기존 연산: middle = (first + last)/2;

변경 연산1: middle = first + (last – first)/2;

변경 연산2: middle. = ((unsigned int)first + (unsigned int)last)) >> 1

 

* unsigned int는 sign(부호)가 없는 정수형으로,

Overflow된 값을 정상적인 값으로 판단하여 unsigned 연산에서 고의적으로 wrap around 이용

 

[참고자료]

The curious case of Binary Search - The famous bug that remained undetected for20 years

https://thebittheories.com/the-curious-case-of-binary-search-the-famous-bug-that-remained-undetected-for-20-years-973e89fc212

 

The curious case of Binary Search — The famous bug that remained undetected for 20 years

Nearly all Binary Search and Merge Sort implementations are broken!

thebittheories.com

 

728x90
728x90

+ Recent posts