Реверсим сканер отпечатков и частично запускаем его в Linux

в 14:49, , рубрики: drivers, fingerprint readers, linux, linux kernel, велосипед, информационная безопасность, Программирование, реверсинг железа, сканер отпечатков пальцев

В прошлой статье я писал, как в линуксе написать драйвер для графического планшета. Но на том история не закончилась: вместе с планшетом я купил и ноутбук, на котором установлен сканер отпечатков. Сканер новый, такой, как повсеместно стоит на мобильных телефонах, маленькая прямоугольная площадка на тачпаде.
Драйвера только для Windows 10, и только на сайте производителя ноутбука. Сразу возникла мысль если и не завести его в линуксе, то хотя бы понять, как же он работает. Висит сканер на usb, вместе с тачпадом.

Для начала сделаем lsusb и посмотрим, как определяется наш сканер:
Bus 001 Device 006: ID 04f3:0c03 Elan Microelectronics Corp.

Смотрим более подробно информацию о нем.

Bus 001 Device 006: ID 04f3:0c03 Elan Microelectronics Corp.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x04f3 Elan Microelectronics Corp.
idProduct 0x0c03
bcdDevice 1.38
iManufacturer 1 ELAN
iProduct 2 ELAN:Fingerprint
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 62
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 5
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
** UNRECOGNIZED: 09 21 10 01 00 01 22 15 00
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x03 EP 3 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
can't get device qualifier: Resource temporarily unavailable
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0000
(Bus Powered)

Тут нас ждет первое разочарование: сканер работает через bulk-transfer. Если в HID формат данных определен в дескрипторе, то тут — тёмный лес, можно пихать всё, что придет в голову. Что делать?
Перезагружаюсь в Windows 10, ставлю Wireshark, запускаю запись всего трафика USB. Дальше в настройках Windows иду в меню, где устанавливаются отпечатки для входа в систему. Регистрирую один палец, выхожу. Выключаю запись в Wireshark, сохраняю полученный дамп. Снова перезагружаюсь в Linux.
Снова запускаю Wireshark, открываю дамп, фильтрую по нужным vid/pid, нахожу искомый сканер.
Среди всей кучи явно выделяются отдельные «жирные» пакеты, больше 16 килобайт:
Реверсим сканер отпечатков и частично запускаем его в Linux - 1

Смотрю, что там. На первый взгляд мусор, ничего определенного. Сохранил данные такого пакета отдельным файлом, 18432 байта, сделал xxd ./image.raw, увидел примерно такое:

0000000: 4123 d328 4828 8d28 aa28 6028 5f29 a528
0000010: f028 1127 ef26 ff27 8129 2628 9d23 9621
0000020: 1d21 d126 db26 aa26 b128 2326 8826 d027
0000030: a226 8925 2120 af1c fa1c 341b b31f 1122
0000040: 1e27 8526 5026 ac26 4f26 fa26 1126 9225
...

Тут сразу в глаза бросились некие чередующиеся пары байт, это очень неспроста. Было сделано предположение, что этот блок не что иное, как 16-битная квадратная картинка, поскольку старший байт случаен, а вот младший плавает совсем немного — весьма характерно для однотонного изображения, коим является отпечаток. Догадку подтвердил расчет, sqrt(18432bytes / (16bit /8)) = 96. Квадратная картинка со стороной 96 пикселей?

Пробуем перегнать равку в изображение:

convert -depth 16 -size 96x96+0 gray:image.raw ./out.png

Успех с первого раза! На выходе получаем картинку, содержащую полноценный отпечаток. Я раньше думал, что аналитика уже встроена в современные сенсоры, и отпечатки его не покидают, вылезает некий хеш… Но нет. Вылезает отпечаток, причем в довольно неплохом качестве. Постить выхлоп сюда не буду по понятным причинам.

Теперь надо это дело автоматизировать, чтобы до конца понять, как же оно работает. Для этого нам будет нужна libusb.
Покопавшись в дампе трафика, я понял, что для того, чтобы забрать скан пальца, надо отправить 0х0009 на endpoint 0x82. Однако тут оказалась засада — сканер отдавал картинку сразу, не ожидая пальца на себе. Ну, черные квадраты такие.
Пришлось ковырять трафик дальше. Выяснилось, что перед получением картинки надо послать 0x403f на endpoint 0x83 и ждать один байт от сканера. Когда сканер выплюнет 0х55, можно запускать сканирование, палец в этот момент на сканере.

Реверсим сканер отпечатков и частично запускаем его в Linux - 2

Делаем шаблонный Makefile

CC	= gcc
LD	= gcc
OPT 	= -O1 -s 
CFLAGS 	= $(OPT) -c -I/usr/include/libusb-1.0
LDFLAGS	= -DPTW32_STATIC_LIB $(OPT)
LIBS	= -lrt -lusb-1.0

all:
	$(CC) $(CFLAGS) elanfp.c -o elanfp.o
	$(LD) $(LDFLAGS) elanfp.o $(LIBS) -o elanfp

clean: 
	rm -f *.o elanfp

И вот такой вот файлик elanfp.c.

#include <stdio.h>    
#include <stdlib.h>    
#include <sys/types.h> 
#include <errno.h>
#include <string.h>
#include <libusb.h> 

#define BULK_EP_OUT1     0x82   
#define BULK_EP_OUT2     0x83
#define BULK_EP_IN       0x01

#define IMAGE_PACKET_SIZE (96 * 96 * 2)
char img_buf[IMAGE_PACKET_SIZE];

char cmd01[2] = { 0x40, 0x3f };
char cmd02[2] = { 0x00, 0x09 };

int write_to_file(char* buf, int size) {
    FILE *fp;
    fp = fopen("./out.fp","w");
    if (fp < 0) return 1;
    fwrite(buf, 1, size, fp);
    fclose(fp);
    return 0;
}

int main() {
    int r0, i, devs_count = 0, libusb_config = 0, transferred = 0;
    
    struct libusb_device **devs;
    struct libusb_device *dev;
    struct libusb_device_descriptor desc;
    struct libusb_device_handle *handle = NULL;
    
    r0 = libusb_init(NULL);
    if (r0 < 0) return 1;
    
    devs_count = libusb_get_device_list(NULL, &devs);
    if (devs_count < 0) {
        libusb_exit(NULL);
        printf("Error %dn", r0);
        return 2;
    }
    
    while (1) {
        dev = devs[i];
        if (dev != NULL) {
            r0 = libusb_get_device_descriptor(dev, &desc);
            if (r0 < 0) continue;

            if ((desc.idVendor == 0x04f3) && (desc.idProduct == 0x0c03)) {
                r0 = 0;
                printf("Device with vid %x pid %x found.n", desc.idVendor, desc.idProduct);
                break;
            }
        } else {
            r0 = 3;
            break;
        }
        
        i++;
        if (i > devs_count) {
            r0 = 99;
            goto app_exit;
        }
    }
    
    if (r0 != 0) goto app_exit;
    
    /*****************************************************/
    
    r0 = libusb_open(dev, &handle);
    if (r0 < 0) {
        r0 = 5;
        goto app_exit;
    }
    
    r0 = libusb_get_configuration(handle, &libusb_config);
    if (r0 != 0) {
        r0 = 98;
        goto app_exit_2;
    }
    
    printf("Config number is %dn", libusb_config);
    
    if (libusb_config > 1) {
        r0 = libusb_set_configuration(handle, 1);
        if (r0 != 0) {
            r0 = 97;
            goto app_exit_2;
        }
        printf("Config set to 1n");
    }
    
    if (libusb_kernel_driver_active(handle, 0) == 1) {
        if(libusb_detach_kernel_driver(handle, 0) != 0) {
            r0 = 96;
            goto app_exit_2;
        }
    }
    
    r0 = libusb_claim_interface(handle, 0);
    if (r0 != 0) {
        r0 = 95;
        goto app_exit_2;
    }
    
    r0 = libusb_bulk_transfer(handle, BULK_EP_IN, cmd01, 2, &transferred, 0);
    if((r0 == 0) && (transferred == 2)) {
        printf("CMD1 sentn");
    }
    
    r0 = libusb_bulk_transfer(handle, BULK_EP_OUT2, img_buf, 1, &transferred, 0); // wait for press
    printf("Received byte %dn", img_buf[0]);
    
    r0 = libusb_bulk_transfer(handle, BULK_EP_IN, cmd02, 2, &transferred, 0);
    if((r0 == 0) && (transferred == 2)) {
        printf("CMD2 sentn");
    }
    
    r0 = libusb_bulk_transfer(handle, BULK_EP_OUT1, img_buf, IMAGE_PACKET_SIZE, &transferred, 0);
    printf("Received %dn", transferred);
    write_to_file(img_buf, IMAGE_PACKET_SIZE);
    
    system("convert -depth 16 -size 96x96+0 gray:out.fp ./out.png");
    system("rm ./out.fp");
    
app_exit_2:
    libusb_close(handle); 
    
    /*****************************************************/
    
app_exit:
    libusb_free_device_list(devs, 1);
    libusb_exit(NULL);
    
    if (r0 != 0) {
        printf("Error %dn", r0);
    }
    
    return r0;
}

Код работы с libusb шаблонный, взятый частично из мануала, частично с SO. Собираем, запускаем, радуемся картинке с отпечатком в папке с кодом.

Дальше, когда тестовый код был закончен, я закинул его в список рассылки libfprint — к сожалению, многие линуксовые проекты недокументированны, и разбираться, как добавить свой код — тот еще квест. Просто нет столько времени.
Так что буду надеятся, что авторы добавят этот код к проекту.

Вообще, несколько удивило то, что отпечатки приходят просто картинкой и обрабатываются в юзерспейсе. Безопасность? Нет, не слышали. На мой взгляд, отпечатки не должны покидать сканер, к системе должен вылетать некий хеш, уникальный для сканера, но никак не сырая картинка. Да, отпечатки публичны, но одно дело собирать их по одному в реальном мире, и совершенно другое — найти уязвимость в ПО и сливать готовые отпечатки миллионами…

Автор: Илья

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js