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

颁布功夫 2021-12-22
近日,,,,, ,在对WebAccess/SCADA系统的缝隙钻研中,,,,, ,8827太阳集团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影响 。。。。。。。