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号