├── README.md ├── requirements.txt └── scan.py /README.md: -------------------------------------------------------------------------------- 1 | # Antiscan 2 | This script makes use of the Antiscan.me service, a site that allows you to scan your binaries with multiple antivirus engines without distributing them. It is meant to make the scanning process easier and more convenient. 3 | ## Requirements 4 | Required packages: 5 | ```txt 6 | requests 7 | colorama 8 | beautifulsoup4 9 | ``` 10 | You also need a working Antiscan.me **API Key** to perform scans. 11 | ## Usage 12 | ```txt 13 | usage: scan.py [-h] [-f FILE] [-img] key 14 | 15 | Antiscan.me automatization script 16 | 17 | positional arguments: 18 | key Your Antiscan.me API Key 19 | 20 | optional arguments: 21 | -h, --help show this help message and exit 22 | -f FILE, --file FILE File to scan 23 | -img, --image Save the scan result as an image 24 | ``` 25 | ## Supported file types 26 | Antiscan.me supports the following file types: 27 | 28 | | Extension | Description | 29 | |-----------|---------------------------------| 30 | | exe | Executable files | 31 | | dll | Dynamic Link Libraries | 32 | | bin | Binary files | 33 | | msi | Windows Installer Packages | 34 | | doc, docx | Microsoft Word Documents | 35 | | rtf | Rich Text Format files | 36 | | xls, xlsx | Microsoft Excel Spreadsheets | 37 | | pdf | Printable Document Format Files | 38 | | js | JavaScript files | 39 | | vbs, vbe | VBScript files | 40 | | ico | Icon files | 41 | 42 | If you want to scan files of a different type, rename them (*.bin) before scanning. 43 | ## Notes 44 | All credit for the scanning service goes to Antiscan.me. 45 | 46 | Free daily scans are not currently supported. 47 | 48 | This script uses the **AVCHECK API**. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.31.0 2 | colorama==0.4.1 3 | beautifulsoup4==4.8.2 4 | -------------------------------------------------------------------------------- /scan.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import argparse 3 | from bs4 import BeautifulSoup 4 | import json 5 | from pathlib import Path 6 | from colorama import init, Fore, Back, Style 7 | 8 | d = 0 # Number of detections 9 | t = 0 # Total number of AVs 10 | 11 | def printResult(av, det): # Function to print and count individual detections 12 | global d 13 | print('{av: <30}'.format(av = av), end = '') 14 | if det == 'Clean': 15 | print(Fore.GREEN + det, end = '') 16 | else: 17 | print(Fore.RED + det, end = '') 18 | d += 1 19 | print(Fore.RESET) 20 | 21 | p = argparse.ArgumentParser(description = "Antiscan.me automatization script") 22 | p.add_argument('key', help = 'Your Antiscan.me API Key') 23 | p.add_argument('-f', '--file', help = 'File to scan') 24 | p.add_argument('-img', '--image', action = 'store_true', help = 'Save the scan result as an image') 25 | args = p.parse_args() 26 | 27 | init() 28 | session = requests.Session() # Initialize a new session 29 | 30 | # Login 31 | res = session.get('https://antiscan.me/login') 32 | soup = BeautifulSoup(res.content, 'html.parser') 33 | csrf = soup.find('meta', attrs={'name': 'csrf-token'})['content'] 34 | 35 | data = { 36 | '_csrf': csrf, 37 | 'LoginForm[auth_key]': args.key, 38 | 'login-button': '' 39 | } 40 | 41 | res = session.post('https://antiscan.me/login', data = data) 42 | if res.url == 'https://antiscan.me/login': 43 | print('Invalid API Key!') 44 | quit() 45 | 46 | # Print account balance 47 | soup = BeautifulSoup(res.content, 'html.parser') 48 | bal = soup.find('a', attrs = {'href': '/user/balance'}).text.strip() 49 | bal = bal[bal.find(' ')+1:] 50 | bal = bal[:bal.find(' ')] 51 | print(Fore.CYAN + 'Account stats for', args.key, Fore.RESET) 52 | print('\tBalance:', bal, 'USD') 53 | print('\tRemaining scans:', int(float(bal)*10)) 54 | print() 55 | 56 | # Scan file 57 | if args.file is None: 58 | quit() 59 | 60 | print(Fore.CYAN + 'Scanning', args.file, Fore.RESET) 61 | files = { 62 | 'FileForm[file]': open(args.file, 'rb') 63 | } 64 | csrf = soup.find('meta', attrs={'name': 'csrf-token'})['content'] 65 | data = { 66 | '_csrf': csrf 67 | } 68 | headers = { 69 | 'X-CSRF-Token': csrf, 70 | 'X-Requested-With': 'XMLHttpRequest', 71 | 'Origin': 'https://antiscan.me', 72 | 'Referer': 'https://antiscan.me/' 73 | } 74 | 75 | res = session.post('https://antiscan.me/scan/new/check', headers=headers, data=data, files=files) 76 | obj = json.loads(res.content) 77 | 78 | # Check for errors 79 | if (obj['status'] == False) or (res.status_code != 200): 80 | print(Fore.RED + 'Scan failed!') 81 | print('\tStatus code:', res.status_code) 82 | print('\tError: ', obj['error']) 83 | quit() 84 | 85 | print('\tScan ID:', Fore.MAGENTA + obj['id'], Fore.RESET) 86 | 87 | # Parse and print results 88 | res = session.get('https://antiscan.me/scan/new/result?id='+obj['id']) 89 | soup = BeautifulSoup(res.content, 'html.parser') 90 | csrf = soup.find('meta', attrs={'name': 'csrf-token'})['content'] 91 | 92 | if args.image: # Save results as an image 93 | headers = { 94 | 'X-CSRF-Token': csrf, 95 | 'X-Requested-With': 'XMLHttpRequest', 96 | 'Referer': 'https://antiscan.me/scan/new/result?id='+obj['id'] 97 | } 98 | res1 = session.get('https://antiscan.me/scan/new/image?id='+obj['id'], headers=headers) # Generate image 99 | res1 = session.get('https://antiscan.me/images/result/'+obj['id']+'.png') # Download image 100 | f = open(Path(args.file).stem+'.png', 'wb') 101 | f.write(res1.content) 102 | f.close() 103 | print('\tImage saved.') 104 | 105 | print() 106 | 107 | s = str(res.content).replace('', '') # Antiscan.me generates malformed HTML by default, we need to fix that 108 | s = s.replace('\\n', '\n') 109 | soup = BeautifulSoup(s, 'html.parser') 110 | line = soup.find('div', attrs = {'class': 'flatLineScanResult'}).text # First line of results 111 | line += soup.find('div', attrs = {'class': 'adjustLineScanResult'}).text # Second line of results 112 | for l in line.splitlines(): 113 | l = l.strip() 114 | if len(l) == 0: 115 | continue # Ignore whitespace 116 | t += 1 117 | av, det = l.split(': ', 1) 118 | printResult(av, det) 119 | print() 120 | print('Detected by ', end = '') 121 | if d == 0: 122 | print(Fore.GREEN, end = '') 123 | else: 124 | print(Fore.RED, end = '') 125 | print(d, '/', t, ' (', '{0:.1f}'.format(d*100/t), '%)', Fore.RESET, sep = '') 126 | print() --------------------------------------------------------------------------------