unbit / foohid

OSX IOKit driver for implementing virtual HID devices (joypads, keyboards, mices, ...) from userspace
MIT License
264 stars 35 forks source link

Extra keycodes dispatched in keyboard.c #12

Closed irtemed88 closed 7 years ago

irtemed88 commented 7 years ago

Hi! Firstly, thanks so much for creating this project. I've been working off of the keyboard.c example and encountered a behavior I can't seem to explain and was wondering if it's a fundamental misunderstanding on my part or possibly something else.

I have a modified keyboard.c that takes an arbitrary keycode via the method stroke(uint_8 keycode). From my app I call this method with the desired keycode (in this case 0x04) followed by 0x00 which I assume acts as a "key up" event (I assume that because the original sample alternated between 0x04 and 0x00)? What I've noticed is while this sometimes works, I get into a state where an unknown keycode is firing nonstop. If TextEdit is in the foreground the character input is the number '8'. Also, the volume on my computer gets turned up all the way and cannot be turned back down until I restart the hid daemon.

I feel like I'm missing something here, but haven't been able to figure out what exactly that is through experimentation. I'm sure you're super busy, but if you could offer some feedback on what I've done wrong with my modifications or point me in the direction of documentation that would help clear up my misunderstanding I'd appreciate it.

I'm running macOS 10.12.5. I've wired up a button in my app that makes the following c calls every time it is clicked. If you'd like to see the full project I'd be happy to zip that up and include it here as well: stroke(0x04); stroke(0x00);

Here's my modified keyboard.c. The main difference is I broke out what I think is the one-time setup logic from the keystroke logic and made the keystroke a variable.

#include <IOKit/IOKitLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char report_descriptor[] = {
     0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x01,                    //   INPUT (Cnst,Ary,Abs)
    0x95, 0x05,                    //   REPORT_COUNT (5)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x05, 0x08,                    //   USAGE_PAGE (LEDs)
    0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
    0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x03,                    //   REPORT_SIZE (3)
    0x91, 0x01,                    //   OUTPUT (Cnst,Ary,Abs)
    0x95, 0x06,                    //   REPORT_COUNT (6)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0x09, 0x00,                    //   USAGE (Reserved (no event indicated))
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0xb1, 0x02,                    //   FEATURE (Data,Var,Abs)
    0xc0                           // END_COLLECTION
};

struct keyboard_report_t {
    uint8_t modifier;
    uint8_t reserved;
    uint8_t key_codes[6];
    uint8_t reserved_bis;
};

#define SERVICE_NAME "it_unbit_foohid"

#define FOOHID_CREATE 0  // create selector
#define FOOHID_SEND 2  // send selector

#define DEVICE_NAME "Foohid Virtual KB"
#define DEVICE_SN "SN 123456"

io_connect_t service_connection = 0;
uint64_t * input;

bool setupVirtualHID() {
    io_iterator_t iterator;
    io_service_t service;

    // Get a reference to the IOService
    kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(SERVICE_NAME), &iterator);

    if (ret != KERN_SUCCESS) {
        printf("Unable to access IOService.\n");
        return false;
    }

    // Iterate till success
    int found = 0;
    while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
        ret = IOServiceOpen(service, mach_task_self(), 0, &service_connection);

        if (ret == KERN_SUCCESS) {
            found = 1;
            break;
        }

        IOObjectRelease(service);
    }
    IOObjectRelease(iterator);

    if (!found) {
        printf("Unable to open IOService.\n");
        return false;
    }

    // Fill up the input arguments.
    uint32_t input_count = 8;
    input = calloc(input_count, sizeof(uint64_t));
    input[0] = (uint64_t) strdup(DEVICE_NAME);  // device name
    input[1] = strlen((char *)input[0]);  // name length
    input[2] = (uint64_t) report_descriptor;  // report descriptor
    input[3] = sizeof(report_descriptor);  // report descriptor len
    input[4] = (uint64_t) strdup(DEVICE_SN);  // serial number
    input[5] = strlen((char *)input[4]);  // serial number len
    input[6] = (uint64_t) 2;  // vendor ID
    input[7] = (uint64_t) 3;  // device ID

    ret = IOConnectCallScalarMethod(service_connection, FOOHID_CREATE, input, input_count, NULL, 0);
    if (ret != KERN_SUCCESS) {
        printf("Unable to create HID device. May be fine if created previously.\n");
    }

    return true;

}

void stroke(uint8_t key_code) {
    // Arguments to be passed through the HID message.
    struct keyboard_report_t keyboard;
    uint32_t send_count = 4;
    uint64_t send[send_count];

    send[0] = (uint64_t)input[0];  // device name
    send[1] = strlen((char *)input[0]);  // name length
    send[2] = (uint64_t) &keyboard;  // keyboard struct
    send[3] = sizeof(struct keyboard_report_t);  // keyboard struct len

    keyboard.modifier = 0;
    keyboard.key_codes[0] = key_code;

    kern_return_t ret = IOConnectCallScalarMethod(service_connection, FOOHID_SEND, send, send_count, NULL, 0);
    if (ret != KERN_SUCCESS) {
        printf("Unable to send message to HID device.\n");
    }
}
dwang733 commented 7 years ago

I think I've encountered this bug before, and I remember uninstalling and reinstalling the kext fixed it. Take a look at issue #4 to see how to uninstall it.

irtemed88 commented 7 years ago

Hey @dwang733 -- Thanks for the suggestion. I uninstalled and reinstalled the latest release from the 'releases' section and still have the issue. Would it help if I uploaded my sample project?

dwang733 commented 7 years ago

Hmm...did you restart your computer between uninstallation and reinstallation? If not, try doing that first. If that also doesn't work, you can try sending the project and I'll see what I can do.

irtemed88 commented 7 years ago

Ah yeah, I restarted between uninstall and reinstall just to be sure. I've attached the project to this ticket. When you run the project a window should appear with a single button. Clicking that will trigger the keystroke sequence I mentioned above after a 2 second delay. The delay is so I had enough time to switch to TextEdit and see in the keystrokes come in (I'm sure there's a more elegant means of testing, but this was the first thing that came to mind).

Typically clicking the button 3 or 4 times quickly will reproduce the error. Once it occurs the only way I could get the keyboard back into a stable state was to kill the app and force-quit 'hidd' from Activity Monitor (it'll restart as soon as you kill it).

Oh also, I'd recommend not listening to music while testing. For some reason this issue causes the volume on my mac to turn all the way up...

foohid_swift.zip

dwang733 commented 7 years ago

I think I fixed it by mallocing the keyboard on the heap rather than the stack. Try using this code for the stroke method:

struct keyboard_report_t* keyboard = malloc(sizeof(struct keyboard_report_t*));

uint32_t send_count = 4;
uint64_t send[send_count];
send[0] = (uint64_t)strdup(DEVICE_NAME);  // device name
send[1] = strlen((char *)send[0]);  // name length
send[2] = (uint64_t) keyboard;  // keyboard struct
send[3] = sizeof(struct keyboard_report_t);  // keyboard struct len

keyboard->modifier = 0;
keyboard->key_codes[0] = key_code;

kern_return_t ret = IOConnectCallScalarMethod(service_connection, FOOHID_SEND, send, send_count, NULL, 0);
if (ret != KERN_SUCCESS) {
    printf("Unable to send message to HID device.\n");
}
irtemed88 commented 7 years ago

That did it. Thanks so much! Any idea why the change was necessary?

dwang733 commented 7 years ago

To be honest, I'm not completely sure why it works. I know that with the old code, if you call stroke() multiple times, there's sometimes random values initialized for keyboard rather than all 0's, so I just allocated new memory for it. Also, before I forget, add this line at the end of the stroke() method:

free(keyboard);

Don't want any memory leaks :)

irtemed88 commented 7 years ago

Oh interesting. I actually just tried changing the struct initialization to struct keyboard_report_t keyboard = {0}; and that seems to work too.

I guess if the struct was getting random values the rest of the entries in key_codes could be causing the unknown inputs. That also explains why there would be continuous keydown events since the random inputs would never be released.