package com.example.forshaw.servicetest; import android.content.Context; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.Button; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { static void log(String logString) { Log.e("MyClass", logString); } static String getStackTrack() { try { throw new Throwable(); } catch(Throwable t) { StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); t.printStackTrace(pw); return writer.toString(); } } static class MediaPlayerServiceOps { static int CREATE = IBinder.FIRST_CALL_TRANSACTION; static int CREATE_MEDIA_RECORDER = CREATE + 1; static int CREATE_METADATA_RETRIEVER = CREATE_MEDIA_RECORDER + 1; static int GET_OMX = CREATE_METADATA_RETRIEVER + 1; static int MAKE_CRYPTO = GET_OMX + 1; static int MAKE_DRM = MAKE_CRYPTO + 1; static int MAKE_HDCP = MAKE_DRM + 1; static int ADD_BATTERY_DATA = MAKE_HDCP + 1; static int PULL_BATTERY_DATA = ADD_BATTERY_DATA + 1; static int LISTEN_FOR_REMOTE_DISPLAY = PULL_BATTERY_DATA + 1; static int GET_CODEC_LIST = LISTEN_FOR_REMOTE_DISPLAY + 1; } static int kMode_Unencrypted = 0; static int kMode_AES_CTR = 1; static int kMode_AES_WV = 2; static int kMode_AES_CBC = 3; static class SubSample { int mNumBytesOfClearData; int mNumBytesOfEncryptedData; public SubSample(int clearData, int encryptedData) { mNumBytesOfClearData = clearData; mNumBytesOfEncryptedData = encryptedData; } public SubSample() { this(0, 0); } } static interface IMemoryHeap { ParcelFileDescriptor getHeapID(); int getSize(); int getFlags(); int getOffset(); } static class IMemoryHeapStub extends Binder { static final String DESCRIPTOR = "android.utils.IMemoryHeap"; private FileDescriptor mFd; private int mOffset; private int mSize; private int mFlags; public static final int READ_ONLY = 0x00000001; public IMemoryHeapStub(FileDescriptor fd, int offset, int size, int flags) { mFd = fd; mOffset = offset; mSize = size; mFlags = flags; } static final int HEAD_ID = IBinder.FIRST_CALL_TRANSACTION; @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case HEAD_ID: { data.enforceInterface(DESCRIPTOR); log("In HEAD_ID: " + getStackTrack()); reply.writeFileDescriptor(mFd); reply.writeInt(mSize); reply.writeInt(mFlags); reply.writeInt(mOffset); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } } static class IMemoryHeapProxy implements IMemoryHeap { private boolean mLoaded; private int mBase; private int mFlags; private int mOffset; private int mSize; private ParcelFileDescriptor mHeapID; private IBinder mBinder; static int HEAP_ID = IBinder.FIRST_CALL_TRANSACTION; static final String DESCRIPTOR = "android.utils.IMemoryHeap"; private void loadFromProxy() { if (!mLoaded) { mLoaded = true; try { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); mBinder.transact(HEAP_ID, data, reply, 0); mHeapID = reply.readFileDescriptor(); mSize = reply.readInt(); mFlags = reply.readInt(); mOffset = reply.readInt(); } catch(RemoteException ex) { log("Exception loading MemoryHeap: " + ex); } } } @Override public ParcelFileDescriptor getHeapID() { loadFromProxy(); return mHeapID; } @Override public int getSize() { loadFromProxy(); return mSize; } @Override public int getFlags() { loadFromProxy(); return mFlags; } @Override public int getOffset() { loadFromProxy(); return mOffset; } public IMemoryHeapProxy(IBinder binder) { mBinder = binder; } } static class MemoryInfo { public int size; public int offset; } static interface IMemory { IMemoryHeap getHeap(); int getSize(); int getOffset(); } static class IMemoryProxy implements IMemory { static final String DESCRIPTOR = "android.utils.IMemory"; private boolean mLoaded; private IBinder mBinder; private IMemoryHeap mHeap; private int mSize; private int mOffset; static int GET_MEMORY = IBinder.FIRST_CALL_TRANSACTION; public IMemoryProxy(IBinder binder) { mBinder = binder; } private void getMemory() { if (!mLoaded) { mLoaded = true; try { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); mBinder.transact(GET_MEMORY, data, reply, 0); mHeap = new IMemoryHeapProxy(reply.readStrongBinder()); mOffset = reply.readInt(); mSize = reply.readInt(); } catch(RemoteException ex) { log("Exception loading Memory: " + ex); } } } @Override public IMemoryHeap getHeap() { getMemory(); return mHeap; } @Override public int getSize() { getMemory(); return mSize; } @Override public int getOffset() { getMemory(); return mOffset; } } static class IMemoryStub extends Binder { static String DESCRIPTOR = "android.utils.IMemory"; private IMemoryHeapStub mHeap; private int mOffset; private int mSize; public IMemoryStub(FileDescriptor fd, int offset, int size, int heapoffset, int heapsize, boolean readonly) { mHeap = new IMemoryHeapStub(fd, heapoffset, heapsize, readonly ? IMemoryHeapStub.READ_ONLY : 0); mOffset = offset; mSize = size; } public IMemoryStub(FileDescriptor fd, int offset, int size) { this(fd, offset, size, offset, size, true); } static final int GET_MEMORY = IBinder.FIRST_CALL_TRANSACTION; @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { log("onTransact IMemory: " + code); switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case GET_MEMORY: { data.enforceInterface(DESCRIPTOR); log("In GET_MEMORY: " + getStackTrack()); reply.writeStrongBinder(mHeap); reply.writeInt(mOffset); reply.writeInt(mSize); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } } static class CryptoProxy { static int INIT_CHECK = IBinder.FIRST_CALL_TRANSACTION; static int IS_CRYPTO_SUPPORTED = INIT_CHECK + 1; static int CREATE_PLUGIN = IS_CRYPTO_SUPPORTED + 1; static int DESTROY_PLUGIN = CREATE_PLUGIN + 1; static int REQUIRES_SECURE_COMPONENT = DESTROY_PLUGIN + 1; static int DECRYPT = REQUIRES_SECURE_COMPONENT + 1; static int NOTIFY_RESOLUTION = DECRYPT + 1; static int SET_MEDIADRM_SESSION = NOTIFY_RESOLUTION + 1; private IBinder mBinder; public CryptoProxy(IBinder binder) { mBinder = binder; } public int initCheck() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.hardware.ICrypto"); mBinder.transact(CryptoProxy.INIT_CHECK, data, reply, 0); return reply.readInt(); } public boolean getIsCryptoSupported(byte[] uuid) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.hardware.ICrypto"); writeRawBytes(data, uuid); mBinder.transact(CryptoProxy.IS_CRYPTO_SUPPORTED, data, reply, 0); return reply.readInt() != 0; } public int createPlugin(byte[] uuid, byte[] opaqueData) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.hardware.ICrypto"); writeRawBytes(data, uuid); int length = opaqueData == null ? 0 : opaqueData.length; data.writeInt(length); if (length > 0) { writeRawBytes(data, opaqueData); } mBinder.transact(CryptoProxy.CREATE_PLUGIN, data, reply, 0); return reply.readInt(); } int destroyPlugin() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.hardware.ICrypto"); mBinder.transact(DESTROY_PLUGIN, data, reply, 0); return reply.readInt(); } boolean requiresSecureDecoderComponent( String mime) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.hardware.ICrypto"); writeRawBytes(data, mime.getBytes()); mBinder.transact(REQUIRES_SECURE_COMPONENT, data, reply, 0); return reply.readInt() != 0; } byte[] decrypt(boolean secure, byte[] key, byte[] iv, int mode, IMemoryStub sharedBuffer, int offset, SubSample[] subSamples, long dstPtr ) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.hardware.ICrypto"); data.writeInt(secure ? 1 : 0); data.writeInt(mode); if (key == null) key = new byte[16]; if (iv == null) iv = new byte[16]; writeRawBytes(data, key); writeRawBytes(data, iv); int totalSize = 0; for (SubSample ss : subSamples) { totalSize += ss.mNumBytesOfEncryptedData; totalSize += ss.mNumBytesOfClearData; } // REMOVE //totalSize = 10; data.writeInt(totalSize); data.writeStrongBinder(sharedBuffer); data.writeInt(offset); data.writeInt(subSamples.length); for (SubSample ss : subSamples) { data.writeInt(ss.mNumBytesOfClearData); data.writeInt(ss.mNumBytesOfEncryptedData); } if (secure) data.writeLong(dstPtr); mBinder.transact(DECRYPT, data, reply, 0); int result = reply.readInt(); log("Result: " + result); if ((result <= -2000) && (result > -4000)) { throw new RemoteException(readCString(reply)); } else { if (!secure && result > 0) { // Read raw result data return readRawBytes(reply, result); } } return new byte[0]; } } static int[] convertBytesToInts(byte[] byteArray) { IntBuffer intBuf = ByteBuffer.wrap(byteArray) .order(ByteOrder.LITTLE_ENDIAN) .asIntBuffer(); int[] array = new int[intBuf.remaining()]; intBuf.get(array); return array; } static void writeRawBytes(Parcel data, byte[] bytes) { int len = bytes.length; if ((len & 3) != 0) { bytes = Arrays.copyOf(bytes, (len + 3) & ~3); } for (int i : convertBytesToInts(bytes)) { data.writeInt(i); } } static byte[] convertIntToBytes(int i) { ByteBuffer byteBuffer = ByteBuffer.allocate(4); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); byteBuffer.putInt(0, i); byte[] ret = new byte[4]; byteBuffer.get(ret); return ret; } static String readCString(Parcel data) { StringBuilder builder = new StringBuilder(); while(data.dataAvail() > 4) { int len = 0; byte[] bytes = convertIntToBytes(data.readInt()); for(len = 0; len < 4; ++len) { if (bytes[len] == 0) { break; } } builder.append(new String(bytes, 0, len)); if (len < 4) { break; } } return builder.toString(); } static byte[] readRawBytes(Parcel data, int length) { int read_len = (length + 3) & ~3; byte[] ret = new byte[length]; ByteBuffer byteBuffer = ByteBuffer.allocate(read_len); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); for(int i = 0; i < read_len / 4; ++i) { byteBuffer.putInt(data.readInt()); } byteBuffer.position(0); byteBuffer.get(ret, 0, length); return ret; } static String dumpHex(byte[] bytes) { StringBuilder builder = new StringBuilder(); for(byte b : bytes) { builder.append(String.format("%02X", b & 0xFF)); } return builder.toString(); } static IBinder getService(String service) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class c = Class.forName("android.os.ServiceManager"); Method m = c.getMethod("getService", String.class); return (IBinder)m.invoke(null, service); } private void testMediaPlayer(Context ctx) throws Throwable { IBinder serviceBinder = getService("media.player"); log(serviceBinder.getInterfaceDescriptor()); Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.media.IMediaPlayerService"); serviceBinder.transact(MediaPlayerServiceOps.MAKE_CRYPTO, data, reply, 0); IBinder crypto = reply.readStrongBinder(); log(crypto.getInterfaceDescriptor()); byte uuid[] = { (byte)0x10, (byte)0x77, (byte)0xEF, (byte)0xEC, (byte)0xC0, (byte)0xB2, (byte)0x4D, (byte)0x02, (byte)0xAC, (byte)0xE3, (byte)0x3C, (byte)0x1E, (byte)0x52, (byte)0xE2, (byte)0xFB, (byte)0x4B }; CryptoProxy cryptoProxy = new CryptoProxy(crypto); log("IsCryptoSupported: " + cryptoProxy.getIsCryptoSupported(uuid)); log("CreatePlugin: " + cryptoProxy.createPlugin(uuid, null)); FileOutputStream fos = ctx.openFileOutput("dummy.bin", 0); byte[] obs = new byte[10]; for (int i = 0; i < obs.length; ++i) { obs[i] = (byte)i; } fos.write(obs, 0, obs.length); fos.close(); FileInputStream fis = ctx.openFileInput("dummy.bin"); IMemoryStub sharedBuffer = new IMemoryStub(fis.getFD(), -1024*1024*1024, 0xFFFFFFFF, 0, 10, true); SubSample[] subSamples = new SubSample[1]; subSamples[0] = new SubSample(10, 0); log("Decrypt: " + dumpHex(cryptoProxy.decrypt(false, null, null, kMode_Unencrypted, sharedBuffer, 0x1234, subSamples, 0))); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); try { testMediaPlayer(this.getBaseContext()); } catch(Throwable t) { log("X" + t.toString()); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }