CreateProcessA参数型Shellcode的编码问题研究

宣布时间 2021-12-22
克日 ,在对WebAccess/SCADA系统的误差研究中 ,尊龙凯时ADLab的工控清静研究员发明了一个未被普遍谈论的误差使用手艺问题 ,即经由CreateProcessA参数举行转达的shellcode的编码问题。


简朴来讲 ,该控制系统的误差由两个程序组成:焦点程序CoreProcess和辅助程序HelpProcess ,焦点程序CoreProcess通过系统函数CreateProcessA来启动HelpProcess(同时转达了相关参数)。其中 ,CoreProcess的简化代码如下:


代码.png


显然 ,HelpProcess的WinMain函数保存一个经典的栈溢出误差。当lpCmdLine的数据长度凌驾400字节时 ,对buff的strcpy操作就会爆发溢出;当长度凌驾404字节时 ,就会笼罩到eipCallerNext ,从而挟制HelpProcess的程序控制流。


回溯代码可知 ,lpCmdLine的数据泉源是CoreProcess的CreateProcessA挪用 ,且是用户可控的。因此 ,该误差的使用看起来是简朴的 ,只需要盘算好eipCallerNext的偏移量并使用shellcode填充buff即可。该误差的使用链和客栈结构如下所示:


回溯代码.png

 

在使用历程中 ,接纳测试填充字符举行溢出时 ,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在程序开发上都无需做任何特另外处置惩罚。


代码.png


通常情形下 ,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 ,如下所示:


 字节.png


别的 ,第2字节的现实有用取值尚有更多约束。好比 ,第2字节不可为0X7F。针对某些取值的字节 ,第2字节的取值比[0x40, 0xFE]的空间更小。如下图所示 ,有的只能取该空间的后半部分 ,有的则只能取前半部分。


关于shellcode来讲 ,其每个字节的取值在0到255之间都是完全正当的。因此 ,本文的误差使用要实现shellcode的随意替换 ,必需要有一种要领来对shellcode中违反GBK编码的字节举行处置惩罚 ,从而阻止Ansi字符和Unicode字符间转换导致的shellcode字符被改变的问题。一个基本的要领是凭证如下的流程对shellcode举行处置惩罚 ,其要害是对GBK表举行查表并修正汇编指令。


 字节调解.png


以如下的shellcode为例 ,在扫描到字节0xEB时 ,发明是非ASCII字符且查表GBK效果是不保存 ,需要举行转换;盘问GBK表后发明 ,在0xEB之前插入0x90可以使得90 EB是一个正当的GBK字符 ,同时90EB 38又不改变原来的汇编语义 ,转换乐成。同理 ,继续扫描到下一个字节0XEB时 ,再做同样的转换就可以。可是 ,第2次的转换插入了新的字节0x90 ,导致了原始lab1对应的偏移量爆发了改变;原始lab的指令现实位于转后的lab+1位置 ,使得第一个0XEB的语义不法了。因此 ,转换历程还要求跟踪指令区块的长度转变。


转换汇编.png


除了指令区块的长度改变外 ,尚有其它兼容性问题。好比 ,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影响。