Lab 02 - Program Analysis
Supporting files
Lab archive - Password: lab-02
Old location: http://elf.cs.pub.ro/oss/res/labs/lab-02.tar.gz
Exercises
1. Where do they live?
In this exercise you will be validating some truths about executables and processes in linux.
-
Copy the following to a file:
live.c#include <stdio.h> #include <stdlib.h> int iAmGlobalVar = 55; int iAmUninit[100] ; int foo(int a) { int g = 90 * a; static int iAmStatic; printf("address of g, a variable in foo\'s stack frame %p\n", &g); return 0; } int main() { int x = 42; printf("Hello world\n"); printf("address of x, a variable in main\'s stack frame %p\n", &x); x = foo(0); int *m = malloc(10000); printf("an address in the heap: %p\n", m); int *n = malloc(10000); printf("another address in the heap: %p\n", n); printf("foo\'s address: %p\n", foo ); while(1); return 0; } -
Now we will create two versions of the executable. One that uses the Position Indendent Executable (PIE) option and one that does not: Create the no PIE first using the
no-pieoption:clang -o livenopie live.c -
... and now the PIE version the
-pieoption:clang -o live live.c -pie -
Validate that the executables are different using
md5sum:md5sum live livenopie -
Is the
filecommand able to tell the difference between the PIE and non-PIE versions?file live livenopie -
Validate that both executables work, by running each of them. The hex addresses that they print might be a little different, but we will learn why in further steps. Since they have a
while(1);each will stay running until you do aCTRL-C -
The main difference between the PIE and non-PIE versions is that:
- In the non-PIE version, the addresses of the functions are established in the executable and the loader will use the instrutions in exactly those addresses.
- In the PIE version, the addresses of the functions are offsets to be used by the loader once it chooses (perhaps randomly) the base address where it wants to load the text.
- We will validate that difference between PIE/non-PIE in the next section. But first, use
objdumpto determine the address of the functionfooboth in theliveandlivenopiebinaries.
ASLR and PIE
-
Address Space Layout Randomization (ASLR) is a computer security technique which involves randomly positioning the base address of an executable and the position of libraries, heap, and stack, in a process's address space. Linux has this featured turned on by default. Some more information: https://blog.morphisec.com/aslr-what-it-is-and-what-it-isnt/
-
If you had sudo priviledges in your computer you could turn off the ASLR in linux by issuing the following command:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_spaceHowever, since you are a simple mortal in
oscar.ccom.uprrp.eduwe will only be able to switch ASLR on/off as we debug the program.GDB turns off ASLR by default to make the process of debugging easier.
-
With ASLR off, everytime that you run an executable, it will be assigned the same base addresses in virtual memory for the stack, heap, and text (program instructions).
-
using gdb, run
liveseveral times and register the output of the program, e.g. the messages displayed by theprintfin the program -
using, run
livenopieseveral times and register the output of the programMake an observation in your report about what you observed in terms of the addresses.
-
The following two steps will be done in the shell (outside of gdb, where ASLR is ON by default)
-
Run
liveseveral times. What do you notice about the various addresses printed in the program? -
Run
livenopieseveral times. What do you notice about the various addresses printed in the program? Comment about which addresses differ between runs and which stay the same.
-
Another way of monitoring the addresses layout of programs
-
run
./live &outside of gdb. (the&leaves the program executing in the background and lets you know its process ID). Linux allows you to see the memory map of your process by looking at the file/proc/<PID>/maps. Here a sample run in my computer.$ ./live & [2] 1479 Hello world stack 0x7ffe1b272744 stack in foo 0x7ffe1b272724 heap 0: 0x557e4e49a420 heap 1 : 0x557e4e49cb40 foo's address: 0x557e4c688850 $ cat /proc/1479/maps 557e4c688000-557e4c689000 r-xp 00000000 08:02 9863938 etc....To kill the
./liveprocess:kill (process number))(kill 1479in this example) -
In your report, present the output of the map, the output of the program and explain how each address printed by the program corresponds to the correct area in the map. For example, in the previous output the statement
foo's address: 0x557e4c688850can be verified to be an address in the renge557e4c688000-557e4c689000presented in themapfile.
2. stripped
Someone has given us a stripped binary called stripped. Let's run it and give it a brief view:
$ ./stripped
Hello, there!
I am looping, looping, looping, looping, looping,
$ file ./stripped
./stripped: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
The executable file is stripped, so we can't rely on any symbol information to look at it. Take a look at the result of the command nm stripped and validate that it has no symbols.
However, stripped is a small executable, so we can try to reverse engineer it by hand. To do that, answer the following questions:
-
What is the file's entry point? (you may find this using
readelf) -
What instructions get executed from that entry point?
-
After the entry point, the program calls a function at
0x40013a. What does that function do? Reference for the linux syscallsmov eax,0x1 mov edi,0x1 syscall retq -
How would you make the program print "looping" 12 times instead of 5.
-
What other control-flow altering instructions are executed besides
callandret?
3. stripped, re-loaded
Looking more carefully at our stripped binary, we notice that there is one string that it never prints out:
$ strings -t x stripped
154 Hello, there!
162 I am looping,
171 All done!
17c .shstrtab
186 .text
18c .data
The string All done! is at offset 0x171 in the binary, that is equivalent to 0x600171` in the loaded program.
$ objdump -d stripped -M intel
00000000004000b1 <h>:
4000b1: ba 0a 00 00 00 mov edx,0xa
4000b6: 48 be 71 01 60 00 00 movabs rsi,0x600171
4000bd: 00 00 00
4000c0: e8 75 00 00 00 call 0x40013a
4000c5: c3 ret
This means that the function that does the print (0x4000b1) is never reached! Why? The reason is that the program exits before doing that.
Find the call to the exit function that occurs at run-time exactly before this print and manually replace it with NOP instructions using the hex editor of your choice. At the end the program should display the following:
./stripped
Hello, there!
I am looping, looping, looping, looping, looping, looping, looping, looping, looping, looping, looping, looping,
All done!
Note that the program should still exit cleanly!
Hint: the NOP instruction has opcode 0x90, so just replace all the bytes of the offending call instruction with that
4. Smash the Stack
Download level01 from Smash the stack and solve it using gdb. Break on main, step through the execution and figure out what it does and how to crack it.
$ scp level1@io.netgarage.org:/levels/level01 . # Password is level1
5. GDB
Run the bash_login executable. The executable gets input from the user and evaluates it against a static condition.
If it succeeds it then calls a password_accepted function that prints out a success message and spawns a shell.
Your task is to use GDB to force the executable to call the password_accepted function.
Gather as much info about the executable as possible through the techniques you have learned in previous sessions. Think of modifying registers for forcing the executable to call the function (there is more than one way of doing this).
Note: you do NOT need to find out the password for this excercise, just manipulate the program dynamically to make it call the password_accepted function. In your report, comment about your process.
Deliverables
Turn in a writeup through Moodle (online.upr.edu). Make sure to answer the questions asked throughout the instructions.