Mommy! what is a file descriptor in Linux?
ssh fd@pwnable.kr -p2222 (pw:guest)
일단 ssh fd@pwnable.kr -p2222
가 주어졌으니 cli 환경에서 저 명령어를 친다. 그리면 password를 치는 부분이 나오는데 guest
를 친다. 그러면 pwnable.kr에 연결이 된다.
문제 파일들을 ls -l
명령어로 보면 이렇다.
파일은 fd.c, fd, flag 이렇게 존재한다. fd.c는 c언어로 코드가 적힌 파일이고, fd는 fd.c의 실행파일일 것이다. 그리고 문제의 답은 flag파일에 적혀 있을 것이다.
fd.c는 권한을 보면 다른 사용자의 읽기 권한이 있다. 그리고 fd 파일은 그룹에 읽기와 실행권한이 있다. fd 파일의 그룹이 fd이기때문에 실행할 수 있다.
flag는 소유자, 그룹에 읽기 권한이 있지만 소유자가 fd_pwn이고, 그룹이 root이기 때문에 flag는 볼 수가 없다.
fd.c 코드는 이렇게 되어 있다.
// vi fd.c
#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;
}
일단 코드 맨 처음은 전처리기가 들어가고, char형의 [32]크기를 가진 buf가 생성된다.
#include <stdio.h> //전처리기
#include <stdlib.h> //전처리기
#include <string.h> //전처리기
char buf[32];
그리고 main에서 입력을 받는다.(argc
, argv[]
)
argc
는 argument count로 전달되는 인수 갯수를 의미한다.
argv[]
는 argument variable로 문자열의 주소를 저장하는 포인터 배열을 의미한다. argv[0]
은 프로그램 실행 경로가 들어가고, argv[1]
부터 사용자가 입력한 argument가 저장이 된다.
envp[]
는 환경변수값을 의미한다.
if문에서 argc
가 2보다 작으면 "pass argv[1] a number"라는 문구가 출력된다. 즉 코드만 실행하면(./fd
) 인수가 하나(./fd
)이기 때문에 저 문구가 출력이 된다.
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
그 다음은 변수 fd
에 atoi
가 실행된 argv[1]
값에 0x1234가 뺀 값이 저장된다. argv[1]
에 인수가 들어올 때는 str
형으로 들어오기 때문에 atoi
를 이용해서 int
형으로 변환시켜줘야 한다.
그리고 변수 len
에 0을 넣고 read의 결과값을 len
에 넣어준다.
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
그런 뒤에 strcmp
을 이용해서 buf
에 있는 값을 "LETMEWIN"이랑 값이 같은지 비교한다. strcmp
는 값이 같으면 0 다르면 크기에 따라 -1 또는 1을 출력한다. if
문에 0이 들어오면 false가 되기 때문에 !
를 넣어서 true로 만들어준다. 즉 buf에 LETMEWIN이 들어있어야 if문으로 들어올 수 있다.
if
문에 들어오면 "good job :)"이란 문구가 출력된 뒤에 flag가 출력이 되고 종료된다.
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
if
문을 지나가면 "learn about Linux file IO"이란 문구갈 출력된 뒤 종료된다.
printf("learn about Linux file IO\n");
return 0;
}
이 문제를 풀기 위해서 fd
, file descriptor(파일 서술자)를 알아본다.
fd이란 시스템으로터부터 할당받은 파일을 대표하는 음수가 아닌 0과 양수인 정수 값이다. 그리고 프로세스에서 열린 파일의 목록을 관리하는 테이블의 인덱스를 의미한다.
흔히 유닉스 시스템에서 모든 것을 파일이라고 한다. 파일부터 디렉토리, 소켓, 파이프 등등 모든 객체들을 파일로써 관리된다. 유닉스 시스템에서는 프로세스가 이 파일들을 접근할 때 fd를 이용하게 된다.
기본적으로 프로세스가 실행 시 0,1,2는 할당이 된다.
정수값 | 이름 |
---|---|
0 | 표준입력(stdin) |
1 | 표준출력(stdout) |
2 | 표준에러(stderr) |
그리고 3번부터는 생성된 파일들이 할당되게 된다.
파일 데이터를 읽는 read
(read from file descriptor)는 3개의 인자를 받는다. 첫번째 fd
를 받고, 두번째는 buf를 받는데 파일에서 읽은 데이터를 저장할 메모리 공간이다. 세번째는 size로 파일에서 읽을 크기를 나타낸다.
#include <unistd.h>
ssize_t read(int fd, void buf[.count], size_t count);
이제 주어진 것을 합쳐보면 argv[1]
값에서 0x1234가 뺀 값이 0이 되게 만들어 준다.(16진수 0x1234는 10진수로 4660이다.) 그러면 fd
이 0이 되면서 표준입력이 되는데 그러면 키보드 입력을 받을 수 있다. "LETMEWIN"입력을 주게 되면 read
에서 buf에 저장이 되고 strcmp
가 true가 되어 if
문 안으로 들어가게 되어 flag를 획득할 수 있다.
$./fd 4660
LETMEWIN
mommy! I think I know what a file descriptor is!!