Initial commit
This commit is contained in:
commit
87154280a8
|
@ -0,0 +1,11 @@
|
|||
.*
|
||||
!.gitignore
|
||||
log/*
|
||||
!log/.keep
|
||||
bundle/*
|
||||
!bundle/.keep
|
||||
*.pyc
|
||||
tmp
|
||||
*.old
|
||||
config.py
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
## LetterBomb web service implementation
|
||||
|
||||
This is the LetterBomb Wii System Menu 4.3 exploit implementation running on
|
||||
https://please.hackmii.com/. Requires Python 2.7, Flask, and geoip2.
|
||||
|
||||
In case you're wondering, `country_regions.txt` is based on reporting data
|
||||
from Homebrew Channel updates. This was implemented because we found out
|
||||
that about 30% of our users are stupid and won't pick the correct system
|
||||
menu version (and then complain that it doesn't work), so we use GeoIP to
|
||||
guess the right default for them. Similarly, the MAC address check was
|
||||
implemented because people would type in garbage for the MAC address and
|
||||
then complain that it doesn't work.
|
||||
|
||||
This does not include the HackMii Installer bundle. Those files would go
|
||||
in `bundle/`.
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
import os, zipfile, StringIO, hashlib, hmac, struct, logging, urllib, random, json
|
||||
import geoip2.database
|
||||
from logging.handlers import SMTPHandler
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Flask, request, g, render_template, make_response, redirect, url_for
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object("config")
|
||||
|
||||
TEMPLATES = {
|
||||
'U':"templateU.bin",
|
||||
'E':"templateE.bin",
|
||||
'J':"templateJ.bin",
|
||||
'K':"templateK.bin",
|
||||
}
|
||||
|
||||
BUNDLEBASE = os.path.join(app.root_path, 'bundle')
|
||||
#BUNDLE = [(name, os.path.join(BUNDLEBASE,name)) for name in os.listdir(BUNDLEBASE)]
|
||||
|
||||
#OUI_LIST = [i.decode('hex') for i in open(os.path.join(app.root_path, 'oui_list.txt')).read().split("\n") if len(i)==6]
|
||||
|
||||
COUNTRY_REGIONS = dict([l.split(" ") for l in open(os.path.join(app.root_path, 'country_regions.txt')).read().split("\n") if l])
|
||||
|
||||
#gi = pygeoip.GeoIP(os.path.join(app.root_path, 'GeoIP.dat'))
|
||||
gi = geoip2.database.Reader('/usr/share/GeoIP/GeoLite2-Country.mmdb')
|
||||
|
||||
class RequestFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
s = logging.Formatter.format(self, record)
|
||||
try:
|
||||
return '[%s] [%s] [%s %s] '%(self.formatTime(record), request.remote_addr, request.method, request.path) + s
|
||||
except:
|
||||
return '[%s] [SYS] '%self.formatTime(record) + s
|
||||
|
||||
if not app.debug:
|
||||
mail_handler = SMTPHandler(app.config['SMTP_SERVER'],
|
||||
app.config['APP_EMAIL'],
|
||||
app.config['ADMIN_EMAIL'], 'LetterBomb ERROR')
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
app.logger.addHandler(mail_handler)
|
||||
|
||||
handler = logging.FileHandler(os.path.join(app.root_path, 'log', 'info.log'))
|
||||
handler.setLevel(logging.INFO)
|
||||
handler.setFormatter(RequestFormatter())
|
||||
app.logger.addHandler(handler)
|
||||
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.logger.warning('Starting...')
|
||||
|
||||
def region():
|
||||
try:
|
||||
country = gi.country(request.remote_addr).country.iso_code
|
||||
app.logger.info("GI: %s -> %s", request.remote_addr, country)
|
||||
return COUNTRY_REGIONS.get(country, 'E')
|
||||
except:
|
||||
app.logger.exception("GeoIP exception")
|
||||
return 'E'
|
||||
|
||||
def _index(error=None):
|
||||
g.recaptcha_args = 'k=%s' % app.config['RECAPTCHA_PUBLICKEY']
|
||||
rs = make_response(render_template('index.html', region=region(), error=error))
|
||||
#rs.headers['Cache-Control'] = 'private, max-age=0, no-store, no-cache, must-revalidate'
|
||||
#rs.headers['Etag'] = str(random.randrange(2**64))
|
||||
rs.headers['Expires'] = 'Thu, 01 Dec 1983 20:00:00 GMT'
|
||||
return rs
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return _index()
|
||||
|
||||
|
||||
def captcha_check():
|
||||
try:
|
||||
oform = {
|
||||
#"privatekey": app.config['RECAPTCHA_PRIVATEKEY'],
|
||||
"secret": app.config['RECAPTCHA_PRIVATEKEY'],
|
||||
"remoteip": request.remote_addr,
|
||||
#"challenge": request.form.get('recaptcha_challenge_field',['']),
|
||||
#"response": request.form.get('recaptcha_response_field',[''])
|
||||
"response": request.form.get('g-recaptcha-response',[''])
|
||||
}
|
||||
|
||||
#f = urllib.urlopen("http://api-verify.recaptcha.net/verify", urllib.urlencode(oform))
|
||||
f = urllib.urlopen("https://www.google.com/recaptcha/api/siteverify", urllib.urlencode(oform))
|
||||
|
||||
#result = f.readline().replace("\n","")
|
||||
#error = f.readline().replace("\n","")
|
||||
d = json.load(f)
|
||||
result = d["success"]
|
||||
f.close()
|
||||
|
||||
if not result:# != 'true':
|
||||
#if error != 'incorrect-captcha-sol':
|
||||
app.logger.info("ReCaptcha fail: %r, %r", oform, d)
|
||||
#g.recaptcha_args += "&error=" + error
|
||||
return False
|
||||
|
||||
except:
|
||||
#g.recaptcha_args += "&error=unknown"
|
||||
return False
|
||||
return True
|
||||
|
||||
@app.route('/haxx', methods=["POST"])
|
||||
def haxx():
|
||||
OUI_LIST = [i.decode('hex') for i in open(os.path.join(app.root_path, 'oui_list.txt')).read().split("\n") if len(i)==6]
|
||||
g.recaptcha_args = 'k=%s' % app.config['RECAPTCHA_PUBLICKEY']
|
||||
dt = datetime.utcnow() - timedelta(1)
|
||||
delta = (dt - datetime(2000, 1, 1))
|
||||
timestamp = delta.days * 86400 + delta.seconds
|
||||
|
||||
try:
|
||||
mac = ''.join([chr(int(request.form[i],16)) for i in "abcdef"])
|
||||
template = TEMPLATES[request.form['region']]
|
||||
bundle = 'bundle' in request.form
|
||||
except:
|
||||
return _index("Invalid input.")
|
||||
if not captcha_check():
|
||||
return _index("Are you a human?")
|
||||
|
||||
if mac == "\x00\x17\xab\x99\x99\x99":
|
||||
app.logger.info('Derp MAC %s at %d ver %s bundle %r', mac.encode('hex'), timestamp, request.form['region'], bundle)
|
||||
return _index("If you're using Dolphin, try File->Open instead ;-).")
|
||||
|
||||
if not any([mac.startswith(i) for i in OUI_LIST]):
|
||||
app.logger.info('Bad MAC %s at %d ver %s bundle %r', mac.encode('hex'), timestamp, request.form['region'], bundle)
|
||||
return _index("The exploit will only work if you enter your Wii's MAC address.")
|
||||
|
||||
|
||||
key = hashlib.sha1(mac+"\x75\x79\x79").digest()
|
||||
|
||||
blob = bytearray(open(os.path.join(app.root_path, template),'rb').read())
|
||||
blob[0x08:0x10] = key[:8]
|
||||
blob[0xb0:0xc4] = "\x00"*20
|
||||
blob[0x7c:0x80] = struct.pack(">I", timestamp)
|
||||
blob[0x80:0x8a] = "%010d"%timestamp
|
||||
blob[0xb0:0xc4] = hmac.new(key[8:], str(blob), hashlib.sha1).digest()
|
||||
|
||||
path = "private/wii/title/HAEA/%s/%s/%04d/%02d/%02d/%02d/%02d/HABA_#1/txt/%08X.000" % (
|
||||
key[:4].encode('hex').upper(), key[4:8].encode('hex').upper(),
|
||||
dt.year, dt.month-1, dt.day, dt.hour, dt.minute, timestamp
|
||||
)
|
||||
|
||||
zipdata = StringIO.StringIO()
|
||||
zip = zipfile.ZipFile(zipdata, 'w')
|
||||
zip.writestr(path, str(blob))
|
||||
BUNDLE = [(name, os.path.join(BUNDLEBASE,name)) for name in os.listdir(BUNDLEBASE) if not name.startswith(".")]
|
||||
if bundle:
|
||||
for name, path in BUNDLE:
|
||||
zip.write(path, name)
|
||||
zip.close()
|
||||
|
||||
app.logger.info('LetterBombed %s at %d ver %s bundle %r', mac.encode('hex'), timestamp, request.form['region'], bundle)
|
||||
|
||||
rs = make_response(zipdata.getvalue())
|
||||
zipdata.close()
|
||||
rs.headers.add('Content-Disposition', 'attachment', filename="LetterBomb.zip")
|
||||
rs.headers['Content-Type'] = 'application/zip'
|
||||
return rs
|
||||
|
||||
application=app
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run('0.0.0.0', 10142)
|
|
@ -0,0 +1,13 @@
|
|||
# -!- coding: utf-8 -!-
|
||||
|
||||
# for cookies and stuff, nobody really cares
|
||||
SECRET_KEY = 'fillme'
|
||||
|
||||
RECAPTCHA_PUBLICKEY = "fillme"
|
||||
RECAPTCHA_PRIVATEKEY = "fillme"
|
||||
|
||||
DEBUG = False
|
||||
ADMIN_EMAIL = ['foo@bar.com']
|
||||
SMTP_SERVER = '127.0.0.1'
|
||||
APP_EMAIL = 'foo@bar.com'
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
E
|
||||
A1 U
|
||||
A2 E
|
||||
AW U
|
||||
AO E
|
||||
AI U
|
||||
AX E
|
||||
AL E
|
||||
AD E
|
||||
AN U
|
||||
AP J
|
||||
AE U
|
||||
AR U
|
||||
AM U
|
||||
AS U
|
||||
AG U
|
||||
AU E
|
||||
AT E
|
||||
AZ E
|
||||
BE E
|
||||
BJ E
|
||||
BF E
|
||||
BD U
|
||||
BG E
|
||||
BH U
|
||||
BS U
|
||||
BA E
|
||||
BY E
|
||||
BZ U
|
||||
BM U
|
||||
BO U
|
||||
BR U
|
||||
BB U
|
||||
BN U
|
||||
BW E
|
||||
CF E
|
||||
CA U
|
||||
CH E
|
||||
CL U
|
||||
CN U
|
||||
CI E
|
||||
CM E
|
||||
CD E
|
||||
CO U
|
||||
CV E
|
||||
CR U
|
||||
CU U
|
||||
KY U
|
||||
CY E
|
||||
CZ E
|
||||
DE E
|
||||
DJ E
|
||||
DM U
|
||||
DK E
|
||||
DO U
|
||||
DZ E
|
||||
EC U
|
||||
EG U
|
||||
ER E
|
||||
ES E
|
||||
EE E
|
||||
EU E
|
||||
FI E
|
||||
FJ E
|
||||
FK E
|
||||
FR E
|
||||
FO E
|
||||
FM U
|
||||
GA E
|
||||
GB E
|
||||
GE U
|
||||
GG E
|
||||
GH E
|
||||
GI E
|
||||
GP E
|
||||
GQ E
|
||||
GR E
|
||||
GD U
|
||||
GL E
|
||||
GT U
|
||||
GU U
|
||||
GY U
|
||||
HK J
|
||||
HN U
|
||||
HR E
|
||||
HT U
|
||||
HU E
|
||||
ID U
|
||||
IM E
|
||||
IN U
|
||||
IO U
|
||||
IE E
|
||||
IR U
|
||||
IQ U
|
||||
IS E
|
||||
IL E
|
||||
IT E
|
||||
JM U
|
||||
JE E
|
||||
JO U
|
||||
JP J
|
||||
KZ E
|
||||
KE E
|
||||
KG U
|
||||
KH U
|
||||
KN U
|
||||
KR K
|
||||
KW U
|
||||
LA U
|
||||
LB U
|
||||
LY E
|
||||
LC U
|
||||
LI E
|
||||
LK E
|
||||
LT E
|
||||
LU E
|
||||
LV E
|
||||
MO J
|
||||
MF E
|
||||
MA E
|
||||
MC E
|
||||
MD E
|
||||
MG E
|
||||
MV U
|
||||
MX U
|
||||
MH U
|
||||
MK E
|
||||
ML E
|
||||
MT E
|
||||
MM U
|
||||
ME E
|
||||
MN U
|
||||
MP U
|
||||
MZ E
|
||||
MR E
|
||||
MQ E
|
||||
MU E
|
||||
MW E
|
||||
MY U
|
||||
NA E
|
||||
NC E
|
||||
NE E
|
||||
NF E
|
||||
NG E
|
||||
NI U
|
||||
NL E
|
||||
NO E
|
||||
NP U
|
||||
NZ E
|
||||
OM U
|
||||
PK U
|
||||
PA U
|
||||
PE U
|
||||
PH U
|
||||
PG E
|
||||
PL E
|
||||
PR U
|
||||
PT E
|
||||
PY U
|
||||
PS U
|
||||
PF E
|
||||
QA U
|
||||
RE E
|
||||
RO E
|
||||
RU E
|
||||
SA U
|
||||
SD U
|
||||
SN E
|
||||
SG U
|
||||
SV U
|
||||
SM E
|
||||
PM E
|
||||
RS E
|
||||
SR U
|
||||
SK E
|
||||
SI E
|
||||
SE E
|
||||
SZ E
|
||||
SC U
|
||||
SY U
|
||||
TC U
|
||||
TG J
|
||||
TH U
|
||||
TJ U
|
||||
TK U
|
||||
TM E
|
||||
TL E
|
||||
TT U
|
||||
TO E
|
||||
TR E
|
||||
TW J
|
||||
TZ U
|
||||
UG U
|
||||
UA E
|
||||
UY U
|
||||
US U
|
||||
UZ U
|
||||
VA U
|
||||
VC U
|
||||
VE U
|
||||
VG U
|
||||
VI U
|
||||
VN U
|
||||
VU E
|
||||
YE U
|
||||
YT E
|
||||
ZA E
|
||||
ZM U
|
||||
ZW E
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/python2
|
||||
from flup.server.fcgi import WSGIServer
|
||||
from app import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
WSGIServer(app, debug=False).run()
|
|
@ -0,0 +1,59 @@
|
|||
0009BF
|
||||
001656
|
||||
0017AB
|
||||
00191D
|
||||
0019FD
|
||||
001AE9
|
||||
001B7A
|
||||
001BEA
|
||||
001CBE
|
||||
001DBC
|
||||
001E35
|
||||
001EA9
|
||||
001F32
|
||||
001FC5
|
||||
002147
|
||||
0021BD
|
||||
00224C
|
||||
0022AA
|
||||
0022D7
|
||||
002331
|
||||
0023CC
|
||||
00241E
|
||||
002444
|
||||
0024F3
|
||||
0025A0
|
||||
002659
|
||||
002709
|
||||
0403D6
|
||||
182A7B
|
||||
2C10C1
|
||||
34AF2C
|
||||
40D28A
|
||||
40F407
|
||||
582F40
|
||||
58BDA3
|
||||
5C521E
|
||||
606BFF
|
||||
64B5C6
|
||||
78A2A0
|
||||
7CBB8A
|
||||
8C56C5
|
||||
8CCDE8
|
||||
9458CB
|
||||
98B6E9
|
||||
9CE635
|
||||
A438CC
|
||||
A45C27
|
||||
A4C0E1
|
||||
B87826
|
||||
B88AEC
|
||||
B8AE6E
|
||||
CC9E00
|
||||
CCFB65
|
||||
D86BF7
|
||||
DC68EB
|
||||
E00C7F
|
||||
E0E751
|
||||
E84ECE
|
||||
ECC40D
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,57 @@
|
|||
body {
|
||||
font: 1.0em "Trebuchet MS", Verdana, Arial, sans-serif;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
input.button {
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-moz-box-shadow: 2px 2px 3px #666;
|
||||
-webkit-box-shadow: 2px 2px 3px #666;
|
||||
box-shadow: 2px 2px 3px #666;
|
||||
|
||||
font-size: 20px;
|
||||
padding: 4px 7px;
|
||||
outline: 0;
|
||||
-webkit-appearance: none;
|
||||
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
input.buttonr:enabled {
|
||||
border-color:red;
|
||||
}
|
||||
input.buttonb:enabled {
|
||||
border-color:blue;
|
||||
}
|
||||
|
||||
input.box {
|
||||
border: 1px solid #ccc;
|
||||
|
||||
text-transform:uppercase;
|
||||
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-moz-box-shadow: 2px 2px 3px #666;
|
||||
-webkit-box-shadow: 2px 2px 3px #666;
|
||||
box-shadow: 2px 2px 3px #666;
|
||||
|
||||
font-size: 20px;
|
||||
padding: 4px 7px;
|
||||
outline: 0;
|
||||
-webkit-appearance: none;
|
||||
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
input.box:focus {
|
||||
border-color: #339933;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,95 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>LetterBomb</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/mac.css" />
|
||||
<script type="text/javascript">
|
||||
captcha_passed = false;
|
||||
function keyHandler(e, obj, next) {
|
||||
if (window.event) { // IE
|
||||
keynum = e.keyCode;
|
||||
} else if(e.which) { // sane browsers
|
||||
keynum = e.which;
|
||||
}
|
||||
if (keynum == 0x08 || keynum == 0x09 || (keynum >= 96 && keynum <= 105))
|
||||
return true;
|
||||
keychar = String.fromCharCode(keynum);
|
||||
if (keychar.match(/[0-9A-F]/) == null) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function check() {
|
||||
var ids = ["a","b","c","d","e","f"];
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var val = document.getElementById(ids[i]).value;
|
||||
if (val.match(/[0-9a-fA-F][0-9a-fA-F]/) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return captcha_passed;
|
||||
}
|
||||
function update() {
|
||||
var ok = check();
|
||||
document.getElementById('submit_btn').disabled = !ok;
|
||||
document.getElementById('submit_btn2').disabled = !ok;
|
||||
}
|
||||
function doNext(obj, next) {
|
||||
if (obj.value.length == 2 && next != null) {
|
||||
document.getElementById(next).value = '';
|
||||
document.getElementById(next).focus();
|
||||
}
|
||||
update();
|
||||
}
|
||||
function captcha_ok() {
|
||||
captcha_passed = true;
|
||||
update();
|
||||
}
|
||||
function captcha_expired() {
|
||||
captcha_passed = false;
|
||||
update();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<img src='/static/letterbomb_icon.png' style="margin-bottom: -30px;"/>
|
||||
<h1>LetterBomb</h1>
|
||||
<form method="POST" action="{{ url_for('haxx') }}">
|
||||
<p>
|
||||
<h2>System Menu Version</h2>
|
||||
<input type='radio' name='region' value='U' {% if region=='U' %}checked{% endif %}>4.3U
|
||||
<input type='radio' name='region' value='E' {% if region=='E' %}checked{% endif %}>4.3E
|
||||
<input type='radio' name='region' value='J' {% if region=='J' %}checked{% endif %}>4.3J
|
||||
<input type='radio' name='region' value='K' {% if region=='K' %}checked{% endif %}>4.3K
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2>MAC Address</h2>
|
||||
<input type='text' maxlength='2' size='2' name='a' id='a' class='box' onkeydown="return keyHandler(event);" onkeyup="doNext(this, 'b');" placeholder='AA' />
|
||||
<input type='text' maxlength='2' size='2' name='b' id='b' class='box' onkeydown="return keyHandler(event);" onkeyup="doNext(this, 'c');" placeholder='BB' />
|
||||
<input type='text' maxlength='2' size='2' name='c' id='c' class='box' onkeydown="return keyHandler(event);" onkeyup="doNext(this, 'd');" placeholder='CC' />
|
||||
<input type='text' maxlength='2' size='2' name='d' id='d' class='box' onkeydown="return keyHandler(event);" onkeyup="doNext(this, 'e');" placeholder='DD' />
|
||||
<input type='text' maxlength='2' size='2' name='e' id='e' class='box' onkeydown="return keyHandler(event);" onkeyup="doNext(this, 'f');" placeholder='EE' />
|
||||
<input type='text' maxlength='2' size='2' name='f' id='f' class='box' onkeydown="return keyHandler(event);" onkeyup="update();" placeholder='FF' />
|
||||
<br />Necessary to create and sign the correct file
|
||||
</p>
|
||||
<p>
|
||||
<input type='checkbox' name='bundle' value='1' checked /> <b>Bundle the HackMii Installer for me!</b>
|
||||
</p>
|
||||
<div style="float: center; display: inline-block;" class="recaptcha">
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
<div class="g-recaptcha" data-sitekey="{{ config.RECAPTCHA_PUBLICKEY }}" data-callback="captcha_ok" data-expired-callback="captcha_expired"></div>
|
||||
</div>
|
||||
{% if error %}
|
||||
<div style="color: red; font-weight: bold; font-size: 18pt;">{{ error }}</div>
|
||||
{% endif %}
|
||||
<div style="color: red; font-weight: bold; font-size: 18pt;" id="nojs">You must have JavaScript enabled to use this site.</div>
|
||||
<script>document.getElementById("nojs").style.display = "none";</script>
|
||||
<p>
|
||||
<input type='submit' value='Cut the red wire' id='submit_btn' class='button buttonr' disabled /> <input type='submit' value='Cut the blue wire' id='submit_btn2' class='button buttonb' disabled />
|
||||
</p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
wget -O - http://standards.ieee.org/develop/regauth/oui/oui.txt | grep -i nintendo | grep base\ 16 | awk '{print $1}' | sort > oui_list.txt.new || exit 1
|
||||
diff -urN oui_list.txt oui_list.txt.new
|
||||
mv oui_list.txt oui_list.txt.old
|
||||
mv oui_list.txt.new oui_list.txt
|
Loading…
Reference in New Issue