Hint: Processes run system calls
Use strace
to see the syscalls that are executed
openat(AT_FDCWD, ".hello_there", O_RDONLY) = -1 ENOENT (No such file or directory)
openat
syscall is trying to open the non-existent file .hello_there
.
So we create it so that the code can proceed.
touch .hello_there
Hint: Stand guard, let noone pass
The file .hello_there
is allowed to be written so "the door is unlocked".
With an strace
we can see the check that happens within the code:
openat(AT_FDCWD, ".hello_there", O_WRONLY) = 4
The openat
syscall, tries to open .hello_there with O_WRONLY
flags, and succeeds (returns fd = 4). For the challenge to be solved we need this syscall to fail.
So we can modify the permissions of the file to only allow reading and not writing on it.
chmod -w .hello_there
Hint: Stuck in the dark, help me move on.
By using strace
we see that the last syscall run by the program, is pause()
, along with alarm(10)
. The alarm(10)
will send a SIGALRM after 10 seconds to the app, waking it up by the pause
that has put it into a STOP
.
We need to wake it up before the alarm reaches our process.
One solution is: CTRL-Z
(to suspend the program) and then fg
to bring it back.
Another would be to just send a SIGCONT to the program by using the kill command.
Hint: ltrace
By using ltrace
we observe the usage of the getenv("ANSWER")
function call. This call will retrieve the content of the ANSWER environment variable.
So we just need to fill this up with the answer to life, the universe and everything.
ANSWER=42 ./riddle
Hint: Mirror, mirror on the wall, who in this land is fairest of all?
First-in, First-out reminds us of pipes (FIFO pipe).
But let's investigate it:
-
By using
strace
, we find out that the program tries to open a file with the name "magic_mirror".openat(AT_FDCWD, "magic_mirror", O_RDWR) = -1 ENOENT (No such file or directory)
-
Let's provide the program with the file and see the result:
touch magic_mirror strace ./riddle
The program tries to write to the file a character and then proceeds to read a character from the same file. Only to find nothing.
"I cannot see my reflection"
-
The hint for "reflection" as well as the
read()
from the same file descriptor as thewrite()
, confirms our suspicions for a FIFO pipe.
So... we use the mknod
command to create one with the name "magic_mirror"
mknod magic_mirror p
Hint: when I bang my head against the wall it goes: dup! dup! dup!.
We hear file descriptors... so that involves syscalls, so... let's run an strace
!
We immediatelly stumble upon this line:
fcntl(99, F_GETFD) = -1 EBADF (Bad file descriptor)
This call tries to return the file descriptor's flags. But the file descriptor doesn't exist! So we need to create it somehow...
By searching (and using the hint), we find that the function call we need is dup2
, to duplicate an existing file descriptor to a new one that has the number we want (99).
But, file descriptors are maintained into the process' context. So we need to create the new file descriptor into the same process (or a child of it). For this we'll create a small C program that creates the new fd, uses the dup2
call to duplicate it and assign it to the 99 fd, and uses execve
to start ./riddle
(to substitute the program's code with ./riddle
code).
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (int argc, const char* argv) {
int fd = open(".hello_there", O_RDONLY);
int res = dup2(fd, 99);
char *newargv[] = { NULL };
char *newenviron[] = { NULL };
printf("%d\n", res);
printf("Success\n");
execve("./riddle", newargv, newenviron);
}
We compile and then run this program, and voila! It works ;)
Hint: 'help us play!'
Well... Let's use our good ol' strace
to inspect what is happening...
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fc8402ada10) = 33732
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD[33732] PING!
, child_tidptr=0x7fc8402ada10) = 33733
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 33732
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=33732, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 33733
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=33733, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
Clone, child, SIGCHLD, wait... is our riddle spawning children?
Let's run an ltrace
to investigate it further...
fork() = 33782
fork() = 33783
There is no doubt anymore. But our trusty tools, strace and ltrace don't follow the children's executions. So we'll need to use -f
flag to see what happens inside the children's code.
strace -f ./riddle
The output becomes mingled but after some reading, we see this:
[pid 33905] read(33, <unfinished ...>
...
[pid 33905] <... read resumed>0x7ffefc3575ec, 4) = -1 EBADF (Bad file descriptor)
So the fork with pid 33905, tries to read from the non-existant file descriptor 33.
After some lines, we read this:
[pid 33904] write(34, "\0\0\0\0", 4 <unfinished ...>
...
[pid 33904] <... write resumed>) = -1 EBADF (Bad file descriptor)
So the fork with pid 33904, tries to write from the non-existant file descriptor 34. Let's give them their file descriptor, exactly like how we did it with the previous challenge, just to see what will happen if the write and read syscalls can proceed.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (int argc, const char* argv) {
int fd1 = open(".hello_there", O_RDONLY);
int fd2 = open(".hi_there", O_WRONLY);
dup2(fd1, 33);
dup2(fd2, 34);
char *newargv[] = { NULL };
char *newenviron[] = { NULL };
printf("Success\n");
execve("./riddle", newargv, newenviron);
}
We compile the program and run it with strace -f ./a.out
We observe this:
[pid 34827] read(53, 0x7fffbd1092ac, 4) = -1 EBADF (Bad file descriptor)
Let's add another file descriptor to the mix...
...
int fd3 = open(".placeholder", O_RDONLY);
dup2(fd3, 53);
...
DEAD END. All the syscalls proceed but the challenge still Fails. Let's think...
PING - PONG, [35029] PING!
...
Is that intra-process communication? Maybe! Let's try pipes!
we modify our program above, by substituting all open syscalls with pipe().
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (int argc, const char* argv) {
int fd[2];
pipe(fd);
dup2(fd[0], 33);
dup2(fd[1], 34);
pipe(fd);
dup2(fd[0], 53);
dup2(fd[1], 54);
char *newargv[] = { NULL };
char *newenviron[] = { NULL };
printf("Success\n");
execve("./riddle", newargv, newenviron);
}
We compile and run... And... It works! :D
Hint: 'A rose, by any other name...'
Let's google the hint...
"A rose by any other name would smell as sweet" is a popular reference to William Shakespeare's play Romeo and Juliet, in which Juliet seems to argue that it does not matter that Romeo is from her family's rival house of Montague, that is, that he is named "Montague". The reference is often used to imply that the names of things do not affect what they really are.
Hmm... Let's run our trusty ol' strace
...
We observe those lines...
lstat(".hello_there", {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
lstat(".hey_there", 0x7ffdbda4aa90) = -1 ENOENT (No such file or directory)
Let's give it the file it wants: touch .hey_there
and run strace
again. Seems to kinda work, but we get this error-hint: "Oops. 9613388 != 9613400."
Let's see what those lstat
calls return.
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
Could those numbers (9613388 & 9613400) be inode numbers? This together with the Shakespear's quote, makes us think: Can it ask for a file that has different name (and location) with another, but refer to the same inode number? This is the description of a hard link!
So we need the 2nd file to be a hard link of the first. Let's do it!
ln .hello_there .hey_there
DONE! :D
Hint: Checking footers
Well... you know the drill... strace
!!!
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf00", O_RDONLY) = -1 ENOENT (No such file or directory)
At a first look, it seems obvious what is going wrong. Let's give it the file it wants...
touch bf00
Let's run strace
again:
openat(AT_FDCWD, "bf00", O_RDONLY) = 4
lseek(4, 1073741824, SEEK_SET) = 1073741824
read(4, "", 16) = 0
Here we see that the program proceeds to open the file "bf00", skip the first 1073741824 bytes and then read the next 16 bytes.
But, the read doesn't produce any output because the file is really smaller than 1073741824 + 16 bytes.
So, we need to solve that. Let's create a file that is 1073741824 + 16 bytes long.
To achieve this, we'll create a small program in C:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (int argc, const char* argv) {
int fd = openat(AT_FDCWD, "bf00", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
lseek(fd, 1073741824, SEEK_SET);
write(fd, "XXXXXXXXXXXXXXXXX", 16);
close(fd);
}
We compile this and run it, to create the "bf00" file, and then run strace ./riddle
again.
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "bf01", O_RDONLY) = -1 ENOENT (No such file or directory)
Oops, it wants another file... (After some trials, we find out that it wants 10 files :P), So we modify the program accordingly...
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void createFile(char* filename) {
int fd = openat(AT_FDCWD, filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
lseek(fd, 1073741824, SEEK_SET);
write(fd, "XXXXXXXXXXXXXXXXX", 16);
close(fd);
}
int main (int argc, const char* argv) {
createFile("bf00");
createFile("bf01");
createFile("bf02");
createFile("bf03");
createFile("bf04");
createFile("bf05");
createFile("bf06");
createFile("bf07");
createFile("bf08");
createFile("bf09");
}
Hint: Let me whisper in your ear
Again... strace
connect(4, {sa_family=AF_INET, sin_port=htons(49842), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)
So the program tries to open a socket that connects to 127.0.0.1:49842 (localhost:49842) Because no process is listening on that port, the connection is refused.
So we need to add a small TCP listener on that port. An easy way to achieve this is with nc
.
On a separate terminal we start this: nc -l -p 49842
And then we run ./riddle
.
And... there is contact! ;)
DONE! :D
Hint: Can you read my mind?
Let's strace
the shit out of this...
openat(AT_FDCWD, "secret_number", O_RDWR|O_CREAT|O_TRUNC, 0600) = 4
unlink("secret_number") = 0
write(4, "The number I am thinking of righ"..., 4096) = 4096
close(4)
Let's examine this closely.
- The program opens/creates the file named "secret_number"
- It then "unlinks" - deletes the file
- THEN (!) writes on it "The number I am thinking of right..."
- And it closes the file.
How can the program unlink/delete the file and then write on it? Well...let's pay a visit on unlink's man page.
From the man page, we see two interesting points:
-
unlink() deletes a name from the file system.
So, unlink, doesn't deletes a file, but a name (Remember Challenge 7?)
-
If that name was the last link to a file and no processes have the file open the file is deleted and the space it was using is made available for reuse.
The file associated with this name will only be deleted if that name was the last link to a file and no processes have the file open.
So, flash backs are coming from Challenge 7! If we create a hard link to this file, then unlink will only delete the initial "name" for the file, but not the hard link of it. Let's try this assumption...
touch secret_number
ln secret_number alias_secret_number
If we run the program and while we wait run cat alias_secret_number
, we actually see the correct contents!
DONE! :D
Hint: Can you read my mind?
strace
again...
openat(AT_FDCWD, "secret_number", O_RDWR|O_CREAT|O_TRUNC, 0600) = 4
unlink("secret_number") = 0
fstat(4, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
write(4, "The number I am thinking of righ"..., 4096) = 4096
...
close(4)
Well, isn't this kinda the same? Let's try the same strategy as the previous challenge...
Oops:
You're employing treacherous tricks.
FAIL
We are busted.
Actually, there is an additional syscall to fstat after the unlink call, comparing to the strace's output of the previous challenge.
Let's see what fstat returns:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
number of hard links (nlink_t), that's how we were busted... so we need to employ a different strategy. Let's go back to unlink's man page:
There is another interesting point on the documentation:
If the name was the last link to a file but any processes still have the file open, the file will remain in existence until the last file descriptor referring to it is closed.
So, this gives us a small loophole to maintain the file even after the unlink() and the close() calls. If we could open the same file from another process, then the file would not be deleted until the second process closes the fd too.
So we can create a program that continuously reads the file and outputs the contents written to it on stdout.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (int argc, const char* argv) {
int fd = openat(AT_FDCWD, "secret_number", O_RDWR);
char buf[1000];
int nbytes;
while (1) {
nbytes = read(fd, buf, 16);
if (nbytes > 0) {
printf("%s\n", buf);
}
}
}
We just have to pre-create the file with touch secret_number
, run the above program, execute ./riddle and watch.
DONE!
Hint: 'Do only what is required, nothing more, nothing less'.
I want to find the char 'Y' at 0x7f7c2413906f
I might be repetitive... but... strace
again.
openat(AT_FDCWD, "/tmp/riddle-w2hAKp", O_RDWR|O_CREAT|O_EXCL, 0600) = 4
ftruncate(4, 4096) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, 4, 0) = 0x7f7c24139000
So, the program open/creates a new file at "/tmp/riddle-{random}", increases its size/length by 4096 bytes with ftruncate, and then memory maps it (with mmap).
The mmap returns the address in the memory where the beginning of the file is mapped. On this case, this is 0x7f7c24139000. The directions say that the program want's to find the char 'Y' at 0x7f7c2413906f.
0x7f7c2413906f - 0x7f7c24139000 = 0x6f = 111
So the program wants to see the char 'Y' at the 111th byte of the file.
One solution we can try is to write 111 characters to the file, making sure that the 111th character would be the one it wants.
This can be easilly done with a text editor (but you must be fast, before the countdown ends!)
But... The program is smarter than that!
You need to change only 0x7f19edf8906f!
On our case we had changed all other addresses before the 111th too!
So we need to change our approach. To be more delicate, we'll need to use mmap. Let's write a program that does exactly that.
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
int main(int argc, char** argv) {
char* buff[1];
buff[0] = argv[2];
int fd = open(argv[1], O_RDWR);
char* data = (char*) mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
data[111] = *argv[2];
printf("Success\n");
}
The above program, takes as input from the argv the filename and the character we want to write, and performs the writing using memory map.
eg: ./a.out /tmp/riddle-MFiZZC Y
We compile and run it before the countdown ends.
AND DONE!
Hint: Memquake! Don't lose the pages beneath your something something
- What is the answer to life the universe and everything?
- strace
We immediatelly see the first issue:
openat(AT_FDCWD, ".hello_there", O_RDWR|O_CREAT, 0600) = -1 EACCES (Permission denied)
Well, this problem was leftover from the 2nd challenge (remember where we removed the write permission?), we can simply remove the file and the newly created one will have the correct permissions.
So let's strace again...
openat(AT_FDCWD, ".hello_there", O_RDWR|O_CREAT, 0600) = 4
ftruncate(4, 32768) = 0
mmap(NULL, 32768, PROT_READ|PROT_WRITE, MAP_SHARED, 4, 0) = 0x7fdf9f6d5000
ftruncate(4, 16384) = 0
read(0,
There it waits for stdin, and when we hit any character...
--- SIGBUS {si_signo=SIGBUS, si_code=BUS_ADRERR, si_addr=0x7f28a858d000} ---
+++ killed by SIGBUS (core dumped) +++
Oops... Why did this happened?
Firstly, what is the SIGBUS error?
SIGBUS (bus error) is a signal that happens when you try to access memory that has not been physically mapped. This is different to a SIGSEGV (segmentation fault) in that a segfault happens when an address is invalid, while a bus error means the address is valid but we failed to read/write.
Let's see what the program did...
- It opens the file ".hello_there".
- It increases its size to 32768 bytes.
- It memory maps it.
- It decreases its size to 16384 bytes.
Now, it becomes apparent. After we decreases the size of the file, a portion of the memory mapped to it, isn't mapped to something anymore. So, when we try to access that portion of the memory, a SIGBUS is raised.
Maybe, if we could raise the file's size again to its original size, the memory will become accessible again. We'll use this small program to achieve that:
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
int main(int argc, char** argv) {
int fd = open(argv[1], O_RDWR);
ftruncate(fd, 32768);
printf("Success\n");
}
This program, changes the size of the file inputted to it through argv to 32768 bytes.
Let's compile it and run it
./a.out .hello_there
DONE!
Hint: Are you 32767? If not, reincarnate!
Well this one is pretty obvious, if the program has a pid of 32767, then we can pass. What is not trivial is how to change the pid of a program.
There is only one way, through fork(). But fork() only gives the next pid.
A trivial solution would be this, but with this solution you should pray that the last pid is smaller than 32767. Maybe you are lucky. Maybe you can be lucky with a restart.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (int argc, const char* argv) {
char *newargv[] = { NULL };
char *newenviron[] = { NULL };
while (getpid() < 32767) {
if (fork() == 0) {
if (getpid() == 32767) {
execve("./riddle", newargv, newenviron);
}
}
}
return 0;
}
If you are not so lucky, you can try this:
The last pid is stored on /proc/sys/kernel/ns_last_pid
. If you edit this file, you can manipulate the pid the next process started will have. If the
To be sure that your solution will work, you need to lock the file so that no other process can write on it, or change the pid before you run fork();
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
int main(int argc, char *argv[])
{
char *newargv[] = { NULL };
char *newenviron[] = { NULL };
int fd, pid;
char buf[32];
if (argc != 2)
return 1;
printf("Opening ns_last_pid...\n");
fd = open("/proc/sys/kernel/ns_last_pid", O_RDWR | O_CREAT, 0644);
if (fd < 0) {
perror("Can't open ns_last_pid");
return 1;
}
printf("Done\n");
printf("Locking ns_last_pid...\n");
if (flock(fd, LOCK_EX)) {
close(fd);
printf("Can't lock ns_last_pid\n");
return 1;
}
printf("Done\n");
pid = atoi(argv[1]);
snprintf(buf, sizeof(buf), "%d", pid - 1);
printf("Writing pid-1 to ns_last_pid...\n");
if (write(fd, buf, strlen(buf)) != strlen(buf)) {
printf("Can't write to buf\n");
return 1;
}
printf("Done\n");
if (fork() == 0) {
execve("./riddle", newargv, newenviron);
return 0;
}
printf("Done\n");
printf("Cleaning up...");
if (flock(fd, LOCK_UN)) {
printf("Can't unlock");
}
close(fd);
printf("Done\n");
return 0;
}
Compile, and run it with root privileges (sudo). It should work.
Done :D
TIER ONE COMPLETE!