Pwnable.kr - input

Write-up for input of pwnable.kr
Write-up ref: this blog

0x00 Puzzle

Mom? how can I pass my input to a computer program?

ssh input2@pwnable.kr -p2222 (pw:guest)

This is really a great puzzle to learn and practice Linux input programming skill.
Covered 5 different possible types of inputting to a program.

Beginners like me can learn a lot from this puzzle, especially solve it only with the help of Google but not direct write-ups.

0x01 Explore - Overview

$ ssh input2@pwnable.kr 2222
input2@ubuntu:~$ ls -l
total 24
-r--r----- 1 input2_pwn root      55 Jun 30  2014 flag
-r-sr-x--- 1 input2_pwn input2 13250 Jun 30  2014 input
-rw-r--r-- 1 root       root    1754 Jun 30  2014 input.c

Same as usual, pwn the binary file to cat the flag.

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

There are 5 stages before the /bin/cat flag, seems no other easy way to bypass them.

0x02 Stage1 - argv

// 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");
  • First, we need to pass 100 arguments to the binary.
  • Second, number 65 (=ord('A')) argument must be \x00 (one byte)
  • Third, number 66 (=ord('B')) argument must be \x20\x0a\x0d (three byte)

My very first idea to pass those checking is simply:

./input `python -c "print 'A ' * 64 + '\x00 \x20\x0a\x0d' + ' A'*(99-64-2)"`

But it didn't work, then I printed out the argv and found that \x00 cannot be passed to the program successfully since it represent the end of a string.

Then I tried two ways in python

subprocess.call(['./input', arg1, arg2, ...])
subprocess.call("./input "+payload, shell=True)

Both threw error and said arguments should contain only strings.

So I started google and found the write-up linked at the top of this page, where I learned how to execute binary with arguments in C. There are no such limits in C like python to the arguments.

Here comes the core code for Stage 1:

#include <unistd.h>
char *argv[101] = {[0 ... 99]="A"};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
execve("./input", argv);

Here I learned a new gesture to set value for char* when defined it.

Stage 1 done.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(){
char *argv[101] = {[0 ... 99]="A"};
char *envp[2]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe"};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "81235";
int pipe1[2], pipe2[2];
if(pipe(pipe1)==-1||pipe(pipe2)==-1){
printf("error pipe\n");
exit(1);
}
if(fork() == 0){
dup2(pipe1[0],0);
close(pipe1[0]);
close(pipe1[1]);
dup2(pipe2[0],2);
close(pipe2[0]);
close(pipe2[1]);
execve("./input", argv, envp);
}else{
write(pipe1[1], "\x00\x0a\x00\xff", 4);
write(pipe2[1], "\x00\x0a\x02\xff", 4);

struct sockaddr_in saddr;
int sock = socket(AF_INET, SOCK_STREAM, 0);
memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family = AF_INET;
        //saddr.sin_addr.s_addr = INADDR_ANY;
        saddr.sin_port = htons( atoi(argv['C']) );
        saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sleep(5);
        connect(sock, (struct sockaddr *) &saddr, sizeof(saddr));
send(sock, "\xde\xad\xbe\xef", 4, 0);
close(sock);

}
return 0;
}

(to be continued)