pwnable.kr (input)

c0wb3ll ㅣ 2020. 12. 5. 00:24

pwnable.kr (input)

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

소스코드가 길다....

 

stage5까지 클리어하고 왜 안되나 writeup을 찾아보니 폰툴을 연습시키기 위한 문제인 듯 싶었다..

실제로 이번 문제를 풀면서 pwntools에는 생각보다 많은 인자를 줄 수 있구나 하고 깨달았다.

 

자 본론으로 가서 소스코드를 보자.

 

5가지의 stage를 클리어하면 보상으로 플래그를 주는 문제인 것 같다.

각각의 스테이지에 주석으로 다음과 같이 쓰여져 있다.

  1. argv

    인자를 주는 방법에 대해서 고민해보라는 것 같다.

  2. stdio

    standard input output으로 fd문제때 공부한 적이 있다.

  3. env

    환경변수에 대한 문제인것 같다.

  4. file

    file read에 대한 문제인 것 같다.

  5. network

    socket 프로그래밍 문제인 듯...?

더 세부적으로 들어가보자.

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

조건은 다음과 같다.

  1. 인자의 갯수는 100개
  2. argv[65] = '\x00'
  3. argv[66] = '\x20\x0a\x0d'

stage1 payload

from pwn import *

stage1 = [str(i) for i in range(100)]
stage1[0] = '/home/input2/input'
stage1[ord('A')] = '\x00'
stage1[ord('B')] = '\x20\x0a\x0d'

p = process(argv=stage1)

pwntools에는 리스트로 인자를 전달할 수 있는 기능이 존재한다.

따라서 다음과 같이 인자를 생성해주었다.

왜 stage1[0] = 'home/input2/input' 이에요?

항상 첫번째 인자는 실행파일이 되기 때문에 첫번째 인자는 실행시킬 파일로 주었다.

stage2

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

stage2는 standard input output 문제이다.

 

read에 첫번째 인자는 fd, file descriptor가 오는데 이것이 0이면 stdio, 2이면 stderr를 의미한다.

따라서 첫번째는 그냥 입력으로 '\x00\x0a\x00\xff'를 주면 되며

두번째는 standard error가 발생하면 읽어올 파일을 지정해주고 그 파일안에 내용이 '\x00\x0a\x02\xff'가 되면 된다.

stage2 payload

from pwn import *

stage2 = '\x00\x0a\x00\xff'
with open('./stderr', 'w') as f:
    f.write('\x00\x0a\x02\xff')

p = process(stderr=open('./stderr'))

마찬가지로 pwntools에 stderr로 지정해줄 수 있는 인자가 있다.

stage3

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

stage3는 환경변수 문제이다.

'\xde\xad\xbe\xef'라는 환경변수가 '\xca\xfe\xba\xbe'라는 값을 가지고 있어야 한다.

stage3 payload

from pwn import *

stage3 = {'\xde\xad\xbe\xef' : '\xca\xfe\xba\xbe'}

p = process(env=stage3)

역시 pwntools에 환경변수 또한 인자로 넘겨줄 수 있다.

환경변수같은 경우에는 딕셔너리 형태로 넘겨주어야 한다.

stage4

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

이번에는 파일 문제이다.

'\x0a'라는 이름이라는 파일을 읽어와 파일 내용이 '\x00\x00\x00\x00' 이면 통과이다.

stage4 payload

from pwn import *

with open('./\x0a', 'w') as f:
    f.write('\x00\x00\x00\x00')

p = process('/home/input2/input')

stage5

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

쪼오오끔 긴 소스코드에 쫄았따.....

중요한 부분만 찾아보면

  1. argv[67]과 같은 값을 포트로 가지고 있어야 한다.
  2. recv 받는 값으로 '\xde\xad\xbe\xef'가 들어와야 한다.

정도가 되겠다.

stage5 payload

stage[ord('C')] = '1337'

stage5 = '\xde\xad\xbe\xef'

stage5_p = remote("localhost", 1337)

stage5_p.sendline(stage5)

그럼 이제 stage5까지 끝났으니 최종 페이로드를 짜보자.

payload

from pwn import *

stage1 = [str(i) for i in range(100)]
stage1[0] = '/home/input2/input'
stage1[ord('A')] = '\x00'
stage1[ord('B')] = '\x20\x0a\x0d'
stage1[ord('C')] = '1337'

stage2 = '\x00\x0a\x00\xff'
with open('./stderr', 'w') as f:
    f.write('\x00\x0a\x02\xff')

stage3 = {'\xde\xad\xbe\xef' : '\xca\xfe\xba\xbe'}

with open('./\x0a', 'w') as f:
    f.write('\x00\x00\x00\x00')

stage5 = '\xde\xad\xbe\xef'

p = process(argv=stage1, stderr=open('./stderr'), env=stage3)

p.sendline(stage2)

stage5_p = remote("localhost", 1337)
stage5_p.sendline(stage5)

p.interactive()

근데 이건 로컬에서 돌릴 수는 없으니 서버에 파이썬 코드를 보내주어야 한다.

하지만 해당 파이썬 코드를 home 디렉토리에 보내려 하면 권한 에러가 난다.

따라서 권한이 있는 폴더를 찾아 /tmp에 c0wb3ll 디렉토리를 만들어 사용하였다.

음...? flag가 안나온다 했더니 생각해보니 현재 디렉토리에 flag가 없다.

그렇다고 홈 디렉토리에서 flag를 가져오기에는 권한이 없다.

따라서 심볼릭 링크를 걸어주기로 하였다.

그럼 다시 페이로드를 날려보자.