记录一次体体现前端工具仔综合实力的实际操作
起因
我们公司在17年的时候有个在手机端设备控制的项目,当时Android端和iOS端是使用apicloud打包而成。当时有款需要控制的设备用到了庆科的WiFi模组,所以项目中使用了庆科在apicloud的模块Store中提供的模块。
在这个项目上线后,很长一段时间都属于停滞状态。直到最近项目重启,再去看,庆科的模块已经全被下架了,而其它满足需求的模块需要付钱。
虽然庆科的官网中有原生android和iOS开发的文档,但是通过我自定义模块编译的APP甚至官方文档中的APP均无法完成配网和设备发现。
过程
通过用Fiddler抓包观察apicloud添加模块的流程,我发现在apicloud中每个模块都有一个唯一的模块ID,而添加模块的操作是针对模块ID进行的。虽然庆科所有的模块均已被下架,但我估计模块的数据依旧存在于apicloud的数据库中,而只是将数据的状态置为搜索不可见。通过apicloud论坛,我得知了庆科在apicloud上架的模块有两个—mico和fog2。
随后我再在百度中搜索apicloud mico,找到属于它的百度快照。通过百度快照,找到了mico模块的模块ID—8647。
再通过添加其它模块,得到添加模块的接口与数据、cookie(我这里是直接用的新版本的fiddler直接抓包,修改,请求)。然后将数据中的模块ID改成mico模块的模块ID—8647,最后执行接口请求。
mico躺在已添加模块中。
在加完mico模块后,我才注意到这个模块居然是只支持Android,并且我打包后的APP依旧无法完成配网和设备发现。
接着我再在百度中搜索apicloud fog2,找到属于它的百度快照,不过与mico模块不同,通过百度快照无法得知fog2的模块ID。
随后我尝试再用其它的搜索引擎,通过Google,找到了模块ID—24853。
最后按照添加mico模块的办法添加fog2模块到项目中。
看似到这一步一切已经完成了,其实并没有。因为fog2模块的开发文档失效了。
又回到了原点。
这时我想到,不如打个Android的包,再将这个包反编译,不就可以得到fog2模块的源码了吗?
祭出我的反编译三件套
首先将apk文件解压到文件夹
然后在当前页面启动命令行模式,输入 java -jar apktool_2.3.4.jar d -f app.apk -o app
随后使用VSCode打开apktool反编译apk的输出文件夹,搜索fog2,得知fog2模块代码存在于classes20.dex文件中。
将classes20.dex文件复制到dex2jar-2.0文件夹中
随即在命令行中定位到dex2jar-2.0目录下,输入 d2j-dex2jar classes20.dex,得到classes20-dex2jar.jar包
经过分析代码和转化其它需要的dex文件,得到以下方法
public void jsmethod_getSSID(UZModuleContext paramUZModuleContext)
{
this.mJsCallback = paramUZModuleContext;
paramUZModuleContext = this.micodev.getSSID();
execCallBack("success", this.errorCode.setSuccJson("{\"ssid\":\"" + paramUZModuleContext + "\"}"), true);
}
public void jsmethod_init(UZModuleContext paramUZModuleContext)
{
this.mJsCallback = paramUZModuleContext;
paramUZModuleContext = paramUZModuleContext.optString("host");
if (checkAndCallBack(true, new String[] { paramUZModuleContext })) {
MiCO.init(paramUZModuleContext);
}
}
public void jsmethod_startEasyLink(UZModuleContext paramUZModuleContext)
{
this.mJsCallback = paramUZModuleContext;
String str1 = paramUZModuleContext.optString("ssid");
String str2 = paramUZModuleContext.optString("password");
int j = paramUZModuleContext.optInt("runSecond");
int k = paramUZModuleContext.optInt("sleeptime");
paramUZModuleContext = paramUZModuleContext.optString("extraData");
int i = j;
if (j == 0) {
i = 60000;
}
j = k;
if (k == 0) {
j = 20;
}
if ((checkAndCallBack(true, new String[] { str1 })) && (i > 0) && (j > 0)) {
this.micodev.startEasyLink(str1, str2, i, j, new EasyLinkCallBack()
{
public void onFailure(int paramAnonymousInt, String paramAnonymousString)
{
Fog2sdk.this.execCallBack("error", Fog2sdk.this.errorCode.setFailureJsonCode(paramAnonymousInt, paramAnonymousString), true);
}
public void onSuccess(String paramAnonymousString)
{
if ("success".equals(paramAnonymousString))
{
Fog2sdk.this.execCallBack("keep", Fog2sdk.this.errorCode.setSuccJson("{\"message\":\"" + paramAnonymousString + "\"}"), false);
return;
}
Fog2sdk.this.execCallBack("keep", Fog2sdk.this.errorCode.setSuccJson("{\"message\":\"" + paramAnonymousString + "\"}"), true);
}
}, paramUZModuleContext);
}
}
public void jsmethod_startSearchDevices(UZModuleContext paramUZModuleContext)
{
this.mNDSCallBack = paramUZModuleContext;
this.micodev.startSearchDevices("_easylink._tcp.local.", new SearchDeviceCallBack()
{
public void onDevicesFind(JSONArray paramAnonymousJSONArray)
{
if (Fog2sdk.this.mNDSCallBack != null) {
Fog2sdk.this.mNDSCallBack.success(Fog2sdk.this.errorCode.setSuccJson("{\"devices\":" + paramAnonymousJSONArray.toString() + "}"), false);
}
}
public void onFailure(int paramAnonymousInt, String paramAnonymousString)
{
Fog2sdk.this.mNDSCallBack.error(null, Fog2sdk.this.errorCode.setFailureJsonCode(paramAnonymousInt, paramAnonymousString), true);
}
public void onSuccess(String paramAnonymousString)
{
Fog2sdk.this.mNDSCallBack.success(Fog2sdk.this.errorCode.setSuccJson("{\"message\":\"" + paramAnonymousString + "\"}"), false);
}
});
}
public void jsmethod_stopEasyLink(UZModuleContext paramUZModuleContext)
{
this.mJsCallback = paramUZModuleContext;
this.micodev.stopEasyLink(new EasyLinkCallBack()
{
public void onFailure(int paramAnonymousInt, String paramAnonymousString)
{
Fog2sdk.this.execCallBack("error", Fog2sdk.this.errorCode.setFailureJsonCode(paramAnonymousInt, paramAnonymousString), true);
}
public void onSuccess(String paramAnonymousString)
{
Fog2sdk.this.execCallBack("success", Fog2sdk.this.errorCode.setSuccJson("{\"message\":\"" + paramAnonymousString + "\"}"), true);
}
});
}
public void jsmethod_stopSearchDevices(UZModuleContext paramUZModuleContext)
{
this.mJsCallback = paramUZModuleContext;
this.micodev.stopSearchDevices(new SearchDeviceCallBack()
{
public void onFailure(int paramAnonymousInt, String paramAnonymousString)
{
Fog2sdk.this.execCallBack("error", Fog2sdk.this.errorCode.setFailureJsonCode(paramAnonymousInt, paramAnonymousString), true);
Fog2sdk.this.mNDSCallBack = null;
}
public void onSuccess(String paramAnonymousString)
{
Fog2sdk.this.execCallBack("success", Fog2sdk.this.errorCode.setSuccJson("{\"message\":\"" + paramAnonymousString + "\"}"), true);
Fog2sdk.this.mNDSCallBack = null;
}
});
}
package com.mxchip.helper;
public class MiCO
{
public static void init(String paramString)
{
Configuration._APIHOST = paramString;
}
}
public static String _APIHOST = "https://v2.fogcloud.io";
最终得出的js代码
let fog2 = null;
let easyLinkService = {
hasInit: false,
init: function() {
if(fog2 == null){
fog2 = api.require('fog2')
}
if (!this.hasInit) {
this.initEasyLink()
this.hasInit = true
}
},
initEasyLink: function() {
fog2.init({
host: "https://v2.fogcloud.io"
})
},
getSSID: function() {
return new Promise ( function(resolve, reject) {
fog2.getSSID(function(ret, err) {
if (ret) {
resolve(ret.ssid)
} else {
reject(err)
}
});
})
},
startEasyLink: function(ssid_val, psw_val) {
return new Promise ( function(resolve, reject) {
let param = {
ssid: ssid_val,
password: psw_val
};
fog2.startEasyLink(param, function(ret, err) {
if (ret) {
resolve(ret.message)
} else {
reject(err.message)
}
});
})
},
stopEasyLink: function() {
return new Promise ( function(resolve, reject) {
fog2.stopEasyLink(function(ret, err) {
if (ret) {
console.log(ret.message);
resolve(ret.message)
} else {
console.log(err.message);
reject(err.message)
}
});
})
},
startSearchDevices: function() {
return new Promise ( function(resolve, reject) {
fog2.startSearchDevices(function(ret, err) {
if (ret) {
let deviceList = [];
if (null != ret.devices && ret.devices.length > 0) {
deviceList = ret.devices
resolve(deviceList)
}
} else {
reject(err)
}
});
})
},
stopSearchDevices: function() {
return new Promise ( function(resolve, reject) {
fog2.stopSearchDevices(function(ret, err) {
if (ret) {
resolve(ret)
} else {
reject(err)
}
});
})
},
}
window.easyLinkService = easyLinkService
export default easyLinkService
完成配网并成功发现设备,大功告成
结尾
这篇文章主要是记录这次操作中的思路,以及抓包、改包、反编译等技能的实践操作,真正做到学以致用,将我们所学的知识,运用到实际操作当中去。