From 908fcb56e80629ac4635967a9c5b5233f0e2e9bc Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Thu, 30 May 2013 23:36:14 -0300 Subject: [PATCH] Added WPA2Decrypter class. --- Makefile.am | 3 +- Makefile.in | 3 +- configure | 84 ++++++++++++++++ configure.ac | 23 +++++ include/config.h.in | 15 +++ include/crypto.h | 148 +++++++++++++++++++++++++++-- include/internals.h | 79 ++++++++++++---- src/crypto.cpp | 226 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 552 insertions(+), 29 deletions(-) diff --git a/Makefile.am b/Makefile.am index ca0adfe..fd329f3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,4 +100,5 @@ libtins_include_HEADERS = include/internals.h \ include/utils.h \ include/cxxstd.h \ include/stp.h \ - include/exceptions.h + include/exceptions.h \ + include/config.h diff --git a/Makefile.in b/Makefile.in index 0885244..eb368d5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -384,7 +384,8 @@ libtins_include_HEADERS = include/internals.h \ include/utils.h \ include/cxxstd.h \ include/stp.h \ - include/exceptions.h + include/exceptions.h \ + include/config.h all: all-am diff --git a/configure b/configure index 1ecb44e..a995716 100755 --- a/configure +++ b/configure @@ -775,6 +775,7 @@ enable_maintainer_mode with_pcap_include_path with_pcap_lib_path enable_c__11 +enable_wpa2 ' ac_precious_vars='build_alias host_alias @@ -1418,6 +1419,7 @@ Optional Features: --disable-maintainer-mode disable make rules and dependencies not useful (and sometimes confusing) to the casual installer --enable-c++11 enable C++11 features + --disable-wpa2 disable WPA2 decryption features Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -15199,6 +15201,8 @@ CPPFLAGS=$old_cppflags # Options +wpa2_msg="WPA2 decryption(which requires openssl) can be disabled using the --disable-wpa2 flag." + # Check whether --enable-c++11 was given. if test "${enable_c__11+set}" = set; then : enableval=$enable_c__11; ax_cxx_compile_cxx11_required=truednl @@ -15324,6 +15328,86 @@ $as_echo "#define HAVE_CXX11 1" >>confdefs.h fi +fi + + +# Check whether --enable-wpa2 was given. +if test "${enable_wpa2+set}" = set; then : + enableval=$enable_wpa2; +else + + for ac_header in openssl/evp.h openssl/hmac.h openssl/aes.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +else + as_fn_error $? "openssl headers are missing! $wpa2_msg " "$LINENO" 5 + +$as_echo "#define HAVE_WPA2_DECRYPTION 0" >>confdefs.h + + +fi + +done + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PKCS5_PBKDF2_HMAC_SHA1 in -lcrypto" >&5 +$as_echo_n "checking for PKCS5_PBKDF2_HMAC_SHA1 in -lcrypto... " >&6; } +if ${ac_cv_lib_crypto_PKCS5_PBKDF2_HMAC_SHA1+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lcrypto $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char PKCS5_PBKDF2_HMAC_SHA1 (); +int +main () +{ +return PKCS5_PBKDF2_HMAC_SHA1 (); + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + ac_cv_lib_crypto_PKCS5_PBKDF2_HMAC_SHA1=yes +else + ac_cv_lib_crypto_PKCS5_PBKDF2_HMAC_SHA1=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypto_PKCS5_PBKDF2_HMAC_SHA1" >&5 +$as_echo "$ac_cv_lib_crypto_PKCS5_PBKDF2_HMAC_SHA1" >&6; } +if test "x$ac_cv_lib_crypto_PKCS5_PBKDF2_HMAC_SHA1" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBCRYPTO 1 +_ACEOF + + LIBS="-lcrypto $LIBS" + +else + as_fn_error $? "openssl library is missing! $wpa2_msg " "$LINENO" 5 + +fi + + +$as_echo "#define HAVE_WPA2_DECRYPTION 1" >>confdefs.h + + + fi diff --git a/configure.ac b/configure.ac index 3f401c0..c38f8b6 100644 --- a/configure.ac +++ b/configure.ac @@ -36,12 +36,35 @@ CPPFLAGS=$old_cppflags # Options +wpa2_msg="WPA2 decryption(which requires openssl) can be disabled using the --disable-wpa2 flag." + AC_ARG_ENABLE( c++11, [ --enable-c++11 enable C++11 features], [AX_CXX_COMPILE_STDCXX_11(noext)] ) +AC_ARG_ENABLE( + wpa2, + [ --disable-wpa2 disable WPA2 decryption features], + [], + [ + AC_CHECK_HEADERS( + [openssl/evp.h openssl/hmac.h openssl/aes.h], + [], + [AC_MSG_ERROR([openssl headers are missing! $wpa2_msg ])] + AC_DEFINE([HAVE_WPA2_DECRYPTION], 0, Have WPA2 decryption library) + ) + AC_CHECK_LIB( + crypto, + PKCS5_PBKDF2_HMAC_SHA1, + [], + [AC_MSG_ERROR([openssl library is missing! $wpa2_msg ])] + ) + AC_DEFINE([HAVE_WPA2_DECRYPTION], 1, Have WPA2 decryption library) + ] +) + # Substitute options AC_SUBST(CXXFLAGS) diff --git a/include/config.h.in b/include/config.h.in index d870a5b..1898be8 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -9,12 +9,24 @@ /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#undef HAVE_LIBCRYPTO + /* Define to 1 if you have the `pcap' library (-lpcap). */ #undef HAVE_LIBPCAP /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H +/* Define to 1 if you have the header file. */ +#undef HAVE_OPENSSL_AES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_OPENSSL_EVP_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_OPENSSL_HMAC_H + /* Define to 1 if you have the header file. */ #undef HAVE_PCAP_H @@ -39,6 +51,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H +/* Have WPA2 decryption library */ +#undef HAVE_WPA2_DECRYPTION + /* Define to the sub-directory in which libtool stores uninstalled libraries. */ #undef LT_OBJDIR diff --git a/include/crypto.h b/include/crypto.h index b0b5b61..619a443 100644 --- a/include/crypto.h +++ b/include/crypto.h @@ -34,15 +34,57 @@ #include #include #include -#include "dot11.h" #include "utils.h" #include "snap.h" #include "rawpdu.h" +#include "handshake_capturer.h" +#include "config.h" namespace Tins { class PDU; +class Dot11; +class Dot11Data; namespace Crypto { + /** + * \cond + */ + #ifdef HAVE_WPA2_DECRYPTION + namespace WPA2 { + class invalid_handshake : public std::exception { + public: + const char *what() const throw() { + return "invalid handshake"; + } + }; + class CCMPSessionKeys { + public: + typedef Internals::byte_array<80> ptk_type; + typedef Internals::byte_array<32> pmk_type; + + CCMPSessionKeys(const RSNHandshake &hs, const pmk_type &pmk); + SNAP *decrypt_unicast(const Dot11Data &dot11, const RawPDU &raw) const; + private: + ptk_type ptk; + }; + + class SupplicantData { + public: + typedef HWAddress<6> address_type; + typedef CCMPSessionKeys::pmk_type pmk_type; + + SupplicantData(const std::string &psk, const std::string &ssid); + + const pmk_type &pmk() const; + private: + pmk_type pmk_; + }; + } + #endif // HAVE_WPA2_DECRYPTION + /** + * \endcond + */ + /** * \brief RC4 Key abstraction. */ @@ -65,11 +107,11 @@ namespace Crypto { }; /** - * + * \brief Decrypts WEP-encrypted traffic. */ class WEPDecrypter { public: - typedef Dot11::address_type address_type; + typedef HWAddress<6> address_type; /** * \brief Constructs a WEPDecrypter object. @@ -93,19 +135,18 @@ namespace Crypto { void remove_password(const address_type &addr); /** - * \brief Decrypts the provided PDU and forwards the decrypted - * PDU to the functor held by this object. + * \brief Decrypts the provided PDU. * * A Dot11Data PDU is looked up inside the provided PDU chain. * If no such PDU exists or there is no password associated * with the Dot11 packet's BSSID, then the PDU is left intact. * * Otherwise, the packet is decrypted using the given password. - * If the CRC found after decrypting it is invalid, - * then false is returned. + * If the CRC found after decrypting is invalid, false is + * returned. * - * \return false if decryption failed due to invalid CRC, true - * otherwise. + * \return false if no decryption was performed or decryption + * failed, true otherwise. */ bool decrypt(PDU &pdu); private: @@ -117,6 +158,95 @@ namespace Crypto { std::vector key_buffer; }; + #ifdef HAVE_WPA2_DECRYPTION + /** + * \brief Decrypts WPA2-encrypted traffic. + * + * This class takes valid PSK and SSID tuples, captures client handshakes, + * and decrypts their traffic afterwards. + */ + class WPA2Decrypter { + public: + /* + * \brief The type used to store Dot11 addresses. + */ + typedef HWAddress<6> address_type; + + /** + * \brief Adds a supplicant's information. + * + * This associates an SSID with a PSK., and allows the decryption of + * any BSSIDs that broadcast the same SSID. + * + * The decrypter will inspect beacon frames, looking for SSID tags + * that contain the given SSID. + * + * Note that using this overload, the decryption of data frames and + * handshake capturing will be disabled until any access point + * broadcasts the provided SSID(this shouldn't take long at all). + * If this is not the desired behaviour, then you should check out + * the ovther add_supplicant_data overload. + * + * \param psk The PSK associated with the SSID. + * \param ssid The network's SSID. + */ + void add_supplicant_data(const std::string &psk, const std::string &ssid); + + /** + * \brief Adds a supplicant's information, including the BSSID. + * + * This overload can be used if the BSSID associated with this SSID is + * known beforehand. The addr parameter indicates which specific BSSID + * is associated to the SSID. + * + * Note that if any other access point broadcasts the provided SSID, + * it will be taken into account as well. + * + * \param psk The PSK associated with this SSID. + * \param ssid The network's SSID. + * \param addr The access point's BSSID. + */ + void add_supplicant_data(const std::string &psk, const std::string &ssid, const address_type &addr); + + /** + * \brief Decrypts the provided PDU. + * + * A Dot11Data PDU is looked up inside the provided PDU chain. + * If no such PDU exists or no PSK was associated with the SSID + * broadcasted by the Dot11 packet's BSSID, or no EAPOL handshake + * was captured for the client involved in the communication, + * then the PDU is left intact. + * + * Otherwise, the packet is decrypted using the generated PTK. + * If the resulting MIC is invalid, then the packet is left intact. + * + * \return false if no decryption was performed, or the decryption + * failed, true otherwise. + */ + bool decrypt(PDU &pdu); + private: + typedef std::map pmks_map; + typedef std::map bssids_map; + typedef std::pair addr_pair; + typedef std::map keys_map; + + void try_add_keys(const Dot11Data &dot11, const RSNHandshake &hs); + addr_pair make_addr_pair(const address_type &addr1, const address_type &addr2) { + return (addr1 < addr2) ? + std::make_pair(addr1, addr2) : + std::make_pair(addr2, addr1); + } + addr_pair extract_addr_pair(const Dot11Data &dot11); + bssids_map::const_iterator find_ap(const Dot11Data &dot11); + void add_access_point(const std::string &ssid, const address_type &addr); + + RSNHandshakeCapturer capturer; + pmks_map pmks; + bssids_map aps; + keys_map keys; + }; + #endif // HAVE_WPA2_DECRYPTION + /** * \brief Pluggable decrypter object which can be used to decrypt * data on sniffing sessions. diff --git a/include/internals.h b/include/internals.h index 520749b..9f6938a 100644 --- a/include/internals.h +++ b/include/internals.h @@ -41,27 +41,70 @@ */ namespace Tins { namespace Internals { - void skip_line(std::istream &input); - bool from_hex(const std::string &str, uint32_t &result); +template +class byte_array { +public: + typedef uint8_t* iterator; + typedef const uint8_t* const_iterator; - template - struct enable_if { - - }; + byte_array() { + std::fill(begin(), end(), 0); + } + + template + byte_array(InputIterator start, InputIterator last) { + std::copy(start, end, data); + } + + template + byte_array(InputIterator start) { + std::copy(start, n, data); + } + + iterator begin() { + return data; + } + + iterator end() { + return data + n; + } + + const_iterator begin() const { + return data; + } + + const_iterator end() const { + return data + n; + } + + size_t size() const { + return n; + } +private: + uint8_t data[n]; +}; - template - struct enable_if { - typedef T type; - }; +void skip_line(std::istream &input); +bool from_hex(const std::string &str, uint32_t &result); + +template +struct enable_if { - PDU *pdu_from_flag(Constants::Ethernet::e flag, const uint8_t *buffer, - uint32_t size, bool rawpdu_on_no_match = true); - - PDU *pdu_from_flag(PDU::PDUType type, const uint8_t *buffer, uint32_t size); - - Constants::Ethernet::e pdu_flag_to_ether_type(PDU::PDUType flag); -} -} +}; + +template +struct enable_if { + typedef T type; +}; + +PDU *pdu_from_flag(Constants::Ethernet::e flag, const uint8_t *buffer, + uint32_t size, bool rawpdu_on_no_match = true); + +PDU *pdu_from_flag(PDU::PDUType type, const uint8_t *buffer, uint32_t size); + +Constants::Ethernet::e pdu_flag_to_ether_type(PDU::PDUType flag); +} // namespace Internals +} // namespace Tins /** * \endcond */ diff --git a/src/crypto.cpp b/src/crypto.cpp index 8b5d1c2..749c852 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -27,7 +27,11 @@ * */ +#include +#include +#include #include "crypto.h" +#include "dot11.h" namespace Tins { namespace Crypto { @@ -98,5 +102,227 @@ PDU *WEPDecrypter::decrypt(RawPDU &raw, const std::string &password) { return 0; } } + +#ifdef HAVE_WPA2_DECRYPTION +// WPA2Decrypter + +const HWAddress<6> &min(const HWAddress<6>& lhs, const HWAddress<6>& rhs) { + return lhs < rhs ? lhs : rhs; } + +const HWAddress<6> &max(const HWAddress<6>& lhs, const HWAddress<6>& rhs) { + return lhs < rhs ? rhs : lhs; } + +template +void xor_range(InputIterator1 src1, InputIterator2 src2, OutputIterator dst, size_t sz) { + for(size_t i = 0; i < sz; ++i) { + *dst++ = *src1++ ^ *src2++; + } +} + +namespace WPA2 { +CCMPSessionKeys::CCMPSessionKeys(const RSNHandshake &hs, const pmk_type &pmk) { + uint8_t PKE[100] = "Pairwise key expansion"; + uint8_t MIC[16]; + min(hs.client_address(), hs.supplicant_address()).copy(PKE + 23); + max(hs.client_address(), hs.supplicant_address()).copy(PKE + 29); + const uint8_t *nonce1 = hs.handshake()[1].nonce(), + *nonce2 = hs.handshake()[2].nonce(); + if(std::lexicographical_compare(nonce1, nonce1 + 32, nonce2, nonce2 + 32)) { + std::copy(nonce1, nonce1 + 32, PKE + 35); + std::copy(nonce2, nonce2 + 32, PKE + 67); + } + else { + std::copy(nonce2, nonce2 + 32, PKE + 35); + std::copy(nonce1, nonce1 + 32, PKE + 67); + } + for(int i(0); i < 4; ++i) { + PKE[99] = i; + HMAC(EVP_sha1(), pmk.begin(), pmk.size(), PKE, 100, ptk.begin() + i * 20, 0); + } + PDU::serialization_type buffer = const_cast(hs.handshake()[3]).serialize(); + std::fill(buffer.begin() + 81, buffer.begin() + 81 + 16, 0); + if(hs.handshake()[3].key_descriptor() == 2) + HMAC(EVP_sha1(), ptk.begin(), 16, &buffer[0], buffer.size(), MIC, 0); + else + HMAC(EVP_md5(), ptk.begin(), 16, &buffer[0], buffer.size(), MIC, 0); + + if(!std::equal(MIC, MIC + sizeof(MIC), hs.handshake()[3].mic())) + throw invalid_handshake(); +} + +SNAP *CCMPSessionKeys::decrypt_unicast(const Dot11Data &dot11, const RawPDU &raw) const { + const RawPDU::payload_type &pload = raw.payload(); + uint8_t MIC[16] = {0}; + uint8_t PN[6] = { + pload[7], + pload[6], + pload[5], + pload[4], + pload[1], + pload[0] + }; + + uint8_t AAD[32] = {0}; + AAD[0] = 0; + AAD[1] = 22 + 6 * int(dot11.from_ds() && dot11.to_ds()); + if(dot11.subtype() == Dot11::QOS_DATA_DATA) + AAD[1] += 2; + AAD[2] = dot11.protocol() | (dot11.type() << 2) | ((dot11.subtype() << 4) & 0x80); + AAD[3] = 0x40 | dot11.to_ds() | (dot11.from_ds() << 1) | + (dot11.more_frag() << 2) | (dot11.order() << 7); + dot11.addr1().copy(AAD + 4); + dot11.addr2().copy(AAD + 10); + dot11.addr3().copy(AAD + 16); + + AAD[22] = dot11.frag_num(); + AAD[23] = 0; + + if(dot11.from_ds() && dot11.to_ds()) + dot11.addr4().copy(AAD + 24); + + AES_KEY ctx; + AES_set_encrypt_key(ptk.begin() + 32, 128, &ctx); + uint8_t crypted_block[16]; + size_t total_sz = raw.payload_size() - 16, offset = 8, blocks = (total_sz + 15) / 16; + std::vector output(total_sz); + + uint8_t counter[16]; + counter[0] = 0x59; + counter[1] = 0; + dot11.addr2().copy(counter + 2); + std::copy(PN, PN + 6, counter + 8); + counter[14] = (total_sz >> 8) & 0xff; + counter[15] = total_sz & 0xff; + + AES_encrypt(counter, MIC, &ctx); + xor_range(MIC, AAD, MIC, 16); + AES_encrypt(MIC, MIC, &ctx); + xor_range(MIC, AAD + 16, MIC, 16); + AES_encrypt(MIC, MIC, &ctx); + + counter[0] = 1; + counter[14] = counter[15] = 0; + AES_encrypt(counter, crypted_block, &ctx); + uint8_t nice_MIC[8]; + std::copy(pload.begin() + pload.size() - 8, pload.end(), nice_MIC); + xor_range(crypted_block, nice_MIC, nice_MIC, 8); + for(size_t i = 1; i <= blocks; ++i) { + size_t block_sz = (i == blocks) ? (total_sz % 16) : 16; + counter[14] = (i >> 8) & 0xff; + counter[15] = i & 0xff; + AES_encrypt(counter, crypted_block, &ctx ); + xor_range(crypted_block, &pload[offset], &output[(i - 1) * 16], block_sz); + + xor_range(MIC, &output[(i - 1) * 16], MIC, block_sz); + AES_encrypt(MIC, MIC, &ctx); + offset += block_sz; + } + return (std::equal(nice_MIC, nice_MIC + sizeof(nice_MIC), MIC)) ? + new SNAP(&output[0], output.size()) : + 0; +} + +// supplicant_data + +SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid) { + PKCS5_PBKDF2_HMAC_SHA1( + psk.c_str(), + psk.size(), + (unsigned char *)ssid.c_str(), + ssid.size(), + 4096, + pmk_.size(), + pmk_.begin() + ); +} + +const SupplicantData::pmk_type &SupplicantData::pmk() const { + return pmk_; +} +} // namespace WPA2 + +void WPA2Decrypter::add_supplicant_data(const std::string &psk, const std::string &ssid) { + pmks.insert(std::make_pair(ssid, WPA2::SupplicantData(psk, ssid))); +} + +void WPA2Decrypter::add_supplicant_data(const std::string &psk, const std::string &ssid, + const address_type &addr) +{ + // ADD CODE PLX + add_supplicant_data(psk, ssid); +} + +void WPA2Decrypter::add_access_point(const std::string &ssid, const address_type &addr) { + pmks_map::const_iterator it = pmks.find(ssid); + if(it == pmks.end()) + throw std::runtime_error("supplicant data not registered"); + aps.insert(std::make_pair(addr, it->second)); +} + +void WPA2Decrypter::try_add_keys(const Dot11Data &dot11, const RSNHandshake &hs) { + bssids_map::const_iterator it = find_ap(dot11); + if(it != aps.end()) { + addr_pair addr_p = extract_addr_pair(dot11); + try { + keys.insert(std::make_pair(addr_p, WPA2::CCMPSessionKeys(hs, it->second.pmk()))); + } + catch(WPA2::invalid_handshake&) { } + } +} + +WPA2Decrypter::addr_pair WPA2Decrypter::extract_addr_pair(const Dot11Data &dot11) { + if(dot11.from_ds() && !dot11.to_ds()) + return make_addr_pair(dot11.addr2(), dot11.addr3()); + else if(!dot11.from_ds() && dot11.to_ds()) + return make_addr_pair(dot11.addr1(), dot11.addr2()); + else + return make_addr_pair(dot11.addr2(), dot11.addr3()); +} + +WPA2Decrypter::bssids_map::const_iterator WPA2Decrypter::find_ap(const Dot11Data &dot11) { + if(dot11.from_ds() && !dot11.to_ds()) + return aps.find(dot11.addr2()); + else if(!dot11.from_ds() && dot11.to_ds()) + return aps.find(dot11.addr1()); + else + return aps.find(dot11.addr3()); +} + +bool WPA2Decrypter::decrypt(PDU &pdu) { + if(capturer.process_packet(pdu)) { + try_add_keys(pdu.rfind_pdu(), capturer.handshakes().front()); + capturer.clear_handshakes(); + } + else if(const Dot11Beacon *beacon = pdu.find_pdu()) { + if(aps.count(beacon->addr3()) == 0) { + try { + std::string ssid = beacon->ssid(); + if(pmks.count(ssid)) { + add_access_point(ssid, beacon->addr3()); + } + } + catch(option_not_found&) { } + } + } + else { + Dot11Data *data = pdu.find_pdu(); + RawPDU *raw = pdu.find_pdu(); + if(data && raw && data->wep()) { + keys_map::const_iterator it = keys.find(extract_addr_pair(*data)); + if(it != keys.end()) { + SNAP *snap = it->second.decrypt_unicast(*data, *raw); + if(snap) { + data->inner_pdu(snap); + data->wep(0); + return true; + } + } + } + } + return false; +} // namespace WPA2 +#endif // HAVE_WPA2_DECRYPTION +} // namespace Crypto +} // namespace Tins