从一题看C++逆向与机制

栏目: C++ · 发布时间: 5年前

内容简介:最近CTF出现了很多C++逆向题,偶然间看到看雪CTF的一题”半加器”,关于C++全局类对象的机制,拿来练手学习首先用

从一题看C++逆向与机制

最近CTF出现了很多C++逆向题,偶然间看到看雪CTF的一题”半加器”,关于C++全局类对象的机制,拿来练手学习

定位main函数

首先用 PEID 查壳,发现是VC++系列编译器写的程序,无壳

从一题看C++逆向与机制

接着运行程序,发现有用户交互 Please Input:

于是直接在IDA中搜索字符串,根据交叉引用定位到关键代码

从一题看C++逆向与机制

另一种方法

如果程序没有给出字符串交互,如何定位main呢?尤其是在本题去除了很多函数信息的情况下

IDA中 start 函数连续跟进后,来到这一大段代码

从一题看C++逆向与机制

这时还是不知道关键代码在哪,正好我本地有visual studio,在debug下写一个x86程序,并用IDA打开,也跟进到这一处

从一题看C++逆向与机制

发现有注释 Code ,于是跟进 sub_413C10 ,发现main函数

同样的,本题的main函数的关键, v4 相当于 Code ,跟进v4,发现关键函数 sub_4A2890 ,也就是main函数

输入部分

又回到了这个 Please input: ,我这里就算重新分析,按P也会报sp错误

从一题看C++逆向与机制

简单算一下 alt+k 修复,然后就可以愉快的F5了

从一题看C++逆向与机制

我试着把retn指令直接nop掉,发现也可以F5,但是后面调试的时候会报错

这里 if ( v2 <= 30 && v2 >= 10 ) 看起来像是对输入的长度进行限制,动态调试验证确实是strlen的结果

接着从 sub_4917A4 继续跟进,注意这里第二个参数是30,正好是上面字符串最大长度,一直进到 sub_54B010

发现了一堆诡异的代码,不过可以发现 common_tcscpy_s 这样的字符串,结合动态调试,发现这就是strcpy系列的函数

从一题看C++逆向与机制

也就是说,函数 sub_4917A4(dword_5FD088, 30, (int)&unk_5FD068); 是在复制输入的字符串

sub_490507 跟进后发现,对复制的字符串每个字符都异或了0x1F

唯一找到的要求相等是第八位必须是 A ,之后被替换为 #

跟到这里,再后面思路就有些断了,只有对输入进行操作,验证部分呢?

验证部分

再次拿出IDA的交叉引用,输入字符串的引用已经全部看到了,那就对复制的字符串查找看看

从一题看C++逆向与机制

又有对字符串的异或操作, pause 字符串也提示我们,flag验证或许就在这附近,因为 system("pause")常用来防止输入后黑框一闪而过

猜测 sub_490CD7 或许是平凡的 strcmp ,在 if(!xxx) 处下断,用IDA调试一番

从一题看C++逆向与机制

得到 a1 的地址后,在内存区看到有字符串

像调试gdb一样,把返回值eax设置成0,程序输出 ok ,看来我们已经找到了验证逻辑

从一题看C++逆向与机制

解题的话,依次把字符串 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下编译运行

从一题看C++逆向与机制

猜测这可以说明,test对象先于cout对象析构,否则将不会输出cout的一行

为了获得更多的信息,添加了带参数的构造函数

发现 全局对象 后声明,先析构,而 静态对象 先声明,后析构

再把声明顺序交换,输出结果正好相反

可以猜测:析构顺序和对象是全局或是静态没有必然的联系?

查资料发现, 这种现象并不是语言层面的特性,而是跟编译器的具体实现有关

因此,如果在 全局对象 或者 静态对象 析构时,用printf代替cout进行输出

实际场景可能并不多,某些时候debug时可能会用到输出,也算是C++的一个小坑吧

@vct 师傅友情赞助


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

有限与无限的游戏

有限与无限的游戏

[美]詹姆斯·卡斯 / 马小悟、余倩 / 电子工业出版社 / 2013-10 / 35.00元

在这本书中,詹姆斯·卡斯向我们展示了世界上两种类型的「游戏」:「有限的游戏」和「无限的游戏」。 有限的游戏,其目的在于赢得胜利;无限的游戏,却旨在让游戏永远进行下去。有限的游戏在边界内玩,无限的游戏玩的就是边界。有限的游戏具有一个确定的开始和结束,拥有特定的赢家,规则的存在就是为了保证游戏会结束。无限的游戏既没有确定的开始和结束,也没有赢家,它的目的在于将更多的人带入到游戏本身中来,从而延续......一起来看看 《有限与无限的游戏》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具