2020. 3. 8. 03:51ㆍ문제연습/Pwnable.kr (PWN)
티스토리 블로그는 PC 환경에 최적화되어 있습니다.
모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.
이번 시간에는 포너블 kr의 첫 번째 문제인 fd를 풀어보도록 하겠습니다.
문제 내용
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:
ssh fd@pwnable.kr -p2222 (pw:guest)
들어가기 전
What is File Descriptor???
리눅스는 물리적 장치들을 일종의 파일로 관리합니다.
특히 리눅스 셸은 작업에 필요한 파일들을 일종의 번호를 붙여서 관리하는 데 이를 '파일 디스크립터'라고 부릅니다
표준 입출력 장치의 파일 디스크립터는 다음과 같습니다
파일 디스크립터 |
FD 대신 사용하는 이름 |
목적 |
0 |
stdin |
표준 입력 |
1 |
stdout |
표준 출력 |
2 |
stderr |
표준 에러 |
FD에 대한 정확한 정보는 아래 링크를 참고해 주세요.
문제 해설
리눅스 터미널 화면을 열어서 ssh fd@pwnable.kr -p2222로 접속하고 pw인 guest를 입력하여 시작합니다.
ls 명령어를 이용해서 디렉터리 내용을 확인해보니 (터미널 창에 ls 입력 후 엔터) fd fd.c flag 세 가지가 존재합니다.
우선 확장자가. c인 fd.c 파일을 읽어 봅시다. (터미널 창에 cat fd.c를 입력 후 엔터)
리눅스 명령어에 관한 내용은 아래 링크를 이용해 주세요.
다음과 같은 소스코드가 나올 것입니다. (주석은 제가 설명상 편의를 위해 추가하였습니다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){ //1
if(argc<2){ //2
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234; //3
int len = 0;
len = read(fd, buf, 32); //4
if(!strcmp("LETMEWIN\n", buf)){ //5
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
|
1
|
int main(int argc, char* argv[], char* envp[]){ //1
|
//1
main 함수에 argc, ...의 인자가 들어갑니다.
각각의 인자를 간단히 설명드리겠습니다.
argc : main 함수에 전달되는 인자의 개수
argv : main 함수에 전달되는 인자
예를 들어 리눅스 터미널에 아래와 같이 입력했다고 가정하겠습니다.
./fd 1234 ICMP
이렇게 입력하면 인자는 ./fd(파일의 경로), 1234, ICMP 3개가 main 문에 전달됩니다.
그러면 argc = 3, argv[0] = ./fd, argv[1] = "1234", argv[2] = "ICMP"가 됩니다.
아래 그림과 링크를 이용하면 좀 더 자세한 내용을 알 수 있습니다.
다시 코드로 돌아오도록 하겠습니다.
1
2
3
4
|
if(argc<2){ //2
printf("pass argv[1] a number\n");
return 0;
}
|
//2
위 조건문을 다시 해석하자면 main 문에 전달되는 인자의 개수(argc)가 2보다 작으면 pass argv[1] a number을 출력하고 프로그램을 종료해 버립니다.
우리는 flag를 찾는 것이 최종 목표니 프로그램이 종료되지 않으려면 main 인자 개수가 2개 이상이어야겠지요??
다음 포인트로 넘어가 보겠습니다.
1
|
int fd = atoi( argv[1] ) - 0x1234; //3
|
//3
int형 변수인 fd에 값을 저장하고 있습니다. 위 코드에서 보이는 atoi 함수는 문자열을 정수 타입으로 바꿔주는 함수입니다. 예를 들어서 "1234"라는 문자열을 atoi 함수에 넣으면 int형의 데이터인 1234로 바뀝니다.
정리하자면 main 함수에 전달된 2번째 인자를 atoi 함수로 int형으로 자료를 변환하고 16진수인 0x1234를 뺀 값을 fd에 저장합니다.
1
|
len = read(fd, buf, 32); //4
|
//4
이 부분이 fd 문제의 가장 핵심인 부분입니다.
우선 read 함수 인자를 간단히 설명드리도록 하겠습니다.
ssize_t read(X, Y, Z)
X에는 파일 디스크립터 , Y에는 읽은 데이터를 저장할 버퍼를, Z에는 얼마큼 읽을지를 전달해 주는 인자입니다.
파일을 정상적으로 읽는데 성공하면 0보다 큰 수를 리턴하고, 반대로 파일을 읽는데 실패하면 0을 리턴합니다.
예를 들어 fd의 값이 0이면 사용자로부터 표준 입력(키보드)을 받은 데이터를 buf에 저장하게 될 겁니다.
-만약 여기까지 보고 방법이 떠오르셨다면 아래 내용을 보지 말고 한 번 더 다시 풀어보세요!!!
read 함수에 대한 정확한 설명은 아래 링크를 참고해 주세요.
1
2
3
4
5
|
if(!strcmp("LETMEWIN\n", buf)){ //5
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
|
//5
if 조건문에서 buf의 내용이 LETMEWIN\n과 일치하면 아래 문자열을 출력하고 system 함수를 통해서 flag를 출력해 줄 것입니다.
그럼 fd 문제의 공략을 세워보도록 하겠습니다.
1. fd 값을 0으로 만들어 read 함수의 첫 번째 인자인 파일 디스크립터를 0으로 인식하도록 한다.
int fd = atoi( argv[1] ) - 0x1234; //3
0x1234를 10 진수로 바꾸면 4660입니다. main 함수에 전달될 2번째 인자를 "4660"으로 전달하면 fd의 값이 0으로 저장될 겁니다
2. 표준 입력을 이용하여 buf에 LETMEWIN\n을 저장하여 if 문을 만족시킨다.
이건 따로 설명이 필요 없습니다. 그냥 터미널 화면에 그대로 입력하면 됩니다.
3. system 함수를 통해 출력된 flag를 얻는다.
터미널에 입력 후 flag가 출력된 모습입니다.
오늘 내용은 여기까지입니다.
다음 시간에는 collision을 풀어보도록 하겠습니다.
이상 ICMP였습니다!!
'문제연습 > Pwnable.kr (PWN)' 카테고리의 다른 글
5. passcode - Pwnable.kr 해설 (0) | 2020.03.12 |
---|---|
4. flag - Pwnable.kr 해설 (0) | 2020.03.11 |
3. 버퍼 오버플로 (bof) - Pwnable.kr 해설 (0) | 2020.03.09 |
2. 충돌 (collision) - Pwnable.kr 해설 (1) | 2020.03.08 |
Pwnable.kr 준비 (0) | 2020.02.22 |