본문으로 바로가기

pwnable.kr --- fd




ssh fd@pwnable.kr 2222 (passwd: guest)로 접속하면 fd 문제를 만날 수 있다.


windows 환경의 cmd로 접속한것이 아니라 xshell5를 통해 접속했다.


windows 환경의 cmd로 접속하려면 ssh fd@pwnable.kr -p2222 이와 같이 접속 해야한다.



우선 현재 디렉터리에 어떤 파일이 있는지 확인해보자.





우선 우리는 flag를 알아야 하는데 현재 id가 fd이기 때문에 flag를 볼 수 있는 권한이 없다.


fd라는 실행파일이 존재하고 setuid가 fd_pwn의 권한으로 걸려있다.


fd 파일을 통해서 fd_pwn의 권한을 얻고 flag를 보면 될것 같은 느낌이 든다.


fd.c를 가지고 fd elf 파일을 만들었을 확률이 높으므로 fd.c를 분석해보자.





전역변수 buf를 선언한다.



1
char buf[32];                                                                        
cs



인자가 없으면 "pass argv[1] a number\n"를 출력하고 종료한다.



1
2
3
4
if(argc<2) {
    printf("pass argv[1] a number\n");                                                
    return 0;
}
cs



fd 라는 4 byte 정수형 변수에, argv[1]을 아스키 코드로 변경하고 0x1234를 뺀 값을 저장한다.



1
int fd = atoi(argv[1]) - 0x1234;                                                    
cs



4 byte 정수형 변수 len을 0으로 초기화하고, fd가 가리키는 파일을 32 byte 읽어들여 buf에 저장한다.

len에는 읽어들인 byte 수를 저장한다.



1
2
int len = 0;
len = read(fd, buf, 32);                                                            
cs



buf에 저장된 문자열이 "LETMEWIN\n"이면 "good job :)\n"을 출력하고

fd_pwn의 권한으로 flag 파일의 내용을 보여주고 종료한다.



1
2
3
4
5
if(!strcmp("LETMEWIN\n", buf)){                                                        
    printf("good job :)\n");
    system("/bin/cat flag");
    exit(0);
}
cs



"learn about Linux file IO\n"를 출력하고 종료한다.



1
2
printf("lean about Linux file IO\n");                                                
return 0;
cs




fd 문제를 풀기 위해서 File Descriptor와 read 함수에 대해 알아봐야 할 것 같다.



File Descriptor시스템으로부터 할당 받을 파일(소켓)을 대표하는 정수 값으로 생각하면 된다.

시스템이 열려 있는 파일에 접근하기 쉽게 하도록 사용자에게 파일에 번호를 붙여 알려준다고 생각하면 이해하기 쉬울 것이다.


File Descriptor는 최대 1,024까지 생성 가능하다.


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



위와 같이 0,1,2 번이 미리 배정되어 있다.



read() 함수에 대해 알아보자.


read() 함수의 원형은 아래와 같다.


ssize_t read(int fd, void *buf, size_t bytes)


int fd: Data를 전송할 대상을 가리키는 File Descriptor.

void *buf: 파일을 읽어들여 저장 할 buffer이다.

size_n bytes: 읽어 들일 byte의 수.


함수 실행에 성공하면 읽어들인 byte의 수, 함수 실행에 실패하면 -1을 반환한다.



fd를 0으로 설정해, 표준 입력 스트림으로 입력 받은 데이터를 buffer로 옮겨 비교하면 flag를 얻을 수 있을 것이다.


0x1234는 10진수로 4660이다. argument를 4660으로 주어 "LETMEWIN\n"을 입력하면 flag를 얻을 수 있을것이다.






-----------------------------------------------------------

-----------------------------------------------------------


추가적으로 fd가 0, 1, 2에 상관없이 입력을 받는것을 확인했다.

read 함수를 보면 fd가 가리키는 대상을 읽어오는 것이기 때문에 fd가 0,1,2 일 때 모두 stream을 가리키기 때문에 키보드 입력이 가능한것 같다.


이제 궁금증이.. read(fd, buf, 32) 함수를 실행할 때, fd가 가리키는 곳을 32 byte 읽어들여 buf에 저장하는 것이다.

근데 왜.. stream은 비어있는데, 사용자로부터 값을 입력받을까..?

stream을 읽으려고 할때, stream이 비어있으면 사용자로부터 값을 읽어들이는건가...



stdin일 때 read할 경우.. 잘 작동..



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
int main()
{
    char buf[80];
 
    memset(buf, 0x0080);
    if (read(0, buf, 80< 0)                // stdin                                
    {
        perror("read erro : ");
        exit(0);
    }
 
    printf("%s", buf);
}
cs




stdout일 때 read할 경우.. 잘 작동..



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
int main()
{
    char buf[80];
 
    memset(buf, 0x0080);
    if (read(1, buf, 80< 0)                // stdout                                
    {
        perror("read erro : ");
        exit(0);
    }
 
    printf("%s", buf);
}
cs




stderr일 때 read할 경우.. 잘 작동..



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
int main()
{
    char buf[80];
 
    memset(buf, 0x0080);
    if (read(2, buf, 80< 0)                // stderr                                
    {
        perror("read erro : ");
        exit(0);
    }
 
    printf("%s", buf);
}
cs




좀 찾아보니, read 함수가 scanf 함수와 같은 기능을 하는 것 같다.


결국 fd가 0, 1, 2일 때, stream을 가리키기 때문에, 사용자로부터 입력을 받는것 같다....