TA的每日心情 | 难过 2023-6-30 13:24 |
|---|
签到天数: 40 天 [LV.5]常住居民I
|
前言 逆向的相关资讯可以到我们网站了解一下,从专业角度出发为您解答相关问题,给您优质的服务!
际分析嵌入式固件过程中,经常会遇到各种非L和非常见系统的固件,这类固件往往就是一个"裸"的二进制程序,而不像ELF和PE这些有特定结构的可执行程序,对于这类固件往往很难对它进行动态调试,而只能静态分析。比较近一段时间,学习了很强大的Q框架,它除了可以模拟运行各种可执行程序,还提供了D接口,于是乎我就想着能不能用Q配合IDA动态调试固件。遗憾的是上关于Q的教程大部分都是模拟ELF和PE这些相对来说比较标准的程序,而模拟像这种就一个"裸"的二进制程序的资料却很少,这里分享下我的摸索过程
关于Q和U这里就不多介绍了,详见
Q:
U:
下面以我上个帖子用到的固件3RCT6来介绍如何用Q框架模拟运行指定函数并启用D,然后使用IDA进行动态调试
开始
首先时官方的有一个引起了我的注意,如下图,的示例有一个模拟下的。据我所知是一个,它在固件中就是一个"裸"的二进制程序,于是这里面可能就有我想要的样例
查看__代码,一直拉到比较后,如下图,这部分代码就是模拟运行"裸"的二进制程序的一个例子
接下来就是照猫画虎,仿照着这个来尝试模拟运行3RCT6固件中的XTEA函数。这里简单介绍下这个3RCT6固件,它的加载基地址为000000,从前一篇的分析可以知道它在地址00E8有个XTEA函数,这个函数就是接下来需要模拟调试运行的函数
首先是导入和的包
1
2
3
4
5
*
*
*
64_
*
*
对照代码样例,读入需要模拟的固件
1
2
3
=
'3RCT6'
(,
''
):
=
()
接下来看原代码的Q对象生成方式,首个=_[0:],剔除了前0字节的原因应该是,固件的前0字节不加载进内存,这里的32固件是整个都加载进内存的,所以可以直接传整个读入的。有个参数需要注意的是="_",看起来是还有一个配置文件"_"
在同级目录下,可以找到这个_,那么接下来,需要简单理解下这个配置文件的各个参数的意义
源码中搜索"_",如下图,可以在中找到关于这几个参数的含义
根据上面代码可画出下图内存映,"_"这里为内存加载地址"_"而不是代码入口点,Q会根据_和_大小分配一块内存,然后将代码写入,需要注意的是默认初始栈寄存器SP指向这块内存_-000的位置,如果模拟运行前不做修改,需要将_预留出一定的栈空间的大小,不然往栈内存写数据时会覆盖内存数据。堆内存的起始地址就是_+_,下图虚拟线表示默认不会直接映堆内存,如果需要使用这块内存,需要先执行()来使用
接下来就可以生成配置文件了,代码如下
1
2
3
4
5
6
7
8
9
#加载地址
_
=
000000
#栈大小
_
=
0000
#堆大小
_
=
0000
#计算固件大小000对齐
_
=
(
()
000
)
*
000
#初始分配的内存大小包括固件和栈空间大小,+000是为了使可用的栈大小与_保持一致
_
=
_
+
_
+
000
_
=
"""
[CODE]
_={_}
_={_}
_={_}
[MISC]
_=
"""
#保存配置文件
(
'-'
,
''
):
(_)
接着,仿照样例生成Q对象,代码如下,因为这里模拟运行的用到了指令,所以需要指定参数=T。因为这里默认模拟的是小端序,而固件恰好是小端序固件,所以可以不指定端序,但是如果模拟的为大端序的固件,则需要指定参数=QL_ENDIANEB。更多参数用法详见官方文档
1
=
Q(
=
,
=
""
,
=
""
,
=
"-"
,
=
T
)
定义模拟运行的起始地址和终止地址,这里因为只模拟运行_800E8函数,所以设为_800E8函数起始地址和终止地址即可
1
2
=
00E8
=
00E6
因为模拟的_800E8函数有3个参数,所以还需要给函数传参
这里简单介绍下ARM中常见的函数传参规范:对于函数参数不超过4个参数时用0,1,2,3寄存器来传参,对于函数参数超过4个参数时,前4个依旧用0,1,2,3寄存器传参,往后的参数以压入栈的方式传参。类似地函数如果有返回值,约定以0寄存器返回。这些规范主要是为了不同程序或模块之间相互调用各自的函数而不出错,因为是规范,所以也就可以不遵循这个规范,比如说你自己用汇编写的程序的话,想怎么传参就怎么传参,只要你自己不限入混乱,程序不出错即可
废话不多说,回到正题,这里获取对象来为函数传参(对象可以读写各个寄存器)。从上面可知,需要模拟的函数的3个参数都是指针,所以需要给0,1,2这3个寄存器写入3个内存地址,而初始SP寄存器(栈寄存器)和之间有块000的内存没有用到,因此这里可以用+00,+00,+00,这3个地址作为函数参数传入。不过单单将3个地址写入0,1,2寄存器还不够,还要在相应地内存写入数据,这样才是完整的传参过程。因为第3个参数是加密结果的输出,所以可以不往该地址写数据。如下代码,为0传入密钥"BA2F96A9BA2F96A9BA2F96A9BA2F96A9",为1传入明文"BE62F8E8DC3446"
1
2
3
4
5
6
7
8
9
=
=
#为第1个参数传参
_(UC_ARM_REG_R0,
+
00
)
_(
+
00
,(
"BA2F96A9BA2F96A9BA2F96A9BA2F96A9"
))
#为第2个参数传参
_(UC_ARM_REG_R1,
+
00
)
_(
+
00
,(
"BE62F8E8DC3446"
))
#为第3个参数传参
_(UC_ARM_REG_R2,
+
00
)
然后是启用,Q默认是不启用的,如下代码,启用并监听相应IP和端口。详细说明见官方文档:
1
=
':0000:9999'
在-4中,设置了后且为""的情况下,直接运行会报"AE:'QOB''_'"错
在源码中找到了相应的描述,如下图,QOB类中有几个函数还没有现,详见:
不过好在,经过测试,在执行之前,可以按以下方式,规避下这个问题。这段代码作用很简单,就是给个空壳给_。P一个非常好的特性就是可以像下面这样,可以很容易地对各种库进行动态修改,而不用去修改库的原文件。下面这部份代码,等以后Q有相应的函数现后就不在需要了
1
2
3
4
5
MM:
__(
,_,_):
_
=
MM()
重新运行后,出现如下信息,说明成功模拟运行,并启用了并监听了相应的IP和端口。需要注意的是,如果在生成Q对象是指定了参数=QL_VERBOSEOFF,那么运行时不会有任何信息
接下来,介绍IDA中如何连接到这个进行动态调试
IDA中选择DS或者捷键F9,选择RGDB
选择DP
输入运行Q机器的IP和端口,如果运行Q和IDA是同一机器同一系统内,则IP填70即可
接着选择DD
勾选如下两个即可
然后选择DM
按照下图,新建几个内存映,否则调试时,IDA可能不能跳转到栈内存中,也不能查看栈内存的数据
这里选择添加两个映分别是栈内存和栈内存到堆内存之间的一小部分,堆内存()因为这里没有用到,所以可以省略,因为IDA分析的固件就是这部分内存,所以也可以省略
比较后选择DA,会出现一个PID为0的进程,点击OK即可
比较后如下图可以看到,IDA进入了调试模式,且停在_800E函数开始的位置,接下来就可以使用IDA进行动态调试了
调试结束后,可以回到Q中,读取相应地址的结果。从我上一个帖子可知预期的密文为"8C79F5DEA9462D",如下图,输出与预期一致
1
(
+
00
,
8
)
()
比较后
本文只是简单介绍了的一些基本用法,关于更多用法详见官方文档:
本文并没有过多介绍的用法,关于的用法可以参看官方文档:,还有论坛的这两个帖子:,
际上对于STM32的模拟可能并不需要那么繁琐,官方有相应的模拟示例,详见:,本文的这种方法主要是用来模拟那些官方还不支持的固件,只不过例子是32的固件而已
本文完整代码包含在附件之中 |
|