● 对一款人脸识别自助储物柜逆向分析(一)
1.抓包获取接口
柜子实物,仅有公开文档:
中间有一次把扩展板玩挂了,后来通过物理按键操作恢复出厂了😅。
就是下面这块板子,既然能恢复那就没有后顾之忧了😈:
储物柜有个WEB管理页面:
浏览器开发者模式,获取到开#5号箱的POST接口,模拟提交:
2.反编译获取源码
2.1 android系统信息
系统为android,root之后获取到如下信息:
adb shell getprop ro.build.version.release
# android版本:5.1.1
adb shell getprop ro.build.version.sdk
# android sdk版本:22
2.2 apk反编译
把apk拖回本地,反编译,获取到上图的开锁web api接口OpenBoxController。精简了一下:
public String OpenBox(String str) {
int intValue = JSONObject.parseArray(str).getInteger(0).intValue();
List find = LitePal.where(new String[]{"boxNumber=?", intValue + ""}).find(Box.class);
if (find != null && find.size() > 0) {
LockAddress lockAddress = (LockAddress) JSONObject.toJavaObject(JSONObject.parseObject(((Box) find.get(0)).getLockAddress()), LockAddress.class);
LockHelper.getInstance().OpenSwitch(lockAddress.getAddress(), lockAddress.getNumber());
}
}
上面的源码里有一个find
,是通过LitePal
获取的,查资料后发现LitePal
是android操作SQLite的类库。又到机器里拖回了数据库:
lockAddress是一个model类,lockAddress.getAddress()=1,lockAddress.getNumber()=5,5表示5号柜。
开锁核心代码: LockHelper.getInstance().OpenSwitch(1, 5);
2.3 追溯实现方法
跳转了很多类,整理到一起:
public static String INT_KEY_LOCKPLATEAGREEMENT = "LockPlateAgreement";
public static int OLD_REEMENT = 0;
public static byte CMD_ON = 4;
public int getInt(String str, int i) {
#腾讯开源组件MMKV
return this.mmkv.getInt(str, i);
}
public void OpenSwitch(int i, int i2) {
synchronized (this.object) {
int i3 = PreferenceUtil.getInstance().getInt(PreferenceUtil.INT_KEY_LOCKPLATEAGREEMENT, PreferenceUtil.OLD_REEMENT);
byte[] bArr = new byte[1];
bArr[0] = (byte) ((byte) i2);
if (i3 == 0) {
SendCommand(CMD_ON, (byte) i, bArr);
} else {
SendCommand2(CMD_ON, (byte) i, bArr);
}
}
}
public void SendCommand(byte b, byte b2, byte[] bArr) {
synchronized (this) {
byte[] bArr2 = new byte[bArr.length + 9];
bArr2[0] = (byte) 90;
bArr2[1] = (byte) 90;
bArr2[2] = (byte) 0;
bArr2[3] = (byte) b2;
bArr2[4] = (byte) 0;
bArr2[5] = (byte) b;
bArr2[6] = (byte) 0;
bArr2[7] = (byte) ((byte) bArr.length);
System.arraycopy(bArr, 0, bArr2, 8, bArr.length);
for (int i = 2; i < bArr.length + 8; i++) {
int length = bArr.length + 8;
bArr2[length] = (byte) ((byte) (bArr2[length] ^ bArr2[i]));
}
sendData(bArr2);
}
}
public void SendCommand2(byte b, byte b2, byte[] bArr) {
synchronized (this) {
int length = bArr.length + 9;
byte[] bArr2 = new byte[length];
bArr2[0] = (byte) 90;
bArr2[1] = (byte) 90;
bArr2[2] = (byte) 0;
bArr2[3] = (byte) b;
bArr2[4] = (byte) 0;
bArr2[5] = (byte) 38;
bArr2[6] = (byte) 0;
bArr2[7] = (byte) ((byte) bArr.length);
bArr2[8] = (byte) b2;
byte[] bArr3 = new byte[length - 2];
for (int i = 2; i < length; i++) {
bArr3[i - 2] = (byte) bArr2[i];
}
bArr2[9] = Verification.orVerification(bArr3);
sendData(bArr2);
}
}
private void sendData(byte[] bArr) {
synchronized (this) {
if (this.mSerialPortManager0 != null) {
this.mSerialPortManager0.sendBytes(bArr);
}
if (this.mSerialPortManager1 != null) {
this.mSerialPortManager1.sendBytes(bArr);
}
if (this.mSerialPortManager2 != null) {
this.mSerialPortManager2.sendBytes(bArr);
}
if (this.mSerialPortManager3 != null) {
this.mSerialPortManager3.sendBytes(bArr);
}
if (this.mSerialPortManager4 != null) {
this.mSerialPortManager4.sendBytes(bArr);
}
}
}
核心实现:
private Handler handler;
private HandlerThread handlerThread;
HandlerThread handlerThread = new HandlerThread(getClass().getName(), -1);
this.handlerThread = handlerThread;
handlerThread.start();
this.handler = new Handler(this.handlerThread.getLooper()) { // from class: com.kongqw.serialportlibrary.SerialPortManager.2
@Override // android.os.Handler
public void handleMessage(Message message) {
handleMessage(message);
byte[] bArr = (byte[]) message.obj;
if (bArr != null) {
if (!(SerialPortManager.this.mFileOutputStream == null || bArr == null || bArr.length <= 0)) {
try {
SerialPortManager.this.mFileOutputStream.write(bArr);
if (SerialPortManager.this.mOnSerialPortDataListener != null) {
SerialPortManager.this.mOnSerialPortDataListener.onDataSent(bArr);
if (SerialPortManager.this.mOnSerialPortDataListener != null) {
SerialPortManager.this.mOnSerialPortDataListener.onDataSent(bArr);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(800);
} catch (InterruptedException e2) {
e2.printStackTrace();
}
}
}
};
public void sendBytes(byte[] bArr) {
this.handler.obtainMessage(1, bArr).sendToTarget();
}
开锁数据做了校验/异或操作,obtainMessage()为获取信息和sendToTarget()为发送信息,为android自带函数,没有再封装了。
3. 编写android应用验证开锁
为了避免兼容问题,根据 #2.1 创建对应的Android SDK版本:
不会android开发,只做了几个简单的按钮事件(在界面布局上浪费了很长时间),通过串口简单调用反编译的源码(如果调用HTTP接口实现就太没技术含量了😬)。界面如下:
测试结果: