mirror of
https://github.com/mfontanini/libtins
synced 2026-01-23 02:35:57 +01:00
Allow retrieving keys on WPA2Decrypter.
This commit is contained in:
@@ -47,26 +47,79 @@ class Dot11;
|
||||
class Dot11Data;
|
||||
|
||||
namespace Crypto {
|
||||
/**
|
||||
* \cond
|
||||
*/
|
||||
struct RC4Key;
|
||||
#ifdef HAVE_WPA2_DECRYPTION
|
||||
namespace WPA2 {
|
||||
class invalid_handshake : public std::exception {
|
||||
public:
|
||||
const char *what() const throw() {
|
||||
return "invalid handshake";
|
||||
}
|
||||
};
|
||||
/**
|
||||
* \brief Class that represents the keys used to decrypt a session.
|
||||
*/
|
||||
class SessionKeys {
|
||||
public:
|
||||
typedef Internals::byte_array<80> ptk_type;
|
||||
typedef Internals::byte_array<32> pmk_type;
|
||||
/**
|
||||
* The size of the Pairwise Master Key.
|
||||
*/
|
||||
static const size_t PMK_SIZE;
|
||||
|
||||
/**
|
||||
* The size of the Pairwise Transient Key.
|
||||
*/
|
||||
static const size_t PTK_SIZE;
|
||||
|
||||
/**
|
||||
* The type used to hold the PTK (this has to be PTK_SIZE bytes long).
|
||||
*/
|
||||
typedef std::vector<uint8_t> ptk_type;
|
||||
|
||||
/**
|
||||
* The type used to hold the PMK (this has to be PMK_SIZE bytes long).
|
||||
*/
|
||||
typedef std::vector<uint8_t> pmk_type;
|
||||
|
||||
/**
|
||||
* Default constructs a SessionKeys object.
|
||||
*/
|
||||
SessionKeys();
|
||||
|
||||
/**
|
||||
* \brief Constructs an instance using the provided PTK and a flag
|
||||
* indicating whether it should use ccmp.
|
||||
*
|
||||
* \param ptk The PTK to use.
|
||||
* \param is_ccmp Indicates whether to use CCMP to decrypt this traffic.
|
||||
*/
|
||||
SessionKeys(const ptk_type& ptk, bool is_ccmp);
|
||||
|
||||
/**
|
||||
* \brief Constructs an instance using a handshake and a PMK.
|
||||
*
|
||||
* This will internally construct the PTK from the input parameters.
|
||||
*
|
||||
* \param hs The handshake to use.
|
||||
* \param pmk The PMK to use.
|
||||
*/
|
||||
SessionKeys(const RSNHandshake &hs, const pmk_type &pmk);
|
||||
|
||||
/**
|
||||
* \brief Decrypts a unicast packet.
|
||||
*
|
||||
* \param dot11 The encrypted packet to decrypt.
|
||||
* \param raw The raw layer on the packet to decrypt.
|
||||
* \return A SNAP layer containing the decrypted traffic or a null pointer
|
||||
* if decryption failed.
|
||||
*/
|
||||
SNAP *decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const;
|
||||
|
||||
/**
|
||||
* \brief Gets the PTK for this session keys.
|
||||
* \return The Pairwise Transcient Key.
|
||||
*/
|
||||
const ptk_type& get_ptk() const;
|
||||
|
||||
/**
|
||||
* \brief Indicates whether CCMP is used to decrypt packets
|
||||
* /return true iff CCMP is used.
|
||||
*/
|
||||
bool uses_ccmp() const;
|
||||
private:
|
||||
SNAP *ccmp_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const;
|
||||
SNAP *tkip_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const;
|
||||
@@ -75,7 +128,10 @@ namespace Crypto {
|
||||
ptk_type ptk;
|
||||
bool is_ccmp;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \cond
|
||||
*/
|
||||
class SupplicantData {
|
||||
public:
|
||||
typedef HWAddress<6> address_type;
|
||||
@@ -87,7 +143,7 @@ namespace Crypto {
|
||||
private:
|
||||
pmk_type pmk_;
|
||||
};
|
||||
}
|
||||
} // WPA2
|
||||
#endif // HAVE_WPA2_DECRYPTION
|
||||
/**
|
||||
* \endcond
|
||||
@@ -180,6 +236,27 @@ namespace Crypto {
|
||||
*/
|
||||
typedef HWAddress<6> address_type;
|
||||
|
||||
/**
|
||||
* \brief Represents a pair of mac addresses.
|
||||
*
|
||||
* This is used to identify a host and the access point to which
|
||||
* it is connected. The first element in the pair will always de
|
||||
* lower or equal than the second one, so that given any host and
|
||||
* the access point it's connected to, we can uniquely identify
|
||||
* it with an address pair.
|
||||
*/
|
||||
typedef std::pair<address_type, address_type> addr_pair;
|
||||
|
||||
/**
|
||||
* \brief Maps an address pair to the session keys.
|
||||
*
|
||||
* This type associates an address pair (host, access point) with the
|
||||
* session keys, as generated using the packets seen on a handshake.
|
||||
*
|
||||
* \sa addr_pair
|
||||
*/
|
||||
typedef std::map<addr_pair, WPA2::SessionKeys> keys_map;
|
||||
|
||||
/**
|
||||
* \brief Adds an access points's information.
|
||||
*
|
||||
@@ -216,6 +293,28 @@ namespace Crypto {
|
||||
*/
|
||||
void add_ap_data(const std::string &psk, const std::string &ssid, const address_type &addr);
|
||||
|
||||
/**
|
||||
* \brief Explicitly add decryption keys.
|
||||
*
|
||||
* This method associates a pair (host, access point) with the given decryption keys.
|
||||
* All encrypted packets sent between the given addresses will be decrypted using the
|
||||
* provided keys.
|
||||
*
|
||||
* This method shouldn't normally be required. The WPA2Decrypter will be waiting for
|
||||
* handshakes and will automatically extract the session keys, decrypting all
|
||||
* encrypted packets with them. You should only use this method if for some reason
|
||||
* you know the actual keys being used (because you checked and stored the keys_map
|
||||
* somewhere).
|
||||
*
|
||||
* The actual order of the addresses doesn't matter, this method will make sure
|
||||
* they're sorted.
|
||||
*
|
||||
* \param addresses The address pair (host, access point) to associate.
|
||||
* \param session_keys The keys to use when decrypting messages sent between the
|
||||
* given addresses.
|
||||
*/
|
||||
void add_decryption_keys(const addr_pair& addresses, const WPA2::SessionKeys& session_keys);
|
||||
|
||||
/**
|
||||
* \brief Decrypts the provided PDU.
|
||||
*
|
||||
@@ -232,11 +331,19 @@ namespace Crypto {
|
||||
* failed, true otherwise.
|
||||
*/
|
||||
bool decrypt(PDU &pdu);
|
||||
|
||||
/**
|
||||
* \brief Getter for the keys on this decrypter
|
||||
*
|
||||
* The returned map will be populated every time a new, complete, handshake
|
||||
* is captured.
|
||||
*
|
||||
* \return The WPA2Decrypter keys map.
|
||||
*/
|
||||
const keys_map& get_keys() const;
|
||||
private:
|
||||
typedef std::map<std::string, WPA2::SupplicantData> pmks_map;
|
||||
typedef std::map<address_type, WPA2::SupplicantData> bssids_map;
|
||||
typedef std::pair<address_type, address_type> addr_pair;
|
||||
typedef std::map<addr_pair, WPA2::SessionKeys> 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) {
|
||||
|
||||
@@ -191,6 +191,20 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
namespace Crypto {
|
||||
namespace WPA2 {
|
||||
/**
|
||||
* \brief Exception thrown when an invalid WPA2 handshake is found.
|
||||
*/
|
||||
class invalid_handshake : public std::exception {
|
||||
public:
|
||||
const char *what() const throw() {
|
||||
return "Invalid WPA2 handshake";
|
||||
}
|
||||
};
|
||||
} // WPA2
|
||||
} // Crypto
|
||||
|
||||
} // Tins
|
||||
|
||||
#endif // TINS_EXCEPTIONS_H
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#endif // HAVE_WPA2_DECRYPTION
|
||||
#include "dot11/dot11_data.h"
|
||||
#include "dot11/dot11_beacon.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
namespace Tins {
|
||||
namespace Crypto {
|
||||
@@ -115,6 +116,8 @@ PDU *WEPDecrypter::decrypt(RawPDU &raw, const std::string &password) {
|
||||
#ifdef HAVE_WPA2_DECRYPTION
|
||||
// WPA2Decrypter
|
||||
|
||||
using WPA2::SessionKeys;
|
||||
|
||||
const HWAddress<6> &min(const HWAddress<6>& lhs, const HWAddress<6>& rhs) {
|
||||
return lhs < rhs ? lhs : rhs;
|
||||
}
|
||||
@@ -232,11 +235,26 @@ HWAddress<6> get_bssid(const Dot11Data &dot11) {
|
||||
|
||||
namespace WPA2 {
|
||||
|
||||
const size_t SessionKeys::PTK_SIZE = 80;
|
||||
const size_t SessionKeys::PMK_SIZE = 32;
|
||||
|
||||
SessionKeys::SessionKeys() {
|
||||
|
||||
}
|
||||
|
||||
SessionKeys::SessionKeys(const RSNHandshake &hs, const pmk_type &pmk) {
|
||||
SessionKeys::SessionKeys(const ptk_type& ptk, bool is_ccmp)
|
||||
: ptk(ptk), is_ccmp(is_ccmp) {
|
||||
if (ptk.size() != PTK_SIZE) {
|
||||
throw invalid_handshake();
|
||||
}
|
||||
}
|
||||
|
||||
SessionKeys::SessionKeys(const RSNHandshake &hs, const pmk_type &pmk)
|
||||
: ptk(PTK_SIZE) {
|
||||
if (pmk.size() != PMK_SIZE) {
|
||||
throw invalid_handshake();
|
||||
}
|
||||
|
||||
uint8_t PKE[100] = "Pairwise key expansion";
|
||||
uint8_t MIC[16];
|
||||
min(hs.client_address(), hs.supplicant_address()).copy(PKE + 23);
|
||||
@@ -253,14 +271,14 @@ SessionKeys::SessionKeys(const RSNHandshake &hs, const pmk_type &pmk) {
|
||||
}
|
||||
for(int i(0); i < 4; ++i) {
|
||||
PKE[99] = i;
|
||||
HMAC(EVP_sha1(), pmk.begin(), pmk.size(), PKE, 100, ptk.begin() + i * 20, 0);
|
||||
HMAC(EVP_sha1(), &pmk[0], pmk.size(), PKE, 100, &ptk[0] + i * 20, 0);
|
||||
}
|
||||
PDU::serialization_type buffer = const_cast<RSNEAPOL&>(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);
|
||||
HMAC(EVP_sha1(), &ptk[0], 16, &buffer[0], buffer.size(), MIC, 0);
|
||||
else
|
||||
HMAC(EVP_md5(), ptk.begin(), 16, &buffer[0], buffer.size(), MIC, 0);
|
||||
HMAC(EVP_md5(), &ptk[0], 16, &buffer[0], buffer.size(), MIC, 0);
|
||||
|
||||
if(!std::equal(MIC, MIC + sizeof(MIC), hs.handshake()[3].mic()))
|
||||
throw invalid_handshake();
|
||||
@@ -298,7 +316,7 @@ SNAP *SessionKeys::ccmp_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) con
|
||||
dot11.addr4().copy(AAD + 24);
|
||||
|
||||
AES_KEY ctx;
|
||||
AES_set_encrypt_key(ptk.begin() + 32, 128, &ctx);
|
||||
AES_set_encrypt_key(&ptk[0] + 32, 128, &ctx);
|
||||
uint8_t crypted_block[16];
|
||||
size_t total_sz = raw.payload_size() - 16, offset = 8, blocks = (total_sz + 15) / 16;
|
||||
|
||||
@@ -343,7 +361,7 @@ SNAP *SessionKeys::ccmp_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) con
|
||||
|
||||
RC4Key SessionKeys::generate_rc4_key(const Dot11Data &dot11, const RawPDU &raw) const {
|
||||
const RawPDU::payload_type &pload = raw.payload();
|
||||
const uint8_t *tk = ptk.begin() + 32;
|
||||
const uint8_t *tk = &ptk[0] + 32;
|
||||
Internals::byte_array<16> rc4_key;
|
||||
uint16_t ppk[6];
|
||||
const Dot11::address_type addr = dot11.addr2();
|
||||
@@ -429,9 +447,18 @@ SNAP *SessionKeys::decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const {
|
||||
tkip_decrypt_unicast(dot11, raw);
|
||||
}
|
||||
|
||||
const SessionKeys::ptk_type& SessionKeys::get_ptk() const {
|
||||
return ptk;
|
||||
}
|
||||
|
||||
bool SessionKeys::uses_ccmp() const {
|
||||
return is_ccmp;
|
||||
}
|
||||
|
||||
// supplicant_data
|
||||
|
||||
SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid) {
|
||||
SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid)
|
||||
: pmk_(SessionKeys::PMK_SIZE) {
|
||||
PKCS5_PBKDF2_HMAC_SHA1(
|
||||
psk.c_str(),
|
||||
psk.size(),
|
||||
@@ -439,7 +466,7 @@ SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid)
|
||||
ssid.size(),
|
||||
4096,
|
||||
pmk_.size(),
|
||||
pmk_.begin()
|
||||
&pmk_[0]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -466,18 +493,27 @@ void WPA2Decrypter::add_access_point(const std::string &ssid, const address_type
|
||||
aps.insert(std::make_pair(addr, it->second));
|
||||
}
|
||||
|
||||
void WPA2Decrypter::add_decryption_keys(const addr_pair& addresses, const SessionKeys& session_keys) {
|
||||
addr_pair sorted_pair = make_addr_pair(addresses.first, addresses.second);
|
||||
keys[sorted_pair] = session_keys;
|
||||
}
|
||||
|
||||
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 {
|
||||
WPA2::SessionKeys session(hs, it->second.pmk());
|
||||
SessionKeys session(hs, it->second.pmk());
|
||||
keys[addr_p] = session;
|
||||
}
|
||||
catch(WPA2::invalid_handshake&) { }
|
||||
}
|
||||
}
|
||||
|
||||
const WPA2Decrypter::keys_map& WPA2Decrypter::get_keys() const {
|
||||
return keys;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
@@ -127,6 +127,37 @@ TEST_F(WPA2DecryptTest, DecryptCCMPWithoutUsingBeacon) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WPA2DecryptTest, DecryptCCMPUsingKey) {
|
||||
Crypto::WPA2Decrypter::addr_pair addresses;
|
||||
Crypto::WPA2::SessionKeys session_keys;
|
||||
|
||||
{
|
||||
Crypto::WPA2Decrypter decrypter;
|
||||
decrypter.add_ap_data("Induction", "Coherer", "00:0c:41:82:b2:55");
|
||||
for(size_t i = 1; i < 5; ++i) {
|
||||
RadioTap radio(ccmp_packets[i], ccmp_packets_size[i]);
|
||||
ASSERT_FALSE(decrypter.decrypt(radio));
|
||||
}
|
||||
const Crypto::WPA2Decrypter::keys_map& keys = decrypter.get_keys();
|
||||
ASSERT_EQ(1, keys.size());
|
||||
addresses = keys.begin()->first;
|
||||
session_keys = keys.begin()->second;
|
||||
}
|
||||
|
||||
Crypto::WPA2Decrypter decrypter;
|
||||
decrypter.add_decryption_keys(addresses, session_keys);
|
||||
for(size_t i = 5; i < 7; ++i) {
|
||||
RadioTap radio(ccmp_packets[i], ccmp_packets_size[i]);
|
||||
ASSERT_TRUE(decrypter.decrypt(radio));
|
||||
if(i == 5)
|
||||
check_ccmp_packet5(radio);
|
||||
else
|
||||
check_ccmp_packet6(radio);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(session_keys.uses_ccmp());
|
||||
}
|
||||
|
||||
TEST_F(WPA2DecryptTest, DecryptTKIPUsingBeacon) {
|
||||
Crypto::WPA2Decrypter decrypter;
|
||||
decrypter.add_ap_data("libtinstest", "NODO");
|
||||
@@ -161,6 +192,37 @@ TEST_F(WPA2DecryptTest, DecryptTKIPWithoutUsingBeacon) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WPA2DecryptTest, DecryptTKIPUsingKey) {
|
||||
Crypto::WPA2Decrypter::addr_pair addresses;
|
||||
Crypto::WPA2::SessionKeys session_keys;
|
||||
|
||||
{
|
||||
Crypto::WPA2Decrypter decrypter;
|
||||
decrypter.add_ap_data("libtinstest", "NODO", "00:1b:11:d2:1b:eb");
|
||||
for(size_t i = 1; i < 5; ++i) {
|
||||
RadioTap radio(tkip_packets[i], tkip_packets_size[i]);
|
||||
ASSERT_FALSE(decrypter.decrypt(radio));
|
||||
}
|
||||
const Crypto::WPA2Decrypter::keys_map& keys = decrypter.get_keys();
|
||||
ASSERT_EQ(1, keys.size());
|
||||
addresses = keys.begin()->first;
|
||||
session_keys = keys.begin()->second;
|
||||
}
|
||||
|
||||
Crypto::WPA2Decrypter decrypter;
|
||||
decrypter.add_decryption_keys(addresses, session_keys);
|
||||
for(size_t i = 5; i < 7; ++i) {
|
||||
RadioTap radio(tkip_packets[i], tkip_packets_size[i]);
|
||||
ASSERT_TRUE(decrypter.decrypt(radio));
|
||||
if(i == 5)
|
||||
check_tkip_packet5(radio);
|
||||
else
|
||||
check_tkip_packet6(radio);
|
||||
}
|
||||
|
||||
EXPECT_FALSE(session_keys.uses_ccmp());
|
||||
}
|
||||
|
||||
TEST_F(WPA2DecryptTest, DecryptCCMPAndTKIPUsingBeacon) {
|
||||
Crypto::WPA2Decrypter decrypter;
|
||||
decrypter.add_ap_data("libtinstest", "NODO");
|
||||
|
||||
Reference in New Issue
Block a user