起因是这样的,某天刷微博看到果壳网转发了一个很有意思的微博,有网友制作了一个连接到电脑上的按钮,按下后自动运行rm -rf /*

enter image description here

原po没有在微博上解释制作过程和运行原理,不过看着这么有趣的小玩意,不折腾会死星人表示必须自己实现一个!下面就是我制作的按钮。

enter image description here

机械开关

这个按钮在网上很容易买得到,关键字搜索“急停按钮”就可以。我买的这个不超过20块钱。这个按钮是一个机械开关,一共有4个触点,两个一组,共两组,一组为常开开关,一组为常闭开关,可以根据实际情况选择接线。我选择使用的是常闭那一组。

开发板

由于需要把机械开关的物理信号转换为数字信号传给电脑,要使用开发板进行转换。由于这个功能非常初级,所有支持GPIO扩展的开发板都可以。我手上有NodeMCU的ESP8266模块就直接用了,这个模块本身体积小,能够塞进按钮下面的仓盒内方便封装。如果你手上没有开发板,可以去网上买一个,价格也不贵,NodeMCU ESP8266的价格也不超过20块。

enter image description here

电路图

电路图也简单得要死,开关断开时GPIO连到GND,输入低电平,开关闭合时GPIO输入高电平。如果使用NodeMCU ESP8266,VDD使用3.3v,因为这个板子没有5v,其他板子有5v的使用5v就可以。R1选择一个比较大的电阻,避免短路,通常10k左右就可以。没有10k的用个差不多的就行,我手上没有10k的,我选用了一个6.8k的电阻。R2选择小一些的电阻,比如100欧姆的。如果使用NodeMCU ESP8266,GPIO不要使用1、2、15,boot的时候会卡住。我使用的是4,对应的Pin是D2。另外请忽略白板上面的气泡,懒癌患者表示不想处理……

enter image description here

开发板代码

本来当时我选择ESP8266的原因还有一点就是可以连接WiFi。我最开始的想法是将开关信号发到网上,电脑再从互联网拉取数据。不过首先因为ESP8266资源有限,连接HTTPS有很大问题。其次这样会产生明显的延迟,同时还要维护一个互联网服务。所以最后偷懒我直接用串口传数据,所以这也不再是物联网示例了。

那么代码逻辑就是定义Pin模式为INPUT,循环读取GPIO电平状态,将结果输出到串口。下面是Arduino源码:

void setup() {
    Serial.begin(115200);
    pinMode(D2, INPUT);
}

void loop() {
    if (digitalRead(D2) == HIGH)
    {
        Serial.write(1);
    }
    else
    {
        Serial.write(0);
    }
    delay(200);
}

有关Arduino IDE、如何在Arduino IDE配置ESP8266开发板,可以参见https://github.com/esp8266/Arduino

电脑端脚本

电脑端脚本首先要能读取到串口数据。脚本我使用了比较熟悉的Node编写,Node读取串口数据可以使用serialport这个包。读取出来的数据是Buffer类型的,需要先转换为整型。

最后一步就是根据串口数据执行相应命令,比如rm -rf /*。Node有一个很有意思的包叫robot,它可以模拟键盘和鼠标等行为。我们可以使用这个包来模拟输入命令并执行。

下面是完整的电脑端脚本,同样逻辑非常简单:

var SerialPort = require('serialport');
var robot = require("robotjs");

var port = new SerialPort('COM16', { autoOpen: false, baudRate: 115200 });
var status = 0;
var lastStatus = 0;

port.open(function (err) {
    if (err) {
        return console.log('Error opening port: ', err.message);
    }
});

port.on('data', function (data) {
    lastStatus = status;
    status = data.readUIntBE(0, 1);
    if (status !== lastStatus && status === 1) {
        robot.typeString('rm -rf /*');
        robot.keyTap('enter');
    }
});

port.on('readable', function () {
    port.read();
});

脚本里创建SerialPort实例时传入的第一个参数是串口名,Windows下是COM<n>,n的具体数值要到设备管理器中确认。Linux和macOS下第一个参数是/dev/tty*,具体串口路径可以使用ls /dev/tty*查看。

如果你使用的是Windows,同时希望运行Linux命令,比如rm,可以通过安装Git BASH实现。如果你使用的是Windows 10,则可以直接安装Windows Linux Subsystem。

演示

最终我所执行的不是rm -rf /*,因为视频中的不是虚拟机。视频中实际的操作是移除git repo本地除master外的其它分支,这是一个非常有用的操作,但同时也是一项非常危险的操作。

视频地址:http://v.youku.com/v_show/id_XMjk0NjA4OTI5Mg==.html