시스템해킹_1주차

2022. 9. 11. 12:4122-2학기 (SISS)/시스템해킹

https://dreamhack.io/lecture/roadmaps/2

 

System Hacking

시스템 해킹을 공부하기 위한 로드맵입니다.

dreamhack.io

1주차 (9/5 ~ 9/11) : STAGE 1, 2


STAGE1 : System Hacking Introduction


1. 소개

[Welcome Hackers]

C언어와 파이썬에 대한 기본적인 이해를 갖고 있어야 한다.

소프트웨어에서 발생할 수 있는 대부분의 취약점 및 이를 공격하는 방법을 총 40코드에 걸쳐 배울 것이다.


2. 리눅스 환경 구축

[Tool: Environment Setup]

1. 환경 구축

1) 실습 환경

Ubuntu 18.04(x86-64)를 기반으로 작성됐다. 이를 위해 가상 머신(Virtual Machie, VM)을 사용해야 한다.

윈도우에서는 VMware, VirtualBox 또는 WSL2(Windows Subsystem For Linux 2)를 이용하여 환경을 구축할 수 있다. 사용하고 계신 윈도우가 Windows 10(Version 2004)이후의 버전이라면 WSL2의 사용을 추천하고, 그 이하의 버전에서는 전자의 소프트웨어들을 사용해야 한다.

이 코스에서는 WSL2와 VMware를 통한 환경 구축방법을 설명할 것이다.

2. 윈도우 환경 구축

1) WSL2

-윈도우 버전 확인 : 실행창을 Windows + R 키를 눌러 연 다음에, winver를 입력하여 윈도우의 버전을 확인한다.

-WSL2 (winver >= 2004)

   (1) Powershell을 관리자 권한으로 실행하고, 다음 커맨드를 실행한다.

   (2) Microsoft store에서 Ubuntu18.04를 설치한다.

   (3) 설치를 마치고 처음 실행하게되면, 간단한 설정 과정을 거친다. 설정이 끝나면 다음 명령어를 입력하여 Hello

       Ubuntu!가 출력되는지 확인해야 한다. Hello Ubuntu!가 제대로 출력됐다면, WSL2를 이용한 실습 환경 구축은 끝난 것         이다. 

2) VMware Workstation Player

VMware Workstation Player는 윈도우에서 무료로 사용 가능한 가상화 소프트웨어이다.

 (1) 링크를 통해 VMware를 설치하고, Ubuntu 18.04 ISO를 다운로드 한다.

 (2) New Virtual Machine을 클릭하고, 위에서 다운받은 Ubuntu 18.04 이미지를 넣어서 Ubuntu를 설치한다.

 (3) 초록색 실행 버튼을 클릭하여 가상 머신을 부팅한다.

 (4) 부팅이 되고 나면, Install Ubuntu를 클릭하고 필요한 설정을 확인한다.

 (5) ctrl+alt+t로 터미널을 열고 echo “Hello Ubuntu!”를 입력했는데 Hello Ubuntu!가 출력됐다면, VMware를 통한 환경

     구축이 완료된 것이다.

 

3. 맥 환경 구축

1) VirtualBox

virtual box는 맥에서 무료로 사용 가능한 가상화 소프트웨어이다.


STAGE2 : Background - Computer Science


1. Linux Memory Layout

[1. Background: Linux Memory Layout]

1. 서론

1) 코스 소개

만약 공격자가 메모리를 악의적으로 조작할 수 있다면 조작된 메모리 값에 의해 CPU도 잘못된 동작을 할 수 있다. 이를 메모리가 오염됐다고 표현하며, 이를 유발하는 취약점을 메모리 오염(Memory Corruption)취약점이라고 부른다. 시스템 해킹에 다양한 공격기법들이 있지만, 많은 공격기법이 메모리 오염을 기반으로 하고 있다.

 

2. 리눅스 프로세스의 메모리 구조

1) 세그먼트

리눅스에서는 프로세스의 메모리를 크게 5가지의 세그먼트(Segment)로 구분한다. 여기서 세그먼트란 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것인데, 크게 코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 그리고 스택 세그먼트로 구분된다.

권한은 읽기, 쓰기, 그리고 실행이 존재하며, CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있다.

예를 들어, 데이터 세그먼트에는 프로그램이 실행되면서 사용하는 데이터가 적재된다. CPU는 이곳의 데이터를 읽을 수 있어야 하며, 따라서 이 영역에는 읽기 권한이 부여된다.

2) 코드 세그먼트

코드 세그먼트(Code Segment)는 실행 가능한 기계 코드가 위치하는 영역으로 텍스트 세그먼트(Text Segment)라고도 불린다.

3) 데이터 세그먼트

데이터 세그먼트(Data Segment)에는 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치한다. CPU가 이 세그먼트의 데이터를 읽을 수 있어야 하므로, 읽기 권한이 부여된다.

데이터 세그먼트는 쓰기가 가능한 세그먼트와 쓰기가 불가능한 세그먼트로 다시 분류되는데, 쓰기가 가능한 세그먼트는 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치한다. 이런 세그먼트를 data 세그먼트라고 부른다.

반면 쓰기가 불가능한 세그먼트에는 프로그램이 실행되면서 값이 변하면 안되는 데이터들이 위치한다. 전역으로 선언된 상수가 여기에 포함된다. 이런 세그먼트를 rodata(read-only data) 세그먼트 라고 부른다.

4) BSS 세그먼트

BSS 세그먼트(BSS Segment, Block Started By Symbol Segment)는 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 메모리 영역이다. 여기에는 개발자가 선언만 하고 초기화하지 않은 전역변수 등이 포함된다. 이 세그먼트의 메모리 영역은 프로그램이 시작될 때, 모두 0으로 값이 초기화된다. 이 세그먼트에는 읽기 권한 및 쓰기 권한이 부여된다.

5) 스택 세그먼트

스택 세그먼트(Stack Segment)는 프로세스의 스택이 위치하는 영역이다. 함수의 인자나 지역 변수와 같은 임시 변수들이 실행중에 여기에 저장된다.

스택 세그먼트는 스택 프레임(Stack Frame)이라는 단위로 사용된다. 스택 프레임은 함수가 호출될 때 생성되고, 반환될 때 해제된다. 그런데 프로그램의 전체 실행 흐름은 사용자의 입력을 비롯한 여러 요인에 영향을 받는다.

이 영역에는 CPU가 자유롭게 값을 읽고 쓸 수 있어야 하므로, 읽기와 쓰기 권한이 부여된다.

6) 힙 세그먼트

힙 세그먼트(Heap Segment)는 힙 데이터가 위치하는 세그먼트이다. 스택과 마찬가지로 실행중에 동적으로 할당될 수 있으며, 리눅스에서는 스택 세그먼트와 반대 방향으로 자란다.

C언어에서 malloc(), calloc() 등을 호출해서 할당받는 메모리가 이 세그먼트에 위치하게 되며, 일반적으로 읽기와 쓰기권한이 부여된다.

 

3. 결론


[퀴즈 : 리눅스 메모리 레이아웃]


2. Computer Architecture

[Background : Computer Architecture]

1. 서론

컴퓨터는 각자 다른 기능을 수행하는 여러 부품들의 도움으로 작동한다. CPU는 컴퓨터의 작동에 핵심이 되는 연산을 처리하고, 저장장치는 데이터를 저장한다.이처럼 서로 다른 부품들이 모여서 ‘컴퓨터’라는 하나의 기계로서 작동할 수 있는 것은 컴퓨터에 대한 기본 설계가 존재하기 때문이다. 이러한 설계를 ‘컴퓨터 구조(Computer Architecture)’라고 부른다. 전체적인 컴퓨터 구조 중에서 특히 CPU가 사용하는 명령어와 관련된 설계를 명령어 집합구조(Instruction Set Architecture, ISA)라고 하는데, 가장 널리 사용되는 ISA 중 하나가 인텔의 x86-64 아키텍처이다. 

2. 컴퓨터 구조와 명령어 집합 구조

1) 컴퓨터 구조

컴퓨터 구조(Computer Architecture)란 컴퓨터가 효율적으로 작동할 수 있도록 하드웨어 및 소프트웨어의 기능을 고안하고, 이들을 구성하는 방법을 말한다.

컴퓨터 구조는 컴퓨터의 기능 구조에 대한 설계, 명령어 집합구조, 마이크로 아키텍처, 그리고 기타 하드웨어 및 컴퓨팅 방법에 대한 설계 등이 포함된다.

‘컴퓨터의 기능 구조에 대한 설계’란 컴퓨터가 연산을 효율적으로 하기 위해 어떤 기능들이 컴퓨터에 필요한지 고민하고, 설계하는 분야로, 대표적으로 폰 노이만 구조, 하버드 구조, 수정된 하버드 구조가 있다.

CPU의 명령어에 대한 설계는 명령어 집합구조(Instruction Set Architecture)라고 불리며, CPU가 처리해야하는 명령어를 설계하는 분야다. 대표적으로 ARM, MIPS, AVR, 인텔의 x86 및 x86-64 등이 있다.

2) 폰 노이만 구조

근대의 컴퓨터는 연산과 제어를 위해 중앙처리장치(Central Processing Unit, CPU)를, 저장을 위해 기억장치(memory)를,  장치간에 데이터나 제어 신호를 교환할 수 있도록 버스(bus)라는 전자 통로를 사용한다.

 

-중앙처리장치

CPU는 프로그램의 연산을 처리하고 시스템을 관리하는 컴퓨터의 두뇌다. 프로세스의 코드를 불러오고, 실행하고, 결과를 저장하는 일련의 모든 과정이 CPU에서 일어난다. CPU는 산술/논리 연산을 처리하는 산술논리장치(Arithmetic Logic Unit, ALU)와 CPU를 제어하는 제어장치(Control Unit), CPU에 필요한 데이터를 저장하는 레지스터(Register) 등으로 구성되어 있다.

 

-기억 장치

기억장치는 컴퓨터가 동작하는데 필요한 여러 데이터를 저장하기 위해 사용되며, 용도에 따라 주기억장치와 보조기억장치로 분류된다. 주기억장치는 프로그램 실행과정에서 필요한 데이터들을 임시로 저장하기 위해 사용되며, 대표적으로 램(Random-Access Memory, RAM)이 있다. 이와 반대로 보조기억장치는 운영 체제, 프로그램 등과 같은 데이터를 장기간 보관하고자 할 때 사용된다.

 

-버스

버스는 컴퓨터 부품과 부품 사이 또는 컴퓨터와 컴퓨터 사이에 신호를 전송하는 통로를 말한다. 대표적으로 데이터가 이동하는 데이터 버스(Data Bus), 주소를 지정하는 주소 버스(Address Bus), 읽기/쓰기를 제어하는 제어 버스(Control Bus)가 있다.

3) 명령어 집합 구조

명령어 집합 구조(Instruction Set Architecture, ISA)란 CPU가 해석하는 명령어의 집합을 의미한다. 프로그램은 기계어로 이루어져 있는데, 프로그램을 실행하면 이 명령어들을 CPU가 읽고, 처리한다.

ISA는 IA-32 , x86-64(x64), MIPS, AVR 등 종류가 다양한데, ISA가 다양하게 개발되고 사용되는 이유는 모든 컴퓨터가 동일한 수준의 연산 능력을 요구하지 않으며, 컴퓨팅 환경도 다양하기 때문이다.

많은 임베디드 장비들은 전력 소모와 발열이 적은 ARM이나 MIPS 또는 AVR의 프로세서를 사용한다.

인텔의 x86기반 CPU의 점유율이 압도적이기 때문에 이 커리큘럼은 x86-64아키텍처만을 대상으로 하고 있다.

 

3. x86-64 아키텍처

1) x86-64 아키텍처

x64 아키텍처는 인텔의 64비트 CPU 아키텍처다. 인텔의 32비트 CPU 아키텍처인 IA-32를 64비트 환경에서 사용할 수 있도록 확장한 것으로, 대다수의 개인용 컴퓨터들이 인텔의 x64 CPU를 사용하고 있다.

 

-n 비트 아키텍처

위의 ‘64비트 아키텍처', '32비트 아키텍처’에서 64와 32는 CPU가 한번에 처리할 수 있는 데이터의 크기를 의미한다. 컴퓨터과학에서는 이를 CPU가 이해할 수 있는 데이터의 단위라는 의미에서 WORD라고 부른다. 

 

-WORD가 크면 유리한 점

현대의 PC는 대부분 64비트 아키텍처의 CPU를 사용하는데, 그 이유 중 하나는 32비트 아키텍처의 CPU가 제공할 수 있는 가상메모리의 크기가 작기 때문이다. 가상메모리는 CPU가 프로세스에게 제공하는 가상의 메모리 공간인데, 32비트 아키텍처에서는 4,294,967,296바이트(=4기가 바이트)가 최대로 제공 가능한 가상메모리의 크기이다. 많은 메모리 자원을 소모하는 전문 소프트웨어나 고사양의 게임 등을 실행할 때는 부족할 수 있다.

반면 64비트 아키텍처에서는 이론상 16엑사 바이트(=16,777,216 테라바이트)의 가상메모리를 제공할 수 있다. 이는 웬만해서는 소프트웨어의 실행이 불가능한 상황은 거의 발생하지 않습니다.

 

2) x86-64 아키텍처: 범용/세그먼트/명령어 포인터 레지스터

레지스터는 CPU가 데이터를 빠르게 저장하고 사용할 때 이용하는 보관소이며, 산술 연산에 필요한 데이터를 저장하거나 주소를 저장하고 참조하는 등 다양한 용도로 사용된다. x64 아키텍처에는 범용 레지스터(General Register), 세그먼트 레지스터(Segment Register), 명령어 포인터 레지스터(Instruction Pointer Register, IP), 플래그 레지스터(Flag Register)가 존재한다.

 

-범용 레지스터

범용 레지스터는 주용도는 있으나, 그 외의 다양한 용도로도 사용될 수 있는 레지스터이다.

-세그먼트 레지스터

x64 아키텍처에는 cs, ss, ds, es, fs, gs 총 6가지 세그먼트 레지스터가 존재하고, 각 레지스터의 크기는 16비트이다.

세그먼트 레지스터는 x64로 아키텍처가 확장되면서 용도에 큰 변화가 생긴 레지스터다.

 

-명령어 포인터 레지스터

프로그램은 일련의 기계어 코드들로 이루어져 있고, 이 중에서 CPU가 어느 부분의 코드를 실행할지 가리키는게 명령어 포인터 레지스터의 역할이다. x64 아키텍처의 명령어 레지스터는 rip이며, 크기는 8바이트다.

 

3) x86-64 아키텍처: 플래그 레지스터

플래그 레지스터는 프로세서의 현재 상태를 저장하고 있는 레지스터다. x64 아키텍처에서는 RFLAGS라고 불리는 64비트 크기의 플래그 레지스터가 존재하며, 과거 16비트 플래그 레지스터가 확장된 것이다. 플래그 레지스터는 자신을 구성하는 여러 비트들로 CPU의 현재 상태를 표현한다.

4) x86-64 아키텍처: 레지스터 호환

IA-32에서 CPU의 레지스터들은 32비트 크기를 가지며, 이들의 명칭은 각각 eax, ebx, ecx, edx, esi, edi, esp, ebp이다. 호환성을 위해 이 레지스터들은 x86-64에서도 그대로 사용이 가능하다.

 

4. 결론


[퀴즈 : 컴퓨터 아키텍쳐]


3. x86 Assembly 

[x86 Assembly : Essential Part(1)] 

1. 서론

1) 해커의 언어 : 어셈블리

어셈블러는 일종의 통역사로, 개발자들이 어셈블리어로 코드를 작성하면 컴퓨터가 이해할 수 있는 기계어로 코드를 치환한다. 어셈블리어가 기계어보다는 인간이 이해하기 훨씬 쉬웠으므로, 개발자들은 더욱 편리하게 개발을 진행할 수 있게 되었다.

그런데 소프트웨어를 역분석하는 사람들은 여기에 역발상을 더해, 기계어를 어셈블리 언어로 번역하는 역어셈블러(Disassembler)를 개발하였다. 기계어로 구성된 소프트웨어를 역어셈블러에 넣으면, 어셈블리 코드로 번역된다.

 

2. 어셈블리어와 x86-64

1) 어셈블리언어

어셈블리 언어는 컴퓨터의 기계어와 치환되는 언어다. 이는 기계어가 여러 종류라면 어셈블리어도 여러 종류여야 함을 의미한다. 

CPU에 사용되는 IS가는 종류가 매우 다양하기 때문에, 많은 수의 어셈블리어가 존재한다. 

 

2) x86-64 어셈블리

-기본 구조

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

-명령어

인텔의 x64에는 매우 많은 명령어가 존재한다.

3) 피연산자

피연산자에는 상수(Immediate Value), 레지스터(Register), 메모리(Memory)의 3가지가 있다.

메모리 피연산자는 []으로 둘러싸인 것으로 표현되며, 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있다. 

 

3. x86-64 어셈블리 명령어

1) Opcode : 데이터 이동

: 데이터 이동 명령어는 어떤 값을 레지스터나 메모리에 옮기도록 지시한다.

 

2) Opcode : 산술 연산

: 산술 연산 명령어는 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시한다.

 

3) Opcode : 논리 연산 - and, or

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

 

4) Opcode : 논리 연산 - xor, not

xor dst, src는 dst와 src의 비트가 서로 다르면 1이고, 같으면 0이다.

nop op는 op의 비트가 전부 반전된다.

 

5) Opcode : 비교/분기

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

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

 

4. 마치며

1) 코스 요약 및 리뷰 퀴즈


[퀴즈 : x86 Assembly 1]

Welcome to assembly world!


[x86 Assembly : Essential Part(2)] 

1. 서론

운영체제의 핵심 자료구조인 스택, C언어의 함수에 대응되는 프로시저, 그리고 시스템 해킹의 관점에서 매우 중요한 시스템콜과 관련된 어셈블리를 소개할 것이다.

(스택 : push, pop, 프로시저 : call, leave, ret,  시스템콜 : syscall)

 

2. x86-64 어셈블리 명령어 Pt.2

1) Opcode : 스택

x64 아키텍처에서는 'push', 'pop'의 명령어로 스택을 조작할 수 있다.

-push val : val을 스택 최상단에 쌓음

-pop reg : 스택 최상단의 값을 꺼내서 reg에 대입

 

2) Opcode : 프로시저

프로시저(Procedure)는 특정 기능을 수행하는 코드 조각을 말한다. 프로시저를 사용하면 반복되는 연산을 프로시저 호출로 대체할 수 있어서 전체 코드의 크기를 줄일 수 있고, 기능별로 코드 조각에 이름을 붙일 수 있게 되어 코드의 가독성을  높일 수 있다.

프로시저를 부르는 행위를 호출(Call)이라고 부르며, 프로시저에서 돌아오는 것을 반환(Return)이라고 부른다.

x64어셈블리언어에는 프로시저의 호출과 반환을 위한 call, leave, ret 명령어가 있다.

-call addr : addr에 위치한 프로시져를 호출한다.

-leave : 스택프레임을 정리한다.

-ret : return address로 반환한다.

 

3) Opcode : 시스템 콜

윈도우, 리눅스, 맥 등의 현대 운영체제는 컴퓨터 자원의 효율적인 사용을 위해, 그리고 사용자에게 편리한 경험을 제공하기 위해, 내부적으로 매우 복잡한 동작을 한다. 운영체제는 연결된 모든 하드웨어 및 소프트웨어에 접근할 수 있으며, 이들을 제어할 수도 있다. 해킹으로부터 이 막강한 권한을 보호하기 위해 커널 모드와 유저 모드로 권한을 나눈다.

 

커널 모드는 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다. 파일시스템, 입력/출력, 네트워크 통신, 메모리 관리 등 모든 저수준의 작업은 사용자 모르게 커널 모드에서 진행된다. 커널 모드에서는 시스템의 모든 부분을 제어할 수 있기 때문에, 해커가 커널 모드까지 진입하게 되면 시스템은 거의 무방비 상태가 된다.

 

유저 모드는 운영체제가 사용자에게 부여하는 권한이다. 브라우저를 이용하여 드림핵을 보거나, 유튜브를 시청하는 것, 게임을 하고 프로그래밍을 하는 것 등은 모두 유저 모드에서 이루어진다. 유저 모드에서는 해킹이 발생해도, 해커가 유저 모드의 권한까지 밖에 획득하지 못하기 때문에 해커로부터 커널의 막강한 권한을 보호할 수 있다.

 

시스템 콜(system call, syscall)은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용된다. 소프트웨어 대부분은 커널의 도움을 필요로 한다.

 

4) Opcode : 시스템 콜

시스템 콜은 함수이다. 필요한 기능과 인자에 대한 정보를 레지스터로 전달하면, 커널이 이를 읽어서 요청을 처리한다.

 

-x64 syscall 테이블

 

3. Epilogue


[퀴즈 : x86 Assembly 2]

'22-2학기 (SISS) > 시스템해킹' 카테고리의 다른 글

시스템 해킹_4주차  (1) 2022.09.30
시스템해킹_3주차  (1) 2022.09.23
시스템해킹_2주차  (0) 2022.09.22