导语:Ghostscript是PostScript和PDF文件的解释器,有助于调试PostScript shellcode。

 

我最近接触了威胁研究领域的一位朋友,他正在研究PostScript中shellcode提取的问题。PostScript是一种由Adobe Systems开发的简单的解释性编程语言,具有强大的图形功能,已集成到当今大多数现代打印机中。在过去几年中,该软件一直是攻击者的目标,进行了多次臭名昭著的攻击,包括FortiGuard Labs去年发现的利用CVE-2015-2545 Encapsulated PostScript(EPS)漏洞一项行动。攻击者利用PostScript中发现的漏洞通常会使用编码算法对其进行混淆,从而使分析人员的工作更加艰难。因此,如果你是那些只具有PostScript基本知识的分析人员,那么可能会发现静态地理解混淆代码很难。如果确实存在这种情况,也很平常。这就是为什么我决定写一篇关于如何分析PostScript的快速指南。

PostScript 101

有些读者可能会问,如果只是想知道它的行为的话,为什么需要静态分析PostScript。不幸的是,一些恶意样本在执行时不能动态工作,可能是由于各种原因,包括不同的测试环境,如不兼容的操作系统或软件版本,损坏或截断的文件等。那些黑盒分析是不可行的,分析人员需要能够对恶意样本静态分析。在我的朋友引介下,我看到了一个shellcode,其中包含用PostScript编写的解码例程,如清单1所示。乍一看,我认为静态解码很容易,但经过几次试错尝试我没有获得解码的shellcode。显然,主要原因是因为首先我不了解PostScript。所以我决定玩玩PostScript,这是我一直想尝试的东西。

Line 1:/shellcode <..redacted..> def
Line 2:0 1 shellcode length 1  
Line 3:{  
Line 4:  /xor_proc exch def shellcode dup  
Line 5: xor_proc get 16#29 xor  
Line 6: xor_proc xor 255 and xor_proc exch put  
Line 7:} for

清单1: PostScript写的shellcode

也许在搜索查询中没有使用正确的关键字,因为在找到Ghostscript之前我找不到任何关于PostScript shellcode分析的有用文章,Ghostscript是PostScript和PDF文件的解释器。

当我启动Ghostscript时,我很高兴看到类似于Python的交互式控制台的交互式shell提示符,这让我觉得我可以调试遇到的shellcode。如果刚开始尝试使用PostScript,可能会对PostScript命令的语法和顺序感到困惑,如清单1所示。幸运的是,一旦将命令输入Ghostscript的控制台,将获得更好的视感。值得一提的是,Adobe Systems对PostScript命令进行了详细记录。

您可能听说PostScript被描述为基于堆栈的语言,并想知道这意味着什么。这是清单2中的线索:

$ gs -dNODISPLAY  
GPL Ghostscript 9.18 (2015-10-05)  
Copyright (C) 2015 Artifex Software,
Inc. All rights reserved.  
This software comes with NO WARRANTY:
see the file PUBLIC for details.  
GS>1  
GS<1>2  
GS<2>stack  
2  
1  
GS<2>

清单2: Ghostscript 交互shell

你明白了吗?PostScript中的每个命令都被推送到操作数堆栈。换句话说,在PostScript中使用的最后一个命令将始终位于堆栈的顶部。如果熟悉逆向工程,我相信这个概念对你来说很熟悉。Ghostscript也足够用户友好,通过“<>”括号之间的数字来显示堆栈中的命令/元素数量。当想要清除堆栈时,可以使用clear命令,根据Adobe的术语,它也称为堆栈运算符。

$ gs -dNODISPLAY
GPL Ghostscript 9.18 (2015-10-05)  Copyright (C) 2015 Artifex Software,
Inc. All rights reserved.  
This software comes with NO WARRANTY:
see the file PUBLIC for details.  
GS>1  
GS<1>2  
GS<2>stack  
2  
1  
GS<2>clear  
GS>

清单3: Ghostscript堆栈操作符 – clear

之所以提到这一点,是因为在开始调试之前确保堆栈clear是很重要的,确保您不会弄乱要调试的内容。

PostScript Debugging 101

接下来我们准备查看清单4中显示的shellcode:

GS>/shellcode <414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141>
def  
GS>stack   
GS>

清单4: 将文本名称定义为变量

可以看出,在Ghostscript中输入的命令定义一个名为shellcode的变量。当名称附加正斜杠“/”并以def运算符结束时,它是一个文本名称。虽然括号“<>”中的数据被定义为十六进制字符串,但在此示例中,我们使用虚拟shellcode字节进行演示,因为我们获取的实际shellcode数据大约为10千字节。不幸的是,Ghostscript很难解析大数据,可能是由于内存溢出。

调试的一个重要方面是显示变量值。通常,变量值可以通过“=”,“==”和print操作符显示:

GS>shellcode ==
(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)
GS>shellcode =
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
GS>shellcode print
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

清单5: 检查变量值的运算符

基本上,如果我们将清单5中的命令分开,它们可以被读作

1.在操作数堆栈中推送shellcode值

2.Pop(==)来自操作数堆栈的值

值得一提的是“==”运算符类似于“=”,但它扩展了数组的值,如清单5所示。

到目前为止,我们已经讨论了如何解释PostScript命令,以及一些有助于调试PostScript的运算符。我们现在可以进入清单1中的第2行。它是'for循环'控制结构的开始。本质上,for循环控制结构的语法类似于“初始 增量 限制 {proc} for”:

GS>0 1 shellcode length 1 
GS<4>stack
1
48
1
0

清单6: for loop控制结构

当我们分解清单6中的命令时,可以解释为:

1.将0推送到操作数堆栈,这是初始值

2.将1推送到操作数堆栈,这是增量值

3.将shellcode对象推送到操作数堆栈。将length运算符(数组/字符串对象的特殊运算符)推送到操作数堆栈。它从前一个操作数获取一个参数,并返回元素数,即限制值。

4.将1推到操作数堆栈

GS<4>/xor_proc exch def shellcode dup  
GS<5>stack  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
48 
1
0 
GS<5>xor_proc ==  
1 
GS<5>

清单7: 清单1中的第4行命令

清单7中的命令可以如下定义:

1./xor_proc exch def =>定义变量xor_proc并将变量推送到操作数堆栈。然后它交换堆栈顶部两个元素的位置(xor_proc, 1, 48, 1, 0) – >(1, xor_proc, 48, 1, 0)。最后,def运算符获取堆栈顶部的值,将其分配给xor_proc,然后从堆栈中删除这两个值

2.将shellcode对象推送到操作数堆栈

3.复制shellcode对象

第5行和第6行,如清单1所示,包含实际的解码命令。执行第5行的命令后,会产生如下结果:

GS<5>xor_proc get 16#29 xor  
GS<5>stack
104 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
48 
1 
0

清单8: 清单1中第五行的命令

1.将xor_proc值推送到操作数堆栈,该堆栈用作shellcode数组对象的索引

2.将GET运算符(数组/字符串对象的特殊运算符)推送到操作数堆栈,以获取前一个操作数堆栈中指定的索引值。结果,shellcode [1]的值将被推送到操作数堆栈

3.将十六进制0x29压入操作数堆栈,然后将其用作XOR密钥。请注意,可以通过在数字前使用#标签符号来定义整数的基数(例如:16#<数字>表示十六进制数字,8#<数字>表示八进制数字)

4.将XOR运算符推送到操作数堆栈,从前两个操作数堆栈中获取值进行算术运算。结果为104(0x29 ^ 0x41)

如果我们扩展第6行中的命令,可以看到解码例程执行另一个XOR操作,该操作获取数组的当前索引XOR在清单8中的堆栈顶部中的值:

GS<5>xor_proc stack
1  
104  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
48  
1  
0  
GS<6>xor stack
105 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
48  
1  
0  
GS<5>255 stack
255  
105  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
48  
1  
0  
GS<6>and stack
105  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
48  
1  
0  
GS<5>xor_proc stack
1  
105  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
48  
1  
0  
GS<6>exch stack
105  
1  
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
48  
1  
0  
GS<6>put stack
48  
1  
0  
GS<3>shellcode == 
(AiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)

清单9: 情单1中第6行的命令

最后,如清单9所示,PUT运算符从堆栈中取出三个值(105,1,shellcode),并将第一个参数中指定的元素(105)存储到第二个参数中指定的数组/字符串对象(shellcode)的索引(1)。

通过分析,我们可以在Python脚本中演示清单1中的PostScript解码例程的更高层次表示,它包含两轮XOR操作,一个静态密钥0x29和一个动态密钥(它是以下字符串的索引):

#!/bin/python  
shellcode_fn ="postcript_shellcode.bin" 
buff = open(shellcode_fn,"rb").read() 
buff_len = len(buff) 
shellcode_defn ="postscript_shellcode_de.bin" 
out = open(shellcode_defn, "wb") 
for i in range(1, buff_len): 
[3 spaces indentation]res =ord(buff[i]) ^ 0x29
[3 spaces indentation]res = (res ^ i)& 0xff 
out.write(chr(res)) 
out.close()

总结

希望这篇对PostScript的介绍以及一些常见的PostScript操作符在调试PostScript时会有所帮助。也希望这可以让你了解在将来遇到时如何分析PostScript shellcode。最后一点,由于学习解码PostScript的原因,我能够通过检测从shellcode下载的有效载荷来帮助我的朋友。

IOC

Payload URLs:

[1] hxxps://tpddata[.]com/skins/skin-8.thm

[2] https://tpddata[.]com/skins/skin-6.thm

Payloads detections:

skin-8.thm – 4838f85499e3c68415010d4f19e83e2c9e3f2302290138abe79c380754f97324
(W32/Manuscrypt.BA!tr)

skin-6.thm – c10363059c57c52501c01f85e3bb43533ccc639f0ea57f43bae5736a8e7a9bc8 
(W64/Manuscrypt.BA!tr)

源链接

Hacking more

...