$ ./minibomb passcode: asdf checking... BOOM!! Further analysis with IDA reveals that the binary has only two functions: {{:writeups:p1.png|}} {{:writeups:p2.png|}} So the flow is the following: * allocate a 0x1000 buffer using mmap * move the stack to this buffer (esp <- buffer + 0xfc4) * read the "passcode" in a buffer on this new stack The program has an obvious buffer overflow: it reads 0x1000 bytes in a buffer of size 16. The difficulty comes from the fact that the stack is not executable (the mmap is done with PROT_READ|PROT_WRITE) and we don't have too many useful gadgets (no libc). == Exploitation == First we use ulimit -s to bypass the ASLR. Then we notice that the program is using old_mmap instead of mmap2. old_mmap takes its parameters from a structure in memory as opposed to mmap2 which uses the registers: {{:writeups:p3.png|}} It would be nice if we could modify the prot parameter from 3 to 7 (PROT_READ|PROT_WRITE|PROT_EXEC). Then we could redo the program from the beginning, but now with an executable stack. While searching for gadgets to accomplish this job, we must not forget that the vdso also consists of executable code (3 functions). We might find something interesting there: 0x4000042f: sbb [ebp+0x5a],0x59; ret 0x40000430: pop ebp; pop edx; pop ecx; ret We can make ebp + 0x5a to point to 0x8049158 (the address of "db 3"), then repeat the sbb until we obtain a value for which the three least significant bits are one, so that mmap sees it as PROT_READ|PROT_WRITE|PROT_EXEC. We find that after three sbb's we get a value of 0xf7, which meets the criterion. Then, after pointing eip to the beginning of the program, we discover we have another problem. Although we now have an executable buffer, the read syscall will fail because the file descriptor was closed in the previous step. To overcome this, let's look at the following gadget: 0x0804813b: xchg eax, ecx ; add al, 0x08 ; mov edx, 0x0000000C ; mov ebx, 0x00000001 ; int 0x80 ; add esp, 0x10; ret We can almost do a syscall using this gadget, but we can't control any of the parameters. However, this is enough for a dup. Since ebx is 1, we'll do the equivalent of dup(1), which duplicates stdout into the first free file descriptor, namely 0. In this way we'll be able to restore stdin. To summarize, this is how the exploit looks like: * stage 1: buffer overflow on read, modify the mmap arg from 0x3 to 0xf7, do the dup(1) call, restart the program. This input will be fed to stdin. * stage 2: buffer overflow on read, exec shellcode. This input will be fed to stdout (Yes, we'll have to run the program with stdout opened for reading) #!/usr/bin/env python # stage 1 from struct import pack p = '' p += pack(' #!/usr/bin/env python # stage 2 from struct import pack p = '' p += pack(' We must also save the original stdin and stdout before running the program, and restore them in the shellcode using dup2, before executing our shell, otherwise we won't be able to interact with the shell. $ ulimit -s unlimited $ exec 4<&0 $ exec 5>&1 $ ./minibomb 0< <(./p1.py) 1< <(./p2.py) sh-4.2$