get_contents( $file ); $this->content = $content; foreach ( $rules as $rule ) { $strings = $rule['strings']; $local_issues = array( 'identifier' => $rule['identifier'], 'catches' => array(), ); foreach ( $strings as $string ) { $offset = false; if ( 0 === $string['type'] ) { $offset = strpos( $content, $string['text'] ); $text = $string['text']; } else { $pattern = "/{$string['text']}/"; $is_match = preg_match_all( $pattern, $content, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE ); if ( $is_match ) { $offset = $matches[0][0][1]; $text = $matches[0][0][0]; } } if ( false === $offset ) { continue; } $local_issues['catches'][] = array( 'id' => $string['id'], 'text' => $text, 'offset' => $offset, ); } if ( array() === $local_issues['catches'] ) { continue; } if ( $this->condition_valid( $local_issues['catches'], $rule ) ) { ++$total_issues; $qs_detail[] = $local_issues; } } } return array( $total_issues, $qs_detail ); } /** * Checks if the condition is valid based on the given rule. * * @param array $found The found items. * @param array $rule The rule to check against. * * @return bool Returns true if the condition is valid, false otherwise. */ private function condition_valid( $found, $rule ): bool { $condition = explode( 'and', $rule['condition'] ); $count = 0; foreach ( $condition as $item ) { if ( preg_match( '/([a-z0-9]+)\s*of\s*(.*)/', $item, $match ) ) { ++$count; $ret = $this->x_of_them( $found, $match, $rule ); if ( false === $ret ) { return false; } } if ( preg_match( '/(\$[a-zA-Z0-9]+)\s*at\s*(\d+)/', $item, $match ) ) { ++$count; $ret = $this->id_at_pos( $found, $match, $rule ); if ( false === $ret ) { return false; } } } if ( 0 === $count ) { $this->log( $rule['identifier'], Malware_Scan::MALWARE_LOG ); $this->log( $found, Malware_Scan::MALWARE_LOG ); return false; } return true; } /** * Determines if a certain number of items in an array meet a certain condition. * * @param array $found The array of items to check. * @param array $check_info The match array containing the number of items to check and the condition. * @param array $rule The rule array containing the strings to check against. * * @return bool Returns true if the number of items that meet the condition is greater than or equal to the * specified number, false otherwise. */ private function x_of_them( $found, $check_info, $rule ): bool { $num = $check_info[1]; $num = 'all' === $num ? count( $rule['strings'] ) : $num; $num = 'any' === $num ? 1 : $num; if ( '(s*)' === $check_info[2] ) { // Hard code in this situation. --$num; } /** * Todo: uncomment the following lines if you need to get log details. * $this->log( sprintf( 'num %s', $num ), Malware_Scan::MALWARE_LOG ); * $this->log( sprintf( 'count %s', count( $found ) ), Malware_Scan::MALWARE_LOG ); */ return ( is_array( $found ) || $found instanceof Countable ? count( $found ) : 0 ) >= $num; } /** * Checks if the given ID is at the specified position in the content. * * @param array $found The array of items to check. * @param array $check_info The match array containing the ID and position to check. * @param array $rule The rule array containing the strings to check against. * * @return bool Returns true if the ID is at the specified position in the content, false otherwise. */ private function id_at_pos( $found, $check_info, $rule ): bool { // Get the text to check. foreach ( $found as $item ) { if ( $item['id'] === $check_info[1] ) { return strpos( $this->content, $item['text'] ) === (int) $check_info[2]; } } return false; } }