Post

(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번째 값을 변경해서 processargv인자로 넣어줬습니다. 위 내용을 /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가 나왔습니다.

This post is licensed under CC BY 4.0 by the author.