오늘은 lotto 문제이다. 사실 원래는 input을 풀려고 하다가 일단 lotto를 먼저 풀었다.

문제 접속 정보는 아래와 같다.

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

문제에 접속하면 3개의 파일을 확인할 수 있다.

-r–r—– 1 lotto_pwn root 55 Feb 18 2015 flag
-r-sr-x— 1 lotto_pwn lotto 13081 Feb 18 2015 lotto
-r–r–r– 1 root root 1713 Feb 18 2015 lotto.c

다른 문제들이랑 동일하게 실행파일, 소스코드, flag 파일이 있다.

lotto.c의 내용은 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <fcntl.h>


unsigned char submit[6];

void play(){
	
	int i;
	printf("Submit your 6 lotto bytes : ");
	fflush(stdout);

	int r;
	r = read(0, submit, 6);

	printf("Lotto Start!\n");
	//sleep(1);

	// generate lotto numbers
	int fd = open("/dev/urandom", O_RDONLY);
	if(fd==-1){
		printf("error. tell admin\n");
		exit(-1);
	}
	unsigned char lotto[6];
	if(read(fd, lotto, 6) != 6){
		printf("error2. tell admin\n");
		exit(-1);
	}
	for(i=0; i<6; i++){
		lotto[i] = (lotto[i] % 45) + 1;		// 1 ~ 45
	}
	close(fd);
	
	// calculate lotto score
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}

	// win!
	if(match == 6){
		system("/bin/cat flag");
	}
	else{
		printf("bad luck...\n");
	}

}

void help(){
	printf("- nLotto Rule -\n");
	printf("nlotto is consisted with 6 random natural numbers less than 46\n");
	printf("your goal is to match lotto numbers as many as you can\n");
	printf("if you win lottery for *1st place*, you will get reward\n");
	printf("for more details, follow the link below\n");
	printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
	printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

int main(int argc, char* argv[]){

	// menu
	unsigned int menu;

	while(1){

		printf("- Select Menu -\n");
		printf("1. Play Lotto\n");
		printf("2. Help\n");
		printf("3. Exit\n");

		scanf("%d", &menu);

		switch(menu){
			case 1:
				play();
				break;
			case 2:
				help();
				break;
			case 3:
				printf("bye\n");
				return 0;
			default:
				printf("invalid menu\n");
				break;
		}
	}
	return 0;
}

먼저 프로그램을 실행하면 메뉴가 3개가 있다. 1을 선택하면 로또 맞추기 게임을 할 수 있고, 2를 선택하면 문제 풀이 방법을, 3을 선택하면 프로그램을 종료시킨다. 2번을 선택했을 때 호출하는 help() 함수는 printf()로 문자열만을 출력한다. 그래서 1을 선택했을 때 호출하는 play() 함수를 살펴보았다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void play(){
	
	int i;
	printf("Submit your 6 lotto bytes : ");
	fflush(stdout);

	int r;
	r = read(0, submit, 6);

	printf("Lotto Start!\n");
	//sleep(1);

	// generate lotto numbers
	int fd = open("/dev/urandom", O_RDONLY);
	if(fd==-1){
		printf("error. tell admin\n");
		exit(-1);
	}
	unsigned char lotto[6];
	if(read(fd, lotto, 6) != 6){
		printf("error2. tell admin\n");
		exit(-1);
	}
	for(i=0; i<6; i++){
		lotto[i] = (lotto[i] % 45) + 1;		// 1 ~ 45
	}
	close(fd);
	
	// calculate lotto score
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}

	// win!
	if(match == 6){
		system("/bin/cat flag");
	}
	else{
		printf("bad luck...\n");
	}

}

먼저 read() 함수를 통해 표준 입력으로 6 byte를 입력 받아 submit에 저장한다. 이 때, submit 변수는 char 형 변수이기 때문에 문자 형태로 저장된다.

이 후 fd를 저장한 후 해당 fd에서 다시 6 byte를 입력 받아 lotto 변수에 저장한다. lotto 변수 또한 char 형 이며, 1 ~ 45 사이의 값을 가지도록 하기 위해 (lotto[i] % 45) + 1의 수식을 통해 연산을 한다.

마지막으로 만약 submitlotto의 각 자리를 비교 해 값이 같다면 match++을 하고, 이 match의 값이 6이라면 /bin/cat flag를 실행시켜 플래그를 읽을 수 있게 된다.\

난 처음에 fd 부분에 초점을 맞췄었다. 그런데 테스트 해 본 결과 fd의 값은 3이 나왔는데, 어떻게 해도 fd3일 때 입력 줄 수 있는 방법을 찾을 수가 없었다. ㅠㅠ 그래서 다시 lottosubmit을 비교하는 부분을 살펴봤는데, 내가 바보였다… 소스코드를 다시 살펴보면 다음과 같다.

1
2
3
4
5
6
7
8
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}

for 문을 2번 실행하는데, 이 때 비교하는 부분을 예외처리를 하지 않아 반복문이 무조건 36번을 돌게 되어있다. 그렇다면 같은 값으로 6 byte를 입력하고, 만약 lotto에 저장된 값이 하나라도 내가 입력 한 문자와 같다면 match가 6이 되어 문제를 풀 수 있을 것이다.

한 가지 더 생각해야하는 부분이 있다. submitlotto 변수는 char 형 변수이다. 때문에 숫자를 입력하더라도 char형으로 저장되기 때문에 0 ~ 9 사이의 숫자를 입력한다면 이 값은 각각 아스키코드 값으로 48 ~ 57이 되기 때문에 lotto의 범위인 1 ~ 45 사이에 값이 되지 않는다. 따라서 아스키코드의 10진수 값 중 1 ~ 45 사이의 문자 중 출력 가능 한 문자를 찾아 submit의 값으로 입력 해 주어야 lotto와 비교를 했을 때 같은 값이 나올 수 있다.

33 ~ 45까지가 출력 가능하며 문자로는 !"#$%&'()*+,-이 된다. 그래서 이 문자 중 아무거나 한개를 정해서 6개를 계속 입력 해 보았다. 그러면 언젠가는 lotto에 저장된 값 중 한개가 내가 입력 한 값과 동일하게 될 것이고, 그렇다면 36번의 반복문에 의해 match의 값이 6이 되며 문제를 풀 수 있을 것이다. 나는 #을 선택 해 입력 해 보았다. 그 결과 무려 13번 만에 플래그를 얻을 수 있었다.

1
2
3
4
5
6
7
8
9
lotto@ubuntu:~$ ./lotto
- Select Menu -
1. Play Lotto
2. Help
3. Exit
1
Submit your 6 lotto bytes : ######
Lotto Start!
sorry mom... I FORGOT to check duplicate numbers... :(

1
FLAG : sorry mom... I FORGOT to check duplicate numbers... :(