在地铁上破解软件,被一群人围观!

注意看,这是一道软件破解题:

图片

据说第一个破解成功的人,会有神秘奖品!

结果200多位小伙伴参赛,只有十几位成功破解拿到了flag,真的有这么难吗?来看看你能扛到第几关?

题目是这样的:附件是一个压缩包,里面是一个exe文件被拆分成了几部分,需要做的是把这个exe文件重新组装起来,成功运行它,然后破解它拿到flag,这是我给学逆向的同学出的一道典型的CTF Pwn类型的题目。

首先下载这个压缩包,解压后,会发现有4个文件:

图片

然后我们用十六进制编辑器分别打开这四个文件,看一下:

b.dat:

图片

看不出是个啥,再看看其他的。

d.dat

图片

只要学习了PE文件格式(第10课)这一节的同学,通过MZ标记很容易看得出来,这是PE文件中的DOS头部,是整个文件最开始的部分。

然后用十六进制编辑器创建一个新文件,把这个第一部分d.dat的内容复制进去。

接下来看看其他块。

p.dat

图片

这个也很容易看出,是NT头,是紧接着前面DOS头后的第二部分。

把这部分内容也复制到咱们刚刚创建的新文件后面。

s.dat

图片

这一个部分,看右边的.text、.rdata、.data就能看得出来,这是节表的内容,是在NT头后的第三部分。

这样推断出来,最开始的b.dat应该就是最后文件的正文内容了,它的体积也是最大的。

把这四部分内容按顺序复制到我们新建的那个文件中,然后另存为一个exe文件,尝试去双击执行它。

PS:

实际上,这四个块的命名也有讲究,d就是dos头,p就是PE头,s就是节表section,b就是文件正文体body。

然后,有很多小伙伴发现了一个问题,双击运行程序报错了:

图片

难道拼的有问题?

有不少小伙伴都倒在了这里。

实际上,这里我埋了一个小小的坑,其中有个节我多塞了一个字节进去,就是这一个字节,让拼出来的PE文件格式错误,运行不起来。

不过毕竟很多同学都是刚刚学,我也不会太难为大家,这一个字节一般都是在某个块开头或者结尾。

仔细去检查刚才的四个数据块,你就会发现节表的那个块,前面多了一个00:

图片

只要认真看了第10课的同学都能查得出来。

删除这一个字节,我们再次尝试双击这个exe程序,结果发现还是不行,还在报错:

图片

不过仔细看,报错类型不一样了,提示是找不到一个动态链接库。其实看到这个报错,就能确定一件事,我们的PE文件组装已经OK了,接下来要解决这个新问题了。

一个exe程序要运行,它通常会依赖一些其他的动态链接库,有系统库,比如kernel32.dll,也有程序自己依赖的其他库。不管哪种情况,这些依赖的动态库都记录在它的文件结构中,这就是PE文件的导入表。

上面这个报错,就是系统在创建进程时,在解析这个PE文件的导入表过程中,发现了它需要依赖一个license.dll文件,然后尝试去加载这个dll,然后又没有找到,所以报了这个错,进程创建失败。

我们可以用DEPENDS工具来看下这个程序的导入表:

图片

可以看到,这个程序引用了一个叫license.dll动态库中的GetLicense函数。

接下来是我们的逆向分析神器IDA登场的时候了。

把这个组装出来的程序放入IDA中来分析分析:

图片

IDA定位到了main函数,然后很容易看出main函数的代码逻辑,这里在调用前面的GetLicense函数,然后检查函数的返回值,检查通过就打印输出flag,失败则输出一个错误信息。

当然,为了避免一眼就直接拿到flag,我对flag进行了一个简单的编码,打印输出的时候,需要先解码还原。

如果大家看汇编有些吃力,还可以直接用IDA反编译成C语言,这看起来就容易得多了:

图片

根据我们刚才的分析,把上面那些函数和变量重新命个名,看起来更清晰(关于这些操作,在第13课IDA的操作使用中有详细的介绍):

图片

是不是好理解得多了?

看到这里,思路就出来了:

方法1、自己编写一个这个名字的DLL文件,然后导出一个名为GetLicense的函数,让程序成功运行起来,打印出flag。

方法2、直接暴力破解,修改关键汇编指令,让程序强行走入打印flag的分支。

方法3、最简单的,找到解码flag的函数,直接分析它是如何解的,自己写程序模拟解一遍就行了。

我们三种方法都试一下。

方法1:编写DLL

观察一下IDA分析视角下,程序中调用GetLicense函数的汇编指令,可以看得出来这个GetLicense函数只有一个整型参数,然后返回值是一个字符串指针。函数的声明就出来了:

char* GetLicense(int n);

第一种方法的关键,是要让GetLicense函数返回一个符合要求的字符串,也就是license,这样才能通过程序里的检查。

所以,我们先来分析一下,原程序中,是怎么在检查license的:

图片

检查函数返回的是一个bool,里面的检查逻辑也比较简单。

首先通过strlen获取license的长度,检查长度是不是16,如果不是,则返回false。

接着是一个for循环,我们先跳过,最后来看它。

最后的return是一组&&连接的检查,首先检查license的第4-7个字符的和(ASCII的和)减去第0-3个字符的和是不是为1。然后检查第8位是不是45。换成ASCII字符就是短横线-:

图片

最后回过头看看中间那个for循环。里面在调用另一个sub_401096函数,然后把license的每一个字符传进去。

进去看一下这个函数:

图片

这个函数里面又是一个for循环,在检查参数传进来的字符是不是在一个byte数组中,如果是就返回1,否则返回0。

这个数组里面是啥呢?进去看看:

图片

好了,总结一下,前面的检查license的函数里面在干四件事:

  1. 检查license字符串长度是不是16

  2. 检查license字符串中的每一个字符是不是在上面的byte数组中

  3. 检查license字符串第0-3个字符的和比第4-7位字符的和小1

  4. 检查license字符串的第8位字符是一个短横线

其实这个license检查规则,就是根据我的微信号:xuanyuan-zhifeng设计出来的,嘿嘿,有没有猜到?

但不是说license必须是这个,你的字符串只要符合上面的要求都可以。

好了,咱们来编写一个DLL:

extern "C"
__declspec(dllexport) 
char* GetLicense(int code)
{
 return "xuanyuan-zhifeng";
}

注意,需要使用extern "C",否则导出的函数名字会发生变化。

然后把DLL放置到前面我们拼接的exe目录下,这时候再来运行我们的程序:

图片

flag已经出来了:xuanyuan@biancheng.universe

方法2:暴力破解

因为我们还没有讲到调试器,所以这里先不动用动态分析,还是用IDA来搞定。

具体怎么暴力破解呢?首先想到的最简单的,就是这条JZ指令,只要能把它窜改成JNZ,即便license检查不通过,也会走入打印flag的分支。

图片

但在这之前,我们需要让程序能够先运行起来,不受那个DLL的依赖束缚。

前面讲过,程序的依赖项是在导入表中,那为今之计,就是找到导入表,把这个DLL干掉。

首先需要定位到导入表在PE文件中的哪个位置,这又需要用到第13课的知识了,先找到文件头的数据目录,数据目录的第二项是导入表的RVA。然后根据节表中每个节的RVA,定位到导入表在哪个节。然后再根据那个节的FOA,定位到文件中的位置。

然后通过导入表描述符的Name字段,就能知道这第一项就是license.dll。

图片

用导入表的后面第二项kernel32.dll的内容覆盖license.dll的导入表项。然后把原来kernel32.dll的项清零,就完成把license.dll从导入表中抹去的工作。

我们用Depends工具来看下现在这个程序的导入表:

图片

可以看到,现在只依赖kernel32.dll了,不需要license.dll了。

但你现在去执行还是有问题,因为咱们程序里面使用了外部引入的GetLicense函数啊,你现在都没license.dll了,这个函数没法调用了。

图片

那干脆一不做二不休,直接HOOK main函数的流程,让它一进来就直奔打印flag的分支。先不管他堆栈平衡的问题,反正咱们的目的是拿到flag,拿到以后哪管它崩不崩溃。(关于HOOK相关的知识,咱们在接下来的课程中会专门来讲解)

图片

所以,我们可以把main函数入口地方直接篡改成一条短跳指令,直接跳到flag解码打印的分支:

图片

然后来执行这个程序:

图片

flag又被我们召唤出来了!

方法3:直接解码flag

前面两种方法还是有一点麻烦,既然我们要的是flag,那么直接对flag下手,看它藏在哪里,然后想办法把它解出来就行了!

通过IDA反编译出来的C代码,可以看到这个函数就是在负责对flag进行解码。

图片

双击这个函数,然后反编译看一下解码函数的逻辑:

图片

经过对汇编指令的分析,这个函数实际上是没有返回值的,我们对其中的一些变量名称以及类型、函数的返回值类型进行人工修正,让它看起来更清晰:

图片

这下简单明了了吧,实际上就是在对flag字符串的每一位,与一个key进行异或运算(看吧,真的不难,毕竟是从零开始学逆向嘛)。

双击看一下这个key是个啥:

图片

就是一个字节的0xCC。

然后回去看一下解码前的那个flag:

图片

切换到十六进制窗口:

图片

都到这儿了,问题都好办了吧,自己写个简单的程序,按照解码函数那样把这段数据处理一遍就出来了:

void my_decode_flag() {

 char flag_bytes[] = {
  0xB4, 0xB9, 0xAD, 0xA2, 0xB5, 0xAD, 0xA2, 0x8C, 
  0xAE, 0xA5, 0xAD, 0xA2, 0xAF, 0xA4, 0xA9, 0xA2, 
  0xAB, 0xE2, 0xB9, 0xA2, 0xA5, 0xBA, 0xA9, 0xBE, 
  0xBF, 0xA9, 0x00
 };

 for (int i = 0; i < strlen(flag_bytes); i++) {
  flag_bytes[i] ^= 0xCC;
 }

 printf(flag_bytes);
 printf("\n");
}

来编译运行下试试:

图片

再一次成功拿到了flag!

源码下载地址: https://pan.quark.cn/s/a4b39357ea24 谷歌公司设计了一款无费用且具备开源特性的网络浏览器,名为Chrome,因其卓越的速度、稳定性和安全性而广受赞誉。该浏览器运用了前沿的Web渲染引擎Blink以及JavaScript引擎V8,旨在保障网页载入与脚本运行的卓越效能。为应对无网络环境下的Chrome安装需求,特别准备了离线安装包。此压缩文件内含32位与64位两种规格的Chrome浏览器离线安装方案,具体文件名分别为"chromedev_x64-v68.0.3423.2.exe"与"chromedev_x86-v68.0.3423.2.exe"。在文件命名中,"x64"标识64位版本,适用于64位操作系统平台,而"x86"则对应32位版本,适配32位操作系统。文件名中的"v68.0.3423.2"代表Chrome的一个特定版本号,各版本可能涵盖安全补丁、性能改进或新增功能。与32位Chrome相比,64位版本具备如下长处:能够处理更多内存容量,从而提升多任务作业能力;针对现代硬件的优化使其运行更为迅猛;64位版本更具备高级别的安全防护,能更周全地抵御恶意软件的侵袭。尽管如此,32位版本对于仍在使用32位操作系统的用户,或是在系统资源需求不高的场景下,依然适用。在部署Chrome浏览器时,用户需依据其个计算机的操作系统平台,挑选匹配的版本进行安装。通过双击相应的.exe文件,安装流程将自动启动,一般包含接受使用许可、确定安装路径及构建桌面快捷方式等环节。若在安装阶段遭遇难题,可参照提示信息或联系技术支援获取协助,同时该压缩文件发布者亦表明欢迎用户以留言形式反映问题。Chrome浏览器的主要特质涵盖:直观的用户界面设计...
内容概要:本文围绕直驱式永磁同步电机(PMSM)矢量控制系统的建模与仿真展开研究,基于Simulink平台构建了完整的控制系统仿真模型,涵盖了电机本体数学建模、三相/两相坐标变换(Clarke/Park变换)、磁场定向控制(FOC)、电流环与速度环双闭环PID控制策略、空间矢量脉宽调制(SVPWM)技术以及转速调节器设计等核心技术环节。通过仿真实验验证了该控制策略在动态响应速度、稳态运行精度及抗负载扰动能力方面的优良性能,充分体现了矢量控制在实现电机高性能调速中的优势,为永磁同步电机在工业驱动、新能源汽车和高端装备制造等领域的实际应用提供了可靠的理论依据与技术支撑。; 适合群:具备电机学、电力电子技术和自动控制原理基础知识的电气工程、自动化、机电一体化等相关专业的研究生、高校教师、科研员,以及从事电机驱动系统、新能源汽车电驱、工业自动化设备研发的工程技术员。; 使用场景及目标:①深入理解永磁同步电机矢量控制的基本原理与实现机制;②掌握在Simulink中搭建高精度电机控制系统仿真模型的方法与技巧;③为电机控制算法的设计、优化与参数整定提供高效的仿真验证平台;④服务于高校课程设计、毕业课题研究、科研项目前期验证及企业产品开发中的控制策略测试。; 阅读建议:建议结合经典电机控制教材进行对照学习,重点关注各功能模块间的信号流向、反馈机制与参数耦合关系,动手复现并调试仿真模型,通过改变PI参数、负载条件和给定转速等方式观察系统响应,从而深入掌握控制策略的内在逻辑与性能优化方法。
代码下载地址: https://pan.quark.cn/s/a4b39357ea24 Java学习路线(鱼皮)是一个全面且循序渐进的Java开发技能培养方案,该路线从基础入门直至高级应用,致力于协助学习者高效地掌握Java编程的全部核心内容。此学习路线的独特之处在于其新颖性、系统性、实践性、开放性以及社区回馈与持续迭代更新。其核心构成涵盖了预备阶段、Java入门知识、Java进阶技能、Java高级技术、Java框架应用以及Java项目实践等多个学习模块,每个模块均整合了相应的知识点、学习策略与资源指引。在预备阶段,学习者需配置在线编程环境、选择笔记工具、熟悉Markdown文档编写等基本技能,为编程学习奠定基础。在Java入门阶段,学习者应重点掌握Java编程的基础理论、开发环境配置、IDEA集成开发环境的使用、项目创建与执行调试、界面设置及插件配置等关键技能。在Java入门阶段,学习者还须深入理解Java基础语法、数据结构类型、程序流程控制、数组操作、面向对象编程、方法重载机制、封装原则、继承特性、多态表现、抽象类的概念、接口定义、枚举类型、常用类库、字符串处理、日期时间管理、集合框架、泛型编程、注解应用、异常处理机制、多线程技术、IO流操作、反射机制等核心知识点。在Java进阶阶段,学习者需要重点学习Java 8的更新特性、Stream API的应用、Lambda表达式的使用、新的日期时间处理API以及接口默认方法的实现。在Java高级阶段,学习者需要掌握Java框架的应用、Spring Boot框架的搭建、Spring Cloud微服务架构的实施等高级技术。在Java项目阶段,学习者需要学习Java项目开发的全过程操作,包括项目架构设计、项目编码实现、项...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值