picoCTF - PIE TIME 2 Write-up

문제

이렇게 인스턴스를 돌리기 전에는 소스코드와 바이너리를 안준다.

인스턴스를 돌리니 소스코드와 바이너리를 다운받을 수 있다.

소스코드

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

void segfault_handler() {
  printf("Segfault Occurred, incorrect address.\n");
  exit(0);
}

// 함수들을 부르는 함수
void call_functions() {
  char buffer[64]; // 64크기의 char 버퍼
  printf("Enter your name:"); 
  fgets(buffer, 64, stdin); // 64바이트 만큼 사용자 입력 값을 buffe에 할당
  printf(buffer); // 입력 받은 값 그대로 출력. 포맷 스트링 버그 취약점

  unsigned long val;
  printf(" enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val); // 사용자에게 주소값을 입력받음

  void (*foo)(void) = (void (*)())val; // 입력받은 주소값을 foo라는 함수 포인터에 할당
  foo(); // foo 실행
}

// 목표 함수. 이 함수를 실행시켜야 플래그 출력
int win() {
  FILE *fptr;
  char c;

  printf("You won!\n");
  // Open file
  fptr = fopen("flag.txt", "r");
  if (fptr == NULL)
  {
      printf("Cannot open file.\n");
      exit(0);
  }

  // Read contents from file
  c = fgetc(fptr);
  while (c != EOF)
  {
      printf ("%c", c);
      c = fgetc(fptr);
  }

  printf("\n");
  fclose(fptr);
}

int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  call_functions();
  return 0;
}

포맷 스트링 버그 취약점이 발견되었다.

바이너리

해당 바이너리에는 스택 카나리, NX, FULL RELRO, PIE가 적용되어 있었다.

이런식으로 포맷 스트링 버그로 메모리 유출을 할 수 있다.

풀이

main


메인 함수에서는 딱히 브레이크 포인트를 걸게 없었다.

call_functions


사용자의 입력을 받고 해당 값을 출력하고 난 뒤인 call_functions + 85 에 브레이크 포인트를 걸었다. 그 다음

%p를 반복하여 유출되는 메모리 주소들을 보았다.

여기서 main, win을 disass 하여 주소를 보았다.

두 함수의 시작 주소가 55555...로 시작한다는 것을 알 수 있다. 여기서 우리가 유출했었던 주소 중 5555...로 시작한 주소는 19번째 %p에 해당하는 0x555555555441이 있었다.
win의 주소는 0x55555555536a 이므로 유출 주소 - win 주소 = offset을 구할 수 있게 된다.


offset = D7이다.
따라서 우리가 유출한 주소 - D7을 다음 입력에 넣게 되면 우리는 win 함수로 이동하여 플래그를 얻을 수 있을것이다.


picoCTF 인스턴스가 만료되어 다른 인스턴스를 실행하여 접속했더니 다른 주소가 나왔었다. 하지만 똑같이 유출된 주소에서 D7을 빼준 값을 입력해주면 플래그가 나오는 것을 알 수 있다.

정리

포너블을 오래간만에 하게되어 정말 아무것도 모르는 상태였는데 해당 문제를 통해 포맷 스트링 버그와 PIE를 복습할 수 있었다.

profile
𝓗𝓮𝓵𝓵𝓸 𝓥𝓻𝓸

0개의 댓글