Windows/macOS Keylogger

JunTak Lee·2023년 5월 17일
0

몇달전에 Raw input을 사용해야하는 일이 있었다
단순한 문제라고 생각했던 나는 관련 라이브러리를 찾아보았으나, 내 목적에 부합하는 건 존재하지 않았다
그래서 그냥 열심히 구글링해가며 새로 만들었다

당시 나에게 필요했던 요구사항은 두가지이다

  • 윈도우와 맥, 두 운영체제에서 실행이 가능해야 할 것
  • C++로 작성된 코드여야할 것

Python의 경우 라이브러리가 존재하기에 손쉽게 설치할 수 있었다
하지만 C/C++의 경우 각 운영체제별 코드는 존재하였지만, 멀티 플랫폼을 위한 코드는 존재하지 않았다
따라서 각 운영체제별 코드를 작성하고, 매크로로 한쪽을 비활성화시키는 방향으로 진행하였다


Windows

윈도우의 경우, 기본적으로 hook과 message 객체를 사용한다
방식은 간단하다

  • WindowHookApi에 Callback 함수 등록
  • Message를 프로세스에서 계속 후킹함으로써 이벤트 발생시 프로세스와 연결 callback 함수 호출

https://learn.microsoft.com/ko-kr/windows/win32/winmsg/about-hooks
https://learn.microsoft.com/ko-kr/windows/win32/learnwin32/window-messages

위 방식을 코드로 구현하기 위해서는 loop과 callback, 두가지 코드가 필요하다
우선 loop의 경우, 아래와 같은 순서로 동작한다

  1. WindowsHookApi에 callback 함수 등록
  2. Hook이 제대로 생성되었는가 확인
  3. 프로그램이 끝날때까지 메세지를 receive, translate, dispatch 단계 반복
  4. 프로그램 끝날때 unhook
void start_loop() {
    hook = SetWindowsHookEx(WH_KEYBOARD_LL, &callback_win32, NULL, 0);
    if (!hook) {
        std::cout << "Failed to create hook : " << GetLastError();
        return;
    }
    while (!GetMessage(&message, NULL, 0, 0) && running) {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    UnhookWindowsHookEx(hook);
}

callback 함수는 다음과 같다

LRESULT CALLBACK callback_win32(int c, WPARAM w, LPARAM l) {
    if (c != HC_ACTION) return CallNextHookEx(NULL, c, w, l);
    auto key = ((PKBDLLHOOKSTRUCT)l)->vkCode;
    if (w == WM_KEYDOWN) {
        // 키 눌림...
    }
    else if (w == WM_KEYUP) {
        // 키 올라감...
    }
    
    return CallNextHookEx(NULL, c, w, l);
}


macOS

macOS는 Xcode 프로젝트에 'ApplicationService.framework'을 추가해줘야 한다
여기에서 EventTap이라는 객체를 사용할 수 있게되고, 이로써 event 발생시 callback을 받을 수 있다
https://www.kuniga.me/blog/2021/11/16/a-simple-key-logger.html

macOS도 Windows와 동일하게 loop과 callback 두가지 함수가 필요하다
우선 loop의 경우 다음과 같다

void start_loop() {
    CFMachPortRef event = CGEventTapCreate(kCGSessionEventTap,
                                         kCGHeadInsertEventTap,
                                         kCGEventTapOptionDefault,
                                         CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp),
                                         &callback_darwin,
                                         NULL);
    
    if (!event) {
        fprintf(stderr, "Failed to create event tap\n");
        exit(1);
    }
    
    loop = CFRunLoopGetCurrent();
    CFRunLoopSourceRef loop_src = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event, 0);
    CFRunLoopAddSource(loop, loop_src, kCFRunLoopCommonModes);
    CGEventTapEnable(event, true);
    CFRunLoopRun();
}

이때 윈도우와 차이점이라면, CFRunLoopRun 함수의 경우 자체적으로 loop을 실행한다는 것이다
즉, 윈도우는 while문을 통해 message를 받아와야 했다면, macOS는 위 함수만 실행함으로써 loop을 돌게 된다

callback은 다음과 같다

CGEventRef callback_darwin(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void*) {
    if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp)) return event;
    CGKeyCode key_code = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
    if (type == kCGEventKeyDown) {
        // 키 눌림...
    }
    else if (type == kCGEventKeyUp) {
        // 키 올라감...
    }
    return event;
}

만들어 놓은게 아까워서 좀더 다듬고 Github에도 올려놓았다
https://github.com/ross1573/key_logger

profile
하고싶은거 하는 사람

0개의 댓글