内存取证(一)

原创文章,未经许可严禁转载

Author:ex@m1ne2

Memory Forensics

在计算机或者一些智能设备运行时,可以直接将物理内存中的临时数据给保存下来、导出成文件,然后就获得了内存的数据文件

实际上,VMware虚拟机的「挂起」功能正是依靠内存文件实现的

虚拟机文件中,比较重要的文件有:

  • .vmdk

    .vmdk 是虚拟机磁盘文件(Virtual Machine Disk),存储着虚拟机中硬盘驱动器的内容

    当启动虚拟机时,VMware将会导入 .vmdk 文件,实现虚拟机的启动;而我们所谓的「虚拟机快照」其实就是是将 .vmdk 文件进行备份并重命名(例如,将 Ubuntu 64 位-000001.vmkd 备份为 Ubuntu 64 位-000001-s001.vmdk

    当需要恢复虚拟机到之前的某个状态时,需要导入了之前备份的 .vmdk 文件,并且还需要其它文件(例如 .vmsn)的协助

  • .vmem

    .vmem 是虚拟机内存文件(Virtual Machine Memory),它表示虚拟机运行时的主存信息

    这个文件只有在虚拟机运行时存在,虚拟机关闭后将被清除(或者虚拟机意外崩溃,.vmem 文件将会保留)

    在VMware的挂起状态下,视为虚拟机仍在运行,.vmem 文件仍然存在

  • .vmss

    .vmss 是虚拟机快照状态文件(Virtual Machine Snapshot State),用于辅助虚拟机的挂起和唤醒

当我们点击虚拟机的挂起键时,虚拟机会将当前的状态保存为一个 .vmem.vmss 文件;「挂起」采用的是「空间换时间」的策略,当挂起后,虚拟机将不再占用资源,但下一次打开虚拟机时,仍能准确地恢复上一次挂起前的状态,精确到任何文件的状态

在CTF中,就存在一类题目是在这些内存文件中进行分析和检索,提取出flag —— 内存取证(Memory Forensics)


Memory File Type

在CTF中,常见的内存镜像文件格式有 .img.dmp.raw.vmem

  • .vmem

    虚拟机软件VMware备份主存信息的文件

    注意VMware运行时存在的 .vmem 与CTF题中常见的 .vmem 有所区别,能够作为题目的 .vmem 都是使用特定的工具导出的

  • .img

    压缩整个软盘或整个光盘的一种文件压缩格式

    .img 可以视为是 .iso 文件的超集,因为 .iso 只能压缩使用ISO9660和UDF这两种文件系统的存储媒介(也就是只能压缩CD或DVD),因此 .img 诞生,它在 .iso 格式的基础上增加了对使用其它文件系统的存储媒介压缩的能力

    .img 向后兼容 .iso

  • .dmp

    中文名为“转储文件”,是导出(Dump)的文件格式,通常是软件出现问题时手动生成或程序自动生成的

    更多介绍见下面memdump插件的介绍

  • .raw

    同样是导出文件,工具DumpIt能够轻松地将一个系统的完整内存进行镜像保存,保存后的文件格式就是 .raw

注意,对 .raw.vmem 等文件使用file命令,显示的都是 data!因此有时候在CTF中拿到一个体积较大、但没有后缀名的文件,并且file的执行结果是 data,那么可以尝试直接用下面提到的volatility检测


内存取证类题目涉及到非常多的知识点,如果采用自顶向下或自底向上的学习方法,耗时太久得不偿失,所以打算基于遇到过的CTF内存取证类题目,结合介绍工具的使用,来逐渐完善对这类题目的了解和认识


Volatility

Volatility是一款基于Python开发、开源的内存分析框架,目前在Kali系统中自带;这款框架支持Windows、Linux、Mac OSX甚至Android手机使用的ARM处理器的取证

虽然Volatility支持不同操作系统,但是这篇文章主要介绍Windows系统

目前Volatility的最新版本为2.6,其下载方式可以通过:

具体安装的方式可以参考网上的文章,例如:https://blog.csdn.net/fly_hps/article/details/79961707

安装完成后,我们可以键入 volatility -h 来查看Volatility的用法:

image-20201107192216598

我们知道,一个内存镜像实际上就相当于一台静止的计算机,其所涉及到的知识点是非常广的

Volatility采用「插件(Plugin)」的方式,将一个个产生具体作用的工具进行封装,以便针对不同功能进行分别调用

我们可以在 -h 参数中看到Volatility支持的所有插件列表:

image-20201107200545410

或者通过 --info 参数打印出所有已经注册的对象(包含插件)

在安装后,我们可以直接在安装目录下查看已有的Plugin;例如,安装在WSL下的位置为:

1
C:\Users\examine\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\usr\lib\python2.7\dist-packages\volatility

在其 plugins 目录下,可以看到每个Plugin以 .py 的形式存在,并且为了更快地被调用,同时以编译后的 .pyc 形式存在

image-20201107201444155

[Parameters]

  • -f & --filename

    输入待检查的内存镜像文件

  • -h & --help

    查看Volatility的基本使用方法

  • --profile

    指定配置文件(具体见后面imageinfo的介绍)


imageinfo

  • 查看镜像文件的镜像信息(Image Infomation)

在不同版本的操作系统中,内核数据结构成员以及偏移量都可能不同,因此在使用Volatility时,需要通过 --profile 参数指定一个profile值,以便加载对应的解析器;只有解析器与内存镜像文件吻合,之后的分析提取才能正常进行

profile(配置文件)在不同的操作系统、不同的版本中都具有一个唯一的名称,通常由:①操作系统名称;②版本;③服务包;④系统结构;等信息组成

例如,Win7SP1x64 表示的是64位的Windows 7 SP1系统配置文件

我们同样可以通过 --info 参数来查看Volatility支持的profile值:

image-20201107204142512

对于一个陌生的内存镜像文件,首先应该使用imageinfo插件提取出内存文件的一些基本信息,例如:

image-20201107204512406

上图中,imageinfoSuggested Profile(s) 字段显示出 Temp.vmem 可能的profile

imageinfo插件是基于另一个插件kdbgscan来猜测profile值的,而kdbgscan则是通过查找分析「内核调试器数据库(_KDDEBUGGER_DATA_64)」的特征值来进行profile的猜测

执行imageinfo通常会有多个结果,通常第一个就是正确的,随后在后面的插件使用中,需要指定Volatility的 --profile 参数;而当不指定时,从 -h 参数中可以看到 --profile 的默认值是 WinXPSP2x86,如果与实际的内存镜像文件不匹配,会导致错误

倘若指定的 --profile 参数错误,那么会提示 No suitable address space mapping found


本部分学习参考:

伪装

不知道你有没有留意,Dump出来的内存镜像文件在 file 命令下的结果都是 data;也就是说,内存镜像文件没有文件头

这也就是导致了有时候我们单纯通过一些常见的文件后缀(如 .vmem.raw 等)进行判断是内存镜像文件,但拿到一个无后缀的文件,检测结果还是 data,那就很有可能被误导

更有甚者,我们可以为 data 的内存镜像文件添加上一些文件头,伪装成其它文件;例如,添加上 .png 的文件头:

1
89504e470d0a1a0a0000000d494844520000003c0000003c08060000003afcd972

举例,2016 Google CTF 内存取证题 For1

这道题目在内存镜像文件前面添加了0x238 Bytes的 .elf 文件头:

image-20201118230101137

使得 flie 检测的结果为 ELF,但实际运行该 .elf 会报错,无法运行


通常为了不被这种伪装迷惑,可以从以下角度入手:

  1. 文件体积较大

  2. binwalk 输出信息,如:

    1
    2
    3
    4
    5
    6
    7
    DECIMAL       HEXADECIMAL     DESCRIPTION
    --------------------------------------------------------------------------------
    150720 0x24CC0 Microsoft executable, portable (PE)
    656418 0xA0422 Copyright string: "Copyright 1985-1998,Phoenix Technologies Ltd.All rights reserved."
    827522 0xCA082 Copyright string: "Copyright (C) 2003-2014 VMware, Inc."
    827561 0xCA0A9 Copyright string: "Copyright (C) 1997-2000 Intel Corporation"
    942684 0xE625C ISO 9660 Boot Record,

pslist

  • 罗列进程(List Process)
image-20201107211054608

内存镜像文件中保存着计算机某一时刻的快照,因而能够记录下当时计算机内存中运行的所有进程,使用pslist插件能够罗列出所有的进程

在CTF中,罗列出来的大部分是系统进程,可以视为干扰项;而有时候Hint会出现在个别进程中,需要根据pslist的返回结果仔细检查某些进程

pslist返回结果中,各个字段的含义是:

  • Offset(V) —— 各个进程在内存中的虚拟偏移量(Virtual Offset)

  • Name —— 进程名称

  • PID —— 进程编号(Process Identifier)

  • PPID —— 父进程编号(Parent Process Identifier)

  • Thds —— 线程数量(number of Threads)

  • Hnds —— 句柄数量(number of Handles)

    句柄(Handle)这一概念应当与进程(Process)关联学习,可以参考:

  • Sess —— 运行进程的各个会话(Session)

  • Wow64 —— 是否是运行在64位系统上的32位进程(Windows 32-bit on Windows 64-bit)

  • Start —— 进程开始运行的时间

  • Exit —— 进程结束的时间;如果该字段存在值,表示当前进程已经结束了


pstree

pstreepslist基本相同,只是在 Name 的显示方式上,pstree采用「树」的结构,将进程间的「父子关系(Child-Parent Relationship)」表示出来,更为清晰

image-20201108220659999

有时候,我们不仅可以通过进程名称来发觉异常,还可以通过「子进程」来发现一些不妥的关系

例如,explorer.exe 表示浏览器,而 svchost.exe 是标准的动态链接库主机处理服务;在系统服务进程中,svchost.exe 很特殊,它往往有很多个,而这一点很容易被黑客利用,将病毒伪装成 svchost.exe

explorer.exe 是绝对不应该生成 svchost.exe 的任何实例的,当从pstree中看到这种父子关系时,就可以察觉出异常

又例如:

image-20201108175114162

你觉得浏览器 explorer.exe 为什么会启动 cmd.exe


psscan

无论是pslist还是pstree,两者都是通过遍历 PsActiveProcessHead 指向的双向链表来列举出系统进程的,而有时候,如果将某个进程从双向链表中解链,那么两者都将检测不到这个进程

通过这种方式使进程不出现在列表中,看上去是一种可靠的隐藏方法,并且这种修改不会影响其性能

「解链」操作可以通过类似"直接内核对象操纵(Direct Kernel Object Manipulation,简称DKOM)"的技术实现

而插件psscan同样是罗列进程,但它采用的方法和pslist、pstree不同,它使用池标签扫描 _POOL_HEADER 来枚举进程,可以找到先前已终止的进程或解链的进程,进而找出可能存在的隐藏的进程

image-20201108220746182

可以看到,psscan输出的内容与pslist稍有些不同:

  • Offset(P)

    psscan直接输出了进程的物理偏移量(Physics Offset)而不是虚拟偏移量(Virtual Offset),这是因为psscan遍历了内存镜像的“原始”字节,并不关心虚拟到物理的内存地址转换

  • PDB

    也正是因为psscan输出的是物理偏移量,所以输出中也出现了页目录库(Page Directory Base,简称PDB)

    PDB包含虚拟地址到物理地址的转换中所使用的索引

在「检索隐藏进程」上,psscan要优于pslist和pstree,虽然psscan花费的时间相对较多;而psscan也是能够呈现进程间的父子关系的,但是需要通过 --output--output-file 参数导出为 .dot 文件:

image-20201108220842086

psscan导出的文件类型只支持 .dot.greptext.html.json.sqlite.txt.xlsx

在Windows操作系统,可以使用Graphviz自带的gvedit.exe打开 .dot 文件进行查看:

image-20201108184552155

而Linux操作系统则通过安装xdot来打开 .dot 文件


本部分学习参考:


memdump

前面介绍的pslist、pstree、psscan都是检查镜像文件中的进程,进程以PID唯一标识,当我们察觉到某个进程可能存在问题时,可以通过memdump插件将进程单独从内存镜像文件中Dump出来,变为转储文件(.dmp)

使用方法为:

  • -p —— 指定PID
  • --dump-dir= —— 指定导出的目录

事实上,在正常的计算机中,也可以根据进程创建 .dmp 转储文件

例如,打开任务管理器,右键点击进程,有创建转储文件(C)的选项:

image-20201108214338060

使用任务管理器生成转储文件需要遵循一个原则:

  • 用32位任务管理器为32位进程生成转储文件;用64位任务管理器为64位进程生成转储文件

在64位操作系统上,默认启动的是64位的任务管理器,而32位的任务管理器位于 C:\Windows\SysWOW64\Taskmgr.exe

正常情况下,.dmp 转储文件是在系统崩溃时自动生成的,其将内存中的数据转储在 .dmp 文件中,给有关人员用作排错分析

很多时候测试人员无法复现遇到的崩溃问题,就可以使用计算机上保存的 .dmp 转储文件

.dmp 转储文件可以视为是进程某一时刻的快照,可以包含模块、堆、栈等信息,我们可以调试转储文件,但无法像调试程序那样让程序运行起来,不能进行单步执行、设置断点等,但可以查看线程、调用栈、变量等信息

一般情况下,.dmp 转储文件的分析需要专门的工具,例如Dumpchk.exe等,但在CTF中通常不涉及那么深


回到进程本身,在CTF中通常通过pslistpsscan罗列出所有进程后,分析其中可能存在异常的进程;而Volatility拥有一些插件,能够更容易地分析出进程的内容


notepad

在Dump为内存镜像时,可能在运行记事本(notepad.exe)进程,如果通过pslist检查到有 notepad.exe,那么可以通过插件notepad检查记事本的内容

Mark,不同进程的 .dmp 文件分析方式不同,notepad能够直接查看notepad.exe进程的内容,本质上是对notepad.exe对应的 .dmp 转储文件进行了解析;如果后续需要学习如何分析 .dmp 文件,可以直接从Volatility的 notepad.py 下手

* [Example_1]

我们以一道例题来查看notepad的用法(源自「内存取证三项」第一项):

链接:https://pan.baidu.com/s/1XJrer01wxTFq7MjRaFfUiA
提取码:cbc6
题目描述:【第一项】小黑在桌面上写着什么,写得是啥?据说是flag

前面通过imageinfo获取profile,然后执行pslist,查看到有 notepad.exe 在运行;结合题目描述,很可能当时在打开记事本写东西,因此使用notepad查看内容:

image-20201109000235252

将其中的Hex转ASCII字符后得到flag:flag{W3lec0me_7o_For3n5ics}


值得一提的是,notepad只能提取ASCII字符,如果记事本的内容为中文,将会得到乱码;目前没有什么解决办法


cmdscan

如果检测到内存镜像有 cmd.exe 的进程,那么可以通过cmdscan插件来获取 cmd.exe 的历史执行命令;其本质是查找常量 MaxHistory,然后应用完整性检验,提取内存镜像中的 COMMAND_HISTORY 缓冲区内容

我们可以右键点击Cmd上方,点击属性后查看其缓冲区:

image-20201109092621099

除此之外,也可以直接在注册表中进行修改,位置为:HKEY_CURRENT_USER\Console\HistoryBufferSize

Windows系统上缓冲区大小的默认值为50,表示能够保存最近的50条命令;因为有保存,所以能够通过一些方式进行提取

值得一提的是,cmdscan是由Michael Ligh从conhost.exe和winsrv.dll文件中逆向分析得到的,这款插件使用的结构是不公开的,你也没法在Volatility中找到相应的 cmdscan.py 文件;cmdscan可以视为是集成在Volatility中,因此它不能在其它取证框架(例如WinDBG等)中使用

* [Example_2]

我们以前面题目来进行cmdscan的学习:

链接:https://pan.baidu.com/s/1XJrer01wxTFq7MjRaFfUiA
提取码:cbc6
题目描述:【第二项】小黑发送了一个机密文件,里面到底有什么?

在获取profile后,用cmdscan提取Cmd的历史记录:

image-20201109005213969

可以看到,cmdscan提取出来共有3个程序打开了Cmd,在了解各个字段的意义之前,还有一些扩展知识


csrss.exe

在早期的Windows版本中,所有代表非GUI活动的应用程序(也就是控制台应用程序)如果要在桌面上运行,必须经过系统进程 csrss.exe 来进行协调

此时称 csrss.exe控制台主机

例如,当控制台应用程序需要接收字符时,会在 Kernel32.dll 中调用一个小型的「控制台APIs」让Kernel32产生LPC来调用 csrss.exe;随后 csrss.exe 会检查控制台窗口的输入队列并进行校验,以字符模式的结果架通过Kernel32返回给控制台应用程序

csrss.exe 的全称为客户端/服务器运行进程(Client Server Runtime Process),我们通常可以在任务管理器中找到它:

image-20201109102709610

csrss.exe 可以有很多个,由所有 csrss.exe 构成客户端/服务器运行子系统(Client Server Runtime SubSystem)

注意这两个概念的区分

但是 csrss.exe 是始终运行在本地账户权限下的,并且所有命令行进程都是使用一个Session唯一的 csrss.exe,因此某些情况下,黑客可以通过开发恶意软件利用 csrss.exe 获取到更多权限。这种攻击模式被称为Shatter Attack


conhost.exe

到了Windows 7,将原本的 csrss.execonhost.exe (Console Host Process)来替代

所有的控制台应用程序都被放到一个新的上下文进程 conhost.exe 中执行,由于这时的控制台主机是 conhost.exe,其与控制台应用程序运行在相同安全级的上下文环境中,因此任何应用程序企图利用消息请求来自动提权都不会成功

更换为 conhost.exe 后,控制台应用程序不再发出LPC消息去请求 csrss.exe,而是去请求 conhost.exe


csrss.execonhost.exe 存在的必要是因为控制台应用程序没有自身代码来显示UI,因此需要「宿主进程」来完成窗口的显示、消息处理等


cmdscan

回到我们cmdscan的提取结果:

image-20201109005213969

我们分析第1个程序的Cmd情况,分析部分字段:

  • CommandProcess —— Cmd主机进程的名称(csrss.execonhost.exe

  • Application —— 使用Cmd的应用程序名称

  • CommandCount —— 从命令的历史记录缓冲区提取的命令数

  • LastAddedLastDisplayed —— 最后添加的命令索引、最后显示的命令索引

  • FirstCommandCommandCountMax —— 首条命令的索引、最大命令记录数量(通常为50)

  • ProcessHandle —— 应用进程句柄

  • Cmd

    最后根据 CommandCount 依次罗列出执行过的命令

分析第1个程序的Cmd情况,可以看到 Applicationcmd.exe,因此是用户直接打开Cmd的;然后依次执行了:① ipconfig 查看网络状况;② cd 切换目录;③ 启动 nc,向192.168.57.14的2333端口发送了一个文件

于是我们在cmdscan这里发现了异常

我们再来分析除 cmd.exe 外其它两个打开Cmd的应用程序:

image-20201109155210643

nc.exe 是由上一条的 cmd.exe 打开的,而 DumpIt.exe 则是一款将计算机内存镜像保存的工具,用于生成 .raw 文件

因此我们可以猜测,出题人进行的操作是:①手动打开Cmd,并通过 nc 发送了一个 .zip 文件;②使用 DumpIt.exe 将当前的内存镜像保存下来,作为CTF题目

此外上图还需要注意两点:

  1. LastAddedLastDisplayed 处的 -1

    -1 是无效的索引,因此这表明「Cmd被打开但是没有执行任何命令」

  2. Cmd 处的 .4148

    「提取Cmd历史命令」实际上是通过暴力检索命令池,因此很难确定检索得到的结果是否是一条有效的命令;cmdscan采用推荐的命令长度(_COMMAND.CmdLength)的方式来确定一条命令是否有效;基于此,有时候cmdscan的结果可能是成功恢复的历史命令,也有可能是无意义的字符串


consoles

Stevens和Casey的研究使得人们知道可以从 csrss.execonhost.exe 中提取到许多有用的信息,插件cmdscan只能够显示历史出现的命令,其它信息都无法恢复,而consoles插件则能够将命令的输出结果也恢复

原理是cmdscan只扫描 COMMAND_HISTORY,并且只打印输入过的命令;而consoles则是扫描 COMMAND_INFORMATION,除了打印输入的命令外,还将收集整个屏幕缓冲区,将命令对应的输出结果也进行打印

我们尝试对前面的 L-12A6C33F43D74-20161114-125252.raw 内存镜像文件使用consoles插件,对比cmdscan的显示结果

consoles首先会输出与cmdscan相似的内容,主要是执行过的历史命令(顺序被打乱了):

image-20201109164555987

随后就按照时间顺序,紧接一行:

1
2
Screen 0x2d77750 X:80 Y:300
Dump:

后,将所有命令的执行过程打印出来;首先是 cmd.exe 中的内容:

  1. 首先是 ipconfig

    image-20201109164744141
  2. 然后是 cd,可以看到当前目录的变化:

    image-20201109164829842
  3. 最后是 nc

    image-20201109165154324

cmd.exe 执行完毕后,紧接着的是 DumpIt.exe 对应的Cmd记录,从这里可以看到出题人的操作:

image-20201109165314812

在做CTF题目时,如果检测到有 cmd.exe 进程,那么通常先使用cmdscan查看大致的命令,然后根据需要,使用consoles查看详细的Cmd内容

在例如 dir 等命令时,consoles能够得到更多有意义的信息


cmdline

插件cmdline与cmdscan作用相似,都是获取Cmd的历史记录,但是似乎cmdline更为强大,在有些时候,cmdscan无法提取出信息,但cmdline却可以

网络上关于cmdline的介绍非常少,几乎都是一句话的介绍 —— "Display process command-line arguments"

cmdline与cmdscan相比,更侧重于「参数(argument)」这个点上,我们可以认为cmdscan和consoles都是显示在 cmd.exe 上的输入命令记录的,但更多的时候,其它的进程在运行时也会打开类似终端的窗口,快速执行命令后关闭,这类记录只能被cmdline检索到

例如:

image-20201121002023313

上图就是一些进程的运行情况,例如PID为2724的 NOTEPAD.EXE,它可能是通过双击软件直接打开的,但某种程度上等价于在Cmd窗口中执行 C:\Users\SmartNet\Videos\NOTEPAD.EXE 打开的

我们可以把这种「看似是直接打开软件,实际上是通过类似Cmd执行方式打开」的情况视为在一个假的 cmd.exe 中执行命令,而cmdline就是检测这些假的 cmd.exe 的记录的

上面只是个人理解,暂时不知道对错


综上所述,要从命令行获取有用的信息,依靠的3个插件是:cmdscanconsolescmdline


filescan

在内存镜像文件中可能保存有许许多多的文件,而filescan插件则是用于查找所有可能的文件的,其原理是通过池标签在物理内存中查找 FILE_OBJECT,也正是因为这个原理,filescan能够检测到被恶意程序隐藏的文件

注意filescan查找的不仅有文件(file),还可能有目录(directory)等,可以将它们统称为「对象(object)」

filescan执行后,主要输出下面的字段:

  1. Offset(P) —— 物理偏移量(这个很重要)

  2. #Ptr#Hnd —— 对象的指针数、对象的句柄数

  3. Access —— 对象的读(Read)、写(Write)、删除(Delete)权限

    这里的权限不同于常规的Linux rwx,并且不知道大写和小写的区别是什么;暂时没有在网上找到相关资料,待补充

  4. Name —— 对象名称

直接对整个内存镜像文件使用filescan可能得到非常多的文件,其中大部分是 .exe.dll 以及目录;因此filescan往往配合grep使用,快速筛选出我们需要的文件类型

例如,在上面的题目中,cmdscan显示将一个 .zip 文件通过 nc 发送给了别人,那么本机理应还保存这个 .zip 文件,于是我们可以使用filescan将其路径捕获:

image-20201109185249479

于是就获得了这个 P@ssW0rd_is_y0ur_bir7hd4y.zip 两个重要的信息:①在内存镜像中的物理偏移量;②路径;


dumpfiles

可以使用该插件来提取内存驻留文件(Memory-Resident Files),需要为它提供一些必要的参数:

  • -Q

    待导出文件的物理地址(可以通过filescan获得)

  • -D

    导出到本地的目录(通常指定为 output/

  • -S

    该参数会将导出文件在原内存镜像中的一些信息,以 .json 的形式存在

此外,dumpfiles会出于「保持空间对齐(maintain spatial alignment)」的目的,在导出的文件末尾进行零填充(Zero-Pads);有些时候会对导出的文件使用其它工具进行分析,而有些工具会因为末尾填充的 \x00 导致分析错误,因此必要情况下需要手动删除末尾的 \x00

在导出后,文件的命名遵循以下规则:

file . PID . [SCMOffset | CAOffset] . [img | dat | vacb]

  • PID —— _FILE_OBJECT 对象所处进程的ID
  • img —— 表明该文件导出自一个 ImageSectionObject 对象
  • dat —— 表明该文件导出自一个 DataSectionObject 对象
  • vacb —— 表明该文件导出自一个 ShareCacheMap 对象

这里由于不深入了解,因此可以不过多的理会,只需知道导出内存镜像中某个特定文件时,可能同时导出 .img.dat.vacb 文件,三者都是相同的(可能零填充的数据量不同);此外 SCMOffsetCAOffset 也暂时不理会

例如,上面的题目中,通过cmdscan获得了 .zip 文件的物理地址 0x0000000002c61318,因此我们可以尝试导出:

image-20201109193707564

可以看到 DataSectionObjectSharedCacheMap 中都存在 .zip 文件的数据,因此会同时导出 .dat.vacb 文件;我们通过修改后缀名、删除零填充数据就可以恢复 .zip

然后根据文件名提示,密码是生日日期,直接爆破数字即可获得压缩包密码 19950101,解而解压得到flag:flag{Thi5_Is_s3cr3t!}

此外,这道题目的另一种做法是:通过cmdscan发现通过 nc 传输了 .zip 文件,并且检测到了 nc.exe 进程,所以用memdump将整个 nc.exe 进程导出为转储文件;然后用foremost强行分离,可以获得 .zip 文件


connscan

扫描内存镜像文件中的所有连接活动,包括活动连接和已终止的连接

以上面的题目为例,我们通过cmdscan发现有通过 nc 的连接活动,主要是向IP为 1992.168.57.14:2333 传输了一个 .zip 文件,那么本次活动也应该被connscan提取到:

image-20201109200732946

事实上,这道题目的预期做题顺序是:①根据题目中的“发送”猜测有连接活动,通过connscan发现远程交互IP以及对应的进程ID为120;②pslist查看所有进程,发现PID = 120对应的是 nc.exe,又因为同时存在 cmd.exe,所以很可能是在Cmd中启动了 nc;③cmdscan查看历史命令记录

可惜的是,connscan只能够支持x86、x64 Windows XP和Windows 2003 Server使用,其余则会报错,例如:

1
ERROR   : volatility.debug    : This command does not support the profile Win7SP1x86_23418

hashdump

用户在登陆Windows系统时,需要输入密码,这里就涉及Windows是如何存储用户密码的了

Windows实际上是对用户的密码进行Hash计算后存储,每当用户输入密码时,对当前的输入进行相同的Hash计算,比对后台的哈希值后,确定是否通过

目前来看,Windows作用于用户密码的Hash计算方法主要有2种:

  1. LM-Hash
  2. NTLM-Hash

这里只介绍Windows的密码Hash计算方式,Linux相关内容可以参考:https://blog.csdn.net/lws123253/article/details/89228589


LM-Hash

LM-Hash的计算规则如下:

  1. 用户输入的密码最大长度为14个字符,多余的字符会被截断
  2. 将用户的密码转换为大写
  3. 将用户的密码转换为十六进制字符串,不足14 Bytes则在后面用 \x00 来补全
  4. 将14 Bytes分为两个7 Bytes部分,每个部分长56 Bits,再每7 Bits分为8组,每组后面添加一个 0,构成8 Bits;添加后14 Bytes变为16 Bytes
  5. 16 Bytes分为两组8 Bytes,分别作为DES的密钥,对Magic Number KGS!@#$% 进行加密
  6. 将加密后的两个十六进制字符串拼接,得到LM-Hash的计算值

我们用Python3来演示一下计算 qwer1234 的LM-Hash值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
M = b"KGS!@#$%"
K1 = b"QWER123"
K2 = b"4\x00\x00\x00\x00\x00\x00"

bin_K1 = ""
for i in K1:
bin_K1 += bin(i)[2:].zfill(8) # 7个字符,每个字符的ASCII码转8 Bits二进制
temp_K1 = ""
for i in range(0, len(bin_K1), 7):
temp_K1 += bin_K1[i:i+7] + "0" # 在每7 Bits后面插入一个"0",使得7 Bytes变为8 Bytes

true_K1 = b""
for i in range(0, len(temp_K1), 8): # 8 Bytes的01序列转换为对应的ASCII码
true_K1 += bytes(chr(int(temp_K1[i:i+8], 2)), encoding="latin_1")
print(true_K1)
# b'P\xaa\xd0\xaa"\x88\xc8f'

用同样的方法得到后半部分作为DES加密的密钥为 b'4\x00\x00\x00\x00\x00\x00\x00';然后进行加密:

1
2
3
4
5
6
7
import pyDes
import binascii

key1 = pyDes.des(true_K1)
cipher_1 = binascii.hexlify(key1.encrypt(M))
print(cipher_1)
# b'1319b0fa23c89f2d'

同样得到另一个密钥加密 KGS!@#$% 的结果为 b'ff17365faf1ffe89'

因此 qwer1234 最终的LM-Hash值为 1319b0fa23c89f2dff17365faf1ffe89

参考网上的文章,有找到Python2快速计算LM-Hash的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# coding=utf-8
import base64
import binascii
from pyDes import *

def DesEncrypt(str, Des_Key):
k = des(Des_Key, ECB, pad=None)
EncryptStr = k.encrypt(str)
return binascii.b2a_hex(EncryptStr)

def Zero_padding(str):
b = []
l = len(str)
num = 0
for n in range(l):
if (num < 8) and n % 7 == 0:
b.append(str[n:n + 7] + '0')
num = num + 1
return ''.join(b)

if __name__ == "__main__":

test_str = "123456"
# 用户的密码转换为大写,并转换为16进制字符串
test_str = test_str.upper().encode('hex')
str_len = len(test_str)

# 密码不足14字节将会用0来补全
if str_len < 28:
test_str = test_str.ljust(28, '0')

# 固定长度的密码被分成两个7byte部分
t_1 = test_str[0:len(test_str) / 2]
t_2 = test_str[len(test_str) / 2:]

# 每部分转换成比特流,并且长度位56bit,长度不足使用0在左边补齐长度
t_1 = bin(int(t_1, 16)).lstrip('0b').rjust(56, '0')
t_2 = bin(int(t_2, 16)).lstrip('0b').rjust(56, '0')

# 再分7bit为一组末尾加0,组成新的编码
t_1 = Zero_padding(t_1)
t_2 = Zero_padding(t_2)
print t_1
t_1 = hex(int(t_1, 2))
t_2 = hex(int(t_2, 2))
t_1 = t_1[2:].rstrip('L')
t_2 = t_2[2:].rstrip('L')

if '0' == t_2:
t_2 = "0000000000000000"
t_1 = binascii.a2b_hex(t_1)
t_2 = binascii.a2b_hex(t_2)

# 上步骤得到的8byte二组,分别作为DES key为"KGS!@#$%"进行加密。
LM_1 = DesEncrypt("KGS!@#$%", t_1)
LM_2 = DesEncrypt("KGS!@#$%", t_2)

# 将二组DES加密后的编码拼接,得到最终LM HASH值。
LM = LM_1 + LM_2
print LM

验证可知与我们的Python3代码效果是相同的


LM-Hash由于提出的年份较早,因此弱点十分明显,例如:

  • 用户密码会被自动转换为大写,并且通过截断或补齐使得长度强制为14 Bytes
  • 14 Bytes被分为两组7 Bytes,分别对 KGS!@#$% 进行加密

14个可打印字符组成的密码共有 $95^{14}$ 种可能,而由于是分为两组单独加密的,被腰斩为 $95^{7}$;又因为不可能有小写字母,因此可能性进一步将为 $69^{7}$

在现代计算机的暴力破解面前,LM-Hash不堪一击


NTLM-Hash

LM-Hash由IBM设计,在洞悉其弱点后,微软提出了自己的哈希算法:NTLM Hash

NTLM Hash主要采用MD4哈希算法,其规则非常简单:

  1. 每1 Byte的密钥后面附加1 Byte的 \x00
  2. 对整个字符串进行MD4计算

我们同样用 qwer1234 来演示NTLM Hash的计算过程,实际上非常简短:

1
2
3
4
import hashlib

print(hashlib.new("md4", b"q\x00w\x00e\x00r\x001\x002\x003\x004\x00").hexdigest())
# 0a640404b5c386ab12092587fe19cd02

密码 qwer1234 的NTLM Hash计算结果就是 0a640404b5c386ab12092587fe19cd02


NTLM Hash的安全性取决于MD4,而如今MD4已经不再是安全的哈希算法了,面对碰撞有很大破解的可能

因此现在更常用的是NTLMv2 Hash算法,这个算法我们先不谈论


Windows SAM

Windows对用户账号的安全管理使用了安全账号管理器(Security Account Manager,简称SAM)的机制

SAM文件其实就是账号-密码的数据库文件,当我们登陆系统时,系统会自动将账号-密码加密,并与Config中的SAM自动校对,如果与SAM中加密的数据全符合,则成功登陆;否则失败

当登陆Windows系统后,SAM是被锁死的,无法复制、移动以及打开;SAM的路径通常是:

1
C:\Windows\System32\config\SAM

因此要想获取SAM文件中的内容,需要一些特殊的方式;而「Dump为内存镜像」则是其中一种

将当前的操作系统Dump为内存镜像,然后可以通过工具检索到其中的SAM(工具例如Volatility等);由于这时的Windows未登陆,因此访问不会遭到拒绝


在CTF中,有时会要求我们提取内存镜像文件中,用户的密码,这时我们可以使用hashdump插件

hashdump插件的功能定义是:「Dump user NTLM and Lanman hashes」,它能够直接提取出内存镜像文件中,存储用户账号-密码的SAM文件的内容

例如:

image-20201108084408153

上图对 Temp.raw 使用了hashdump插件,得到结果:

1
2
3
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
CTF:1000:aad3b435b51404eeaad3b435b51404ee:0a640404b5c386ab12092587fe19cd02:::

在Windows操作系统中,存储用户账号-密码的格式是:

username : RID : LM-Hash : NTLM-Hash : : :

默认的,当Windows用户的密码 ≤ 14个字符时,SAM文件中既存放着LM-Hash值、又存放着NTLM-Hash值;当用户密码大于14个字符时,SAM中只存放NTLM-Hash

而由于LM-Hash十分不安全,我们可以修改注册表,使得SAM文件中只存放NTLM-Hash

具体操作参考网上文章:在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa 下,将名为 nolmhash 的DWORD值修改为1(如果没有该字段则新建),然后重启系统即可

实际上,从Windows Server 2008版本开始,系统就自动禁用了LM-Hash

当LM-Hash为 aad3b435b51404eeaad3b435b51404ee 时,表示的是「空密码」或「未使用LM-Hash」;从我们上面的截图来看,3个用户的LM-Hash都是该值

(而NTLM-Hash为 31d6cfe0d16ae931b73c59d7e0c089c0 也表示「空密码」或「未使用NTLM-Hash」)

Windows Hash中的 RID运行标识(Runtime Identifier)的缩写,它标识着每个系统用户

对内置的管理员权限账户Adminstrator,RID 为500;而对其它的普通账户,RID 从1000开始、逐渐递增


Brute Hash

在提取到用户的Hash后,需要想办法获得Hash计算前的明文;基于Hash的特性,只能通过爆破枚举的方式

我们以前面hashdump获得的SAM文件内容举例:

image-20201108084408153

尝试破解用户CTF的密码 0a640404b5c386ab12092587fe19cd02

  1. 在线查询网站

  2. JoheTheRipper

    下载和使用参考官网:https://www.openwall.com/john/

    我们首先将hashdump提取的Hash导出为文件:

    1
    volatility -f Temp.raw --profile=Win7SP1x86_23418 hashdump > hash.txt

    然后只需为JoheTheRipper提供该 hash.txt,并指定破解的格式是 NT 即可

    由于JoheTheRipper破解 qwer1234 需要的时间有点长,因此可以尝试破解下面的内容:

    1
    2
    3
    4
    5
    7UNEJz9$:1080:5e011cc31459cf7c25ad3b83fa6627c7:885e980ae56101097d2010bfb540de0c:::
    q0Y3a69$:1081:f79f60ca37893010c2265b23734e0dac:58db4601967b755c1a05f447764bae63:::
    RhdV8i6$:1083:b5a4c5eb4b9d3ee1aad3b435b51404ee:96880159e785de5314803b1169768900:::
    lwq9SL5$:1085:104e1e570ba3356d25ad3b83fa6627c7:226d410164c5e2d14204f05cfea5107b:::
    g6mMT0p$:1066:a3218b383ae047331aa818381e4e281b:6142c213414e24fb890baa14ca909953:::

    将上面内容保存为 hash.txt,然后使用JoheTheRipper进行破解:

    image-20201108103253831

    可以看到,JohnTheRipper成功将用户名对应的密码爆破出来

    由于指定JohnTheRipper破解的方式是 NT,因此其会尝试结合LM-Hash和NTLM-Hash,将密码破解;但同时得注意,由于JohnTheRipper采用的是暴力破解的方式,因此有可能会长时间卡在某次解密中,通常结合在线网站和JohnTheRipper是比较好的选择

    JohnTheRipper成功将某次的Hash破解后,会将其记录在同目录的 john.pot 文件中:

    image-20201108103622406

    john.pot 记录的格式则是:$ <format> $ <hash> : plaintextJohnTheRipper在破解一个Hash前会在 john.pot 中进行查找,如果是已破解的记录则会跳过破解


* [Example_3]

题目来源:2020 湖湘杯线上赛 Misc_2 [passwd]

题目描述:we need sha1(password)

链接:https://pan.baidu.com/s/1DsA9TUtxiwgEUiukZbmkVg
提取码:Exam

解压压缩包,得到 WIN-BU6IJ7FI9RU-20190927-152050.raw

由于是需要用户密码,Volatility直接解决即可:

  1. imageinfo查看profile,选择一个 Win7SP1x86_23418 使用
  2. hashdump查看哈希值,留意到有个CTF用户,哈希值为 0a640404b5c386ab12092587fe19cd02
  3. 在线网站解哈希得 qwer1234,SHA1计算得到 db25f2fc14cd2d2b1e7af307241f548fb03c312a 即为flag

本部分学习参考:


本部分结束

参考文献:

参考Write-up: