首页
社区
课程
招聘
[原创]64位CreateProcess逆向:(一)3环下的各参数效验、整合以及0环返回后的检查
发表于: 2016-1-26 11:35 23320

[原创]64位CreateProcess逆向:(一)3环下的各参数效验、整合以及0环返回后的检查

2016-1-26 11:35
23320
点击下面进入总目录:
64位Windows创建64位进程逆向分析(总目录)

    这是科锐逆向64位CreateProcess函数的第1篇。作为整个篇章的开始,我们先要谈谈CreateProcess应该做什么,逆向它的意义在哪。
    按MSDN的定义:
        CreateProcess函数,将创造一个进程以及它的主线程,这个新进程会执行指定的可执行文件。
    其中“进程”、“可执行文件”是重点,一般说来,进程是内存中的数据,而可执行文件(狭义地讲)就是Windows存放在磁盘中的PE文件。
    所以,CreateProcess之所以重要,是因为它起码做了两件大事:
        1.将磁盘中的数据,(按一定格式)加载到内存中去
        2.让内存中的数据运行起来
    研究CreateProcess这个API,对于理解PE、Windows进程、线程管理都会有很大帮助。

    在此系列的第1篇中,我们会一起梳理下整个CreateProcess整体的流程,让大家有一个整体认识。

CreateProcessInternal
    进入CreateProcess的入口可以发现,微软为了保持结构改动的灵活性,多封装了一层CreateProcessInernal,CreateProcess基本上没做多余的工作。而是直接调用CreateProcessInternalA并将CreateProcessInternalA返回值作为结果返回。
    唯一多做了一点事情,就是在传递参数的过程中,多传了两个(均为0的)参数,这应该是设计上防止未来变动而保留的灵活性。

    因此,我们真正的起点,应该是从CreateProcessInternal开始的。
    它的函数原型如下:
BOOL WINAPI CreateProcessInternalW(HANDLE hToken,
                       LPCWSTR lpApplicationName,
                       LPWSTR lpCommandLine,
                       LPSECURITY_ATTRIBUTES lpProcessAttributes,
                       LPSECURITY_ATTRIBUTES lpThreadAttributes,
                       BOOL bInheritHandles,
                       DWORD dwCreationFlags,
                       LPVOID lpEnvironment,
                       LPCWSTR lpCurrentDirectory,
                       LPSTARTUPINFOW lpStartupInfo,
                       LPPROCESS_INFORMATION lpProcessInformation,
                       PHANDLE hNewToken);

    整体流程件最后附图。
    我们可以把它分成三个阶段:
        1.进入0环前,主要进行参数检查以及3环参数到0环参数的转换
        2.调用NtCreateUserProcess进入0环
        3.从0环返回后,主要对返回值进行检查并决定程序流程
进入0环前
    最开始的工作,是对由最初CreateProcess所传递的几个参数
进行检查,如下所示:

检查lpApplicationName、lpCommandLine、lpProcessInformation、lpStartupInfo
首先检查的是lpApplicationName、lpCommandLine、lpProcessInformation、lpStartupInfo四个参数,若不合格,会直接退出并设置相关错误吗,细节如下:
1.初始化局部变量,检查参数lpApplicationName和lpCommandLine是否为NULL,都为NULL则退出,错误码为0C0000030h。

2.然后检查参数lpProcessInformation和lpStartupInfo是否为NULL,是则退出,设置错误码0C000000Dh。

检查dwCreationFlags并决定流程
    我们知道,dwCreationFlags这个参数,既决定了新进程的创建方式,又决定了新进程的执行的优先级。CreateProcessInternal函数对它的检查及处理细节如下:
1.如MSDN中所说,dwCreationFlags的有些标志位是不能同时使用的,所以函数内会检测创建标志CreationFlags是否同时包含它们,是则设置错误码返回:
    检测创建标志CreationFlags是否同时包含DETACHED_PROCESS 和 CREATE_NEW_CONSOLE,是则设置错误码0x57,然后返回:

    检查CreationFlags如果同时否包含CREATE_SEPARATE_WOW_VDM和CREATE_SHARED_WOW_VDM标志,则设置错误码0x57,并返回。

    否则继续检查是否包含CREATE_SHARED_WOW_VDM和BaseStaticServerData + 0x744位置上是否不为0,如二者都不成立则将CREATE_SEPARATE_WOW_VDM标志位置1。

2.根据dwCreationFlags中优先级相关的信息,设置新进程的运行优先级,参数宏的值与函数内设置的值的对应关系如下:
为IDLE_PRIORITY_CLASS对应1,
为NORMAL_PRIORITY_CLASS 对应2,
为BELOW_NORMAL_PRIORITY_CLASS 对应5,
为ABOVE_NORMAL_PRIORITY_CLASS对应6,
为HIGH_PRIORITY_CLASS时判断REALTIME_PRIORITY_CLASS标志,标志为0时,对应0,为1时,对应结果(BasepIsRealtimeAllowed(0, Token != 0) != 0) + 3
上述都不符合时,优先级为3

接着,通过与操作屏蔽NORMAL_PRIORITY_CLASS 、IDLE_PRIORITY_CLASS 、HIGH_PRIORITY_CLASS 、REALTIME_PRIORITY_CLASS 、BELOW_NORMAL_PRIORITY_CLASS、ABOVE_NORMAL_PRIORITY_CLASS标志位。
并检测是否包含CREATE_PROTECTED_PROCESS(CREATE_NEW_CONSOLE|DEBUG_ONLY_THIS_PROCESS)标志位。若是,则将hDebug赋值为40h。
然后,检测CREATE_BREAKAWAY_FROM_JOB标志位,包含则hDebug|=1,检测INHERIT_PARENT_AFFINITY标志位,包含则hDebug|REALTIME_PRIORITY_CLASS。

我们还知道,CreateProcess还与调试有关,是编写调试器的基础,因此,其中有代码会判断dwCreationFlags是否包含DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS标志位,有则调用DbgUiConnectToDbg创建调试对象,并调用DbgUiGetThreadDebugObject获取该调试对象。



初始化0环参数AttributeList部分成员
    我们知道,3环与0环的栈是相互独立的,并且一般0环都会有一个类似名称的函数与Win API对应,因此3环切换到0环前,要做好参数的转换工作,具体而言,就是将3环的参数转化为0环的参数,在CreateProcessInternal中的有相关代码会初始化AttributeList,其目的就是在此。


检查lpProcessInformation并决定流程

    我们知道,lpProcessInformation是一个传出参数,会带出关于新进程的信息。在CreateProcessInternal中会初始化局部变量ProcessInformation结构体,判断参数lpProcessInformatio是否有效以及CreationFlags是否包含CREATE_UNICODE_ENVIRONMENT标志位,如果均成立则调用RtlCreateEnvironmentEx创建新进程环境块:


lpStartupInfo的处理

    因为lpStartupInfo参数记录有与新进程的窗口UI有关的信息,而Windows从16位到32位再到我们现在分析的64位,体系有几代改变,所以对这个参数,在CreateProcessInternal中,大部分代码目的在于保证其兼容性,细节如下:
首先,保存参数lpStartupInfo内容到局部变量中。

检测lpStartupInfo中是否包含扩展数据,如果包含扩展数据,则调用BasepConvertWin32AttributeList进行转换。

检测是否16位程序(CREATE_SEPARATE_WOW_VDM),因为64为系统不再兼容16位程序了。
在确认不是16为程序后,,调用IsProcessInJob判断ProcessHandle是否在jobs里面,在的话dwCreationFlags的CREATE_SEPARATE_WOW_VDM标志位置1,CREATE_SHARED_WOW_VDM位置0。

判断StartupInfo.dwFlags是否包含STARTF_USECOUNTCHARS | STARTF_USEHOTKEY | 0x400,如果包含的话StartupInfo.dwFlags的STARTF_USECOUNTCHARS标志位置1。


lpCurrentDirectory的处理
    如MSDN中所说,这个参数制定新进程的当前全路径,也可以填NULL,若为NULL的话,则新进程的路径与当前创造它的进程路径一致。
可想而知,CreateProcessInternal中对这个参数的处理,就是根据字符串找出对应的全路径,细节如下:
判断lpCurrentDirectory是否为NULL,不为NULL则申请堆空间,调用GetFullPathNameW由字符串获取对应的全路径,调用GetFileAttributesW获取结果属性并判断结果是否为路径(包含FILE_ATTRIBUTE_DIRECTORY标志),是则释放资源并返回,否则判断字符串最后一位是否是字符’\’,不是的话则对文件全路径拼接上字符“\0”。


接着,调用BaseFormatObjectAttributes通过参数ProcessAttributes格式化ProcessObjectAttributes对象,通过参数ThreadAttributes格式化ThreadObjectAttributes对象。
这也是为3环参数转0环参数做准备。


bInheritHandles参数的处理
这个参数的处理很简单:
判断bInheritHandles是否为TRUE,为TRUE则hDebug |= 4;
为FALSE则hDebug &= 0xFFFFFFFB。即hDebug的第2位(0位算起)是bInheritHandles的标志位。


CreateInfo的初始化
    分析至此,CreateProcess中的所有参数,只剩lpEnvrionment没有处理了,而lpEnvrionment作为可以填NULL(直接继承自当前进程)的参数,对创建进程成功与否并不那么至关重要。换句话说,集合以上所有参数的信息,CreateProcessInternal已经具备创建新进程的条件了,所以它也确实是这样做准备的,在局部变量中,有一个CreateInfo变量,函数将对它进行处理:
0环创建完信息后,需要将新进程的相关信息带出,这些信息就放在CreateInfo中
CreateInfo的初始化,将CreateInfo->cb(结构体大小)填0x58,CreateInfo->Flags2 |= 2 | 1,
CreateInfo->ImageCharacteristics = 0x2000,CreateInfo.DesiredAccess = 0x81;


1.判断Unkown、bInheritHandles、ProcessHandle是否为NULL,StartupInfo.dwFlags是否包含STARTF_USESTDHANDLES,dwCreationFlags是否包含CREATE_NO_WINDOW | CREATE_NEW_CONSOLE | DETACHED_PROCESS,条件都不成立的话,则取PEB->ProcessParameters的成员,循环3次计算出一个值,然后用这个值填充AttributeList.Entry为2000Ah的AttributeList.Entry.Value。

2.比较dwCreationFlags 是否包含DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS或者PEB->ReadImageFileExecOptions是否为NULL,都满足的话
CreateInfo.UnKown0 = var_CreateInfo.UnKown0 & 0xFE | 2;

都不符合的话判断bIsRealStatus是否为TRUE,为true则置位bIsRealStatus为FALSE,CreateInfo.UnKown0 = CreateInfo.UnKown0 & 0xFD | 1;

CreateInfo.Flags2| = 1;
CreateInfo.ImageCharacteristics = 0x2000;
CreateInfo.DesiredAccess = 0x81;

而CreateProcess中的lpApplicationame(或lpCommandLine,视传参情况而定)也已经被整理成了DosFileName。在此,判断DosFileName是否为NULL,为NULL则申请堆空间。


lpCommandLine的格式化
    在我们前文中提到,在CreateProcessInternal的最开始,就已经检查了lpCommandLine,但并没有对lpCommandLine做更多的处理。
    这其实也很好理解,因为lpCommandLine作为传入的命令行参数,其可能的情况众多。进行相应的字符串分割、格式化的工作必定会要比较复杂的流程,若一开始就做了这些工作,结果因为其它参数不正确而不能创建进程,那么是比较浪费资源的。
    所以,这些工作被放在了此处,虽然业务逻辑稍显复杂,但我们只要把握,此处的所有工作,都只是对命令行参数进行分割、格式化处理,就不难理解了:
    检测lpCommandLine字符中是否包含’”’引号,’ ’空格,’\t’制表符,找到的话则调用BaseGetProcessExePath获取文件路径,调用SearchPathW查找指定文件,并调用GetFileAttributesW获取文件属性。判断参数是否需要拼接”\””引号。


进入0环前的参数转换
    至此,几乎所有的参数检查、处理工作都已经完成,可以准备进入0环了,于是对各参数进行集中处理:
    要进入内核了,将DOS文件路径格式的(C:\\WINDOWS\\XXX)DosFileName转为Nt格式文件路径(\\Device\\HarddiskVolume1\\WINDOWS\\XXX),并转为UnicodeStringEx、调用RtlDetermineDosPathNameType_U判断文件路径类型。
其中参数DosFileName的值取决于:如果CreateProcessInternalW的参数lpApplicationName不为NULL,DosFileName为lpApplicationName,如果lpApplicationName为NULL,参数lpCommandLine不为NULL,DosFileName为lpCommandLine。


如果不符合上面的盘符类型或者DosFileName路径无效,则重新获取全路径,并将路径地址给FileName。


进入0环前的最后一棒--内部函数BasepCreateProcessParameters
    BasepCreateProcessParameters是一个内部API。调用BasepCreateProcessParameters,可根据FileName、bInheritHandles、Environment、StartupInfo这些信息,创建RTL_USER_PROCESS_PARAMETERS结构体作为0环参数函数的第九个参数。

BasepCreateProcessParameters的函数实现比较简单,BasepCreateProcessParameters函数原型如下:
PRTL_USER_PROCESS_PARAMETERS __fastcall BasepCreateProcessParameters(
                        LPCWSTR    DosFileName,
                        LPCWSTR    FileName.Buffer, 
                        LPCWSTR    lpCurrentDirectory,       //GetFullPathName获取到的CurrentDirectory
                        LPCWSTR    CommandLine, 
                        LPVOID    lpEnvironment, 
                        LPSTARTUPINFOEX  lpStartupInfo, 
                        DWORD    dwCreationFlags, 
                        BOOL    bInheritHandles 
                        )

其内部直接调用RtlCreateProcessParametersEx,RtlCreateProcessParametersEx的函数原型如下:
NTSTATUS __fastcall RtlCreateProcessParametersEx(
PRTL_USER_PROCESS_PARAMETERS pProcessParameters,
PUNICODE_STRING  ImagePathName,
PUNICODE_STRING  DllPath,
PUNICODE_STRING  CurrentDirectory
PUNICODE_STRING  CommandLine,
PWSTR            Environment,
PUNICODE_STRING  WindowTitle,
PUNICODE_STRING  DesktopInfo,
PUNICODE_STRING  ShellInfo,
PUNICODE_STRING  RuntimeData
DWORD           UNKONW);

传参过程为:
PCWSTR pDllPath = BaseGetProcessDllPath(FileName.Buffer, lpEnvironment, &i64DLLPath);
RtlInitUnicodeStringEx(&ImagePathName, FileName_Buffer);
RtlInitUnicodeStringEx(&DllPath, pDllPath);
RtlInitUnicodeStringEx(&usCommandLine, CommandLine);
RtlInitUnicodeStringEx(&usCurrentDirectory, lpCurrentDirectory);
RtlInitUnicodeStringEx(&DestinationString, lpStartupInfo->lpDesktop);
RtlInitUnicodeStringEx(&ShellInfo, lpStartupInfo->lpReserved);
RtlInitUnicodeStringEx(&usTitle, lpStartupInfo->lpTitle);
RtlCreateProcessParametersEx(&pProcessParameters, 
              &ImagePathName, 
              &DllPath, 
              &usCurrentDirectory, 
              &usCommandLine, 
              lpEnvironment, 
              &usTitle, 
              &DestinationString, 
              &ShellInfo, 
              &cbReserved2);
调用后
pProcessParameters-> StartingX = lpStartupInfo.dwX;
pProcessParameters-> StartingY= lpStartupInfo.dwY;
pProcessParameters-> CountX= lpStartupInfo.dwXSize;
pProcessParameters-> CountY= lpStartupInfo.dwYSize;
pProcessParameters-> CountCharsX= lpStartupInfo.dwXCountChars;
pProcessParameters-> CountCharsY= lpStartupInfo.dwYCountChars;
pProcessParameters-> FillAttribute= lpStartupInfo.dwFillAttribute;
pProcessParameters-> WindowFlags= lpStartupInfo.dwFlags;
pProcessParameters-> ShowWindowFlags= lpStartupInfo.wShowWindow;

RtlCreateProcessParametersEx的实现主要为:
Length = ((DllPath->MaximumLength + 3) & 0xFFFFFFFFFFFFFFFC)
                   + ((WindowTitle->MaximumLength + 3) & 0xFFFFFFFFFFFFFFFC)
                   + ((unLength + 5) & 0xFFFFFFFFFFFFFFFC)
                   + ((ShellInfo->MaximumLength + 3) & 0xFFFFFFFFFFFFFFFC)
                   + ((DesktopInfo->MaximumLength + 3) & 0xFFFFFFFFFFFFFFFC)
                   + ((CommandLine->Length + 5) & 0xFFFFFFFFFFFFFFFC)
                   + ((RuntimeData->MaximumLength + 3) & 0xFFFFFFFFFFFFFFFC)
                   + 0x608;
pProcessParameters->MaximumLength = Length;
pProcessParameters->Length = Length;
pProcessParameters->DebugFlags = 0;
pProcessParameters->CurrentDirectory.Handle = 0;
pProcessParameters->Environment = lpEnvironment;
pProcessParameters->Flags = 1;
其他相关实现可参考ReactOS的RtlCreateProcessParameters实现方法


调用NtCreateProcess
    经过以上所有准备过后,CreateProcessInternal内部会调用NtCreateProcess,进入0环处理各项事情。相关的分析工作,我们会在这个系列的后续文章中剖析。
    在这里,先把它当黑箱看待就好。


从0环返回后
从0环返回后

    首先,会调用RtlDestroyProcessParameters函数,将之前合并各参数而成的NtCreateUserProcess第九个参数ProcessParameters释放掉。
    接着,判断NtCreateProcess是否调用成功,若成功,还要经过一系列的检查,如上图中的BasepCheckImageVersion、BasepCheckWebBladehashes等。
    这些检查从符号和流程上看,与PE文件的结构有关,但是,关于PE结构的解析与检查的实质工作,都是在0环做的,这里的检查,可能也只是微软为未来可能的变动留下的接口(机会)。总之,经实测,这么多实测检测函数中,只有BasepCheckImageVersion会根据PE数据的不同而改变流程:
    BasepCheckImageVersion判断创建的进程子系统版本号是否低于3.10,并且版本号不大于当前系统的版本号,否则程序返回并报错is not a valid Win32 application。
    另外,在调用BasepIsProcessAllowed函数的过程中,有一个劫持点。
    因为在BasepIsProcessAllowed内部,为提供给Windows XP Embedded系统的程序有一些线程开跑前初始化的机会,判断一个全局的函数指针是否为NULL,若不为NULL,则调用之。除Windows XP Embedded系统外,这个全局针指都是NULL,但是,若我们将其填充我们自己指定的函数地址,则可以使指定代码在线程开跑前运行。

    最终,所有工作做完后,CreateProcessInteral调用NtResumeThrad来恢复新进程的主线程,或者直接返回,等待用户去恢复线程,总之新进程就愉快地跑起来了。

    在下一篇文章中,我们将进入到NtCreateProcess内部,剖析0环中做了哪些工作。

    总图


附件
doc版 CreateProcess3环工作.doc
pdf版 CreateProcess3环工作.pdf
idb文件 kernel32.rar

[课程]Linux pwn 探索篇!

上传的附件:
收藏
免费 9
支持
分享
最新回复 (27)
雪    币: 310
活跃值: (2237)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2016-1-26 11:44
0
雪    币: 60
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
精前留名,
非常喜欢这种做学问的方式,知其所以然。
希望可以做一份PDF存档~
2016-1-26 12:16
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
4
风格不错,内容详细清楚~ 另外问下楼主的流程图用什么画的
2016-1-26 12:25
0
雪    币: 3092
活跃值: (1599)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
5
微软自家的vivso,用起来还是蛮方便的
2016-1-26 13:25
0
雪    币: 3092
活跃值: (1599)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
6
微软自家的vivso,画流程图感觉还是蛮方便的
2016-1-26 13:26
0
雪    币: 3092
活跃值: (1599)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
7
pdf格式已上传,就是后面图太大,网页上看起来好点,doc和pdf看着都不太方便
2016-1-26 16:14
0
雪    币: 44229
活跃值: (19965)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
8
帖图的方法不对,参考这帖:http://bbs.pediy.com/showpost.php?postid=292659
2016-1-26 16:17
0
雪    币: 60
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
居然不是放在尾部~
2016-1-26 17:40
0
雪    币: 129
活跃值: (333)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
xed
10
过来学习了。期待r0的分析
2016-1-26 19:04
0
雪    币: 878
活跃值: (496)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
11
3环...ring3为何不是环3
2016-1-26 19:38
0
雪    币: 3092
活跃值: (1599)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
12
谢谢老大提醒,已修正
2016-1-26 22:47
0
雪    币: 15686
活跃值: (3847)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
感谢分享,收藏了
2016-1-26 23:50
0
雪    币: 2664
活跃值: (3401)
能力值: ( LV13,RANK:1760 )
在线值:
发帖
回帖
粉丝
14
排版不错... support
2016-1-27 00:45
0
雪    币: 99
活跃值: (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
mark
2016-1-27 01:26
0
雪    币: 16444
活跃值: (2463)
能力值: ( LV9,RANK:147 )
在线值:
发帖
回帖
粉丝
16
科锐也开始放大招了。不错,mark。流程图要给个赞
2016-1-27 09:34
0
雪    币: 43
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
Mark这个系列
2016-1-27 15:56
0
雪    币: 14
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
活捉 老钱徒弟。。。
2016-1-27 22:39
0
雪    币: 284
活跃值: (250)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
19
奈斯,写的太好了
2016-1-27 23:48
0
雪    币: 50
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
感谢分享!努力学习!
2016-2-2 21:10
0
雪    币: 160
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
; )
当年第三阶段的项目我也分析过64位的,但是没形成文档,自己对比了一下跟32位的区别就完事了
2016-2-5 12:41
0
雪    币: 14
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
学习了...
2016-2-16 11:55
0
雪    币: 522
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
23
mark  期待下一篇
2016-2-19 11:38
0
雪    币: 346
活跃值: (25)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
24
好多大牛啊! 感谢!
2016-4-21 09:34
0
雪    币: 244
活跃值: (169)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
好文,谢谢
2019-1-10 14:48
0
游客
登录 | 注册 方可回帖
返回
//