SkillAgentSearch skills...

CcRemote

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

Install / Use

/learn @Cc28256/CcRemote
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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 进程监控

进行进程枚举有很多方法
A:CreateToolhelp32Snapshot()、Process32First()和Process32Next()
B:EnumProcesses()、EnumProcessModules()、GetModuleBaseName()
C:Native Api的ZwQuerySystemInformation
D:wtsapi32.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 

Related Skills

View on GitHub
GitHub Stars525
CategoryDevelopment
Updated26d ago
Forks198

Languages

C++

Security Score

80/100

Audited on Mar 3, 2026

No findings