#!/usr/local/bin/perl -w # # anti-antirobot - An automated icecream flavor voting system # # A while back, comrade merlyn wrote a little script that defeats automatic # voters, as part of one of his columns. I decided it would be fun if it # it could automatically be voted, so I wrote this little widget. # # http://www.stonehenge.com/merlyn/WebTechniques/col68.listing.txt is the # home of the original text for his article. # # It requires that 'convert' from the ImageMagick suite be installed. This # is used to convert the .PNGs that merlyn returns to .BMP files, so I can # take them apart to a bitmap. It's probably easy enough to do this with # .PNG, but I'm not familiar with it. # # The theory is quite simple. You grab the page with the security image, # break down to a bit map, and brute force match known characters against # it. As you get a match, you add that character to the secret word string. # And after the last character is matched, *poof*, you have the code. # # The OCR code is not very sophisticated. It counts on the fact that the # characters in the security image are generated on the fly, and contain no # noise, misregistration, etc. It only handle two-color images and doesn't # do anything smart like trimming blank lines prior to compare. # # Note that merlyn never claimed that the antirobot was foolproof. I'm just # a better fool (for having spent the time to do this, when I could have # been watching re-runs of BayWatch). There's a lot of tricks he could do, # such as changing colors (which could be compensated), variable fonts, edge # dithering, etc. These could be defeated, given time. Subtlely varying the # shades of the cells could be handled with a high-pass filter, so it's # either black or white. Fonts could be accumulated so you have a complete # OCR map. There's a lot of things he could implement to make it more # difficult, and given a little time, it could be defeated. # # Hopefully, if merlyn updates his script to defeat the antiantirobot, he'll # leave the original in place as part of this demonstration, and call the # new one 'auntierobot', or 'antirobot2' # # I re-used my OCR routines from stuff I wrote that took radar loops from # www.weathertap.com and built multi-day national animated radar maps. They # time stamp the images graphically, and I extracted that and used it to # arrange the frames (a subsequent loop may contain frames the previous # loop did not contain, due to the refresh period of the radar). # use strict; use LWP::UserAgent; use HTML::LinkExtor; use HTML::Form; # # These are the bitmaps for the OCR, gleaned by running his antirobot script # multiple times, and cutting the files up in PaintShopPro, and saving them # as .BMPs. A small 'C' program then read the .BMP files, and built the # Perl code for the characters. # my @char_2 = ('.........', '.........', '.........', '.........', '...####..', '..##..##.', '.##....##', '.......##', '......##.', '.....##..', '....##...', '...##....', '..##.....', '.########', '.........', '.........', '.........'); my @char_3 = ('.........', '.........', '.........', '.........', '..#####..', '.##...##.', '.......##', '......##.', '....###..', '......##.', '.......##', '.......##', '.##...##.', '..#####..', '.........', '.........', '.........'); my @char_4 = ('.........', '.........', '.........', '.........', '......##.', '.....###.', '....####.', '...##.##.', '..##..##.', '.##...##.', '.########', '......##.', '......##.', '......##.', '.........', '.........', '.........'); my @char_5 = ('.........', '.........', '.........', '.........', '.#######.', '.##......', '.##......', '.##.###..', '.###..##.', '.......##', '.......##', '.##....##', '..##..##.', '...####..', '.........', '.........', '.........'); my @char_6 = ('.........', '.........', '.........', '.........', '...####..', '..##..##.', '.##....#.', '.##......', '.##.###..', '.###..##.', '.##....##', '.##....##', '..##..##.', '...####..', '.........', '.........', '.........'); my @char_7 = ('.........', '.........', '.........', '.........', '.########', '.......##', '.......##', '......##.', '.....##..', '....##...', '...##....', '..##.....', '.##......', '.##......', '.........', '.........', '.........'); my @char_8 = ('.........', '.........', '.........', '.........', '...####..', '..##..##.', '.##....##', '..##..##.', '...####..', '..##..##.', '.##....##', '.##....##', '..##..##.', '...####..', '.........', '.........', '.........'); my @char_9 = ('.........', '.........', '.........', '.........', '...####..', '..##..##.', '.##....##', '.##....##', '..##..###', '...###.##', '.......##', '..#....##', '..##..##.', '...####..', '.........', '.........', '.........'); my @char_a = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '...#####.', '..##...##', '.......##', '..#######', '.##....##', '.##...###', '..####.##', '.........', '.........', '.........'); my @char_b = ('.........', '.........', '.........', '.........', '.##......', '.##......', '.##......', '.##.###..', '.###..##.', '.##....##', '.##....##', '.##....##', '.###..##.', '.##.###..', '.........', '.........', '.........'); my @char_c = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '...#####.', '..##...##', '.##......', '.##......', '.##......', '..##...##', '...#####.', '.........', '.........', '.........'); my @char_d = ('.........', '.........', '.........', '.........', '.......##', '.......##', '.......##', '...###.##', '..##..###', '.##....##', '.##....##', '.##....##', '..##..###', '...###.##', '.........', '.........', '.........'); my @char_e = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '...####..', '..##..##.', '.##....##', '.########', '.##......', '..##...##', '...#####.', '.........', '.........', '.........'); my @char_f = ('.........', '.........', '.........', '.........', '....####.', '...##..##', '...##..##', '...##....', '...##....', '.######..', '...##....', '...##....', '...##....', '...##....', '.........', '.........', '.........'); my @char_g = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '..#####.#', '.##...###', '.##...##.', '.##...##.', '..#####..', '.##......', '..######.', '.##....##', '..######.', '.........'); my @char_h = ('.........', '.........', '.........', '.........', '.##......', '.##......', '.##......', '.##.###..', '.###..##.', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_k = ('.........', '.........', '.........', '.........', '..##.....', '..##.....', '..##.....', '..##..##.', '..##.##..', '..####...', '..####...', '..##.##..', '..##..##.', '..##...##', '.........', '.........', '.........'); my @char_m = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.#.##.##.', '.##.##.##', '.##.##.##', '.##.##.##', '.##.##.##', '.##.##.##', '.##.##.##', '.........', '.........', '.........'); my @char_n = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##.###..', '.###..##.', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_p = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##.###..', '.###..##.', '.##....##', '.##....##', '.##....##', '.###..##.', '.##.###..', '.##......', '.##......', '.........'); my @char_q = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '...###.##', '..##..###', '.##....##', '.##....##', '.##....##', '..##..###', '...###.##', '.......##', '.......##', '.........'); my @char_r = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##.####.', '..###..##', '..##.....', '..##.....', '..##.....', '..##.....', '..##.....', '.........', '.........', '.........'); my @char_s = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '..######.', '.##....##', '.##......', '..######.', '.......##', '.##....##', '..######.', '.........', '.........', '.........'); my @char_t = ('.........', '.........', '.........', '.........', '.........', '...##....', '...##....', '.######..', '...##....', '...##....', '...##....', '...##....', '...##..##', '....####.', '.........', '.........', '.........'); my @char_u = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '..##..###', '...###.##', '.........', '.........', '.........'); my @char_v = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##....##', '.##....##', '..##..##.', '..##..##.', '...####..', '...####..', '....##...', '.........', '.........', '.........'); my @char_w = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##.##.##', '.##.##.##', '.##.##.##', '.########', '..##..##.', '.........', '.........', '.........'); my @char_x = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##....##', '..##..##.', '...####..', '....##...', '...####..', '..##..##.', '.##....##', '.........', '.........', '.........'); my @char_y = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '..##..###', '...###.##', '.#.....##', '..######.', '.........'); my @char_z = ('.........', '.........', '.........', '.........', '.........', '.........', '.........', '..######.', '......##.', '.....##..', '....##...', '...##....', '..##.....', '..######.', '.........', '.........', '.........'); my @char_A = ('.........', '.........', '.........', '.........', '....##...', '...####..', '..##..##.', '.##....##', '.##....##', '.##....##', '.########', '.##....##', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_B = ('.........', '.........', '.........', '.........', '.######..', '.##...##.', '.##....##', '.##...##.', '.######..', '.##...##.', '.##....##', '.##....##', '.##...##.', '.######..', '.........', '.........', '.........'); my @char_C = ('.........', '.........', '.........', '.........', '...#####.', '..##...##', '.##.....#', '.##......', '.##......', '.##......', '.##......', '.##.....#', '..##...##', '...#####.', '.........', '.........', '.........'); my @char_D = ('.........', '.........', '.........', '.........', '.######..', '.##...##.', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.##...##.', '.######..', '.........', '.........', '.........'); my @char_E = ('.........', '.........', '.........', '.........', '.#######.', '.##......', '.##......', '.##......', '.######..', '.##......', '.##......', '.##......', '.##......', '.#######.', '.........', '.........', '.........'); my @char_F = ('.........', '.........', '.........', '.........', '.########', '.##......', '.##......', '.##......', '.######..', '.##......', '.##......', '.##......', '.##......', '.##......', '.........', '.........', '.........'); my @char_G = ('.........', '.........', '.........', '.........', '...#####.', '..##...##', '.##......', '.##......', '.##......', '.##...###', '.##....##', '.##....##', '..##...##', '...#####.', '.........', '.........', '.........'); my @char_H = ('.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##....##', '.##....##', '.########', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_K = ('.........', '.........', '.........', '.........', '.##....##', '.##...##.', '.##..##..', '.##.##...', '.####....', '.####....', '.##.##...', '.##..##..', '.##...##.', '.##....##', '.........', '.........', '.........'); my @char_M = ('.........', '.........', '.........', '.........', '.##....##', '.###..###', '.########', '.##.##.##', '.##.##.##', '.##.##.##', '.##....##', '.##....##', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_N = ('.........', '.........', '.........', '.........', '.##....##', '.###...##', '.####..##', '.####..##', '.##.##.##', '.##.##.##', '.##..####', '.##...###', '.##...###', '.##....##', '.........', '.........', '.........'); my @char_P = ('.........', '.........', '.........', '.........', '.#######.', '.##....##', '.##....##', '.##....##', '.#######.', '.##......', '.##......', '.##......', '.##......', '.##......', '.........', '.........', '.........'); my @char_Q = ('.........', '.........', '.........', '.........', '...####..', '..##..##.', '.##....##', '.##....##', '.##....##', '.##....##', '.##.##.##', '.##..####', '..##..##.', '...####.#', '.........', '.........', '.........'); my @char_R = ('.........', '.........', '.........', '.........', '.#######.', '.##....##', '.##....##', '.##....##', '.#######.', '.#####...', '.##..##..', '.##...##.', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_S = ('.........', '.........', '.........', '.........', '..######.', '.##....##', '.##......', '.##......', '..######.', '.......##', '.......##', '.......##', '.##....##', '..######.', '.........', '.........', '.........'); my @char_T = ('.........', '.........', '.........', '.........', '.########', '....##...', '....##...', '....##...', '....##...', '....##...', '....##...', '....##...', '....##...', '....##...', '.........', '.........', '.........'); my @char_U = ('.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '.##....##', '..##..##.', '...####..', '.........', '.........', '.........'); my @char_V = ('.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##....##', '..##..##.', '..##..##.', '..##..##.', '...####..', '...####..', '....##...', '....##...', '.........', '.........', '.........'); my @char_W = ('.........', '.........', '.........', '.........', '.##....##', '.##....##', '.##....##', '.##....##', '.##.##.##', '.##.##.##', '.##.##.##', '.########', '.###..###', '.##....##', '.........', '.........', '.........'); my @char_X = ('.........', '.........', '.........', '.........', '.##....##', '.##....##', '..##..##.', '...####..', '....##...', '....##...', '...####..', '..##..##.', '.##....##', '.##....##', '.........', '.........', '.........'); my @char_Y = ('.........', '.........', '.........', '.........', '.##....##', '.##....##', '..##..##.', '...####..', '....##...', '....##...', '....##...', '....##...', '....##...', '....##...', '.........', '.........', '.........'); my @char_Z = ('.........', '.........', '.........', '.........', '.#######.', '......##.', '......##.', '.....##..', '....##...', '...##....', '..##.....', '.##......', '.##......', '.#######.', '.........', '.........', '.........'); my %charlist = ( '2' => \@char_2, '3' => \@char_3, '4' => \@char_4, '5' => \@char_5, '6' => \@char_6, '7' => \@char_7, '8' => \@char_8, '9' => \@char_9, 'a' => \@char_a, 'b' => \@char_b, 'c' => \@char_c, 'd' => \@char_d, 'e' => \@char_e, 'f' => \@char_f, 'g' => \@char_g, 'h' => \@char_h, 'k' => \@char_k, 'm' => \@char_m, 'n' => \@char_n, 'p' => \@char_p, 'q' => \@char_q, 'r' => \@char_r, 's' => \@char_s, 't' => \@char_t, 'u' => \@char_u, 'v' => \@char_v, 'w' => \@char_w, 'x' => \@char_x, 'y' => \@char_y, 'z' => \@char_z, 'A' => \@char_A, 'B' => \@char_B, 'C' => \@char_C, 'D' => \@char_D, 'E' => \@char_E, 'F' => \@char_F, 'G' => \@char_G, 'H' => \@char_H, 'K' => \@char_K, 'M' => \@char_M, 'N' => \@char_N, 'P' => \@char_P, 'Q' => \@char_Q, 'R' => \@char_R, 'S' => \@char_S, 'T' => \@char_T, 'U' => \@char_U, 'V' => \@char_V, 'W' => \@char_W, 'X' => \@char_X, 'Y' => \@char_Y, 'Z' => \@char_Z, ); # # The OCR routine # sub ocr_it { my $data = shift; my @image = (); my $passphrase = ""; my ($hdr, $filesize, $rsvrd, $bdo, $hdrsize, $width, $height, $planes, $bpp, $compression, $datasize, $hres, $vres, $colors, $icolors) = unpack ("a2IIIIIISSIIIIII", $data); $data = substr ($data, 54 + ($icolors * 4)); for (my $i = $height - 1; $i >= 0; $i--) { my $s = unpack ("B$width", substr ($data, $i * (int (($width + 31) / 32) * 4))); $s =~ s/0/./g; $s =~ s/1/#/g; push @image, $s; } print join ("\n", @image), "\n"; # # For the width of the bitmap # for (my $column = 0; $column < $width; $column++) { # # For each character we can match against # foreach my $char (keys %charlist) { # # For the number of rows in this character # my $match = 1; my $charwidth = length (@{$charlist {$char}}[0]); for (my $row = 0; $row < scalar @{$charlist {$char}}; $row++) { # # For the number of columns in this character # if (substr ($image [$row], $column, $charwidth) ne @{$charlist {$char}}[$row]) { $match = 0; last; } } if ($match) { $passphrase .= $char; $column += ($charwidth - 1); last; } } } return $passphrase; } # # This is "main" # { my $baseurl = "http://www.stonehenge.com"; my $url = "$baseurl/cgi/antirobot"; my $tempname = "/tmp/$$." . time . ".tmp"; my @images; # # Get the base page with the choices and the security code # my $ua = LWP::UserAgent->new; $ua->agent ("Strawberry/1.0 (chocolate sucks; vanilla is dull; anti-antirobot 1.0)"); my $req = new HTTP::Request ('GET' => $url, HTTP::Headers->new ('Content-Type' => 'application/x-www-form-urlencoded')); my $res = $ua->request ($req); $res->is_error && die "Can't get page"; my $form = HTML::Form->parse ($res->content (), $baseurl); # # Extract links. There can be only one! (And it should be the link # to the .PNG image that contains the security code.) In retrospect, # HTML::SimpleLinkExtor may have made more sense. # my $p = HTML::LinkExtor->new (\&cb, $baseurl); sub cb { my ($tag, %links) = @_; push @images, $links{src} if ($tag =~ m/img/i); } $p->parse ($res->content ()); die "Not just 1 image!" if (scalar @images != 1); # # Get the security image # $req = new HTTP::Request ('GET' => $images [0], HTTP::Headers->new ('Content-Type' => 'application/x-www-form-urlencoded')); $res = $ua->request ($req); $res->is_error && die "Can't get security image"; # # Now the fun part. Save to a temporary file, convert to a .BMP # file (so we can get the bits easily, I don't know how .PNG works), # then OCR it. This gives us the secret number. # open (FH, ">$tempname") || die $!; print FH $res->content (); close FH; my $bmpimg = `convert $tempname bmp:-`; my $secretword = ocr_it ($bmpimg); unlink $tempname; # # Tell the user what it is, then vote for Strawberry (my favorite) # print "\nThe secret word is: $secretword\n\n"; $form->value ("flavor", "Strawberry"); $form->value ("verify", $secretword); $res = $ua->request ($form->click); $res->is_error && die "Can't post vote"; # # Finally, print the result of what the antibot sent back. If it's # good, we should see some text about thanking us. # if ($res->content () =~ m/thank/i) { print "Looks like he liked us. Strawberry it is!\n\n"; } else { print "Uh oh, we didn't pass a good security code\n\n"; } }