180515 逆向-被隐藏的真实(DDCTF_Re3)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/whklhhhh/article/details/80317360

这题本来单纯地以为是很简单的题,听家俊师傅讲了一下出题思路才发现他的想法真的比答题人多得多……

main函数里调用了三次get_pwd()这个函数来check输入

get_pwd中接受输入,然后对count自增,调用了Bitcoin对象的一个函数来校验输入

如果熟悉C++逆向的话,一眼就能看出来这是在调用虚函数
因为v2是对象的空间,在C++的对象构造中,开头4个字节指向的是虚函数表
*v2指向的是虚函数表,**v2就是虚函数表的第一个函数了

(图片引自C++对象模型详解https://www.cnblogs.com/tgycoder/p/5426628.html

做题的时候不是很熟悉C++的模型,以及虚函数反编译的不是很明显,直接动态调试做的

初始状态这个虚函数是init,其中调用了verify,第一次直接返回输入,对应输出列表的需求,要输入0xdeadbeef的小端序表示”efbeadde”

如果纯静态逆向,会继续往下看verify函数的第二、三次校验,但事实上第二次就没有调用init了
我在做的时候因为不熟悉虚函数,所以动态调试直接跟进函数,发现进入了sub_4046D7这个函数,其中的核心函数b58e乍看起来很复杂,但其实通过其中的*24(实际上是*256)、%58,和题目内的信息描述很容易想到比特币地址转换方法–base58
直接进行解密获得bytes类型即可通关(注意最后4字节是sha256的验算字节,不可提交,否则会导致flag的sha256计算错误。因为第二关仅截取19个字符送入,但跟flag有关的sha256却会把所有input全部进行运算,导致最后提示Correct实际上的flag却不对)

话是这么说,直接套来的脚本解密出来其实没看懂,还是自己查资料从加密到解密走了一趟才get到应该是hex格式

第三小关本来以为是脑洞题了,其实是误打误撞做出来的,运气是真的好OTZ
这次虚函数又回到了verify,将Input进行两次sha256然后逆序与结果比较,当时的想法是结合提示语:

If you know what exactly ‘1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa’ is, I believe you must know the correct input corresponding to this secret

查了一下发现这条地址是中本聪在开始比特币时记录的第一个块–创世块
刚开始想到的是根据创世块向区块链后端爆破,某个区块的sha将会满足要求
不过查了一下好像也没什么适合计算的,总不能自己重复一遍挖矿过程吧233

卡了许久,代码中突然发现一个关键点

长度80是个很关键的提示
于是去找了区块链结构解析,发现区块头的长度正好是80个字节
https://webbtc.com/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f.hex

在这里得到了创世块的头部信息,提交即可获得flag

事实上在经过家俊师傅的讲解后,再回头逆才发现这里的memcmp被覆盖到了sub_404A36函数

这个函数中通过异或生成了一个串,然后将输入的字符串与做过两次sha256再逆序的输入进行memcmp

这个两次sha256再逆序的操作,在之前的查资料过程中发现就是比特币的哈希方法,把异或生成的串dump出来去搜索

IDC>auto i;for(i=0;i<80;i++){Message(“%02x”, Byte(0x6d0a00+i));}
000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f4e61

发现是创世块的哈希值,由此倒推出原输入是创世块

比赛的时候从一个长度猜到创世块头部,不得不感叹自己的运气真的是……

最后再分析一下虚函数的覆盖,和家俊师傅挖下的种种坑
首先注意到虚函数表中的第一个函数在初始情况下是Init
逐步跟踪,发现Bitcoin在构造函数中就有玄机

这里跳转到了0x6D0F88处,过去看看

这时是直接一个leave和retn返回了

但是后面有很多不可识别的脏数据,暂且先放着不管,继续往后走

get_pwd函数中就如之前分析的一样,没什么问题
问题在于析构函数里

乍一看好像没什么问题哦,delete释放空间嘛
注意这里的(this+3)指向的就是刚才跳转的0x6D0F88
再点进delete内一看

?!
跟正常调用free的delete完全不一样,左边function列表中也竟然出现了两个同名的函数
另外一个才是调用free的原delete,这个是冒牌的!

这里利用的是IDA的重命名机制–C++编译器为了区分重载函数,会对函数生成一些其他字符来修饰。delete函数被修饰以后的名称是”_ZdaPv”,但是冒牌delete函数的原名是”__ZdaPv”,IDA同样也会将其重命名为delete,导致被忽视

这个delete中将参数指向的空间写为0x90,即NOP的机器码
因此可以将刚才的leave、retn和大量脏数据全部写成NOP,从而使下一次调用构造函数的时候可以执行一些其他代码

而这个机密的函数就是脏数据之后的代码,sub_6D1048

这里的a1是rbp,频繁调用的a1-8就是this指针
可以看到,每次调用都会覆盖一次虚函数
另外当第三次执行的时候会将memcmp重写

整个理透以后这个题目学到的应该是最多的,各种阴险技术,真的很有意思23333
可惜做的时候动态跟过去会忽视掉这里的大量重写,比较可惜

阅读更多
换一批

没有更多推荐了,返回首页