内容简介:最近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 机制(原理篇)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序是怎样跑起来的
[日] 矢泽久雄 / 李逢俊 / 人民邮电出版社 / 2015-4 / 39.00元
本书从计算机的内部结构开始讲起,以图配文的形式详细讲解了二进制、内存、数据压缩、源文件和可执行文件、操作系统和应用程序的关系、汇编语言、硬件控制方法等内容,目的是让读者了解从用户双击程序图标到程序开始运行之间到底发生了什么。同时专设了“如果是你,你会怎样介绍?”专栏,以小学生、老奶奶为对象讲解程序的运行原理,颇为有趣。本书图文并茂,通俗易懂,非常适合计算机爱好者及相关从业人员阅读。一起来看看 《程序是怎样跑起来的》 这本书的介绍吧!