1#!/usr/local/bin/perl -T 2#-*-Perl-*- 3# 4# $Id: kuang2.pl,v 1.1 2004/12/31 18:54:22 provos Exp $ 5# 6# kuang2.pl -- Honeyd module that emulates the backdoor installed by the 7# Kuang2 virus. 8# 9# Copyright (c) 2003, 2004 Klaus Steding-Jessen 10# All rights reserved. 11# 12# Redistribution and use in source and binary forms, with or without 13# modification, are permitted provided that the following conditions 14# are met: 15# 16# - Redistributions of source code must retain the above copyright 17# notice, this list of conditions and the following disclaimer. 18# - Redistributions in binary form must reproduce the above 19# copyright notice, this list of conditions and the following 20# disclaimer in the documentation and/or other materials provided 21# with the distribution. 22# 23# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34# POSSIBILITY OF SUCH DAMAGE. 35 36###################################################################### 37 38=head1 NAME 39 40kuang2.pl -- Honeyd module that emulates the backdoor installed by the Kuang2 virus. 41 42=head1 SYNOPSIS 43 44kuang2.pl [-Vh] [-d] [-f file] 45 46=head1 DESCRIPTION 47 48Start kuang2.pl as a Honeyd service. 49 50The options are as follows: 51 52=over 6 53 54=item -V 55 56Display version number and exit. 57 58=item -h 59 60Display this help and exit. 61 62=item -d 63 64debug mode. 65 66=item -f file 67 68configuration file. 69 70=back 71 72=head1 SEE ALSO 73 74Honeyd. 75 76=head1 AUTHOR 77 78Klaus Steding-Jessen <jessen@nic.br> 79 80=head1 AVAILABILITY 81 82The latest version of kuang2.pl is available from 83http://www.honeynet.org.br/tools/ 84 85=cut 86 87###################################################################### 88 89(my $program_name = $0) =~ s@.*/@@; 90 91use strict; 92use warnings; 93use Getopt::Std; 94use Fcntl qw(:flock); 95use POSIX qw(strftime); 96use File::Path; 97use File::Basename; 98 99unless (eval "use Digest::SHA1; 1") { 100 die "$program_name: Please install Digest::SHA1\n"; 101} 102 103###################################################################### 104 105my %option = (); 106getopts('Vhdf:n', \%option); 107 108my $version='0.2'; 109 110# unbuffered output. 111$| = 1; 112 113# set PATH. 114$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin'; 115 116###################################################################### 117# configuration defaults -- can be changed via configuration file. 118# parameter, regex value and default value. 119 120my %checksum = (); 121my %conf = ( 122 "logdir" => { 123 regex => '[\w\.-\/]+', 124 value => '/var/kuang2' }, 125 "timeout" => { 126 regex => '\d+', 127 value => 30 }, 128 "num_drives" => { 129 regex => '\d+', 130 value => 1 }, 131 "computer_name" => { 132 regex => '[\w\s]+', 133 value => 'MY COMPUTER' }, 134 "max_upload_size" => { 135 regex => '\d+', 136 value => 15728640 }, 137 ); 138 139###################################################################### 140# kuang2 constants. 141 142my $K2_BUFFER_SIZE = 1024; 143my $K2_HELO = 0x324B4F59; 144my $K2_ERROR = 0x52525245; 145my $K2_DONE = 0x454E4F44; 146my $K2_QUIT = 0x54495551; 147my $K2_DELETE_FILE = 0x464C4544; 148my $K2_RUN_FILE = 0x464E5552; 149my $K2_FOLDER_INFO = 0x464E4946; 150my $K2_DOWNLOAD_FILE = 0x464E5744; 151my $K2_UPLOAD_FILE = 0x46445055; 152my $K2_UPLOAD_FILE_2 = 0x0000687f; 153my $K2_UPLOAD_FILE_3 = 0x00007620; 154my $K2_UPLOAD_FILE_4 = 0x00004820; 155 156###################################################################### 157 158# display usage if requested. 159show_usage() if ($option{'h'}); 160 161# display version if requested. 162show_version() if ($option{'V'}); 163 164# parse config options from the configuration file, if requested. 165if (defined($option{'f'})) { 166 my ($confref) = process_config_file($option{'f'}, \%conf); 167 %conf = %{$confref}; 168} 169 170# logfile. 171my $logfile = $conf{'logdir'}{'value'} . '/' . 'logfile'; 172 173###################################################################### 174# get srchost, dsthost, srcport and dstport from the environment 175# variables set by honeyd. 176 177my $srchost = '127.0.0.1'; 178if (defined($ENV{'HONEYD_IP_SRC'}) && 179 $ENV{'HONEYD_IP_SRC'} =~ /^(\d+\.\d+\.\d+\.\d+)$/) { 180 $srchost = $1; 181} 182 183my $dsthost = '127.0.0.1'; 184if (defined($ENV{'HONEYD_IP_DST'}) && 185 $ENV{'HONEYD_IP_DST'} =~ /^(\d+\.\d+\.\d+\.\d+)$/) { 186 $dsthost = $1; 187} 188 189my $srcport = 0; 190if (defined($ENV{'HONEYD_SRC_PORT'}) && 191 $ENV{'HONEYD_SRC_PORT'} =~ /^(\d+)$/) { 192 $srcport = $1; 193} 194 195my $dstport = 0; 196if (defined($ENV{'HONEYD_DST_PORT'}) && 197 $ENV{'HONEYD_DST_PORT'} =~ /^(\d+)$/) { 198 $dstport = $1; 199} 200 201###################################################################### 202# main. 203 204# install SIGALRM handler. 205$SIG{ALRM} = sub { 206 my $msg = sprintf("timed out after %d seconds", $conf{'timeout'}{'value'}); 207 logentry($logfile, $msg); 208 die("$program_name: $msg\n"); 209 exit 0; 210}; 211 212alarm $conf{'timeout'}{'value'}; 213 214logentry($logfile, sprintf("connection from %s:%d to %s:%d", 215 $srchost, $srcport, $dsthost, $dstport)); 216 217# send the initial K2_HELO to the client. 218my $name_size = $K2_BUFFER_SIZE - 8; 219my $nwrite = syswrite(STDOUT, pack("IIZ$name_size", $K2_HELO, 220 $conf{'num_drives'}{'value'}, 221 $conf{'computer_name'}{'value'})); 222 223logentry($logfile, "DEBUG: K2_HELO sent to client") 224 if ($option{'d'}); 225 226# read stdin from honeyd -- loop reading commands from the client. 227my $nread; 228while ($nread = sysread(STDIN, my $buffer, $K2_BUFFER_SIZE)) { 229 230 # debug. 231 #logentry($logfile, "DEBUG: $nread byte(s) read") if ($option{'d'}); 232 233 my $cmd = (unpack("I*", $buffer)); 234 if (defined($cmd)) { 235 #logentry($logfile, sprintf("DEBUG: cmd: 0x%02X", $cmd)) 236 # if ($option{'d'}); 237 } else { 238 logentry($logfile, sprintf("data: %s", data2hex($buffer))); 239 next; 240 } 241 242 # process commands sent from the kuang2 client. 243 if ($cmd == $K2_UPLOAD_FILE) { 244 k2_upload_file($buffer, 0); 245 246 } elsif ($cmd == $K2_DOWNLOAD_FILE) { 247 k2_download_file($buffer); 248 249 } elsif ($cmd == $K2_QUIT) { 250 logentry($logfile, "K2_QUIT received, exiting"); 251 exit 0; 252 253 } elsif ($cmd == $K2_DELETE_FILE) { 254 k2_delete_file($buffer); 255 256 } elsif ($cmd == $K2_RUN_FILE) { 257 k2_run_file($buffer); 258 259 } elsif ($cmd == $K2_FOLDER_INFO) { 260 k2_folder_info($buffer); 261 262 } elsif (($cmd == $K2_UPLOAD_FILE_2) || 263 ($cmd == $K2_UPLOAD_FILE_3) || 264 ($cmd == $K2_UPLOAD_FILE_4)) { 265 # different upload procotol. 266 k2_upload_file($buffer, -1); 267 268 } else { 269 # unknown command. 270 logentry($logfile, sprintf("unknown command: %s", data2hex($buffer))); 271 272 my $nwrite = syswrite(STDOUT, pack("I", $K2_ERROR)); 273 logentry($logfile, "DEBUG: K2_ERROR sent to client") if ($option{'d'}); 274 } 275 alarm $conf{'timeout'}{'value'}; 276} 277 278logentry($logfile, "ERROR: sysread: $!") if (!defined($nread)); 279 280alarm 0; 281 282logentry($logfile, "exiting"); 283exit 0; 284 285###################################################################### 286# handle upload file requests. 287sub k2_upload_file { 288 my ($buffer, $flag) = @_; 289 290 my ($cmd, $filesize, $filename); 291 if ($flag == 0) { 292 ($cmd, $filesize, $filename) = unpack("IIZ*", $buffer); 293 } else { 294 $filesize = -1; 295 ($cmd, $filename) = unpack("IZ*", $buffer); 296 } 297 298 # check if unpack() succeeded. 299 if (!defined($cmd) || !defined($filesize) || !defined($filename)) { 300 my $msg = "unpack() error"; 301 logentry($logfile, $msg); 302 die("$program_name: $msg\n"); 303 } 304 305 # untaint filename. 306 if ($filename =~ /^([\w\\\/\.:]+)$/) { 307 $filename = $1; 308 } 309 310 if ($flag == 0) { 311 # we only accept files >0 and <= MAX_UPLOAD_FILESIZE bytes long. 312 if (!(($filesize > 0) && 313 ($filesize <= $conf{'max_upload_size'}{'value'}))) { 314 my $nwrite = syswrite(STDOUT, pack("I", $K2_ERROR)); 315 logentry($logfile, "DEBUG: K2_ERROR sent to client") 316 if ($option{'d'}); 317 my $msg = sprintf("illegal file size: %ld byte(s)", $filesize); 318 319 logentry($logfile, $msg); 320 die("$program_name: $msg\n"); 321 } 322 } 323 324 if ($flag == 0) { 325 logentry($logfile, 326 sprintf("cmd received: K2_UPLOAD_FILE, file: %s, size: %d byte(s)", 327 quotemeta($filename), $filesize)); 328 } else { 329 logentry($logfile, 330 sprintf("cmd received: K2_UPLOAD_FILE (ALT), file: %s", 331 quotemeta($filename))); 332 } 333 334 # ack the K2_UPLOAD_FILE request. 335 my $nwrite = syswrite(STDOUT, pack("I", $K2_DONE)); 336 logentry($logfile, "DEBUG: K2_DONE sent to client") if ($option{'d'}); 337 338 # keep only the basename component and also remove the drive part 339 # (c:\\, d:\\, etc) on MSDOS/Win filenames. 340 fileparse_set_fstype("MSWin32"); 341 $filename = basename($filename); 342 343 # create directory hierarchy (adapted from smtp.pl). 344 my $srchostdir = $srchost; 345 $srchostdir =~ s/\./\//g; 346 my $upload_dir = "$conf{'logdir'}{'value'}/$srchostdir/$srcport"; 347 348 if (! -d "$upload_dir" ) { 349 eval { mkpath($upload_dir) }; 350 if ($@) { 351 logentry($logfile, "ERROR: $upload_dir: $@"); 352 die("$program_name: $upload_dir: $@"); 353 } 354 } 355 356 my $upload_file = "$upload_dir/$filename"; 357 358 if (!defined(open(UPLOAD_FILE, ">$upload_file"))) { 359 logentry($logfile, "ERROR: $upload_file: $!"); 360 die("$program_name: $upload_file: $!\n"); 361 } 362 363 # read the file. 364 my $uploaded = 0; 365 my $nread = 0; 366 do { 367 368 $nread = sysread(STDIN, my $buffer, $K2_BUFFER_SIZE); 369 #logentry($logfile, "DEBUG: $nread byte(s) read") if ($option{'d'}); 370 371 my $nwrite = syswrite(UPLOAD_FILE, $buffer, $nread); 372 $uploaded += $nwrite; 373 374 if ($uploaded > $conf{'max_upload_size'}{'value'}) { 375 my $msg = sprintf("ERROR: upload limit exceeded: %ld byte(s)", 376 $uploaded); 377 logentry($logfile, $msg); 378 die("$program_name: $msg\n"); 379 } 380 381 alarm $conf{'timeout'}{'value'}; 382 } while (($uploaded != $filesize) && $nread); 383 384 close(UPLOAD_FILE); 385 386 # if filesize is known, check if it matches uploaded size. 387 if (($flag == 0) && ($uploaded != $filesize)) { 388 logentry($logfile, 389 sprintf("sizes don't match, filesize: %ld, uploaded: %ld", 390 $filesize, $uploaded)); 391 } 392 393 # check for empty uploaded files. 394 if (-z $upload_file) { 395 my $msg = sprintf("ERROR: %s is empty", $upload_file); 396 logentry($logfile, $msg); 397 die("$program_name: $msg\n"); 398 } 399 400 # determine the SHA-1 of uploaded file. 401 if (!defined(open(UPLOAD_FILE, "$upload_file"))) { 402 logentry($logfile, "ERROR: $upload_file: $!"); 403 die("$program_name: $upload_file: $!\n"); 404 } 405 my $ctx = Digest::SHA1->new; 406 $ctx->addfile(*UPLOAD_FILE); 407 my $digest = $ctx->hexdigest; 408 close(UPLOAD_FILE); 409 410 logentry($logfile, 411 sprintf("file uploaded to %s, size: %d byte(s), SHA-1: %s", 412 $upload_file, -s $upload_file, $digest)); 413 414 # we're done. 415 $nwrite = syswrite(STDOUT, pack("I", $K2_DONE)); 416 logentry($logfile, "DEBUG: K2_DONE sent to client") if ($option{'d'}); 417 418 return 0; 419} 420###################################################################### 421# handle run file requests. 422sub k2_run_file { 423 my ($buffer) = @_; 424 425 my ($cmd, $filename) = unpack("IZ*", $buffer); 426 427 # check if unpack() succeeded. 428 if (!defined($cmd) || !defined($filename)) { 429 my $msg = "unpack() error"; 430 logentry($logfile, $msg); 431 die("$program_name: $msg\n"); 432 } 433 434 # untaint filename. 435 if ($filename =~ /^([\w\\\/\.:]+)$/) { 436 $filename = $1; 437 } 438 439 logentry($logfile, 440 sprintf("cmd received: K2_RUN_FILE, file: %s", 441 quotemeta($filename))); 442 443 # we're done. 444 $nwrite = syswrite(STDOUT, pack("I", $K2_DONE)); 445 logentry($logfile, "DEBUG: K2_DONE sent to client") if ($option{'d'}); 446 447 return 0; 448} 449###################################################################### 450# handle delete file requests. 451sub k2_delete_file { 452 my ($buffer) = @_; 453 454 my ($cmd, $filename) = unpack("IZ*", $buffer); 455 456 # check if unpack() succeeded. 457 if (!defined($cmd) || !defined($filename)) { 458 my $msg = "unpack() error"; 459 logentry($logfile, $msg); 460 die("$program_name: $msg\n"); 461 } 462 463 # untaint filename. 464 if ($filename =~ /^([\w\\\/\.:]+)$/) { 465 $filename = $1; 466 } 467 468 logentry($logfile, 469 sprintf("cmd received: K2_DELETE_FILE, file: %s", 470 quotemeta($filename))); 471 472 # return an error to the client. 473 $nwrite = syswrite(STDOUT, pack("I", $K2_ERROR)); 474 logentry($logfile, "DEBUG: K2_ERROR sent to client") if ($option{'d'}); 475 476 return 0; 477} 478###################################################################### 479# handle download file requests. 480sub k2_download_file { 481 my ($buffer) = @_; 482 483 my ($cmd, $filename) = unpack("IZ*", $buffer); 484 485 # check if unpack() succeeded. 486 if (!defined($cmd) || !defined($filename)) { 487 my $msg = "unpack() error"; 488 logentry($logfile, $msg); 489 die("$program_name: $msg\n"); 490 } 491 492 # untaint filename. 493 if ($filename =~ /^([\w\\\/\.:]+)$/) { 494 $filename = $1; 495 } 496 497 logentry($logfile, 498 sprintf("cmd received: K2_DOWNLOAD_FILE (init), file: %s", 499 quotemeta($filename))); 500 501 # return an error to the client. 502 $nwrite = syswrite(STDOUT, pack("I", $K2_ERROR)); 503 logentry($logfile, "DEBUG: K2_ERROR sent to client") if ($option{'d'}); 504 505 return 0; 506} 507###################################################################### 508# handle folfer info requests. 509sub k2_folder_info { 510 my ($buffer) = @_; 511 512 my ($cmd, $filename) = unpack("IZ*", $buffer); 513 514 # check if unpack() succeeded. 515 if (!defined($cmd) || !defined($filename)) { 516 my $msg = "unpack() error"; 517 logentry($logfile, $msg); 518 die("$program_name: $msg\n"); 519 } 520 521 # untaint filename. 522 if ($filename =~ /^([\w\\\/\.:]+)$/) { 523 $filename = $1; 524 } 525 526 logentry($logfile, 527 sprintf("cmd received: K2_FOLDER_INFO, start: %s", 528 quotemeta($filename))); 529 530 # return an error to the client. 531 $nwrite = syswrite(STDOUT, pack("I", $K2_ERROR)); 532 logentry($logfile, "DEBUG: K2_ERROR sent to client") if ($option{'d'}); 533 534 return 0; 535} 536###################################################################### 537# create a log entry. 538sub logentry { 539 my ($logfile, $msg) = @_; 540 541 my $datum = strftime "%F %T %z", localtime; 542 my $pid = $$; 543 544 open(LOG, ">>$logfile") || 545 die "$program_name: $logfile: $!\n"; 546 flock(LOG, LOCK_EX); 547 seek(LOG, 0, 2); 548 549 printf LOG ("%s: %s[%d]: %s\n", $datum, $program_name, $pid, $msg); 550 551 flock(LOG, LOCK_UN); 552 close(LOG); 553 554} 555###################################################################### 556# return a hexa representation of data. 557sub data2hex { 558 my ($data) = @_; 559 560 my $hex = unpack("H*", $data); 561 if (defined($hex)) { 562 $hex =~ s/../0x$& /g; 563 $hex =~ s/ $//g; 564 } else { 565 $hex = ""; 566 } 567 568 return $hex; 569} 570###################################################################### 571# process the configuration file. Exits in case of error. 572sub process_config_file { 573 my ($filename, $confref) = @_; 574 my %conf = %{$confref}; 575 576 # filename: words, numbers, '-', '/' and '.' are ok. 577 if ($filename =~ /^([\w\.\-\/]+)$/) { 578 $filename = $1; 579 } else { 580 $filename = quotemeta($filename); 581 die("$program_name: invalid filename: $filename\n"); 582 } 583 584 open(CONF, "$filename") || 585 die ("$program_name: can't open $filename: $!\n"); 586 587 while (my $line = <CONF>) { 588 chomp($line); 589 next if ($line =~ /^$|^\s*$|^\s*#/); # skip comments and empty lines. 590 foreach my $key (keys %conf) { 591 if ($line =~ /^$key\s*[=:]\s*($conf{$key}{'regex'})\s*$/) { 592 $conf{$key}{'value'} = $1; 593 last; 594 } elsif ($line =~ /^$key\s*/) { 595 die("$program_name: invalid parameter: $line\n"); 596 } 597 } 598 } 599 close(CONF); 600 return \%conf; 601} 602###################################################################### 603# print program usage and exit. 604sub show_usage { 605 print <<EOF; 606Usage: $program_name [-Vh] [-d] [-f file] 607 -h display this help and exit. 608 -V display version number and exit. 609 -d debug. 610 -f file configuration file. 611EOF 612 613 exit 0; 614} 615###################################################################### 616# print program version and exit. 617sub show_version { 618 printf("%s %s\n", $program_name, $version); 619 exit 0; 620} 621###################################################################### 622 623# kuang2.pl ends here. 624