1.进程的继承
创建进程的函数:BOOL CreateProcess(
LPCTSTR lpApplicationName, // 创建进程时打开的exe文件名 LPTSTR lpCommandLine, // 创建进程时的命令行参数 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 安全属性,可用来设置该进程句柄是否可继承 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 可用来设置进程的主线程句柄是否可继承 BOOL bInheritHandles, // 是否继承父进程的句柄表 DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // new environment block LPCTSTR lpCurrentDirectory, // 进程的当前目录 LPSTARTUPINFO lpStartupInfo, // 程序状态设置 LPPROCESS_INFORMATION lpProcessInformation // out参数进程信息 );
lpProcessAttributes ->安全属性,用来设定进程是否能被继承;一个SECURITY_ATTRIBUTES结构的指针,第三个成员为TRUE可继承;
lpThreadAttributes ->安全属性,线程能否被继承;SECURITY_ATTRIBUTES结构指针,第三个成员为TRUE可继承;
dwCreationFlags ->创建标记;如果是控制台程序,CREATE_NEW_CONSOLE表示子进程和父进程都有自己的控制台窗口;为NULL则子进程会把信息打印到父进程的控制台;
lpCurrentDirectory ->进程的当前目录;如果为NULL,子进程的当前目录将和父进程一样;
如果想要当前目录为子进程exe文件所在目录,则需要用字符串指定;
获取进程当前目录:
char szBuffer[256] = {0}; GetCurrentDirectory(256,szBuffer); printf("%s\n",szBuffer);
当在进程A中CreateProcess创建一个子进程B时,进程A的句柄表中会多有两个条记录,一个是进程B的内核对象句柄,一个是B主线程的内核对象句柄;
如果lpProcessAttributes、lpThreadAttributes都为NULL;则两个句柄都不可继承,也就是可继承状态为0;
进程A句柄表:
此时如果在进程A中创建一个子进程C,那么C将不能继承A的进程内核和线程内核句柄;
但如果将进程B设为可继承,那么C可以从A的句柄表中继承到B的进程句柄和主线程句柄;
利用这一特点可以在进程C中空值进程B;
1)目标
在A进程中创建一个进程(比如浏览器进程IE),并设定该子进程的进程内核句柄与主线程内核句柄为可继承
在A进程中再创建一个进程B,在B中对IE进程控制
2)进程B
#include<stdio.h> #include<windows.h> int main(int argc, char* argv[]){ DWORD dwProcessHandle = -1; DWORD dwThreadHandle = -1; char szBuffer[256] = {0}; memcpy(szBuffer,argv[1],8); sscanf(szBuffer,"%x",&dwProcessHandle); memset(szBuffer,0,256); memcpy(szBuffer,argv[2],8); sscanf(szBuffer,"%x",&dwThreadHandle); printf("获取IE进程、主线程句柄\n"); Sleep(2000); //挂起主线程 printf("挂起主线程\n"); ::SuspendThread((HANDLE)dwThreadHandle); Sleep(5000); //恢复主线程 ::ResumeThread((HANDLE)dwThreadHandle); printf("恢复主线程\n"); Sleep(5000); //关闭ID进程 ::TerminateProcess((HANDLE)dwProcessHandle,1); ::WaitForSingleObject((HANDLE)dwProcessHandle, INFINITE); printf("ID进程已经关闭.....\n"); return 0; }
3)进程A
#include<stdio.h> #include<windows.h> int main(int argc, char* argv[]){ char szHandle[8] = {0}; char szBuffer[256] = {0}; SECURITY_ATTRIBUTES ie_sa_p; ie_sa_p.nLength = sizeof(ie_sa_p); ie_sa_p.lpSecurityDescriptor = NULL; ie_sa_p.bInheritHandle = TRUE; SECURITY_ATTRIBUTES ie_sa_t; ie_sa_t.nLength = sizeof(ie_sa_t); ie_sa_t.lpSecurityDescriptor = NULL; ie_sa_t.bInheritHandle = TRUE; //创建一个可以被继承的内核对象,此处是个进程 STARTUPINFO ie_si = {0}; PROCESS_INFORMATION ie_pi; ie_si.cb = sizeof(ie_si); TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe"); CreateProcess( NULL, szCmdline, &ie_sa_p, &ie_sa_t, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &ie_si, &ie_pi); //组织命令行参数 sprintf(szHandle,"%x %x",ie_pi.hProcess,ie_pi.hThread); sprintf(szBuffer,"D:/create_child.exe %s",szHandle); //定义创建进程需要用的结构体 STARTUPINFO si = {0}; PROCESS_INFORMATION pi; si.cb = sizeof(si); //创建子进程 BOOL res = CreateProcess( NULL, szBuffer, NULL, NULL, TRUE, //可以继承父进程的句柄表 CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); return 0; }
4)结果
运行进程A,ie浏览器被打开;然后被进程B结束,浏览器关闭;
2.以挂起的方式创建进程
用CreateProcess函数可以创建一个进程;
正常情况下CreateProcess做以下事情:创建一个进程的内核对象、给进程分配一个4GB的空间、加载pe:包括exe、dll、修复dll、创建主线程,把程序入口地址交给EIP
如果参数dwCreationFlags传入CREATE_SUSPENDED将以挂起的方式创建进程;
以这种方式创建的进程只有一个壳;也就是进程的主线程没有开始运行;
需要调用函数让进程的主线程恢复执行:
ResumeThread(ie_pi.hThread);
1)已挂起方式打开进程实例
例如:以挂起方式打开notpad++
#include<stdio.h> #include<windows.h> int main(int argc, char* argv[]){ TCHAR szAppName[256] = TEXT("D:\\Program Files\\Notepad++\\notepad++.exe"); STARTUPINFO si = {0}; //程序启动设置 si.cb = sizeof(si); //只需要传递结构大小即可 PROCESS_INFORMATION pi; //记录进程句柄信息等 ::CreateProcess( NULL, szAppName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi ); //恢复主进程 //::ResumeThread(pi.hThread); return 0; }
当没有恢复主进程时,notepad++没有运行;
但任务管理器中可以看到notpad++的进程,只不过占内存比正常运行小,因为主线程根本没运行;
当调用ResumeThread恢复主进程时,notepad++正常运行
2)关于加壳
挂起方式创建的进程之是一个外壳;
我们可以修改外壳程序的内容;
这意味着在程序调用ResumeThread恢复执行的时候,外壳还是notepad++,但里面的内容完全变了;
可以将自己的程序拉伸为镜像文件,然后替换外壳程序,接下来恢复执行时执行的就是自己的程序了;
这就是所谓的加壳;
想以这种借助其它程序的外壳来执行程序时,必须将外壳进程的主线程入口和ImageBase给替换掉;
入口地址=程序的镜像基址ImageBase + 程序的入口函数地址OEP;
因为外壳程序已挂起的方式运行的;
也就是说外壳主线程是挂起的,可以得到主线程上下文对象CONTEXT;
CONTEXT contx; contx.ContextFlags = CONTEXT_FULL; //CONTEXT_FULL表示除了寄存器的值,还要得到其他信息 GetThreadContext(pi.hThread, &contx); //第一个参数为主线程句柄,主线程句柄在进程创建后会存在PROCESS_INFORMATION结构中;
然后就可以通过CONTEXT结构获取程序入口点
//获取入口点 DWORD dwEntryPoint = contx.Eax; //eax中存储的入口点并不一定是ImageBase+oep,而是真正的入口点; //当程序不是占据ImageBase时,入口点将不等于ImageBase+oep; //获取ImageBase char* baseAddress = (CHAR *) contx.Ebx+8; //这里的baseAddress不能直接输出
这里获取的baseAddress并不是想要的ImageBase,而是ImageBase存在哪里;
但父进程并不能直接通过这个地址找到ImageBase,因为这是子进程中的地址;
需要用到进程间读取的函数:ReadProcessMemory;
5个参数依次为:进程句柄、从这进程的什么位置开始读、读的东西放到哪里、读多少个字节、(out参数)真正读了多少个字节;
TCHAR szBuffer[256]; memset(szBuffer,0,256); ReadProcessMemory(pi.hProcess,baseAddress,szBuffer,4,NULL);
这样就得到了需要修改的外壳进程的入口点和ImageBase;
接下来想借壳上市只需要做相应的修改即可;
来源:https://www.cnblogs.com/ShiningArmor/p/12175053.html