声明:
首先声明,这篇文章完全是根据自己对gh0st源代码的理解写出来的,如果有理解上的偏差或者表述上的错误本人不负责,仅供参考!
前言:
在我刚学习gh0st源代码的时候,觉得这东西好难,三个工程放在一起不说,还有那么多文件,当时就懵了。这怎么看呢?从何下手呢?我想大部分刚学习gh0st源代码的同胞们都有和我一样的想法,我废话不多说了,下面就为大家详细讲解gh0st源代码。
思路:
首先我会讲解gh0st的整体框架,包括三个工程之间的关系和每个工程的作用;然后再根据程序的执行顺序,从入口函数WinMain开始逐步向下讲解。
框架:
gh0st有三个工程,分别是gh0st、install和svchost。那么这三个工程有什么作用呢?首先gh0st这个工程,是生成控制端的一个工程,换句话说就是用MFC写的界面。但是你千万不要小看它。因为它绝不仅仅是一个界面那么简单,在这个控制端里面还包括IOCP完成端口、网络socket和控制端与服务端之间的数据传输;install这个工程较其他两个工程就简单多了,因为它只有一个.h文件和一个.cpp文件。这个工程是整个程序的入口,因为WinMain函数就在这里,它的主要功能就是生成一个exe文件,而这个exe文件的工程就是安装和启动dll里面的服务的;接下来就是第三个工程了,前面所说的dll就是这个工程。它的主要功能就是执行服务函数,并完成与控制端的通信。
所以用一句话来概括gh0st的功能就是:exe安装并启动dll里面的服务,dll执行服务并与控制端进行通信,从而实现各项远程功能。
代码解读:
首先看install这个工程:(红色部分为源代码)
int APIENTRY WinMain(HINSTANCE hInstance,
{
这个WinMain函数就是程序的入口,四个参数分别是hInstance应用程序当前的实例句柄、hPrevInstance应用程序先前的实例句柄、lpCmdLine指定传给应用程序的命令行参数、nCmdShow指定窗口如何显示;首先是GetInputState()这个API函数,它的功能是要让小漏斗马上消失,这是因为以前的cpu运行速度很慢,所以当你打开一个程序的时候会有一个小漏斗在屏幕上显示,直到程序打开漏斗才消失;然后是PostThreadMessage(GetCurrentThreadId(),NULL,0,0)这个函数,大家首来先看GetCurrentThreadId这个函数,这个函数的功能是获得当前线程的PID,也就是获得当前正在运行线程的唯一标示符;而PostThreadMessage这个函数呢就是把当前线程的唯一标示符送入系统的消息队列中。说了半天的当前线程,那么线程究竟是什么呢?所谓线程,说白了就是计算机中正在运行的程序,那么在gh0st里面这个正在运行的程序是什么呢?其实这个正在运行的程序就是在控制端生成的服务端,也就是人们所说的木马。但是它一定是加上上线字串的,否则它是不会运行的,这个我们后面会讲到。接下来定义了几个指针变量,并赋值为NULL,下面我们会依次讲解。
lpEncodeString = (char *)FindConfigString(hInstance, "AAAAAA");
下面就让我们来看一下lpEncodeString这个变量是什么意思,在看它之前呢要先看一下FindConfigString这个函数的作用。
LPCTSTR FindConfigString(HMODULE hModule, LPCTSTR lpString)
{
首先看这个函数的两个参数,hModule当前程序的实例句柄,lpString就是"AAAAAA"这个字符串。后面定义了一个大小为260的字符数组和一个为空的字符串指针还有一个双字型的变量;然后调用了GetModuleFileName这个函数,这个函数就是获取一个已装载模板的完整路径名称并复制到它的第二个参数里面,也就是把服务端的完整路径保存在数组变量strFileName里面;然后调用了CreateFile这个API函数用来打开刚刚保存路径的文件,返回文件句柄。后面进行一个判断,如果文件不存在,那么返回空并跳出。
接下来来看memfind这个函数,先看它的四个参数:lpConfigString刚刚申请的栈,里面放有文件末尾的10234个字符信息、lpString六个A的字符串、MAX_CONFIG_LEN1024、0。
int memfind(const char *mem, const char *str, int sizem, int sizes)
{
int da,i,j;
if (sizes == 0)
da = strlen(str);
else
da = sizes;
for (i = 0; i < sizem; i++)
{
for (j = 0; j < da; j ++)
if (mem[i+j] != str[j]) break;
if (j == da) return i;
}
return -1;
}
首先定义了三个整形变量;进行了一个判断,sizes为零那么da=6。之后是一个for循环的嵌套,外面的for循环次数为1024次,也就是那个栈中所有字符遍历一遍。里面的for循环次数为6次,接下来进行一个判断,如果栈里面的字符没有连续六个A出现,那么就跳出内部循环i++,直到找到六个A为止,返回找到六个A的为止。如果最后没有找到那么就返回-1
里接下来进行了一次判断,如果刚刚的memfind函数返回的是-1,那么删除栈空间,然后返回空。
}
如果返回值不是空,那么返回找到六个A的为止。结束FindConfigString函数。相信现在你一定知道lpEncodeString这个变量是什么意思了,没错!它就是在原始文件中六个A的出现位置。
接下来回到WinMain函数
lpServiceConfig = (char *)FindConfigString(hInstance, "CCCCCC");
我想这段代码应该不用解释了吧,和上面的原理完全相同!要是连这段代码都看不懂,那我只能说:亲,别往下看了,回去学C语言吧!
char
接下来看一下这个函数strchr,它的功能是在文件中找到第一次出现'|'的为止,并将这个位置赋值给新定义的字符指针*pos,接下来进行一个判断,如果不存在这样的位置,那么返回-1,若存在,则在'|'后面加上'\0'进行字符串截断。
接下来调用了两次MyDecode函数,这是作者自写的一个加密函数,加密算法可以查到,这个加密函数我就不讲了,总之这个函数的目的就是将这两个字符串加密。其实现在你应该能看出来:'CCCCCC'后面的字符串就是服务名称,而'AAAAAA'后面的字符串就是服务描述。接下来进行了一次判断,如果服务名称或者服务描述任何一个为空,程序都不会再继续执行,这就是我之前说的那个服务端一定要加上上线字串的,否则它是不会运行的!
char
接下来定义了两个字符型指针变量,第一个是服务名称,下面会用到;第二个是更新服务端的一个标志字符串。接下来进行一个判断,首先看一下GetCommandLine这个API函数,它的功能是获取当前线程的命令行参数,那么if (strstr(GetCommandLine(), lpUpdateArgs) == NULL)这句代码的意思就很明显了,就是判断在当先线程的命令行中"MyCmd Update"这个字符串的出现位置,如果没有这个字符串,那么就要创建一个指向lpEncodeString的互斥体,创建它的作用就是防止程序运行两次或者两次以上;接下来如果创建失败的话就返回失败原因,后面进行一个判断,如果互斥体已经存在或者拒绝访问则返回-1并跳出。接下来释放互斥体,关闭互斥体。
else
如果是更新服务端,则等待服务端自删除。
SetUnhandledExceptionFil
这是一个设置异常捕获函数。当异常没有处理的时候,系统就会调用SetUnhandledExceptionFil
SetAccessRights();
这是一个设置获取系统权限的函数,接下来进入到这个函数内部看看。
void SetAccessRights()
{
lpSysDirectory[MAX_PATH];
上面都是变量的定义,后面用到的时候再说。
接下来调用了几个ZeroMemory函数,作用是将那三个内存块清零。然后调用了GetSystemDirectory获取系统目录的完整路径并储存到lpSysDirectory这个数组中。后面调用了GetUserName函数获取当前用户名,并保存在lpUserName这个变量中。
提升用户权限;将用户名转换为宽字符并保存在wUserName变量中。
接下来是一个判断,如果上面的检索组操作成功,那么定义一个LPLOCALGROUP_USERS_INFO_0结构类型的指针变量pTmpBuf和双字型的变量i;接下来又是一个判断,先将用户所在的组名赋值给pTmpBuf并判断是否为空,若不为空则进入到下面的for循环。
如果pTmpBuf指向的字符为空则跳出,接下来调用WideCharToMultiByte函数将宽字符的组名转换为多字符集。最后调用AddAccessRights设置改组的权限为最高
}
最后,释放缓冲区pBuf。
接下来请看下面的代码,个人认为这是最为关键的地方之一。
lpServiceName = InstallService(lpServiceDisplayName, lpServiceDescription, lpEncodeString);
。这句代码的功能就是安装服务,它的三个参数分别为加密过的服务名称、加密过的服务描述和未加密的六个A所在的位置。
下面看这个函数是如何实现的:
char *InstallService(LPCTSTR lpServiceDisplayName, LPCTSTR lpServiceDescription, LPCTSTR lpConfigString)
{
上面的都是定义的变量,用到的时候再说。
接下来定义了两个变量,其中第二个变量赋值为注册表中Svchost的目录;接下来调用了RegOpenKeyEx函数来打开注册表目录HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost。后面进行一个判断,如果打开失败则跳出。
接下来又定义了两个变量,调用了RegQueryValueEx函数来获取hkRoot路径下的netsvcs服务组的设置值,并将它储存到缓冲区strSubKey中。接下来关闭刚刚打开的注册表路径,设置错误返回值rc并判断,如果没有取得netsvcs的设置值,那么跳出。
接下来调用了OpenSCManager函数来打开服务控制管理器并将句柄赋值给hscm ,判断,如果没有打开则跳出。
接下来调用GetSystemDirectory函数获取windows系统目录的完整路径名,并将其保存在缓冲区strSysDir中。后面是两个变量的定义,要说一下bin这个变量的值是服务的可执行文件的路径名,后面的netsvcs就是该服务所在的服务组。