Well it’s really late right now, and I’ve been working on a ton of thing, though I thought I’d release this.
This is a decompile version of Vending.odex (Vending.apk/Market.apk) for the G1. It was done using a slightly modified DeDexer by Gabor, mentioned in previous posts.
Enjoy — I’ll post more on it later!
Was playing around with dedexer, mention in this previous post, and noticed it wasn’t working well on my ubuntu dev. machine. Turns out it just didn’t play well with the default ubuntu java – so switching it made all the difference. So if your getting the following error or something like this when running:
tstrazze@strazz-workstation:~/Desktop$ java -jar ddx.jar -d dump classes.dex
Processing com/android/im/util/QueryUtils
Exception in thread “main” java.lang.NoSuchMethodError: method java.io.PrintStream.with signature (Ljava.io.File;)V was not found.
at hu.uw.pallergabor.dedexer.JasminStyleCodeGenerator.generate(JasminStyleCodeGenerator.java:29)
at hu.uw.pallergabor.dedexer.Dedexer.run(Dedexer.java:116)
at hu.uw.pallergabor.dedexer.Dedexer.main(Dedexer.java:12)
Then run the following command;
tstrazze@strazz-workstation:~/Desktop$ java -version
java version “1.5.0″
gij (GNU libgcj) version 4.2.4 (Ubuntu 4.2.4-1ubuntu3)Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
tstrazze@strazz-workstation:~/Desktop$ sudo update-java-alternatives -l
java-6-sun 63 /usr/lib/jvm/java-6-sun
java-gcj 1042 /usr/lib/jvm/java-gcj
We want to be using java-6-sun, not java-gcj so we’ll do the following;
tstrazze@strazz-workstation:~/Desktop$ sudo update-java-alternatives -s java-gcj
No alternatives for apt.
No alternatives for extcheck.
No alternatives for firefox-3.0-javaplugin.so.
No alternatives for HtmlConverter.
No alternatives for idlj.
No alternatives for javap.
No alternatives for java-rmi.cgi.
No alternatives for jconsole.
No alternatives for jdb.
No alternatives for jhat.
No alternatives for jinfo.
No alternatives for jmap.
No alternatives for jps.
No alternatives for jrunscript.
No alternatives for jsadebugd.
No alternatives for jstack.
No alternatives for jstat.
No alternatives for jstatd.
No alternatives for jvisualvm.
No alternatives for schemagen.
No alternatives for wsgen.
No alternatives for wsimport.
No alternatives for xjc.
Using ‘/usr/lib/jvm/java-gcj/bin/appletviewer’ to provide ‘appletviewer’.
Using ‘/usr/lib/jvm/java-gcj/bin/jarsigner’ to provide ‘jarsigner’.
Using ‘/usr/lib/jvm/java-gcj/bin/javac’ to provide ‘javac’.
Using ‘/usr/lib/jvm/java-gcj/bin/javadoc’ to provide ‘javadoc’.
Using ‘/usr/lib/jvm/java-gcj/bin/javah’ to provide ‘javah’.
Using ‘/usr/lib/jvm/java-gcj/bin/native2ascii’ to provide ‘native2ascii’.
Using ‘/usr/lib/jvm/java-gcj/bin/rmic’ to provide ‘rmic’.
Using ‘/usr/lib/jvm/java-gcj/bin/tnameserv’ to provide ‘tnameserv’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/jar’ to provide ‘jar’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/java’ to provide ‘java’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/keytool’ to provide ‘keytool’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/orbd’ to provide ‘orbd’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/rmid’ to provide ‘rmid’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/rmiregistry’ to provide ‘rmiregistry’.
Using ‘/usr/lib/jvm/java-gcj/jre/bin/serialver’ to provide ‘serialver’.
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
update-java-alternatives: plugin alternative does not exist: /usr/lib/gcj-4.2/libgcjwebplugin.so
Ta-da! A simple (and probably obvious for most) work around. Just figured I’d throw it up here to help anyone who might bump into the problem.
So haven’t been around for a while, due to the flu. So it gave me plenty of time to play PapiJump and PapiRiver. Though I kind of got interested in the method of high scores so I took a more indepth look at it. I’ll posted all the code to emulator a papi* score submit. I took out the little tidbit that actually sends the request, so this just prints to the screen. I’ve also removed the “secret keys” which wouldn’t be too hard to find if your really wanted to use this.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
/* * @file papihack.java * * @author Tim Strazzere * * @date Jan 20th, 2009 (finished same day) * * @desc: * An attempt to spoof the high score function of papijump, * and papiriver since they are just different secret keys * * should produce a url similar to: * * http://www.sunflat.net/android/cmd/postSc?gid=2001&v=1.0.1&lid=1&tid=4232145dfg8432145&dt=1232488839&sc=39927&ha=1007200355&tt=1&tn=T-Mobile+G1+Vi116143 * [broken down] * http://www.sunflat.net/android/cmd/postSc? * // game id * gid=2000 * // game version * &v=1.0.0 * // license? * &lid=1 * // terminal id * &tid=4232145dfg8432145 * // date / 1000 * &dt=1232171270 * * // score? * &sc=844 * // CRC'ed hash * &ha=3817043337 * * // eh? always 1 * &tt=1 * // terminal name * &tn=T-Mobile+G1+Vi116143 * */ import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.zip.CRC32; import java.util.zip.Checksum; public class papihack { public static void main(String[] args) throws UnsupportedEncodingException{ String comma = ","; //String appVersion = "1.0.0"; // version for papijump String appVersion = "1.0.1"; // version for papiriver //String secretKey = "6977616e74746f6368656174"; // secret key for papijump String secretKey = "69276d616368656174657221"; // secret key for papriver String terminalName = "T-Mobile G1 Vi116143"; // (Build.Model + " Vi" + Build.Version.Incremental) (needs to be websafe) String terminalID = "4232145dfg8432145"; // (Android_ID) //String gameID = "2000"; // papijump game id String gameID = "2001"; // papiriver game id String lid = "1"; String score = "39927"; // score you want String postUrl = "http://www.sunflat.net/android/cmd/postSc?"; Date date = new Date(); long dateLong = date.getTime()/1000; StringBuilder hash = new StringBuilder(); hash.append(gameID + comma + appVersion + comma + lid + comma + terminalID + comma + dateLong + comma + secretKey + comma + score); StringBuilder newHash = new StringBuilder(hash.toString().valueOf(hash)); newHash.append(",tt1"); Checksum checksum = new CRC32(); byte[] bytes = new byte[1024]; int len = newHash.toString().length(); Long value; bytes = newHash.toString().getBytes("UTF-8"); checksum.update(bytes, 0, len); value = checksum.getValue(); StringBuilder url = new StringBuilder(); url.append(postUrl); url.append("gid=" + gameID); url.append("v=" + appVersion); url.append("lid=" + lid); url.append("tid=" + terminalID); url.append("dt=" + dateLong); url.append("sc=" + score); url.append("ha=" + value.toString()); url.append("tt=1"); // const ? url.append("tn=" + terminalName); System.out.println(url.toString()); } } |
Ah, also – kudos to anyone who figures out what I replaced the keys with. It’s sort of a little joke…
On a little after thought, maybe it should be called papipwn? Eh – oh well, either way it will be easily bannable/removable by the administrators. It’s not hard as they are linked to your specific android id so I wouldn’t recommend using this or if you do, going over board with it.
Thanks to my friend Gabor, over at http://mylifewithandroid.blogspot.com/ has created a really well done dex file dissembler. The direct link for the post is here and the source code is all free and located at dedexer.sourceforge.net.
It’s nice as it outputs the format in jasmin like the following;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.method public calc1(I)I packed-switch v2,0 ps418_422 ; case 0 ps418_426 ; case 1 ps418_42a ; case 2 default: ps418_default ps418_default: const/4 v0,15 l420: return v0 ps418_422: const/4 v0,2 goto l420 ps418_426: const/4 v0,5 goto l420 ps418_42a: const/4 v0,6 goto l420 nop .end method |
Opposed to the normal;
|
1 2 3 4 5 6 7 8 9 10 11 |
000418: 2b02 0c00 0000 |0000: packed-switch v2, 0000000c // +0000000c 00041e: 12f0 |0003: const/4 v0, #int -1 // #ff 000420: 0f00 |0004: return v0 000422: 1220 |0005: const/4 v0, #int 2 // #2 000424: 28fe |0006: goto 0004 // -0002 000426: 1250 |0007: const/4 v0, #int 5 // #5 000428: 28fc |0008: goto 0004 // -0004 00042a: 1260 |0009: const/4 v0, #int 6 // #6 00042c: 28fa |000a: goto 0004 // -0006 00042e: 0000 |000b: nop // spacer 000430: 0001 0300 faff ffff 0500 0000 0700 ... |000c: packed-switch-data (10 units) |
Great work Gabor, and keep up the good work!
I was looking around at some applications on the Android Market today and was trying to look at how some of the applications well, did what they did. I was also interested in looking at how they did they’re own licensing schemes, anyways. Hopefully all the developers know this – but maybe some of this, surprisingly, don’t know this. A .apk file is merely a .jar file – meaning it is a [b]zipped[/b] up. This means it can [b]easily be unzipped[/b]. So before releasing things, you should [i]really[/i] double check your .apk file and make sure it contrains [i]only[/i] what you want. This would normally include a /res folder, /META folder and you main directory that has AndroidManifest.xml, Classes.dex and resources.arsc.
You should definitely check if it contains extra files like “LicenseActivity.java.bak”;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
@Override protected void onResume() { super.onResume(); checkClipboard(); } /** * */ private void checkClipboard() { // If someone copied a license, paste it now: ClipboardManager clippy = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); if (clippy.hasText()) { String clip = clippy.getText().toString(); clip = clip.trim(); Log.i(TAG, "Clipboard: " + clip); if (clip.length() == 19 && clip.substring(4,5).equals("-") && clip.substring(9,10).equals("-") && clip.substring(14,15).equals("-")) { // This is a license: setLicenseText(clip); Toast.makeText(this, getString(RD.string.license_from_clipboard), Toast.LENGTH_LONG).show(); } } } /** * @param licensekey */ private void setLicenseText(String licensekey) { if (licensekey != null && licensekey.length() == 19) { mLic1.setText(licensekey.substring(0, 4)); mLic2.setText(licensekey.substring(5, 9)); mLic3.setText(licensekey.substring(10, 14)); mLic4.setText(licensekey.substring(15, 19)); } } protected void requestLicense(String targetUri) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(targetUri +"?ctmcode=" + mCtmCode + "&appcode=" + mAppCode + "liccode=" + mLicCode)); startActivity(intent); } protected void storeLicenseAndFinish() { Editor editor = PreferenceManager.getDefaultSharedPreferences(this) .edit(); StringBuilder sb = new StringBuilder(); sb.append(mLic1.getText()); sb.append('-'); sb.append(mLic2.getText()); sb.append('-'); sb.append(mLic3.getText()); sb.append('-'); sb.append(mLic4.getText()); if (LicenseChecker.checkLicense(this, sb.toString())) { editor.putString(LicenseChecker.PREFS_LICENSE_KEY, sb.toString()); editor.commit(); ((LicensedApplication) getApplication()).newLicense(); finish(); } else { Toast.makeText(this, getString(RD.string.invalid_license), Toast.LENGTH_LONG).show(); } } private class TextChangedWatcher implements TextWatcher { TextView mPrevTextView; TextView mNextTextView; public TextChangedWatcher(TextView prevTextView, TextView nextTextView) { mPrevTextView = prevTextView; mNextTextView = nextTextView; } public void afterTextChanged(Editable s) { // Jump focus to next when field is full if (s.toString().length() == 4 && mNextTextView != null) { mNextTextView.requestFocus(); } // Jump focus to previous when last character has been deleted if (s.toString().length() == 0 && mPrevTextView != null) { mPrevTextView.requestFocus(); } } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_ID_MARKET_WARNING: return new AlertDialog.Builder(LicenseActivity.this).setIcon( android.R.drawable.ic_dialog_alert).setTitle(RD.string.alert).setMessage( RD.string.dialog_market_warning).setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { requestLicense(LICENSE_MARKET_URL); finish(); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).create(); //TODO: fix me. what should be in those strings? case DIALOG_ID_HANDANGO_WARNING: return new AlertDialog.Builder(LicenseActivity.this).setIcon( android.R.drawable.ic_dialog_alert).setTitle(RD.string.alert).setMessage( RD.string.dialog_market_warning).setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { requestLicense(LICENSE_HANDANGO_URL); finish(); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).create(); } return null; } } |
Or more files… Which might essentially give your “paid” application out for free, and it’s source code essentially open source. While it’s a “nice” thing that you’ve essentially open sourced your application. I’m sure it wasn’t your intent to do so, and distribute it on the Android Market and your website.
Hopefully no one else other than the developer (who has been contacted) has to learn this lesson… Shesh! There was also a few other (and large fully working) .java.bak files that where include, above is just a “licensing snippet” I was looking over. Though I’ve only pasted a small portion that doesn’t include any indication on exactly which application it is.
So I’m not sure why I didn’t think to try to get live market data? For some reason I just *figured* it would be done with SSL or something so I just didn’t try it. Long story short, after building a market-cache parser – I realized, DOH! You can just get the market data live using certain requests! Luckily the parser I made worked fine with the data that I was getting sent back.
It took a little reversing of Vending.apk to see exactly what type of encoding was being used – I sort of guessed right just by looking at what the phone was sending via a Cain&Abel and Wireshark dump. Though google just happened to have some data in there that would through errors if you tried decoding the data directly or certain ways.
I’ll post come up some of the reversing I did on Vending.apk – more specifically the .buildPostParameters routine which is where everything was created for post. It’s actually pretty interesting stuff, and it helped find some routines online that google has publicly available through apache… Though I didn’t find them in the Android libraries
(nice of them, no? haha)
Anyway, tournament tomorrow followed by a snowboarding trip tomorrow – so I’ll probably post that data on monday!




Recent Comments