2020-08-01 11:50:20 +08:00
2020-08-01 11:50:20 +08:00
2020-08-01 11:50:20 +08:00
2020-07-28 16:53:43 +08:00
2020-06-26 10:27:25 +08:00
2020-07-18 13:47:51 +08:00
2020-06-30 18:54:36 +08:00
2020-06-20 17:09:43 +08:00
2020-06-25 18:20:20 +08:00
2020-07-18 13:57:59 +08:00
2020-07-31 17:15:45 +08:00

CcRemote

这是一个基于gh0st远程控制的项目使自己更深入了解远控的原理来编写一款自己的远控(正在编写)项目采用VS2017

这是基于gh0st更改的项目其中加入了大量注释以及思维导图提供帮助代码的框架思想非常值得学习越看越觉得项目得精妙设计。

通讯框架

通讯被控端采用socket主控端采用的是IOCP完成端口,它可以高效地将I/O事件通知给应用程序,能够处理较多连接,处理逻辑我做成了xmind一张图来了解通讯框架

Image text

主界面

Image text

各个功能实现的方法

1 shell控制

shell管理用到匿名管道创建CMD子进程实现进程间通信达到操作控制的目的 管道pipe 用于进程间通讯的一段共享内存。创建管道的进程称为服务器连接到一个管道的进为管道客户机。一个进程在想管道写入数据有另一个进程就可以从瓜岛的另一端将其读取出来。匿名管道Anonymous Pipes 是在父进程和子进程单向传输数据的一种未命名的管道,只能在本地计算机中是同,不能用于网络间的通讯。

如何使用的匿名管道进行通信 匿名管道主要用于父进程与子进程之间的的通信,首先父进程创建匿名管道,创建成功后可以获取这个匿名管道进行读写句柄,然后再创建一个子进程,子进程必须继承和使用父进程的一些公开句柄,创建子进程的时候必须将标准输入、标准输出句柄设置为父进程创建管道的管道句柄,然后就可以进行通讯了。

创建匿名管道
BOOL WINAPI CreatePipe(
          __out   PHANDLE hReadPipe,                        // __out 读取句柄
          __out   PHANDLE hWritePipe,                       // __out 写入句柄
          __in    LPSECURITY_ATTRIBUTES lpPipeAttributes,   // __in SECURITY_ATTRIBUTES结构体指针 加测返回的句柄是否能够被子进程继承为NULL不能继承 匿名管道必须有这个结构体
          __in    DWORD nSize );                            // 缓冲区大小参数为0时使用默认大小
          
typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

lpPipeAttributes指向一个SECURITY_ATTRIBUTES 的结构体指针其检测返回的句柄是否能够被子进程继承如果参数为NULL表明不能被继承
子进程与父进程之间的通信必须构建一个这样的结构体并且该结构体的的第三个成员变量参数必须设置为True
这样子进程才可以进程父进程所创建的匿名管道句柄。
创建子进程
BOOL  CreateProcess( 
        LPCWSTR pszImageName,                   // 指向程序名称以NULL结尾的字符串
        LPCWSTR pszCmdLine,                     // 命令行
        LPSECURITY_ATTRIBUTES psaProcess,       // 创建进程对象设置安全性
        LPSECURITY_ATTRIBUTES psaThread,        // 该进程主线程设置安全性
        BOOL fInheritHandles,                   // *指定父进程创建的子进程是否能够继承父进程对象句柄
        DWORD fdwCreate,                        // 指定控件优先级类和进程创建的附加标记
        LPVOID pvEnvironment,                   // 只想环境块的指针
        LPWSTR pszCurDir,                       // 用来指定子进程当前的路径
        LPSTARTUPINFOW psiStartInfo,            // *指向 StartUpInfo 的结构体的指针,用来指定新进程的主窗口如何显示
        LPPROCESS_INFORMATION pProcInfo );      // ROCESS_INFORMATION 结构体的指针,用来接收关于新进程的标识信息
        
typedef struct _STARTUPINFOA {
    DWORD   cb;
    LPSTR   lpReserved;
    LPSTR   lpDesktop;
    LPSTR   lpTitle;
    DWORD   dwX;
    DWORD   dwY;
    DWORD   dwXSize;
    DWORD   dwYSize;
    DWORD   dwXCountChars;
    DWORD   dwYCountChars;
    DWORD   dwFillAttribute;
    DWORD   dwFlags;
    WORD    wShowWindow;
    WORD    cbReserved2;
    LPBYTE  lpReserved2;
    HANDLE  hStdInput;  // *
    HANDLE  hStdOutput; // *
    HANDLE  hStdError;  // *
} STARTUPINFOA, *LPSTARTUPINFOA;

typedef struct _PROCESS_INFORMATION {
    HANDLE hProcess;
    HANDLE hThread;
    DWORD dwProcessId;
    DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
创建进程时fInheritHandles字段我们需要设置为true继承父进程句柄
LPSTARTUPINFOW psiStartInfo 结构体中进行如下设置
si.wShowWindow = SW_HIDE;                                 //隐藏CMD进程窗口
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; //使用标准输出和标准错误输出句柄 | 控制CMD窗口隐藏
si.hStdInput  = m_hReadPipeShell;                         // 将管道赋值 设置标准输入句柄
si.hStdOutput = si.hStdError = m_hWritePipeShell;         // 将管道赋值 设置标准输出、标准错误句柄

然后通过PeekNamedPipe查询是否有新的数据以及ReadFile进行读取管道中的内容进行读操作WriteFile进行写入管道内容进行操作。 一般是使用while循环配套ReadFile函数。如果控制台程序暂时没有输出并且没有退出ReadFile函数将一直等待导致死循环。所以在使用ReadFile之前加入PeekNamedPipe函数调用。

2 进程监控

进行进程枚举有很多方法
ACreateToolhelp32Snapshot()、Process32First()和Process32Next()
BEnumProcesses()、EnumProcessModules()、GetModuleBaseName()
CNative Api的ZwQuerySystemInformation
Dwtsapi32.dll的WTSOpenServer()、WTSEnumerateProcess()

gh0st使用的最常见的方法A通过建立进程快照进行遍历进程获取信息

HANDLE
WINAPI
CreateToolhelp32Snapshot(
    DWORD dwFlags,      // 用来指定快照中需要返回的对象
    DWORD th32ProcessID // 一个进程ID号为0可获取所有或当前快照
    );

通过函数CreateToolhelp32Snapshot获取的快照句柄使用Process32First、Process32Next遍历所有进程的PROCESSENTRY32信息 再通过GetProcessFullPath获取进程路径等信息。

下面的方法可以获取进程内存列表、模块等信息,不过没有加入到项目中:
获取进程模块信息使用到的API
    HANDLE WINAPI OpenProcess(
  __in          DWORD dwDesiredAccess,      // 打开的标识
  __in          BOOL bInheritHandle,        // 是否继承句柄
  __in          DWORD dwProcessId           // 被打开的进程句柄
);
    //枚举进程里的模块
   BOOL WINAPI EnumProcessModules(
  __in          HANDLE hProcess,            // 进程句柄
  __out         HMODULE* lphModule,         // 返回进程里的模块
  __in          DWORD cb,                   // 模块的个数
  __out         LPDWORD lpcbNeeded          // 存储的模块的空间大小
);  
  //得到模块的名字
  DWORD WINAPI GetModuleFileNameEx(
  __in          HANDLE hProcess,            // 进程的句柄
  __in          HMODULE hModule,            // 模块的句柄
  __out         LPTSTR lpFilename,          // 返回模块的名字
  __in          DWORD nSize                 // 缓冲区大小
);
获取进程所有内存信息:
//枚举指定进程所有内存块
//assert(hProcess != nullptr);
//参数:
//  hProcess:  要枚举的进程,需拥有PROCESS_QUERY_INFORMATION权限
//  memories:  返回枚举到的内存块数组
//返回:
//  成功返回true,失败返回false.
static bool EnumAllMemoryBlocks(HANDLE hProcess, OUT vector<MEMORY_BASIC_INFORMATION>& memories) {
	// 如果 hProcess 为空则结束运行
	assert(hProcess != nullptr);

	// 初始化 vector 容量
	memories.clear();
	memories.reserve(200);

	// 获取 PageSize 和地址粒度
	SYSTEM_INFO sysInfo = { 0 };
	GetSystemInfo(&sysInfo);
	/*
		typedef struct _SYSTEM_INFO {
		  union {
			DWORD dwOemId;							            // 兼容性保留
			struct {
			  WORD wProcessorArchitecture;			    // 操作系统处理器体系结构
			  WORD wReserved;						            // 保留
			} DUMMYSTRUCTNAME;
		  } DUMMYUNIONNAME;
		  DWORD     dwPageSize;						        // 页面大小和页面保护和承诺的粒度
		  LPVOID    lpMinimumApplicationAddress;	// 指向应用程序和dll可访问的最低内存地址的指针
		  LPVOID    lpMaximumApplicationAddress;	// 指向应用程序和dll可访问的最高内存地址的指针
		  DWORD_PTR dwActiveProcessorMask;			  // 处理器掩码
		  DWORD     dwNumberOfProcessors;			    // 当前组中逻辑处理器的数量
		  DWORD     dwProcessorType;				      // 处理器类型,兼容性保留
		  DWORD     dwAllocationGranularity;		  // 虚拟内存的起始地址的粒度
		  WORD      wProcessorLevel;				      // 处理器级别
		  WORD      wProcessorRevision;				    // 处理器修订
		} SYSTEM_INFO, *LPSYSTEM_INFO;
	*/

	//遍历内存
	const char* p = (const char*)sysInfo.lpMinimumApplicationAddress;
	MEMORY_BASIC_INFORMATION  memInfo = { 0 };
	while (p < sysInfo.lpMaximumApplicationAddress) {
		// 获取进程虚拟内存块缓冲区字节数
		size_t size = VirtualQueryEx(
			hProcess,								              // 进程句柄
			p,										                // 要查询内存块的基地址指针
			&memInfo,								              // 接收内存块信息的 MEMORY_BASIC_INFORMATION 对象
			sizeof(MEMORY_BASIC_INFORMATION32)		// 缓冲区大小
		);
		if (size != sizeof(MEMORY_BASIC_INFORMATION32)) { break; }

		// 内存块属性memInfo保存一些内存块信息可以从这里判断获取
		if (memInfo.Protect == PAGE_EXECUTE_READWRITE)
			if (memInfo.State == MEM_COMMIT)
				if (memInfo.Type == MEM_PRIVATE)
					memories.push_back(memInfo);// 将内存块信息追加到 vector 数组尾部

		// 移动指针
		p += memInfo.RegionSize;
	}

	return memories.size() > 0;
}

3 注册表监控

通过RegOpenKeyEx打卡一个注册表项得要打开项的句柄PHKEY phkResult 利用这个句柄来获取子项和信息

    LONG WINAPI RegOpenKeyEx(
    _In_ 		HKEY hKey, 		// 需要打开的主键的名称
    _In_opt_ 	LPCSTR lpSubKey,		// 需要打开的子键的名称
    _In_opt_ 	DWORD ulOptions,		// 保留 设为零
    _In_ 		REGSAM samDesired,	// 安全访问标记 也就是权限
    _Out_ 		PHKEY phkResult 	// 得到的将要打开键的句柄
    );

得到PHKEY句柄后使用API RegQueryInfoKey获取该项信息

    LONG WINAPI RegQueryInfoKey(    			// 获取某项有关的信息 
    _in          HKEY hKey,          			// 已打开项的句柄 或指定一个标准项名 
    _out         LPTSTR lpClass,      			// 指定一个字串 用于装载这个注册表项的类名 
    _in_out      LPDWORD lpcClass,    			// 指定一个变量 用于装载lpClass缓冲区的长度。一旦返回 它会设为实际装载到缓冲区的字节数量 
                 LPDWORD lpReserved,   			// 未用 设为零 
    _out         LPDWORD lpcSubKeys,    		// 用于装载(保存)这个项的子项数量的一个变量 
    _out         LPDWORD lpcMaxSubKeyLen,   		// 指定一个变量 用于装载这个项最长一个子项的长度。注意这个长度不包括空中止字符 
    _out         LPDWORD lpcMaxClassLen,    		// 指定一个变量 用于装载这个项之子项的最长一个类名的长度。注意这个长度不包括空中止字符 
    _out         LPDWORD lpcValues,         		// 用于装载这个项的设置值数量的一个变量 
    _out         LPDWORD lpcMaxValueNameLen,   		// 指定一个变量 用于装载这个项之子项的最长一个值名的长度。注意这个长度不包括空中止字符 
    _out         LPDWORD lpcMaxValueLen,       		// 指定一个变量 用于装载容下这个项最长一个值数据所需的缓冲区长度
    _out         LPDWORD lpcbSecurityDescriptor,  	// 装载值安全描述符长度的一个变量 
    _out         PFILETIME lpftLastWriteTime        	// 指定一个结构 用于容纳该项的上一次修改时间
);

通过RegQueryInfoKey获取到lpcSubKeys子项数量同于RegEnumKeyEx的DWORD dwIndex,参数进行循环遍历得到索引项名LPTSTR lpName

LONG WINAPI RegEnumKeyEx(          			// 枚举指定项下方的子项 
    _in          HKEY hKey,        			// 一个已打开项的句柄,或者指定一个标准项名 
    _in          DWORD dwIndex,     			// 欲获取的子项的索引。第一个子项的索引编号为零 
    _out         LPTSTR lpName,     			// 用于装载指定索引处项名的一个缓冲区
    _in_out      LPDWORD lpcName,      			// 指定一个变量用于装载lpName缓冲区的实际长度含空字符。一旦返回它会设为实际装载到lpName缓冲区的字符数量 
                 LPDWORD lpReserved,   			// 未用,设为零
    _in_out      LPTSTR lpClass,       			// 项使用的类名
    _in_out      LPDWORD lpcClass,     			// 用于装载lpClass缓冲区长度的一个变量。
    _out         PFILETIME lpftLastWriteTime   		// 枚举子项上一次修改的时间
);

使用API RegEnumValue 获取键值内容 以及获取lpType判断类型、lpData获取内容

LONG WINAPI RegEnumValue(									// 读取键值
    _In_ HKEY hKey,										// 一个已打开项的句柄,或者指定一个标准项名
    _In_ DWORD dwIndex,										// 欲获取值的索引。注意第一个值的索引编号为零
    _Out_writes_to_opt_(*lpcchValueName,*lpcchValueName + 1) LPSTR lpValueName,			// 用于装载位于指定索引处值名的一个缓冲区
    _Inout_ LPDWORD lpcchValueName,								// 用于装载lpValueName缓冲区长度的一个变量。一旦返回它会设为实际载入缓冲区的字符数量
    _Reserved_ LPDWORD lpReserved,								// 未用 设为零
    _Out_opt_ LPDWORD lpType,									// 用于装载值的类型代码的变量
    _Out_writes_bytes_to_opt_(*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPBYTE lpData,	// 用于装载值数据的一个缓冲区
    _Inout_opt_ LPDWORD lpcbData								// 用于装载lpData缓冲区长度的一个变量。一旦返回它会设为实际载入缓冲区的字符数量
    );

4 服务监控

建立一个连接到服务控制管理器,并打开指定的数据库

SC_HANDLE WINAPI OpenSCManager(         	
  __in          LPCTSTR lpMachineName,        	// 指向零终止字符串 名为目标计算机
  __in          LPCTSTR lpDatabaseName,       	// 指向零终止字符串 名称的服务控制管理数据库
  __in          DWORD dwDesiredAccess          	// 指定服务的访问控制管理
);

由OpenSCManager返回的句柄做参数使用API EnumServicesStatus枚举系统当前服务信息

BOOL WINAPI EnumServicesStatus(           		// 枚举当前系统服务
  _in          SC_HANDLE hSCManager,     		// 打开的服务管理的句柄
  _in          DWORD dwServiceType,      		// 所要枚举服务的类型
  _in          DWORD dwServiceState,     		// 所要枚举服务的状态
  _out         LPENUM_SERVICE_STATUS lpServices,  	// 指向转载枚举服务的缓冲区
  _in          DWORD cbBufSize,                   	// 缓冲区大小
  _out         LPDWORD pcbBytesNeeded,            	// 如果提供的缓冲区太小 那么这里将返回需要的缓冲区大小
  _out         LPDWORD lpServicesReturned,        	// 服务的个数 枚举每个服务信息时用到
  _in_out      LPDWORD lpResumeHandle            	// 返回枚举是否成功
);

通过api EnumServicesStatus得到的lpServicesReturned数量、lpServices缓冲区遍历服务OpenService获取服务句柄

SC_HANDLE OpenService( 		// 获取服务句柄
  SC_HANDLE hSCManager,		// 服务控制管理器数据库的句柄
  LPCSTR    lpServiceName,	// 要打开的服务的名称
  DWORD     dwDesiredAccess	// 访问服务权限
);

根据获取到的服务句柄调用API QueryServiceConfig获取服务信息

BOOL WINAPI QueryServiceConfig(
  _in          SC_HANDLE hService,                        	// 指向要检索的服务
  _out         LPQUERY_SERVICE_CONFIG lpServiceConfig,  	// 指向包含服务信息的缓冲区指针
  _in          DWORD cbBufSize,                         	// 缓冲区大小
  _out         LPDWORD pcbBytesNeeded                   	// 实际需要的缓冲区大小
);

5 键盘监控

键盘钩子

windows系统是建立在事件驱动的机制上整个系统都是通过消息传递来实现的而钩子是windows系统中非常重要的系统接口用它可以截获并处理发送给其他进程的消息来实现诸多功能钩子种类很多每种钩子可以截取相应的消息例如键盘钩子截取键盘消息等等。

全局钩子运行机制通过系统调用将狗子挂入系统每当特定消息发出在消息没有到达目标窗口之前钩子就会先行捕获到消息。这时钩子回调函数可以对消息进行操作然后继续传递该消息也可结束该消息的传递。每种类型的钩子都会由系统来维护一个钩子链并且最后安装的钩子在链子的开始最先安装的在最后。实现win32的系统钩子必须调用API函数SetWindowsHookEx来安装这个函数

安装钩子
HHOOK WINAPI SetWindowsHookEx(
__in int idHook,            \\ 钩子类型
__in HOOKPROC lpfn,         \\ 回调函数地址 
__in HINSTANCE hMod,        \\ 实例句柄 (包含钩子函数的模块句柄)
__in DWORD dwThreadId);     \\ 线程ID (指定监视的线程,如果指定确定的线程,即为线程专用钩子;如果指定为空,即为全局钩子。)

几点需要说明的地方:   1 如果对于同一事件(如键盘消息)既安装了线程钩子又安装了系统钩子,系统会优先调用线程钩子,然后调用系统钩子。   2 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。处理顺序是先安装的后处理,后安装的先处理。   3 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。

定义钩子回调
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam) 

我们先在钩子函数中实现自定义的功能,然后调用函数 CallNextHookEx 把钩子信息传递给钩子链的下一个钩子函数。

LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam )

参数 hhk是钩子句柄。nCode、wParam和lParam 是钩子函数。 当然也可以通过直接返回TRUE来丢弃该消息就阻止了该消息的传递。

当不再使用钩子时,必须及时卸载。简单地调用下面的函数即可。

BOOL UnhookWindowsHookEx( HHOOK hhk)

值得注意的是线程钩子和系统钩子的钩子函数的位置有很大的差别。 线程钩子一般在当前线程或者当前线程派生的线程内,而系统钩子必须放在独立的动态链接库中。

active启动方式

win7 64下

 64位程序注册表位置 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components
 32位重定位注册表位置 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Active Setup\Installed Components

例如{052860C8-3E53-3D0B-9332-48A8B4971352}

Active Setup是微软使用此键来安装windows组件可以在这个位置下看到已安装组件得列表每个组件都有一个值windows使用这些值来识别组件。其中StubPath是其中最重要的一项它包含一个命令windows每次启动都会执行这个命令。

创建一个在64位位置需要根据启动程序而定{052860C8-3E53-3D0B-9332-48A8B4971352} StubPath 项为REG_EXPAND_SZ类型 calc.exe

1 重启计算机后calc便会启动但是启动后程序执行会造成电脑卡住无法进入系统必须要退出程序才能执行。 2 并且再次启动calc不会再启动了这是因为在user同位置的active setup下有相同的guid将其删除再次重启就会启动了。

所以每次执行要将user位置guid删除并且程序通过再次启动自己或者注入到其他进程来解决上面的两个问题。

喜欢的话点个Star哦

这个项目花了2个月的时间来进行源码阅读和编译
如果存在bug可以留言给我我将尝试着将它修复
免杀过程将不会在这记录,我不会去免杀该项目
项目仅供技术探讨,请勿用于非法用途

-------------Thank You-------------

Description
这是一个基于gh0st远程控制的项目,使自己更深入了解远控的原理,采用VS2017,默认分支hijack还在修改不能执行,master分支的项目可以正常的运行的,你可以切换到该分支查看可以执行的代码
Readme 120 MiB
Languages
C++ 74.7%
C 25.2%