Tag-Archive for » dex bytecode «

Friday, April 03rd, 2009 | Author: Tim

Pew pew! Take that green circles!

Pew pew! Take that green circles!


Your Ad Here

A recent addition to the android market has been ATD, Android Turret Defense. This is a Plox-like game, though it has the “maze” strategy element combined in it. Strangely — it reminds me of a few old maps I used to play with friend for starcraft… Anyway I finally got around to beating it which isn’t too difficult once you get the hang of placing turrets and a get a decent strategy. At the end it awards you with a “badge code” — not sure exactly what the author intends to use this for, but I decided to take a look at how these are created. I was interested in how they where generated, and to see if people could easily replicate them, or if there would be any deterrents to keep people from just sharing them. Again, this is possibly completely useless information, since we have no idea what these codes will be used for. The could be used for tournaments, downloads, prizes - or maybe to just “give” you an image of a badge… As of right now we just don’t know.

Below is a dump of the function we will be analyzing with my comments in it (highlighted green), they should be pretty easy to follow:

.method private createBadgeCode()Ljava/lang/String;
// Date now = New Date();
new-instance v2,java/util/Date
invoke-direct {v2},java/util/Date/ ; ()V

// SimpleDateFormat dateFormat = new SimpleDateFormat(”yyMMddhhmm”);
new-instance v5,java/text/SimpleDateFormat
const-string v7,”yyMMddhhmm”
invoke-direct {v5,v7},java/text/SimpleDateFormat/ ; (Ljava/lang/String;)V

// StringBuilder raw = new StringBuilder();
new-instance v7,java/lang/StringBuilder
invoke-direct {v7},java/lang/StringBuilder/ ; ()V

// raw.append(dateFormat.format(now));
invoke-virtual {v5,v2},java/text/SimpleDateFormat/format ; format(Ljava/util/Date;)Ljava/lang/String;
move-result-object v8
invoke-virtual {v7,v8},java/lang/StringBuilder/append ; append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v7

// raw.append(difficulty);
iget v8,v12,tx/games/atd_world.difficulty I
invoke-virtual {v7,v8},java/lang/StringBuilder/append ; append(I)Ljava/lang/StringBuilder;
move-result-object v7

// raw.append(”tensaix2j”);
const-string v8,”tensaix2j”
invoke-virtual {v7,v8},java/lang/StringBuilder/append ; append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v7

// Bytes[] rawbytes = raw.toString.getBytes;
invoke-virtual {v7},java/lang/StringBuilder/toString ; toString()Ljava/lang/String;
move-result-object v4
invoke-virtual {v4},java/lang/String/getBytes ; getBytes()[B
move-result-object v0

/* Below code refined;
int sum = 0;

for(int i = 0; i < rawbytes.length(); i++)
sum += rawbytes[i];
*/

const/4 v6,0
const/4 v3,0
l3c1e:
// length = rawbytes.length();
array-length v7

// if( v3 > v7 ) goto: l3c30
if-ge v3,v7,l3c30

// v7 = rawbytes(v0);
aget-byte v7,v0,v3

// v6 += v7;
add-int/2addr v6,v7

// v3 ++;
add-int/lit8 v3,v3,1
goto l3c1e

l3c30:

// StringBuilder badge = new StringBuilder();
new-instance v7,java/lang/StringBuilder
invoke-direct {v7},java/lang/StringBuilder/ ; ()V

// v8 = Math.random();
invoke-static {},java/lang/Math/random ; random()D
nop
move-result-wide v8

// v10 = 4652007308841189376;
const-wide v10,4652007308841189376 ; 0×408f400000000000

// v8 = Math.round(v8*v10);
mul-double/2addr v8,v10

// I thought it only took one variable??
invoke-static {v8,v9},java/lang/Math/round ; round(D)J
move-result-wide v8

// v10 = 1000
const-wide/16 v10,1000

// v8 += v10;
add-long/2addr v8,v10

// badge.append(v8);
invoke-virtual {v7,v8,v9},java/lang/StringBuilder/append ; append(J)Ljava/lang/StringBuilder;
move-result-object v7

// badge.append(dateFormat.format(now));
invoke-virtual {v5,v2},java/text/SimpleDateFormat/format ; format(Ljava/util/Date;)Ljava/lang/String;
move-result-object v8
invoke-virtual {v7,v8},java/lang/StringBuilder/append ; append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v7

// badge.append(difficulty);
iget v8,v12,tx/games/atd_world.difficulty I
invoke-virtual {v7,v8},java/lang/StringBuilder/append ; append(I)Ljava/lang/StringBuilder;
move-result-object v7

// badge.append(sum);
invoke-virtual {v7,v6},java/lang/StringBuilder/append ; append(I)Ljava/lang/StringBuilder;
move-result-object v7

// return badge.toString();
invoke-virtual {v7},java/lang/StringBuilder/toString ; toString()Ljava/lang/String;
move-result-object v1
return-object v1
.end method

An example of the output of this function is; 1310090403121501473

Broken down the output looks like this;

1310090403121501473, (round(random * const)+1000

1310090403121501473, Date in yyMMddhhmm format.

1310090403121501473, “0″ Difficulty, Noob = 0, Normal = 1, Pro = 3

1310090403121501473, sum of bytes (date + difficulty + “tensaix2″)

I’ll post more later if the “badge system” is every finished and released. Hopefully this serves as a decent example on how to reverse simple android programs… Enjoy!

Monday, January 12th, 2009 | Author: Tim

Your Ad Here
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;

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

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!

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.