Esempio n. 1
0
class HookingMacroWin32:
    def __init__(self, log_path="c:\\development\\keymouselog.txt"):
        #####
        # initialize classes
        self.append_queue = queue.Queue()
        self.log_path = log_path
        self.text_thread = TextThread()
        self.hooking_thread = HookingThread(log_path=log_path,
                                            send_queue=self.append_queue)
        self.replay_thread = ReplayThread(log_path=log_path,
                                          send_queue=self.append_queue)
        #####
        # set append thread
        self.ender = False
        self.append_thread = threading.Thread(target=self.append_manager,
                                              daemon=False,
                                              name='AppendThread')
        # set keyinput thread
        self.user32 = WinDLL('user32',
                             use_last_error=True)  # user32 = windll.user32
        self.kernel32 = windll.kernel32
        self.keychk_thread = threading.Thread(target=self._keychk_runner,
                                              daemon=False,
                                              name="KeychkThread")
        #####
        # class flag setting
        self._ismain = False
        self.hookstate = {'key': True, 'mouse': False}

    def start_main(self):
        if not self.text_thread.is_alive():  # text_window 실행 및 설정
            # 창 실행
            self.text_thread.start_window(
            )  # start로 하면 _txwin에 대한 접근오류 날 수 있다. start_window로 실행할 것.
            # 창 설정
            self.text_thread.update_window_attribute(
                title="Keyboard and Mouse Hooker - python 3.6")  # 창 title
            self.text_thread.update_window_attribute(
                btn2="Hook")  # button2 setting
            # 버튼 실행
            self.text_thread.set_button_action(
                btn0=lambda: self._push_open_button())  # 버튼0 실행
            self.text_thread.set_button_action(
                btn1=lambda: self._start_replay_thread())  # 버튼1 실행
            self.text_thread.set_button_action(
                btn2=lambda: self._start_hooking_thread())  # 버튼2 실행
            self.text_thread.set_button_action(
                btn3=lambda: self._stop_sub())  # 버튼3 실행
            self.text_thread.set_button_action(
                btn4=lambda: self.stop_main())  # 버튼4 실행
            self.text_thread.set_button_action(
                xbtn=lambda: self.stop_main())  # x 버튼 실행 설정
        """
        if not self.append_hook_thread.is_alive():  # text_window에 값을 전달하는 append thread
            self.append_hook_thread.start()
        if not self.append_replay_thread.is_alive():  # text_window에 값을 전달하는 append thread
            self.append_replay_thread.start()
            """
        if not self.append_thread.is_alive():
            self.append_thread.start()
        if not self.keychk_thread.is_alive():  # 입력된 키값을 모니터링하는 key_thread 시작
            self.keychk_thread.start()
        self._ismain = True
        # 시작 메시지 welcome message
        self.text_thread.append(
            "--------------------------------------------------------------------\n",
            " Hello! This is hooking and Replaying Program.\n",
            " - If you want to REPLAY HOOKED ACTION, press REPLAY button or 'F2'\n",
            " - If you want to HOOK, press HOOK button or 'F3'\n",
            " - When you wanna STOP YOUR ACTION, press STOP button or 'ESC'\n",
            " - If you want open ANOTHER MACRO FILE, press OPEN button or 'F5'\n",
            " - Change Keyboard and Mouse Hooking State, press 'F6'\n",
            " - If you need to DISABLE SHOW HOOKED KEY, press 'F7'\n"
            "           (it is usefull to speed up hooking process)\n",
            " - If you want to press key in DIRECTX GAME,\n"
            "       switch from keyboard event to directX input by press 'F8'\n"
            "--------------------------------------------------------------------\n",
        )
        self.text_thread.append("default log path : ", self.log_path)
        self.text_thread.append(
            "        keypress option : ",
            self.replay_thread.key_input_type.replace('_', ' '))
        self.text_thread.append(
            "        show hooked key option : ",
            'able' if self.hooking_thread.action_visible else 'disable')

    def stop_main(self):
        self.text_thread.destroy()  # 창 정지
        self.hooking_thread.stop_hookThread()  # 후커 정지
        self.replay_thread.stop_replay()  # 리플레이 정지
        self.ender = True  # append 정지
        self._uninstall_keychk()  # keychk uninstall 및 정지
        self._ismain = False  # main 종료 표시
        import os
        os._exit(1)  # append thread를 정상적으로 종료할 수 없어서 사용하는 임시방편

    def is_main(self):
        return self._ismain

    def _stop_sub(self):
        if self.hooking_thread.ishookThread():
            self.hooking_thread.stop_hookThread()
        if self.replay_thread.is_replay():
            self.replay_thread.stop_replay()
        return

    def _start_hooking_thread(self):
        if self.hooking_thread.ishookThread():
            return
        elif self.replay_thread.is_replay():
            self.text_thread.append("Relplayer is alive")
        else:
            self.hooking_thread.start_hookThread(log_path=log_path)

    def _start_replay_thread(self):
        if self.replay_thread.is_replay():
            return
        elif self.hooking_thread.ishookThread():
            self.text_thread.append("Hook is alive")
        else:
            self.replay_thread.start_replay(log_path=log_path)

    def _push_open_button(self):
        filename: str = self.text_thread.open_file()
        filename = filename.replace("/", u"\\")
        if filename == "":
            pass
        else:
            self.log_path = filename
        self.text_thread.append("set new log_path : ", self.log_path)

    ###############################

    def append_manager(self):
        while True:
            try:
                get_queue = self.append_queue.get(block=True, timeout=5)
                args, kwargs = get_queue
                # print(*args, **kwargs)
                if self.text_thread.is_alive():
                    self.text_thread.append(*args, *kwargs)
            except queue.Empty as e:
                print(e.args)

    ###############################

    def _keychk_proc(self, nCode, wParam,
                     lParam):  # replay중 runner를 종료시키기 위한 프로시져
        if wParam is win32con.WM_KEYDOWN:  # keydown 처리
            if VK_CODE['esc'] == int(lParam[0]):
                self.text_thread.append("ESC inputed")
                self._stop_sub()
            elif VK_CODE['F2'] == int(lParam[0]):
                # self.text_thread.append("F2 inputed")
                self._start_replay_thread()
            elif VK_CODE['F3'] == int(lParam[0]):
                # self.text_thread.append("F3 inputed")
                # time.sleep(0.5)  # to not press 'f3' keyup.
                self._start_hooking_thread()
            elif VK_CODE['F5'] == int(lParam[0]):
                # self.text_thread.append('F5 inputed')
                self._push_open_button()
            elif VK_CODE['F6'] == int(lParam[0]):
                if not self.hookstate['key']:
                    self.hookstate['key'] = True
                    self.hookstate['mouse'] = True
                    self.text_thread.append('keyboard hooking only')
                elif not self.hookstate['mouse']:
                    self.hookstate['key'] = False
                    self.hookstate['mouse'] = True
                    self.text_thread.append('mouse hooking only')
                else:
                    self.hookstate['key'] = True
                    self.hookstate['mouse'] = False
                    self.text_thread.append('both keyboard and mouse hooking')
            elif VK_CODE['F7'] == int(lParam[0]):
                # self.text_thread.append("F7 inputed")
                if self.hooking_thread.action_visible:
                    self.hooking_thread.action_visible = False
                    self.text_thread.append(
                        '"hooked command view" is disabled')
                else:
                    self.hooking_thread.action_visible = True
                    self.text_thread.append('"hooked command view" is abled')
            elif VK_CODE['F8'] == int(lParam[0]):
                if self.replay_thread.key_input_type == 'event_input':
                    self.replay_thread.key_input_type = "directx_input"
                    self.text_thread.append('switch to "DirectX Event" input')
                else:
                    self.replay_thread.key_input_type = 'event_input'
                    self.text_thread.append('switch to "Keyboard Event" input')
        # elif wParam is win32con.WM_KEYUP:  # keydup 처리
        #####
        return self.user32.CallNextHookEx(None, nCode, wParam, lParam)

    def _keychk_runner(self):
        cmp_func = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
        pointer = cmp_func(self._keychk_proc)
        self.hooked = self.user32.SetWindowsHookExA(  # 키보드 후커
            win32con.WH_KEYBOARD_LL,  # 후킹 타입. 13 0xD
            pointer,  # 후킹 프로시저
            self.kernel32.GetModuleHandleA(None),  # 앞서 지정한 후킹 프로시저가 있는 핸들
            0  # thread id. 0일 경우 글로벌로 전체를 후킹한다.
        )  # hook 인스톨
        msg = MSG()
        b = self.user32.GetMessageW(byref(msg), None, 0,
                                    0)  # 메시지 받기 시작. byref가 있어야 에러 발생 안함.

    def _uninstall_keychk(self):
        if self.hooked is not None:
            self.user32.UnhookWindowsHookEx(self.hooked)
            self.hooked = None
        if self.keychk_thread.is_alive():
            win32gui.PostThreadMessage(self.keychk_thread.ident,
                                       win32con.WM_CLOSE, 0,
                                       0)  # WM_QUIT 말고 WM_CLOSE로.
Esempio n. 2
0
class HookingThread:
    __slots__ = (
        'hooked',
        'hooked2',
        'user32',
        'kernel32',
        'hookThread',
        'hookThread_name',
        'starttime',
        'endtime',
        'stateget',
        'log_path',
        'keymouselog',
        'send_queue',
        'action_visible',
    )  # 속도상승을 위한 slots 설정

    def __init__(self,
                 log_path="C:/development\\KeyMouseLog.txt",
                 send_queue: queue.Queue = None):
        # 후킹준비하기
        self.hooked, self.hooked2 = None, None  # 키보드 후킹 핸들러 전달용, 마우스 후킹 핸들러 전달용
        self.user32 = WinDLL('user32',
                             use_last_error=True)  # user32 = windll.user32
        self.kernel32 = windll.kernel32
        # 쓰레드 준비하기
        self.hookThread, self.hookThread_name = None, None
        # 로그파일 경로
        self.log_path = log_path
        self.keymouselog = []
        self.stateget = []
        self.starttime = 0
        # 창에 append 전달용 queue
        self.send_queue = send_queue
        self.action_visible = True

    def print1(self, *args, **kwargs):
        # print("append queue")
        # print(*args, **kwargs)
        self.send_queue.put((args, kwargs))

    def _getFPTR(self, fn):  # 포인터로 만들어줌.
        CMPFUNC = CFUNCTYPE(c_int, c_int, c_int,
                            POINTER(c_void_p))  # CMPFUNC 가 포인터로 만들어주는 함수.
        return CMPFUNC(fn)  # 입력받은 fn을 가리키는 포인터 생성

    def _hookProc(self, nCode, wParam, lParam):  # 키보드 프로시저, 콜백함수로 후킹에 사용되는 부분
        # self.print1("[wParam:", wParam, " lParam[0]:", lParam[0], ",", type(lParam[0]), " : ", nCode, "]")
        # keyup 이벤트 처리
        hooked_key, key_info = "", ""
        if wParam == win32con.WM_KEYUP:  # 257 key_up
            hooked_key = VK_REVERSE_CODE[lParam[0]]
            key_info = "keyup"
        # keydown, syskeydown 이벤트 처리
        elif wParam == win32con.WM_KEYDOWN or wParam == win32con.WM_SYSKEYDOWN:  # 256 key_up # 260 syskey_down
            if VK_CODE['esc'] == int(lParam[0]):  # esc는 기록하지 않는다.
                return self.user32.CallNextHookEx(None, nCode, wParam, lParam)
            hooked_key = VK_REVERSE_CODE[lParam[0]]
            key_info = "keydown"
        # keylog 기록
        dd = [
            'keyboard', hooked_key, key_info,
            str(time.time() - self.starttime)
        ]
        self.keymouselog.append(dd)
        if self.action_visible:
            self.print1(self.keymouselog[-1])  # 창에 가장 최신의 keymouselog 입력정보 출력
        return self.user32.CallNextHookEx(self.hooked, nCode, wParam, lParam)

    def _hookProc2(self, nCode, wParam, lParam):  # 마우스 프로시져
        # self.print1("nCode:", nCode, " wParam:", wParam, "lParam[0]:", lParam[0])
        # MouseButtonDown 처리 및 mouselog 기록
        if wParam == win32con.WM_LBUTTONDOWN or \
                wParam == win32con.WM_RBUTTONDOWN:  # win32con을 따르지 않는다. 작동안함 -> 아님, is와 ==의 차이.
            dd = [
                'mouse', "LBUTTONDOWN"
                if wParam == win32con.WM_LBUTTONDOWN else "RBUTTONDOWN",
                str(lParam[0]) + "|" + str(lParam[1]),
                str(time.time() - self.starttime)
            ]  # lParam[0], lParam[1] 은 각각 x좌표, y좌표이다. 그 이상은 없음.
            self.keymouselog.append(dd)
            if self.action_visible:
                self.print1(
                    self.keymouselog[-1])  # 창에 가장 최신의 keymouselog 입력정보 출력
        # MouseButtonUp 처리 및 mouselog 기록
        elif wParam == win32con.WM_LBUTTONUP or \
                wParam == win32con.WM_RBUTTONUP:
            dd = [
                'mouse', "LBUTTONUP"
                if wParam == win32con.WM_LBUTTONUP else "RBUTTONUP",
                str(lParam[0]) + "|" + str(lParam[1]),
                str(time.time() - self.starttime)
            ]  # lParam[0], lParam[1] 은 각각 x좌표, y좌표이다. 그 이상은 없음.
            self.keymouselog.append(dd)
            if self.action_visible:
                self.print1(
                    self.keymouselog[-1])  # 창에 가장 최신의 keymouselog 입력정보 출력
        return self.user32.CallNextHookEx(self.hooked2, nCode, wParam, lParam)

    def _msg_loop(self, keyboard=True, mouse=True):  # 쓰레드 대상
        if keyboard == True:
            pointer1 = self._getFPTR(self._hookProc)
            self.hooked = self.user32.SetWindowsHookExA(  # 키보드 후커
                win32con.WH_KEYBOARD_LL,  # 후킹 타입. 13 0xD
                pointer1,  # 후킹 프로시저
                self.kernel32.GetModuleHandleA(None),  # 앞서 지정한 후킹 프로시저가 있는 핸들
                0  # thread id. 0일 경우 글로벌로 전체를 후킹한다.
            )  # hook 인스톨
        if mouse == True:
            pointer2 = self._getFPTR(self._hookProc2)
            self.hooked2 = self.user32.SetWindowsHookExA(  # 키보드 후커
                win32con.WH_MOUSE_LL,  # 후킹 타입. 13 0xD
                pointer2,  # 후킹 프로시저
                self.kernel32.GetModuleHandleA(None),  # 앞서 지정한 후킹 프로시저가 있는 핸들
                0  # thread id. 0일 경우 글로벌로 전체를 후킹한다.
            )  # hook2
        if keyboard == False and mouse == False:
            self.print1("no hooker installed")
            return
        self.print1("hooker installed")
        msg = MSG()
        while True:
            bRet = self.user32.GetMessageW(
                byref(msg), None, 0, 0)  # 메시지 받기 시작. byref가 있어야 에러 발생 안함.
            if bRet == 1:
                break
            if bRet == -1:
                # print(bRet, " raise WinError(get_last_error())")
                # raise WinError(get_last_error())
                break
            self.user32.TranslateMessage(byref(msg))
            self.user32.DispatchMessageW(byref(msg))

    def saveKeyMouseLog(self):
        with open(self.log_path, 'w', encoding='UTF-8', newline='') as f:
            keys = ('type', 'input', 'info', 'time')
            cwrite = csv.DictWriter(f, fieldnames=keys)  # DictWriter 사용
            # print(stateget)  # 마지막 네번째 kor/en_key 값이 이상함.
            cwrite.writeheader()
            cwrite.writerow({
                'type': 'start',
                'input': 'state_keys',  # start log 시작
                'info': ''.join(self.stateget),
                'time': str(self.starttime - self.starttime)
            })
            for log in self.keymouselog:
                kk = list(keys)
                tmpdic = {kk.pop(0): x for x in log}
                cwrite.writerow(tmpdic)
            cwrite.writerow({
                'type': 'end',
                'input': '',  # end log 끝
                'info': '',
                'time': str(self.endtime - self.starttime)
            })
            self.keymouselog = []  # log 초기화
            f.close()  # Logging 기록

    def start_hookThread(self,
                         log_path=None,
                         keyboard=True,
                         mouse=True):  # 훅쓰레드 실행.
        state_keys = ['caps_lock', 'num_lock', 'scroll_lock',
                      'kor/en_key']  # state_key 설정, 시작설정
        self.stateget = [
            str(win32api.GetKeyState(VK_CODE[key])) for key in state_keys
        ]  # 키값 받아 0/1로 저장
        self.starttime = time.time()
        if log_path is not None:
            self.log_path = log_path
        if self.hookThread_name is None:  # 생성했으면 참, 이미 있으면 거짓
            self.hookThread = threading.Thread(
                target=self._msg_loop,
                args=(keyboard, mouse),
                daemon=True,
                name="HookingKeyboardMouseThread")  # 키보드/마우스 동시후킹
            self.hookThread.start()  # 쓰레드 시작
            self.hookThread_name = self.hookThread.ident  # 쓰레드 살아있는지 여부 저장 / is_alive는 느릴 수 있음
            return True
        return False

    def _uninstallHooker(self):
        self.endtime = time.time()
        if self.hooked is not None:
            self.user32.UnhookWindowsHookEx(self.hooked)
        if self.hooked2 is not None:  # 이걸 체크하지 않아서 속도가 느려졌던것. 당연히 체크했어야 했다.
            self.user32.UnhookWindowsHookEx(self.hooked2)
        self.hooked, self.hooked2 = None, None
        if self.ishookThread():
            win32gui.PostThreadMessage(self.hookThread.ident,
                                       win32con.WM_CLOSE, 0,
                                       0)  # WM_QUIT 말고 WM_CLOSE로.
        self.print1("hooker uninstalled")
        self.hookThread_name = None  # 쓰레드 죽은걸로 갱신 / is_alive보다 빠르게 정보 접근

    def stop_hookThread(self):  # 클래스내의 메인쓰레드 파괴
        if self.ishookThread():
            self._uninstallHooker(
            )  # 언인스톨 -> WM_CLOSE 메시지 전달 -> GetMessage에서 멈춘 thread 진행 및 종료
            self.saveKeyMouseLog()  # log에 저장 / log는 다 날라가버림
            # self.print1("hooker stopped")
            return True
        return False

    def ishookThread(self):  # 쓰레드 살아있으면 True/False, 쓰레드 생성 자체가 안됐으면 None
        if self.hookThread_name is not None:
            return True
        return False

    def get_log_path(self):  # 키보드, 마우스 입력 로그 저장 경로 반환
        return self.log_path

    def set_log_path(
            self,
            set_log_path):  # 키보드, 마우스 입력 로그 저장 경로 설정 / replay에서 읽는 로그경로도 같이 변경
        self.log_path = set_log_path

    def get_stop_hookThread(self):
        return self.stop_hookThread