● 对一款人脸识别自助储物柜逆向分析(一)

对一款人脸识别自助储物柜逆向分析(二)

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接口实现就太没技术含量了😬)。界面如下:

测试结果: zlkws-0iuy7