diff --git a/.gitignore b/.gitignore index 02d44d8..7dc44ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -.vscode -.micropico -.gitignore -PiPicoPATLFGGGGG.code-workspace -dev -ComicCode-Regular.* +.vscode +.micropico +.gitignore +PiPicoPATLFGGGGG.code-workspace +dev +ComicCode-Regular.* logs \ No newline at end of file diff --git a/COPYING.txt b/COPYING.txt index a4bb4d4..3315b83 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -1,12 +1,12 @@ - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 - - Everyone is permitted to copy and distribute verbatim or modified - copies of this license document, and changing it is allowed as long - as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. - + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/README.md b/README.md index e2bc25f..5af2411 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,114 @@ -# SillyFilly Pi Pico W Power Analysis Tool -# LFGGGGGGGGGGG -# FRONG -A tool for simplifying power analysis attacks against other gadgets. -Built on the Raspberry Pi Pico W, it runs a PWM channel to do manual clock control on the nugget you're hacking, and then reads an ADC channel to measure voltage used at each clock cycle. - -## Screenshots -Config page -![index-page](resources/index.png) - -Running page -![running-page](resources/running.png) - -Serial terminal at boot -![terminal-boot](resources/terminal0.png) - -Serial terminal while sampling and looping -![terminal-running](resources/terminal-running.png) - -## Installation -Make sure you have Micropython installed on your Pico W. -Open up your Pico W in your favorite IDE (Thonny and VS Code are commonly used) and upload main.py. -If you're having trouble, see the official [Raspberry Pi Pico W Getting Started Page](https://projects.raspberrypi.org/en/projects/get-started-pico-w) - -## Usage -1) Optional: edit the `power_analysis_pin` and `clock_pulse_pin` variables as you see fit -2) Edit the `ssid` and `password` variables with your wifi name (ssid) and the password -3) Set up the Pico W with pins on a breadboard, connect your power_analysis_pin and clock_pulse pin to the nugget (See Theory and Method) -3) Plug the Pico W to USB of your computer -4) Open the serial terminal of the Pico W -5) Reset the Pico W -6) When the Pico W connects, it will display a message like `Open web browser and navigate to http://x.x.x.x` Open a web browser on the same network and navigate to that address -7) Configure the options on the page to your liking (see below under Options) - - -## Options -### main.py -`power_analysis_pin` - This is the pin that is reading the power usage from the device - Defaults to GPIO 28 - -`clock_pulse_pin` - This pin provides the clock signal that you will be using to manually take control of the nuggets clock - Defaults to GPIO 20 - -`ssid` - This is your wifi network name. Only bgn supported (2.4GHz) - Dont forget the 's around the name - -`password` - Your wifi password - Dont forget the 's around the password - -`power_on_pin` - The pin that toggles 3.3v on and off to activate and reset the nugget board - -`seconds_awake` - The number of seconds to turn on power_on_pin before resetting - -### On the Webpage -`Duty Cycle` - The Percentage of the time that the clock is on vs off - 50% is by far the most common, it makes a normal square wave and is the default output - for most oscilators that you will be replacing - -`Frequency` - Frequency in Hertz (Hz) - How many times per second you want your nuggets clock to pulse - Dont make this too high - -`Samples Per Manual Clock Pulse` - This is how many times per clock pulse to sample the nuggets power usage - Dont make this too high - -## Hardware Needed -1) Raspberry Pi Pico W or Raspberry Pi Pico WH -2) Pins -3) Breadboard -4) Small capacitior (470uF is fine or anywhere within that range) -5) Wires -6) Optional: spikey probes - -## Theory -Power analysis attacks are very simple in concept. -The idea is monitoring the amount of power a device uses very carefully to get some data leaked. -Two things are most important during these attacks: -1) Getting the most accurate, raw read of the power usage at a high sample rate -2) Controlling the clock of the device to slow down the processor enough to get the readings - -To accomplish #1, we will get our power reading probe as close to the action of the processor as possible, while als adding a capacitor ground, which will pull any remaining juice out of the system. - -For #2, we need to remove the builtin crystal oscilator and replace that connection with our clock pulse probe. - -For this example, I will describe a firmware decryption power analysis attack. - -Scenario: You have and ESP32-S3 based device with flash and bootloader encryption set up on it. You Can remove the RF sheield and get a raw dump of the flash chip with a SOP8 test clip and a programmer, but the data is encrypted. - -## Method -1) We disassemble the device to the bare board we're interested in -2) Then remove the RF shield and crystal oscilator from the ESP32-S3 via reflow -3) Clip a SOP8 test clip on the 8pin flash chip and dump the encrypted data with a programmer like a CH431a or T48 -4) Set up the breadboard with the Pico W. -5) Place the clock_pulse_pin probe where the crystal oscillator used to be -6) Place power_analysis_pin probe on the rawest vin power spot you can find -7) Run the Pico W (see Usage) -8) Dump serial data to some manner of logger Todo: figure out this tooling -9) Perform statistical attack to dump key! Todo: automate and figure out tooling - -Todo: Make readme not suck --- - -Distributed under the [WTFPL - The Do What the Fuck You Want to Public License](http://www.wtfpl.net/) -See [COPYING.txt](COPYING.txt) -![WTFPL](resources/wtfpl.png) \ No newline at end of file +# SillyFilly Pi Pico W Power Analysis Tool +# LFGGGGGGGGGGG +# FRONG +A tool for simplifying power analysis attacks against other gadgets. +Built on the Raspberry Pi Pico W, it runs a PWM channel to do manual clock control on the nugget you're hacking, and then reads an ADC channel to measure voltage used at each clock cycle. + +## Screenshots +Config page +![index-page](resources/index.png) + +Running page +![running-page](resources/running.png) + +Serial terminal at boot +![terminal-boot](resources/terminal0.png) + +Serial terminal while sampling and looping +![terminal-running](resources/terminal-running.png) + +## Installation +Make sure you have Micropython installed on your Pico W. +Open up your Pico W in your favorite IDE (Thonny and VS Code are commonly used) and upload main.py. +If you're having trouble, see the official [Raspberry Pi Pico W Getting Started Page](https://projects.raspberrypi.org/en/projects/get-started-pico-w) + +## Usage +1) Optional: edit the `power_analysis_pin` and `clock_pulse_pin` variables as you see fit +2) Edit the `ssid` and `password` variables with your wifi name (ssid) and the password +3) Set up the Pico W with pins on a breadboard, connect your power_analysis_pin and clock_pulse pin to the nugget (See Theory and Method) +3) Plug the Pico W to USB of your computer +4) Open the serial terminal of the Pico W +5) Reset the Pico W +6) When the Pico W connects, it will display a message like `Open web browser and navigate to http://x.x.x.x` Open a web browser on the same network and navigate to that address +7) Configure the options on the page to your liking (see below under Options) + + +## Options +### main.py +`power_analysis_pin` + This is the pin that is reading the power usage from the device + Defaults to GPIO 28 + +`clock_pulse_pin` + This pin provides the clock signal that you will be using to manually take control of the nuggets clock + Defaults to GPIO 20 + +`ssid` + This is your wifi network name. Only bgn supported (2.4GHz) + Dont forget the 's around the name + +`password` + Your wifi password + Dont forget the 's around the password + +`power_on_pin` + The pin that toggles 3.3v on and off to activate and reset the nugget board + +`seconds_awake` + The number of seconds to turn on power_on_pin before resetting + +### On the Webpage +`Duty Cycle` + The Percentage of the time that the clock is on vs off + 50% is by far the most common, it makes a normal square wave and is the default output + for most oscilators that you will be replacing + +`Frequency` + Frequency in Hertz (Hz) + How many times per second you want your nuggets clock to pulse + Dont make this too high + +`Samples Per Manual Clock Pulse` + This is how many times per clock pulse to sample the nuggets power usage + Dont make this too high + +## Hardware Needed +1) Raspberry Pi Pico W or Raspberry Pi Pico WH +2) Pins +3) Breadboard +4) Small capacitior (470uF is fine or anywhere within that range) +5) Wires +6) Optional: spikey probes + +## Theory +Power analysis attacks are very simple in concept. +The idea is monitoring the amount of power a device uses very carefully to get some data leaked. +Two things are most important during these attacks: +1) Getting the most accurate, raw read of the power usage at a high sample rate +2) Controlling the clock of the device to slow down the processor enough to get the readings + +To accomplish #1, we will get our power reading probe as close to the action of the processor as possible, while als adding a capacitor ground, which will pull any remaining juice out of the system. + +For #2, we need to remove the builtin crystal oscilator and replace that connection with our clock pulse probe. + +For this example, I will describe a firmware decryption power analysis attack. + +Scenario: You have and ESP32-S3 based device with flash and bootloader encryption set up on it. You Can remove the RF sheield and get a raw dump of the flash chip with a SOP8 test clip and a programmer, but the data is encrypted. + +## Method +1) We disassemble the device to the bare board we're interested in +2) Then remove the RF shield and crystal oscilator from the ESP32-S3 via reflow +3) Clip a SOP8 test clip on the 8pin flash chip and dump the encrypted data with a programmer like a CH431a or T48 +4) Set up the breadboard with the Pico W. +5) Place the clock_pulse_pin probe where the crystal oscillator used to be +6) Place power_analysis_pin probe on the rawest vin power spot you can find +7) Run the Pico W (see Usage) +8) Dump serial data to some manner of logger Todo: figure out this tooling +9) Perform statistical attack to dump key! Todo: automate and figure out tooling + +Todo: Make readme not suck +-- + +Distributed under the [WTFPL - The Do What the Fuck You Want to Public License](http://www.wtfpl.net/) +See [COPYING.txt](COPYING.txt) +[![WTFPL](resources/wtfpl.png)](http://www.wtfpl.net/) \ No newline at end of file diff --git a/main.py b/main.py index 4f57449..9b89572 100644 --- a/main.py +++ b/main.py @@ -1,259 +1,259 @@ -import network -import socket -import machine -from machine import Timer -import utime -import _thread -import math - -# gpio pin to read power analysis off of (int) -# reads 16bit (0-65535) between 0.0v and 3.3v -# (16 bit is phony, real read is 12 bit) -power_analysis_pin = 28 - -# output gpio pin generating clock pulse -clock_pulse_pin = 20 - -# this pin gets power and toggles off and on every X PWM cycles -# it powers the board you're targeting -power_on_pin = 2 - -# how many seconds to power the board before reset -seconds_awake = 3 - -# Wi-Fi credentials -ssid = 'your-wifi-name-here' -password = 'your-wifi-password-here' - -# == Clock Pulse Genertor == # -# starts a pwm -# usage do_pwm(int gpio_pin, float duty_cycle, int hertz frequency, int samples_per_clock_pulse) -def do_pwm(outpin, duty_cycle, frequency): - global pwm_pin - output_pin = machine.Pin(outpin) - - # configure pwm - pwm_pin = machine.PWM(output_pin) - pwm_pin.freq(frequency) # frequency in Hz - pwm_duty = math.floor(duty_cycle*65535) # duty cycle is a uint 16bit - - # run da pwm - pwm_pin.duty_u16(pwm_duty) - -# us of sleep calculate4d by ceil(1000000 / (frequency_in_Hz * samples_per_pulse)) -# 1000000 is one million, 1,000,000 or 10^6 -def us_samples(frequency, samples_per_pulse): - return math.ceil(1000000/(frequency*samples_per_pulse)) - -# == Read Value off of device == # -# uses ADC, 16bit output is phony, is actualloy 12bit. -kill = False # for the thread killing hack -def do_adc(adcpin, frequency, samples_per_pulse): - analog_value = machine.ADC(adcpin) - us_sample = us_samples(frequency, samples_per_pulse) - - global kill - while True: - reading = analog_value.read_u16() # do the actual reading, actual precision is 12bit - print(reading,',',sep='') - utime.sleep_us(us_sample) - - # just keep checking if kill is set to True by reset_init() - # if it is True, reset kill to False, kill the thread, and break for good measure - if kill is True: - kill = False - _thread.exit() - break - -# HTML template for the webpage -header = """ - - - - SillyFilly Pi Pico Power Analysis Tool (LFFGGGGG) - - - - - - -""" - -footer = """ - - -""" - -def default_webpage(): - template = f""" - {header} -
-

SillyFilly Pi Pico Power Analysis Tool (LFFGGGGG)

- Config:
- Duty Cycle
50%

- Frequency (Hz)


- Samples Per Clock Pulse:


- -
- - {footer} - """ - return str(template) - -def running_webpage(outpin, adcpin, duty_cycle, frequency, samples_per_pulse, loop_time): - dutyy = duty_cycle*100 - us_sample = us_samples(frequency, samples_per_pulse) - template = f""" - {header} -
-

SillyFilly Pi Pico Power Analysis Tool (LFFGGGGG)

-

WE RUNNING NOWWWWW LFGGGGGGGGGGGGG

-

- Clock Pulse Pin: {outpin}
- Power Analysis Pin: {adcpin}
- Power On Pin:{power_on_pin}


- - Frequency: {frequency}Hz (Duty Cycle: {dutyy}%
- Samples Per Pulse: {samples_per_pulse}
- Delay Between Samples: {us_sample}us

- Loop length: {loop_time}


- LFGGGGGGGG FRONG -

-
- {footer} - """ - return str(template) - -print("Board Started or Reset\n===========\n\n") - -toggle_power = machine.Pin(power_on_pin, machine.Pin.OUT) -toggle_power.value(0) - -# I like to have the LED on :3 -led = machine.Pin('LED', machine.Pin.OUT) -led.value(1) - -# do da thingggggssssss for da loooop -def reset_init(t): - global kill - kill = True # kill da adc thread - toggle_power.value(0) # kill power on pin - print("==Looping==") - pwm_pin.deinit() # stop pwm - utime.sleep(1) - toggle_power.value(1) # re-enable power on pin - do_pwm(clock_pulse_pin, duty_cycle_in, freq_in) # restart pwm - - # restart da threaddyyyyy - _thread.start_new_thread(do_adc, (power_analysis_pin, freq_in, samples_per_pulse_in)) - -# Connect to WLAN -wlan = network.WLAN(network.STA_IF) -wlan.active(True) -wlan.connect(ssid, password) - -# Wait for Wi-Fi connection -connection_timeout = 9 -while connection_timeout > 0: - if wlan.status() >= 3: - break - connection_timeout -= 1 - print('Waiting for Wi-Fi connection...') - utime.sleep(1) - -# Check if connection is successful -if wlan.status() != 3: - raise RuntimeError('Failed to establish a network connection') -else: - print('Connection successful!') - network_info = wlan.ifconfig() - print(f"\n\nOpen web browser and navigate to http://{network_info[0]}\n\n") - -# Set up socket and start listening -addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] -s = socket.socket() -s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -s.bind(addr) -s.listen() - -while True: - try: - conn, addr = s.accept() - request = conn.recv(1024) - request = str(request) - - try: - fullreq = request.split() - request = fullreq[1] - except IndexError: - pass - - # Process the action page! - if request.startswith('/action?', 0): - # parse the request path to extract da GET vars - req_string = request.split('?')[1] - req_vars = req_string.split('&') - for f in req_vars: - req_split = f.split('=') - # Type strict whitelist for safety and compatibility - if req_split[0] == 'duty_cycle': - duty_cycle_in = float(req_split[1]) - elif req_split[0] == 'freq': - freq_in = int(req_split[1]) - elif req_split[0] == 'samples_per_pulse': - samples_per_pulse_in = int(req_split[1]) - - loop_time = (seconds_awake-1)*1000 # the -1 is because reset_init() adds a 1 second delay - dutyyy = duty_cycle_in*100 - us_sample = us_samples(freq_in, samples_per_pulse_in) - - print(f"Clock pulse pin: {clock_pulse_pin}\n\nPower analysis pin: {power_analysis_pin}\n\nPower on pin: {power_on_pin}\n\nDuty cycle: {duty_cycle_in}%\n\nFrequency: {freq_in}Hz\n\nSamples per clock pulse: {samples_per_pulse_in}\n\nTime between samples: {us_sample}us\n\nLoop length: {loop_time}ms\n\n") - - # run the clock pulse (PWM) task - do_pwm(clock_pulse_pin, duty_cycle_in, freq_in) - - # fork the voltage measuring ADC reading to the second core - _thread.start_new_thread(do_adc, (power_analysis_pin, freq_in, samples_per_pulse_in)) - - Timer(mode=Timer.PERIODIC, period=loop_time, callback=reset_init) - - # outpin, adcpin, duty_cycle, frequency, samples_per_pulse, loop_time - response = running_webpage(clock_pulse_pin, power_analysis_pin, dutyyy, freq_in, samples_per_pulse_in, loop_time) - - else: - response = default_webpage() - - # Send the HTTP response and close the conn - conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') - conn.send(response) - conn.close() - - except OSError as e: - conn.close() +import network +import socket +import machine +from machine import Timer +import utime +import _thread +import math + +# gpio pin to read power analysis off of (int) +# reads 16bit (0-65535) between 0.0v and 3.3v +# (16 bit is phony, real read is 12 bit) +power_analysis_pin = 28 + +# output gpio pin generating clock pulse +clock_pulse_pin = 20 + +# this pin gets power and toggles off and on every X PWM cycles +# it powers the board you're targeting +power_on_pin = 2 + +# how many seconds to power the board before reset +seconds_awake = 3 + +# Wi-Fi credentials +ssid = 'your-wifi-name-here' +password = 'your-wifi-password-here' + +# == Clock Pulse Genertor == # +# starts a pwm +# usage do_pwm(int gpio_pin, float duty_cycle, int hertz frequency, int samples_per_clock_pulse) +def do_pwm(outpin, duty_cycle, frequency): + global pwm_pin + output_pin = machine.Pin(outpin) + + # configure pwm + pwm_pin = machine.PWM(output_pin) + pwm_pin.freq(frequency) # frequency in Hz + pwm_duty = math.floor(duty_cycle*65535) # duty cycle is a uint 16bit + + # run da pwm + pwm_pin.duty_u16(pwm_duty) + +# us of sleep calculate4d by ceil(1000000 / (frequency_in_Hz * samples_per_pulse)) +# 1000000 is one million, 1,000,000 or 10^6 +def us_samples(frequency, samples_per_pulse): + return math.ceil(1000000/(frequency*samples_per_pulse)) + +# == Read Value off of device == # +# uses ADC, 16bit output is phony, is actualloy 12bit. +kill = False # for the thread killing hack +def do_adc(adcpin, frequency, samples_per_pulse): + analog_value = machine.ADC(adcpin) + us_sample = us_samples(frequency, samples_per_pulse) + + global kill + while True: + reading = analog_value.read_u16() # do the actual reading, actual precision is 12bit + print(reading,',',sep='') + utime.sleep_us(us_sample) + + # just keep checking if kill is set to True by reset_init() + # if it is True, reset kill to False, kill the thread, and break for good measure + if kill is True: + kill = False + _thread.exit() + break + +# HTML template for the webpage +header = """ + + + + SillyFilly Pi Pico Power Analysis Tool (LFFGGGGG) + + + + + + +""" + +footer = """ + + +""" + +def default_webpage(): + template = f""" + {header} +
+

SillyFilly Pi Pico Power Analysis Tool (LFFGGGGG)

+ Config:
+ Duty Cycle
50%

+ Frequency (Hz)


+ Samples Per Clock Pulse:


+ +
+ + {footer} + """ + return str(template) + +def running_webpage(outpin, adcpin, duty_cycle, frequency, samples_per_pulse, loop_time): + dutyy = duty_cycle*100 + us_sample = us_samples(frequency, samples_per_pulse) + template = f""" + {header} +
+

SillyFilly Pi Pico Power Analysis Tool (LFFGGGGG)

+

WE RUNNING NOWWWWW LFGGGGGGGGGGGGG

+

+ Clock Pulse Pin: {outpin}
+ Power Analysis Pin: {adcpin}
+ Power On Pin:{power_on_pin}


+ + Frequency: {frequency}Hz (Duty Cycle: {dutyy}%
+ Samples Per Pulse: {samples_per_pulse}
+ Delay Between Samples: {us_sample}us

+ Loop length: {loop_time}


+ LFGGGGGGGG FRONG +

+
+ {footer} + """ + return str(template) + +print("Board Started or Reset\n===========\n\n") + +toggle_power = machine.Pin(power_on_pin, machine.Pin.OUT) +toggle_power.value(0) + +# I like to have the LED on :3 +led = machine.Pin('LED', machine.Pin.OUT) +led.value(1) + +# do da thingggggssssss for da loooop +def reset_init(t): + global kill + kill = True # kill da adc thread + toggle_power.value(0) # kill power on pin + print("==Looping==") + pwm_pin.deinit() # stop pwm + utime.sleep(1) + toggle_power.value(1) # re-enable power on pin + do_pwm(clock_pulse_pin, duty_cycle_in, freq_in) # restart pwm + + # restart da threaddyyyyy + _thread.start_new_thread(do_adc, (power_analysis_pin, freq_in, samples_per_pulse_in)) + +# Connect to WLAN +wlan = network.WLAN(network.STA_IF) +wlan.active(True) +wlan.connect(ssid, password) + +# Wait for Wi-Fi connection +connection_timeout = 9 +while connection_timeout > 0: + if wlan.status() >= 3: + break + connection_timeout -= 1 + print('Waiting for Wi-Fi connection...') + utime.sleep(1) + +# Check if connection is successful +if wlan.status() != 3: + raise RuntimeError('Failed to establish a network connection') +else: + print('Connection successful!') + network_info = wlan.ifconfig() + print(f"\n\nOpen web browser and navigate to http://{network_info[0]}\n\n") + +# Set up socket and start listening +addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] +s = socket.socket() +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +s.bind(addr) +s.listen() + +while True: + try: + conn, addr = s.accept() + request = conn.recv(1024) + request = str(request) + + try: + fullreq = request.split() + request = fullreq[1] + except IndexError: + pass + + # Process the action page! + if request.startswith('/action?', 0): + # parse the request path to extract da GET vars + req_string = request.split('?')[1] + req_vars = req_string.split('&') + for f in req_vars: + req_split = f.split('=') + # Type strict whitelist for safety and compatibility + if req_split[0] == 'duty_cycle': + duty_cycle_in = float(req_split[1]) + elif req_split[0] == 'freq': + freq_in = int(req_split[1]) + elif req_split[0] == 'samples_per_pulse': + samples_per_pulse_in = int(req_split[1]) + + loop_time = (seconds_awake-1)*1000 # the -1 is because reset_init() adds a 1 second delay + dutyyy = duty_cycle_in*100 + us_sample = us_samples(freq_in, samples_per_pulse_in) + + print(f"Clock pulse pin: {clock_pulse_pin}\n\nPower analysis pin: {power_analysis_pin}\n\nPower on pin: {power_on_pin}\n\nDuty cycle: {duty_cycle_in}%\n\nFrequency: {freq_in}Hz\n\nSamples per clock pulse: {samples_per_pulse_in}\n\nTime between samples: {us_sample}us\n\nLoop length: {loop_time}ms\n\n") + + # run the clock pulse (PWM) task + do_pwm(clock_pulse_pin, duty_cycle_in, freq_in) + + # fork the voltage measuring ADC reading to the second core + _thread.start_new_thread(do_adc, (power_analysis_pin, freq_in, samples_per_pulse_in)) + + Timer(mode=Timer.PERIODIC, period=loop_time, callback=reset_init) + + # outpin, adcpin, duty_cycle, frequency, samples_per_pulse, loop_time + response = running_webpage(clock_pulse_pin, power_analysis_pin, dutyyy, freq_in, samples_per_pulse_in, loop_time) + + else: + response = default_webpage() + + # Send the HTTP response and close the conn + conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') + conn.send(response) + conn.close() + + except OSError as e: + conn.close() print('Connection closed') \ No newline at end of file