본문 바로가기
pwnable

[Toddler's Bottle] input

by 미스터 J 2024. 5. 11.
반응형

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

}


input.c 의 코드이다. input을 무작정 실행해 보았다.

stage clear가 있는걸 보니 알맞은 입력값을 맞춰주면 차례차례 if문이 실행되는 구조같다.

 

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

인자의 수가 100이 아닐때, argv['A']=\x00, argv['B']=\x20\0a\x0d, 이 세 가지 조건을 만족하면 스테이지 1이 클리어 된다.

하지만 어떤 인사를 넣고 해도 스테이지 클리어 문구를 보지 못했다.

 

스테이지 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");

read()함수에서 첫번째 인자는 파일 번호이지만 0은 stdin(표준입력), 2는 stderr(표준에러)처리이다.

따라서 buf의 표준입력에는 \x00\x0a\x00\xff를, 표준에러에는 \x00\x0a\x02\xff를 넣어주어야 한다.

하지만 이도 인자를 넣다가 실패했다.

 

스테이지3을 보자

// env

        if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;

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

env -> 환경변수에 대한 문제같다. getenv()함수는 명시된 환경변수를 검색하고, 그 값을 반환한다.

따라서 \xde\xad\xbe\xef의 환경변수값이 \xca\xfe\xba\xbe로 만들어주면 되는 것이다.

 

그 다음 스테이지 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");

fopen()함수에서 첫번째 인자는 파일 이름을 지정한다. \x0a 라는 파일을 "읽기 모드"로 여는 것이다.

fread()함수에 대해서 공부하고 넘어가자면,

size_t fread(void* ptr, size_t size, size_t count, FILE* stream); 형태로서

ptr = size * count 의 크기를 가지는 배열을 가리키는 포인터

size = 읽어들일 원소의 크기로 바이트 단위

count = 읽어들일 원소들의 개수로 각 원소의 코기는 size 바이트

stream = 데이터를 입력받을 스트림의 FILE 객체를 가리키는 포인터

읽어들인 원소의 개수가 size_t 형태로 리턴 된다.

결국, 총 4바이트인 FILE(\x0a)을 1바이트씩 읽어들여 읽은 수를 리턴하는 것이다. 그러므로 읽은 처음 4바이트의 값이 \x00\x00\x00\x00일때 스테이지가 클리어 되는 것이다.

 

마지막으로 스테이지 5를 보자

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

 

스테이지 5는 네트워크 부분인다. 생각보다 아는게 몇개 보이지만 헷갈리는 것들이 많아서 정리를 하고 넘어가야 겠다.

AF_INET = IPv4의 주소 프로토콜 체계를 표현한다.

INADDR_ANY = 서버의 IP주소를 자동으로 찾아서 대입해주는 기능이다. 멀티네트워크 카드 동시지원과 다른 사용자가 해당 코드를 실행할 때 자동으로 지원된다는 점이 있다.

htons() = short intger 데이터(2byte)를 네트워크 byte order로 저장한다.

atoi() = char to int 문자열을 정수 타입으로 변환한다.

 

int bind(itn sockfd, sturck sockaddr *myaddr, soclen_t addrlen); = 소켓에 IP주소와 포트번호 지정 // 0성공, -1 실패

        sockfd = 소켓 디스크립터

        struct sockaddr *myaddr = 주소 정보로 인터넷을 이용하는 AF_INET인지 시스템 내에서 통신하는 AF_UNIX인지에 따라서 달라짐. 전자인 경우에는 struck sockaddr_in 사용

        socklen_t addrlen = myadd 구조체의 크기

 

int listen(int s, int backlog); = 소켓을 통해 클라이언트의 접속 요청을 기다리도록 설정 // 0 성공, -1 실패

          int s = 소켓 디스크립터

          int backlog = 대기 메시지 큐의 개수

 

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); = 클라이언트의 접속 요청을 받아드리고 클라이언트와 통신하는 전용 소켓을 생성 // 성공: 소켓의 파일디스크립터, -1 실패

          sock = 서버 소켓의 파일 디스크립터 전달

          addr = 연결요청 한 클라이언트의 주소정보를 담을 변수의 주소 값 전달

          addrlen = addr로 전달된 주소의 변수 크기를 바이트 단위로 전달 (단, 크기정보를 저장한 다음에 전달)

->accept함수는 연결요청 대기 큐에서 대기중인 클라이언트의 연결요청을 수락하는 기능이다

함수호출이 완료되면 서버와 클라이언트가 데이터를 송수신하는데 사용될 소켓을 리턴한다.

socket으로 생성한 소켓은 문지기 역할을 하고, accept함수로 생성된 소켓이 진짜 데이터를 송수신하는 소켓이다.

 

int recv(int s, void *buf, size_t len, int flags); = 소켓으로부터 데이터를 수신 // -1 실패, 이외는 실제 수신한 바이트 수 

          int s = 소켓 디스크립터

          void *buf = 수신할 버퍼 포인터 데이터

          size_t len = 버퍼의 바이트 단위 크기

          int flags = 아래와 같은 옵션 사용 가능

                   MSG_DONTWAIT = 수신을 위해 대기가 필요하다면 기다리지 않고 -1 반환하면서 바로 복귀

                   MSG_NOSIGNAL = 상대방과 연결이 끊겼을떄, SIGPIPE 시그넣을 받지 않도록 함

 

..... 뭔가 많기도 하다. 해석을 해보면 argv['C'] 가 포트번호의 역할을 한다.

bind로 소켓을 갖추고, listen으로 대기 한 후, accept으로 받을 준비를 한 다음에, recv로 받는다.

후에 buf(받는 IP)와 \xde\xad\xbe\xef(222.173.190.239) 가 일치하면 스테이지 클리어.

 

문제는 input 실행 시, 어떤 인자도 받지 못한다는것. 다음부터는 write-up을 참고 한다.

........................익스 코드를 짜야하는 것이었다!

from pwn import *
#stage1
argvs = [str(i) for i in range(100)]
argvs[ord('A')] = '\x00'
argvs[ord('B')] = '\x20\x0a\x0d'
#stage2
with open('./stderr''a') as f:
    f.write('\x00\x0a\x02\xff')
#stage3
envVal = {'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
#stage4
with open('./\x0a''a') as f:
    f.write('\x00\x00\x00\x00')
#stage5 
argvs[ord('C')] = '40000'(임의 지정)
#인자전달, stderr 파일 열기, 환경변수 설정등
target = process(executable='/home/input2/input', argv=argvs, stderr=open('./stderr'), env=envVal)
#stage2의 stdin
target.sendline('\x00\x0a\x00\xff')
#다시 stage5, 포트가 argv['C']와 일치해야겠죠?
conn = remote('localhost'40000)
conn.send('\xde\xad\xbe\xef')
target.interactive()  

또 모르는 것을 정리해보자......

ord() = 문자의 아스키 코드 값을 돌려주는 함수

with open() = 파일 열고 닫는 것 자동으로 해주는 함수, as 는 약자

envVal = {인자 이름 : 인자 값} pwntools 사용시 변수 설정 가능

process() 를 통해 위의 값을 모두 지정 (pwntools에서 제공하는 기능이므로 나중에 pwntools정리도 필요할 것!)

sendline("전송할 문자열")

우선 인자를 넣어줄 리스트를 생성하고, 각 스테이지 별로 값을 대입해 준다.

스테이지2의 값을 한번더 입력해야하므로 다시 지정해주고, localhost로 연결해서 포트를 임의로 연결한후, 스테이지5의 문자열을 전송해주면 끝!

다만 현재는 코드를 생성할 파일이 없으므로, tmp폴더로 진입힌다.

흠.... ls 명령을 사용할 수 없도록 해놨다. 권한별로 사용할 수 있는 명령어를 지정하다니.... 흥미롭다 나중에 이것도 알아봐야지

이런 식으로 tmp 폴더에 진입할 수 있었다.

하지만 flag를 실행할 수 없었는데 /tmp/mark 폴더에 flag파일이 없어서 그렇다. 그래서 심볼릭 링크를 해줘야 된다고 한다.

 

Mommy! I learned how to pass various input in Linux :)

 

다양한 익스 방법을 한번에 배울 수 있어서 되게 알찬 문제였다. 제대로 된 익스도 처음 짜봐서 아직 뭔가 어색하지만 후에 pwntools을 본격적으로 공부할 수 있을 것이다.

반응형

'pwnable' 카테고리의 다른 글

[Toddler's Bottle] cmd1  (0) 2024.05.11
[Toddler's Bottle] lotto  (0) 2024.05.11
[Toddler's Bottle] blukat  (0) 2024.05.11
[Toddler's Bottle] leg  (0) 2024.05.11
[Toddler's Bottle] asm  (0) 2024.05.11