Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Comment on

( #3333=superdoc: print w/ replies, xml ) Need Help??

For Decembers column in WebTechniques, merlyn has written an anti-robot voting script, located here (source for the script is here, the actual column isn't up at WT yet). (BTW, I hear this is your last column, which is unfortunate. Sorry to hear that, Randal.) Update: The column text is located here. I hadn't seen the last paragraph of the article until well after the column was published.

Using some on-the-fly image generation with GD, the script requires that you type a 8 digit security code that the image contains into the text box, before you're allowed to vote on your favorite flavor of icecream (which as any self respecting individual knows is Strawberry).

Since I can't resist a good challenge, as long as it's pointless and a complete waste of time, I decided I needed a script to auto-vote for Strawberry (the respectable choice of icecream flavors).

Below is a script which does just that, using very primitive brute force OCR techniques. The code contains the comments for those that are interested, in particular regarding what Randal could have done to make it more difficult to do this.

I voted a bunch of times, and it always seems to work, unless you hit his server throttling, in which case you might get a 500 error. I thought about cron-jobbing it for a few days, but since he doesn't actually record votes, a little of the fun is removed.

The code follows the readmore tag.

--Chris

e-mail jcwren

#!/usr/local/bin/perl -w # # anti-antirobot - An automated icecream flavor voting system # # A while back, comrade merlyn wrote a little script that defeats aut +omatic # voters, as part of one of his columns. I decided it would be fun i +f 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 im +age, # break down to a bit map, and brute force match known characters aga +inst # 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 cont +ain no # noise, misregistration, etc. It only handle two-color images and d +oesn'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 h +ave # been watching re-runs of BayWatch). There's a lot of tricks he cou +ld do, # such as changing colors (which could be compensated), variable font +s, edge # dithering, etc. These could be defeated, given time. Subtlely var +ying 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 co +mplete # OCR map. There's a lot of things he could implement to make it mor +e # 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 previo +us # 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 savin +g 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, $plan +es, $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 + 3 +1) / 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 @{$char +list {$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 ('Co +ntent-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 li +nk # to the .PNG image that contains the security code.) In retrospe +ct, # 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"; } }

In reply to A little fun with merlyn by jcwren

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • Outside of code tags, you may need to use entities for some characters:
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others scrutinizing the Monastery: (8)
    As of 2014-12-22 08:30 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      Is guessing a good strategy for surviving in the IT business?





      Results (112 votes), past polls