文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

网络安全编程:创建进程

2024-12-03 11:05

关注

微信公众号:计算机与网络安全

ID:Computer-network

当运行一个程序的时候,操作系统就会将这个程序从磁盘文件装入内存,分配各种运行程序所需的资源,创建主线程等一系列的工作。进程是运行当中的程序,进程是向操作系统申请资源的基本单位。运行一个记事本程序时,操作系统就会创建一个记事本的进程。当关闭记事本时,记事本进程也随即结束。对进程感性上的认识,这么多也就够了。

如果要观察系统中正在运行的进程,那么同时按下键盘上的Ctrl+Shift+Esc组合键就可以打开“任务管理器”,也就看到了系统中正常的进程列表,如图1所示。对于任务管理器中的众多列,主要关心的是“映像名称”“PID”和“线程数”3项,这3项在编程中都会用到和涉及。

 

 

 

 

图1 任务管理器

任何一个计算机文件都是一个二进制文件。对于可执行程序来说,它的二进制数据是可以被CPU执行的。程序是一个静态的概念,本身只是存在于硬盘上的一个二进制文件。当用鼠标双击某个可执行程序以后,这个程序被加载入内存,这时就产生了一个进程。操作系统通过装载器将程序装入内存时,会为其分配各种进程所需的各种资源,并产生一个主线程,主线程会拥有CPU执行时间,占用进程申请的内存……在编程的时候也经常需要通过运行中的程序再去创建一个新的进程,本文介绍常见的用于创建进程的API函数。

1. 简单下载者的演示

在Windows下创建进程的方法有多种,这里通过一个例子先介绍最简单的一种方法。该方法用到的API函数为WinExec(),其定义如下:

  1. UINT WinExec( 
  2.  LPCSTR lpCmdLine, // command line 
  3.  UINT uCmdShow // window style 
  4. ); 

参数说明如下。

lpCmdLine:指向一个要执行的可执行文件的字符串。

uCmdShow:程序运行后的窗口状态。

第1个参数比较好理解,比如要执行“记事本”程序,那么这个参数就可以是“C:\Windows\ System32\Notepad.exe”。第2个参数是指明程序运行后窗口的状态,常用的参数有两个,一个是SW_SHOW,另一个是SW_HIDE。SW_SHOW表示程序运行后窗口状态为显示状态,SW_HIDE表示程序运行后窗口状态为隐藏状态。可以试着创建一个隐藏显示状态的“记事本”程序,方法如下:

  1. WinExec("c:\\windows\\system32\\notepad.exe", SW_HIDE); 

这样创建的“记事本”进程在“任务管理器”中可以看到“notepad.exe”这个进程,但是无法看到其窗口界面。

WinExec()函数在很多“下载者”中使用,“下载者”的英文名字为“Downloader”,也就是下载器的意思。它是一种恶意程序,其功能较为单一(相对木马、后门来说,功能单一)。下载者程序的功能是让受害者计算机到黑客指定的URL地址去下载更多的病毒文件或木马文件并运行。下载者的体积较小,容易传播。当下载者下载到病毒或木马后,通常都会使用WinExec()来运行下载到本地的恶意程序,调用它的原因是只有两个参数且参数非常简单。

下面简单来做一个下载者进行演示,这仅仅只是一个演示。如果心怀歹意的话,不要企图拿它来做任何坏事,因为演示代码会很轻易地被杀毒软件干掉。记住,目的是学习编程知识。

要完成一个模拟的下载者,就要让程序可以从网络上某个地址下载程序。文件下载的方式比较多,相对简单而又比较常用的函数是URLDownloadToFile()。这个函数也是被下载者进程使用的函数,其定义如下:

  1. HRESULT URLDownloadToFile( 
  2.  LPUNKNOWN pCaller, 
  3.  LPCTSTR szURL, 
  4.  LPCTSTR szFileName, 
  5.  DWORD dwReserved, 
  6.  LPBINDSTATUSCALLBACK lpfnCB 
  7. ); 

在这个函数中,只会用到两个参数,分别是szURL和szFileName。这两个参数的说明如下。

szURL:指向下载地址的 URL 的字符串。

szFileName:指向要保存到本地位置的字符串。

其余的参数赋值为0或NULL即可。

使用URLDownloadToFile()函数,需要包含Urlmon.h头文件和Urlmon.lib导入库文件,否则在编译和连接时会无法通过。

已经了解了需要用到的API函数,那么完成代码也就非常简单了。具体代码不过几行而已,具体如下:

  1. #include  
  2. #include  
  3. #pragma comment (lib, "urlmon"
  4. int main() 
  5.   char szUrl[MAX_PATH] = "c:\\windows\\system32\\notepad.exe"
  6.   char szVirus[MAX_PATH] = "d:\\virus.exe"
  7.   URLDownloadToFile(NULL, szUrl, szVirus, 0, NULL); 
  8.   // 为了模拟方便看到效果,这里使用参数 SW_SHOW 
  9.   // 一般可以传递 SW_HIDE 参数 
  10.   WinExec(szVirus, SW_SHOW); 
  11.   return 0; 

这里的模拟是把C盘系统目录下的记事本程序下载到D盘并保存成名为virus.exe,然后运行它。如果是从网络上某个地址处进行下载,那么只要修改szUrl变量保存的字符串即可。我们的代码是一个简单的模拟代码,如果真正完成一个“下载者”的话,要比这个代码复杂很多,如果要在源代码上进行“免杀”,那么要考虑到问题也会很多。我们还是以学习编程知识为目的,不要进行破坏,否则随时可能会被“查水表”。

2. CreateProcess()函数介绍与程序的启动

通常情况下,创建一个进程会选择使用CreateProcess()函数,该函数的参数非常多,功能强大,使用也更为灵活。对于WinExec()函数来说,其使用简单,也只能完成简单的进程创建工作。如果要对被创建的进程具有一定的控制能力,那么必须使用功能更为强大的CreateProcess()函数。

在介绍CreateProcess()函数之前,先来介绍一个内容。通常,在编写C语言的程序时,如果是控制台下的程序,那么编写程序的入口函数是main()函数,也就是通常所说的主函数。如果编写一个Windows下程序,那么入口函数是WinMain()。即使是使用MFC进行开发,其实也是有WinMain()函数的,只不过是被庞大的MFC框架封装了。那么程序真的是从main()函数或者是WinMain()函数开始执行的吗?在写控制台程序时,如果需要给程序提供参数,那么这个参数是从哪里来的,主函数为什么会有返回值,它会返回哪里去呢?

使用VC6来写一个简单的程序。通过调试这个简单的程序,看看C语言程序是否真的由main()函数开始执行。写一个简单的输出“Hello World”的程序来进行调试。程序代码如下:

  1. #include  
  2. int main() 
  3.   printf("Hello World!!! \r\n"); 
  4.   return 0; 

这是非常简单的一个程序,按下F7键进行编译和连接,然后按下F10键开始进行单步调试状态,打开VC6的CallStack窗口(调用栈窗口),观察其内容,如图2所示。

 

 

 

 

图2 CallStack窗口内容

在调用栈中有3行记录,双击第2行“mainCRT Startup() line 206 + 25 bytes”,查看代码编辑窗口的内容,此时的代码为调用主函数main()的C运行时启动函数(简称启动函数)。代码编辑窗口内容如图3所示。

 

 

 

 

图3 启动函数

可以看到,在代码编辑窗口的左侧有一个绿色的三角,表示这行代码调用了主函数main()。并且通过该行代码可以发现,main()函数的返回值赋值给了mainret变量。将代码上移,找到定义mainret变量的代码处。mainret的定义如下:

int mainret;

该变量的类型为int型。通常在定义main()函数时,main()函数的返回值是int型。从上面的调用过程可以看出,main()函数只是程序员编程时的入口函数,程序的启动并不是从main()函数开始。在执行main()函数前,操作系统及C语言的启动代码已经为程序做了很多工作。

上面的内容只是一个简单的小插曲。回归正题,开始介绍CreateProcess()函数的使用。CreateProcess()函数的定义如下:

  1. BOOL CreateProcess( 
  2.  LPCTSTR lpApplicationName, // name of executable module 
  3.  LPTSTR lpCommandLine, // command line string 
  4.  LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD 
  5.  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD 
  6.  BOOL bInheritHandles, // handle inheritance option 
  7.  DWORD dwCreationFlags, // creation flags 
  8.  LPVOID lpEnvironment, // new environment block 
  9.  LPCTSTR lpCurrentDirectory, // current directory name 
  10.  LPSTARTUPINFO lpStartupInfo, // startup information 
  11.  LPPROCESS_INFORMATION lpProcessInformation // process information 
  12. ); 

参数说明如下。

lpApplicationName:指定可执行文件的文件名。

lpCommandLine:指定欲传给新进程的命令行的参数。

lpProcessAttributes:进程安全属性,该值通常为 NULL,表示为默认安全属性。

lpThreadAttributes:线程安全属性,该值通常为 NULL,表示为默认安全属性。

bInheritHandlers:指定当前进程中的可继承句柄是否被新进程继承。

dwCreationFlags:指定新进程的优先级以及其他创建标志。

该参数一般情况下可以为0。

如果要创建一个被调试进程的话,需要把该参数设置为DEBUG_PROCESS。创建进程的进程称为父进程,被创建的进程称为子进程。也就是说,父进程要对子进程进行调试的话,需要在调用CreateProcess()函数时传递DEBUG_PROCESS参数。在传递DEBUG_PROCESS参数后,子进程创建的“孙”进程同样也处在被调试状态中。如果不希望子进程创建的“孙”进程也处在被调试状态,那么在父进程创建子进程时传递DEBUG_ONLY_THIS_PROCESS和DEBUG_PROCESS。

在有些情况下,希望被创建子进程的主线程暂时不要运行,那么可以指定CREATE _SUSPENDED参数。事后希望该子进程的主线程运行的话,可以使用ResumeThread()函数使子进程的主线程恢复运行。

lpEnvironment:指定新进程的环境变量,通常这里指定为 NULL 值。

lpCurrentDirectory:指定新进程使用的当前目录。

lpStartupInfo:指向 STARTUPINFO 结构体的指针,该结构体指定新进程的启动信息。

该参数是一个结构体,该结构体决定进程启动的状态。该结构体的定义如下:

  1. typedef struct _STARTUPINFO { 
  2.  DWORD cb; 
  3.  LPTSTR lpReserved; 
  4.  LPTSTR lpDesktop; 
  5.  LPTSTR lpTitle; 
  6.  DWORD dwX; 
  7.  DWORD dwY; 
  8.  DWORD dwXSize; 
  9.  DWORD dwYSize; 
  10.  DWORD dwXCountChars; 
  11.  DWORD dwYCountChars; 
  12.  DWORD dwFillAttribute; 
  13.  DWORD dwFlags; 
  14.  WORD wShowWindow; 
  15.  WORD cbReserved2; 
  16.  LPBYTE lpReserved2; 
  17.  HANDLE hStdInput; 
  18.  HANDLE hStdOutput; 
  19.  HANDLE hStdError; 
  20. } STARTUPINFO, *LPSTARTUPINFO; 

该结构体在使用前,需要对cb成员变量进行赋值,该成员变量用于保存结构体的大小。一般创建一个进程,只需要初始化其中几个参数即可,如果要对新进程的输入输出重定向的话,会用到该结构体的更多成员变量等。

lpProcessInformation:指向PROCESS_INFORMATION结构体的指针,该结构体用于返回新创建进程和主线程的相关信息。该结构体的定义如下:

  1. typedef struct _PROCESS_INFORMATION { 
  2.  HANDLE hProcess; 
  3.  HANDLE hThread; 
  4.  DWORD dwProcessId; 
  5.  DWORD dwThreadId; 
  6. } PROCESS_INFORMATION; 

该结构体用于返回新创建进程的句柄和进程ID,进程主线程的句柄和主线程ID。

下面通过一个实例来对CreateProcess()函数进行演示。

  1. #include  
  2. #include  
  3. #define EXEC_FILE "c:\\windows\\system32\\notepad.exe" 
  4. int main() 
  5.  PROCESS_INFORMATION pi = { 0 }; 
  6.  STARTUPINFO si = { 0 }; 
  7.  si.cb = sizeof(STARTUPINFO); 
  8.  BOOL bRet = CreateProcess(EXEC_FILE, 
  9.  NULLNULLNULLFALSE
  10.  NULLNULLNULL, &si, &pi); 
  11.  if ( bRet == FALSE ) 
  12.  { 
  13.  printf("CreateProcess Error ! \r\n"); 
  14.  return -1; 
  15.  } 
  16.  CloseHandle(pi.hThread); 
  17.  CloseHandle(pi.hProcess); 
  18.  return 0; 

进程创建后,PROCESS_INFORMATION结构体变量的两个句柄需要使用CloseHandle()函数进行关闭。

 

 

来源:计算机与网络安全内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯