Dreamhack/Dreamhack Wargame (Challenge)

[108] IT 비전공자 [dreamhack]cpp_string문제 풀기

imaginefuture-1 2024. 12. 26. 09:02

ㄷㄷㄷ 보안미쳐따

 

 

이 정보는 Ubuntu 16.04 LTS 환경에서 실행 중인 바이너리의 보안 설정과 특성을 나타냅니다. 각 항목을 분석하여 해당 바이너리의 보안 강점을 설명하겠습니다.


분석

1. RELRO (Relocation Read-Only)

  • 상태: Full RELRO
    • 설명: Global Offset Table(GOT)의 엔트리들을 읽기 전용으로 설정합니다.
    • 보안 이점:
      • GOT overwrite 공격(동적 함수 호출 시 악의적인 주소로 리다이렉트)이 불가능합니다.
    • 공격 가능성:
      • Full RELRO에서는 GOT 공격을 수행할 수 없으므로, 이 부분은 안전합니다.

2. Stack Canary

  • 상태: Canary found
    • 설명: 함수 호출 시 반환 주소 바로 앞에 **스택 카나리(Stack Canary)**를 삽입하여, 스택 오버플로우로 인해 반환 주소가 덮어씌워지는 것을 방지합니다.
    • 보안 이점:
      • 스택 오버플로우를 악용해 함수 반환 주소를 덮어쓰는 공격을 방어할 수 있습니다.
      • 카나리 값이 변조되면 프로그램이 강제 종료됩니다.
    • 공격 가능성:
      • Canary를 우회하려면 카나리 값을 유출하거나, Canary가 없는 영역을 활용한 공격이 필요합니다.

3. NX (No eXecute)

  • 상태: NX enabled
    • 설명: 데이터 영역(스택, 힙 등)에 저장된 코드의 실행을 금지합니다.
    • 보안 이점:
      • 쉘코드(Shellcode)를 스택이나 힙에 삽입하고 실행하는 전통적인 버퍼 오버플로우 공격을 방어합니다.
    • 공격 가능성:
      • NX가 활성화된 경우, **ROP(Return-Oriented Programming)**과 같은 기술을 사용하여 기존의 실행 가능한 코드 조각을 조합해 악의적인 동작을 수행할 수 있습니다.

4. PIE (Position Independent Executable)

  • 상태: PIE enabled
    • 설명: 프로그램의 실행 시점마다 코드와 데이터의 메모리 주소를 랜덤화하여 고정된 주소를 악용하는 공격을 방어합니다.
    • 보안 이점:
      • 실행 파일의 코드, 데이터 섹션이 항상 다른 주소에 매핑되므로 정적 주소 기반의 공격이 어려워집니다.
    • 공격 가능성:
      • PIE를 우회하려면 **정보 유출(Information Leakage)**을 통해 실제 메모리 주소를 알아내야 합니다.
      • 주소를 유출하는 취약점(예: Format String, 메모리 덤프 등)을 찾는 것이 필요합니다.

종합 평가

이 바이너리는 여러 강력한 보안 메커니즘을 갖추고 있어, 익스플로잇을 시도하기 어려운 상태입니다.

보안 메커니즘 현재 상태 취약점/우회 가능성

RELRO Full RELRO GOT 공격 불가능
Stack Canary Canary found Canary 값을 유출하거나 우회해야 함
NX NX enabled 쉘코드 실행 불가능, ROP 필요
PIE PIE enabled 주소 랜덤화, 정보 유출 필요

공격 가능성 및 우회 방법

1. Canary 우회

  • Canary 값을 메모리에서 유출하거나, Canary 보호를 우회해야 합니다.
  • 가능성:
    • Canary 유출은 Printf Format String 취약점 또는 메모리 누출을 통해 가능.

2. ROP 공격

  • NX 보호로 인해 쉘코드 삽입 공격은 불가능하므로 ROP(Return-Oriented Programming) 기법을 활용해야 합니다.
  • ROP를 위해 다음이 필요:
    • 바이너리나 라이브러리 내에서 유효한 가젯(Gadget)을 찾기.
    • 실행 흐름을 가젯으로 리다이렉트.

3. PIE 우회

  • PIE 활성화로 정적 주소 기반 공격이 불가능하므로, 정보 누출 취약점이 필요합니다.
  • 취약점 예:
    • Printf Format String으로 메모리 주소를 유출.
    • Stack/Heap Overflow로 메모리 덤프 유도.

공격 성공 시나리오

  1. Canary 유출:
    • Printf Format String 취약점으로 Canary 값을 유출.
    • Canary를 우회하여 스택 오버플로우 공격 성공.
  2. PIE 주소 유출:
    • 메모리 누출 취약점으로 libc 또는 실행 파일의 실제 메모리 주소를 유출.
    • ROP 기법으로 쉘 실행.
  3. ROP 체인 구성:
    • NX 활성화를 우회하기 위해 가젯을 조합한 ROP 체인을 구성.
    • libc 함수 호출(예: system("/bin/sh"))로 쉘 획득.

결론

이 바이너리는 강력한 보안 메커니즘으로 보호되고 있지만, 특정 취약점(정보 누출, Canary 유출 등)을 통해 우회 가능성이 있습니다. 공격 시:

  1. Canary 유출: Canary 값을 유출해야 스택 오버플로우를 실행할 수 있습니다.
  2. PIE 우회: PIE를 우회하려면 메모리 주소를 유출해야 합니다.
  3. ROP 활용: NX 보호로 인해 ROP 체인을 구성해야 합니다.

공격을 성공적으로 수행하려면 정보 유출 취약점을 우선적으로 찾아야 합니다. 😊

 


 

 

 

//g++ -o cpp_string cpp_string.cpp
#include <iostream>
#include <fstream>
#include <csignal>
#include <unistd.h>
#include <stdlib.h>

char readbuffer[64] = {0, };
char flag[64] = {0, };
std::string writebuffer;

void alarm_handler(int trash)
{
    std::cout << "TIME OUT" << std::endl;
    exit(-1);
}

void initialize()
{
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int read_file(){
	std::ifstream is ("test", std::ifstream::binary);
	if(is.is_open()){
        	is.read(readbuffer, sizeof(readbuffer));
		is.close();

		std::cout << "Read complete!" << std::endl;
        	return 0;
	}
	else{
        	std::cout << "No testfile...exiting.." << std::endl;
        	exit(0);
	}
}

int write_file(){
	std::ofstream of ("test", std::ifstream::binary);
	if(of.is_open()){
		std::cout << "Enter file contents : ";
        	std::cin >> writebuffer;
		of.write(writebuffer.c_str(), sizeof(readbuffer));
                of.close();
		std::cout << "Write complete!" << std::endl;
        	return 0;
	}
	else{
		std::cout << "Open error!" << std::endl;
		exit(0);
	}
}

int read_flag(){
        std::ifstream is ("flag", std::ifstream::binary);
        if(is.is_open()){
                is.read(flag, sizeof(readbuffer));
                is.close();
                return 0;
        }
        else{
		std::cout << "You must need flagfile.." << std::endl;
                exit(0);
        }
}

int show_contents(){
	std::cout << "contents : ";
	std::cout << readbuffer << std::endl;
	return 0;
}
	


int main(void) {
    initialize();
    int selector = 0;
    while(1){
    	std::cout << "Simple file system" << std::endl;
    	std::cout << "1. read file" << std::endl;
    	std::cout << "2. write file" << std::endl;
	std::cout << "3. show contents" << std::endl;
    	std::cout << "4. quit" << std::endl;
    	std::cout << "[*] input : ";
	std::cin >> selector;
	
	switch(selector){
		case 1:
			read_flag();
			read_file();
			break;
		case 2:
			write_file();
			break;
		case 3:
			show_contents();
			break;
		case 4:
			std::cout << "BYEBYE" << std::endl;
			exit(0);
	}
    }
}

소스코드다.

flag 파일 내용을 입력하여야 읽고 볼 수 있어야 flag를 획득할 수 있는 것으로 보인다 

문제 제목이 cpp string인게 힌트인 것 같다 

 

 

이 코드는 간단한 파일 시스템을 구현한 프로그램입니다. 사용자는 파일을 읽고 쓰며, 특정 파일(flag)의 내용을 프로그램에서 관리합니다. 다음은 코드의 주요 기능과 잠재적인 취약점을 분석한 결과입니다.


1. 주요 기능

1.1. 초기화 함수 (initialize)

  • 설명:
    • stdin과 stdout 버퍼를 비활성화하여 실시간 입출력을 보장.
    • 30초의 타이머를 설정하고, 타이머가 만료되면 프로그램 종료.

1.2. 파일 읽기 (read_file)

  • 설명:
    • test 파일의 내용을 readbuffer에 읽어 들입니다.
    • 파일이 존재하지 않으면 프로그램 종료.
  • 버그/취약점:
    • readbuffer의 크기(64바이트)만큼 파일 내용을 읽지만, 파일이 더 크면 초과된 데이터는 무시됩니다.

1.3. 파일 쓰기 (write_file)

  • 설명:
    • 사용자 입력을 받아 test 파일에 기록.
    • 입력은 std::string(writebuffer)로 받아, C 스타일 문자열로 변환한 뒤 파일에 기록.
  • 버그/취약점:
    • 입력 데이터 크기 제한이 없음:
      • 사용자 입력이 64바이트를 초과하면 프로그램이 의도한 데이터 크기를 초과하여 기록하려고 시도합니다.
      • of.write(writebuffer.c_str(), sizeof(readbuffer));는 항상 64바이트를 쓰려고 시도하므로, 입력이 64바이트 미만인 경우 파일에 불필요한 데이터가 포함됩니다.

1.4. 플래그 파일 읽기 (read_flag)

  • 설명:
    • flag 파일 내용을 flag 버퍼에 읽음.
    • 파일이 없으면 프로그램 종료.
  • 버그/취약점:
    • 읽은 데이터는 사용자에게 노출되지 않음(단순히 메모리에 저장).

1.5. 내용 표시 (show_contents)

  • 설명:
    • readbuffer 내용을 출력.
  • 버그/취약점:
    • readbuffer가 초기화되지 않거나 파일이 읽히지 않은 상태에서 호출될 경우, 이전 쓰레기 데이터를 출력.

1.6. 메인 함수

  • 설명:
    • 사용자 인터페이스를 통해 파일 읽기, 쓰기, 내용을 확인하거나 프로그램 종료.
    • read_flag는 항상 read_file 전에 호출되며, 플래그 파일이 없는 경우 실행이 차단됨.
  • 버그/취약점:
    • 사용자 입력 selector의 값이 의도한 범위를 벗어날 경우 처리하지 않음.
      • 예: 5 이상의 값이나 비정상적인 입력.

2. 잠재적 취약점 및 공격 가능성

2.1. write_file 입력 크기 제한 없음

  • 취약점:
    • 사용자 입력에 크기 제한이 없으므로, 과도한 입력이 메모리 오염 및 이상 동작을 유발할 수 있습니다.
  • 공격 가능성:
    • 파일 오염: 입력 크기가 64바이트를 초과하면 test 파일에 의도하지 않은 데이터가 기록됩니다.
    • DOS 공격: 매우 큰 입력으로 메모리를 고갈시킬 가능성.

2.2. readbuffer 초기화 문제

  • 취약점:
    • readbuffer는 read_file이 호출되기 전에는 초기화되지 않으므로, show_contents가 호출될 경우 쓰레기 데이터가 출력됩니다.
  • 공격 가능성:
    • 프로그램 메모리에 저장된 데이터를 유추할 수 있는 정보 누출(Information Leakage) 공격.

2.3. 플래그 보호 부족

  • 취약점:
    • read_flag는 플래그 파일 내용을 읽어 메모리에 저장하지만, 보호 장치가 부족합니다.
  • 공격 가능성:
    • 플래그 파일 내용은 메모리(flag 변수)에 저장되므로, 메모리 누출 취약점을 악용해 플래그를 유출할 가능성이 있습니다.
    • 예: show_contents가 readbuffer를 출력하는데, 메모리 오염으로 인해 flag가 readbuffer에 복사되면 플래그 노출 가능.

3. 개선 방안

  1. 입력 크기 제한 추가 (write_file):
    • 사용자 입력의 크기를 제한하여 메모리 오염 및 프로그램 충돌 방지.
    std::cin >> std::setw(64) >> writebuffer;
    
  2. readbuffer 초기화:
    • read_file 호출 전 readbuffer를 명시적으로 초기화.
    memset(readbuffer, 0, sizeof(readbuffer));
    
  3. 불필요한 데이터 쓰기 방지:
    • write_file에서 실제 사용자 입력 크기만큼만 파일에 기록.
    of.write(writebuffer.c_str(), writebuffer.size());
    
  4. 사용자 입력 검증:
    • selector 값이 허용된 범위 내에 있는지 확인.
    if (selector < 1 || selector > 4) {
        std::cout << "Invalid input!" << std::endl;
        continue;
    }
    
  5. 플래그 보호 강화:
    • flag 파일을 메모리에 로드하지 않고, 직접 접근을 제한하거나 암호화.

4. 공격 시나리오

  1. 입력 크기 초과로 메모리 오염:
    • write_file에서 초과 입력으로 메모리를 덮어쓰거나 프로그램 이상 동작 유발.
  2. 메모리 누출로 플래그 유출:
    • readbuffer와 flag가 동일한 메모리 공간을 공유하도록 조작하여 show_contents로 플래그 출력.

결론

이 프로그램은 간단한 파일 시스템을 구현했지만, 여러 가지 취약점이 존재합니다. 특히, 입력 검증 부족과 메모리 초기화 문제로 인해 정보 누출 및 프로그램 충돌 가능성이 있습니다. 위 개선 방안을 적용하여 보안을 강화할 수 있습니다. 😊


 

**cpp_string**은 주어진 C++ 코드에서 실행 파일 이름으로 지정된 프로그램 이름입니다. 이 프로그램은 간단한 파일 시스템처럼 작동하며, 파일 읽기, 쓰기, 내용을 표시할 수 있는 기능을 제공합니다. 문제를 해결하려면 이 프로그램의 동작을 분석하고, 약점을 찾아 이를 활용하여 플래그를 추출하거나 특정 목표를 달성하는 것이 목표입니다.


주어진 프로그램의 동작

1. 주요 기능

이 프로그램은 다음과 같은 메뉴를 제공합니다:

  1. Read File:
    • test 파일의 내용을 읽어서 readbuffer에 저장.
    • 플래그 파일(flag)도 읽어서 메모리에 로드.
  2. Write File:
    • 사용자 입력을 받아 test 파일에 작성.
  3. Show Contents:
    • readbuffer의 내용을 화면에 출력.
  4. Quit:
    • 프로그램 종료.

문제 풀이 목표

문제 풀이의 목표는 보통 다음과 같은 작업 중 하나입니다:

  1. 플래그 추출:
    • flag 파일의 내용을 프로그램의 동작을 통해 읽고 표시.
  2. 취약점 이용:
    • 프로그램의 입력 검증 부족이나 메모리 관리 취약점을 악용하여 제한된 데이터를 조작하거나 노출.
  3. 특정 작업 수행:
    • 예: 파일 내용을 덮어쓰기, 예상치 못한 데이터를 읽기 등.

문제 풀이 접근 방법

1. 코드 분석

  1. read_flag 함수:
    • 플래그 파일(flag)의 내용을 읽고 flag 변수에 저장.
    • 이 내용은 메모리에만 저장되고 직접 출력되지 않습니다.
  2. show_contents 함수:
    • readbuffer 내용을 출력.
    • read_file로 파일(test)의 내용을 읽어야 정상적으로 데이터를 확인 가능.
  3. write_file 함수:
    • 사용자 입력을 받아 test 파일에 작성.
    • 입력 크기 제한이 없어 메모리 오염 가능.

2. 취약점 탐지 및 활용

취약점 1: 입력 크기 제한 부족 (Write File)

  • 사용자 입력에 크기 제한이 없어 초과 입력으로 메모리 오염 가능.
  • writebuffer에 데이터를 초과 입력하여 readbuffer나 flag와 같은 메모리 공간을 덮어쓸 가능성.

취약점 2: 정보 노출 가능성 (Show Contents)

  • readbuffer를 출력하지만, 제대로 초기화되지 않은 경우 쓰레기 데이터나 메모리에 저장된 다른 데이터(flag 등)를 출력할 가능성.

취약점 3: 플래그 보호 부족

  • 플래그 데이터는 메모리에 저장되며, 직접 접근하지 않아도 메모리 누출 취약점을 통해 읽어낼 수 있음.

공격 시나리오

목표: 플래그 파일의 내용(flag)을 추출.

  1. 초기화:
    • 프로그램을 실행하고 read_file을 호출하여 flag 내용을 메모리에 로드.
  2. 메모리 덮어쓰기:
    • write_file을 이용하여 입력 크기 초과로 메모리 덮어쓰기 시도.
    • readbuffer가 flag 내용을 가리키도록 조작.
  3. 내용 표시:
    • show_contents를 호출하여 flag 내용을 출력.


 

 

즉 wirte_file이 입력 크기 제한이 없어서, 사용자 입력 받아->  test파일에 작성

이test파일을 read_file로 읽어와서, show_contents 함수가  작동 ,readbuffer 내용을 출력, 

문제는 그게 읽기 크기 제한을 초과해서, read_flag 함수가 호출되어 flag 변수 튀어나와, flag 파일이 보이는거임

 

 


 

 

학원가야함;;갔다와서 풉니다

 

짜자잔~