About
The /home/flag11/flag11 binary processes standard input and executes a shell command.
There are two ways of completing this level, you may wish to do both :-)
To do this level, log in as the level11 account with the password level11 . Files for this level can be found in /home/flag11.
1#include <stdlib.h>
2#include <unistd.h>
3#include <string.h>
4#include <sys/types.h>
5#include <fcntl.h>
6#include <stdio.h>
7#include <sys/mman.h>
8
9/*
10 * Return a random, non predictable file, and return the file descriptor for it.
11 */
12
13int getrand(char **path)
14{
15 char *tmp;
16 int pid;
17 int fd;
18
19 srandom(time(NULL));
20
21 tmp = getenv("TEMP");
22 pid = getpid();
23
24 asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
25 'A' + (random() % 26), '0' + (random() % 10),
26 'a' + (random() % 26), 'A' + (random() % 26),
27 '0' + (random() % 10), 'a' + (random() % 26));
28
29 fd = open(*path, O_CREAT|O_RDWR, 0600);
30 unlink(*path);
31 return fd;
32}
33
34void process(char *buffer, int length)
35{
36 unsigned int key;
37 int i;
38
39 key = length & 0xff;
40
41 for(i = 0; i < length; i++) {
42 buffer[i] ^= key;
43 key -= buffer[i];
44 }
45
46 system(buffer);
47}
48
49#define CL "Content-Length: "
50
51int main(int argc, char **argv)
52{
53 char line[256];
54 char buf[1024];
55 char *mem;
56 int length;
57 int fd;
58 char *path;
59
60 if(fgets(line, sizeof(line), stdin) == NULL) {
61 errx(1, "reading from stdin");
62 }
63
64 if(strncmp(line, CL, strlen(CL)) != 0) {
65 errx(1, "invalid header");
66 }
67
68 length = atoi(line + strlen(CL));
69
70 if(length < sizeof(buf)) {
71 if(fread(buf, length, 1, stdin) != length) {
72 err(1, "fread length");
73 }
74 process(buf, length);
75 } else {
76 int blue = length;
77 int pink;
78
79 fd = getrand(&path);
80
81 while(blue > 0) {
82 printf("blue = %d, length = %d, ", blue, length);
83
84 pink = fread(buf, 1, sizeof(buf), stdin);
85 printf("pink = %d\n", pink);
86
87 if(pink <= 0) {
88 err(1, "fread fail(blue = %d, length = %d)", blue, length);
89 }
90 write(fd, buf, pink);
91
92 blue -= pink;
93 }
94
95 mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
96 if(mem == MAP_FAILED) {
97 err(1, "mmap");
98 }
99 process(mem, length);
100 }
101
102}
103
Ah, what a month! First I did some traveling, then I’ve been busy with work & life; Finally I did some good ol’ slacking. But no more.
This level is quite easy to solve even though it looks complicated at first. Also, as noted, there are two ways to solve it however I will present only one of them (the key to the other one is the fact that getrand()
function isn’t secure — from there one would need to craft proper input and I’m too tired to do that now).
After initial reading we can quickly conclude that the finish line lies in process()
function. It’s also trivial to spot that the input is XOR-ed and remembering that XOR is a commutative operation we already know that we need to provide XOR-ed input (process()
will XOR our XOR-ed input which will result in proper command).
OK, so how will we get to process()
function? By executing first block in following if
statement:
1
|
if
(length <
sizeof
(buf)) {
|
In order to do that we need to fulfill couple of requirements:
First — we need to provide proper header (which is #defined
as “Content-Length: “)
Second — we need to provide proper length (tricky part)
Third — we need to provide proper input
After meeting all these requirements we will be able to execute commands via system()
function.
For the sake of completeness let’s walk from the beginning of main()
.
First if
statement that we need to pass is:
1
|
if
(
fgets
(line,
sizeof
(line), stdin) == NULL) {
|
Which of course is passed by providing any input.
Our second if
statement is header checking:
1
|
if
(
strncmp
(line, CL,
strlen
(CL)) != 0) {
|
Here we need to provide a valid header “Content-Length: ” along with the input length value(here’s the trick!). For now type “4″:
Content-Length: 4
After that silent assigning will take place:
1
|
length =
atoi
(line +
strlen
(CL));
|
And finally, we’re in the if statement that we wanted to be:
1
|
if
(length <
sizeof
(buf)) {
|
Since 4 < 1024. We will focus on the first block.
So, we're already executing fread() function from if
statement which is in our main if
statement. And here be dragons kids. Go read fread()
man page and you will know that this can’t possible work. The only accepted value in this case is 1 hence our input is limited to only 1 character.
Empirical proof:
level11@nebula:/home/flag11$ ./flag11
Content-Length: 4
ABCD
flag11: fread length: Success
level11@nebula:/home/flag11$ ./flag11
Content-Length: 1
a
sh: -c: line 0: unexpected EOF while looking for matching ``'
sh: -c: line 1: syntax error: unexpected end of file
As you can see in first run we’ve got the fread() error (from line 72) however in second run we’ve got no error from flag11
, what’s more we did get an error from shell. Nice.
So, we can execute command which is one character long. At first it looks limiting but it’s not — we can connect the dots from previous levels:
level11@nebula:~$ ln -s /bin/getflag a
level11@nebula:~$ export PATH=`echo $PATH`:/home/level11
level11@nebula:~$ cd /
level11@nebula:/$ a
getflag is executing on a non-flag account, this doesn't count
Voila! One character command working like a charm however we need to provide XOR-ed value of “a”. Lucky for us the XOR key depends on user input:
1
|
key = length & 0xff;
|
So for us it’s 0x1 & 0xff
. I’m leaving writing trivial code to XOR “a” for the reader (but acute observer does not need to do so!).
level11@nebula:~$ /home/flag11/flag11
Content-Length: 1
`
You have successfully executed getflag on a target account
Funny fact: You may need to try it couple of times — the reason is that array buf
is not zero-ed meaning it contains garbage which makes problems for C-like strings since they need to be NULL-terminated. (Quick fix — declare it as static
.)