实战 | 记一次二房东智能电表的白嫖之路

admin 2024年12月6日15:38:23评论20 views字数 5420阅读18分4秒阅读模式

1背景

前段时间有位好友,搬家,结果新的二房东竟然私装电表,计价1.5元/kwh,于是本着求知欲就帮忙看了下。

通过查看电表铭牌,发现为某品牌单相蓝牙电能表,具体使用流程为,电表上附有一个二维码,扫码后打开微信小程序xx水电智能管家,通过小程序储值后,会通过蓝牙同步信息给电表。

通过此工作方式,初步判断此表本身不具备联网校验能力。

2背景调查

但是处于稳妥考虑,我依旧到淘宝上找到了卖同款表的商家,再次确认,是否有远程抄表等功能,得到了与我猜测一致的结果。同时与商家沟通了解到,该表也可以在出厂时,调节度量精度,即1 KWh的用量,可以量出来最多1.5KWh,相当于增加了50%,但是幅度仅能由厂家调节,发货后,不能再调节。

3反编译微信小程序

这一步已经很成熟了,这里就不过多赘述,我自己图方便,是通过PC版小程序解密得到。

4分析代码

可以看到目标小程序并没有什么安全措施,很快在一个名为rechargeV2.js的文件中发现相关逻辑

                    createPayOrder: function(a, o, t) {var n = this;                        e.request({url: c.default.apiUrl + "/zk-payment/create",method: "POST",header: {"Content-Type": "application/x-www-form-urlencoded"                            },data: {accessToken: n.accessToken,plathForm: "zk",money: t,payMoney: o,deviceId: n.deviceData.id,openId: a                            },success: function(a) {                                console.log("支付订单参数", a.data);var o = a.data;if ("success" == o.code) {var i = o.data.recordId, r = o.data.buyNum, s = parseInt(o.data.buyTimes) + 1;                                    e.requestPayment({provider: "wxpay",timeStamp: o.data.timeStamp,nonceStr: o.data.nonceStr,package: o.data.package,signType: o.data.signType,paySign: o.data.paySign,success: function(a) {                                            console.log("rechargeId", i);var o = 30, l = setInterval(function() {                                                console.log("request query " + o), o--, e.request({url: c.default.apiUrl + "/zk-payment/query-recharge",method: "POST",header: {"Content-Type": "application/x-www-form-urlencoded"                                                    },data: {accessToken: n.accessToken,plathForm: "zk",deviceId: n.deviceData.id,rechargeId: i                                                    },success: function(e) {var a = e.data;"success" == a.code && 1 == a.data.status && (clearInterval(l), o = 0, n.wechatPayAction(t, r, s));                                                    },                                                    fail: function(e) {                                                        console.log("失败", e);                                                    }                                                }), o < 1 && (clearInterval(l), o = 0, e.hideLoading(), e.showToast({title: "充值超时",icon: "none"                                                }));                                            }, 1e3);                                            console.log("success:" + JSON.stringify(a));                                        },                                        fail: function(a) {                                            e.hideLoading(), console.log("fail:" + JSON.stringify(a));                                        },                                        complete: function(e) {                                            console.log("支付付款完成", e);                                        }                                    });                                } else e.hideLoading(), e.showToast({title: o.message,icon: "none"                                });                            }                        });                    },

在调用充值,并通过服务端确认充值完毕后,最终调用了getOrder方法后,随即通过蓝牙向电能表写入数据,一些参数说明,已经写在代码中

                    getOrders: function(a, o) {var t = this, n = "no";                        t.isLeftMoney && (n = "yes"), e.request({url: c.default.apiUrl + "/zk-order/create",method: "POST",header: {"Content-Type": "application/x-www-form-urlencoded"                            },data: {accessToken: e.getStorageSync("accessToken"),plathForm: "zk",deviceCode: t.deviceCode, // <- 设备标识符orderType: a, // <- 操作类型,这里就是充值了buyNum: o.buyNum, // <- 充值的度数,而不是金额buyCount: o.buyTimes, // <- 此参数控制重放攻击,也就是电能表的充值次数buyMoney: o.buyMoney, // <- 充值的单价rechargeId: o.rechargeId,buchong: t.buchong,isLeft: n                            },success: function(e) {                                console.log(e);var c = e.data;if ("success" == c.code) {                                    t.isLeftMoney && t.clearLeftMoney(t.deviceData.id, o.buyMoney), t.rechargeId = c.data.rechargeId;var n = c.data.order; // <- 写入电能表的hex string                                    t.writeBLECharacteristicValue(n, a, c.data.rechargeId);                                }                            },                            fail: function(a) {                                e.hideLoading(), console.log(a);                            },                            complete: function(e) {}                        });                    },

上面的请求中有个buyCount参数,用于防止中间人对电能表进行重放攻击,也就是说,当表内记录的buyCount数大于等于请求数据内的buyCount时,电能表会直接忽略这次请求。

除了这一个小小的特殊点以外,其他并无特别,拿到服务端的数据后,也没有过多的操作,即通过蓝牙写入数据

writeBLECharacteristicValue: function(a) {var o = arguments.length > 1 && void0 !== arguments[1] ? arguments[1] : "", t = arguments.length > 2 && void0 !== arguments[2] ? arguments[2] : 0, c = this, i = n.default.hex2buffer(a);// 中间有一大段无效代码 在此省略                        e.setBLEMTU({                            deviceId: c.bleData.bleDeviceId,                            mtu: 100,                            success: function() {                                e.writeBLECharacteristicValue({                                    deviceId: c.bleData.bleDeviceId,                                    serviceId: c.bleData.bleServiceId,                                    characteristicId: c.bleData.bleCharacteristicIdWrite,                                    value: i, // <- 写入电能表的数据                                    writeType: "writeNoResponse",                                    success: function(e) {                                        console.log("指令下发成功", e);                                    },                                    fail: function(a) {                                        c.closeBLEConnection(), e.hideLoading(), c.showMask = !1, console.log("指令发送失败", a),                                         c.isLeftMoney || "buy" != o || c.orderExcFailed(o, t);                                    }                                });                            },                            fail: function(a) {                                c.closeBLEConnection(), e.hideLoading(), c.isLeftMoney || "buy" != o || c.orderExcFailed(o, t),                                 c.showMask = !1, e.showToast({                                    icon: "none",                                    title: JSON.stringify(a),                                    duration: 3e3                                });                            }                        });                    },

对照微信关于蓝牙相关的开发文档,可以知道这是BLE蓝牙通讯,且并无特别之处。

因此可以看出,整个服务端几乎没有什么校验,接下来正常充值一个,看看具体向电能表写入了什么数据呢?

5DLT645-2007 ?

通过PostMan发送相同的请求,得到服务端返回的数据格式为68XXXXXXXXXXXX68141869F8A38207A3EF88ECFE96D2622E87F4016E521BA10796F2EC16,其中XX部分为设备ID,已脱敏。

从整体格式上看,非常像DLT645协议(这是后话了,当初我并不知道有这个协议,花了一些分析,在查着资料时,发现有这个协议),于是立马找了一份文档,发现其结构基本符合DLT645,但是数据域似乎是自定义了,或者说被加密了

如果有懂的大佬,也请告诉我,这是厂家自行魔改的,还是说也是某种规范?

对于同一个充值请求,每次返回的写入数据并不一致。

68XXXXXXXXXXXX68141869F8A38207A3EF88ECFE96D2622E87F4016E521BA10796F2EC1668XXXXXXXXXXXX68141869F8A38207A3EF88523ABCC12E588865B64E007EE2EF123EA21668XXXXXXXXXXXX68141869F8A38207A3EF88B66E992139D84BE61B5D0D87EA4B9A49C716

比如,上面的三组数据,都是来自于同一个充值请求,且效用也都一样,任意一个数据发送到表后,其余的都不在有效。

考虑到电能表的计算能力比较一般,通常不会采用比较复杂的运算,初步推测是一些简单的偏移量加减或者异或运算,但是鄙人不才,获取了上千组数据后,仍未分析出数据域经过了什么改造。

6总结

整个流程为:

1.用户扫码 -> 2.小程序支付 -> 3.获取写表数据 -> 4.BLE向电能表写入数据 -> 5.电能表处理后,返回数据 -> 6.电能表返回数据送回服务端

但是服务端也很神奇,因为生成数据的结构(即步骤3.),即不校验订单(是否存在有效的支付订单),也不校验金额(请求生成的金额是否与订单支付一致),因此可以任意填写充值金额,并获得写表的数据。最终实现白嫖。当然这一步有可能是服务端会有记录的。

由于最终卡在了电能表接受的数据上,不能自主生成(也确实猜不到数据域到底是如何自定义的),最后还是得依赖于服务端的接口,所以这一次的任务,完成的并不完美。但是并不影响白嫖,写个小工具,只需要完成3.4.两步即可正常使用。

后续在翻看小程序代码时,发现getOrders方法的orderType还有其他可选的几组值,可以实现开闸/合闸/清表等操作,请求这些操作也没有更多的鉴权。在向表发送相关数据后,也确实可以实现相关功能。

如果有相关行业的大佬,知道应该如何解析其每次都变化的数据域,希望您可以不吝赐教。

7补充

最后有一些朋友怀疑自己的电表可能被调表了。这里提供一份排查步骤,供参考(原本是在回某位网友时发布的,但是比较靠后,为了方便各位能看到,再次复制一遍):

1、把家庭内所有用电器关闭,最好把插座拔掉或者空开直接断开

2、连通一个插座,并插入一个已知功率的用电器,例如电脑、电灯、电吹风等,相对功率波动较小的用电器。假设是个吹风机,额定功率为1200W

3、查看电表上有个脉冲灯,记一下一分钟内闪烁了多少次(在测量前确保脉冲灯不闪烁),假设闪烁了40次

4、电表铭牌上有一个这样的标识“xxx imp/KWh”,例如1600 imp/KWh,代表第三步中的脉冲灯每闪烁1600次,即为一度电。那么闪烁了40次则代表电能表计量一分钟用了(40/1600) = 0.025 KWh,而已知用电器1200W在一分钟应该使用0.02 KWh,因此可知此时电表被调快了25%

当然可以寻求当地电力部门的帮助,他们有更为专业的设备进行检测。据我了解多数地区也是不收费的。

最后的最后,本文不是教学,仅仅是技术讨论,请各位不要尝试。

原创作者:roB0yJEs,如侵权亲联系删除。

原文地址:https://www.52pojie.cn/thread-1980475-1-1.html

原文始发于微信公众号(TtTeam):实战 | 记一次二房东智能电表的白嫖之路

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月6日15:38:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   实战 | 记一次二房东智能电表的白嫖之路http://cn-sec.com/archives/3474289.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息