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

인스턴스를 돌리니 소스코드와 바이너리를 다운받을 수 있다.
#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가 적용되어 있었다.

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

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

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

%p를 반복하여 유출되는 메모리 주소들을 보았다.
여기서 main, win을 disass 하여 주소를 보았다.


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

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

picoCTF 인스턴스가 만료되어 다른 인스턴스를 실행하여 접속했더니 다른 주소가 나왔었다. 하지만 똑같이 유출된 주소에서 D7을 빼준 값을 입력해주면 플래그가 나오는 것을 알 수 있다.
포너블을 오래간만에 하게되어 정말 아무것도 모르는 상태였는데 해당 문제를 통해 포맷 스트링 버그와 PIE를 복습할 수 있었다.