微信公众号:计算机与网络安全ID:计算机-网络需要资源,创建主线程等一系列工作。进程是一个正在运行的程序,进程是向操作系统申请资源的基本单位。当运行记事本程序时,操作系统会创建一个记事本进程。当您关闭记事本时,记事本进程也会立即结束。对过程的感性理解就这么多了。如果想观察系统中正在运行的进程,同时按下键盘上的Ctrl+Shift+Esc组合键,打开“任务管理器”,就可以看到系统中正常的进程列表,如图在图1中。对于任务管理器中的众多栏目,主要关注的是“图像名称”、“PID”和“线程号”3项,这3项在编程时会用到和涉及到。图1任务管理器任何计算机文件都是二进制文件。对于一个可执行程序,它的二进制数据可以被CPU执行。程序是一个静态的概念,它本身只是一个存在于硬盘上的二进制文件。当用鼠标双击一个可执行程序时,该程序被加载到内存中,此时创建了一个进程。操作系统通过加载器将程序加载到内存中时,会为其分配各种进程所需的各种资源,并产生一个主线程。主线程会有CPU执行时间,占用进程申请的内存。。。在编程中,有时候经常需要通过一个正在运行的程序来创建一个新的进程。本文介绍创建流程的常用API函数。一、简单下载器的演示Windows下创建进程的方法有很多种,这里举例介绍最简单的方法。该方法使用的API函数为WinExec(),定义如下:UINTWinExec(LPCSTRlpCmdLine,//commandlineUINTuCmdShow//windowstyle);参数说明如下。lpCmdLine:指向要执行的可执行文件的字符串。uCmdShow:程序运行后的窗口状态。第一个参数比较容易理解。比如你要执行“记事本”程序,那么这个参数可以是“C:\Windows\System32\Notepad.exe”。第二个参数表示程序运行后窗口的状态。常用的参数有两个,一个是SW_SHOW,一个是SW_HIDE。SW_SHOW表示程序运行后窗口状态显示,SW_HIDE表示程序运行后窗口状态隐藏。可以尝试创建一个隐藏显示状态的“记事本”程序,如下:WinExec("c:\\windows\\system32\\notepad.exe",SW_HIDE);这样创建的“记事本”进程在“任务管理器”中可以看到“notepad.exe”进程,但是看不到它的窗口界面。很多“下载器”中都用到了WinExec()函数”。“下载器”的英文名称是“Downloader”,意思是下载器。它是一种功能比较单一的恶意程序(相对于木马和后门来说,功能比较单一)。下载器程序的功能是让受害者的电脑去黑客指定的URL地址下载更多的病毒文件或木马文件并运行。通常使用WinExec()来运行下载到本地的恶意程序,调用它的原因是只有两个参数,参数很简单,我们简单做一个下载器来演示,这里只是演示,大家不要尝试如果你用它做任何坏事你有恶意,因为演示代码很容易被杀毒软件清除。记住,目的是学习编程知识。要完成一个模拟下载器,需要让程序从网络上的某个地址下载程序。下载文件的方法有很多种,比较简单常用的函数是URLDownloadToFile()。这个函数也被下载进程使用,它的定义如下:在这个函数中,只使用了两个参数,即szURL和szFileName。这两个参数的说明如下。szURL:一个字符串,指向下载位置的URL。szFileName:指向要保存到本地位置的字符串。其余参数可以赋值为0或NULL。使用URLDownloadToFile()函数需要包含Urlmon.h头文件和Urlmon.lib导入库文件,否则编译链接时会失败。一旦了解了需要使用的API函数,完成代码就非常简单了。具体代码只有几行,如下:#include#include#pragmacomment(lib,"urlmon")intmain(){charszUrl[MAX_PATH]="c:\\windows\\system32\\notepad.exe";charszVirus[MAX_PATH]="d:\\virus.exe";URLDownloadToFile(NULL,szUrl,szVirus,0,NULL);//为了模拟看看效果,这里使用参数SW_SHOW//一般可以传SW_HIDE参数WinExec(szVirus,SW_SHOW);return0;}这里模拟的是将C盘系统目录下的记事本程序下载到D盘保存为病毒。exe,然后运行。如果是从网络上的某个地址下载的,只需要修改szUrl变量中保存的字符串即可。我们的代码是一个简单的模拟代码。如果我们真的完成一个“下载器”,会比这段代码复杂的多。如果我们要在源代码上“免杀”,就会有很多问题需要考虑。我们还是以学习编程知识为目标,不要毁了它,否则随时都有可能被“查水表”。2.CreateProcess()函数介绍和程序启动通常,要创建一个进程,你会选择使用CreateProcess()函数。该函数参数多,功能强大,使用更灵活。对于WinExec()函数,其使用简单,只能完成简单的进程创建工作。如果你想对创建的进程有一定的控制,就必须使用更强大的CreateProcess()函数。在介绍CreateProcess()函数之前,先介绍一件事。通常,在用C语言编写程序时,如果是控制台下的程序,那么编写程序的入口函数就是main()函数,也就是通常所说的main函数。如果你在Windows下写程序,那么入口函数就是WinMain()。即使用MFC来开发,其实也有一个WinMain()函数,只不过是被庞大的MFC框架封装了而已。那么程序真的是从main()函数还是WinMain()函数开始执行的呢?在写控制台程序的时候,如果需要给程序提供参数,参数从哪里来,为什么main函数会返回Value,会返回到哪里呢?用VC6写了一个简单的程序。通过调试这个简单的程序,看看这个C语言程序是否真的是由main()函数执行的。编写一个简单的程序,输出“HelloWorld”以供调试。程序代码如下:#includeintmain(){printf("HelloWorld!!!\r\n");return0;}这是一个很简单的程序,按F7键编译连接,然后按F10键进入单步调试状态,打开VC6的CallStack窗口(调用堆栈窗口),观察其内容,如图2所示。图2CallStack窗口内容有3行调用堆栈中的记录。双击第二行“mainCRTStartup()line206+25bytes”可以查看代码编辑窗口的内容。此时的代码就是调用主函数main()的C运行时启动函数(简称启动函数)。代码编辑窗口的内容如图3所示。图3启动函数可以看到代码编辑窗口的左侧有一个绿色的三角,说明这行代码调用了主函数main()。并且通过这行代码可以发现,main()函数的返回值赋值给了mainret变量。将代码向上移动,找到定义mainret变量的代码。mainret的定义如下:intmainret;变量的类型是int类型。通常在定义main()函数时,main()函数的返回值都是int类型。从上面的调用过程可以看出,main()函数只是程序员在编程时的一个入口函数,程序并不是从main()函数开始的。在执行main()函数之前,操作系统和C语言的启动代码已经为程序做了很多工作。以上内容只是一个简单的小插曲。回到正题,我们来介绍一下CreateProcess()函数的使用。CreateProcess()函数的定义如下:BOOLCreateProcess(LPCTSTRlpApplicationName,//nameofexecutablemoduleLPTSTRlpCommandLine,//commandlinestringLPSECURITY_ATTRIBUTESlpProcessAttributes,//SDLPSECURITY_ATTRIBUTESlpThreadAttributes,//SDBOOLbInheritHandles,//handleinheritanceoptionDWORDdwCreationFlags,//creationflagsLPVOIDlpEnvironment,//newenvironmentblockLPCTSTRlpCurrentDirectory,//currentdirectorynameLPSTARTUPINFOlpStartupInfo,//startupinformationLPPROCESS_INFORMATIONlpProcessInformation//processinformation);参数说明如下。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结构的指针,该结构指定新进程的启动信息。Thisparameterisastructurethatdeterminesthestatusoftheprocessstartup.该结构体的定义如下:typedefstruct_STARTUPINFO{DWORDcb;LPTSTRlpReserved;LPTSTRlpDesktop;LPTSTRlpTitle;DWORDdwX;DWORDdwY;DWORDdwXSize;DWORDdwYSize;DWORDdwXCountChars;DWORDdwYCountChars;DWORDdwFillAttribute;DWORDdwFlags;WORDwShowWindow;WORDcbReserved2;LPBYTElpReserved2;HANDLEhStdInput;HANDLEhStdOutput;HANDLEhStdError;}STARTUPINFO,*LPSTARTUPINFO;在使用结构体之前,需要先给cb成员变量赋值,用来保存结构体的大小。一般情况下,创建一个进程只需要初始化几个参数。如果要重定向新进程的输入输出,会用到更多结构体的成员变量等。lpProcessInformation:指向PROCESS_INFORMATION结构体的指针,用于返回新创建的进程和主线程的信息.结构定义如下:typedefstruct_PROCESS_INFORMATION{HANDLEhProcess;HANDLEhThread;DWORDdwProcessId;DWORDdwThreadId;}PROCESS_INFORMATION;该结构体用于返回新创建进程的句柄和进程ID,进程主线程的句柄和主线程ID。让我们通过一个例子来演示CreateProcess()函数。#include#include#defineEXEC_FILE"c:\\windows\\system32\\notepad.exe"intmain(){PROCESS_INFORMATIONpi={0};STARTUPINFOsi={0};si.cb=sizeof(STARTUPINFO);BOOLbRet=CreateProcess(EXEC_FILE,NULL,NULL,NULL,FALSE,NULL,NULL,NULL,&si,&pi);if(bRet==FALSE){printf("CreateProcessError!\r\n");return-1;}CloseHandle(pi.hThread);CloseHandle(pi.hProcess);return0;}创建进程后,需要使用CloseHandle()函数关闭PROCESS_INFORMATION结构变量的两个句柄。