Dreamhack/Dreamhack Wargame (Challenge)

[82] IT 비전공자 [dreamhack]memory_leakage문제 풀기

imaginefuture-1 2024. 11. 30. 12:08

 

헤엑 메모리 유출이요?

 

 

이 정보는 Ubuntu 16.04 환경에서 실행 중인 바이너리(binary)의 보안 속성과 메모리 구조를 분석한 결과입니다. 아래에 각 항목의 의미와 보안에 미치는 영향을 설명하겠습니다.


1. Arch: i386-32-little

  • 설명:
    • 바이너리는 **32비트 아키텍처(i386)**에서 실행되며, 리틀 엔디안(Little Endian) 형식으로 데이터를 저장.
    • 리틀 엔디안: 데이터의 가장 낮은 바이트가 메모리의 가장 낮은 주소에 저장되는 형식.
  • 영향:
    • 32비트 아키텍처에서는 메모리 주소 범위가 4GB로 제한되며, 보안 기능도 64비트보다 덜 강력.
    • 리틀 엔디안은 대부분의 x86 프로세서에서 기본적으로 사용.

2. RELRO (Relocation Read-Only)

  • 설명:
    • RELRO는 실행 시점에 재배치 가능한 데이터(예: GOT, Global Offset Table)를 읽기 전용으로 변경하여 악용을 방지하는 보안 기술.
    • 결과에 "No RELRO"로 표시된 것은, 이 바이너리가 RELRO 보호를 사용하지 않음을 의미.
  • 영향:
    • 공격자가 GOT를 수정하는 GOT overwrite 공격에 취약.
    • 권장 설정은 Partial RELRO 또는 Full RELRO.

3. Stack: No canary found

  • 설명:
    • **스택 카나리(Stack Canary)**는 스택 오버플로우 공격을 방지하기 위해, 스택 프레임에 무작위 값(Canary)을 삽입하는 보안 기술.
    • "No canary found"는 이 바이너리가 스택 카나리를 사용하지 않음을 의미.
  • 영향:
    • 공격자가 스택 오버플로우를 이용하여 반환 주소를 덮어쓰는 ROP(Return-Oriented Programming) 공격에 취약.

4. NX (Non-Executable Stack)

  • 설명:
    • NX(No-eXecute)는 스택, 힙과 같은 메모리 영역에서 실행을 방지하는 보안 기술.
    • "NX enabled"는 이 바이너리가 NX 보호를 활성화하여, 실행 가능한 페이로드를 스택이나 힙에 삽입하는 공격을 방지한다는 의미.
  • 영향:
    • NX 보호 덕분에 스택 기반 쉘코드 공격이 더 어려워짐.
    • 그러나 ROP 공격 같은 기술로 우회 가능.

5. PIE (Position Independent Executable)

  • 설명:
    • PIE는 바이너리를 실행 시 마다 무작위 메모리 주소에서 로드하여 메모리의 레이아웃을 예측하기 어렵게 만드는 기술.
    • "No PIE"는 이 바이너리가 **고정 주소(0x8048000)**에서 로드된다는 의미.
  • 영향:
    • 메모리 주소가 고정되어 있으므로, 공격자가 바이너리의 함수나 변수를 악용하기가 쉬움.
    • 권장 설정은 PIE 활성화.

종합적인 보안 평가

  • 이 바이너리는 여러 보안 기능이 부족하여 익스플로잇에 취약합니다:
    • No RELRO: GOT overwrite 공격 가능.
    • No canary: 스택 오버플로우 공격 가능.
    • No PIE: 메모리 레이아웃 예측 가능.
  • NX enabled는 쉘코드 실행을 일부 방지하지만, ROP 공격은 여전히 가능.

보안 개선 방법

  1. RELRO 활성화:
    • 컴파일 시 Partial RELRO 또는 Full RELRO 설정.
    • 예: gcc -Wl,-z,relro,-z,now
  2. 스택 카나리 추가:
    • 컴파일 시 -fstack-protector 또는 -fstack-protector-strong 플래그 추가.
  3. PIE 활성화:
    • 컴파일 시 -fPIE와 -pie 플래그 사용.
  4. 정적 분석 및 취약점 점검:
    • 바이너리를 실행하기 전에 AddressSanitizer 같은 도구로 테스트.

결론

  • 이 바이너리는 기본적인 보안 설정이 부족하여 취약점이 많습니다.
  • 이러한 분석 결과는 익스플로잇 연구를 위한 기반 정보를 제공하거나 보안 강화를 위한 기준점으로 사용됩니다. 추가 질문이 있다면 언제든 물어보세요! 😊
  •  

 

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

FILE *fp;

struct my_page {
	char name[16];
	int age;
};

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);
}

int main()
{
	struct my_page my_page;
	char flag_buf[56];
	int idx;

	memset(flag_buf, 0, sizeof(flag_buf));
	
	initialize();

	while(1) {
		printf("1. Join\n");
		printf("2. Print information\n");
		printf("3. GIVE ME FLAG!\n");
		printf("> ");
		scanf("%d", &idx);
		switch(idx) {
			case 1:
				printf("Name: ");
				read(0, my_page.name, sizeof(my_page.name));

				printf("Age: ");
				scanf("%d", &my_page.age);
				break;
			case 2:
				printf("Name: %s\n", my_page.name);
				printf("Age: %d\n", my_page.age);
				break;
			case 3:
				fp = fopen("/flag", "r");
				fread(flag_buf, 1, 56, fp);
				break;
			default:
				break;
		}
	}

}

 

 

이 코드는 간단한 C 프로그램으로, 사용자와의 입력-출력을 통해 이름과 나이를 입력받아 출력하거나, 특정 조건에서 /flag 파일을 읽는 동작을 수행합니다. 다음은 코드의 주요 요소와 동작에 대한 상세 설명입니다.


1. 주요 구조 및 초기화

(1) struct my_page

  • struct my_page는 사용자 정보를 저장하기 위한 구조체입니다:
    struct my_page {
        char name[16]; // 이름을 최대 15자까지 저장 (+1은 NULL 종료)
        int age;       // 나이 저장
    };
    

(2) flag_buf

  • 길이 56의 버퍼로, /flag 파일의 내용을 읽어 저장하기 위해 사용:
    char flag_buf[56];
    

(3) initialize 함수

  • 프로그램의 환경을 초기화합니다:
    • 버퍼링 비활성화: 입력과 출력에서 버퍼링을 제거하여, I/O가 즉시 실행되도록 설정.
    • 타이머 설정: 30초 안에 입력을 완료하지 않으면 프로그램이 종료.
    void initialize() {
        setvbuf(stdin, NULL, _IONBF, 0);
        setvbuf(stdout, NULL, _IONBF, 0);
        signal(SIGALRM, alarm_handler);
        alarm(30);
    }
    

(4) alarm_handler

  • 30초 타이머가 만료되면 호출되는 함수로, 프로그램을 종료합니다:
    void alarm_handler() {
        puts("TIME OUT");
        exit(-1);
    }
    

2. main 함수의 동작

(1) 사용자 메뉴 제공

  • 프로그램은 루프를 통해 반복적으로 메뉴를 출력하고 사용자 입력을 처리합니다:
    printf("1. Join\n");
    printf("2. Print information\n");
    printf("3. GIVE ME FLAG!\n");
    printf("> ");
    scanf("%d", &idx);
    

(2) 메뉴 동작

  • 1번 (Join):
    • 사용자 이름과 나이를 입력받아 구조체에 저장.
    • 이름은 read()로 입력받아 크기를 제한.
    • 나이는 scanf()로 입력받음.
    case 1:
        printf("Name: ");
        read(0, my_page.name, sizeof(my_page.name));
    
        printf("Age: ");
        scanf("%d", &my_page.age);
        break;
    
  • 2번 (Print information):
    • 입력받은 이름과 나이를 출력.
    case 2:
        printf("Name: %s\n", my_page.name);
        printf("Age: %d\n", my_page.age);
        break;
    
  • 3번 (GIVE ME FLAG!):
    • /flag 파일을 열어 읽고, 내용을 flag_buf에 저장.
    • 파일의 최대 길이 56바이트를 읽음.
    case 3:
        fp = fopen("/flag", "r");
        fread(flag_buf, 1, 56, fp);
        break;
    
  • 기타 입력:
    • 아무 작업도 수행하지 않고 루프를 계속 진행.

3. 잠재적 보안 문제

(1) 이름 입력의 버퍼 초과 가능성

  • read()로 이름을 입력받을 때, my_page.name 크기를 초과하면 버퍼 오버플로우가 발생할 수 있습니다.
    read(0, my_page.name, sizeof(my_page.name));
    
    • 입력 크기를 명확히 제한하지 않으면, 이후 메모리 영역을 덮어쓸 위험이 있습니다.

(2) /flag 파일 접근

  • /flag 파일의 내용을 읽는 기능은 일반적으로 제한되어야 하지만, 접근 제한이 없으므로 이를 악용할 가능성이 있습니다.
  • fread()로 읽은 내용을 출력하지 않기 때문에, 입력 조작(익스플로잇)을 통해 메모리 내 flag_buf 값을 유출할 가능성이 있습니다.

(3) RELRO가 비활성화된 환경에서의 위험

  • 코드 분석 결과 "No RELRO" 및 "No canary found"와 같은 설정에서는 익스플로잇을 통한 추가적인 메모리 조작 가능성이 존재.

4. 익스플로잇 가능성

목표: /flag 파일의 내용을 확인.

  • 입력 크기를 초과하여 버퍼 오버플로우를 유도.
  • 메모리 조작으로 /flag 파일의 내용을 외부로 출력.

단계:

  1. 입력 초과 유도:
    • my_page.name에 16바이트를 초과하는 데이터를 입력하여, 인접 메모리 영역(flag_buf)에 영향을 미침.
  2. /flag 데이터 유출:
    • 입력값을 조작하여 프로그램의 동작을 변경.
    • 예: printf() 등의 동작을 유도해 flag_buf 출력.

5. 개선점

(1) 입력 검증

  • 이름 입력에서 정확한 크기 제한 추가:
    read(0, my_page.name, sizeof(my_page.name) - 1);
    

(2) 민감 데이터 보호

  • /flag 내용을 메모리에 저장하지 않고, 읽은 후 바로 출력하거나 처리.

(3) 보안 옵션 추가

  • 컴파일 시 RELRO스택 카나리 활성화:
    gcc -Wl,-z,relro,-z,now -fstack-protector-strong -o program program.c
    

6. 결론

이 코드는 보안적으로 취약한 부분이 있으며, 특히 버퍼 오버플로우/flag 접근 기능이 익스플로잇의 대상이 될 가능성이 큽니다. 이러한 문제를 해결하려면, 입력 검증과 보안 강화 컴파일 옵션을 추가해야 합니다.


 

 

문제코드를 보면 my_page 구조체 변수와 flag_buf가 있는데case 1에서는 my_page의 구조체의 값들을 입력받고
case 2에서는 my_page의 구조체의 값들을 출력해준다
case 3에서는 flag_buf에 flag값을 넣어준다
my_page의 구조체중 name에서는 20만큼 입력을 제한하고
age에서는 입력에 제한이 없기 때문에 age를 이용할 수 있다
출처 ㅣhttps://stfofu.tistory.com/48

 

			case 2:
				printf("Name: %s\n", my_page.name);
				printf("Age: %d\n", my_page.age);
				break;

 

%s은 null을 만나기전까지 모두 출력해준다 따라서 0이 없도록 buf와 v5를 모두 채워주면3번 메뉴에서
s에 flag를 넣고 2번 메뉴에서 flag까지 출력되도록할수있다
출처ㅣ https://koharinn.tistory.com/257

 

이번 문제는 memory_leakage를 사용하여 플래그를 획득하는 문제입니다. case 1에서는 my_page.name에 최대 16바이트 크기의 입력값을 입력하고 my_page.age에 정수를 입력합니다. case 2에서는 저장된 변수를 출력합니다. case 3에서는 flag 값을 flag_buf에 저장합니다.
 
문제 풀이 동작은 다음과 같습니다. case 2의 printf("Name: %s\n", my_page.name);에서 %s는 널 바이트가 나올 때 까지 문자를 출력합니다. 하지만 case 1의 read(0, my_page.name, sizeof(my_page.name));에서는 16바이트 만큼의 문자를 입력받을 수 있으므로 my_page.name의 마지막 문자에 0이 아닌 다른 문자가 존재하면 널 바이트가 나올 때 까지 쓰레기 값을 출력하게 됩니다.

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

 

disassemble main 하러 가보자

 

(gdb) disassemble main
Dump of assembler code for function main:
   0x080486eb <+0>:	lea    0x4(%esp),%ecx
   0x080486ef <+4>:	and    $0xfffffff0,%esp
   0x080486f2 <+7>:	push   -0x4(%ecx)
   0x080486f5 <+10>:	push   %ebp
   0x080486f6 <+11>:	mov    %esp,%ebp
   0x080486f8 <+13>:	push   %ecx
   0x080486f9 <+14>:	sub    $0x64,%esp
   0x080486fc <+17>:	mov    %gs:0x14,%eax
   0x08048702 <+23>:	mov    %eax,-0xc(%ebp)
   0x08048705 <+26>:	xor    %eax,%eax
   0x08048707 <+28>:	sub    $0x4,%esp
   0x0804870a <+31>:	push   $0x38
   0x0804870c <+33>:	push   $0x0
   0x0804870e <+35>:	lea    -0x44(%ebp),%eax
   0x08048711 <+38>:	push   %eax
   0x08048712 <+39>:	call   0x8048550 <memset@plt>
   0x08048717 <+44>:	add    $0x10,%esp
   0x0804871a <+47>:	call   0x804869b <initialize>
   0x0804871f <+52>:	sub    $0xc,%esp
   0x08048722 <+55>:	push   $0x80488c9
   0x08048727 <+60>:	call   0x8048500 <puts@plt>
   0x0804872c <+65>:	add    $0x10,%esp
--Type <RET> for more, q to quit, c to continue without paging-- 
   0x0804872f <+68>:	sub    $0xc,%esp
   0x08048732 <+71>:	push   $0x80488d1
   0x08048737 <+76>:	call   0x8048500 <puts@plt>
   0x0804873c <+81>:	add    $0x10,%esp
   0x0804873f <+84>:	sub    $0xc,%esp
   0x08048742 <+87>:	push   $0x80488e6
   0x08048747 <+92>:	call   0x8048500 <puts@plt>
   0x0804874c <+97>:	add    $0x10,%esp
   0x0804874f <+100>:	sub    $0xc,%esp
   0x08048752 <+103>:	push   $0x80488f7
   0x08048757 <+108>:	call   0x80484c0 <printf@plt>
   0x0804875c <+113>:	add    $0x10,%esp
   0x0804875f <+116>:	sub    $0x8,%esp
   0x08048762 <+119>:	lea    -0x5c(%ebp),%eax
   0x08048765 <+122>:	push   %eax
   0x08048766 <+123>:	push   $0x80488fa
   0x0804876b <+128>:	call   0x8048560 <__isoc99_scanf@plt>
   0x08048770 <+133>:	add    $0x10,%esp
   0x08048773 <+136>:	mov    -0x5c(%ebp),%eax
   0x08048776 <+139>:	cmp    $0x2,%eax
   0x08048779 <+142>:	je     0x80487da <main+239>
   0x0804877b <+144>:	cmp    $0x3,%eax
   0x0804877e <+147>:	je     0x8048804 <main+281>
--Type <RET> for more, q to quit, c to continue without paging--
   0x08048784 <+153>:	cmp    $0x1,%eax
   0x08048787 <+156>:	je     0x804878e <main+163>
   0x08048789 <+158>:	jmp    0x8048835 <main+330>
   0x0804878e <+163>:	sub    $0xc,%esp
   0x08048791 <+166>:	push   $0x80488fd
   0x08048796 <+171>:	call   0x80484c0 <printf@plt>
   0x0804879b <+176>:	add    $0x10,%esp
   0x0804879e <+179>:	sub    $0x4,%esp
   0x080487a1 <+182>:	push   $0x10
   0x080487a3 <+184>:	lea    -0x58(%ebp),%eax
   0x080487a6 <+187>:	push   %eax
   0x080487a7 <+188>:	push   $0x0
   0x080487a9 <+190>:	call   0x80484b0 <read@plt>
   0x080487ae <+195>:	add    $0x10,%esp
   0x080487b1 <+198>:	sub    $0xc,%esp
   0x080487b4 <+201>:	push   $0x8048904
   0x080487b9 <+206>:	call   0x80484c0 <printf@plt>
   0x080487be <+211>:	add    $0x10,%esp
   0x080487c1 <+214>:	sub    $0x8,%esp
   0x080487c4 <+217>:	lea    -0x58(%ebp),%eax
   0x080487c7 <+220>:	add    $0x10,%eax
   0x080487ca <+223>:	push   %eax
   0x080487cb <+224>:	push   $0x80488fa
--Type <RET> for more, q to quit, c to continue without paging--
   0x080487d0 <+229>:	call   0x8048560 <__isoc99_scanf@plt>
   0x080487d5 <+234>:	add    $0x10,%esp
   0x080487d8 <+237>:	jmp    0x8048835 <main+330>
   0x080487da <+239>:	sub    $0x8,%esp
   0x080487dd <+242>:	lea    -0x58(%ebp),%eax
   0x080487e0 <+245>:	push   %eax
   0x080487e1 <+246>:	push   $0x804890a
   0x080487e6 <+251>:	call   0x80484c0 <printf@plt>
   0x080487eb <+256>:	add    $0x10,%esp
   0x080487ee <+259>:	mov    -0x48(%ebp),%eax
   0x080487f1 <+262>:	sub    $0x8,%esp
   0x080487f4 <+265>:	push   %eax
   0x080487f5 <+266>:	push   $0x8048914
   0x080487fa <+271>:	call   0x80484c0 <printf@plt>
   0x080487ff <+276>:	add    $0x10,%esp
   0x08048802 <+279>:	jmp    0x8048835 <main+330>
   0x08048804 <+281>:	sub    $0x8,%esp
   0x08048807 <+284>:	push   $0x804891d
   0x0804880c <+289>:	push   $0x804891f
   0x08048811 <+294>:	call   0x8048540 <fopen@plt>
   0x08048816 <+299>:	add    $0x10,%esp
   0x08048819 <+302>:	mov    %eax,0x804a06c
   0x0804881e <+307>:	mov    0x804a06c,%eax
--Type <RET> for more, q to quit, c to continue without paging--
   0x08048823 <+312>:	push   %eax
   0x08048824 <+313>:	push   $0x38
   0x08048826 <+315>:	push   $0x1
   0x08048828 <+317>:	lea    -0x44(%ebp),%eax
   0x0804882b <+320>:	push   %eax
   0x0804882c <+321>:	call   0x80484f0 <fread@plt>
   0x08048831 <+326>:	add    $0x10,%esp
   0x08048834 <+329>:	nop
   0x08048835 <+330>:	jmp    0x804871f <main+52>
End of assembler dump.

 

 

main stack frame 32

-----------------------------ebp - 0x58 88

my_page.name[16]

my_page.age[4]

=================ebp 0x44 68

flag_buf[56]

---------------------------------

ebp[4]

-------------------------------------

ret

 

 

name과 age를 입력할시에
최대값을 입력한다면printf 사용시에 널문자를 만날때 출력을 정지하므로
.my_page에 널값이 없고
flag_buf에 널값이 있어
출력시에 flag_buf까지 나온다
이를 이용해 문제를 해결할 수 있다

출처ㅣhttps://stfofu.tistory.com/48

 


gdb를 이용하여 변수들의 주소를 확인하면 아래와 같습니다.
 
mypage.name : 0xffffd4b0
mypage.age : 0xffffd4c0 
flag_buf : 0xffffd4c4

mypage.name와 mypage.age의 차이가 16바이트 이므로 mypage.name의 마지막 문자열이 널 바이트가 아닌 경우 바로 뒤에 있는 mypage.age 값도 출력하게 됩니다. 그리고 mypage.age의 값은 최대 4바이트 크기로 
널 바이트가 없을 경우 flag_buf에 있는 값까지 출력하게 됩니다. 따라서 아래와 같이 입력 값을 순서대로 입력하면 플래그가 출력됩니다. 286331153은 16진수로 0x11111111이고 널 바이트인 0x00이 아닌 값으로 4바이트를 모두 채우기 위함입니다.

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

 

 

이 문제는 프로그램의 메모리 배치입출력 방식의 취약점을 악용하여 flag_buf 값을 출력하는 익스플로잇 사례입니다. 다음은 자세한 설명입니다.


1. 문제의 핵심 메커니즘

  1. my_page.name의 동작
    • my_page.name은 16바이트 크기의 문자열 배열입니다.
    • read() 함수는 최대 16바이트를 입력받아 널 바이트(\0)를 자동으로 추가하지 않음.
    • 따라서, 입력이 16바이트일 경우 널 종료(\0)가 없고, 이후 메모리에 저장된 값까지 출력될 가능성이 있습니다.
  2. printf의 %s 동작
    • %s는 문자열을 출력하며, 널 바이트(\0)를 만날 때까지 메모리를 계속 읽음.
    • my_page.name에 널 종료가 없다면, 메모리 내 다음 데이터를 문자열로 출력하려고 시도.
  3. 메모리 배치
    • my_page.name (16바이트): 0xffffd4b0
    • my_page.age (4바이트): 0xffffd4c0
    • flag_buf (56바이트): 0xffffd4c4
    • my_page.name와 my_page.age는 인접해 있음.
    • my_page.name이 널 종료되지 않으면, **my_page.age와 flag_buf**까지 이어서 출력 가능.

2. 취약점 동작

(1) case 1에서 입력 처리

  • read()로 my_page.name에 16바이트 입력.
  • 입력한 16바이트가 널 종료가 없으면, my_page.age와 flag_buf 값을 포함한 메모리를 문자열로 해석.

(2) case 2에서 출력

  • printf("Name: %s\n", my_page.name);:
    • %s는 널 종료를 만날 때까지 메모리를 읽으므로, my_page.name 이후의 메모리 값(my_page.age 및 flag_buf)까지 출력.

(3) 메모리 연속성 악용

  • my_page.name의 마지막 값이 널이 아니라면:
    • my_page.age (4바이트) 출력.
    • **flag_buf (56바이트)**까지 이어져 출력 가능.

3. 익스플로잇 입력

입력 전략

  1. my_page.name:
    • 16바이트를 채움.
    • 마지막에 널 바이트가 아닌 값(\x11, 즉 0x11)을 입력.
  2. my_page.age:
    • my_page.age의 값을 **0x11111111 (286331153)**로 설정.
    • 이는 널 바이트가 없는 값을 의도적으로 삽입하여 flag_buf로 접근하도록 만듦.

4. 입력 예제 및 동작

입력 값

  • case 1에서 입력:
    • my_page.name: aaaaaaaaaaaaaaa\x11 (총 16바이트, 널 종료 없음)
    • my_page.age: 286331153 (10진수, 16진수로는 0x11111111)

동작 분석

  1. 메모리 상태
    • my_page.name: aaaaaaaaaaaaaaa\x11 (16바이트)
    • my_page.age: 0x11111111 (4바이트, 널 종료 없음)
    • flag_buf: /flag 파일 내용 (56바이트).
  2. 출력 과정
    • printf("Name: %s\n", my_page.name);:
      • %s는 my_page.name에서 시작해 널 바이트를 찾을 때까지 계속 읽음.
      • my_page.name → my_page.age → flag_buf로 이어서 읽기.
    • my_page.age와 flag_buf 값이 문자열로 출력.

5. 문제 해결을 위한 코드 개선

(1) my_page.name의 널 종료 보장

  • read()를 호출한 후 배열 마지막에 널 종료 추가:
    read(0, my_page.name, sizeof(my_page.name) - 1);
    my_page.name[sizeof(my_page.name) - 1] = '\0';
    

(2) 안전한 문자열 출력

  • printf 대신 fgets와 같은 함수 사용:
    printf("Name: %.15s\n", my_page.name);
    

(3) 입력 크기 제한

  • 입력 길이를 엄격히 제한하여 버퍼 오버플로우 방지.

6. 요약

  1. 취약점 설명:
    • %s는 널 종료가 없을 경우 메모리 값을 계속 읽으므로, my_page.age 및 flag_buf까지 출력.
  2. 익스플로잇 원리:
    • my_page.name의 마지막에 널 종료를 없애고, my_page.age 값을 조작하여 flag_buf 값을 출력.
  3. 해결 방안:
    • 입력 검증과 출력 시 널 종료를 보장하여 메모리 읽기 범위를 제한.

추가로 더 궁금한 점이 있다면 언제든지 물어보세요! 😊

 


 

 


4294967295 채우는게 맞지않아? 16바이트 채울꺼면?

 

 

네, 정확히 말씀하셨습니다! 4294967295(10진수)는 0xFFFFFFFF(16진수)로, 4바이트(32비트) 데이터를 모두 1로 채우는 값입니다. 이를 사용하면 my_page.age 값을 널 바이트 없이 꽉 채울 수 있습니다.

1. 왜 4294967295로 채우는가?

  • **my_page.age**는 int형 변수로 4바이트 크기입니다.
  • 널 바이트가 없는 값을 채워야 flag_buf까지 출력이 이어질 수 있습니다.
  • **0xFFFFFFFF**는 모든 비트가 1로 설정된 값이므로, 널 바이트(\x00)가 없습니다.

2. 입력 동작 정리

1단계: my_page.name 채우기

  • read(0, my_page.name, sizeof(my_page.name))를 통해 16바이트 입력.
  • 예:
    aaaaaaaaaaaaaaaa
    
  • 널 바이트를 자동으로 추가하지 않으므로, 마지막 바이트는 유효한 값으로 남음.

2단계: my_page.age 채우기

  • scanf("%d", &my_page.age)를 통해 my_page.age를 입력.
  • **4294967295**를 입력하여 my_page.age의 4바이트를 모두 1로 채움:
    • 메모리 값: 0xFFFFFFFF.

3. 메모리 상태

메모리 레이아웃

입력을 아래와 같이 하면:

  1. my_page.name: aaaaaaaaaaaaaaa\x11
  2. my_page.age: 4294967295 (0xFFFFFFFF)
  3. flag_buf: /flag 파일의 내용.

메모리 상태는 다음과 같습니다:

주소 내용

0xffffd4b0 aaaaaaaaaaaaaaa\x11
0xffffd4c0 0xFFFFFFFF
0xffffd4c4 /flag 내용

4. 출력 결과

출력 흐름

  1. printf("Name: %s\n", my_page.name):
    • %s는 널 바이트를 만날 때까지 읽으므로:
      • my_page.name → my_page.age → flag_buf를 모두 읽음.

출력 결과

입력된 16바이트 + 4294967295(4바이트) + flag_buf 내용을 한꺼번에 출력.


5. 코드 상의 문제점

  1. 널 바이트 미보장:
    • my_page.name에서 널 종료가 없어 이후 메모리를 읽음.
  2. 입력 검증 부족:
    • my_page.age에 널 바이트 없는 값을 채워 악용 가능.
  3. 메모리 보호 부족:
    • 변수 간 메모리가 인접하여 잘못된 데이터 출력 가능.

6. 결론

**4294967295**는 **my_page.age**에 4바이트를 꽉 채우고 널 바이트 없이 데이터 출력을 확장하기에 적합한 값입니다. 이를 통해 flag_buf의 내용을 출력할 수 있습니다. 😊

 

 


 

 

 


왜 그럼 4294967295게 아니라 286331153을 채우는거야?

 

 

4294967295(0xFFFFFFFF) 대신 **286331153(0x11111111)**를 사용하는 이유는 특정 입력 패턴을 통해 익스플로잇을 더 쉽게 제어하거나 출력 데이터를 구분하기 위함일 가능성이 큽니다. 자세히 설명하겠습니다.


1. 숫자 선택의 이유

1-1. 4294967295(0xFFFFFFFF)의 특징

  • 모든 비트가 1로 설정된 값.
  • 널 바이트(0x00)가 없어서 출력 범위를 확장하기에 적합.
  • 하지만, 출력된 데이터를 해석하기 어려울 수 있음.
    • 예: 출력이 모두 ÿÿÿÿ(ASCII 값 0xFF)처럼 보임.

1-2. 286331153(0x11111111)의 특징

  • 비트 패턴이 **0001 0001 0001 0001 ...**처럼 반복.
  • 출력 시 !!!!(ASCII 값 0x11)로 표시되어, 익스플로잇의 결과를 시각적으로 쉽게 확인 가능.
  • 디버깅 및 출력 확인에 유리.

2. 사용자의 목표

2-1. 출력 범위 확장

  • printf("Name: %s\n", my_page.name);에서 %s는 널 바이트를 만날 때까지 메모리를 읽습니다.
  • my_page.age 값을 널 바이트 없는 값(0xFFFFFFFF 또는 0x11111111)으로 설정하면, 다음 메모리 영역(flag_buf)까지 읽히도록 조작 가능합니다.

2-2. 데이터 확인 용이성

  • 0x11111111은 사람이 쉽게 구분할 수 있는 출력(!!!!)을 제공.
  • 디버깅 중 데이터를 시각적으로 확인하기 쉽게 하려는 의도가 있습니다.

3. 익스플로잇 동작 비교

4294967295(0xFFFFFFFF)를 사용하는 경우

  • my_page.age 값: ÿÿÿÿ (ASCII 값 0xFF).
  • 출력 데이터가 모두 동일한 값으로 채워져, flag_buf를 구분하기 어려울 수 있음.

286331153(0x11111111)를 사용하는 경우

  • my_page.age 값: !!!! (ASCII 값 0x11).
  • 출력 결과에서 my_page.age와 flag_buf의 경계를 쉽게 확인 가능.

4. 선택된 값의 의미

  • 0xFFFFFFFF: 가장 단순하고 확실하게 널 바이트 없는 값.
  • 0x11111111: 디버깅, 출력 확인, 익스플로잇 성공 여부를 시각적으로 명확히 하려는 의도.

5. 결론

  • 286331153(0x11111111)을 사용하는 이유는 출력 데이터를 더 쉽게 확인하고 익스플로잇을 디버깅하기 용이하기 때문입니다.
  • **4294967295(0xFFFFFFFF)**를 사용하는 것도 가능하지만, 데이터 시각화에서 불편할 수 있습니다.

궁금한 점이 더 있다면 말씀해주세요! 😊

 

 


 

 

 

ax16개 널 종료 없는거

286331153으로 널 종료 없는거 0x11111111  이진수로 10001000100010001000100010001

하면 flag_buf까지 이어서 읽어져서 flag가 출력된다