social_engineering_detector.py
Social Engineering Detector This script analyzes phone calls, text messages, and in-person interactions for social engineering indicators.
- detection
- python
- 721 lines
Run security tools only on systems you own or have written permission to test.
Run it
From the repository root, after
pip install -r tools/requirements.txtcd 04-social-engineering/pretexting/detection python3 social_engineering_detector.py --help
Source
#!/usr/bin/env python3"""Social Engineering DetectorThis script analyzes phone calls, text messages, and in-person interactionsfor social engineering indicators.""" import reimport jsonimport argparseimport timefrom datetime import datetimeimport phonenumbersfrom phonenumbers import carrier, geocoder, timezoneimport requestsfrom colorama import init, Fore, Style init(autoreset=True) class SocialEngineeringDetector: """ Social Engineering Detection System """ def __init__(self): self.risk_score = 0 self.findings = [] # Social engineering patterns self.urgency_patterns = [ r'urgent|immediate|asap|right away', r'account.*(suspended|closed|limited)', r'legal.*action|lawsuit|court', r'warrant.*arrest|police', r'(24|48) hours', r'deadline|expires?', r'final.*warning|last chance' ] self.financial_patterns = [ r'credit card|debit card', r'bank account|routing number', r'social security|ssn', r'wire transfer|western union', r'gift card|itunes card', r'paypal|venmo|cash app', r'verify.*(pin|password|code)', r'atm|bitcoin|cryptocurrency' ] self.authority_patterns = [ r'irs|tax|treasury', r'social security administration', r'fbi|cia|dhs|homeland security', r'court|judge|attorney', r'police|sheriff|law enforcement', r'microsoft|apple|google support', r'tech support|customer service', r'fraud department|security team' ] self.scare_tactics = [ r'virus.*detected|infected', r'identity theft|stolen identity', r'hacked|compromised', r'criminal.*activity', r'investigation|audit', r'deportation|immigration', r'prison|jail|arrest' ] self.generic_greetings = [ r'dear (customer|user|account holder)', r'valued (customer|member)', r'hello (sir|madam)', r'to whom it may concern', r'dear (friend|neighbor)' ] self.suspicious_requests = [ r'confirm.*(identity|information)', r'verify.*(account|details)', r'update.*(records|profile)', r'click.*link', r'download.*(software|app)', r'install.*(program|update)', r'call.*number', r'text.*code' ] def analyze_phone_call(self, caller_id, conversation_text, duration=None): """Analyze a phone call for social engineering""" results = { 'type': 'phone_call', 'timestamp': datetime.now().isoformat(), 'caller_id': caller_id, 'duration': duration, 'risk_score': 0, 'risk_level': 'LOW', 'findings': [], 'recommendations': [] } # Analyze caller ID caller_analysis = self.analyze_caller_id(caller_id) results.update(caller_analysis) # Analyze conversation if conversation_text: text_analysis = self.analyze_text(conversation_text) results['findings'].extend(text_analysis['findings']) results['risk_score'] += text_analysis['risk_score'] # Check for robocall indicators if self.is_robocall(conversation_text): results['findings'].append({ 'type': 'robocall', 'severity': 'HIGH', 'description': 'Conversation has robocall characteristics' }) results['risk_score'] += 30 # Calculate final risk level results['risk_level'] = self.get_risk_level(results['risk_score']) # Generate recommendations results['recommendations'] = self.generate_recommendations(results) return results def analyze_text_message(self, sender, message_text): """Analyze a text message for social engineering""" results = { 'type': 'text_message', 'timestamp': datetime.now().isoformat(), 'sender': sender, 'message_length': len(message_text), 'risk_score': 0, 'risk_level': 'LOW', 'findings': [], 'recommendations': [] } # Analyze sender if self.is_suspicious_sender(sender): results['findings'].append({ 'type': 'suspicious_sender', 'severity': 'HIGH', 'description': f'Sender appears suspicious: {sender}' }) results['risk_score'] += 20 # Analyze message text_analysis = self.analyze_text(message_text) results['findings'].extend(text_analysis['findings']) results['risk_score'] += text_analysis['risk_score'] # Check for shortened URLs urls = self.extract_urls(message_text) for url in urls: if self.is_shortened_url(url): results['findings'].append({ 'type': 'shortened_url', 'severity': 'MEDIUM', 'description': f'Message contains shortened URL: {url}' }) results['risk_score'] += 15 # Calculate risk level results['risk_level'] = self.get_risk_level(results['risk_score']) # Generate recommendations results['recommendations'] = self.generate_recommendations(results) return results def analyze_email(self, sender, subject, body): """Analyze an email for social engineering""" results = { 'type': 'email', 'timestamp': datetime.now().isoformat(), 'sender': sender, 'subject': subject, 'body_length': len(body), 'risk_score': 0, 'risk_level': 'LOW', 'findings': [], 'recommendations': [] } # Analyze sender if self.is_suspicious_sender(sender): results['findings'].append({ 'type': 'suspicious_sender', 'severity': 'HIGH', 'description': f'Sender appears suspicious: {sender}' }) results['risk_score'] += 20 # Analyze subject subject_analysis = self.analyze_text(subject) results['findings'].extend(subject_analysis['findings']) results['risk_score'] += subject_analysis['risk_score'] # Analyze body body_analysis = self.analyze_text(body) results['findings'].extend(body_analysis['findings']) results['risk_score'] += body_analysis['risk_score'] # Calculate risk level results['risk_level'] = self.get_risk_level(results['risk_score']) # Generate recommendations results['recommendations'] = self.generate_recommendations(results) return results def analyze_in_person_interaction(self, description, visitor_info=None): """Analyze an in-person interaction for social engineering""" results = { 'type': 'in_person', 'timestamp': datetime.now().isoformat(), 'visitor_info': visitor_info or {}, 'description': description[:200] + '...' if len(description) > 200 else description, 'risk_score': 0, 'risk_level': 'LOW', 'findings': [], 'recommendations': [] } # Check for common pretexting scenarios scenarios = self.identify_scenarios(description) for scenario in scenarios: results['findings'].append({ 'type': 'scenario_match', 'severity': scenario['severity'], 'description': f'Matches {scenario["name"]} scenario: {scenario["description"]}' }) results['risk_score'] += scenario['score'] # Analyze visitor info if available if visitor_info: if not visitor_info.get('id_verified'): results['findings'].append({ 'type': 'unverified_id', 'severity': 'HIGH', 'description': 'Visitor ID not verified' }) results['risk_score'] += 20 if visitor_info.get('no_appointment'): results['findings'].append({ 'type': 'no_appointment', 'severity': 'MEDIUM', 'description': 'Visitor has no appointment' }) results['risk_score'] += 10 # Calculate risk level results['risk_level'] = self.get_risk_level(results['risk_score']) # Generate recommendations results['recommendations'] = self.generate_recommendations(results) return results def analyze_caller_id(self, caller_id): """Analyze caller ID information""" results = { 'caller_id_analysis': {}, 'risk_score': 0 } # Try to parse phone number try: if caller_id and caller_id != 'Unknown' and caller_id != 'Blocked': parsed = phonenumbers.parse(caller_id, 'US') results['caller_id_analysis'] = { 'valid': phonenumbers.is_valid_number(parsed), 'possible': phonenumbers.is_possible_number(parsed), 'country': geocoder.description_for_number(parsed, 'en'), 'carrier': carrier.name_for_number(parsed, 'en'), 'timezone': timezone.time_zones_for_number(parsed), 'type': 'mobile' if carrier.name_for_number(parsed, 'en') else 'landline' } # Check for VoIP (often used by scammers) if 'VOIP' in results['caller_id_analysis'].get('carrier', '').upper(): results['findings'] = { 'type': 'voip_caller', 'severity': 'MEDIUM', 'description': 'Caller uses VoIP service (common for scammers)' } results['risk_score'] += 15 except Exception: results['caller_id_analysis'] = { 'valid': False, 'note': 'Could not parse number' } # Suspicious caller ID formats if caller_id in ['Unknown', 'Blocked', 'Private', 'Restricted']: results['findings'] = { 'type': 'hidden_caller_id', 'severity': 'HIGH', 'description': f'Caller ID is hidden: {caller_id}' } results['risk_score'] += 25 elif re.match(r'^\d{1,3}$', caller_id): # Very short numbers results['findings'] = { 'type': 'suspicious_caller_id', 'severity': 'MEDIUM', 'description': f'Suspicious caller ID format: {caller_id}' } results['risk_score'] += 20 return results def analyze_text(self, text): """Analyze text for social engineering patterns""" results = { 'findings': [], 'risk_score': 0 } text_lower = text.lower() # Check for urgency patterns for pattern in self.urgency_patterns: if re.search(pattern, text_lower): results['findings'].append({ 'type': 'urgency', 'severity': 'HIGH', 'description': f'Contains urgency language: {pattern}' }) results['risk_score'] += 15 # Check for financial requests for pattern in self.financial_patterns: if re.search(pattern, text_lower): results['findings'].append({ 'type': 'financial_request', 'severity': 'CRITICAL', 'description': f'Requests financial information: {pattern}' }) results['risk_score'] += 25 # Check for authority claims for pattern in self.authority_patterns: if re.search(pattern, text_lower): results['findings'].append({ 'type': 'authority_claim', 'severity': 'HIGH', 'description': f'Claims authority: {pattern}' }) results['risk_score'] += 20 # Check for scare tactics for pattern in self.scare_tactics: if re.search(pattern, text_lower): results['findings'].append({ 'type': 'scare_tactic', 'severity': 'HIGH', 'description': f'Uses scare tactics: {pattern}' }) results['risk_score'] += 20 # Check for generic greetings for pattern in self.generic_greetings: if re.search(pattern, text_lower): results['findings'].append({ 'type': 'generic_greeting', 'severity': 'MEDIUM', 'description': f'Uses generic greeting: {pattern}' }) results['risk_score'] += 10 # Check for suspicious requests for pattern in self.suspicious_requests: if re.search(pattern, text_lower): results['findings'].append({ 'type': 'suspicious_request', 'severity': 'HIGH', 'description': f'Makes suspicious request: {pattern}' }) results['risk_score'] += 15 return results def is_suspicious_sender(self, sender): """Check if sender appears suspicious""" suspicious_patterns = [ r'@.*\.(tk|ml|ga|cf|gq|xyz|top|win)$', r'noreply@', r'no-reply@', r'service@', r'help@', r'security@', r'admin@', r'support@' ] sender_lower = sender.lower() for pattern in suspicious_patterns: if re.search(pattern, sender_lower): return True return False def is_robocall(self, conversation): """Check if conversation has robocall characteristics""" indicators = 0 # Check for scripted responses if conversation and len(conversation) < 50: # Very short indicators += 1 # Check for repeated phrases words = conversation.lower().split() if len(set(words)) < len(words) * 0.7: # High repetition indicators += 1 # Check for unnatural pauses (would need audio analysis) return indicators >= 2 def extract_urls(self, text): """Extract URLs from text""" url_pattern = r'https?://[^\s<>"\'(){}|\\^`\[\]]+|bit\.ly/[^\s]+|tinyurl\.com/[^\s]+' return re.findall(url_pattern, text, re.IGNORECASE) def is_shortened_url(self, url): """Check if URL is shortened""" shorteners = ['bit.ly', 'tinyurl.com', 'goo.gl', 'ow.ly', 'is.gd', 'buff.ly', 'short.link'] return any(shortener in url for shortener in shorteners) def identify_scenarios(self, description): """Identify common pretexting scenarios""" scenarios = [] desc_lower = description.lower() # Tech support scenario if any(word in desc_lower for word in ['virus', 'infected', 'tech support', 'microsoft', 'apple']): scenarios.append({ 'name': 'Tech Support Scam', 'severity': 'HIGH', 'score': 25, 'description': 'Claims computer has viruses or problems' }) # Government impersonation if any(word in desc_lower for word in ['irs', 'tax', 'fbi', 'police', 'warrant', 'court']): scenarios.append({ 'name': 'Government Impersonation', 'severity': 'CRITICAL', 'score': 30, 'description': 'Claims to be from government agency' }) # Charity scam if any(word in desc_lower for word in ['charity', 'donation', 'fundraiser', 'non-profit']): scenarios.append({ 'name': 'Charity Scam', 'severity': 'MEDIUM', 'score': 20, 'description': 'Soliciting donations for fake charity' }) # Romance scam if any(word in desc_lower for word in ['dating', 'relationship', 'love', 'romance', 'single']): scenarios.append({ 'name': 'Romance Scam', 'severity': 'HIGH', 'score': 25, 'description': 'Building romantic relationship for financial gain' }) # Grandparent scam if any(word in desc_lower for word in ['grandchild', 'grandson', 'granddaughter', 'family emergency']): scenarios.append({ 'name': 'Grandparent Scam', 'severity': 'HIGH', 'score': 25, 'description': 'Posing as family member in distress' }) # Lottery scam if any(word in desc_lower for word in ['lottery', 'prize', 'won', 'sweepstakes', 'winnings']): scenarios.append({ 'name': 'Lottery Scam', 'severity': 'MEDIUM', 'score': 20, 'description': 'Claims you won a prize requiring payment' }) return scenarios def get_risk_level(self, score): """Get risk level from score""" if score >= 70: return 'CRITICAL' elif score >= 50: return 'HIGH' elif score >= 30: return 'MEDIUM' else: return 'LOW' @staticmethod def _dedupe_recommendations(recommendations): """Keep the first recommendation for each action (the same advice was printed twice).""" seen, unique = set(), [] for rec in recommendations: if rec['action'] not in seen: seen.add(rec['action']) unique.append(rec) return unique def generate_recommendations(self, results): return self._dedupe_recommendations(self._build_recommendations(results)) def _build_recommendations(self, results): """Generate recommendations based on findings""" recommendations = [] if results['risk_level'] in ['CRITICAL', 'HIGH']: recommendations.append({ 'priority': 'IMMEDIATE', 'action': 'Do not engage further', 'details': 'End the conversation immediately' }) recommendations.append({ 'priority': 'IMMEDIATE', 'action': 'Verify through official channels', 'details': 'Contact the organization directly using verified contact information' }) recommendations.append({ 'priority': 'HIGH', 'action': 'Report incident', 'details': 'Report this interaction to appropriate authorities (FTC, FBI, local police)' }) elif results['risk_level'] == 'MEDIUM': recommendations.append({ 'priority': 'MEDIUM', 'action': 'Verify independently', 'details': 'Do not provide information. Verify the request through separate channels.' }) recommendations.append({ 'priority': 'MEDIUM', 'action': 'Document interaction', 'details': 'Take notes on the interaction for future reference' }) else: recommendations.append({ 'priority': 'LOW', 'action': 'Stay vigilant', 'details': 'No immediate threat detected, but remain cautious' }) # Specific recommendations based on findings for finding in results.get('findings', []): if finding['type'] == 'financial_request': recommendations.append({ 'priority': 'CRITICAL', 'action': 'Never share financial information', 'details': 'Legitimate organizations never ask for financial information over phone/email' }) elif finding['type'] == 'urgency': recommendations.append({ 'priority': 'HIGH', 'action': 'Ignore urgency claims', 'details': 'Scammers create urgency to bypass your judgment. Take time to verify.' }) return recommendations def print_results(self, results): """Print analysis results""" print(f"\n{Fore.CYAN}{'='*60}{Style.RESET_ALL}") print(f"{Fore.CYAN}📊 SOCIAL ENGINEERING ANALYSIS RESULTS{Style.RESET_ALL}") print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}") # Basic info print(f"\n{Fore.GREEN}Interaction Type: {results['type'].upper()}{Style.RESET_ALL}") print(f"Timestamp: {results['timestamp']}") # Risk level risk_color = { 'LOW': Fore.GREEN, 'MEDIUM': Fore.YELLOW, 'HIGH': Fore.RED, 'CRITICAL': Fore.RED + Style.BRIGHT }.get(results['risk_level'], Fore.WHITE) print(f"\n{risk_color}Risk Level: {results['risk_level']} (Score: {results['risk_score']}){Style.RESET_ALL}") # Caller/sender info if results['type'] == 'phone_call': print(f"\n{Fore.CYAN}Caller Information:{Style.RESET_ALL}") print(f" Caller ID: {results.get('caller_id', 'Unknown')}") if 'caller_id_analysis' in results: analysis = results['caller_id_analysis'] if analysis.get('valid'): print(f" Country: {analysis.get('country', 'Unknown')}") print(f" Carrier: {analysis.get('carrier', 'Unknown')}") print(f" Type: {analysis.get('type', 'Unknown')}") elif results['type'] == 'text_message': print(f"\n{Fore.CYAN}Message Information:{Style.RESET_ALL}") print(f" Sender: {results.get('sender', 'Unknown')}") print(f" Length: {results.get('message_length', 0)} characters") elif results['type'] == 'email': print(f"\n{Fore.CYAN}Email Information:{Style.RESET_ALL}") print(f" Sender: {results.get('sender', 'Unknown')}") print(f" Subject: {results.get('subject', 'None')}") # Findings if results.get('findings'): print(f"\n{Fore.RED}Findings ({len(results['findings'])}):{Style.RESET_ALL}") for finding in results['findings']: severity_color = { 'LOW': Fore.CYAN, 'MEDIUM': Fore.YELLOW, 'HIGH': Fore.RED, 'CRITICAL': Fore.RED + Style.BRIGHT }.get(finding.get('severity', 'MEDIUM'), Fore.WHITE) print(f" {severity_color}• [{finding['severity']}] {finding['description']}{Style.RESET_ALL}") # Recommendations if results.get('recommendations'): print(f"\n{Fore.GREEN}Recommendations:{Style.RESET_ALL}") for rec in results['recommendations']: priority_color = { 'IMMEDIATE': Fore.RED, 'HIGH': Fore.YELLOW, 'MEDIUM': Fore.CYAN, 'LOW': Fore.GREEN }.get(rec['priority'], Fore.WHITE) print(f" {priority_color}• [{rec['priority']}] {rec['action']}{Style.RESET_ALL}") print(f" {rec['details']}") print(f"\n{Fore.CYAN}{'='*60}{Style.RESET_ALL}") def main(): parser = argparse.ArgumentParser(description='Social Engineering Detector') parser.add_argument('--type', choices=['call', 'sms', 'email', 'in-person'], required=True, help='Type of interaction to analyze') parser.add_argument('--caller', help='Caller ID for phone calls') parser.add_argument('--sender', help='Sender for SMS/email') parser.add_argument('--subject', help='Email subject') parser.add_argument('--message', help='Message/conversation text') parser.add_argument('--description', help='Description of in-person interaction') parser.add_argument('--duration', type=int, help='Call duration in seconds') parser.add_argument('--visitor', help='JSON file with visitor information') args = parser.parse_args() print(f""" {Fore.CYAN}╔═══════════════════════════════════════╗ ║ Social Engineering Detector v1.0 ║ ╚═══════════════════════════════════════╝{Style.RESET_ALL} """) detector = SocialEngineeringDetector() # Load visitor info if provided visitor_info = None if args.visitor: try: with open(args.visitor, 'r') as f: visitor_info = json.load(f) except Exception as e: print(f"{Fore.RED}[!] Error loading visitor info: {e}{Style.RESET_ALL}") # Analyze based on type if args.type == 'call': if not args.caller or not args.message: print(f"{Fore.RED}[!] Phone call analysis requires --caller and --message{Style.RESET_ALL}") return results = detector.analyze_phone_call(args.caller, args.message, args.duration) detector.print_results(results) elif args.type == 'sms': if not args.sender or not args.message: print(f"{Fore.RED}[!] SMS analysis requires --sender and --message{Style.RESET_ALL}") return results = detector.analyze_text_message(args.sender, args.message) detector.print_results(results) elif args.type == 'email': if not args.sender or not args.message: print(f"{Fore.RED}[!] Email analysis requires --sender and --message{Style.RESET_ALL}") return results = detector.analyze_email(args.sender, args.subject or '', args.message) detector.print_results(results) elif args.type == 'in-person': if not args.description: print(f"{Fore.RED}[!] In-person analysis requires --description{Style.RESET_ALL}") return results = detector.analyze_in_person_interaction(args.description, visitor_info) detector.print_results(results) if __name__ == "__main__": main()