package Engine; use strict; # initialise this one for later filling out by this routine. If we don't # initialise it, then the mainline will be using an unitialised value where # no successful exploits were found. @Engine::successful_exploits = (); # find_match() # Find a match if one exists. There will be two sorts of matches. The # first type, and easiest to find, is an exact match where the given # string matches another string exactly. The second type is where the # given string comprises a wildcard, which will match with another string. # # Returns: 1 if a match is found, 0 otherwise. # Expects: a CO string and the name of an asociative array to lookup. # Called By: addto() # Side Effects: none. # External References: The associative array passed as the second argument. # Errors: None looked for. sub find_match { my($co) = $_[0]; my($name) = $_[1]; my($assoc_r) = $_[2]; my($key); KuangPlus::pdebug "find_match(): Matching for \"$co\" in \"$name\"", 9.3; if ( defined $$assoc_r{$co} ) { KuangPlus::pdebug "find_match(): simple, it was defined.", 9.3; return 1; } foreach $key (keys(%$assoc_r)) { KuangPlus::pdebug "find_match(): test: \"$co\" =~ \"$key\"\?",9.3; if ( "$co" =~ /^$key$/ ) { KuangPlus::pdebug "find_match(): regex match found.", 9.3; return 1; } } return 0; } # end of find_match() # addto() # selectively adds and evaluates plans with a view to getting access to the # superuser account. Popluates @Engine::successful_exploits as they # are found. # # Calls: KuangPlus::debug() # Called by: # Expects: a list of at least 2 elements. # Returns: # Side effects: # External references: # Errors: looks out for plans which are the same as the objective. sub addto { my($op) = $_[0]; my($ob) = $_[1]; my($plan) = $_[2]; @Addto::plan = $plan ? split(' ',$plan) : (); my($co) = "$op $ob"; my($i); KuangPlus::pdebug "addto(op: \"$op\", ob: \"$ob\" plan[".@Addto::plan."]: ". "\"@Addto::plan\")", 3.3; # check to see if $op and $ob is "u 0". If the plan is empty, then # its ok to pursue this (i.e. its the initial goal). However, if # the plan is not-empty, then it is silly to pursue it because we can # do what ever we like if we have root access. if ( $op eq "u" && $ob eq "0" && $#Addto::plan >= 0 ) { KuangPlus::pdebug "addto: silly to pursue: \"$co\"", 3.3; return; } # The %KuangPlus::known_facts associative array is populated with what # access we have at the moment and other "known" facts such as from # the "uname" call. If the $co is amongst the things we "know" then # we have a chain of controlling operation pairs (a plan) which # will take us backward from the goal to a known fact i.e. SUCCESS KuangPlus::pdebug "addto(): Checking known_facts for \"$co\"", 3.3; if ( Engine::find_match($co,"known_facts",\%KuangPlus::known_facts) ) { push(@Engine::successful_exploits,"@Addto::plan $co"); KuangPlus::pdebug "addto(): added \"@Addto::plan $co\" to successful_exploits", 3.3; } # loop checker - make sure that the "$co" we're looking at doesn't # already appear in the plan we're looking at. for ( $i = 0; $i <= $#Addto::plan; $i += 2 ) { my($entry) = "@Addto::plan[$i .. $i+1]"; KuangPlus::pdebug "addto(): loop checker: comparing \"$co\" to \"$entry\"", 3.3; if ($entry eq $co ) { KuangPlus::pdebug "addto(): loop found \"$entry\" eq \"$co\". ", 3.3; KuangPlus::pdebug "addto(): loop: \"$co\" is in \"@Addto::plan\" already.", 3.3; return; } } # add this $co to the existing @plan because it is not "known" and is not # a loop. It will be later loaded as a new goal if we haven't already # looked at this @plan push(@Addto::plan, "$co"); # check to see if the plan has been done if ( defined($Engine::beendone{"@Addto::plan"} ) ) { KuangPlus::pdebug "addto(): This plan, \"@Addto::plan\", has been done.", 3.3; return; } # This plan hasn't been done already, so store it in the "beendone" # array ... $Engine::beendone{"@Addto::plan"} = 1; # and load it to "new" as it is worth pursuing further. push(@Engine::new, "@Addto::plan"); KuangPlus::pdebug "addto(): Adding \"@Addto::plan\" to \@Engine::new plan array.", 3.3; } # end of addto() # ------------------ # init_kuang() # Creates other data structures as required. # # Calls: # Called by: evaluate_plans() # Expects: Nothing. # Returns: # Global references: sets $KuangPlus::goal # Errors: prints to STDERR if plan doesn't have the correct ingredients. # sub init_kuang { my($plans_r) = $_[0]; KuangPlus::pdebug "init_kuang: plans are ".keys(%$plans_r), 4.2; $KuangPlus::goal = "u 0"; # load pre-generated plans to search space. The pregenerated plans # come from the scripts in the "rules/" subdirectory. By loading the # plans into a new "search_space" array, we get the opportunity to # discard anything that doesn't appear to be sensible. In this context, # a rule which contains the goal on the RHS is silly to pursue i.e. we # are looking for exploits to achieve the goal so it makes no sense to # pursue a rule which requires the goal as a pre-existing condition. foreach (keys(%$plans_r)) { # The "next" line is only useful if we are reading from a file. # next if ( /^#/ || /^$/ ); # ignore blank lines and comments if ( $_ =~ /^[ugrw] [^ \t]+ $KuangPlus::goal$/ ) { KuangPlus::pdebug "Goal is on RHS of plan. Skipping \"$_\"", 4.6; } else { KuangPlus::pdebug "init_kuang: loading \"$_\" to search space", 4.2; push(@Engine::search_space,$_); } } KuangPlus::pdebug "init_kuang: search_space is \"@Engine::search_space\"", 4.3; } # ------------------ # evaluate_plans() # Main component of Engine # # Calls: init_kuang(), KuangPlus::pdebug() # Called by: KuangPlus:: # Expects: # Returns: nothing # Side effects: prints out successful attacks if encountered. # External references: uses $KuangPlus::goal. # Errors: # sub evaluate_plans { my($plans_r) = $_[0]; my($s_plan,$o_plan); my(@old); init_kuang($plans_r); KuangPlus::pdebug "goal: \"$KuangPlus::goal\"", 5.3; if ( $KuangPlus::goal =~ /^([ugrw]) ([^ \t]+)$/ ) { # initial setup for a new goal @old = (); @Engine::new = (); %Engine::beendone = (); # As a side effect of the next "addto()" call, the "@Engine::new" # variable is set to the goal. Note that $1 and $2 are from the # regular expression match in the "if" above. addto($1, $2); # The following "while" loop will continue trying to match the RHS # of the goal(s) with the LHS of plans in the search space. while ( $#Engine::new >= 0 ) { KuangPlus::pdebug "Starting search.".@Engine::new." goals: \"@Engine::new\"", 5.3; @old = @Engine::new; @Engine::new = (); foreach $o_plan (@old) { my(@o_plan) = split(' ',$o_plan); my($o_op) = $o_plan[$#o_plan-1]; my($o_ob) = $o_plan[$#o_plan]; KuangPlus::pdebug "* matching for goal \"$o_plan\" ...", 5.3; foreach $s_plan ( @Engine::search_space ) { my(@s_plan) = split(' ',$s_plan); my($s_op) = $s_plan[0]; my($s_ob) = $s_plan[1]; KuangPlus::pdebug "** search space:\"$s_plan\"", 5.3; KuangPlus::pdebug "** goal:\"$o_plan\"", 5.3; # in order to make the regexp work, the regexp has to # be on the RHS of the test. However, there is a chance # it will appear on either side so we have to do two # tests, where the second has the LHS and RHS # transposed in relation to the first. Note the use of # start '^' and end '$' characters on RHS to stop # sub-string matches. if (( "$o_op $o_ob" =~ /^$s_op $s_ob$/ ) || ( "$s_op $s_ob" =~ /^$o_op $o_ob$/ )) { KuangPlus::pdebug "*** found a match, calling addto()", 5.3; my($s_lop) = $s_plan[$#s_plan-1]; my($s_lob) = $s_plan[$#s_plan]; addto($s_lop,$s_lob,"@o_plan"); } } } } return @Engine::successful_exploits; } else { print STDERR "bad goal \"$KuangPlus::goal\""; } } 1;