/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project
* -----------------------------------------------------------
* LICENSE: http://git.io/vki47 | TERMS: http://git.io/vki4o
* -----------------------------------------------------------
*/
package com.secupwn.aimsicd.rilexecutor;
import android.content.Context;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import com.secupwn.aimsicd.utils.Helpers;
import com.secupwn.aimsicd.utils.OemCommands;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import io.freefair.android.util.logging.AndroidLogger;
import io.freefair.android.util.logging.Logger;
/*
* Class to handle Ril and Samsung MultiRil implementation. Used by the Aimsicd Service.
*/
public class RilExecutor {
private final Logger log = AndroidLogger.forClass(RilExecutor.class);
public boolean mMultiRilCompatible;
/*
* Samsung MultiRil Implementation
*/
// E:V:A 2014-12-18
// This function as implemented here , only works on Samsung Galaxy S2 GT-I9100
// and possibly on the S3 GT-I9300 and the P7100. It currently depend on:
// 1. baseband is an Intel XMM modem
// 2. gsm.version.ril-impl = "Samsung RIL (IPC) v2.0"
// However, it should be possible to extend this to the latest devices
// and those also using Qualcomm basebands
//
// Q: How does it work?
// A: It uses the internal BP ServiceMode function on the BP and forwards the info
// to the AP ServiceMode wrapper App. that is known by various other names, such as:
//
// FactoryTest, FactoryTest_FB, serviceModeApp_FB, ServiceModeApp_RIL,
// SecFactoryPhoneTest, Factory_RIL, ServiceMode etc etc.
//
// The protocol to make these forwarded messages are through IPC messages via
// the RIL QMI interface on /dev/socket/rild | ril-debug ...or something like that.
private static final int ID_REQUEST_START_SERVICE_MODE_COMMAND = 1;
private static final int ID_REQUEST_FINISH_SERVICE_MODE_COMMAND = 2;
private static final int ID_REQUEST_PRESS_A_KEY = 3;
private static final int ID_REQUEST_REFRESH = 4;
private static final int ID_RESPONSE = 101;
private static final int ID_RESPONSE_FINISH_SERVICE_MODE_COMMAND = 102;
private static final int ID_RESPONSE_PRESS_A_KEY = 103;
private static final int REQUEST_TIMEOUT = 10000; // ms
private final ConditionVariable mRequestCondvar = new ConditionVariable();
private final Object mLastResponseLock = new Object();
private volatile List<String> mLastResponse;
private DetectResult mRilExecutorDetectResult;
private OemCommands mOemCommands;
private OemRilExecutor mRequestExecutor;
private HandlerThread mHandlerThread;
private Handler mHandler;
public RilExecutor(Context context) {
mOemCommands = OemCommands.getInstance(context);
mRequestExecutor = new SamsungMulticlientRilExecutor();
mRilExecutorDetectResult = mRequestExecutor.detect();
if (!mRilExecutorDetectResult.available) {
mMultiRilCompatible = false;
log.error("Samsung Multiclient RIL not available: " + mRilExecutorDetectResult.error);
mRequestExecutor = null;
} else {
mRequestExecutor.start();
mMultiRilCompatible = true;
// Samsung MultiRil Initialization
mHandlerThread = new HandlerThread("ServiceModeSeqHandler");
mHandlerThread.start();
Looper l = mHandlerThread.getLooper();
if (l != null) {
mHandler = new Handler(l, new MyHandler());
}
}
}
public void stop() {
//Samsung MultiRil Cleanup
if (mRequestExecutor != null) {
mRequestExecutor.stop();
mRequestExecutor = null;
mHandler = null;
mHandlerThread.quit();
mHandlerThread = null;
}
}
/**
* Check the status of the RIL Executor
*
* @return DetectResult providing access status of the RIL Executor
*/
public DetectResult getRilExecutorStatus() {
return mRilExecutorDetectResult;
}
/**
* Service Mode Command Helper to call with Timeout value
*
* @return executeServiceModeCommand adding REQUEST_TIMEOUT
*/
private List<String> executeServiceModeCommand(int type, int subtype,
java.util.Collection<KeyStep> keySeqence) {
return executeServiceModeCommand(type, subtype, keySeqence, REQUEST_TIMEOUT);
}
/**
* Service Mode Command Helper to call with Timeout value
*
* @return executeServiceModeCommand adding REQUEST_TIMEOUT
*/
private synchronized List<String> executeServiceModeCommand(int type, int subtype,
java.util.Collection<KeyStep> keySeqence, int timeout) {
if (mRequestExecutor == null) {
return Collections.emptyList();
}
mRequestCondvar.close();
mHandler.obtainMessage(ID_REQUEST_START_SERVICE_MODE_COMMAND,
type,
subtype,
keySeqence).sendToTarget();
if (!mRequestCondvar.block(timeout)) {
log.error("request timeout");
return Collections.emptyList();
} else {
synchronized (mLastResponseLock) {
return mLastResponse;
}
}
}
/**
* Executes and receives the Ciphering Information request using
* the RIL Executor
*
* @return String list response from RIL Executor
*/
public List<String> getCipheringInfo() {
return executeServiceModeCommand(
OemCommands.OEM_SM_TYPE_TEST_MANUAL,
OemCommands.OEM_SM_TYPE_SUB_CIPHERING_PROTECTION_ENTER,
null
);
}
/**
* Executes and receives the Neighboring Cell request using
* the RIL Executor
*
* @return String list response from RIL Executor
*/
public List<String> getNeighbors() {
KeyStep getNeighborsKeySeq[] = new KeyStep[]{
new KeyStep('\0', false),
new KeyStep('1', false), // [1] DEBUG SCREEN
new KeyStep('4', true), // [4] NEIGHBOR CELL
};
return executeServiceModeCommand(
OemCommands.OEM_SM_TYPE_TEST_MANUAL,
OemCommands.OEM_SM_TYPE_SUB_ENTER,
Arrays.asList(getNeighborsKeySeq)
);
}
private static class KeyStep {
public final char keychar;
public final boolean captureResponse;
KeyStep(char keychar, boolean captureResponse) {
this.keychar = keychar;
this.captureResponse = captureResponse;
}
public static final KeyStep KEY_START_SERVICE_MODE = new KeyStep('\0', true);
}
private class MyHandler implements Handler.Callback {
private int mCurrentType;
private int mCurrentSubtype;
private Queue<KeyStep> mKeySequence;
@Override
public boolean handleMessage(Message msg) {
byte[] requestData;
Message responseMsg;
KeyStep lastKeyStep;
switch (msg.what) {
case ID_REQUEST_START_SERVICE_MODE_COMMAND:
mCurrentType = msg.arg1;
mCurrentSubtype = msg.arg2;
mKeySequence = new ArrayDeque<>(3);
if (msg.obj != null) {
mKeySequence.addAll((java.util.Collection<KeyStep>) msg.obj);
} else {
mKeySequence.add(KeyStep.KEY_START_SERVICE_MODE);
}
synchronized (mLastResponseLock) {
mLastResponse = new ArrayList<>();
}
requestData = mOemCommands.getEnterServiceModeData(
mCurrentType, mCurrentSubtype, OemCommands.OEM_SM_ACTION);
responseMsg = mHandler.obtainMessage(ID_RESPONSE);
mRequestExecutor.invokeOemRilRequestRaw(requestData, responseMsg);
break;
case ID_REQUEST_FINISH_SERVICE_MODE_COMMAND:
requestData = mOemCommands.getEndServiceModeData(mCurrentType);
responseMsg = mHandler.obtainMessage(ID_RESPONSE_FINISH_SERVICE_MODE_COMMAND);
mRequestExecutor.invokeOemRilRequestRaw(requestData, responseMsg);
break;
case ID_REQUEST_PRESS_A_KEY:
requestData = mOemCommands.getPressKeyData(msg.arg1, OemCommands.OEM_SM_ACTION);
responseMsg = mHandler.obtainMessage(ID_RESPONSE_PRESS_A_KEY);
mRequestExecutor.invokeOemRilRequestRaw(requestData, responseMsg);
break;
case ID_REQUEST_REFRESH:
requestData = mOemCommands.getPressKeyData('\0', OemCommands.OEM_SM_QUERY);
responseMsg = mHandler.obtainMessage(ID_RESPONSE);
mRequestExecutor.invokeOemRilRequestRaw(requestData, responseMsg);
break;
case ID_RESPONSE:
lastKeyStep = mKeySequence.poll();
try {
RawResult result = (RawResult) msg.obj;
if (result == null) {
log.error("result is null");
break;
}
if (result.exception != null) {
log.error("", result.exception);
break;
}
if (result.result == null) {
log.verbose("No need to refresh");
break;
}
if (lastKeyStep.captureResponse) {
synchronized (mLastResponseLock) {
mLastResponse
.addAll(Helpers.unpackByteListOfStrings(result.result));
}
}
} finally {
if (mKeySequence.isEmpty()) {
mHandler.obtainMessage(ID_REQUEST_FINISH_SERVICE_MODE_COMMAND)
.sendToTarget();
} else {
mHandler.obtainMessage(ID_REQUEST_PRESS_A_KEY,
mKeySequence.element().keychar, 0).sendToTarget();
}
}
break;
case ID_RESPONSE_PRESS_A_KEY:
mHandler.sendMessageDelayed(mHandler.obtainMessage(ID_REQUEST_REFRESH), 10);
break;
case ID_RESPONSE_FINISH_SERVICE_MODE_COMMAND:
mRequestCondvar.open();
break;
}
return true;
}
}
}