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);
|
}
|
}
|