内容简介:最近CTF出现了很多C++逆向题,偶然间看到看雪CTF的一题”半加器”,关于C++全局类对象的机制,拿来练手学习首先用
最近CTF出现了很多C++逆向题,偶然间看到看雪CTF的一题”半加器”,关于C++全局类对象的机制,拿来练手学习
定位main函数
首先用 PEID
查壳,发现是VC++系列编译器写的程序,无壳
接着运行程序,发现有用户交互 Please Input:
于是直接在IDA中搜索字符串,根据交叉引用定位到关键代码
另一种方法
如果程序没有给出字符串交互,如何定位main呢?尤其是在本题去除了很多函数信息的情况下
IDA中 start
函数连续跟进后,来到这一大段代码
这时还是不知道关键代码在哪,正好我本地有visual studio,在debug下写一个x86程序,并用IDA打开,也跟进到这一处
发现有注释 Code
,于是跟进 sub_413C10
,发现main函数
同样的,本题的main函数的关键, v4
相当于 Code
,跟进v4,发现关键函数 sub_4A2890
,也就是main函数
输入部分
又回到了这个 Please input:
,我这里就算重新分析,按P也会报sp错误
简单算一下 alt+k
修复,然后就可以愉快的F5了
我试着把retn指令直接nop掉,发现也可以F5,但是后面调试的时候会报错
这里 if ( v2 <= 30 && v2 >= 10 )
看起来像是对输入的长度进行限制,动态调试验证确实是strlen的结果
接着从 sub_4917A4
继续跟进,注意这里第二个参数是30,正好是上面字符串最大长度,一直进到 sub_54B010
发现了一堆诡异的代码,不过可以发现 common_tcscpy_s
这样的字符串,结合动态调试,发现这就是strcpy系列的函数
也就是说,函数 sub_4917A4(dword_5FD088, 30, (int)&unk_5FD068);
是在复制输入的字符串
而 sub_490507
跟进后发现,对复制的字符串每个字符都异或了0x1F
唯一找到的要求相等是第八位必须是 A
,之后被替换为 #
跟到这里,再后面思路就有些断了,只有对输入进行操作,验证部分呢?
验证部分
再次拿出IDA的交叉引用,输入字符串的引用已经全部看到了,那就对复制的字符串查找看看
又有对字符串的异或操作, pause
字符串也提示我们,flag验证或许就在这附近,因为 system("pause")常用来防止输入后黑框一闪而过
猜测 sub_490CD7
或许是平凡的 strcmp
,在 if(!xxx)
处下断,用IDA调试一番
得到 a1
的地址后,在内存区看到有字符串
像调试gdb一样,把返回值eax设置成0,程序输出 ok
,看来我们已经找到了验证逻辑
解题的话,依次把字符串 urj}pux<}n{iqyrh
异或 0x1CF,得到这一串
jmubojg#bqdvnfmw`
再替换第八位为 A
,即是最后的flag jmubojgAbqdvnfmw
做到这里,想用angr试着跑跑,但是有点问题,跑不出来,也没想清楚为什么
一些思考
到这里题目已经做完了,但是作者是如何实现的?main函数中并没有找到验证部分
看了其他人的解析,发现是在类的 析构函数 中,但应该是 全局对象 ,析构函数发生在main结束之后
在《C++反汇编与逆向分析技术揭秘》中有详细的讨论
局部对象:作用域结束前调用析构函数
堆对象:释放堆空间前调用析构函数
参数对象:退出函数前,调用参数对象的析构函数
返回对象:如无对象引用定义,退出函数后,调用返回对象的析构函数,否则与对象引用的作用域一致
全局对象:main函数退出后调用析构函数
静态对象:main函数退出后调用析构函数
在main函数结束之后,会由 exit
函数终止程序,全局对象的析构也在其中,其中有 构造代理函数
和 析构代理函数
,后者将所有的全局对象析构,因此main函数中无法发现验证过程
此外,突然想到一个实验,看以下代码
#include<iostream> #include<stdio.h> using namespace std; class test { public: int num; test(int num){ this->num = num; } ~test() { printf("%d:printf test destructedn",this->num); cout << "cout test destructed" << endl; } }; static test t1(1); test t2(2); int main() { return 0; }
由于vs2017会在main结束之后一闪而过,因此在ubuntu下编译运行
猜测这可以说明,test对象先于cout对象析构,否则将不会输出cout的一行
为了获得更多的信息,添加了带参数的构造函数
发现 全局对象 后声明,先析构,而 静态对象 先声明,后析构
再把声明顺序交换,输出结果正好相反
可以猜测:析构顺序和对象是全局或是静态没有必然的联系?
查资料发现, 这种现象并不是语言层面的特性,而是跟编译器的具体实现有关
因此,如果在 全局对象 或者 静态对象 析构时,用printf代替cout进行输出
实际场景可能并不多,某些时候debug时可能会用到输出,也算是C++的一个小坑吧
@vct 师傅友情赞助
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 快速失败机制 & 失败安全机制
- JavaScript线程机制与事件机制
- 区块链是怎样将分布式组网机制、合约机制、共识机制等技术结合并应用
- Java内存机制和GC回收机制-----笔记
- javascript垃圾回收机制 - 标记清除法/引用计数/V8机制
- Android 8.1 源码_机制篇 -- 全面解析 Handler 机制(原理篇)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
有限与无限的游戏
[美]詹姆斯·卡斯 / 马小悟、余倩 / 电子工业出版社 / 2013-10 / 35.00元
在这本书中,詹姆斯·卡斯向我们展示了世界上两种类型的「游戏」:「有限的游戏」和「无限的游戏」。 有限的游戏,其目的在于赢得胜利;无限的游戏,却旨在让游戏永远进行下去。有限的游戏在边界内玩,无限的游戏玩的就是边界。有限的游戏具有一个确定的开始和结束,拥有特定的赢家,规则的存在就是为了保证游戏会结束。无限的游戏既没有确定的开始和结束,也没有赢家,它的目的在于将更多的人带入到游戏本身中来,从而延续......一起来看看 《有限与无限的游戏》 这本书的介绍吧!