content = $content; $total_issues = 0; $qs_detail = []; foreach ( $rules as $rule ) { $strings = $rule['strings']; $local_issues = [ 'identifier' => $rule['identifier'], 'catches' => [] ]; foreach ( $strings as $string ) { $offset = false; if ( $string['type'] == 0 ) { $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'] [] = [ 'id' => $string['id'], 'text' => $text, 'offset' => $offset ]; } if ( empty( $local_issues['catches'] ) ) { continue; } if ( $this->condition_valid( $local_issues['catches'], $rule ) ) { $total_issues ++; $qs_detail[] = $local_issues; } } return [ $total_issues, $qs_detail ]; } /** * Parse the condition to see if this is an issue or not. * * @param $found * @param $rule * * @return bool */ private function condition_valid( $found, $rule ) { $condition = explode( 'and', $rule['condition'] ); $count = 0; foreach ( $condition as $item ) { $patterns = [ 'x_of_them' => '/([a-z0-9]+)\s*of\s*(.*)/', 'id_at_pos' => '/(\$[a-zA-Z0-9]+)\s*at\s*(\d+)/', //'not_x' => '/not\s*(\$[a-z0-9]+)/' ]; foreach ( $patterns as $func => $pattern ) { if ( preg_match( $pattern, $item, $match ) ) { $count ++; $ret = $this->$func( $found, $match, $rule ); if ( false === $ret ) { return false; } } } } if ( $count === 0 ) { $this->log( $rule['identifier'], 'malware_scan.log' ); $this->log( var_export( $found, true ), 'malware_scan.log' ); return false; } return true; } /** * This will parse the condition x of y. * * @param $found * @param $match * @param $rule * * @return bool */ private function x_of_them( $found, $match, $rule ) { $num = $match[1]; $num = 'all' === $num ? count( $rule['strings'] ) : $num; $num = 'any' === $num ? 1 : $num; if ( '(s*)' == $match[2] ) { // Hard code in this situation. $num -= 1; } //$this->log( sprintf( 'num %s', $num ), 'malware_scan.log' ); //$this->log( sprintf( 'count %s', count( $found ) ), 'malware_scan.log' ); return (is_array($found) || $found instanceof \Countable ? count( $found ) : 0) >= $num; } /** * @param $found * @param $match * @param $rule * * @return bool */ private function id_at_pos( $found, $match, $rule ) { // Get the text to check. foreach ( $found as $item ) { if ( $item['id'] === $match[1] ) { return strpos( $this->content, $item['text'] ) == $match[2]; } } return false; } private function not_x( $found, $match, $rule ) { $this->log( 'not x', 'malware_scan.log' ); $this->log( var_export( $match, true ), 'malware_scan.log' ); return true; } }