· 5 years ago · Nov 12, 2019, 07:10 PM
1package com;
2
3import java.applet.Applet;
4import java.applet.AppletStub;
5import java.awt.Dimension;
6import java.io.BufferedInputStream;
7import java.io.BufferedOutputStream;
8import java.io.BufferedReader;
9import java.io.ByteArrayInputStream;
10import java.io.ByteArrayOutputStream;
11import java.io.Closeable;
12import java.io.File;
13import java.io.FileOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.io.InputStreamReader;
17import java.io.OutputStream;
18import java.lang.reflect.Method;
19import java.net.HttpURLConnection;
20import java.net.MalformedURLException;
21import java.net.URL;
22import java.net.URLClassLoader;
23import java.nio.ByteBuffer;
24import java.nio.file.Path;
25import java.security.GeneralSecurityException;
26import java.security.InvalidAlgorithmParameterException;
27import java.security.InvalidKeyException;
28import java.security.NoSuchAlgorithmException;
29import java.util.ArrayList;
30import java.util.Base64;
31import java.util.HashMap;
32import java.util.Map;
33import java.util.Map.Entry;
34import java.util.Properties;
35import java.util.jar.JarEntry;
36import java.util.jar.JarFile;
37import java.util.jar.JarInputStream;
38import java.util.jar.JarOutputStream;
39import java.util.jar.Pack200;
40import java.util.zip.GZIPInputStream;
41import java.util.zip.ZipEntry;
42
43import javax.crypto.BadPaddingException;
44import javax.crypto.Cipher;
45import javax.crypto.IllegalBlockSizeException;
46import javax.crypto.NoSuchPaddingException;
47import javax.crypto.spec.IvParameterSpec;
48import javax.crypto.spec.SecretKeySpec;
49import javax.swing.JFrame;
50
51/**
52 * An Applet used for loading the RS3 Client.
53 *
54 * This class includes options to connect to rs/rsps, and also dump packets from
55 * rs This class also, grabs the latest gamepack off the rs website, decrypts
56 * it, and add the it to the classpath. There is a new packet {@since 826~} that
57 * checks the field/method/class count of the deob. adding the decryted gamepack
58 * bypasses this packet.
59 *
60 * @author Kyle Friz
61 * @author Allen Kinzalow
62 *
63 * @author Major - Classes from Lynx
64 */
65public class RS3Applet extends Applet implements AppletStub {
66
67 /**
68 * An generated serial UID.
69 */
70 private static final long serialVersionUID = 1670498001014004354L;
71
72 /**
73 * load url
74 */
75 private static final String LOAD_URL = "http://world5.runescape.com/,j0";
76
77 /**
78 * library path
79 */
80 private static final String LIB_DIR = "./library/";
81
82 /**
83 * Connecting to Rs or a Rsps
84 */
85 public static boolean RSPS = false;
86
87 /**
88 * Chooses ip based on if Rs or Rsps
89 */
90 public static String Host = RSPS ? "127.0.0.1" : "world35.runescape.com";
91
92 /**
93 * Whether or not to dump info such as vars
94 */
95 public static boolean DUMP = false;
96
97 /**
98 * The parameters of the client.
99 */
100 private Properties map = new Properties();
101
102 /**
103 * The current frame of the client application.
104 */
105 public JFrame clientFrame = null;
106
107 /**
108 * The main entry point of the current application.
109 *
110 * @param args
111 * The command line arguments.
112 * @throws IOException
113 * @throws MalformedURLException
114 */
115 public static void main(String... args) throws MalformedURLException, IOException {
116 RS3Applet rs3applet = new RS3Applet();
117 rs3applet.prepareDeobForRun();
118 rs3applet.openFrame();
119 rs3applet.startClient();
120 rs3applet.clientFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
121 }
122
123 /**
124 * Starts the actual client.
125 */
126 private void startClient() {
127 try {
128 Class<?> client_class = Class.forName("com.jagex.client");
129
130 Object v_client = client_class.getConstructor().newInstance();
131 client_class.getSuperclass().getMethod("supplyApplet", Applet.class).invoke(v_client, this);
132 client_class.getMethod("init").invoke(v_client);
133 client_class.getMethod("start").invoke(v_client);
134 } catch (Exception e) {
135 e.printStackTrace();
136 }
137 }
138
139 /**
140 * Opens the actual frame application.
141 */
142 private void openFrame() {
143 clientFrame = new JFrame("RS3 Build: 910");
144 clientFrame.add(this);
145 clientFrame.setVisible(true);
146 clientFrame.setSize(1000, 800);
147 }
148
149 /**
150 * Reads the parameters text file, and stores the parameters.
151 *
152 * @throws IOException
153 * @throws MalformedURLException
154 */
155 private void prepareDeobForRun() throws MalformedURLException, IOException {
156 if (!RSPS) {
157 String packUrl = LOAD_URL;// getPackUrl();
158 if (packUrl == "")
159 throw new RuntimeException("Invalid Pack URL");
160 Host = packUrl.substring(packUrl.indexOf("http://") + "http://".length(), packUrl.indexOf("/,"));
161 String gamePackUrl = "";
162 for (String line : fetchPageDetails(packUrl)) {
163 if (line.contains("<param name=")) {
164 String key = line.split("<param name=\"")[1].split("\" ")[0];
165 String value = line.split("value=\"")[1].split("\">'")[0];
166 value = value.replaceAll("\">", "");
167 if (value.isEmpty())
168 value = "";
169 System.out.println("[" + key + ", " + value + "]");
170 map.put(key, value);
171 }
172 if (line.contains("archive=")) {
173 System.out.println(line);
174 gamePackUrl = line.substring(line.indexOf("archive=") + "archive=".length())
175 .replaceAll("\'\\);", "").trim();
176 gamePackUrl = gamePackUrl.substring(0, gamePackUrl.indexOf(".jar") + ".jar".length());
177 gamePackUrl = "http://" + Host + "/" + gamePackUrl;
178 }
179 }
180 if (gamePackUrl == "")
181 throw new RuntimeException("Invalid GamePack URL");
182 downloadAndApplyGamePack(gamePackUrl);
183 System.out.println("Connecting to: " + Host);
184
185 } else {
186 // Put params here
187 }
188 }
189
190 /**
191 * (non-Javadoc)
192 *
193 * @see java.applet.AppletStub#appletResize(int, int)
194 */
195 @Override
196 public void appletResize(int dimensionX, int dimensionY) {
197 super.resize(new Dimension(dimensionX, dimensionY));
198 }
199
200 /**
201 * (non-Javadoc)
202 *
203 * @see java.applet.Applet#getParameter(java.lang.String)
204 */
205 @Override
206 public String getParameter(String paramName) {
207 return (String) map.get(paramName);
208 }
209
210 /**
211 * (non-Javadoc)
212 *
213 * @see java.applet.Applet#getDocumentBase()
214 */
215 @Override
216 public URL getDocumentBase() {
217 try {
218 return new URL("http://" + Host);
219 } catch (MalformedURLException e) {
220 e.printStackTrace();
221 }
222 return null;
223 }
224
225 /**
226 * (non-Javadoc)
227 *
228 * @see java.applet.Applet#getCodeBase()
229 */
230 @Override
231 public URL getCodeBase() {
232 try {
233 return new URL("http://" + Host);
234 } catch (MalformedURLException e) {
235 e.printStackTrace();
236 }
237 return null;
238 }
239
240 /**
241 * Fetches the current parameters from the specified address
242 *
243 * @return
244 * @throws MalformedURLException
245 * @throws IOException
246 */
247 public ArrayList<String> fetchPageDetails(String packUrl) throws MalformedURLException, IOException {
248 ArrayList<String> pageSource = new ArrayList<String>();
249 URL urlToLoad = new URL(packUrl);
250 HttpURLConnection urlConn = (HttpURLConnection) urlToLoad.openConnection();
251 urlConn.setRequestMethod("GET");
252 urlConn.addRequestProperty("Accept",
253 "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
254 urlConn.addRequestProperty("Cookie", "JXFLOWCONTROL=5199577627689498308; global__user-visited=true; settings=wwGlrZHF5gKjTPk4r1FNn2hXlErby-Vo6x9cJwcL1wM; JXTRACKING=0117ED781A0000016780B72C28; JXWEBUID=04DCF14FFB56780A416AAFD8B462BA1F291D88F4E31052F74BCC92F0977D7BE9E02F93F5B2C8F911CB5664C8EE0FDB07; JXADDINFO=DBXPZaBPotHnzeZldoHBTxXnbnMFe3VcAAAAAAA");
255 urlConn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36");
256
257 BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
258 String line;
259 while ((line = reader.readLine()) != null)
260 pageSource.add(line);
261 reader.close();
262 return pageSource;
263 }
264
265 public String getPackUrl() {
266 String url = "";
267 try {
268 URL urlToLoad = new URL(LOAD_URL);
269 HttpURLConnection urlConn = (HttpURLConnection) urlToLoad.openConnection();
270 urlConn.setRequestMethod("GET");
271 urlConn.addRequestProperty("Accept",
272 "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
273 urlConn.addRequestProperty("Cookie", "JXFLOWCONTROL=5199577627689498308; global__user-visited=true; settings=wwGlrZHF5gKjTPk4r1FNn2hXlErby-Vo6x9cJwcL1wM; JXTRACKING=0117ED781A0000016780B72C28; JXWEBUID=04DCF14FFB56780A416AAFD8B462BA1F291D88F4E31052F74BCC92F0977D7BE9E02F93F5B2C8F911CB5664C8EE0FDB07; JXADDINFO=DBXPZaBPotHnzeZldoHBTxXnbnMFe3VcAAAAAAA");
274 urlConn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36");
275 BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
276 String line;
277 while ((line = reader.readLine()) != null)
278 if (line.startsWith("<iframe id=\"game\"")) {
279 System.out.println(line);
280 url = line.substring(line.indexOf("src=\"") + "src=\"".length(), line.indexOf("\" frameborder="));
281 System.out.println(url);
282 }
283 reader.close();
284 } catch (Exception e) {
285 e.printStackTrace();
286 }
287 return url;
288 }
289
290 public void downloadAndApplyGamePack(String packUrl) {
291 String packName = packUrl.substring(packUrl.indexOf("gamepack")).replace('*', '_');
292 try {
293 System.out.println("Downloading GamePack: " + packUrl);
294 URL url = new URL(packUrl);
295 InputStream is = new BufferedInputStream(url.openStream());
296 OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(LIB_DIR + packName)));
297
298 int read;
299 while ((read = is.read()) != -1) {
300 os.write(read);
301 }
302 os.close();
303 // addPath(LIB_DIR + (packUrl.substring(packUrl.indexOf("gamepack"))));
304 Map<String, ByteBuffer> classes = new HashMap<String, ByteBuffer>();
305
306 // Decrypt and decompress the inner pack.
307 try {
308 InnerPackDecrypter decrypter = new InnerPackDecrypter(LIB_DIR + packName, (String) map.get("0"),
309 (String) map.get("-1"));
310 classes = decrypter.decrypt();
311 decrypter.close();
312 } catch (GeneralSecurityException e) {
313 System.err.println("Error decrypting the inner archive.");
314 e.printStackTrace();
315 System.exit(0);
316 }
317 if (classes.size() > 0) {
318 writeJar(classes, LIB_DIR + "gamepack.jar");
319 addPath(LIB_DIR + "gamepack.jar");
320 } else {
321 System.err.println("No classes found in archive...");
322 System.exit(0);
323 }
324 } catch (Exception e) {
325 System.err.println("Error retrieving gamepack(" + packName + ") ... try again.");
326 e.printStackTrace();
327 System.exit(0);
328 }
329 }
330
331 private void writeJar(Map<String, ByteBuffer> classes, String loc) throws IOException {
332
333 try (JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(new File(loc))))) {
334 for (Entry<String, ByteBuffer> clazz : classes.entrySet()) {
335 ZipEntry zip = new ZipEntry(clazz.getKey());
336
337 jos.putNextEntry(zip);
338 jos.write(clazz.getValue().array());
339 }
340 } catch (IOException e) {
341 System.err.println("Error writing classes to jar - please ensure this program has write permissions.");
342 throw new IOException(e);
343 }
344 }
345
346 public void addPath(String s) throws Exception {
347 File f = new File(s);
348 URL u = f.toURI().toURL();
349 URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
350 Class<?> urlClass = URLClassLoader.class;
351 Method method = urlClass.getDeclaredMethod("addURL", new Class[] { URL.class });
352 method.setAccessible(true);
353 method.invoke(urlClassLoader, new Object[] { u });
354 System.out.println("Gamepack added to classpath!");
355
356 ClassLoader cl = ClassLoader.getSystemClassLoader();
357
358 URL[] urls = ((URLClassLoader) cl).getURLs();
359
360 for (URL url : urls) {
361 System.out.println(url.getFile());
362 }
363 }
364
365 public final class InnerPackDecrypter implements Closeable {
366
367 /**
368 * The key returned if an empty (i.e. {@code length == 0} string is
369 * decrypted.
370 */
371 private final byte[] EMPTY_KEY = new byte[0];
372
373 /**
374 * The encoded secret key for the AES block cipher.
375 */
376 private final String encodedSecret;
377
378 /**
379 * The encoded initialisation vector for the AES block cipher.
380 */
381 private final String encodedVector;
382
383 /**
384 * The input stream to the {@code inner.pack.gz} file.
385 */
386 private final InputStream input;
387
388 /**
389 * The gamepack jar file.
390 */
391 private final JarFile jar;
392
393 /**
394 * Creates the inner pack decrypter.
395 *
396 * @param gamepack
397 * The {@link Path} to the gamepack jar.
398 * @param secret
399 * The encoded secret key.
400 * @param vector
401 * The encoded initialisation vector.
402 * @throws IOException
403 * If the path to the gamepack is invalid.
404 */
405 public InnerPackDecrypter(String gamePack, String secret, String vector) throws IOException {
406 this.encodedSecret = secret;
407 this.encodedVector = vector;
408 this.jar = new JarFile(new File(gamePack));
409
410 ZipEntry archive = jar.getEntry("inner.pack.gz");
411 this.input = new BufferedInputStream(jar.getInputStream(archive));
412 }
413
414 @Override
415 public void close() throws IOException {
416 input.close();
417 jar.close();
418 }
419
420 /**
421 * Decrypts the {@code inner.pack.gz} archive using the AES cipher. The
422 * decrypted data is then un-gzipped and unpacked from the pack200
423 * format, before finally being split into a {@link ByteBuffer} per
424 * class. The data is then returned as a {@link Map} of class names to
425 * byte buffers.
426 *
427 * @return The map of class names to the byte buffers containing their
428 * data.
429 * @throws NoSuchAlgorithmException
430 * If the current system does not have an AES
431 * implementation.
432 * @throws NoSuchPaddingException
433 * If the current system does not support the specified
434 * padding scheme.
435 * @throws InvalidKeyException
436 * If the secret key is invalid.
437 * @throws InvalidAlgorithmParameterException
438 * If the initialisation vector is invalid.
439 * @throws IOException
440 * If there is an error reading from or writing to any of
441 * the various streams used.
442 * @throws IllegalBlockSizeException
443 * If AES is unable to process the input data provided.
444 * @throws BadPaddingException
445 * If the data lacks the appropriate padding bytes.
446 */
447 public Map<String, ByteBuffer> decrypt()
448 throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
449 InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, BadPaddingException {
450 int secretKeySize = getKeySize(encodedSecret.length());
451 int vectorSize = getKeySize(encodedVector.length());
452
453 byte[] secretKey = secretKeySize == 0 ? EMPTY_KEY : decodeBase64(encodedSecret, secretKeySize);
454 byte[] initialisationVector = vectorSize == 0 ? EMPTY_KEY : decodeBase64(encodedVector, vectorSize);
455
456 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
457 SecretKeySpec secret = new SecretKeySpec(secretKey, "AES");
458 IvParameterSpec vector = new IvParameterSpec(initialisationVector);
459
460 // Initialise the cipher.
461 cipher.init(Cipher.DECRYPT_MODE, secret, vector);
462
463 byte[] buffer = new byte[5 * 1024 * 1024];
464 int read = 0, in = 0;
465
466 while (read < buffer.length && (in = input.read(buffer, read, buffer.length - read)) != -1) {
467 read += in;
468 }
469
470 System.out.println("Decrypting the archive.");
471
472 // Decrypt the inner.pack.gz file.
473 byte[] decrypted = cipher.doFinal(buffer, 0, read);
474 ByteArrayOutputStream bos = new ByteArrayOutputStream(5 * 1024 * 1024);
475
476 // Un-gzip and unpack the jar file contained in the archive, and
477 // write the decompressed data out.
478 try (JarOutputStream jos = new JarOutputStream(bos);
479 GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(decrypted))) {
480 Pack200.newUnpacker().unpack(gzip, jos);
481 }
482
483 Map<String, ByteBuffer> classes = new HashMap<>();
484
485 // Iterate through the jar entries from the stream, read and wrap
486 // them, and add them to the map.
487 try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
488 for (JarEntry entry = jis.getNextJarEntry(); entry != null; entry = jis.getNextJarEntry()) {
489 String name = entry.getName();
490 if (!name.endsWith(".class")) {
491 System.out.println(name);
492 continue;
493 }
494
495 read = in = 0;
496 while (read < buffer.length && (in = jis.read(buffer, read, buffer.length - read)) != -1) {
497 read += in;
498 }
499
500 ByteBuffer data = ByteBuffer.allocate(read);
501 data.put(buffer, 0, read).flip();
502 classes.put(name, data);
503 }
504 }
505
506 return classes;
507 }
508
509 /**
510 * Decodes the base64 string into a valid secret key or initialisation
511 * vector.
512 *
513 * @param string
514 * The string.
515 * @param size
516 * The size of the key, in bytes.
517 * @return The key, as a byte array.
518 */
519 private byte[] decodeBase64(String string, int size) {
520 // JaGex's implementation uses * and - instead of + and /, so replace them.
521 String valid = string.replaceAll("\\*", "\\+").replaceAll("-", "/");
522 return Base64.getDecoder().decode(valid);
523 }
524
525 /**
526 * Gets the key size for a string of the specified length.
527 *
528 * @param length
529 * The length of the string.
530 * @return The key size.
531 */
532 private int getKeySize(int length) {
533 if (length == 0) {
534 return 0;
535 }
536
537 return 3 * (int) Math.floor((length - 1) / 4) + 1;
538 }
539
540 }
541
542}