一共两题,一题是固件逆向,一题是pwn。
key–逆向
这是一个嵌入式固件的逆向。
固件逆向,第一要做的就是确定片型和板型。确定片型可以知道硬件资源,通用寄存器及内外部IO寄存器地址及指令集。确定板型能知道外部器件及固件大致可能的功能。
因为之前碰巧在flare-on做过一次,还是用仿真投机出来的,这次也想偷懒来投机一把。
没有直接IDA,先找工具把hex转成bin,用16进制软件打开,发现有 的字样。上次接触的是Arduino UNO,试了下以前的工程加载固件,提示ram超范围了。
虽然对arduino各板型不熟,但有一点可以肯定,那就是用了atmel的片子。用ida加载,选择atmega32_l没出什么问题,于是就开始上atmel的官网进行查找芯片,最后确定芯片是 ,下面就是找板子了。
在这又花了很多功夫,一度怀疑 是扰乱视线了,因为找到了 的资料,感觉是改的它的固件,让程序自动输出字符。照着 的原理图做了仿真,并没有反应。
又尝试看没有接触过的avr指令集代码,突然发现 的字样,于是搜了下,这是Arduino的一个板型。看介绍猜想是模拟键盘打字,因为鼠标模拟在陌生环境下不好实现功能。
于是乎,照着板子的功能继续画图仿真,就是USB没有反应。用arduino的例程编译成固件,再仿真还是USB没有反应。又去查资料,原来proteus的USB仿真还没有支持这个片子。折腾了一天。
现下只有两个办法,一是找硬件跑跑;二是看代码。
多渴望有一个硬件啊,然而并没有。只能啃代码了。
找了点资料,通过阅读 的初始化部分代码,按初始化结果重新创建 segmentation。并简单更改了下寄存器的名。
具体做法是,建一个文件,按初始化的结果及相应地址偏移写文件。注意 的ram大小是2.5k,地址从0x100开始,所以最后文件大小为0xa00+0x100=0xb00,数据从0x100开始写,与上图中的代码对应。
IDA载入固件文件,处理器选 ,具体型号选 。因为两者内存布局相差不大,ROM大小也一致。 调出 视图,选择 seg删除。打开 ,选择刚才为ram建的文件,参数如下。再seg进行重新命名。
加载完RAM之后,寄存器的名字就都没有了。现在要修改ida目录下的cft/avr.cfg 找到 的部分复制一份对照datasheet进行修改。做题时我没有进行配置文件修改,直接用的此配制,虽后来在ida中进行了修改,但是这种修改只对外部IO寄存器起作用,其它的代码中的显示不会改变。
修改好后,进入 的 选项卡,点开 选择刚修改好片型配置。这样就能重新定义寄存器名了。
以上有点啰嗦,仅供没接触过此类的童鞋参考。下面进行程序简要分析。
此片型硬件资源有限,结合之前模拟键盘的猜想,到此应该比较确定了。所以当时我的想法是从USB着手找关键代码。
AVR指令集没有接触过,就一步步对着datasheet理代码,从USB寄存器入手,找到了USB相关操作的代码,然后溯源,找到USB输出的部分,到键盘模拟控制部分,直至主功能函数。最后发现主功能函数就紧接在 初始化之后,真是远在天边,近在眼前。现在可以看出,arduino的固件是静态编译的单文件启动固件,且代码量不大,其主函数入口就在 函数的最后部分。
下面的图就是溯源过程。这里已经追踪到了部分键盘模拟动作的功能实现。
再往上一层,就能到达主功能函数及模拟输出字符串的键盘动作。
下面看看主程序。先设置USB端口,这部分不看,贴下主功能函数的部分代码。
以上代码是键盘模拟操作的开头一部分。
先按下 ,运行
再按shift,确保是英文输入状态
然后调用一个模拟发送字符串的函数发送 ,回车。运行了记事本程序
再在记事本中模拟输入,包括不时出现的退格键
程序最后循环输入退格键,删除全部的输入
再看下模拟发送字符串的部分。
函数先检查传送的字符串地址是否有效,无效则返回
再计算字符串长度,并跳到0x1B1
取出一个字符
通过计算得到目标函数地址,0x81b,此函数就是模拟键盘按键及释放的过程,发送一个字符
检查字符串是否发送完成,未完成取下一个字符发送
最后删除前的文本为
按计算式计算出结果,并转成字串为: 。
annul–pwn
查下保护:
此题共有两处存在栈溢出。分别在rename及get packet输入message时。
信息泄露点有两个,一个是 时的金币数目,而且此处开始能把初始的指针地址泄露出来;另一个是 。因为与此相关的两个指针都可以通过 覆盖。
只是另一个溢出不知道怎么用。当时看题粗糙, 的 没看到,后面就根本不会去看了。
我的思路是:先把表示金币总数所在的 指令泄露出来,然后再泄露got表中的Libc函数地址。
接下来还有两条路:一是将 的got表改了;二是绕过canary,构造rop。
但是写got表只有一种办法,就是通过金币总数写。 一次加一个不大于100的随机数。
查了下, 的偏移在 后面,随机数也可以预测,算下来,把 的got改成 或其附近的,要 8000万次左右。时间代价太大,行不通。翻看代码,确实没能找出这办法可行的路径。
于是就想绕过canary,构造rop。将 在got表的值改成一个其原始值后面一点的 所在的地址,让其直接返回就可绕过。这次 的次数大多在几十次。可行。
下面是完整exp。
此题也可泄露栈地址,再泄露canary然后ROP。
还有一种方法,就是利用C++的异常处理过程。
领取专属 10元无门槛券
私享最新 技术干货