본문 바로가기

포너블

[포너블/pwnable.kr] input

input2@pwnable:~$ ls
flag input input.c

ls로 파일을 열어 보면 3개의 파일이 보인다.

cat으로 열어 본 input.c는 다음과 같다.

#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;
}

main

  1. stage 1 ~ 5까지의 문제들이 보인다.
  2. stage5까지 전부 풀면 flag 출력

stage1부터 천천히 보자


stage 1

// 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");
  1. argc가 100이 아니면 return 0
  2. argv[’A’], 즉 argv[65]의 값이 \x00이 아니면 return 0
  3. argv[’B’], 즉 argv[66]의 값이 \x20\x0a\x0d이 아니면 return 0

처음엔 collision 문제에서 했던 것처럼 python을 이용해 넣어 주려고 했다.

 

 

하지만 이렇게 한 줄로 넣으면 그저 문자열 하나로 들어가서 실패…

끈기의 한국인답게 100개를 적어 보기도 했다.

 

 

… 당연히 될 줄 알았는데 안 돼서 당황했지만, argv를 넣어 줄 다른 방법을 찾아 보았다.

찾아보던 중 pwntools라는 걸 써 보기로 했다.

vs code로 python 파일을 하나 만들어서 코드를 만들었다.

 

from pwn import *

argv1 = ["a" for i in  range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"

p = process(executable= "/input",argv= argv1)

 

하지만 input 파일의 경로를 어떻게 찾아야 할지 감이 잡히지 않았다…

로컬에서 서버로 어떻게 접근하지?? 라는 의문이 들 때 tmp 파일에 다른 파일을 생성할 수 있다는 정보를 들었다.

mkdir로 tmp 파일에 내 파일을 하나 만들고, 그 안에 python 파일을 만들었다.

서버에서 파이썬 파일을 만드니 금방 경로를 찾을 수 있었다.

 

from pwn import *

argv1 = ["a" for i in  range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"

p = process(executable= "/home/input2/input",argv= argv1)

p.interactive()

결과


 

stage 2

// 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");

 

처음 포너블을 풀 때 봤던 파일 디스크립터를 이용한 문제이다. 0은 표준 입력을, 2는 표준 에러를 의미한다.

그럼 간단하게 표준 입력과 표준 에러를 입력해 주면 되는 것이다!

 

 

fd 문제를 풀 때 read(2, but, …)처럼 2가 들어가도 입력을 받았던 것이 생각나서 똑같이 보내 주었다. 하지만 clear가 안 되는 걸 보니 이렇게 푸는 게 아닌 것 같다.

 

 

process 함수에 대해 찾아 볼 때 표준 에러를 넣어 줄 수 있다는 것을 알게 되었다. 또한 표준 에러는 파일 형식으로 넣어 줘야 한다고 한다.

 

from pwn import *

argv1 = ["a" for i in  range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"

stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()

p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'))
a = "\x00\x0a\x00\xff"
p.send(a)

p.interactive()

 

그래서 dummy.txt 파일을 만들어 안에 값을 넣어 주었다. 그리고 process 함수 인자로 stderr를 넘겨 주면 stage 2 clear!

결과


 

stage 3

// env
        if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
        printf("Stage 3 clear!\n");

 

stage1, 2를 풀고 넘어오니 stage 3, 4는 금방 풀 수 있었다.

stderr를 process함수의 인자로 넘겨 준 것처럼 env도 인자로 넘겨 줄 수 있다고 한다.

이때 env는 딕셔너리 형식으로 맞춰 줘야 한다!

따라서 \xde\xad\xbe\xef에 대응되는 곳에 \xca\xfe\xba\xbe를 넣어 주고, process의 인자로 env를 넘겨 주면 된다.

 

from pwn import *

_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"

argv1 = ["a" for i in  range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"

stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()

p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)

p.interactive()

결과


 

stage 4

// 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");

 

stage4도 상당히 간단하다.

/x0a라는 파일을 열고, 읽었을 때 \x00\x00\x00\x00의 값을 가지면 된다.

따라서 /x0a 파일을 만들고 그 안에 \x00\x00\x00\x00를 넣어 주면 된다.

 

from pwn import *

_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"

argv1 = ["a" for i in  range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"

stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()

c = open("\x0a", 'w')
c.write("\x00\x00\x00\x00")
c.close()

p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)

p.interactive()

결과


 

stage5

// network
        int sd, cd;
        struct sockaddr_in saddr, caddr;
        sd = socket(AF_INET, SOCK_STREAM, 0);
        if(sd == -1){
                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");

 

소켓에 관한 자세한 설명은 생략하고, 코드 분석부터 해 보자

  1. socket 함수를 통해 소켓을 생성하여 반환한다.
  2. 포트 번호는 argv[’C’], 즉 argv 67번째 인자 값으로 한다.
  3. bind 함수를 통해 소켓에 IP 주소와 포트 번호를 지정한다.
  4. 클라이언트와 연결되면 buf에 데이터를 수신한다.
  5. 수신한 데이터가 \xde\xad\xbe\xef와 같다면 클리어.

 

stage 5는 원격 서버에 접속해야 하므로 process 함수가 아닌 remote 함수를 사용해 접속해야 한다.

argv[’C’]에 포트 번호를 넣고, 동일한 포트 번호로 접속해 \xde\xad\xbe\xef의 값을 넘겨 주기만 하면 된다!

 

from pwn import *

_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"

argv1 = ["a" for i in  range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
argv1[67] = '23132'

stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()

c = open("\x0a", 'w')
c.write("\x00\x00\x00\x00")
c.close()

p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)

m = remote("127.0.0.1", 23132)
qq = "\xde\xad\xbe\xef"
m.send(qq)

p.interactive()

결과

 

하지만 이렇게만 하면 flag를 얻지 못한 상태로 종료된다.


 

flag

// here's your flag
        system("/bin/cat flag");
        return 0;

그 이유는 flag 파일이 현재 경로인 /tmp/hwannow에 없기 때문이다

 

Symbolinc Link

원본 파일을 가리키도록 링크를 연결 시키는 것.

심볼릭 링크를 이용해 home/input2/flag에 있는 파일을 코드에 있는 flag에서 접근할 수 있도록 하였다.

 

ln -s /home/input2/flag flag 명령어를 사용하여 현재 경로에 flag를 생성한 후 실행 시켜 주면, flag까지 나오며 문제를 해결해 줄 수 있다.

 

최종 코드

from pwn import *

_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"

argv1 = ["a" for i in  range(100)]
argv1[65]  "\x00"
argv1[66] = "\x20\x0a\x0d"
argv1[67] = '23132'

stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()

c = open("\x0a", 'w')
c.write("\x00\x00\x00\x00")
c.close()

p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)

m = remote("127.0.0.1", 23132)
qq = "\xde\xad\xbe\xef"
m.send(qq)

p.interactive()

'포너블' 카테고리의 다른 글

[포너블/pwnable.kr] shellshock  (1) 2023.09.02
[포너블/pwnable.kr] blukat  (0) 2023.08.30
[포너블/pwnable.kr] cmd2  (0) 2023.08.30
[포너블/pwnable.kr] cmd1  (0) 2023.08.30
[포너블/pwnable.kr] passcode  (0) 2023.08.30