/*
* Copyright 2012 Rui Araújo, Luís Fonseca
*
* This file is part of Router Keygen.
*
* Router Keygen is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Router Keygen is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Router Keygen. If not, see <http://www.gnu.org/licenses/>.
*/
package org.exobel.routerkeygen.algorithms;
import android.os.Parcel;
import android.os.Parcelable;
import org.exobel.routerkeygen.R;
import org.exobel.routerkeygen.ui.Preferences;
import org.exobel.routerkeygen.utils.InputStreamUtils;
import org.exobel.routerkeygen.utils.StringUtils;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipInputStream;
public class ThomsonKeygen extends Keygen {
public static final Parcelable.Creator<ThomsonKeygen> CREATOR = new Parcelable.Creator<ThomsonKeygen>() {
public ThomsonKeygen createFromParcel(Parcel in) {
return new ThomsonKeygen(in);
}
public ThomsonKeygen[] newArray(int size) {
return new ThomsonKeygen[size];
}
};
final private static byte[] charectbytes0 = {'3', '3', '3', '3', '3', '3',
'3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5',
'5', '5', '5', '5',};
final private static byte[] charectbytes1 = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A',};
final private byte[] cp = new byte[12];
final private byte[] table = new byte[1282];
final private byte[] routerESSID = new byte[3];
final private String ssidIdentifier;
private byte[] entry;
private int a, b, c;
private int year;
private int week;
private int sequenceNumber;
private boolean internetAlgorithm;
private boolean errorDict;
private int len = 0;
private String dictionaryPath;
private MessageDigest md;
private InputStream webdic;
public ThomsonKeygen(String ssid, String mac) {
super(ssid, mac);
this.errorDict = false;
this.ssidIdentifier = ssid.substring(ssid.length() - 6);
}
private ThomsonKeygen(Parcel in) {
super(in);
ssidIdentifier = in.readString();
errorDict = in.readInt() == 1;
}
@Override
public List<String> getKeys() {
try {
md = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e1) {
setErrorCode(R.string.msg_nosha1);
return null;
}
if (ssidIdentifier.length() != 6) {
setErrorCode(R.string.msg_shortessid6);
return null;
}
for (int i = 0; i < 6; i += 2)
routerESSID[i / 2] = (byte) ((Character.digit(
ssidIdentifier.charAt(i), 16) << 4) + Character.digit(
ssidIdentifier.charAt(i + 1), 16));
if (!internetAlgorithm) {
if (!localCalc())
return null;
} else {
if (!internetCalc())
return null;
}
if (getResults().size() == 0) {
setErrorCode(R.string.msg_errnomatches);
return null;
}
return getResults();
}
private boolean internetCalc() {
try {
ZipInputStream fis = new ZipInputStream(webdic);
fis.getNextEntry();
if (!InputStreamUtils.readFromInput(table, 1024, fis)) {
setErrorCode(R.string.msg_err_webdic_table);
errorDict = true;
fis.close();
return false;
}
int totalOffset = 0;
int lastLength = 0;
int i = (0xFF & routerESSID[0]) * 4;
int offset = ((0xFF & table[i]) << 24) | ((0xFF & table[i + 1]) << 16)
| ((0xFF & table[i + 2]) << 8) | (0xFF & table[i + 3]);
if (i != 1020) // routerESSID[0] != 0xFF ( 255*4 == 1020 )
lastLength = ((0xFF & table[i + 4]) << 24)
| ((0xFF & table[i + 5]) << 16)
| ((0xFF & table[i + 6]) << 8) | (0xFF & table[i + 7]);
totalOffset += offset;
long checkLong = 0, retLong;
/*
* ZipInputStream doens't seems to
* block.
*/
while (checkLong != (i / 4) * 768) {
retLong = fis.skip((i / 4) * 768 - checkLong);
if (retLong == -1) {
setErrorCode(R.string.msg_err_webdic_table);
errorDict = true;
fis.close();
return false;
} else
checkLong += retLong;
}
if (!InputStreamUtils.readFromInput(table, 768, fis)) {
setErrorCode(R.string.msg_err_webdic_table);
errorDict = true;
fis.close();
return false;
}
i = (0xFF & routerESSID[1]) * 3;
offset = ((0xFF & table[i]) << 16) | ((0xFF & table[i + 1]) << 8)
| (0xFF & table[i + 2]);
/*
* There's no check here because humans are lazy people and because
* it doesn't matter
*/
int length = ((0xFF & table[i + 3]) << 16)
| ((0xFF & table[i + 4]) << 8) | (0xFF & table[i + 5]);
totalOffset += offset;
length -= offset;
if ((lastLength != 0) && ((0xFF & routerESSID[1]) == 0xFF)) {
/*
* Only for SSID starting with XXFF. We use the next item on the
* main table to know the length of the sector we are looking
* for.
*/
lastLength -= totalOffset;
length = lastLength;
}
if (((0xFF & routerESSID[0]) == 0xFF)
&& ((0xFF & routerESSID[1]) == 0xFF)) {
/*
* Only for SSID starting with FFFF as we don't have a marker of
* the end.
*/
length = 2000;
}
final URL url = new URL(Preferences.PUB_DOWNLOAD);
URLConnection con = url.openConnection();
con.setRequestProperty("Range", "bytes=" + totalOffset + "-"
+ (totalOffset + length));
final DataInputStream onlineFile = new DataInputStream(con.getInputStream());
len = length;
this.entry = new byte[len];
if (!InputStreamUtils.readFromInput(entry, len, onlineFile)) {
setErrorCode(R.string.msg_err_webdic_table);
onlineFile.close();
errorDict = true;
return false;
}
onlineFile.close();
fis.close();
forthDic();
return true;
} catch (IOException e) {
setErrorCode(R.string.msg_err_webdic_table);
errorDict = true;
return false;
}
}
private boolean localCalc() {
RandomAccessFile fis;
try {
File dictionay = new File(dictionaryPath);
fis = new RandomAccessFile(dictionay, "r");
} catch (FileNotFoundException e2) {
setErrorCode(R.string.msg_dictnotfound);
errorDict = true;
return false;
}
int version;
try {
if (InputStreamUtils.readFromInput(table, 1282, fis)) {
setErrorCode(R.string.msg_errordict);
errorDict = true;
fis.close();
return false;
}
version = table[0] << 8 | table[1];
int totalOffset = 0;
int offset = 0;
int lastLength = 0, length = 0;
if (table[(0xFF & routerESSID[0]) * 5 + 2] == routerESSID[0]) {
int i = (0xFF & routerESSID[0]) * 5 + 2;
offset = ((0xFF & table[i + 1]) << 24)
| ((0xFF & table[i + 2]) << 16)
| ((0xFF & table[i + 3]) << 8) | (0xFF & table[i + 4]);
if ((0xFF & table[i]) != 0xFF)
lastLength = ((0xFF & table[i + 6]) << 24)
| ((0xFF & table[i + 7]) << 16)
| ((0xFF & table[i + 8]) << 8)
| (0xFF & table[i + 9]);
}
totalOffset += offset;
fis.seek(totalOffset);
if (InputStreamUtils.readFromInput(table, 1024, fis)) {
setErrorCode(R.string.msg_errordict);
errorDict = true;
fis.close();
return false;
}
if (table[(0xFF & routerESSID[1]) * 4] == routerESSID[1]) {
int i = (0xFF & routerESSID[1]) * 4;
offset = ((0xFF & table[i + 1]) << 16)
| ((0xFF & table[i + 2]) << 8) | (0xFF & table[i + 3]);
length = ((0xFF & table[i + 5]) << 16)
| ((0xFF & table[i + 6]) << 8) | (0xFF & table[i + 7]);
}
totalOffset += offset;
length -= offset;
if ((lastLength != 0) && ((0xFF & routerESSID[1]) == 0xFF)) {
/*
* Only for SSID starting with XXFF. We use the next item on the
* main table to know the length of the sector we are looking
* for.
*/
lastLength -= totalOffset;
length = lastLength;
}
fis.seek(totalOffset);
if (((0xFF & routerESSID[0]) != 0xFF)
|| ((0xFF & routerESSID[1]) != 0xFF)) {
this.entry = new byte[length];
} else { /*
* Only for SSID starting with FFFF as we don't have a
* marker of the end.
*/
length = 2000;
this.entry = new byte[length];
}
len = 0;
while (len < length) {
int bytesRead = fis.read(entry, len, length - len);
if (bytesRead == -1)
break;
len += bytesRead;
}
if (len == -1) {
setErrorCode(R.string.msg_errordict);
errorDict = true;
fis.close();
return false;
}
fis.close();
} catch (IOException e1) {
errorDict = true;
setErrorCode(R.string.msg_errordict);
return false;
}
if (version > 4) {
setErrorCode(R.string.msg_errversion);
errorDict = true;
return false;
}
if (version == 1)
firstDic();
else if (version == 2)
secondDic();
else if (version == 3)
return thirdDic();
else if (version == 4)
forthDic();
return true;
}
// This has been implemented natively for instant resolution!
private boolean thirdDic() {
String[] results;
try {
final ThomsonNative nat = new ThomsonNative();
results = nat.thirdDicNative(routerESSID, entry, entry.length);
} catch (Exception e) {
setErrorCode(R.string.msg_err_native);
return false;
} catch (LinkageError e) {
setErrorCode(R.string.err_misbuilt_apk);
return false;
}
if (isStopRequested())
return false;
for (String result : results)
addPassword(result);
return true;
}
private void forthDic() {
cp[0] = (byte) (char) 'C';
cp[1] = (byte) (char) 'P';
for (int offset = 0; offset < len; offset += 3) {
for (int i = 0; i <= 1; ++i) {
if (isStopRequested())
return;
sequenceNumber = i
+ (((0xFF & entry[offset]) << 16)
| ((0xFF & entry[offset + 1]) << 8) | (0xFF & entry[offset + 2]))
* 2;
c = sequenceNumber % 36;
b = sequenceNumber / 36 % 36;
a = sequenceNumber / (36 * 36) % 36;
year = sequenceNumber / (36 * 36 * 36 * 52) + 4;
week = (sequenceNumber / (36 * 36 * 36)) % 52 + 1;
cp[2] = (byte) Character.forDigit((year / 10), 10);
cp[3] = (byte) Character.forDigit((year % 10), 10);
cp[4] = (byte) Character.forDigit((week / 10), 10);
cp[5] = (byte) Character.forDigit((week % 10), 10);
cp[6] = charectbytes0[a];
cp[7] = charectbytes1[a];
cp[8] = charectbytes0[b];
cp[9] = charectbytes1[b];
cp[10] = charectbytes0[c];
cp[11] = charectbytes1[c];
md.reset();
md.update(cp);
final byte[] hash = md.digest();
if (hash[19] != routerESSID[2])
continue;
if (hash[18] != routerESSID[1])
continue;
if (hash[17] != routerESSID[0])
continue;
try {
addPassword(StringUtils.getHexString(hash).substring(0, 10)
.toUpperCase(Locale.getDefault()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
private void secondDic() {
cp[0] = (byte) (char) 'C';
cp[1] = (byte) (char) 'P';
for (int offset = 0; offset < len; offset += 3) {
if (isStopRequested())
return;
sequenceNumber = ((0xFF & entry[offset]) << 16)
| ((0xFF & entry[offset + 1]) << 8)
| (0xFF & entry[offset + 2]);
c = sequenceNumber % 36;
b = sequenceNumber / 36 % 36;
a = sequenceNumber / (36 * 36) % 36;
year = sequenceNumber / (36 * 36 * 36 * 52) + 4;
week = (sequenceNumber / (36 * 36 * 36)) % 52 + 1;
cp[2] = (byte) Character.forDigit((year / 10), 10);
cp[3] = (byte) Character.forDigit((year % 10), 10);
cp[4] = (byte) Character.forDigit((week / 10), 10);
cp[5] = (byte) Character.forDigit((week % 10), 10);
cp[6] = charectbytes0[a];
cp[7] = charectbytes1[a];
cp[8] = charectbytes0[b];
cp[9] = charectbytes1[b];
cp[10] = charectbytes0[c];
cp[11] = charectbytes1[c];
md.reset();
md.update(cp);
final byte[] hash = md.digest();
if (hash[19] != routerESSID[2])
continue;
if (hash[18] != routerESSID[1])
continue;
if (hash[17] != routerESSID[0])
continue;
try {
addPassword(StringUtils.getHexString(hash).substring(0, 10)
.toUpperCase(Locale.getDefault()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
private void firstDic() {
cp[0] = (byte) (char) 'C';
cp[1] = (byte) (char) 'P';
for (int offset = 0; offset < len; offset += 4) {
if (isStopRequested())
return;
if (entry[offset] != routerESSID[2])
continue;
sequenceNumber = ((0xFF & entry[offset + 1]) << 16)
| ((0xFF & entry[offset + 2]) << 8)
| (0xFF & entry[offset + 3]);
c = sequenceNumber % 36;
b = sequenceNumber / 36 % 36;
a = sequenceNumber / (36 * 36) % 36;
year = sequenceNumber / (36 * 36 * 36 * 52) + 4;
week = (sequenceNumber / (36 * 36 * 36)) % 52 + 1;
cp[2] = (byte) Character.forDigit((year / 10), 10);
cp[3] = (byte) Character.forDigit((year % 10), 10);
cp[4] = (byte) Character.forDigit((week / 10), 10);
cp[5] = (byte) Character.forDigit((week % 10), 10);
cp[6] = charectbytes0[a];
cp[7] = charectbytes1[a];
cp[8] = charectbytes0[b];
cp[9] = charectbytes1[b];
cp[10] = charectbytes0[c];
cp[11] = charectbytes1[c];
md.reset();
md.update(cp);
final byte[] hash = md.digest();
try {
addPassword(StringUtils.getHexString(hash).substring(0, 10)
.toUpperCase(Locale.getDefault()));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
public void setWebdic(InputStream webdic) {
this.webdic = webdic;
}
public boolean isErrorDict() {
return errorDict;
}
public void setErrorDict(boolean errorDict) {
this.errorDict = errorDict;
}
public void setDictionary(String dic) {
dictionaryPath = dic;
}
public void setInternetAlgorithm(boolean thomson3g) {
this.internetAlgorithm = thomson3g;
}
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(ssidIdentifier);
dest.writeInt(errorDict ? 1 : 0);
}
@Override
public int getSupportState() {
if (getMacAddress().length() < 12)
return SUPPORTED;
// It is a new generation router which the probability of working is
// very low.
if (getMacAddress().substring(6).equalsIgnoreCase(ssidIdentifier))
return UNLIKELY_SUPPORTED;
return SUPPORTED;
}
private static class ThomsonNative {
static {
System.loadLibrary("thomson");
}
private native String[] thirdDicNative(byte[] essid, byte[] entry,
int size);
}
}