OSX下调试Flash插件

其实无论调试什么,都会发现lldb的功能朴实的让人心急如焚。比如Windows调试器基本都会自动记录上一次的断点信息,每次调试时根据模块位置重新下好端点。

lldb可能天生就是为源码调试准备的,一旦没有源码,根据模块名+偏移的下端点方式它是无论如何都不能识别。好在它提供了python接口,方便开发调试插件弥补自身的缺陷。

所以与其说lldb是个调试器,不说它是个SDK,只有基于它开发出来的图形调试器才具备实用性。

那就来看看一些在Windows调试时不值得一提的简单操作,在lldb下该如何达阵

附加Flash进程


用Safari打开指定页面后,执行以下脚本lldb就会附加到包含Flash的进程上了

pid=$(ps aux | grep WebKit.Plugin | grep 64 | awk '{print $2}'|sort| tail -1) 
lldb -p $pid

搜索内存中的指定常数


由于没有查看memory layout的命令,只能借助vmmap,先找到比如malloc的内存区域,然后再逐一生成查找命令。以下命令用于从WebKit找到HeapSpray的特定字符:

pid=$(ps aux | grep Build | grep web | awk '{print $2}' | sort | tail -1)
vmmap $pid | grep "WebKit Malloc " | grep "-" | awk '{print $3}' | awk -F '-' '{print "memory find -e '$1'","0x" $1, "0x" $2}

在模块固定偏移下断点


这个听起来是最稀松平常的任务了,比如打算在Flash的0x78A4C0偏移处下一个断点,而且要在每次lldb附加后自动完成。首先要编写一个lldb的插件,完成Flash模块基地址的查找和断点地址的计算,最后下断点:

import lldb
import shlex
import optparse
import time

def obreak(debugger, command, result, dict):
  command_args = shlex.split(command)
  parser = create_obreak_options()
  try:
    (options, args) = parser.parse_args(command_args)
  except:
   return
  if len(args) > 0:
    offset = int(args[0], 16)
  else:
    offset = 0x78A4C0
  target = debugger.GetSelectedTarget()
  base = 0
  for mod in target.modules:
    if mod.file.GetFilename() == "FlashPlayer-10.6":
      for sec in mod.sections:
        if sec.name == "__TEXT":
          base = sec.get_addr().load_addr
  address = base+offset
  target.BreakpointCreateByAddress(address) 

def create_obreak_options():
  usage = "usage: %prog -f offset"
  description='''break on offset_belongs_to_Flash'''
  parser = optparse.OptionParser(description=description, prog='obreak',usage=usage)
  parser.add_option("-f", "--offset", dest="offset", help="break on certain offset of Flash", metavar="OFFSET")
  return parser

def __lldb_init_module (debugger, dict):
  parser = create_obreak_options()
  obreak.__doc__ = parser.format_help()
  debugger.HandleCommand('command script add -f %s.obreak obreak' % __name__)

但这还不够,还需要在.lldbinit.rc文件中增加一些辅助代码,保证上述插件的自动执行

command script import ~/path_to_obreak.py
target stop-hook add -o "script '--loading script--'"
target stop-hook add
obreak
target stop-hook disable 2
target stop-hook disable 3

Linux下运行Flash独立播放器

Adobe很早就停止了对Linux下Flash的功能更新,和最新的Flash 18相比,Linux下的Flash仍然停留在11.2。

即便如此,Flash 11.2日常播放个简单的动画还是绰绰有余的,点击下载Flash独立播放器

直接运行绝对报错,形式类似:

(flashplayer:23126): GLib-GObject-WARNING **: instance with invalid (NULL) class pointer
(flashplayer:23126): GLib-GObject-CRITICAL **: g_signal_handlers_disconnect_matched: assertion 'G_TYPE_CHECK_INSTANCE (instance)' failed

官方也没详细说明,后来在64位的ubuntu 14上折腾一阵,发现安装以下的依赖库就OK了

sudo apt-get install 
  libglib2.0-0:i386 
  libxt6:i386 
  libxcursor1:i386 
  libnss3:i386 
  libgtk2.0-0:i386 
  libcurl3:i386 
  libxcursor1:i386

反编译doswf加密的Flash

使用诸如Sothink SWF Decompiler等软件可以很容易反编译不加保护的Flash程序。如果Flash内使用了秘密的网络通信或引入了一段漏洞利用代码,编写者不会希望自己的劳动成果被轻易获取的。

doswf用于给Flash程序加壳,混淆变量、函数和类名,增加逆向的难度。最近拿到了一份使用Flash编写的exploit,想要分析当中触发代码,但Flash已经使用doswf做了处理,直接反编译查看代码行不通。

根据近一段时间的分析,doswf在加密Flash时做了以下三个工作:

  1. 在原始的Flash程序中加入花指令,混淆变量函数类名
  2. 使用ByteArray的压缩算法压缩原始Flash,并针对压缩后数组做逐字节的加密处理,并将加密后的ByteArray附到doswf生成的一个Flash程序中,doswf的Flash程序会动态解码释放原始Flash
  3. 在doswf生成的Flash程序中加入花指令,混淆变量函数类名

花指令主要用于对抗反编译引擎。

Flash中的ActionScript源码被编译成字节码,类似于C语言代码被编译成机器码。对于C语言编写的可执行程序,由于汇编指令长度可变,通过在机器码中加入花指令(je jne 的组合等)可以造成反汇编引擎错误的解释机器码。

对于Flash编译的字节码,由于指令长度固定为一个字节,无法通过加入花指令实现字节码级别的解析错误,但反编译引擎在尝试将包含花指令的字节码转化为ActionScript源码时会发生错乱,导致Sothink SWF Decompiler无法正常工作(程序停止响应)。

混淆变量函数和类型名是一种不可逆的过程doswf直接修改了Flash的常数表,用一些特殊符号替代用户自定义的名称。不过名称的混淆对分析代码影响有限,毕竟ActionScript的运行时类、成员函数和成员变量的调用仍然保持了原有名字。

压缩与加密Flash虽然很靠静态处理很棘手但考虑到Flash最后一定会被动态解码加载到内存中,处理它可以靠动态内存抓取。

综合上述的特性,下面通过实际的例子说明如何得到原始Flash的反编译结果。

其实整个问题的关键就是针对1和3中提到的花指令的去除,而且对于3和2,除非你对doswf的加解密算法感兴趣,不然使用动态抓取就很容易绕过了。

我使用debug版的flashplayer加载执行目标Flash程序,相比使用浏览器,干扰更少,内存更小,抓取速度和准确度都会快很多。在内存中搜索FWS和CWS(Flash的文件头部的magic word)字样可以找到疑似的Flash程序,根据Flash文件格式中标识长度的字段能够从内存中将它直接Dump出来。实现类似功能的有很多类似软件,比如SwfReader.jar。Dump结果可能有二三十个,文件过小过大都能迅速排除,再使用swfretools格式解析器观察每个SWF文件,如果解析正确,并且包含DoABC(Flash程序的代码段),则予以保留。

通过动态抓取可以得到加花后的原始Flash程序。我分析的这个由doswf处理的Flash程序并没有使用过于复杂多样的加花指令,仅是

原始代码A

原始代码B

转化为

原始代码A

jmp [OFFSET]

一些奇怪的字节码

label: [OFFSET]

原始代码B

每隔几十行字节码就会出现一次这种插入,也正是它们导致Sothink Decompiler崩溃。

去掉它们的算法也很基本,对代码段中的所有ActionScript方法进行扫描,将原始代码A和B之间的字节码全部使用空指令代替(0x02)。我写了个一个Python脚本完成这个工作,使用时需要修改Python代码中的两个偏移值,指向method_bodies的开始和结尾。

处理后的Flash文件就可以用Sothink反编译了。上述方法不见得universal,也许只是我遇到的一个特殊情况,doswf如果引入多种花指令,去花更为繁琐。或者新版本的Sothink如果已经更为强大,能够自行避开花指令,分析Flash也就不需要这么折腾了。