[드림핵] baby-bof
A A

문제: https://dreamhack.io/wargame/challenges/974

 

baby-bof

Description Simple pwnable 101 challenge Q. What is Return Address? Q. Explain that why BOF is dangerous.

dreamhack.io

 

문제 이름에서 알 수 있듯 버퍼 오버플로우로 푸는 문제이다.

 

📍 bof = Buffer Overflow

버퍼 오버플로우는 프로그램이 버퍼에 할당된 메모리 크기를 초과하여 인접한 메모리를 덮어쓰는 현상을 말한다.

함수의 반환 주소를 overwrite하는 식으로 공격할 수 있다.

 

📍 소스코드

(중요하지 않은 부분은 생략)

void win () 
{
  char flag[100] = {0,};
  int fd;
  puts ("You mustn't be here! It's a vulnerability!");

  fd = open ("./flag", O_RDONLY); // 읽기 전용 모드로 .flag 파일 열기
  read(fd, flag, 0x60); // fd에서 최대 96바이트만큼 읽어와 flag에 저장
  puts(flag); // flag에 저장된 내용 출력
  exit(0);
}

 

win() 함수에서 친절하게 이곳이 취약점이라고 알려준다. 실행하면 flag 파일을 볼 수 있다.

 

 

아래부터는 메인 함수이다.

printf ("the main function doesn't call win function (0x%lx)!\n", win);

 

win 함수의 주소를 알려주고 있다.
0x는 16진수임을 나타내는 표기이고, %lx 포맷스트링은 printf에서 변수를 출력할 때 사용하는 형식 지정자로 llong 또는 long unsigned 타입을, x는 소문자 16진수를 의미한다.

 

C에서는 함수 이름 자체가 그 함수의 시작 주소를 의미하기에, win이라는 함수 이름을 printf에 넘김으로써 win 함수의 메모리 주소를 출력할 수 있는 것이다.

 

printf ("name: ");
scanf ("%15s", name);

 

이때, scanf 함수의 포맷스트링 %s는 문자열을 입력받을 때 사용하는데, 입력 길이를 제한하지 않으며 공백 문자(띄어쓰기, 탭, 개행 문자)가 들어올 때까지 계속 입력을 받기에 오버플로우가 발생할 수 있다.

따라서 %s 형태는 절대로 사용하지 말아야 하며, %\[n\]s처럼 정확히 n개의 문자만 입력받는 형태로 사용해야 한다.

 

 

long idx = 0;
for (idx = 0; idx < 0x10; idx++) {
    printf ("| %lx\t| %16lx\t|\n", name + idx *8, *(long*)(name + idx*8));
}

 

0부터 0x10까지, 총 16번을 실행하는 반복문이다.


첫 번째 %lx에서는 name + idx*8의 주소를 16진수로 출력해 준다.
이때 nameidx*8값을 더해주는 것은 메모리에서 8바이트, 즉 long 타입의 크기만큼 건너뛰어서 보여주기 위해서이다.


두 번째 %16lx에서는 포인터 name + idx*8값을 타입 long *으로 강제 변환시키고 참조해서 첫 번째 포맷스트링에서 보여준 주소에 들어 있는 실제 값을 16진수로 16칸만큼 보여준다.


8바이트씩 16번 반복하므로, name이 있는 메모리 시작 주소부터 총 128바이트를 출력해서 보여주는 함수가 된다.

 

 

그렇다면 한번 s 15개를 입력해 보자.

 

name의 시작 주소인 7ffd35a61f10부터 8바이트씩 메모리 주소를 건너뛰면서 해당 주소에 있는 값을 출력해준다.
첫 번째와 두 번째 줄에 15번 입력되어 있는 73이란 숫자는 내가 입력한 s의 아스키코드 값이다.
여기까지가 name 배열이고, 그 이후부터는 여러 가지 값들이 보인다.

 

이후 메인 함수의 흐름을 더 살펴보자.

printf ("hex value: ");
  scanf ("%lx%c", &value);

  printf ("integer count: ");
  scanf ("%d%c", &count);


  for (idx = 0; idx < count; idx++) {
    *(long*)(name+idx*8) = value;
  }

 

차례로 long 타입의 value 값과 integer 타입의 count 값을 입력받는다.
(%c는 입력 마지막의 개행문자 등을 읽어서 버퍼를 비우기 위한 용도의 포맷스트링)

 

마지막 반복문을 보면, count 값만큼 name의 메모리 시작 주소부터 8바이트씩 건너뛰며, 포인터가 가리키는 주소값에 value 값을 저장한다.

 

따라서, name의 메모리 시작 주소부터 8바이트씩 16번 건너뛰며 스택 프레임의 모든 값에 win()의 주소를 저장해 두면,

main 함수가 종료되어 리턴 주소를 저장해둔 곳으로 돌아갈 때 win 함수가 실행되도록 할 수 있다.

 

📍 익스플로잇

 

첫 줄에서 win 함수의 시작 주소를 알려주고 있다.
이걸 hex value 값으로 입력하고, count 값으로는 16을 입력한다.

 

 

 

 

플래그를 획득했다. 🚩

'𝐖𝐚𝐫𝐠𝐚𝐦𝐞𝐬 > 𝐏𝐰𝐧𝐚𝐛𝐥𝐞' 카테고리의 다른 글

[드림핵] basic_exploitation_003  (0) 2025.03.17
[드림핵] memory_leakage  (0) 2025.03.16
[드림핵] cpp_string  (0) 2025.03.15
[드림핵] Cherry  (0) 2025.03.14
[드림핵] bof  (0) 2025.03.13
Copyright 2024. GRAVITY all rights reserved