pwnhub年前最后一战——“血月归来”writeup

一共两题,一题是固件逆向,一题是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++的异常处理过程。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180206G05JXV00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券