Tag-Archive for » code «

Monday, January 05th, 2009 | Author: Tim

Your Ad Here

Keep your code secure!

Keep your code secure!


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.

Source code is usually NOT given to users, right?

Source code is usually NOT given to users, right?

You should definitely check if it contains extra files like “LicenseActivity.java.bak”;

	@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.

Wednesday, November 26th, 2008 | Author: Tim

Previously I wrote about how to uniquely identify Android devices without special permissions. However, maybe you want to get into the nitty-gritty and get an even more unique identifier for the device. This can be done, but you need special permissions. Essentially what I mean by “special permissions” is that the user will be prompted when installing that “this application tries to do this”. “This” referring to (in this specific case) Reading Phone State. This doesn’t mean it’s doing anything bad, however users might be turned off if your calculator wants to read the phones state etc. This is just how Google has set up the installation of applications, so that a user is properly notified of what an application is given permission to do.

What are the kind of identifiers we can get with this special permission? We can grade the “Device ID” which is the IMEI number, the phone number, Software Version (not sure if it’s currently being used?), Sim Serial Number and Subscriber ID.

That should be enough unique identifiers for anyone to come up with some hash! Heck, phone number alone should be enough since it would be readily known by a customer and easy to use.

To get these values add the following somewhere in your program;

import android.telephony.*;

...

		TelephonyManager mTelephonyMgr =
        	(TelephonyManager)getSystemService(TELEPHONY_SERVICE); 

		String imei = mTelephonyMgr.getDeviceId(); // Requires READ_PHONE_STATE
        String phoneNumber=mTelephonyMgr.getLine1Number(); // Requires READ_PHONE_STATE
        String softwareVer = mTelephonyMgr.getDeviceSoftwareVersion(); // Requires READ_PHONE_STATE
        String simSerial = mTelephonyMgr.getSimSerialNumber(); // Requires READ_PHONE_STATE
        String subscriberId = mTelephonyMgr.getSubscriberId(); // Requires READ_PHONE_STATE

Note that you MUST add permission access to android.permission.READ_PHONE_STATE otherwise your program will force close. This is added in the AndroidManifest.xml like the following;

<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>

On the emulator it will output things like the following;

DeviceId(IMEI) = 000000000000000
DeviceSoftwareVersion = null
Line1Number = 15555218135
SimSerialNumber = 89014103211118510720
SubscriberId(IMSI) = 310995000000000

One should also note that a real device currently returns “00″ for Device Software Version, so it’s possible that this is something reserved for future use. These could all easily be used in some type of registration algorithm that you want to tie to a certain device. Also if you choose your identifiers properly you could allow your registration code to be complaint across all versions of your product. Using a phone number for example could allow a user to use your application on any phone they put their SIM card into. If you want to prevent this you could tie it to both the device ID and the phone number.

Saturday, November 22nd, 2008 | Author: Tim

I’ve been working on the header a little more - so I figured I’d post some code I just finished throwing together quickly. It’s not all the code, since most of it is experimental and I’m not finished doing it, but this will provide people with the information on how to dump the dex file header information.

/* File: DexNfo.java
 *
 * Coded: Timothy Strazzere
 * Date: 11/22/08
 *
 * Dump header information from a dex file, only supports '035' dex files, though will
 * attempt to dump rest of the information, but will just warn you otherwise.
 *
 * Some code has been removed as it isn't sure if it full works properly yet.
 *
 */

import java.security.*;
import java.util.zip.Adler32;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/* To do...
 *
 * lots...
 *
 */
public class DexNfo{

	public static void main(String[] args) {
        if (args.length == 1) {
        	try {
        		File file = new File(args[0]);

        		byte[] barr, newbytes = null;
        		newbytes = barr = getBytesFromFile(file);

        		// add switch for this?
        		System.out.printf("Original information: " + args[0]);

        		int magic = 0;

        		for(int i = 0; i<8; i++)
        			magic+=barr[i];

        		if(magic!=483) // technically anything higher should be a new dex file... 'dex 036' etc..
        			System.out.printf("\n** Warning: Magic; bad dex file or unsupported version loaded!");

        		// Don't output char 4 since it's a newline char
        		System.out.printf("\nMagic: %c%c%c %c%c%c", barr[0], barr[1], barr[2], /*barr[3],*/ barr[4], barr[5], barr[6], barr[7]);

        		System.out.print("\nChecksum: ");
        		for(int i = 8; i<12; i+=4)
        			System.out.printf("0x%02X%02X%02X%02X ", barr[i+3], barr[i+2], barr[i+1], barr[i]);

        		System.out.print("\nSignature: 0x");
        		for(int i = 12; i<32; i+=4)
        			System.out.printf("%02X%02X%02X%02X ", barr[i], barr[i+1], barr[i+2], barr[i+3]);

        		System.out.printf("\nLength: 0x%02X%02X", barr[33], barr[32]);

        		if(barr[36]!= 112) // currently is always 0x70==(int)112...
        			System.out.printf("\n** Warning: Header Length; bad dex file or unsupported version loaded!");
        		System.out.printf("\nHeader Length: 0x%02X", barr[36]);

        		// endian tag
        		int endian = 0;

        		for(int i = 0; i < 4; i++)
        			endian += barr[40+i];

        		if(endian != 276) // Currently always should be 0x78563412, which when added = int 114
        			System.out.printf("\n** Warning: Endian Tag; bad dex file or unsupported version loaded!");
        		System.out.printf("\nEndian Tag: 0x%02X%02X%02X%02X", barr[40],
        				barr[41], barr[42], barr[43]);

        		// map offset
        		System.out.printf("\nMap Offset: 0x%02X%02X", barr[53], barr[52]);

        		// string table size
        		System.out.printf("\nString Table Size: 0x%02X%02X", barr[57], barr[56]);        		

        		// string table offset
        		System.out.printf("\nString Table Offset: 0x%02X%02X", barr[61], barr[60]);

        		// type table size
        		System.out.printf("\nType Table Size: 0x%02X%02X", barr[65], barr[64]);        		

        		// type table offset
        		System.out.printf("\nType Table Offset: 0x%02X%02X", barr[69], barr[68]);

        		// Prototype table size
        		System.out.printf("\nPrototype Table Size: 0x%02X%02X", barr[73], barr[72]);        		

        		// Prototype table offset
        		System.out.printf("\nPrototype Table Offset: 0x%02X%02X", barr[77], barr[76]);  

        		// Field table size
        		System.out.printf("\nField Table Size: 0x%02X%02X", barr[81], barr[80]);        		

        		// Field table offset
        		System.out.printf("\nField Table Offset: 0x%02X%02X", barr[85], barr[84]);  

        		// Method table size
        		System.out.printf("\nMethod Table Size: 0x%02X%02X", barr[89], barr[88]);        		

        		// Method table offset
        		System.out.printf("\nMethod Table Offset: 0x%02X%02X", barr[93], barr[92]);  

        		// Class table size
        		System.out.printf("\nClass Table Size: 0x%02X%02X", barr[97], barr[96]);        		

        		// Class table offset
        		System.out.printf("\nClass Table Offset: 0x%02X%02X", barr[101], barr[100]);  

        		System.out.println();

        		// add switch for this too?

        		calcSignature(newbytes);
        		calcChecksum(newbytes);
        		System.out.print("\n\nNew Checksum: ");
        		for(int i = 8; i<12; i+=4)
        			System.out.printf("0x%02X%02X%02X%02X ", newbytes[i+3], newbytes[i+2], newbytes[i+1], newbytes[i]);
        		System.out.print("\nNew Signature: 0x");
        		for(int i = 12; i<32; i+=4)
        			System.out.printf("%02X%02X%02X%02X ", newbytes[i], newbytes[i+1], newbytes[i+2], newbytes[i+3]);

        		System.out.printf("\nLength: %04X", calcSize(newbytes));

        		// output compares to the two, highlight differences...
        	}
            catch (Exception e) {
            	System.err.println("File input error");
            }
        }
        else
        	System.out.println("Invalid parameters");
	}

    public static byte[] getBytesFromFile(File file) throws IOException {
        InputStream is = new FileInputStream(file);

        // Get the size of the file
        long length = file.length();

        if (length > Integer.MAX_VALUE) {
            // File is too large
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int)length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        return bytes;
    }
    private static void calcSignature(byte bytes[])
    {
        MessageDigest md;
        try
        {
            md = MessageDigest.getInstance("SHA-1");
        }
        catch(NoSuchAlgorithmException ex)
        {
            throw new RuntimeException(ex);
        }
        md.update(bytes, 32, bytes.length - 32);
        try
        {
            int amt = md.digest(bytes, 12, 20);
            if(amt != 20)
                throw new RuntimeException((new StringBuilder()).append("unexpected digest write: ").append(amt).append(" bytes").toString());
        }
        catch(DigestException ex)
        {
            throw new RuntimeException(ex);
        }
    }

    private static void calcChecksum(byte bytes[])
    {
        Adler32 a32 = new Adler32();
        a32.update(bytes, 12, bytes.length - 12);
        int sum = (int)a32.getValue();
        bytes[8] = (byte)sum;
        bytes[9] = (byte)(sum >> 8);
        bytes[10] = (byte)(sum >> 16);
        bytes[11] = (byte)(sum >> 24);
    }

    public static int calcSize(byte bytes[])
    {
    	return(bytes.length);
    }
}

This now dumps all the header information from the original file, and will recalculate the signature and checksum in case something has changed. A version should be available shortly to check for differences in all the values, hopefully soon being able to calculate the correct values if something is wrong.

Maybe this will be useful for someone? Otherwise, oh well it’s just here in case I delete my files. Working on functions to find the new values after patching and to allow patching/injection of code. I’ll have to write up more later as I don’t have an overwhelming amount of time right now, busy day and I’m exhausted. Saw Sara play some volleyball, finished up solo campaign in COD5, spent a few hours reading and researching some dex related things and trying to get some more injection to work. Tomorrow I probably won’t have time to post - but trust me, this stuff will be up sooner or later. It’s a big puzzle I’m chipping away at, and it’s bugging the heck out of me not having the answers.