[Pwnable.kr Prob] input

코코·2023년 2월 5일
0

Pwnable.kr

목록 보기
7/10

Pwnable.kr input 문제 풀이

이번에는 input이라는 문제를 풀어보려한다.
어려웠었다..😂 특히 fd부분.. fd의 2가 표준 에러인지 알고 있었지만 stderr에 문제에서 요구하는 Hex값을 어떻게 넣어줘야할지 생각나지 않았다....

접속 후 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;
}

소스코드는 위와 같다.. 차근차근 살펴보자.

Stage1

먼저 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")

첫 번째 IF문을 보면, argc가 100이 아니면 return 시키고 있다. argc는 main 함수에 전달되는 인자의 개수를 의미한다! 쨌든 해당 값이 100이 아니면, 프로그램이 종료된다.
두 번째 IF문을 보면, argv['A']의 값이 "\x00"이 아니면, 프로그램이 종료된다.
세 번째 IF문은 argv['B']의 값이 "\x20\x0a\x0d"가 아니면, 프로그램이 종료된다.

int strcmp(const char *string1, const char *string2);

strcmp 함수의 원형은 위와 같으면, string1과 string2가 같을 경우 0을 리턴한다.

위의 조건들을 고려하여, 익스 코드를 짜보자!

argvs = []

for i in range(0, 100):
    argvs.append("0")

# Stage 1
argvs[65] = b"\x00" # ord('A')
argvs[66] = b"\x20\x0a\x0d"

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

pwntools의 경우, process 함수의 argv 매개변수를 이용하여 argvs를 전달할 수있다.(argv는 List 형태로 입력받는다!)

process함수의 자세한 옵션을 알고 싶다면, 아래의 링크를 클릭하자!
👉 Pwntools process

Stage2

다음으로는 Stage 2의 stdio 부분이다. 여기서 두 번째 read 함수에서 정신을 못차렸다.. 헤롱헤롱😵‍💫

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

우선 첫 번째 read함수는 간단했다. fd의 0은 stdin으로, 사용자의 입력값을 받겠다는 의미라서 pwntools의 send 함수를 통해 "\x00\x0a\x00\xff"을 전송해주면 되는 것이다.
문제는 두 번째 read함수이다 ! fd의 2는 stderr으로, 표준 에러를 통해 해당 문자열을 받겠다는 것인데... 처음에는 Error를 저장하는 파일을 찾으려했지만 표준 오류의 경우 따로 저장하지 않고, 바로 출력해준다고 한다..

여기서 막혀서, write up을 참고하였다...

마찬가지로 pwntools의 process 함수를 자세히 살펴봐야한다.
process 함수의 stderr 매개변수를 살펴보면, 사용할 파일 개체 or fd를 지정할 수 있다.
따라서 "\x00\x0a\x00\xff" 내용을 가진 파일을 생성하여, stderr의 인자로 전달해주면 될 것이다! python의 open 함수를 이용하여, 해당 파일을 생성하자! (참고로 input2 계정의 경우, /tmp 디렉터리 안에서만 파일을 생성할 수 있음)

Stage2의 익스코드를 짜면 아래와 같다.

with open("/tmp/stderr", 'a') as f:
    f.write("\x00\x0a\x02\xff")
    
p = process(executable = "/home/input2/input", argv=argvs, stderr=open('/tmp/stderr'))

p.sendline('\x00\x0a\x00\xff') # Keyboard input OK

또한 해당 파일("/tmp/stderr")이 존재하지 않으면, 파일을 새로 생성하여 내용을 작성한다.
위의 방법이 아니어도, 아래의 명령을 통해 해당 내용을 가진 파일을 생성할 수 있다.

python -c 'print "\x00\x0a\x02\xff"' > stderr

※ pwntools의 stderr(int)은 파일 디스크립터를 값으로 받고, Python의 open 함수는 파일을 open하면 해당 파일 객체를 반환한다.


Stage3

다음은 Stage3의 env이다!

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

코드를 굉장히 간단하다!
getenv 함수를 통해 환경 변수를 가져오는 함수이다. 인자로 환경 변수명을 넣으면 해당 환경 변수의 값을 리턴해준다.
환경 변수의명을 "\xde\xad\xbe\xef", 해당 환경 변수의 값을 "\xca\xfe\xba\xbe"로 설정하면 Stage 3도 Clear!!!(=Pwntools의 process 함수 활용)

Stage4

다음으로 Stage4로 이동하자!

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"라는 파일명을 가진 파일을 fopen 함수를 통해 Open한다.
첫 번째 if문에서는 해당 파일을 제대로 불러오지 못하면 프로그램이 종료된다.
두 번째 if문의 경우, 해당 파일에서 4byte를 읽어와 buf에 저장한다. fread가 4byte를 정상적으로 읽어올 경우, 4를 Return하게 된다.
마지막 if문은 buf에 저장한 4byte와 "\x00\x00\x00\x00"을 비교한다. 같지않으면, 프로그램이 종료된다.

즉, "\x0"라는 파일명을 생성하고, 해당 파일안에 "\x00\x00\x00\x00"을 저장하면 Stage 4를 클리어할 수 있다...!

Stage5

마지막으로 Stage 5로 이동하자!

int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
// 소켓을 생성하기 위한 함수, AF_INET는 IPv4주소 체계를 사용하겠다는 뜻이고,
// SOCK_STREAM은 입/출력을 Stream 방식으로 데이터를 주고 받겠다는 뜻이다.


if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
}
// socket을 생성하지 못할 경우, 뜨는 Error

saddr.sin_family = AF_INET;
// AF_INET, IPv4 주소 체계 사용
saddr.sin_addr.s_addr = INADDR_ANY;
// INADDR_ANY, 서버의 IP를 자동으로 찾아서 할당
saddr.sin_port = htons( atoi(argv['C']) );
// argv['C']로 받은 문자열을 숫자로 변환하여(atoi), sin_port에 넣어줌.
// socket 통신 시 사용할 port 지정!


if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
        return 1;
}
// bind 함수를 사용하여, socket에 필요한 정보를 할당 & 커널에 등록

listen(sd, 1);
// 백로그 큐를 1로 설정

int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
// accept 함수로 클라이언트의 접속을 받음.

if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
// 연결된 socket에서 4byte만큼의 데이터를 받아서, buf에 저장.
// recv 함수의 경우, 성공적으로 수신한 데이터의 개수를 return.

if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
// 수신한 데이터를 buf에 저장하고, "\xde\xad\xbe\xef"와 비교. 일치하지 않으면 프로그램이 종료됨.

printf("Stage 5 clear!\n");

Stage 5는 Socket 통신에 관한 예제이다!
생각보다 코드가 길다... 코드에 대한 내용은 주석처리로 GoGo~
Stage 5는 클라이언트 측 Socket Code를 작성하여 "\xde\xad\xbe\xef"를 전송하면 해결할 수 있을 것이다...!

Exploit Code

from pwn import *
from socket import AF_INET, SOCK_STREAM
from socket import *


context.log_level = 'debug'

argvs = []

for i in range(0, 100):
    argvs.append("0")

# Stage 1
argvs[65] = b"\x00" # ord('A')
argvs[66] = b"\x20\x0a\x0d"
 
# Stage 2
# python -c 'print "\x00\x0a\x02\xff"' > stderr
with open("/tmp/stderr", 'a') as f:
    f.write("\x00\x0a\x02\xff")

# Stage 3
envs = {b"\xde\xad\xbe\xef" : b"\xca\xfe\xba\xbe"}

# Stage 4
# python -c 'print "\x00\x00\x00\x00"' > '\x0a'
with open("\x0a", 'a') as f:
    f.write("\x00\x00\x00\x00")

# Stage 5
argvs[67] = "60102" 

p = process(executable = "/home/input2/input", argv=argvs, stderr=open('/tmp/stderr'), env=envs)

p.recvuntil(b"Stage 1 clear!")
p.sendline('\x00\x0a\x00\xff') # Keyboard input OK
p.recvuntil(b"Stage 2 clear!")
p.recvuntil(b"Stage 3 clear!")
p.recvuntil(b"Stage 4 clear!")

SERVER_IP = '127.0.0.1'
SERVER_PORT = int(argvs[67])
SIZE = 1024
SERVER_ADDR = (SERVER_IP, SERVER_PORT)

payload = b"\xde\xad\xbe\xef"
csock = socket(AF_INET, SOCK_STREAM)
csock.connect(SERVER_ADDR)
print("[*] Connected!")

csock.send(payload)
csock.close()

p.recvuntil(b"Stage 5 clear!")
p.recvline()

p.interactive()

Exploit Code를 Server로 옮겨주자
scp -P 2222 ./ex.py input2@pwnable.kr:/tmp/kk/ex.py

마지막으로 ln -s 명령어를 통해 flag파일을 Symbolic Link로 생성해준다!

🚩 Flag 획득 !

다음에 Socket 통신에 대해 자세하게 살펴봐야겠다....

※ 참고

profile
화이팅!

0개의 댓글