├── EmailReader.py ├── EmailSender.py ├── EmailSenderGUI.py ├── EmailSenderGUI.spec ├── README.md ├── email.ico ├── generate_icon.py ├── icon.py ├── img2base64.py ├── read_file.py ├── requirements.txt └── temp.html /EmailReader.py: -------------------------------------------------------------------------------- 1 | import poplib 2 | from email.parser import Parser 3 | import re 4 | 5 | 6 | #退件邮件收件人获取 7 | #连接邮箱服务器 8 | server = poplib.POP3('smtp.xxxx.com') 9 | server.user('xxxx') 10 | server.pass_('xxxx') 11 | 12 | #获取邮件列表 13 | resp, mails, octets = server.list() 14 | 15 | #指定要读取的邮件主题 16 | subject = 'Undelivered Mail Returned to Sender' 17 | 18 | #获取邮件的字符编码,首先在message中寻找编码,如果没有,就在header的Content-Type中寻找 19 | def guess_charset(msg): 20 | charset = msg.get_charset() 21 | if charset is None: 22 | content_type = msg.get('Content-Type', '').lower() 23 | pos = content_type.find('charset=') 24 | if pos >= 0: 25 | charset = content_type[pos+8:].strip() 26 | return charset 27 | 28 | def get_content(msg): 29 | for part in msg.walk(): 30 | content_type = part.get_content_type() 31 | charset = guess_charset(part) 32 | #如果有附件,则直接跳过 33 | if part.get_filename()!=None: 34 | continue 35 | email_content_type = '' 36 | content = '' 37 | if content_type == 'text/plain': 38 | email_content_type = 'text' 39 | elif content_type == 'text/html': 40 | continue #不要html格式的邮件 41 | if charset: 42 | try: 43 | content = part.get_payload(decode=True).decode(charset) 44 | #这里遇到了几种由广告等不满足需求的邮件遇到的错误,直接跳过了 45 | except AttributeError: 46 | print('type error') 47 | except LookupError: 48 | print("unknown encoding: utf-8") 49 | if email_content_type =='': 50 | continue 51 | #如果内容为空,也跳过 52 | return content 53 | #邮件的正文内容就在content中 54 | 55 | 56 | if __name__ == '__main__': 57 | f = open("reject.txt", 'a+') 58 | #遍历邮件列表 59 | for i in range(len(mails), 0, -1): 60 | resp, lines, octets = server.retr(i) 61 | #合并邮件内容 62 | message = b'\n'.join(lines).decode('utf-8') 63 | # 将邮件内容解析成Message对象 64 | emailcontent = Parser().parsestr(message) 65 | #如果主题匹配,则输出邮件内容 66 | if subject == emailcontent['Subject'] : 67 | content = get_content(emailcontent) 68 | match = re.findall(r'<.*?>', content) 69 | result = match[0].lstrip('<').rstrip('>') 70 | print(result) 71 | f.write(result) 72 | f.write("\n") 73 | # break 74 | f.close() 75 | 76 | 77 | #关闭连接 78 | server.quit() -------------------------------------------------------------------------------- /EmailSender.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | import email 3 | from email.mime.application import MIMEApplication 4 | from email.mime.multipart import MIMEMultipart 5 | from email.mime.text import MIMEText 6 | from email.header import Header 7 | from email.utils import formataddr 8 | import time 9 | import os 10 | from datetime import datetime 11 | 12 | cur_path = os.path.dirname(os.path.realpath(__file__)) # 当前项目路径 13 | # cur_path = os.path.dirname(sys.executable) # 打包运行路径 14 | log_path = cur_path + '\\logs' # log_path为存放日志的路径 15 | if not os.path.exists(log_path): os.mkdir(log_path) # 若不存在logs文件夹,则自动创建 16 | 17 | 18 | def File_Read(file_path): 19 | global Lines 20 | Lines = [] 21 | with open(file_path, 'r') as f: 22 | while True: 23 | line = f.readline() # 逐行读取 24 | if not line: # 到 EOF,返回空字符串,则终止循环 25 | break 26 | File_Data(line, flag=1) 27 | File_Data(line, flag=0) 28 | 29 | 30 | def File_Data(line, flag): 31 | global Lines 32 | if flag == 1: 33 | Lines.append(line) 34 | else: 35 | return Lines 36 | 37 | 38 | class Log: 39 | 40 | def __init__(self): 41 | now_time = datetime.now().strftime('%Y-%m-%d') 42 | self.__all_log_path = os.path.join(log_path, now_time + "-all" + ".log") # 收集所有日志信息文件 43 | self.__error_log_path = os.path.join(log_path, now_time + "-error" + ".log") # 收集错误日志信息文件 44 | self.__send_error_log_path = os.path.join(log_path, now_time + "-send_error" + ".log") # 收集发送失败邮箱信息文件 45 | self.__send_done_log_path = os.path.join(log_path, now_time + "-send_done" + ".log") # 收集发送成功邮箱信息文件 46 | 47 | def SaveAllLog(self, message): 48 | with open(r"{}".format(self.__all_log_path), 'a+') as f: 49 | f.write(message) 50 | f.write("\n") 51 | f.close() 52 | 53 | def SaveErrorLog(self, message): 54 | with open(r"{}".format(self.__error_log_path), 'a+') as f: 55 | f.write(message) 56 | f.write("\n") 57 | f.close() 58 | 59 | def SaveSendErrorLog(self, message): 60 | with open(r"{}".format(self.__send_error_log_path), 'a+') as f: 61 | f.write(message) 62 | f.write("\n") 63 | f.close() 64 | 65 | def SaveSendDoneLog(self, message): 66 | with open(r"{}".format(self.__send_done_log_path), 'a+') as f: 67 | f.write(message) 68 | f.write("\n") 69 | f.close() 70 | 71 | def tips(self, message): 72 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 73 | print(now_time_detail + ("\033[34m [TIPS]: {}\033[0m").format(message)) 74 | self.SaveAllLog(now_time_detail + (" [TIPS]: {}").format(message)) 75 | 76 | def info(self, message): 77 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 78 | print(now_time_detail + (" [INFO]: {}").format(message)) 79 | self.SaveAllLog(now_time_detail + (" [INFO]: {}").format(message)) 80 | 81 | def warning(self, message): 82 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 83 | print(now_time_detail + ("\033[33m [WARNING]: {}\033[0m").format(message)) 84 | self.SaveAllLog(now_time_detail + (" [WARNING]: {}").format(message)) 85 | 86 | def error(self, message): 87 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 88 | print(now_time_detail + ("\033[31m [ERROR]: {}\033[0m").format(message)) 89 | self.SaveAllLog(now_time_detail + (" [ERROR]: {}").format(message)) 90 | self.SaveErrorLog(now_time_detail + (" [ERROR]: {}").format(message)) 91 | 92 | def send_error(self, message): 93 | self.SaveSendErrorLog(("{}").format(message)) 94 | 95 | def done(self, message): 96 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 97 | print(now_time_detail + ("\033[32m [DONE]: {}\033[0m").format(message)) 98 | self.SaveAllLog(now_time_detail + (" [DONE]: {}").format(message)) 99 | 100 | def send_done(self, message): 101 | self.SaveSendDoneLog(("{}").format(message)) 102 | 103 | 104 | Log = Log() 105 | Lines = [] 106 | 107 | 108 | class EmailSender: 109 | def __init__(self, Subject, From, Content, smtp_user, smtp_passwd, smtp_server, Server=None, email_list=None, 110 | file=None, sleep=0.5): 111 | self.Subject = Subject 112 | self.From = From 113 | self.Content = Content 114 | self.file = file 115 | self.smtp_user = smtp_user 116 | self.smtp_passwd = smtp_passwd 117 | self.smtp_server = smtp_server 118 | self.email_list = email_list 119 | self.Server = Server 120 | self.sleep = sleep 121 | 122 | def Sender(self): 123 | global Lines, client 124 | if not os.path.exists(r"{}".format(self.email_list)): 125 | Log.error('收件人列表文件未找到!') 126 | return 127 | File_Read(self.email_list) # 读取全部内容 ,并以列表方式返回 128 | 129 | for line in Lines: 130 | rcptto = [] 131 | rcptto.append(line.rstrip("\n")) 132 | self.victim = line.split('@')[0] 133 | # 显示的Cc收信地址 134 | rcptcc = [] 135 | # Bcc收信地址,密送人不会显示在邮件上,但可以收到邮件 136 | rcptbcc = [] 137 | # 全部收信地址,包含抄送地址,单次发送不能超过60人 138 | receivers = rcptto + rcptcc + rcptbcc 139 | 140 | # 参数判断 141 | if self.Subject == None or self.Subject == "": 142 | Log.error('邮件主题不存在!') 143 | return 144 | if self.smtp_user == None or self.smtp_user == "": 145 | Log.error('SMTP账号不存在!') 146 | return 147 | if self.smtp_passwd == None or self.smtp_passwd == "": 148 | Log.error('SMTP密码不存在!') 149 | return 150 | 151 | # 构建alternative结构 152 | msg = MIMEMultipart('alternative') 153 | msg['Subject'] = Header(self.Subject) 154 | if self.From[0] == None or self.From[0] == '': 155 | Log.error('发件人名称不存在!') 156 | return 157 | if self.From[1] == None or self.From[1] == '': 158 | self.From[1] = self.smtp_user 159 | msg['From'] = formataddr(self.From) # 昵称+发信地址(或代发) 160 | # list转为字符串 161 | msg['To'] = ",".join(rcptto) 162 | msg['Cc'] = ",".join(rcptcc) 163 | # 自定义的回信地址,与控制台设置的无关。邮件推送发信地址不收信,收信人回信时会自动跳转到设置好的回信地址。 164 | # msg['Reply-to'] = replyto 165 | msg['Message-id'] = email.utils.make_msgid() 166 | msg['Date'] = email.utils.formatdate() 167 | 168 | email_content = self.Content.replace("victim", "victim=" + self.victim) 169 | 170 | # 加载远程图片以标记打开邮件的受害者 171 | if self.Server != ':': 172 | # Log.tips("监听地址: {}".format(self.Server)) 173 | listen_code = '' 174 | else: 175 | listen_code = '' 176 | 177 | # 构建alternative的text/html部分 178 | texthtml = MIMEText( 179 | email_content + listen_code, 180 | _subtype='html', _charset='UTF-8') 181 | msg.attach(texthtml) 182 | 183 | # 附件 184 | if self.file != None and self.file != "": 185 | files = [r'{}'.format(self.file)] 186 | for t in files: 187 | part_attach1 = MIMEApplication(open(t, 'rb').read()) # 打开附件 188 | part_attach1.add_header('Content-Disposition', 'attachment', filename=t.rsplit('/', 1)[1]) # 为附件命名 189 | msg.attach(part_attach1) # 添加附件 190 | 191 | # 发送邮件 192 | try: 193 | client = smtplib.SMTP(self.smtp_server, 25) 194 | client.login(self.smtp_user, self.smtp_passwd) 195 | client.sendmail(self.smtp_user, receivers, msg.as_string()) # 支持多个收件人,最多60个 196 | client.quit() 197 | Log.done("{:<30}\t邮件发送成功!".format(rcptto[0])) 198 | Log.send_done("{}".format(rcptto[0])) 199 | time.sleep(self.sleep) 200 | except Exception as e: 201 | Log.error('邮件发送失败, {}'.format(e)) 202 | Log.send_error(rcptto[0]) 203 | 204 | 205 | if __name__ == '__main__': 206 | mail_text_path = "./email/email.html" #邮件正文 207 | mail_to_list = "./target/email.txt" #收件人列表 208 | Log.tips("执行邮件正文读取操作!") 209 | f = open(mail_text_path, "r", encoding='utf-8') 210 | Log.done("邮件正文读取完成!") 211 | body_text = f.read() 212 | Log.tips("执行邮件发送操作!") 213 | E = EmailSender("【重要】数据迁移的通知", ['信息安全部', 'admin@admin.com'], body_text, "admin@admin.com", "pass", 214 | "smtp.admin.com", "html.admin.com:8080", mail_to_list) 215 | E.Sender() 216 | Log.done("邮件发送完成!") 217 | -------------------------------------------------------------------------------- /EmailSenderGUI.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import smtplib 3 | from tkinter.ttk import Combobox 4 | 5 | import email 6 | from email.mime.application import MIMEApplication 7 | from email.mime.multipart import MIMEMultipart 8 | from email.mime.text import MIMEText 9 | from email.header import Header 10 | from email.utils import formataddr 11 | from email.parser import Parser 12 | import time 13 | from tkinter import * 14 | import tkinter.filedialog 15 | import os 16 | from datetime import datetime 17 | import sys 18 | import icon 19 | import re 20 | 21 | class StdoutRedirector(object): 22 | # 重定向输出类 23 | def __init__(self, text_widget): 24 | self.text_space = text_widget 25 | # 将其备份 26 | self.stdoutbak = sys.stdout 27 | self.stderrbak = sys.stderr 28 | 29 | def write(self, str): 30 | self.text_space.tag_config('fc_info', foreground='white') 31 | self.text_space.tag_config('fc_error', foreground='red') 32 | self.text_space.tag_config('fc_done', foreground='green') 33 | self.text_space.tag_config('fc_tips', foreground='cyan') 34 | if 'INFO' in str: 35 | self.text_space.insert(END, str, 'fc_info') 36 | self.text_space.insert(END, '\n') 37 | elif 'ERROR' in str: 38 | self.text_space.insert(END, str, 'fc_error') 39 | self.text_space.insert(END, '\n') 40 | elif 'DONE' in str: 41 | self.text_space.insert(END, str, 'fc_done') 42 | self.text_space.insert(END, '\n') 43 | elif 'TIPS' in str: 44 | self.text_space.insert(END, str, 'fc_tips') 45 | self.text_space.insert(END, '\n') 46 | self.text_space.see(END) 47 | self.text_space.update() 48 | 49 | def restoreStd(self): 50 | # 恢复标准输出 51 | sys.stdout = self.stdoutbak 52 | sys.stderr = self.stderrbak 53 | 54 | def flush(self): 55 | # 关闭程序时会调用flush刷新缓冲区,没有该函数关闭时会报错 56 | pass 57 | 58 | 59 | # cur_path = os.path.dirname(os.path.realpath(__file__)) # 当前项目路径 60 | cur_path = os.path.dirname(sys.executable) # 打包运行路径 61 | log_path = cur_path + '\\logs' # log_path为存放日志的路径 62 | if not os.path.exists(log_path): os.mkdir(log_path) # 若不存在logs文件夹,则自动创建 63 | 64 | 65 | class Log: 66 | 67 | def __init__(self): 68 | now_time = datetime.now().strftime('%Y-%m-%d') 69 | self.__all_log_path = os.path.join(log_path, now_time + "-all" + ".log") # 收集所有日志信息文件 70 | self.__error_log_path = os.path.join(log_path, now_time + "-error" + ".log") # 收集错误日志信息文件 71 | self.__send_error_log_path = os.path.join(log_path, now_time + "-send_error" + ".log") # 收集发送失败邮箱信息文件 72 | self.__send_done_log_path = os.path.join(log_path, now_time + "-send_done" + ".log") # 收集发送成功邮箱信息文件 73 | 74 | def SaveAllLog(self, message): 75 | with open(r"{}".format(self.__all_log_path), 'a+') as f: 76 | f.write(message) 77 | f.write("\n") 78 | f.close() 79 | 80 | def SaveErrorLog(self, message): 81 | with open(r"{}".format(self.__error_log_path), 'a+') as f: 82 | f.write(message) 83 | f.write("\n") 84 | f.close() 85 | 86 | def SaveSendErrorLog(self, message): 87 | with open(r"{}".format(self.__send_error_log_path), 'a+') as f: 88 | f.write(message) 89 | f.write("\n") 90 | f.close() 91 | 92 | def SaveSendDoneLog(self, message): 93 | with open(r"{}".format(self.__send_done_log_path), 'a+') as f: 94 | f.write(message) 95 | f.write("\n") 96 | f.close() 97 | 98 | def tips(self, message): 99 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 100 | print(now_time_detail + ("[TIPS]: {}").format(message)) 101 | self.SaveAllLog(now_time_detail + (" [TIPS]: {}").format(message)) 102 | 103 | def info(self, message): 104 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 105 | print(now_time_detail + ("[INFO]: {}").format(message)) 106 | self.SaveAllLog(now_time_detail + (" [INFO]: {}").format(message)) 107 | 108 | def warning(self, message): 109 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 110 | print(now_time_detail + ("[WARNING]: {}").format(message)) 111 | self.SaveAllLog(now_time_detail + (" [WARNING]: {}").format(message)) 112 | 113 | def error(self, message): 114 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 115 | print(now_time_detail + ("[ERROR]: {}").format(message)) 116 | self.SaveAllLog(now_time_detail + (" [ERROR]: {}").format(message)) 117 | self.SaveErrorLog(now_time_detail + (" [ERROR]: {}").format(message)) 118 | 119 | def done(self, message): 120 | now_time_detail = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 121 | print(now_time_detail + ("[DONE]: {}").format(message)) 122 | self.SaveAllLog(now_time_detail + (" [DONE]: {}").format(message)) 123 | 124 | def send_done(self, message): 125 | self.SaveSendDoneLog(("{}").format(message)) 126 | 127 | def send_error(self, message): 128 | self.SaveSendErrorLog(("{}").format(message)) 129 | 130 | Log = Log() 131 | stop_flag = 0 132 | start_flag = 0 133 | init_flag = 0 134 | send_index = 0 135 | 136 | 137 | def StopSend(): 138 | global stop_flag, start_flag 139 | stop_flag = 1 140 | start_flag = 0 141 | 142 | 143 | def StartSend(): 144 | global stop_flag, start_flag 145 | stop_flag = 0 146 | start_flag = 1 147 | 148 | 149 | class EmailSender: 150 | def __init__(self, Subject, From, Content, smtp_user, smtp_passwd, smtp_server, Server=None, email_list=None, 151 | test_user=None, 152 | file=None, img=None, sleep=0.5): 153 | self.Subject = Subject 154 | self.From = From 155 | self.Content = Content 156 | self.file = file 157 | self.smtp_user = smtp_user 158 | self.smtp_passwd = smtp_passwd 159 | self.smtp_server = smtp_server 160 | self.email_list = email_list 161 | self.Server = Server 162 | self.test_user = test_user 163 | self.img = img 164 | self.sleep = sleep 165 | 166 | def Sender(self): 167 | global client 168 | global stop_flag, start_flag 169 | global send_index 170 | if self.test_user != None and self.test_user != '': 171 | lines = [self.test_user] 172 | else: 173 | if not os.path.exists(r"{}".format(self.email_list)): 174 | Log.error('收件人列表文件未找到!') 175 | return False 176 | f = open(r"{}".format(self.email_list), "r") 177 | lines = f.readlines() # 读取全部内容 ,并以列表方式返回 178 | 179 | for line in lines[send_index:]: 180 | if stop_flag == 1: 181 | Log.tips("暂停发送,目前成功发送: {} 封".format(send_index)) 182 | Log.tips("发送截至目标: {}".format(line)) 183 | break 184 | if line == "\n" or line == '': 185 | continue 186 | rcptto = [] 187 | rcptto.append(line.rstrip("\n")) 188 | self.victim = line.split('@')[0] 189 | # 显示的Cc收信地址 190 | rcptcc = [] 191 | # Bcc收信地址,密送人不会显示在邮件上,但可以收到邮件 192 | rcptbcc = [] 193 | # 全部收信地址,包含抄送地址,单次发送不能超过60人 194 | receivers = rcptto + rcptcc + rcptbcc 195 | 196 | # 构建alternative结构 197 | msg = MIMEMultipart('alternative') 198 | msg['Subject'] = Header(self.Subject) 199 | if self.From[1] == None or self.From[1] == '': 200 | self.From[1] = self.smtp_user 201 | msg['From'] = formataddr(self.From) # 昵称+发信地址(或代发) 202 | # list转为字符串 203 | msg['To'] = ",".join(rcptto) 204 | msg['Cc'] = ",".join(rcptcc) 205 | # 自定义的回信地址,与控制台设置的无关。邮件推送发信地址不收信,收信人回信时会自动跳转到设置好的回信地址。 206 | # msg['Reply-to'] = replyto 207 | msg['Message-id'] = email.utils.make_msgid() 208 | msg['Date'] = email.utils.formatdate() 209 | 210 | email_content = self.Content 211 | 212 | # base64加载图片 213 | if self.img != None and self.img != '': 214 | with open(r"{}".format(self.img), "rb") as i: 215 | b64str = base64.b64encode(i.read()) 216 | # Log.info(str(b64str)) 217 | img_code = '' 218 | else: 219 | img_code = '' 220 | 221 | # 加载远程图片以标记打开邮件的受害者 222 | if self.Server != ':': 223 | Log.tips("监听地址: {}".format(self.Server)) 224 | listen_code = '' 225 | else: 226 | listen_code = '' 227 | 228 | # 构建alternative的text/html部分 229 | texthtml = MIMEText( 230 | email_content + img_code + listen_code, 231 | _subtype='html', _charset='UTF-8') 232 | msg.attach(texthtml) 233 | 234 | # 附件 235 | if self.file != None and self.file != "": 236 | files = [r'{}'.format(self.file)] 237 | for t in files: 238 | part_attach1 = MIMEApplication(open(t, 'rb').read()) # 打开附件 239 | # print(t.rsplit('/', 1)[1]) 240 | part_attach1.add_header('Content-Disposition', 'attachment', filename=t.rsplit('/', 1)[1]) # 为附件命名 241 | msg.attach(part_attach1) # 添加附件 242 | 243 | # 发送邮件 244 | try: 245 | # 判断长度,当测试单次发送时值为“腾讯企业邮:*25”长度为9,当批量发送时,经过strip('*:25'),长度为5 246 | if len(self.smtp_server) == 9 or len(self.smtp_server) == 5: 247 | self.smtp_server = self.smtp_server.strip('*:25') 248 | if self.smtp_server == "腾讯企业邮": 249 | # 若需要加密使用SSL,可以这样创建client 250 | client = smtplib.SMTP_SSL('smtp.exmail.qq.com', 465) 251 | elif self.smtp_server == "网易企业邮": 252 | client = smtplib.SMTP_SSL('smtphz.qiye.163.com', 465) 253 | elif self.smtp_server == "阿里企业邮": 254 | client = smtplib.SMTP_SSL('smtpdm.aliyun.com', 465) 255 | else: 256 | tmp = self.smtp_server.split('*')[1] 257 | server_ip = tmp.split(':')[0] 258 | server_port = tmp.split(':')[1] 259 | if int(server_port) == 465: 260 | client = smtplib.SMTP_SSL(server_ip, int(server_port)) 261 | else: 262 | client = smtplib.SMTP(server_ip, int(server_port)) 263 | # 开启DEBUG模式 264 | # client.set_debuglevel(0) 265 | # 发件人和认证地址必须一致 266 | client.login(self.smtp_user, self.smtp_passwd) 267 | # 备注:若想取到DATA命令返回值,可参考smtplib的sendmail封装方法: 268 | # 使用SMTP.mail/SMTP.rcpt/SMTP.data方法 269 | # Log.tips("执行发送") 270 | client.sendmail(self.smtp_user, receivers, msg.as_string()) # 支持多个收件人,最多60个 271 | client.quit() 272 | Log.done('{:<30}\t邮件发送成功!'.format(rcptto[0])) 273 | Log.send_done("{}".format(rcptto[0])) 274 | send_index += 1 275 | time.sleep(self.sleep) 276 | except smtplib.SMTPConnectError as e: 277 | Log.error('邮件发送失败,连接失败: [Code]{},[Error]{}'.format(e.smtp_code, e.smtp_error)) 278 | Log.send_error(rcptto[0]) 279 | except smtplib.SMTPAuthenticationError as e: 280 | Log.error('邮件发送失败,认证错误: [Code]{},[Error]{}'.format(e.smtp_code, e.smtp_error)) 281 | Log.send_error(rcptto[0]) 282 | except smtplib.SMTPSenderRefused as e: 283 | Log.error('邮件发送失败,发件人被拒绝: [Code]{},[Error]{}'.format(e.smtp_code, e.smtp_error)) 284 | Log.send_error(rcptto[0]) 285 | except smtplib.SMTPRecipientsRefused as e: 286 | Log.error('邮件发送失败,收件人被拒绝: [Error]{}'.format(e)) 287 | Log.send_error(rcptto[0]) 288 | except smtplib.SMTPDataError as e: 289 | Log.error('邮件发送失败,数据接收拒绝:[Code]{},[Error]{}'.format(e.smtp_code, e.smtp_error)) 290 | Log.send_error(rcptto[0]) 291 | except smtplib.SMTPException as e: 292 | Log.error('邮件发送失败, {}'.format(str(e))) 293 | Log.send_error(rcptto[0]) 294 | except Exception as e: 295 | Log.error('邮件发送异常, {}'.format(str(e))) 296 | Log.send_error(rcptto[0]) 297 | 298 | 299 | def read_mail(path): 300 | if os.path.exists(path): 301 | with open(path) as fp: 302 | email = fp.read() 303 | return email 304 | else: 305 | Log.error('EML模板文件不存在!') 306 | return None 307 | 308 | 309 | def emailInfo(emailpath): 310 | raw_email = read_mail(emailpath) # 将邮件读到一个字符串里面 311 | if raw_email == None: 312 | return None 313 | # print('EmailPath : ', emailpath) 314 | emailcontent = Parser().parsestr(raw_email) # 经过parsestr处理过后生成一个字典 315 | # for k, v in emailcontent.items(): 316 | # print(k, v) 317 | From = emailcontent['From'] 318 | To = emailcontent['To'] 319 | Subject = emailcontent['Subject'] 320 | Date = emailcontent['Date'] 321 | MessageID = emailcontent['Message-ID'] 322 | XOriginatingIP = emailcontent['X-Originating-IP'] 323 | 324 | User = From.split("<")[0].strip() 325 | if "<" in From: 326 | From = re.findall(".*<(.*)>.*", From)[0] 327 | if "<" in To: 328 | To = re.findall(".*<(.*)>.*", To)[0] 329 | if "<" in MessageID: 330 | MessageID = re.findall(".*<(.*)>.*", MessageID)[0] 331 | try: 332 | Subject = base64.b64decode(Subject.split("?")[3]).decode('utf-8') 333 | except Exception: 334 | pass 335 | try: 336 | User = base64.b64decode(User.split("?")[3]).decode('utf-8') 337 | except Exception: 338 | pass 339 | 340 | Log.info("[From-User]:\t{}".format(User)) 341 | Log.info("[From]:\t{}".format(From)) 342 | Log.info("[X-Originating-IP]:\t{}".format(XOriginatingIP)) 343 | Log.info("[To]:\t{}".format(To)) 344 | Log.info("[Subject]:\t{}".format(Subject)) 345 | Log.info("[Message-ID]:\t{}".format(MessageID)) 346 | Log.info("[Date]:\t{}".format(Date)) 347 | 348 | # 循环信件中的每一个mime的数据块 349 | for par in emailcontent.walk(): 350 | if not par.is_multipart(): # 这里要判断是否是multipart(无用数据) 351 | content = par.get_payload(decode=True) 352 | try: 353 | if content.decode('utf-8', 'ignore').startswith('<'): 354 | # print("content:\t\n", content.decode('utf-8', 'ignore').strip()) # 解码出文本内容,直接输出来就可以了。 355 | return content.decode('utf-8', 'ignore').strip() 356 | except UnicodeDecodeError: 357 | if content.decode('gbk', 'ignore').startswith('<'): 358 | # print("content:\t\n", content.decode('utf-8', 'ignore').strip()) # 解码出文本内容,直接输出来就可以了。 359 | return content.decode('gbk', 'ignore').strip() 360 | 361 | 362 | class EmailSenderGUI(): 363 | def __init__(self, init_window): 364 | self.init_window = init_window 365 | 366 | def set_init_windows(self): 367 | self.init_window.title("钓鱼邮件发送工具") 368 | # self.init_window.geometry('1230x810') # 2k窗口大小,若1k分辨率,请自己调试 369 | self.init_window.geometry('1400x1000') # 4k窗口大小 370 | with open('tmp.ico', 'wb') as tmp: 371 | tmp.write(base64.b64decode(icon.Icon().img)) 372 | root.iconbitmap('tmp.ico') 373 | os.remove('tmp.ico') 374 | self.init_window.resizable(0, 0) 375 | # pop = Pmw.Balloon(self.init_window) 376 | # 基础配置项 377 | self.basic_setting_label = Label(self.init_window, text="基础配置" 378 | ) 379 | self.basic_setting_label.grid(row=0, column=0) 380 | self.basic_setting = Frame(self.init_window) 381 | # SMTP服务器选择 382 | self.smtp_server_label = Label(self.basic_setting, text='SMTP服务器:') 383 | self.smtp_server_label.grid(row=0, column=0) 384 | self.smtp_server_combobox = Combobox(self.basic_setting, width=25) 385 | self.smtp_server_combobox.grid(row=0, column=1, sticky='w', pady=3) 386 | self.smtp_server_combobox['value'] = ('腾讯企业邮', '网易企业邮', '阿里企业邮') 387 | self.smtp_server_combobox.current(0) 388 | self.send_sleep_time_label = Label(self.basic_setting, text="发送间隔(s):") 389 | self.send_sleep_time_label.grid(row=0, column=1, sticky='e', padx=48) 390 | self.send_sleep_time_entry = Entry(self.basic_setting, width=6) 391 | self.send_sleep_time_entry.insert(tkinter.INSERT, '0.5') 392 | self.send_sleep_time_entry.grid(row=0, column=1, sticky='e', pady=3) 393 | # 账号输入框 394 | self.smtp_account_label = Label(self.basic_setting, text="SMTP账号:") 395 | self.smtp_account_label.grid(row=1, column=0) 396 | self.smtp_account_entry = Entry(self.basic_setting, width=45) 397 | # pop.bind(self.smtp_account_label, "必填") 398 | self.smtp_account_entry.grid(row=1, column=1, pady=3) 399 | # 密码输入框 400 | self.smtp_passwd_label = Label(self.basic_setting, text="SMTP密码:") 401 | self.smtp_passwd_label.grid(row=2, column=0) 402 | self.smtp_passwd_entry = Entry(self.basic_setting, show='*', width=45) 403 | # pop.bind(self.smtp_passwd_label, "必填") 404 | self.smtp_passwd_entry.grid(row=2, column=1, pady=3) 405 | # 自定义邮件服务器 406 | self.smtp_server_self_label = Label(self.basic_setting, text="自建SMTP:") 407 | self.smtp_server_self_label.grid(row=3, column=0) 408 | self.smtp_server_self_entry = Entry(self.basic_setting, width=30) 409 | # pop.bind(self.smtp_server_self_entry, "当设置自定邮服时,SMTP选择无效,填写格式:127.0.0.1:465") 410 | self.smtp_server_self_entry.grid(row=3, column=1, sticky='w') 411 | self.smtp_server_self_port_entry = Entry(self.basic_setting, width=8) 412 | self.smtp_server_self_port_entry.insert(tkinter.INSERT, '25') 413 | self.smtp_server_self_blank = Label(self.basic_setting, text="PORT: ", width=5) 414 | self.smtp_server_self_blank.grid(row=3, column=1, sticky='e', padx=60, pady=3) 415 | self.smtp_server_self_port_entry.grid(row=3, column=1, sticky='e', pady=3) 416 | # 监听服务器地址 417 | self.listen_server_label = Label(self.basic_setting, text="监听服务器地址:") 418 | self.listen_server_label.grid(row=4, column=0) 419 | self.listen_server_entry = Entry(self.basic_setting, width=30) 420 | # pop.bind(self.listen_server_entry, "填写格式:127.0.0.1:8080") 421 | self.listen_server_entry.grid(row=4, column=1, sticky='w', pady=3) 422 | self.listen_server_blank = Label(self.basic_setting, text="PORT: ", width=5) 423 | self.listen_server_blank.grid(row=4, column=1, sticky='e', padx=60) 424 | self.listen_server_port_entry = Entry(self.basic_setting, width=8) 425 | self.listen_server_port_entry.grid(row=4, column=1, sticky='e', pady=3) 426 | 427 | # 测试发送 428 | self.test_user_label = Label(self.basic_setting, text="测试接收邮箱:") 429 | self.test_user_label.grid(row=5, column=0) 430 | self.test_user_entry = Entry(self.basic_setting, width=45) 431 | self.test_user_entry.grid(row=5, column=1, pady=3, ) 432 | self.test_email_sender = Button(self.basic_setting, text="测试发送", command=lambda: self.SenderHandler(0), 433 | height=1) 434 | self.test_email_sender.grid(row=5, column=2, padx=3) 435 | self.basic_setting.grid(row=1, column=0) 436 | 437 | # 邮件配置 438 | self.email_setting_label = Label(self.init_window, text="邮件配置") 439 | self.email_setting_label.grid(row=2, column=0) 440 | self.email_setting = Frame(self.init_window) 441 | # 邮件头配置 442 | # 伪造发件人配置 443 | self.email_sender_name_label = Label(self.email_setting, text="伪造发件人名称:") 444 | self.email_sender_name_label.grid(row=0, column=0) 445 | self.email_sender_name_entry = Entry(self.email_setting, width=45) 446 | # pop.bind(self.email_sender_name_entry, "必填") 447 | self.email_sender_name_entry.grid(row=0, column=1, pady=3) 448 | self.email_sender_email_label = Label(self.email_setting, text="伪造发件人邮箱:") 449 | self.email_sender_email_label.grid(row=1, column=0) 450 | self.email_sender_email_entry = Entry(self.email_setting, width=45) 451 | self.email_sender_email_entry.grid(row=1, column=1, pady=3) 452 | # 邮件主题 453 | self.email_subject_label = Label(self.email_setting, text="邮件主题:") 454 | self.email_subject_label.grid(row=2, column=0) 455 | self.email_subject_entry = Entry(self.email_setting, width=45) 456 | # pop.bind(self.email_subject_entry, "必填") 457 | self.email_subject_entry.grid(row=2, column=1, pady=3) 458 | 459 | # 邮件原文EML文件选择 460 | self.eml_file_label = Label(self.email_setting, text="EML文件:") 461 | self.eml_file_label.grid(row=3, column=0) 462 | self.EML_file = StringVar() 463 | self.eml_file_entry = Entry(self.email_setting, textvariable=self.EML_file, width=45) 464 | self.eml_file_entry.grid(row=3, column=1, pady=3) 465 | self.eml_file_selector = Button(self.email_setting, text="选择文件", 466 | command=lambda: self.FileSelector(self.eml_file_entry, self.EML_file, 1)) 467 | self.eml_file_selector.grid(row=3, column=2, padx=5) 468 | 469 | # 收件人列表 470 | self.email_label = Label(self.email_setting, text="收件人列表:") 471 | self.email_label.grid(row=4, column=0) 472 | self.EMAIL_list = StringVar() 473 | self.email_list_entry = Entry(self.email_setting, textvariable=self.EMAIL_list, width=45) 474 | self.email_list_entry.grid(row=4, column=1, pady=3) 475 | self.email_file_selector = Button(self.email_setting, text="选择文件", 476 | command=lambda: self.FileSelector(self.email_list_entry, self.EMAIL_list, 0)) 477 | self.email_file_selector.grid(row=4, column=2, padx=5) 478 | 479 | # 邮件附件选择 480 | self.file_label = Label(self.email_setting, text="邮件附件:") 481 | self.file_label.grid(row=5, column=0) 482 | self.EMAIL_file = StringVar() 483 | self.file_entry = Entry(self.email_setting, textvariable=self.EMAIL_file, width=45) 484 | self.file_entry.grid(row=5, column=1, pady=3) 485 | self.file_selector = Button(self.email_setting, text="选择附件", 486 | command=lambda: self.FileSelector(self.file_entry, self.EMAIL_file, 0)) 487 | self.file_selector.grid(row=5, column=2, padx=5) 488 | 489 | # 邮件正文图片插入 490 | self.img_label = Label(self.email_setting, text="正文图片:") 491 | self.img_label.grid(row=6, column=0) 492 | self.EMAIL_img = StringVar() 493 | self.img_entry = Entry(self.email_setting, textvariable=self.EMAIL_img, width=45) 494 | self.img_entry.grid(row=6, column=1, pady=3) 495 | self.img_selector = Button(self.email_setting, text="选择图片", 496 | command=lambda: self.FileSelector(self.img_entry, self.EMAIL_img, 0)) 497 | self.img_selector.grid(row=6, column=2, padx=5) 498 | self.email_setting.grid(row=3, column=0) 499 | 500 | # 邮件编辑区头部 501 | self.email_editor_label = Label(self.init_window, text="邮件正文编辑") 502 | self.email_editor_label.grid(row=0, column=1) 503 | # 邮件编辑区 504 | self.email_editor = Frame(self.init_window) 505 | self.email_editor_text = Text(self.email_editor, height=56, width=90) 506 | self.email_editor_text.grid(row=0, column=0) 507 | self.email_editor.grid(row=1, column=1, rowspan=5) 508 | self.email_editor_text.tag_configure("found", background="yellow") 509 | 510 | # 正文搜索 511 | self.email_function_frame = Frame(self.init_window) 512 | self.email_searcher_entry = Entry(self.email_function_frame, width=35) 513 | self.email_searcher_entry.grid(row=0, column=0, pady=3) 514 | self.email_searcher = Button(self.email_function_frame, text="搜索", command=self.TextSearcher) 515 | self.email_searcher.grid(row=0, column=1, padx=5) 516 | 517 | # 邮件按钮 518 | self.email_preview = Button(self.email_function_frame, text="邮件预览", command=self.HTMLRunner) 519 | self.email_preview.grid(row=0, column=2, padx=5) 520 | # 邮件发送 521 | self.email_sender = Button(self.email_function_frame, text="批量发送", command=lambda: self.SenderHandler(1)) 522 | self.email_sender.grid(row=0, column=3, padx=5) 523 | # 重置 524 | self.email_reset_sender = Button(self.email_function_frame, text="重置", command=lambda: self.Reset()) 525 | self.email_reset_sender.grid(row=0, column=4, padx=5) 526 | self.email_function_frame.grid(column=1) 527 | 528 | # 控制台输出 529 | self.log_print_label = Label(self.init_window, text="控制台输出") 530 | self.log_print_label.grid(row=4, column=0) 531 | self.log_print_from = Frame(self.init_window) 532 | self.log_print_window = Text(self.log_print_from, bg='black') 533 | sys.stdout = StdoutRedirector(self.log_print_window) 534 | self.log_print_window.grid(row=0, column=0) 535 | self.log_print_from.grid(row=5, column=0, padx=10) 536 | 537 | # banner区 538 | self.banner_frame = Frame(self.init_window) 539 | self.banner_label = Label(self.banner_frame, text="Github: https://github.com/A10ha ") 540 | self.banner_label.grid(row=0, column=0) 541 | self.banner_frame.grid(row=6, column=0, pady=20) 542 | 543 | def TextSearcher(self): 544 | self.email_editor_text.tag_remove("found", "1.0", END) 545 | start = "1.0" 546 | key = self.email_searcher_entry.get() 547 | 548 | if (len(key.strip()) == 0): 549 | return 550 | while True: 551 | pos = self.email_editor_text.search(key, start, END) 552 | # print("pos= ",pos) # pos= 3.0 pos= 4.0 pos= 553 | if (pos == ""): 554 | break 555 | self.email_editor_text.tag_add("found", pos, "%s+%dc" % (pos, len(key))) 556 | start = "%s+%dc" % (pos, len(key)) 557 | 558 | def FileSelector(self, File_Entry, Entry_Value, flag): 559 | Entry_Value.set('') 560 | self.filename = tkinter.filedialog.askopenfilename() 561 | if self.filename != '': 562 | File_Entry.insert(tkinter.INSERT, self.filename) 563 | if flag == 1: 564 | self.HTMLReader(self.filename) 565 | 566 | def HTMLRunner(self): 567 | HTML_content = self.email_editor_text.get("0.0", "end") 568 | if self.img_entry.get() != None and self.img_entry.get() != '': 569 | with open(r"{}".format(self.img_entry.get()), "rb") as i: 570 | b64str = base64.b64encode(i.read()) 571 | img_code = '' 573 | else: 574 | img_code = '' 575 | # self.email_editor_text.insert(tkinter.INSERT, img_code) 576 | with open('temp.html', 'wb') as f: 577 | HTML = HTML_content + img_code 578 | f.write(HTML.encode('utf-8')) 579 | os.popen('start temp.html') 580 | 581 | def HTMLReader(self, eml_file): 582 | self.email_editor_text.delete('1.0', 'end') 583 | Log.tips("================================================") 584 | HTML_content = emailInfo(eml_file) 585 | self.email_editor_text.insert(tkinter.INSERT, HTML_content) 586 | with open('temp.html', 'wb') as f: 587 | f.write(HTML_content.encode('utf-8')) 588 | 589 | def SenderHandler(self, flag): 590 | global start_flag, stop_flag, init_flag, E 591 | if init_flag == 0: 592 | self.InitSender(flag) 593 | return 594 | if start_flag == 1: 595 | StopSend() 596 | self.email_sender['text'] = "恢复发送" 597 | self.email_reset_sender['state'] = 'normal' 598 | else: 599 | StartSend() 600 | self.email_sender['text'] = "暂停发送" 601 | self.email_reset_sender['state'] = 'disabled' 602 | E.Sender() 603 | 604 | def Reset(self): 605 | global init_flag, start_flag, stop_flag, send_index 606 | init_flag = 0 607 | start_flag = 0 608 | stop_flag = 0 609 | send_index = 0 610 | time.sleep(0.5) 611 | Log.tips("发送队列已重置,可重新选择收件人邮箱文件进行重新发送。\n") 612 | self.email_sender['text'] = "批量发送" 613 | self.test_email_sender['state'] = 'normal' 614 | self.email_editor_text['state'] = 'normal' 615 | self.send_sleep_time_entry['state'] = 'normal' 616 | self.email_sender_name_entry['state'] = 'normal' 617 | self.email_sender_email_entry['state'] = 'normal' 618 | self.email_subject_entry['state'] = 'normal' 619 | 620 | def InitSender(self, flag): 621 | global init_flag, start_flag, send_index, E 622 | if self.smtp_server_self_entry.get() == None or self.smtp_server_self_entry.get() == '': 623 | Log.tips("SMTP服务器: {}".format(self.smtp_server_combobox.get())) 624 | else: 625 | Log.tips( 626 | "SMTP服务器: {}".format(self.smtp_server_self_entry.get() + ':' + self.smtp_server_self_port_entry.get())) 627 | # 参数判断 628 | if self.email_subject_entry.get() == None or self.email_subject_entry.get() == "": 629 | Log.error('邮件主题不存在!') 630 | return False 631 | if self.smtp_account_entry.get() == None or self.smtp_account_entry.get() == "": 632 | Log.error('SMTP账号不存在!') 633 | return False 634 | if self.smtp_passwd_entry.get() == None or self.smtp_passwd_entry.get() == "": 635 | Log.error('SMTP密码不存在!') 636 | return False 637 | if self.email_sender_name_entry.get() == None or self.email_sender_name_entry.get() == '': 638 | Log.error('发件人名称不存在!') 639 | return False 640 | if flag == 0: 641 | if self.test_user_entry.get() == None or self.test_user_entry.get() == '': 642 | Log.error("测试收件人邮箱未设置!") 643 | else: 644 | Log.tips("测试发送: {}".format(self.test_user_entry.get())) 645 | E = EmailSender(self.email_subject_entry.get(), 646 | [self.email_sender_name_entry.get(), self.email_sender_email_entry.get()], 647 | self.email_editor_text.get("0.0", "end"), 648 | self.smtp_account_entry.get(), self.smtp_passwd_entry.get(), 649 | self.smtp_server_combobox.get() + '*' + self.smtp_server_self_entry.get() + ':' + self.smtp_server_self_port_entry.get(), 650 | self.listen_server_entry.get() + ':' + self.listen_server_port_entry.get(), 651 | None, 652 | self.test_user_entry.get(), 653 | self.file_entry.get(), self.img_entry.get(), float(self.send_sleep_time_entry.get())) 654 | send_index = 0 655 | E.Sender() 656 | elif flag == 1: 657 | if self.email_list_entry.get() == None or self.email_list_entry.get() == '': 658 | Log.error("收件人列表文件路径未设置!") 659 | else: 660 | init_flag = 1 661 | start_flag = 1 662 | self.email_sender['text'] = "暂停发送" 663 | self.email_reset_sender['state'] = 'disabled' 664 | self.test_email_sender['state'] = 'disabled' 665 | self.email_editor_text['state'] = 'disabled' 666 | self.send_sleep_time_entry['state'] = 'disabled' 667 | self.email_sender_name_entry['state'] = 'disabled' 668 | self.email_sender_email_entry['state'] = 'disabled' 669 | self.email_subject_entry['state'] = 'disabled' 670 | Log.tips("批量发送: {}".format(self.email_list_entry.get())) 671 | E = EmailSender(self.email_subject_entry.get(), 672 | [self.email_sender_name_entry.get(), self.email_sender_email_entry.get()], 673 | self.email_editor_text.get("0.0", "end"), 674 | self.smtp_account_entry.get(), self.smtp_passwd_entry.get(), 675 | self.smtp_server_combobox.get() + '*' + self.smtp_server_self_entry.get() + ':' + self.smtp_server_self_port_entry.get(), 676 | self.listen_server_entry.get() + ':' + self.listen_server_port_entry.get(), 677 | self.email_list_entry.get(), 678 | None, 679 | self.file_entry.get(), self.img_entry.get(), float(self.send_sleep_time_entry.get())) 680 | E.Sender() 681 | 682 | 683 | if __name__ == '__main__': 684 | root = Tk() # 实例化出一个父窗口 685 | PORTAL = EmailSenderGUI(root) 686 | # 设置根窗口默认属性 687 | PORTAL.set_init_windows() 688 | # 父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示 689 | root.mainloop() 690 | -------------------------------------------------------------------------------- /EmailSenderGUI.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | block_cipher = pyi_crypto.PyiBlockCipher(key='password') 5 | 6 | 7 | a = Analysis(['EmailSenderGUI.py'], 8 | pathex=[], 9 | binaries=[], 10 | datas=[], 11 | hiddenimports=[], 12 | hookspath=[], 13 | hooksconfig={}, 14 | runtime_hooks=[], 15 | excludes=[], 16 | win_no_prefer_redirects=False, 17 | win_private_assemblies=False, 18 | cipher=block_cipher, 19 | noarchive=False) 20 | pyz = PYZ(a.pure, a.zipped_data, 21 | cipher=block_cipher) 22 | 23 | exe = EXE(pyz, 24 | a.scripts, 25 | a.binaries, 26 | a.zipfiles, 27 | a.datas, 28 | [], 29 | name='EmailSenderGUI', 30 | debug=False, 31 | bootloader_ignore_signals=False, 32 | strip=False, 33 | upx=True, 34 | upx_exclude=[], 35 | runtime_tmpdir=None, 36 | console=False, 37 | disable_windowed_traceback=False, 38 | target_arch=None, 39 | codesign_identity=None, 40 | entitlements_file=None , icon='email.ico') 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EmailSender 2 | 钓鱼邮件便捷发送工具(GUI) 3 | # **程序简介** 4 | 5 | 本程序利用Python语言编写,使用Tkinter实现图形化界面,可使用Pyinstaller进行exe打包,程序主界面截图如下: 6 | ![image](https://github.com/A10ha/EmailSender/assets/60035496/77da46b8-ce46-4488-b960-8f86bdedeeda) 7 | 8 | # **功能简介** 9 | 10 | 1. 支持腾讯企业邮、网易企业邮、阿里企业邮、自建邮服SMTP授权账号(其他邮服,可在自建SMTP服务器处填写,默认使用25端口,无SSL)。 11 | 2. 批量发送钓鱼邮件,可暂停发送,可重置发送。 12 | 3. 伪造发件人名称与发件人邮箱。 13 | 4. 解析EML邮件原文文件,快捷利用邮件原文生成钓鱼邮件。 14 | 5. 快捷邮件正文HTML代码编辑预览,快速本地调试邮件内容。 15 | 6. 设置监听服务器,监听接收受害者是否打开邮件,适用于应急演练场景。 16 | 7. 本地日志记录,自动创建本地日志目录,记录全量日志以及错误日志。 17 | 8. 正文图片Base64编码插入,可用于插入钓鱼网页二维码等图片。 18 | 19 | # **关键参数说明** 20 | 21 | 1. SMTP账号密码必填,由相关企业邮设置-客户端授权生成。 22 | 2. 自建SMTP优先级最高,若要使用其他官方SMTP服务器请将自建SMTP置空。 23 | 3. 监听服务器地址选填,可开启HTTP服务监听访问。 24 | 4. EML文件导入,由邮箱导出邮件,选择后,可进行邮件正文(HTML代码)编辑,通过邮件预览查看样式。 25 | 5. 收件人列表,批量发送时配置,使用txt文件指定,每个邮箱使用换行分割。 26 | 27 | # **开发思路** 28 | 29 | # **发送邮件函数** 30 | 31 | 利用**python**的**email**库,使用**smtplib**创建SMTP通信客户端与服务端通信发送邮件。 32 | 33 | 配置发送人信息参数实现发件人信息伪造,有些邮件管理客户端会存在代发字段。 34 | 35 | 利用在正文中添加隐藏图片代码,当受害者打开邮件时,请求攻击者监听服务器资源,达到识别邮件是否启封。 36 | 37 | # **EML文件处理** 38 | 39 | 利用**python**的**email**库,使用**Parser()**进行邮件原文文件解析,之后针对解析出来的信息进行处理输出。 40 | 41 | # **HTML正文编辑预览** 42 | 43 | 利用邮件原文文件解析处理的正文**HTML**信息,定向输出到**Tkinter**的**Text()**控件中,达到即时编辑。 44 | 45 | 通过**Python**的**File**类函数将**Text()**控件中的内容写入本地**temp.html**中利用**os**库执行”**start temp.html**“,利用本地浏览器打开文件,实现邮件预览。 46 | 47 | # 本项目是仅为个人研究练习开发,禁止用于非法活动。 48 | # 项目开发者对软件的滥用不承担任何责任。 由使用者负责。 49 | -------------------------------------------------------------------------------- /email.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A10ha/EmailSender/8254c5782ac2295ebdf74b34326b0f30687efd53/email.ico -------------------------------------------------------------------------------- /generate_icon.py: -------------------------------------------------------------------------------- 1 | import base64 2 | with open("icon.py","a") as f: 3 | f.write('class Icon(object):\n') 4 | f.write('\tdef __init__(self):\n') 5 | f.write("\t\tself.img='") 6 | with open("email.ico","rb") as i: 7 | b64str = base64.b64encode(i.read()) 8 | with open("icon.py","ab+") as f: 9 | f.write(b64str) 10 | with open("icon.py","a") as f: 11 | f.write("'") -------------------------------------------------------------------------------- /icon.py: -------------------------------------------------------------------------------- 1 | class Icon(object): 2 | def __init__(self): 3 | self.img='AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAABTTEgLVEtGfVRLReBUTkr+VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE9L/1RPS/9UT0v/VE5K/VRLRd9US0V8VE1IC1RLRnxTU1D0T4WX/02lw/9Mp8X/TKbE/0ymxP9MpsT/TKbE/0ymxP9MpsT/TKbE/0ymxP9MpsT/TKbE/0ymxP9MpsT/TKbE/0ymxP9MpsT/TKbE/0ymxP9MpsT/TKbE/0ymxP9MpsT/TKbE/0ynxf9NpsP/T4eZ/1NTUfRUS0V7VEtG3E+EmP9Jz/z+SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0v//SdL//0nS//9J0P3/T4WZ/1RLRtxUTkr9Spa//UjK//tJ0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0jK//xKlr/+VE5K/VROS/9Kk7//Rbn6/EjI/PtJ0P3/SdH+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdH//0nQ/v9IyPz8Rbn6/kqTv/9UTkv/VE5L/0qTv/9Ftvn/RrLv/U6Jn/1Mr8//SdH//0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nR//9MsNH/Toyk/ka08P5Ftvn/SpO//1ROS/9UTkv/SpO//0W2+f9GsO//T2+D/1NeX/9Mrs7/SdH//0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0f//TK/Q/1JfYf9PcYX/RrDw/0W2+f9Kk7//VE5L/1ROS/9Kk7//Rbb5/0W19/9Gqub/T2x+/1NeYP9Mrs7/SdH//0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdH//0yv0P9SX2H/T26A/0aq5v9Ftff/Rbb5/0qTv/9UTkv/VE5L/0qTv/9Ftvn/RbT2/0W1+P9Gq+b/T2x+/1NeYP9Mrs7/SdH//0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nR//9Mr9D/Ul9i/09tgP9Gq+b/RbX4/0W09v9Ftvn/SpO//1ROS/9UTkv/SpO//0W2+f9FtPb/RbT2/0W1+P9Gq+b/T2x+/1NeYP9Mrs7/SdH//0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0P7/SdD+/0nQ/v9J0f//TK/Q/1JfYf9PbYD/Rqvm/0W1+P9FtPb/RbT2/0W2+f9Kk7//VE5L/1ROS/9Kk7//Rbb5/0W09v9FtPb/RbT2/0W1+P9Gq+b/T2x+/1NeYP9Mrs7/SdH//0nQ/v9J0P7/SdH+/0nR//9J0f//SdH+/0nQ/v9J0P7/SdH//0yv0P9SX2H/T22A/0ar5v9Ftfj/RbT2/0W09v9FtPb/Rbb5/0qTv/9UTkv/VE5L/0qTv/9Ftvn/RbT2/0W09v9FtPb/RbT2/0W1+P9Gq+b/T2x+/1NeYP9Mrs7/SdH//0nS//9Kxu//TZSs/02UrP9KxvD/SdL//0nS//9Mr9D/Ul9h/09tgP9Gq+b/RbX4/0W09v9FtPb/RbT2/0W09v9Ftvn/SpO//1ROS/9UTkv/SpO//0W2+f9FtPb/RbT2/0W09v9FtPb/RbT2/0W1+P9Gq+b/T2x+/1NeYP9Mrc7/S6/R/1Budv9hWlX/YVpV/09ud/9Lr9H/TK/Q/1JgYv9PboD/Rqvm/0W1+P9FtPb/RbT2/0W09v9FtPb/RbT2/0W2+f9Kk7//VE5L/1ROS/9Kk7//Rbb5/0W09v9FtPb/RbT2/0W09v9FtPb/RbT2/0W1+P9GrOj/UGl4/1JYWP9VWln/gnt2/8/OzP/Pzsz/gXp1/1VZWf9RWFj/UGp5/0as6P9Ftfj/RbT2/0W09v9FtPb/RbT2/0W09v9FtPb/Rbb5/0qTv/9UTkv/VE5L/0qTv/9Ftvn/RbT2/0W09v9FtPb/RbT2/0W09v9Ftff/RbP0/0mXxf9PX2j/Z15Z/6yopP/o6Of/9PTz//T08//o6Ob/rKil/2dfWf9PX2j/SZfF/0Wz9P9Ftff/RbT2/0W09v9FtPb/RbT2/0W09v9Ftvn/SpO//1ROS/9UTkv/SpO//0W2+f9FtPb/RbT2/0W09v9FtPb/RbX4/0ep4/9Me5f/VlZV/4eAfP/S0c//8/Py//Pz8v/z8/L/8/Py//Pz8v/z8/L/09LQ/4aAe/9WVVX/THuX/0ep4/9Ftfj/RbT2/0W09v9FtPb/RbT2/0W2+f9Kk7//VE5L/1ROS/9Kk7//Rbb5/0W09v9FtPb/RbX3/0Wy8/9Jk77/T2Bq/2lhXP+zr6z/6+vq//T08//y8vH/9PTz/9jX1f/Y19X/9PTz//Ly8f/09PP/6+vp/7OvrP9pYVz/T2Br/0mTv/9Fs/T/RbX3/0W09v9FtPb/Rbb5/0qTv/9UTkv/VE5L/0qTv/9Ftvn/RbT3/0W1+P9Hp+D/THWN/1hXVf+MhoH/1tTT//Pz8v/z8/L/8/Py/+/u7f/h4d//jomF/46Jhf/h4N//7+7t//Pz8v/z8/L/8/Py/9bV0/+MhoH/WFdW/0x1jv9Hp+D/RbX4/0W09/9Ftvn/SpO//1ROS/9UTkv/SpO//0W3+/9FsfH/SpC6/1BdZf9uZmL/t7Ow/+zs6//09PP/8vLx//Ly8f/s6+r/m5aT/2dgW/9aU07/WlNO/2dgW/+cl5T/7Ozr//Ly8f/y8vH/9PTz/+zs6/+3s7H/bmZh/1BdZf9Kkbv/RbHx/0W3+/9Kk7//VE5L/1ROS/9KlMH/R6Xd/05zif9aVlT/kYuH/9va2P/09PP/8/Py//Ly8f/y8vH/9PTz/8XDwf9hWVX/n5uY/7Wyr/+1sq//oJuY/2FaVf/Fw8H/9PTz//Ly8f/y8vH/8/Py//T08//b2tj/kYqG/1pWU/9Oc4r/R6be/0qUwP9UTkv/VE5L/09vgv9RXWT/XGVm/7W4t//v7u3/8/Pz//Ly8f/y8vH/8vLx//Ly8f/19fT/uLaz/21mYv/k4+H/9/f3//f39//j4uD/Z2Bb/7Kv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' -------------------------------------------------------------------------------- /img2base64.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | if __name__ == '__main__': 4 | with open("email.ico", "rb") as i: 5 | b64str = base64.b64encode(i.read()) 6 | img_code = '' 7 | with open("img.html", "wb") as f: 8 | f.write(img_code.encode('utf-8')) 9 | -------------------------------------------------------------------------------- /read_file.py: -------------------------------------------------------------------------------- 1 | Lines = [] 2 | 3 | 4 | def File_Read(file_path): 5 | global Lines 6 | Lines = [] 7 | with open(file_path, 'r') as f: 8 | while True: 9 | line = f.readline() # 逐行读取 10 | if not line: # 到 EOF,返回空字符串,则终止循环 11 | break 12 | File_Data(line, flag=1) 13 | File_Data(line, flag=0) 14 | 15 | 16 | def File_Data(line, flag): 17 | global Lines 18 | if flag == 1: 19 | Lines.append(line) 20 | else: 21 | return Lines 22 | 23 | 24 | if __name__ == '__main__': 25 | File_Read('./test_email.txt') 26 | print(Lines) 27 | File_Read('./email.txt') 28 | print(Lines) 29 | # print(line) 30 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A10ha/EmailSender/8254c5782ac2295ebdf74b34326b0f30687efd53/requirements.txt -------------------------------------------------------------------------------- /temp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 492 | 493 |
494 | 496 | 497 | 499 | 500 | 501 | 502 | 503 | 504 | 914 | 915 | 916 | 917 | 918 | 936 | 937 | 938 | 939 | 940 | 941 | 998 | 999 | 1000 | 1001 |
505 | 506 | 507 | 510 | 511 | 512 | 513 | 514 | 524 | 525 | 600 | 601 | 623 | 624 | 630 | 631 | 650 | 651 | 674 | 675 | 718 | 719 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 |
919 | 920 | 921 | 933 | 934 | 935 |
942 | 943 | 944 | 970 | 971 | 972 | 980 | 981 | 982 | 995 | 996 | 997 |
1002 | 1003 |
1004 | 1005 | 1006 | 1007 | 1008 | --------------------------------------------------------------------------------