네트워크 보안/CTF

[pwnable] pwnable.kr fd

uzguns 2024. 9. 11. 09:55

Problem

Mommy! what is a file descriptor in Linux?

* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link:
https://youtu.be/971eZhMHQQw

ssh fd@pwnable.kr -p2222 (pw:guest)


Exploration

먼저 디렉토리를 탐색하면 flag는 fd_pwn 권한을 얻어야 읽을 수 있었다. fd 파일을 통해 fd_pwn 권한을 얻어 flag를 읽는 방향으로 접근하였다.

fd@pwnable:~$ id
uid=1002(fd) gid=1002(fd) groups=1002(fd)

fd@pwnable:~$ ls -lahr
total 40K
drwxr-xr-x   2 root   root 4.0K Oct 23  2016 .pwntools-cache
dr-xr-xr-x   2 root   root 4.0K Dec 19  2016 .irssi
-rw-------   1 root   root  128 Oct 26  2016 .gdb_history
-r--r-----   1 fd_pwn root   50 Jun 11  2014 flag
-rw-r--r--   1 root   root  418 Jun 11  2014 fd.c
-r-sr-x---   1 fd_pwn fd   7.2K Jun 11  2014 fd
d---------   2 root   root 4.0K Jun 12  2014 .bash_history
drwxr-xr-x 116 root   root 4.0K Oct 30  2023 ..
drwxr-x---   5 root   fd   4.0K Aug 31 16:09 .

fd.c 파일을 살펴보면 fd 라는 4 byte 정수형 변수에 argv[1] 을 ASCII 코드로 변환하고 0x1234를 뺀 값을 저장한다.

그리고 fd에 32byte를 읽어 buf 에 저장하고 "LETMEWIN\n" 이라는 문자열과 비교하여 같으면 flag 값을 출력한다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}

 

File descriptor와 read 함수에 대해 알아보면 File descriptor는 system으로부터 할당받을 file(socket)을 식별하는 정수값이다. file 을 열거나 socket을 생성하거나 pipe를 만들 때 os는 process에 file descriptor를 반환한다. 예를 들어 open system call을 사용하여 파일을 열 때 반환되는 값이 file descriptor 이다. file descriptor 는 최대 1024까지 생성 가능하다.

프로세스가 메모리에 올라갈 때 프로세스마다 fd 의 번호가 미리 배정되어있는데 0, 1, 2가 사전 배정되어있다.

 

standard file descriptor

  • 0: stdin
  • 1: stdout
  • 2: stderr

 

read() 함수는 file descriptor로부터 data를 읽어들이는데 사용되는 system call이다. 이 함수는 file, socket, pipe 등 과 같은 다양한 file descriptor로 부터 data를 읽어들일 수 있다. read() 함수의 prototype은 다음과 같다. 

READ(2)                                                                                                 Linux Programmer's Manual                                                                                                READ(2)

NAME
       read - read from a file descriptor

SYNOPSIS
       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

 

  • fd: read 작업을 수행할 file descriptor
  • buf: data를 저장할 buffer의 pointer
  • count: read할 최대 byte 수

 


Solution

fd에서 input stream을 열어 'LETMEWIN\n'  문자열을 입력하면 flag값을 얻을 수있다.

 

file descriptor로 입력을 받으려면 보통 socket과 같은 system call을 통해 i/o stream 을 열어야한다.

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    // TCP 소켓 생성
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // IPv4, TCP 프로토콜
    if (sockfd == -1) {
        perror("socket failed");
        return 1;
    }

    printf("TCP socket created with fd: %d\n", sockfd);
    // 추가적인 소켓 연결 및 데이터 송수신 코드가 필요합니다.
    close(sockfd);  // 소켓 닫기
    return 0;
}

 

#include <unistd.h>
#include <stdio.h>

int main() {
    int fds[2];
    if (pipe(fds) == -1) {
        perror("pipe failed");
        return 1;
    }

    printf("Pipe created with read fd: %d, write fd: %d\n", fds[0], fds[1]);
    // fds[0]: 읽기 끝, fds[1]: 쓰기 끝
    close(fds[0]);
    close(fds[1]);
    return 0;
}

 

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    // 파일 내용을 메모리에 매핑
    void *map = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap failed");
        close(fd);
        return 1;
    }

    printf("Memory mapped at address %p\n", map);
    munmap(map, 4096);
    close(fd);
    return 0;
}

 

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int efd = eventfd(0, 0);  // 초기값 0, 기본 플래그 사용
    if (efd == -1) {
        perror("eventfd failed");
        return 1;
    }

    printf("Eventfd created with fd: %d\n", efd);
    close(efd);
    return 0;
}

 

하지만 코드 상으로는 fd를 여는 부분이 없기 때문에 system 표준 입출력인 0, 1, 2를 제외한 값으로 열었을 경우 유효하지 않은 file descriptor이기 때문에 입력을 받을 수 없다.

 

fd 는 입력값에 0x1234(10진수로 4660)을 뺀 값의 file descriptor를 연다. 따라서 fd 4660을 실행하면 stdin(0) stream을 열 수 있게 된다.

fd@pwnable:~$ ./fd 4659
learn about Linux file IO
fd@pwnable:~$ ./fd 4658
learn about Linux file IO
fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!

fd@pwnable:~$ ./fd 4662
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!
fd@pwnable:~$ ./fd 4661
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!