Dreamhack/Dreamhack Wargame (Challenge)

[86] IT 비전공자 [dreamhack]basic_heap_overflow문제 풀기

imaginefuture-1 2024. 12. 4. 08:56

heap overflow~

 

 

 

 

소스코드보러가자

 

 

이 결과는 파일 바이너리의 보안 설정 및 실행 환경을 분석한 결과입니다. 각각의 항목은 컴파일 옵션 및 바이너리 보안 강화와 관련된 정보를 제공합니다. 아래에서 하나씩 자세히 설명하겠습니다.


1. Arch: i386-32-little

  • 의미:
    • 바이너리가 **32비트 아키텍처(i386)**용으로 컴파일되었음을 의미합니다.
    • little-endian 형식을 사용하여 데이터를 저장하고 처리.
      • Little-endian: 낮은 바이트가 메모리의 낮은 주소에 저장.
  • 영향:
    • 현대 시스템은 대부분 64비트 아키텍처를 사용하지만, 32비트 바이너리도 여전히 실행 가능.
    • 익스플로잇을 시도할 때 메모리 레이아웃 및 주소 계산에서 32비트임을 고려해야 함.

2. RELRO: No RELRO

  • RELRO (Relocation Read-Only):
    • 런타임 시 **GOT(Global Offset Table)**를 보호하는 메커니즘.
    • GOT는 외부 함수의 실제 주소를 저장하는 구조체로, 공격자가 이를 덮어써 악성 코드를 실행할 수 있음.
  • No RELRO:
    • RELRO 보호가 전혀 활성화되지 않았음을 의미.
    • 공격자는 GOT를 덮어쓸 수 있어 GOT overwrite 기반 공격이 가능.
  • 보호 수준 종류:
    • No RELRO: 보호 없음.
    • Partial RELRO: GOT를 읽기 전용으로 설정하지만, GOT 초기화 전에 공격 가능.
    • Full RELRO: GOT를 읽기 전용으로 설정하며 초기화 후 변경 불가.

3. Stack: No canary found

  • Stack Canary:
    • 스택 오버플로우를 방지하기 위해 스택 프레임에 삽입된 보호 값.
    • 함수 종료 시 이 값이 변경되었는지 확인하여 공격을 탐지.
  • No canary found:
    • 이 바이너리는 Stack Canary를 사용하지 않음.
    • 공격자가 스택 오버플로우를 통해 함수 리턴 주소를 덮어쓰는 공격(예: ROP(Return-Oriented Programming))이 가능.
  • 영향:
    • 스택 오버플로우 공격 방어가 전혀 없음.
    • 공격자가 스택을 마음대로 조작할 수 있음.

4. NX: NX enabled

  • NX (No-eXecute):
    • 메모리의 특정 영역(예: 스택, 힙)을 실행 불가능하게 설정하는 보안 메커니즘.
    • 코드 실행은 실행 가능한 메모리 영역에서만 가능.
  • NX enabled:
    • NX가 활성화되어 있어 스택과 힙에서 명령어 실행이 불가능.
    • 쉘코드를 직접 실행하려는 시도가 차단될 가능성 높음.
  • 우회 방법:
    • ROP(Return-Oriented Programming) 같은 기법 사용.
    • 기존 코드 조각을 재사용하여 공격 수행.

5. PIE: No PIE

  • PIE (Position-Independent Executable):
    • 실행 파일의 메모리 주소를 실행 시마다 무작위화(ASLR: Address Space Layout Randomization)하는 기술.
    • 메모리 주소가 고정되지 않아 익스플로잇이 더 어려워짐.
  • No PIE:
    • PIE가 활성화되지 않았음을 의미.
    • 바이너리의 코드 섹션(예: .text)의 메모리 주소가 고정되어 있음.
    • 공격자는 메모리 주소를 예측하고 익스플로잇을 수행할 수 있음.
  • 영향:
    • 메모리 주소가 고정되므로 익스플로잇이 비교적 쉬움.

6. Base Address: 0x8048000

  • 의미:
    • 바이너리의 기본 메모리 주소가 고정되어 있음.
    • 이 경우, 0x8048000에서 실행이 시작됨.
    • PIE가 활성화된 경우 기본 주소가 무작위화되지만, 여기서는 No PIE이므로 고정.
  • 영향:
    • 공격자는 메모리 주소를 정확히 예측할 수 있어 익스플로잇이 용이.

결론

이 바이너리는 다음과 같은 보안 취약점이 있습니다:

  1. No RELRO:
    • GOT overwrite 공격 가능.
  2. No canary found:
    • 스택 오버플로우에 취약.
  3. No PIE:
    • 메모리 주소가 고정되어 익스플로잇 가능성 증가.

보호 활성화된 부분:

  1. NX enabled:
    • 스택에서 쉘코드 직접 실행 방지.

이 바이너리는 보안 메커니즘이 미흡하여 메모리 취약점을 이용한 공격이 가능하며, ROP 기법이나 GOT overwrite 공격을 통해 익스플로잇이 가능합니다.

 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

struct over {
    void (*table)();
};

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

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

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

void get_shell() {
    system("/bin/sh");
}

void table_func() {
    printf("overwrite_me!");
}

int main() {
    char *ptr = malloc(0x20);

    struct over *over = malloc(0x20);

    initialize();

    over->table = table_func;

    scanf("%s", ptr);

    if( !over->table ){
        return 0;
    }

    over->table();
    return 0;
}

 

 

이 코드는 C언어로 작성된 프로그램으로, 메모리 할당과 함수 포인터를 활용한 구조체를 통해 동작합니다. 또한, 취약점을 악용해 쉘을 실행할 가능성을 제공하는 구조를 가지고 있습니다. 아래에 코드의 주요 부분을 분석하고 설명합니다.


1. 헤더 파일

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
  • <stdio.h>: 입출력 함수 (printf, scanf 등) 제공.
  • <stdlib.h>: 동적 메모리 할당 및 프로세스 제어 함수 (malloc, exit, system) 제공.
  • <signal.h>: 신호 처리 관련 함수 (signal, alarm) 제공.
  • <unistd.h>: POSIX API 함수 (alarm, system) 제공.
  • <string.h>: 문자열 처리 함수 (memset, strlen) 제공.

2. 구조체 정의

struct over {
    void (*table)();
};
  • struct over: 함수 포인터 table을 멤버로 가지는 구조체.
    • void (*table)();: 함수 포인터로, table이 호출되면 연결된 함수를 실행.

3. alarm_handler 함수

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
  • 설명:
    • SIGALRM 신호를 받으면 호출됨.
    • "TIME OUT" 메시지를 출력하고 프로그램 종료.

4. initialize 함수

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

    signal(SIGALRM, alarm_handler);
    alarm(30);
}
  • 설명:
    • 버퍼링 비활성화:
      • stdin과 stdout을 버퍼 없이 즉시 입력/출력되도록 설정.
    • SIGALRM 핸들러 등록:
      • 30초 후 SIGALRM 신호를 보냄.
      • 신호를 받으면 alarm_handler 호출.

5. get_shell 함수

void get_shell() {
    system("/bin/sh");
}
  • 설명:
    • /bin/sh를 실행하여 쉘을 제공.
    • 이 함수는 정상적인 경로로는 호출되지 않으나, 취약점 공격으로 실행 가능.

6. table_func 함수

void table_func() {
    printf("overwrite_me!");
}
  • 설명:
    • table 멤버의 기본 함수로 설정됨.
    • "overwrite_me!" 메시지를 출력.

7. main 함수

int main() {
    char *ptr = malloc(0x20);

    struct over *over = malloc(0x20);

    initialize();

    over->table = table_func;

    scanf("%s", ptr);

    if( !over->table ){
        return 0;
    }

    over->table();
    return 0;
}

주요 동작:

  1. 동적 메모리 할당:
    • ptr와 over에 각각 32바이트(0x20) 크기의 메모리 할당.
    • ptr은 사용자 입력 저장.
    • over는 struct over 구조체를 위한 메모리.
  2. 초기화:
    • initialize 함수 호출:
      • 버퍼링 비활성화.
      • 30초 타이머 시작.
  3. 함수 포인터 설정:
    • over->table = table_func;:
      • table 포인터를 table_func로 초기화.
  4. 입력 받기:
    • scanf("%s", ptr);:
      • 사용자 입력을 ptr에 저장.
      • 취약점: 입력 길이 제한이 없어, 버퍼 오버플로우 가능.
  5. 함수 포인터 호출:
    • if (!over->table):
      • table이 NULL이면 프로그램 종료.
    • over->table();:
      • table이 가리키는 함수를 호출.
      • 기본적으로 table_func 호출, 하지만 공격자가 이를 덮어쓸 수 있음.

8. 취약점 분석

  • 취약점:
    • 사용자 입력으로 인해 버퍼 오버플로우 발생 가능.
    • ptr은 32바이트만 할당되었지만, 입력 길이에 제한이 없어 구조체 over의 데이터(table)를 덮어쓸 수 있음.
  • 공격 시나리오:
    • over->table을 get_shell 주소로 덮어씀.
    • 이후 over->table() 호출 시 get_shell 실행.
    • 결과적으로 공격자가 쉘을 획득.

9. 요약

  • 기본 동작:
    • 사용자 입력을 받고, 구조체의 table 멤버를 호출.
  • 취약점:
    • 입력 검증 부족으로 함수 포인터(table)를 덮어쓸 수 있음.
  • 공격 방법:
    • 구조체 멤버를 덮어쓰는 방식으로 table을 get_shell로 설정.
    • 프로그램 실행 중 쉘을 획득 가능.

이 코드는 메모리 취약점 학습이나 C언어 취약점 이해를 위한 좋은 예시입니다. 😊

 


 

read가 아닌 scanf로 입력을 받고있기에

길이를 지정해주지않고 입력을 받으니 이 부분에서 overflow가 발생한다

 

ptr 크기가 0x20이니까 길이 0x20이상의 무언가를 입력하면 over의 값에 접근이 가능하고 

지피티가 말한대로

  • 취약점:
    • 사용자 입력으로 인해 버퍼 오버플로우 발생 가능.
    • ptr은 32바이트만 할당되었지만, 입력 길이에 제한이 없어 구조체 over의 데이터(table)를 덮어쓸 수 있음.
  • 공격 시나리오:
    • over->table을 get_shell 주소로 덮어씀.
    • 이후 over->table() 호출 시 get_shell 실행.
    • 결과적으로 공격자가 쉘을 획득.

 

table을 get_shell주소로 덮어쓰고 over 해버리면 table호출시 get_shell이 실행되는거다

 

 

 

gdb로 disassemble main하러 가보자

 

(gdb) disassemble main
Dump of assembler code for function main:
   0x080486ad <+0>:	lea    0x4(%esp),%ecx
   0x080486b1 <+4>:	and    $0xfffffff0,%esp
   0x080486b4 <+7>:	push   -0x4(%ecx)
   0x080486b7 <+10>:	push   %ebp
   0x080486b8 <+11>:	mov    %esp,%ebp
   0x080486ba <+13>:	push   %ecx
   0x080486bb <+14>:	sub    $0x14,%esp
   0x080486be <+17>:	sub    $0xc,%esp
   0x080486c1 <+20>:	push   $0x20
   0x080486c3 <+22>:	call   0x8048490 <malloc@plt>
   0x080486c8 <+27>:	add    $0x10,%esp
   0x080486cb <+30>:	mov    %eax,-0x10(%ebp)
   0x080486ce <+33>:	sub    $0xc,%esp
   0x080486d1 <+36>:	push   $0x20
   0x080486d3 <+38>:	call   0x8048490 <malloc@plt>
   0x080486d8 <+43>:	add    $0x10,%esp
   0x080486db <+46>:	mov    %eax,-0xc(%ebp)
   0x080486de <+49>:	call   0x804862b <initialize>
   0x080486e3 <+54>:	mov    -0xc(%ebp),%eax
   0x080486e6 <+57>:	movl   $0x8048694,(%eax)
   0x080486ec <+63>:	sub    $0x8,%esp
   0x080486ef <+66>:	push   -0x10(%ebp)
--Type <RET> for more, q to quit, c to continue without paging--  
   0x080486f2 <+69>:	push   $0x80487cf
   0x080486f7 <+74>:	call   0x80484f0 <__isoc99_scanf@plt>
   0x080486fc <+79>:	add    $0x10,%esp
   0x080486ff <+82>:	mov    -0xc(%ebp),%eax
   0x08048702 <+85>:	mov    (%eax),%eax
   0x08048704 <+87>:	test   %eax,%eax
   0x08048706 <+89>:	jne    0x804870f <main+98>
   0x08048708 <+91>:	mov    $0x0,%eax
   0x0804870d <+96>:	jmp    0x804871b <main+110>
   0x0804870f <+98>:	mov    -0xc(%ebp),%eax
   0x08048712 <+101>:	mov    (%eax),%eax
   0x08048714 <+103>:	call   *%eax
   0x08048716 <+105>:	mov    $0x0,%eax
   0x0804871b <+110>:	mov    -0x4(%ebp),%ecx
   0x0804871e <+113>:	leave
   0x0804871f <+114>:	lea    -0x4(%ecx),%esp
   0x08048722 <+117>:	ret

 

 

 

 0x080486de <+49>:	call   0x804862b <initialize>
   0x080486e3 <+54>:	mov    -0xc(%ebp),%eax
   0x080486e6 <+57>:	movl   $0x8048694,(%eax)
   0x080486ec <+63>:	sub    $0x8,%esp
   0x080486ef <+66>:	push   -0x10(%ebp)
--Type <RET> for more, q to quit, c to continue without paging--  
   0x080486f2 <+69>:	push   $0x80487cf
   0x080486f7 <+74>:	call   0x80484f0 <__isoc99_scanf@plt>

 

문제의 핵심은 scanf 함수와 table_func 함수의 
관계입니다.  위 어셈블리 코드에서 핵심부분만 가져왔습니다.

scanf 함수 이전에 over->table = table_func;와 같이 over->table이라는 함수 포인터에
table_func 함수 주소값을 저장하는데 이곳에 대신 get_shell 함수 주소값을 주입할 수 있다면
쉘을 획득할 수 있습니다.
 
   0x080486e6 <+57>:    mov    DWORD PTR [eax],0x8048694 에서 0x8048694는 table_func을 나타내는데 [eax]에 해당 주소를 저장하므로 0x080486e6에 브레이크 포인터를 걸고 [eax] 값을 찾아줍니다.


출처ㅣ https://lemon-soju.tistory.com/53

 

 

 

ptr = 0x804b1a0
table_func = 0x804b1d0
get_shell = 0x0804867b
 
ptr과 table_func의 주소값 차이는 48입니다. 하지만 실제 문제 서버와 로컬 환경이 달라서 그런지 문제 서버에서의 ptr과 table_func의 주소값 차이는 40입니다. 가장 유력한 원인은 운영체제의 비트 수 차이인데 제 PC는 64bit, 문제 서버는 32bit로 추정됩니다.
 
제 PC는 64bit이므로 힙 메모리를 할당할 때 처음 여유 비트로 8바이트를 더 부여하고
0x10단위로 힙 메모리를 부여하기 때문에 처음 32바이트에서 여유메모리를 합하면 40바이트
16바이트가 단위이므로 48바이트를 부여하기 때문에 지금까지의 결과가 나왔습니다.
하지만 문제 서버가 32bit 운영체제라면 처음 32바이트에서 여유 메모리는 정확히 모르지만
최대 8바이트이므로 8바이트를 더해 40바이트, 단위는 8바이트 이므로 40바이트의 힙 메모리를 할당합니다.
따라서 아래 익스플로잇 코드를 작성할 때는 주소값 차이를 40으로 계산해야합니다. 


출처ㅣ 
https://lemon-soju.tistory.com/53

 

 

페이로드