逆向Daily
每天都来看一看逆向的小知识和手法。
嵌入式固件
PowerPC固件逆向
ref:https://mp.weixin.qq.com/s/-eUweGQi633D2W1Vs_bIFg
主要用到的工具binwalk、ida pro 7.5、ghidra
binwalk:
- 提取固件:binwalk -e <firmware>
- 失败=>单文件固件或者加密的固件
- 判断明文字符串信息=>分布正常=>非加密固件
- 字节码信息:binwalk -A <firmware>
- 提示指令类型:PowerPC big endian instructions
- 识别函数开始prologue和结束epilogue的指令 => 辅助IDA Pro进行函数识别。
IDA Pro:
-
预设处理器类型(Processor type):PowerPC big-endian[PPC]
-
如果知道基地址还可以设置基地址。
-
函数识别失败 => 跳转到binwalk扫描出的函数prologue指令地址,按C键反汇编,可以自动识别关联的一些函数
-
查看字符串窗口 => 无法查看到对字符串的引用
-
IDA现在识别出的函数太少,很多函数还未识别出来,导致无引用
-
上ghidra自动分析或IDA脚本:根据函数序言特征识别出函数头,再自动分析函数,特征码为
94 21 FF ?? 7C 08 02 A6
。 -
ROM:000020EC 94 21 FF F8 stwu r1, back_chain(r1) //开辟栈空间 # …… ROM:000020F0 7C 08 02 A6 mflr r0 //结束
-
-
加载基地址不正确,导致引用地址错误,无法形成交叉引用
-
识别出函数后,字符串仍然无法形成引用,在于基地址错误
-
确定加载基地址:通过跳转表特征确定基地址、通过字符串引用次数暴力搜索确定基地址
- 通过跳转表特征确定基地址:找到跳转表,如果是绝对地址可以直接通过offset计算出base addr;如果是相对地址,那么需要动态计算指令,从而确定idx=0时的真实地址,根据offset得到base addr
- 通过字符串引用次数暴力搜索确定基地址:大概原理是首先获取固件中的字符串地址,然后通过设置不同的基地址测试该基地址下字符串的引用次数,引用次数越高说明该地址为基地址的概率越大,有一定的通用性。【查找ARM固件效果比较好,需要设置好参数,特别是大小端、字符串长度范围,用时较长】【参考:https://github.com/sgayou/rbasefind】
-
跳转表识别:根据switch语句特征
7D ?? 03 A6 4E 80 04 20
,获取switch语句地址-
#switch语句 7D ?? 03 A6 mtctr rS 4E 80 04 20 bctr
-
-
根据跳转表地址关系可以计算出固件基地址,设置基地址后字符串可以正常引用。
-
-
-
反汇编
- 借用ghirda的函数自动识别结果:将ghirda反汇编得函数地址信息导出,然后使用脚本导入到ida中make code。
- ghirda中导出函数列表方法:Window->Functions 在Functions窗口右键Export->Export to CSV保存。
- ida中导入ghirda函数脚本(见分析链接)=>
ida_funcs.add_func(start_addr)
- 使用PowerPC函数序言prologue特征码:大部分编译器编译生成的函数头可能会有一些固定的指令,如x86平台的mov edi, edi;push ebp,这种情况在PowerPc也存在PowerPC特征码为stwu rS,rD(n);mflr r0,我们可以利用这个特征编写ida python脚本使ida开始自动反编译固件生成函数。
- 借用ghirda的函数自动识别结果:将ghirda反汇编得函数地址信息导出,然后使用脚本导入到ida中make code。
由于固件文件并不像PE、ELF文件有导入表,IDA中也没有常见的内置sig文件,所有的函数都必须靠自己人工识别。但是IDA支持导入sig文件。因此考虑以下方式:
- 搜索获取powerpc相关sig库:https://github.com/IridiumXOR/uclibc-sig
- 找到依赖库:
- 安装linux powerpc交叉编译库,提取lib
- 根据固件中的Copyright string: “Copyright MGC 2004 - Nucleus PLUS - MPC860 Diab C/C++ v. 1.14”等常量字符串,安装VxWorks Tornado开发环境,提取lib。
- 最后基于lib生成sig文件
补充↓
指令:
bctr
:根据ctr寄存器(计数寄存器)值跳转mtctr reg
:表示将reg的值加载到ctr寄存器slwi reg, reg, 2
:reg = reg « 2 = reg*4- i表示立即数Immediate
addis r12, r3, 7
:r12 = r3 + 7« 16 = r3 + 0x70000- s表示左移16位
- i表示立即数
工具:
- 搜索powerpc固件加载基地址可以获取到一些信息,ppc_rebase运行可以得到一个基地址。
- idapython笔记
- 对IDA 7.0编写的IDAPython函数识别
知识:
- 使用IDA分析程序时,目标程序通常都没有pdb符号文件,这时候如果IDA没有对应的sig签名文件,就会有很多库函数和自定义函数无法识别。
- IDA“flair68.zip”工具包中的“pcf.exe”创建模式文件.pat,然后采用sigmake.exe自制sig文件,放入IDA/sig文件夹中,在List of available library modules中使用Shift+F5加载签名文件
- IDA sig文件的工作原理是硬编码特征的方式来匹配和识别指定的函数或者程序入口【其中pat模式文件就是某些函数的编码特征信息】
- 基本上至少可以识别libc中一些字符串处理的函数【采用静态库,无优化的情况下产生的lib比较好】
- ref:https://www.cnblogs.com/SunsetR/p/12252495.html
- 判断加载地址是否正确的方式包括:1) 成功识别出的函数个数;2)正确的字符串交叉引用个数。
Zyxel设备eCos固件加载地址分析
ref:eCos固件加载地址分析
基本的固件加载地址分析方法:
-
对于bare-metal firmware加载地址分析,可以查看对应芯片或者SDK手册等资料,得到内存空间的映射分布,比如STM32F103C8 memory mapping,34页中,
Flash memory
的范围为0x08000000~0x0801FFFF
。 -
对于ARM架构,可以通过中断向量表的方式来推测固件的加载地址【这里对加载地址有一些疑惑,感觉是整个固件都会放到内存里面】。因为在一些ARM bare-metal firmware中,中断向量表中的前2项内容分别为Initial SP value和Reset,其中Reset为reset routine的地址,设备上电/重置时将会从这里开始执行,根据该地址推测可能的加载地址。
- 这一段对IDT表的前两项作用解释的很清楚了:
In the used cores, an ARM Cortex-M3, the boot process is build around the reset exception. At device boot or reboot the core assumes the vector table at
0x0000.0000
. The vector table contains exception routines and the initial value of the stack pointer. On power-on now the microcontroller first loads the initial stack pointer from0x0000.0000
and then address of the reset vector (0x0000.0004
) into the program counter register (R15
). The execution continues at this addressReference:ARM Cortex-M3 Vector table
-
尝试从固件本身出发,通过分析固件中的一些特征来推测可能的加载地址,如查找固件中存在的固定地址(绝对地址),因为即使加载地址不正确,引用的这些固定地址也不会改变;之后再结合偏移就可以得到加载基地址。
- Magpie通过识别
ARM
固件中的函数入口表,然后基于函数入口表中的地址去推测可能的加载基址;- 根据代码片段地址引用处是否是函数的序言指令来判断加载地址是否正确
- limkopi.me通过查找指令中引用的固定地址,成功试出了该
eCos
固件的加载地址。
- Magpie通过识别
eCos固件加载地址分析
查看binwalk工具中总结出来的magic,用于分析固件中的一些字符的特征,比如压缩文件、加密固件的一些特征等,还是实时操作系统ecos的特征。
通过在IDA Pro中的Hex View中观察十六进制特征,提取出可能的引用固定地址。排序后,最小的地址为加载地址的上限,之后在一定的范围内,如0x80000000-加载地址上限,进行尝试,判断字符串引用正确数量最多的加载地址【字符串引用正确可以用“引用地址的前一个字节为\x00”来判定】,则为固件的正确加载地址。
这里还是要注意Ghidra和IDA的差别,使用IDA
加载该固件并设置正确的架构、加载地址等参数后,默认情况下IDA
不会自动进行分析。相比而言,Ghidra
则可以自动进行分析,成功识别出函数并建立字符串的交叉引用。
- 一种方式是对照
Ghidra
分析的结果,在IDA
中进行部分手动Make Code
(当然,也可以直接使用Ghidra
… ); - 另一种方式是写一个简单的
eCos loader
插件,然后IDA
就可以自动进行分析了。
补充↓
- 对于
MIPS32
架构的程序,常见的函数调用约定遵循O32 ABI
【ABI包括传参方式、清空参数方式、返回值放置位置、异常传递等】,即$a0-$a3
寄存器用于函数参数传递,多余的参数通过栈进行传递,返回值保存在$v0-$v1
寄存器中。而该eCos
固件则遵循N32 ABI,最大的不同在于$a0-$a7
寄存器用于函数参数传递(对应O32 ABI
中的$a0-$a3
,$t0-$t3
)。IDA
中支持更改处理器选项中的ABI
模式,但仅修改该参数似乎不起作用。默认情况下"Compiler"
是"Unknown"
,将其修改为"GNU C++"
,同时修改ABI
为n32
,之后反编译代码中函数参数的显示就正常了。
CVE-2020-26567 DSR-250N 远程拒绝服务漏洞分析
ref:https://mp.weixin.qq.com/s/akmzdfEkAyvrPalBBTHCcw
不使用binwalk 的情况下,如何手动的从固件中提取文件系统,并且对漏洞进行分析。
手动提取固件文件系统:
- 通过固件的magic字段,确定文件系统类型。
- 以squashfs文件系统为例,我们首先要确定固件的magic签名头,常见的squashfs的头部特征有这些sqsh、hsqs、qshs、shsq、hsqt、tqsh、sqlz。
- 确定squashfs文件系统的大小。
- 通过dd工具对固件进行剪裁,使用先将 “hsqs”的 magic 签名头的偏移地址开始 dump 出一段数据(一般 squashfs 文件系统的头部校验不会超过100字节)。然后使用 file 命令查看,可以看到 squashfs 文件的大小了。
- dd if=
source file
of=out file
skip=offset of magic number
bs=block size(maybe 1 means one byte)
count=dump size
- 根据文件系统的大小,采用dd工具dump出整个固件的文件系统【即修改count选项为文件系统size】
- 使用unsquashfs解开dd dump的文件,得到完整的root目录
固件分析:
- cgi文件尝尝位于/www/中,一般来说,cgi文件都是和html的界面放在一起的,因此可以直接去/var/www/查找。
- cgi文件的作用往往伴随着http的服务,我们应该往固件中web组件去分析,如bin或sbin中的httpd或uhttpd或thttpd,以及其对应的init文件
- 可以使用grep -r
<string>
来对某目录下的所有文件进行递归查找特定的字符串
Linksys EA6100 固件解密分析
ref:https://mp.weixin.qq.com/s/-D7NeAZyzNGUDr4ItbHksA
固件加密判断及解密方式
固件基本信息判断
- 正常的固件包是以 “img”、“bin”、“chk” 为结尾。
- 如果固件中包含gpg,基本上是使用了GnuPG进行加密【这是为文件生成签名、管理密钥以及验证签名的工具】,即固件很有可能是使用GPG生成的密钥进行加密的。
- 加密判断
- 使用binwalk的熵分析: -E
- 固件任何位置(offset)的熵值都接近1的话,基本是加密。如果存在不接近1的波动一般是没有被加密。
- 加密的固件一般通过
file
只能识别出data,binwalk
也不会有特别的描述 - 一般加密的固件会需要中间件固件来提供密钥或已有密钥来进行解密
- 密钥的常量字符串形式:“BEGIN RSA PRIVATE KEY”(私钥内容)、“BEGIN CERTIFICATE”(证书信息)
- 解密方式
- 使用
gpg --import <path of key>
,来加载密钥。 - 再使用
gpg --decrypt <path of firmware> > <path of decrypted firmware>
解密 - 之后使用
binwalk -Me
就可以成功解密了
- 使用
病毒分析
IDA静态分析REvil/Sodinokibi家族勒索病毒
ref:https://bbs.pediy.com/thread-267604.htm
涉及到的内容有:模块基地址和API地址动态解析、
Insight:
- 基础特性:
- 查看各个节区的熵值——在6左右基本上都是加密的了
- 查看dll表导入,如果只有常见地几个dll被导入,比如kernel32.dll、user32.dll——对IAT导入表做了处理或者加壳
- 查看识别出的API——没有特别的,则说明恶意行为被隐藏了,解密修复IAT。
- 查看字符串资源——大部分为未识别的字符串,加密了。
- 动态解析API:
- IAT表的动态解析/IAT表的解密:对某个.data节的全局数组使用寄存器作为index进行循环解密(每轮可能不断调用同一个函数),解密之后写回该全局数组【一般是以4字节为单位,这样是任何hash算法的因数,采用的是hash对IAT表进行加密处理)
- 验证方式:在之后的汇编代码中看到通过这个数组来调用函数,说明这个数组确实是IAT表【“call 数组基址+数组偏移”的形式】
- 动态获取目标模块【eg:dll】的基地址:switch/case的判断加上函数地址赋值+返回的解析地址被不断解引用
- 定位目标库的导出符号表
_IMAGE_DATA_DIRECTORY
的位置:偏移0x3C解引用后再偏移0x78解引用。- imagebase+0x3c偏移为
_IMAGE_DOS_HEADER
的e_lfanew
字段,即_IMAGE_NT_HEADERS
【占用0x18个字节】,后面是_IMAGE_OPTIONAL_HEADER
【占用0x60个字节】,因此偏移0x3C解析后再偏移0x78为数据目录表的首地址,数据目录表第0项正好记录着导出符号表的偏移和大小。
- imagebase+0x3c偏移为
- 导出符号表中包含:导出函数序号表、导出函数名称表、导出函数地址表
- 定位目标库的导出符号表
- 通过动态基地址获取,从而动态获取需要的API地址
- 基于进程环境块PEB获取进程加载的模块:通过FS:30h获取PEB,PEB的0xC偏移处包含了为
LDR
:information about the loaded modules for the process.【包括所有相关模块的基地址和名称等】- 其中的InMemoryOrderModuleList字段包含了该二进制文件的所有模块信息,是由双向链表进行组织的,即
_LIST_ENTRY
(第一个字段为前向指针,第二个字段为后向指针),每一个元素为LDR_DATA_TABLE_ENTRY结构体,其中的 PVOID DllBase包含了模块基地址。
- 其中的InMemoryOrderModuleList字段包含了该二进制文件的所有模块信息,是由双向链表进行组织的,即
- IAT表的动态解析/IAT表的解密:对某个.data节的全局数组使用寄存器作为index进行循环解密(每轮可能不断调用同一个函数),解密之后写回该全局数组【一般是以4字节为单位,这样是任何hash算法的因数,采用的是hash对IAT表进行加密处理)
- 加密特征:
- RC4算法:两层循环,每轮循环次数为256次,后续还存在异或操作。
IDA Pro:
-
第一次静态加载时应该尽量勾选右下角的所有选项
-
IDA会额外加载资源数据动态导入的DLL数据以及默认不加载的静态节数据
-
资料索引
- Android安全:https://bbs.pediy.com/thread-179524.htm
- 二进制漏洞利用:https://bbs.pediy.com/thread-221734.htm
- Android入门:https://bbs.pediy.com/thread-259556.htm