模糊测试: 初学者入门指南

模糊测试简介

模糊测试(Fuzzing), 简而言之, 就是为了触发新的或不可预见的代码执行路径或bug而在程序中插入异常的, 非预期的, 甚至是随机的输入. 因为模糊测试涉及到为目标提供大量的测试样例, 因此至少也会实现部分自动化. 模糊测试可以也应当用于测试每个需要接受某种形式输入的接口. 实际上, 模糊测试最起码就应该拿来用于测试每个从潜在恶意来源(比如互联网或用户提供的文件)获取输入的接口.

模糊测试是对其他测试技术的补充. 由模糊测试揭露出的问题往往是开发人员不太可能构建的输入(例如, 在处理一些边界情况, 数据正确性和错误处理例程时的输入)触发的. 在常规自动化测试过程中, 模糊测试扩大了代码覆盖范围, 提高了代码覆盖率测试程度. 通过模糊测试使用的非预期输入通常会触发一些平时不会触发的执行流.

很多地方都需要进行模糊测试. 它是你系统开发生命周期(SDLC)的一部分, 在这部分里, 你需要确保你完成了改善目标所要的系统性工作, 或是你只是想解决一些bug也行. 要如何费心于模糊测试取决于你的最终目标和相关资源, 本文只是帮你如何从模糊测试中获取更多的回报.

Continue reading

Posted in fuzzing | Leave a comment

检测Android虚拟机的方法和代码实现

刚刚看了一些关于Detect Android Emulator的开源项目/文章/论文, 我看的这些其实都是13年14年提出的方法, 方法里大多是检测一些环境属性, 检查一些文件这样, 但实际上检测的思路并不局限于此. 有的是很直接了当去检测qemu, 而其它的方法则是旁敲侧击比如检测adb, 检测ptrace之类的. 思路也很灵活. 最后看到有提出通过利用QEMU这样的模拟CPU与物理CPU之间的实际差异(任务调度差异), 模拟传感器和物理传感器的差异, 缓存的差异等方法来检测. 相比检测环境属性, 检测效果会提升很多.

Continue reading

Posted in android | Leave a comment

Intro to malware “TSCookie”

2018年1月17日左右, 社交媒体上开始出现一些关于恶意邮件的报道, 这些邮件声称来自日本的教育部, 文化部, 体育部和科技部. 这些邮件里包含有指向恶意软件”TSCookie”的URL链接(趋势科技将其称为为PLEAD恶意软件, 因为PLEAD取自趋势科技过往捕获到的一次APT攻击活动, 故本文中我们将该恶意软件命名为”TSCookie”). TSCookie在2015年在野外被发现, 并且怀疑黑客组织”BlackTech”与此次攻击活动有关. JPCERT/CC证实称, 使用恶意软件的敌对团伙已经对日本组织进行了针对性的攻击. 本文将介绍我们在分析TSCookie后的成果.

TSCookie概述

下图描述了TSCookie的执行流程:

1.png

TSCookie本身只是用作一个下载器, 通过从C&C服务器下载模块来扩展功能. 我们所检查的样本下载了一个具有传出信息和其他功能的DLL文件(以下简称”TSCookieRAT”). 下载的模块仅在内存上运行

TSCookie和TSCookieRAT的行为将在下面的章节中详细解释.

Continue reading

Posted in malware | Leave a comment

Solve the White Rabbit CrackMe

Crackme文件可以从此处下载: White Rabbit crackme!

因为crackme里稍微使用了混淆和一些像恶意程序的把戏, 所以可能会被一些杀毒软件标记为恶意程序, 所以也建议在虚拟机下运行.

这个crackme运行的截图如下:

1.png

OK, 首先要做的第一件事就是将其载入到IDA中(我这里使用的是刚刚发布的IDA 7的免费版本). 通过搜索字符串Password#1来看它的交叉引用以及前后都发生了些什么.

2.png

就这了! 我们可以看到它被sub_4034D0所引用. 现在我们将跟随到引用处, 来看看接下来发生什么

3.png

sub_403D90中有一些初始化操作, 随后在sub_404150的结果与可疑值0x57585384的比较后又一个分支跳转. 子分支中的sub_403990输出了一些提示语以及后续一些有关接受用户输入的内容.

我们首先来看初始化部分(sub_403D90):

4.png

函数取了两个参数, 内容看上去也非常清楚: 通过给出的标识符查找资源文件, 加载资源文件, 确定它的文件大小, 然后申请内存空间并将资源文件的数据复制进去. 该函数返回那个新申请的内存空间的指针, 并将资源文件的大小存储在第一个参数中.

现在我们唯一需要注意的就是图中的sub_406A70, 它取了3个参数(target pointer, source pointer 以及 data size)并且看起来非常像是memcpy(或memmove, 是哪个不重要, 因为内存区域没有重叠). 但是函数内的代码却包含有大量的分支, 难以分析. 所以我们不能确定它有没有在复制的过程中以某种方式修改了数据(比如, 解密数据). 最简便的检查方式就是在调试器里动态分析, 比较函数返回时, souretarget内存是否有区别.

我使用[x64Dbg](https://x64dbg.com/)来分析. 在启动调试器后我们打开crackme, 调试器会自动运行程序并暂停在入口点位置.

5.png

现在我们需要在我们感兴趣的函数返回处设下一个断点. 指令地址是0x00403DF9(给定.text段的基址是0x00401000). 你可以根据内存布局来了解真正.text段载入的基址(我这里是0x00281000). 因此我的实际断点地址应该是0x00283DF9.

现在我们用bp 0x00283DF9命令设下断点, 继续执行触发断点. 然后我们右键点击右侧面板ebxedi寄存器的值, 选择在数据窗口跟随. 现在我们就可以确认sub_406A70仅仅复制了内存as is, 我们可以放心地将该函数重命名为更易理解的memcpy. 同样我们也把sub_403D90重命名为loadResource

现在我们来分析sub_404150

6.png

映入眼帘的是一个常量0x82F63B78. 通过google搜索知道说这是一个用于CRC32计算的多项式值. 代码里看也有从输入缓冲区里对每个字节的值异或累加, 随后再移位/异或8次. 因此它确实是一个crc32c计算函数.

在重命名和初期的分析后, 我们再来看看改动后的代码

7.png

注意: 也许你会对lea/cmovnb指令有些许困惑. 不过很好解释: lpPasswordText的值实际上是如下的结构体:

struct {
    union {
        char static[16];
        char *dynamic;
    }
    size_t length;
    size_t size;
}

这可能就是栈上std::string的形式. 当字符串仅有static数组那么长时, 不会申请额外的内存空间(并且static缓冲区的地址用lea加载). 相反如果超出了缓冲区, cmovnb会获取dynamic域所分配的内存的指针. 最后, eax会获得指向真正字符串数据的指针, 不论其位置具体在哪.

因此, sub_401000读取键盘输入到std::string, std::string随后传递给crc32c函数. 现在我们知道说我们的password应该含有CRC32的0x57585384, 我们可以根据这个条件判断我们是否获取到了正确的password.

现在我们来假定password跟给出的CRC32值相匹配, 来继续往下分析:

8.png

有趣的第一点就是sub_403C90, 因为它同时取了password资源数据作为参数.

9.png

很显然这里是一个异或加密的操作. 它首先确定password的长度, 随后用相应的password字符对输入缓冲区的每一个字节进行异或.

随后生成一个临时文件名, 将解密的资源数据内容写入到该文件(在函数sub_403090里). 待一切完成, 却也再没有给出任何关于password的线索了. 我们来看一下sub_403D20, 该函数接收新创建的文件名并执行了一些操作.

10.png

OK, 现在事情已经越发清晰. crackme尝试设置新生成的文件作为桌面壁纸, 因此很显然这个文件应该是一个图片.

现在我们要提取crackme里的资源文件, 看看我们能否有所收获. 你可以使用任意的资源编辑软件, 例如: Resource Hacker

11.png

我们可以看到它的大小是6,220,854字节, 对于一个图像来说已经很大了, 据此我们猜测, 这是一个无压缩的BMP图像文件.

BMP格式已经是众所周知, 并且有文档说明. 文件起始于一个"BM"签名, 随后是4字节的文件大小(小端序), 接着是两个4字节的保留字(全0), 一个4字节存储着位图数据的起始位置, 再紧接着是40字节的位图信息头(起始的是该信息头所占用的字节数). 再下面就是各种关于BMP信息了, 我们现在也不知道.

由于我们得知了真正的文件大小值, 所以我们可以较准确地推测出文件的前18个字节.

资源文件里的字节:
24 22 5A 80 31 77 5F 64 61 5F 44 61 62 62 41 74 7A 66
期待的结果:
42 4D 36 EC 5E 00 00 00 00 00 36 00 00 00 28 00 00 00

现在我们逐个将实际资源文件里的字节和说期望的字节进行异或, 这样我们就可以恢复出部分key的内容. 如果幸运的话, 我们可以获得一个完整的key

66 6F 6C 6C 6F 77 5F 64 61 5F 72 61 62 62 69 74 7A 66

异或得到的结果是"follow_da_rabbitzf". 最后的这一个"f"也许是重复的下一个key的起始字母, 也许就是这个key的一部分. 最简单的检查方法就是将其输入到crackme里看看结果如何.

12.png

Yeah. 我们的结果是正确的. 我们再继续.

现在我们有一个超酷的桌面壁纸, 然后还有另外一个password需要破解出来. 我们再次搜索"Password#2"字符串并跟随到交叉引用处:

13.png

这看起来跟之前非常相似, 因此我们自己向下来到解密开始的部分:

14.png

有趣的部分在sub_403E10, 这里在写入数据到文件之前进行了解密:

15.png

这里根据password导出一个AES128的密钥(使用SHA256作为密钥导出算法)并用于解密资源数据.

没有必要去破解AES加密(恐怕就连NSA也无法破解), 我们只知道password的crc32值. 很显然不足以通过暴力破解的手段来获取它(我尝试过!). 但等等, 我们有一个壁纸啊! 或许在壁纸里会有某些隐藏的信息!

用图像编辑器打开并使用”颜色选择”工具:

16.png

这应该就是我们一直在寻找的key! 接下来继续:

17.png

但是事情还没结束. 现在我们在临时目录下有一个解密过的可执行文件, 但我们还是没有拿到flag. 我们还需要用IDA继续分析.

因为第二个可执行文件按并没有产生任何字符串信息, 也就难以下手. 我们就来看看导入表情况:

18.png

这里有一系列的按顺序导入的ws2_32.dll的函数, 这给我们两个线索:

  • 程序有在进行网络socket操作
  • 程序有隐藏些什么!

因此我们的第一步就是去到这些函数被调用的地方, 并将这些函数重命名为可读性更高更有意义的名称. 序号与之对应的函数名称可以很容易地通过google搜索找到.

现在我们知道了所有的网络操作都在sub_404480里, 因此接下来仔细看看这个函数. 该函数开始是一个标准流程(WSAStartup/socket/bind/listen), 所以没太多亮点, 有趣的部分在下图:

19.png

因此它等待接受一个连接, 从连接中读取4字节, 基于静态缓冲区buf和接收的数据在sub_404640中执行一些操作. 如果操作成功转型(函数返回非零值), 它就会将buf的内容发回给客户端随后关闭连接. 否则它会关闭连接监听新的连接. 所有的操作都是同步的, 所以在sub_404640成功执行前不会退出函数.

来看看sub_404640的内容:

20.png

看起来非常像是一个小的状态机, 成功转移到下一状态时返回1, 有如下几个转移:

  • 从 初始状态(0) 到 ‘Y’ 状态 (如果接收到9)
  • 从 ‘Y’ 状态 到 ‘E’ 状态 (如果接收到3)
  • 从 ‘E’ 状态 到 ‘S’ 状态 (如果接收到5)
  • 接收到其他的任何值, 都会将状态机重置为 初始状态(0)

因此, 我们可能需要按顺序发起3个连接, 连到"server", 更新状态机到下一状态.

但是我们仍有两个问题需要解决:

  1. 我们不知道需要连接到哪一个端口(函数需要取端口号作参数)
  2. 在每次成功转移状态后, 监听的套接字都会关闭

因此我们需要找到所有的函数被调用的地方, 然后跟踪看它启动了哪一个端口.

如同我们所预料的那样, 函数被调用了3次(因为有3次合法的状态转移), 并且幸运的是, 它都是在同一个步骤里被调用的:

21.png

在这里

22.png

所以, server一开始开启了端口1337, 随后是1338, 最后是1339. 因此我们首先需要连接到1337端口并发送9, 然后连接到1338端口, 发送3. 最后连接到1339端口, 发送5. 我们可以使用内置的telnet工具来完成这一操作.

完成上述操作后会打开一个简短视频的YouTube页面:

23.png

我们成功地拿到了flag. 收工回家!

Posted in reverse engnieering | Leave a comment

Intro to unicorn engine

什么是Unicorn引擎

Unicorn是一个轻量级, 多平台, 多架构的CPU模拟器框架. 我们可以更好地关注CPU操作, 忽略机器设备的差异. 想象一下, 我们可以将其应用于这些情景: 比如我们单纯只是需要模拟代码的执行而非需要一个真的CPU去完成那些操作, 又或者想要更安全地分析恶意代码, 检测病毒特征, 或者想要在逆向过程中验证某些代码的含义. 使用CPU模拟器可以很好地帮助我们提供便捷.

它的亮点(这也归功于Unicorn是基于qemu而开发的)有:

  • 支持多种架构: Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64).
  • 对Windows和*nix系统(已确认包含Mac OSX, Linux, *BSD & Solaris)的原生支持
  • 具有平台独立且简洁易于使用的API
  • 使用JIT编译技术, 性能表现优异

你可以在Black Hat USA 2015获悉有关Unicorn引擎的更多技术细节. Github项目主页: unicorn

尽管它不同寻常, 但它无法模拟整个程序或系统, 也不支持系统调用. 你需要手动映射内存并写入数据进去, 随后你才能从指定地址开始模拟.

应用的情景

什么时候能够用到Unicorn引擎呢?

  • 你可以调用恶意软件中一些有趣的函数, 而不用创建一个有害的进程.
  • 用于CTF竞赛
  • 用于模糊测试
  • 用于gdb插件, 基于代码模拟执行的插件
  • 模拟执行一些混淆代码

如何安装

安装Unicorn最简单的方式就是使用pip安装, 只要在命令行中运行以下命令即可(这是适合于喜爱用python的用户的安装方法, 对于那些想要使用C的用户, 则需要去官网查看文档编译源码包):

pip install unicorn

但如果你想用源代码进行本地编译的话, 你需要在下载页面中下载源代码包, 然后可以按照以下命令执行:

  • *nix 平台用户
$ cd bindings/python
$ sudo make install
  • Windows平台用户
cd bindings/python
python setup.py install

对于Windows, 在执行完上述命令后, 还需要将下载页面的Windows core engine的所有dll文件复制到C:\locationtopython\Lib\site-packages\unicorn位置处.

使用unicorn的快速指南

我们将会展示如何使用python调用unicorn的api以及它是如何轻易地模拟二进制代码. 当然这里用的api仅是一小部分, 但对于入门已经足够了.

 1 from __future__ import print_function
 2 from unicorn import *
 3 from unicorn.x86_const import *
 4 
 5 # code to be emulated
 6 X86_CODE32 = b"\x41\x4a" # INC ecx; DEC edx
 7 
 8 # memory address where emulation starts
 9 ADDRESS = 0x1000000
10 
11 print("Emulate i386 code")
12 try:
13     # Initialize emulator in X86-32bit mode
14     mu = Uc(UC_ARCH_X86, UC_MODE_32)
15 
16     # map 2MB memory for this emulation
17     mu.mem_map(ADDRESS, 2 * 1024 * 1024)
18 
19     # write machine code to be emulated to memory
20     mu.mem_write(ADDRESS, X86_CODE32)
21 
22     # initialize machine registers
23     mu.reg_write(UC_X86_REG_ECX, 0x1234)
24     mu.reg_write(UC_X86_REG_EDX, 0x7890)
25 
26     # emulate code in infinite time & unlimited instructions
27     mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32))
28 
29     # now print out some registers
30     print("Emulation done. Below is the CPU context")
31 
32     r_ecx = mu.reg_read(UC_X86_REG_ECX)
33     r_edx = mu.reg_read(UC_X86_REG_EDX)
34     print(">>> ECX = 0x%x" %r_ecx)
35     print(">>> EDX = 0x%x" %r_edx)
36 
37 except UcError as e:
38     print("ERROR: %s" % e)

运行结果如下:

$ python test1.py 
Emulate i386 code
Emulation done. Below is the CPU context
>>> ECX = 0x1235
>>> EDX = 0x788f

样例里的注释已经非常直观, 但我们还是对每一行代码做出解释:
* 行号2~3: 在使用Unicorn前导入unicorn模块. 样例中使用了一些x86寄存器常量, 所以也需要导入unicorn.x86_const模块
* 行号6: 这是我们需要模拟的二进制机器码, 使用十六进制表示, 代表的汇编指令是: “INC ecx” 和 “DEC edx”.
* 行号9: 我们将模拟执行上述指令的所在虚拟地址
* 行号14: 使用Uc类初始化Unicorn, 该类接受2个参数: 硬件架构和硬件位数(模式). 在样例中我们需要模拟执行x86架构的32位代码, 我们使用变量mu来接受返回值.
* 行号17: 使用mem_map方法根据在行号9处声明的地址, 映射2MB用于模拟执行的内存空间. 所有进程中的CPU操作都应该只访问该内存区域. 映射的内存具有默认的读,写和执行权限.
* 行号20: 将需要模拟执行的代码写入我们刚刚映射的内存中. mem_write方法接受2个参数: 要写入的内存地址和需要写入内存的代码.
* 行号23~24: 使用reg_write方法设置ECXEDX寄存器的值
* 行号27: 使用emu_start方法开始模拟执行, 该API接受4个参数: 要模拟执行的代码地址, 模拟执行停止的内存地址(这里是X86_CODE32的最后1字节处), 模拟执行的时间和需要执行的指令数目. 如果我们像样例一样忽略后两个参数, Unicorn将会默认以无穷时间和无穷指令数目的条件来模拟执行代码.
* 行号32~35: 打印输出ECXEDX寄存器的值. 我们使用函数reg_read来读取寄存器的值.

要想查看更多的python示例, 可以查看文件夹bindings/python下的代码. 而C的示例则可以查看sample文件夹下的代码.

参考链接

Posted in reverse engnieering | Leave a comment

The structure of .pyc files

原文链接: The structure of .pyc files

简单来说, 一个pyc文件包含以下三块
* 一个4字节的魔数(magic number)
* 一个4直接的修改时间戳(modification timestamp)
* 一个编排过的代码对象

对于各个版本的python解释器, magic number都各不相同, 对于python 2.5则是b3f20d0a

修改时间戳则是源文件生成.pyc文件的Unix修改时间戳, 当源文件改变的时候, 该值也会变化

整个文件剩下的部分则是在编译源文件产生的代码对象编排后的输出. marshal跟python的pickle类似, 它对python对象进行序列化操作. 不过marshal和pickle的目标不同. pickle目的在于产生一个持久的独立于版本的序列化, 而marshal则是为了短暂地序列化对象, 因此它的表示会随着python版本二改变.

而且, pickle被设计用于适用用户定义的类型, 而marshal这时用于处理python内部类型的复杂结构

marshal的特性给出了pyc文件的重要特征: 它对平台独立, 但依赖于python版本. 一个2.4版本的pyc文件不能在2.5版本下执行, 但是它可以很好地移植到不同操作系统里.

接下来的部分也简单: 对于两个长整数和一个marshalled的代码对象, 复杂点在于代码对象的结构. 它们包含有编译器禅师的各种信息, 其中内容最丰富的这是字节码本身.

所幸有了marshal和dis模块, 编写程序导出这些信息并不会很难.

import dis, marshal, struct, sys, time, types

def show_file(fname):
    f = open(fname, "rb")
    magic = f.read(4)
    moddate = f.read(4)
    modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))
    print "magic %s" % (magic.encode('hex'))
    print "moddate %s (%s)" % (moddate.encode('hex'), modtime)
    code = marshal.load(f)
    show_code(code)

def show_code(code, indent=''):
    print "%scode" % indent
    indent += '   '
    print "%sargcount %d" % (indent, code.co_argcount)
    print "%snlocals %d" % (indent, code.co_nlocals)
    print "%sstacksize %d" % (indent, code.co_stacksize)
    print "%sflags %04x" % (indent, code.co_flags)
    show_hex("code", code.co_code, indent=indent)
    dis.disassemble(code)
    print "%sconsts" % indent
    for const in code.co_consts:
        if type(const) == types.CodeType:
            show_code(const, indent+'   ')
        else:
            print "   %s%r" % (indent, const)
    print "%snames %r" % (indent, code.co_names)
    print "%svarnames %r" % (indent, code.co_varnames)
    print "%sfreevars %r" % (indent, code.co_freevars)
    print "%scellvars %r" % (indent, code.co_cellvars)
    print "%sfilename %r" % (indent, code.co_filename)
    print "%sname %r" % (indent, code.co_name)
    print "%sfirstlineno %d" % (indent, code.co_firstlineno)
    show_hex("lnotab", code.co_lnotab, indent=indent)

def show_hex(label, h, indent):
    h = h.encode('hex')
    if len(h) < 60:
        print "%s%s %s" % (indent, label, h)
    else:
        print "%s%s" % (indent, label)
        for i in range(0, len(h), 60):
            print "%s   %s" % (indent, h[i:i+60])

show_file(sys.argv[1])

我们使用这段代码, 来处理一个极简单的python文件生成的pyc文件:

a, b = 1, 0
if a or b:
    print "Hello", a

产生的结果如下:

magic b3f20d0a
moddate 8a9efc47 (Wed Apr 09 06:46:34 2008)
code
   argcount 0
   nlocals 0
   stacksize 2
   flags 0040
   code
      6404005c02005a00005a0100650000700700016501006f0d000164020047
      65000047486e01000164030053
  1           0 LOAD_CONST               4 ((1, 0))
              3 UNPACK_SEQUENCE          2
              6 STORE_NAME               0 (a)
              9 STORE_NAME               1 (b)

  2          12 LOAD_NAME                0 (a)
             15 JUMP_IF_TRUE             7 (to 25)
             18 POP_TOP
             19 LOAD_NAME                1 (b)
             22 JUMP_IF_FALSE           13 (to 38)
        >>   25 POP_TOP

  3          26 LOAD_CONST               2 ('Hello')
             29 PRINT_ITEM
             30 LOAD_NAME                0 (a)
             33 PRINT_ITEM
             34 PRINT_NEWLINE
             35 JUMP_FORWARD             1 (to 39)
        >>   38 POP_TOP
        >>   39 LOAD_CONST               3 (None)
             42 RETURN_VALUE
   consts
      1
      0
      'Hello'
      None
      (1, 0)
   names ('a', 'b')
   varnames ()
   freevars ()
   cellvars ()
   filename 'C:\\ned\\sample.py'
   name '<module>'
   firstlineno 1
   lnotab 0c010e01

有很多内容我们都不明白, 但是字节码却很好地被反汇编并呈现了出来. python虚拟机是一个面向栈的解释器, 因此有许多操作都是load和pop, 并且当然也有很多jump和条件判断. 字节码的解释器则是在ceval.c中实现, 对于字节码的具体改变则会依赖于python的主版本. 比如PRINT_ITEMPRINT_NEWLINE在python 3中则被去掉了.

在反汇编的输出中, 最左的数字(1,2,3)是源文件中的行号, 而接下来的数字(0,3,6,9)这是指令中的字节偏移. 指令的操作数这是直接用数字呈现, 而在括号内的这是符号性的解释. &gt;&gt;所表示的行实际上是代码中某处跳转指令的目标地址.

我们这个样例非常简单, 它只是一个模块中单一的代码对象中的指令流程. 在现实中有着类和函数定义的模块会十分复杂. 那些类和函数本身就是const列表中的代码对象, 在模块中进行了足够深的嵌套.

一旦你开始在这个级别挖掘, 会发现有各种各样适用于代码对象的工具. 在标准库中有内置的compile函数, 以及compiler, codeopopcode模块. 在真实场景中会有很多第三方包比如codewalk, byteplaybytecodehacks等. PEP 339给出了有关编译和操作码的更多详细信息. 最后

Posted in reverse engnieering | Leave a comment