在之前我们分析了EDUP设备工作原理以及通信协议,在这一节中我们将会讨论如何实现重放攻击,通过冒充合法服务器获取客户端的控制。
由于我们发送的数据段是固定的,所以在登陆时我们不能确定实际连接的客户端。
我们可以从两个基本属性上确定客户端:MAC和IP地址,MAC地址会在一些客户端响应中获得。如果我们将其作为id,可以通过随机选择一个有效的登录载荷启动登录进程,从响应载荷中提取MAC地址,并迫使开启另一个登录。
另一方面,IP地址不是固定的,那么MAC是固定的?如果我们控制了AP,通过设置DHCP服务配置文件,我们就可以判断IP地址是如何进行分配的。从一个套接字对象中成功调用accept()方法后,我们能清楚知道运行时连接到的是哪个客户端。
我们将使用dnsmasq作为DHCP服务,通过修改/etc/dnsmasq.conf在MAC地址,IP地址以及客户端这三者之间建立一个直接路径。
# Only listen to routers' LAN NIC. Doing so opens up tcp/udp port 53 to # localhost and udp port 67 to world: interface=br0 # dnsmasq will open tcp/udp port 53 and udp port 67 to world to help with # dynamic interfaces (assigning dynamic ips). Dnsmasq will discard world # requests to them, but the paranoid might like to close them and let the # kernel handle them: bind-interfaces # Dynamic range of IPs to make available to LAN pc dhcp-range=192.168.1.200,192.168.1.210,infinite # If you'd like to have dnsmasq assign static IPs, bind the LAN computer's # NIC MAC address: dhcp-host=00:25:09:06:24:74,192.168.1.201 dhcp-host=00:25:09:06:30:9D,192.168.1.202 dhcp-host=00:25:09:06:16:C6,192.168.1.203
我们的控制服务器上有两个套接字:一个名为EDUP的用来管理设备的套接口,另一个则扮演着一个用以接受命令的控制接口。
为了提高效率,我们需要集成多线程。接着我们将分离这个设备,再然后我们将编写一个SmartSocket类。
所有的 SmartSocket对象都应该有一个描述属性(IP,MAC,命令载荷,一段简单的描述等)以及相关的状态信息(是否在线?是开着的还是关着的?)
同时我们还需要将真是对象与虚拟进行连接,所以每次我们都通过accept()函数通过套接口连接到构造的 SmartSocket函数。
当客户端建立一个新的连接,服务端会创建一个新的SmartSocket对象(IP地址作为参数)。这个构造函数会查询sqlite3数据库并检索客户端属性。
CREATE TABLE devices ( id INTEGER, desc TEXT, online TEXT, estado TEXT); CREATE TABLE parameters( id integer, macAddr text, ipAddr text, heloCMD text, onCMD text, offCMD text, keepAliveCMD text, FOREIGN KEY(id) REFERENCES devices(id) );
现在构造函数就能够分辨出设备了,接着开始登录阶段
# Start talking # Send HELO first self.socket.send(self.heloCMD) # Check if received MAC address matches our own data = ByteToHex(self.socket.recv(30)) if data[22:34] == self.macAddr: pass # Discard next packet data = self.socket.recv(30) # -- Connection is now established --
在写完上面的几行代码后,我突然意识一个检测获得的MAC地址是否正确的好方法。这个就作为一个作业留给大家了。
目前SmartSocket类有下面这些方法:
def getID(self): return str(self.id) def on(self): self.socket.send(self.onCMD) data = self.socket.recv(30) self.status = 1 def off(self): self.socket.send(self.offCMD) data = self.socket.recv(30) self.status = 0 def sendKeepAlive(self): self.socket.send(self.keepAliveCMD) def close(self): self.socket.close() def showInfo(self): return "Id: {} - {} - Status: {}".format(self.id, self.description, self.status)
实际上,控制连接支持3种命令:
LIST:显示在线设备以及设备的实际状态。
ON <device id>: 打开设备
OFF <device id>: 关闭设备
每隔30秒我们就ping客户端一次,以保持其活跃。当检测到一个新的用户就启用另一个线程。
try: while 1: conn, addr = socketEDUP.accept() print '[!] Incoming SmartSocket from ' + addr[0] + ':' + str(addr[1]) # Create new SmartSocket object from incoming connection connectedDevice = SmartSocket(conn,addr[0]) # Add it to the devices dictionary devicesDict[connectedDevice.getID()] = connectedDevice # Start a new thread to send keep alives every 30 secs start_new_thread(sendKA, (connectedDevice,)) except KeyboardInterrupt: QUIT = 1 socketControl.close() socketEDUP.close()
(…)
def sendKA(smartSocket): while QUIT <> 1: time.sleep(30) smartSocket.sendKeepAlive() smartSocket.close()
完整代码并不长(发布时有207行),完整代码可参考(https://github.com/n0w/edupServer)
整个项目还没有完成。
视频演示
https://www.youtube.com/watch?v=sCR2lIMFu1s[小水管,每次上传都出错。]
精彩预告
在下一篇文章中,我们将讨论如何将此服务加入家庭自动化系统中(比如OpenHAB或者Domoticz)