CreateProcessA参数型Shellcode的编码问题钻研
颁布功夫 2021-12-22单一来讲,该节造系统的缝隙由两个法式组成:主题法式CoreProcess和辅助法式HelpProcess,主题法式CoreProcess通过系统函数CreateProcessA来启动HelpProcess(同时传递了有关参数)。其中,CoreProcess的简化代码如下:

显然,HelpProcess的WinMain函数存在一个经典的栈溢露马脚。当lpCmdLine的数据长度超过400字节时,对buff的strcpy操作就会产生溢出;当长度超过404字节时,就会覆盖到eipCallerNext,从而劫持HelpProcess的法式节造流。
回溯代码可知,lpCmdLine的数据起源是CoreProcess的CreateProcessA挪用,且是用户可控的。因而,该缝隙的利用看起来是单一的,只必要推算好eipCallerNext的偏移量并利用shellcode填充buff即可。该缝隙的利用链和仓库布局如下所示:

在利用过程中,选取测试填充字符进行溢出时,eipCallerNext的覆盖总是正确的;但选取metasploit的shellcode来溢出时,eipCallerNext的覆盖就变得不正确。对数据进行比力后发现,shellcode在CoreProcess和HelpProcess是不一样的,即shellcode传递到HelpProcess后产生了扭转。此表,通过尝试metasploit的分歧shellcode,发现这种扭转没有显著的法规可循。
针对这个问题,ADLab的安全钻研员进行了深刻的分析,弄清了CreateProcessA参数传递的shellcode的编码问题,并开发了自动化处置步骤,从而兼容肆意shellcode。
CreateProcessA的参数处置
Windows操作系统的内核是支持全球各类说话的,其提供统一的Unicode编码型内核态API;针对具体的国度或地域,Windows通过区域编码来实现本地说话支持,即Ansi字符串型的用户态API。这些用户态API在内部先把Ansi字符串转换为Unicode字符串,而后再挪用内核态API;这个转换过程是通明的,用户编写的法式对此无感知。
在Window操作系统上,1个Unicode字符由2个字节组成,1个Ansi字符由1个字节或2个字节组成。当首字节的值是0到127时,它是1个ASCII字符,对应Unicode字符的2字节的内容就是该ASCII字符加1个填充字符0;例如,Ansi字符”A”,其对应的Unicode字符是”A\x00”。当首字节的值大于127时,则当前字节和下个字节组合起来是一个区域说话的字符,区域说话字符存在对应的Unicode字符映射表;例如,”\xce\xd2”的“\xce”不是1个合法的ASCII字符,它只能和“\xd2”结合作为1个中文字符“我”,对应的Unicode字符是”\x11\x62”。
如下所示,CreateProcessA就是一个Ansi编码型的用户态API,字符串”AAAA”会被自动转换为Unicode字符串并传递给HelpProcess,而后在挪用WinMain之前又被自动还原为Ansi字符串。因而,对于Ansi字符串”AAAA”,CoreProcess和HelpProcess在法式开发上都无需做任何额表的处置。

通常情况下,CreateProcessA参数lpCmdline的起源是靠得住的,好比编译时预约义的字符串和API的返回值,此时lpCmdline都是正确的Ansi字符串。因而,CreateProcess险些总能在Unicode和Ansi之间自由地正确转换。
现实上,对于任何一门区域说话,其Ansi字符和Unicode字符的映射都不是逐一映射关系;即在2字节的全数取值空间中,Ansi字符表的有效项数总是幼于Unicode字符表的有效项数。这意味着,针对无法确认是区域说话的2个字节,若是强造视作Ansi字符则转换成Unicode字符后不愿定能还原为初始的Ansi字符。例如:”\xeb\x2a”是一条通例的jmp offset指令,它不是1个合法的中文字符;若是视作Ansi字符强造转换为Unicode字符则是”\x3f\x00”,再次转换为Ansi字符即是”?”,迷失了jmp offset指令的语义。
因而,通过CreateProcessA的cmdline参数进行shellcode传递,必必要思考区域说话的Ansi字符和Unicode字符相互转换的问题。
在本文的缝隙利用案例中,本地域域的说话是中文简体,对应Ansi编码表是GBK。因而,必必要对metasploit的shellcode进行GBK编码,确保其是正确的Ansi字符串。
GBK表的编码在2字节取值空间的领域是8140-FEFE,即第1字节的取值领域是0x81到0xFE,第2字节的取值是0x40到0xFE,如下所示:

此表,第2字节的现实有效取值还有更多约束。好比,第2字节不能为0X7F。针对某些取值的字节,第2字节的取值比[0x40, 0xFE]的空间更幼。如下图所示,有的只能取该空间的后半部门,有的则只能取前半部门。
对于shellcode来讲,其每个字节的取值在0到255之间都是齐全合法的。因而,本文的缝隙利用要实现shellcode的轻易代替,必必要有一种步骤来对shellcode中违背GBK编码的字节进行处置,从而预防Ansi字符和Unicode字符间转换导致的shellcode字符被扭转的问题。一个根基的步骤是依照如下的流程对shellcode进行处置,其关键是对GBK表进行查表并建改汇编指令。

以如下的shellcode为例,在扫描到字节0xEB时,发现长短ASCII字符且查表GBK了局是不存在,必要进行转换;查问GBK表后发现,在0xEB之前插入0x90能够使得90 EB是一个合法的GBK字符,同时90EB 38又不扭转原来的汇编语义,转换成功。同理,持续扫描到下一个字节0XEB时,再做同样的转换就能够。但是,第2次的转换插入了新的字节0x90,导致了原始lab1对应的偏移量产生了扭转;原始lab的指令现实位于转后的lab+1地位,使得第一个0XEB的语义犯法了。因而,转换过程还要求跟踪指令区块的长度变动。

除了指令区块的长度扭转表,还有其它兼容性问题。好比,shellcode中特殊取值(典型有0)的字节处置问题,对shellcode的内嵌参数批改问题等。因而,只管查表转换是最底子的法子,但全表查问的空间大,限度了shellcode的矫捷性。为相识决该问题,ADLab的安全钻研员提出了一种基于推算的shellcode编码步骤。
Shellcode推算转换
首先,我们把shellcode分为两部门:头部的固定decoder和尾部的多变payload。而后,选取查表方式进行手工编写切合GBK编码的汇编代码。其中,decoder的长度很有限,决定了这个编写的价值不大;同时,多变payload是没有额表限度的,通过编写对应的encoder来编码payload使其不违反GBK编码,又能够被decoder还原。通过这种方式,对原始shellcode的选择和扭转就齐全不用关切GBK编码问题,使得该缝隙的利用越发丰硕。
为了削减decoder的体积,我们设计了一种推算步骤来编码和解码,这样就不必要存储GBK字符表或者复杂的规定。原始shellcode编码时的推算规定如下:
遇到字节是ASCII、0x80和0xff,直接保留。
遇到字节是\x00,转换成加法运算符\x90和2个推算数符\x80和\x80。
遇到字节是\x90,转换成加法运算符\x90和2个推算数符\x48和\x48。
遇到2个字节能够转换为unicode字符,直接保留这2个字节。
遇到前面都不能处置的字节,直接转换成加法运算符\x90和2个推算数符,第1个是\x80,第2个是差值。
选取上述的编码步骤后,任何shellcode都能够被转换为合法GBK字符串,并且decoder对payload的解码推算也极度单一,只必要如下的1条文则:
遇到字符是\x90,直接对后2个字符进行加法推算,并用了局代替字符\x90。
至此,CreateProcessA参数传递的shellcode的编码问题就全数被约束在了只有一条文则的decoder代码中,很显然这是一个天堑极度明确的部门问题,因而很容易就解决了。选取这种步骤,本文的缝隙利用能够轻易挪用metasploit中的shellcode,无需再不安它们的指令内部细节。
在多说话环境下,shellcode若是不是直接的内存传递,则可能会被系统API函数所转换,从而导致其因在获得执行权之前产生内容扭转而无效。因而,在缝隙利用过程中,必要把稳shellcode是否受到多说话版本的API影响。


京公网安备11010802024551号