【总结笔记】reverse
待办
- [x] FIndcrypd插件用法(已下载)
- [ ] LCG算法
- [x] 魔改XTEA
- [ ] 白盒AES:DFA攻击
- 缺陷数据后使用phoenixAES和aeskeyschedule库恢复主密钥
- [ ] C++异常处理
- [ ] TEB PEB SEH整理
- [ ] 原创-15pb调试器学习总结_except_handler4以及栈展开分析
工具
cheat engine
ida
插件
- ida mcp
- FIndcrypd插件
- https://www.52pojie.cn/thread-1992165-1-1.html
- 我移动后就可以用了(怀疑环境变量设置问题,先存档
- ida旧版导出数据插件
- https://github.com/Krietz7/IDA-DataExportPlus/blob/main/README.zh_CN.md
- py文件丢plungin文件夹里面
知识点总结归纳
- 以下文章默认参考学姐博客:https://seeker-fang.github.io/
- 其余参考会备注来源,侵删
算法
恩尼格玛机【待】
百度:键盘、转子和显示器
- 发送信息与接收信息的恩尼格玛密码机的设置必须相同:转子、其排列顺序,起始位置和接线板的连线
- 指示器
- 指示器步骤:转子的起始位置却是每发送一条信息就要更换的,因为如果一定数量的文件都按照相同的加密设置来加密的话,密码学家就会从中得到一些信息,并且有可能利用频率分析来破译这个密码
- 先按照密码本中的记录来设置机器,我们假设这时的转子位置为AOH,之后他会随意打三个字母,假设为EIN,接着为了保险起见,他会将这三个字母重新打一遍。这六个字母会被转换成其它六个字母,这里假设为XHTLOA。最后,操作员会将转子重新设置为EIN,即他一开始打的三个字母,之后输入密电原文
- 在接收方将信息解密时,他会使用相反的步骤。首先,他也会将转子按照密码本中的记录设置好,然后他就会打入密文中的头六个字母,即XHTLOA,如果发送方操作正确的话,显示板上就会显示EINEIN。这时接收方就会将转子设置为EIN,之后他就可将密电打入而得到原文了
工具
- 恩尼格玛机加解密网站cryptii:https://cryptii.com/pipes/enigma-machine
- github仓库:https://github.com/cryptii/cryptii
- 已本地汉化:
npm run dev启动
tea
- 参考
- 点击跳转脚本
tea
以加密解密速度快,实现简单著称
TEA算法使用64bit(8byte)的明文分组和128bit(16byte)的密钥,它使用Feistel分组加密框架,采用迭代的形式,推荐的迭代轮数是64轮,最少32轮。
简单的说就是,TEA加密解密是以原文以8字节(64位bit)为一组,密钥16字节(128位bit)为一组,(char为1字节,int为4字节,double为8字节)
该算法使用了一个DELTA常数作为倍数,它来源于黄金分割数与232的乘积,以保证每一轮加密都不相同。但δ的精确值似乎并不重要,这里TEA把它定义为δ=「(√5 - 1)231」,这个δ对应的数指就是0×9E3779B9,但有时该常数会以减法的形式出现,-0x61c88647=0×9E3779B9。
代码实现
解密只需要将加密逆过来即可
|
识别特征:
- 标准DELTA常数(魔数)
- 如果改了ida findcypto插件就识别不出来了
- 密钥为16字节(4个DWORD)
- 加密轮数为16/32/64轮
- 加密结构中存在左4右5移位以及异或运算(v(2^n)==v<<n*)
- 加密结构中存在轮加/减相同常数的语句
腾讯TEA算法
- TEA标准中使用的32轮加密,而腾讯只用了16轮。
xtea
TEA 算法被发现存在缺陷,作为回应,设计者提出了一个 TEA 的升级版本——XTEA。
XTEA是TEA的扩展,也称做TEAN,它使用与TEA相同的简单运算,同样是一个64位块的Feistel密码,使用128位密钥,建议64轮,但四个子密钥采取不正规的方式进行混合以阻止密钥表攻击。它增加了更多的密钥表,移位和异或操作等。
代码实现
|
识别特征:
- 加密结构中存在右移11位并&3的运算
- 最显眼的特征:原先TEA算法的v1 + sum变成了(sum + key[sum & 3])以及sum + key[(sum>>11) & 3]
- 可以魔改的点依然是Delta的值,轮数
[HNCTF 2022 WEEK2]TTTTTTTTTea
- shift+e导出dword格式或者ddd快捷键
exp:
|
xxtea
XXTEA,又称Corrected Block TEA,是XTEA的升级版 ,设计者是Roger Needham, David Wheeler。其实现过程要比前两种的算法略显复杂些,加密的明文不再是64bit(两个32位无符号整数),并且其加密轮数是由n,即待加密数据个数决定的。
特点:原字符串长度可以不是4的倍数了
|
XXTEA算法的解密同样只是对加密算法的数据处理顺序进行倒置,同时加法改减法(减法改加法)。
识别特征:
- 轮数一般为 rounds = 6 + 52 / n,比较明显,一般很少改动
总结
xxtea算法的例题很少见,感觉见的最多的是xtea及其变形。每一道题都有不同,不能死套着脚本,应该根据题的不同而进行修改,比如轮数,魔数,异或等的一些改变。
识别特征:
-
可能存在针对64bit以及128bit数字的操作(输入的msg和key)
-
存在先进行位移,然后异或的类似操作(
(z>>5^y<<2)这类混合变换) -
前面一个复杂的混合变换的结果可能会叠加到另一个值上,两者相互叠加(Feistel 结构)
-
获取密钥的时候,会使用某一个常量值作为下标(
key[(sum>>11) & 3]) -
会在算法开始定义一个delta,并且这个值不断的参与算法,但是从来不会受到输入的影响(delta数值,根据见过的题目中很少会直接使用0x9e3779b9)
借鉴于一位大佬写的博客https://www.kn0sky.com/?p=279
快速幂
- 快速幂引入
- 为了方便大家理解,我会用两种角度去讲解快速幂算法,二进制与指数折半,其实这两个的本质是一样的,只是分析的角度有些许不同,大家可以按照适合自己的角度去理解快速幂算法,当然两种角度都了解了,那更是最好了。
- 快速幂 二进制
- 核心思想:利用二进制来加速运算
- c 中 & 作为双目运算符/按位与:
a & b,只有a、b都是 1 结果才为 1
long long int quik_power(int base, int power) |
- 快速幂 指数折半
- 核心思想:每一次运算都把指数折半,底数变其平方
应用
- 快速幂一般不会独立使用,常常配合取余运算法则来使用。
- 需要了解一下关于取余的公式
(a + b) % p = (a % p + b % p) % p |
- 例题链接:点击跳转
安卓逆向
apk 文件结构
- APK 是 Android PacKage 的缩写,是 Android 源文件打包后的安装包。apk 其实就是一个 zip 格式的压缩包。想要知道 apk 包含了什么,可以修改后缀名为zip,然后用解压缩工具打开 apk 文件。 如果有代码混淆和加密,通过普通解压缩工具打开里面的文件或目录会看到各种乱码。
- 文件目录:
- assets 不经过 aapt 编译的资源文件,apk 中不用编译的资源(其他类型的文件)通常放在 /assets 目录和 /res/raw 目录下
- drawable 图片
- lib .so 文件 放到IDA64位里逆向
- META-INF 文件摘要,摘要加密和签名证书文件目录
- CERT.RSA 公钥和加密算法描述
- CERT.SF 加密文件,它是使用私钥对摘要明文加密后得到的 密文信息,只有使用私钥配对的公钥才能解密该文件
- MANIFEST.MF 程序清单文件,它包含包中所有文件的摘要明文
- res 资源文件目录,二进制格式
- layout 布局
- menu 菜单
- resources.arsc 经过 aapt 编译过的资源文件
- classes.dex 可执行文件
- AndroidManifest.xml 配置文件,做题时可以先查找 AndroidManifest.xml 文件里的 Activity 标签,一个Activity相当于一个页面,可以快速找到MainActivity并跳转。
- assets 文件夹:程序资源目录
- assets 文件夹用于保存需要保持原始文件的资源文件夹,开发过程中拖了什么到里面,打包完之后里面还是什么。
- 一般用于存放音频,网页(帮助页面之类的),字体等文件。
- 主要需要知道的点是,它与 res 文件夹的区分以及如何在应用中访问该文件夹的资源,如它可以有多级目录而 res 则只有两级。
- res 文件夹:资源文件夹
- 它里面存放的所有文件都会被映射到 R 文件中,生成对应的资源 ID,便于代码中通过 ID 直接访问。
- 其中的资源文件包括了动画(anim),图像(drwable),布局(layout),常量值(values),颜色值(colors),尺寸值(dimens),字符串(strings),自定义样式(styles)等。
- 在编译时会自动生成索引文件(R.java),在 Java 代码中用 R.xxx.yyy 来引用。
- 而 assets 目录下资源文件不会生成索引,在 Java 代码中需要使用 AssetManager 来访问。一般使用 Java 开发的 Android 工程使用的资源文件都会放在 res下。
- lib 文件夹:so 文件存放位置
- 该目录存放着应用需要的 native 库文件。
- 文件夹下有时会多一个层级,这是根据不同CPU 型号而划分的,如 ARM,ARM-v7a,x86等。
- META-INF:签名证书目录
- AndroidManifest.xml:全局配置文件
- 它包含了这个应用的很多配置信息,例如包名、版本号、所需权限、注册的服务等。可以根据这个文件在相当程度上了解这个应用的一些信息。
- 该文件是被编译为二进制的 XML 文件,可以通过一些工具(如 apktool、jadx、jeb、AndroidKiller 等)反编译后进行查看。也可以通过 android studio —> build —> Analyze Apk 来分析 apk
- android studio下载地址:https://developer.android.com/studio?hl=zh-cn
- 有点难用我就先不用这玩意了(指免费版本
- dex 文件:classes.dex 文件,是 Android 系统运行于 Dalvik Virtual Machine 上的可执行文件,也是Android 应用程序的核心所在。
- 项目工程中的 Java 源码通过 javac 生成 class 文件,再通过 dx 工具转换为 classes.dex,注意到我们这里有 classes2.dex 和 classes3.dex。这是方法数超过一个 dex 的上限,分 dex 的结果。
- resource.arsc 文件:字符串、资源索引文件。它记录了资源文件,资源文件位置(各个维度的路径)和资源 id 的映射关系。并且将所有的 string 都存放在了 string pool 中,节省了在查找资源时,字符串处理的开销。
- META-INF 文件夹:该目录的主要作用是用于保证 APK 的完整性以及安全性。该文件夹下,主要有三个文件。
- MANIFEST.MF:这个文件保存整个apk文件中 所有文件的文件名 + SHA-1后的编码值。这也就意味着,MANIFEST.MF 象征着 apk 包的完整性。
apk编译打包流程
-
ok根据这篇文章让我们看看官网
-
请各位看英文版本官网再浏览器或者其他翻译工具,不然下面全是错位看不了太搞笑了
-
实力不够先只了解和逆向有关的部分
1.编译器将您的源代码转换成 DEX 文件(Dalvik 可执行文件,其中包括在 Android 设备上运行的字节码),并将其他所有内容转换成编译后的资源。
2.打包器将 DEX 文件和编译后的资源组合成 APK 或 AAB(具体取决于所选的 build 目标)。
3.打包器使用调试或发布密钥库为 APK 或 AAB 签名。
4.在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,以减少其在设备上运行时所占用的内存
这是学习安卓逆向时要了解的知识。
安卓逆向的工具
1. jadx
jadx是一款开源的DEX到Java的反汇编工具。它支持apk、dex、jar、class、zip、aar等文件。比较方便的是可以搜索。jadx还支持对Smali代码的调试,单步,设置断点,修改寄存器的值,修改类属性等相关功能。
官方地址:https://github.com/skylot/jadx/
jadx的文本搜索功能比较高效,可以同时从类名,方法名,代码等选择搜索字段
在jadx中还有很多功能比如将反编译的文件保存为Gradle项目
右键点击方法名
- 跳到声明,这样就能找到该方法的位置。
- 查找用例,右键点击方法名,就可以找到调用该方法的位置了。
2. JEB工具
JEB是一款强大的安卓APK逆向分析工具。JEB安装好后,在它的安装目录下分别有jeb_linux.sh , jeb_macos.sh和jeb_wincon.bat这3个文件,它们是不同操作系统的启动程序,分别对应Linux,macOS和 Windows操作系统。运行jeb_wincon.bat文件启动JEB,首次启动时的速度可能稍慢。
常用功能:
在安卓逆向动态分析中动态调试有两种方法:
1.JEB调试(两种模式)
2.AndroidStudio+smalidea插件进行动态调试
3. GDA
GDA 不只是一款反编译器,同时也是一款轻便且功能强大的综合性逆向分析利器,不依赖 java 环境。支持 apk, dex, odex, oat, jar, class, aar文件的反编译,支持python及java脚本自动化分析。其包含多个由作者独立研究的高速分析引擎:反编译引擎、漏洞检测引擎、 恶意行为检测引擎、污点传播分析引擎、反混淆引擎、apk壳检测引擎等等
详细的应用操作可以学习这篇博客:https://zhuanlan.zhihu.com/p/28354064
安卓逆向解题思路
刚入门,学的不多,思路待补充
- 拿到apk文件,用安卓逆向工具打开(我一般用jadx),然后找到MainActivity
- 找到并打开AndroidManifest.xml,然后找到文件里的 Activity 标签,一个Activity相当于一个页面,可以快速找到MainActivity并跳转。
- 分析MainActivity的Java源代码,看如何对flag加密,进行静态分析。
- 如果有 native 标签说明函数是 C 语言编写的,主体在 so 文件,需要逆向so文件
相关例题
java
CTF游戏逆向入门
Unity游戏逆向
- Unity3D最大的一个特点是一次制作,多平台部署,而这一核心功能是靠 Mono 实现的。可以说一直以来 Mono 是 Unity3D 核心中的核心,是 Unity3D 跨平台的根本。这种形式一直持续到 2014 年年中,Unity3D 官方博客上发了一篇 “The future of scripting in unity ” 的文章,引出了 IL2CPP 的概念,这种相比 Mono 来说安全性更强的方式。
- Mono打包逆向
- Mono打包方式逆向比较简单,其核心代码都在 game/data/Managed/AssemblyCSarp.dll 这个 dll 文件中,使用 dnSpy 进行分析,几乎就是可以明文随便篡改。
例题:
- [BJDCTF2020]BJD hamburger competition
- 这个Mono还是比较好识别出来的, 游戏名_Data -> Managed -> AssemblyCSarp.dll
- 将AssemblyCSarp.dll放入dnSpy里面进行分析,找到ButtonSpawnFruit(),里面有md5和sha1,点进去进行交叉引用
可以推测,每一个菜都对应着一种操作,这与上菜的顺序有关,按照一定的顺序加菜,使得 str 的 Sha1与那一串相等,然后将 str 进行md5加密,但是这里要注意,要看看Sha1加密和md5是否都是标准的。
Sha1和 md5都是标准的,但是md5是取前20个字符,X表示的是大写16进制,x表示小写16进制
- ai
在 C# 中,Substring(0, 20) 会返回从索引 0 开始的20个字符(即从位置 0 到位置 19)。
在 Python 中,range(0, 20) 也会从0开始,但不会包括20,因此类似地会覆盖索引 0 到 19。
- 结果一样但意思不一样这个要注意
- [SCTF2019]Who is he
- ai:加密模式是 DES CBC(因为 DESCryptoServiceProvider 默认 CBC),密钥和IV相同
同样的方法,将AssemblyCSarp.dll放入dnSpy里面进行分析,里面密文和key都有
flag就是 EncryptData 通过 Decrypt 来解密得到的。
- 全局常量
这个解密过程是先进行base64解密,然后再进行DES-CBC模式解密,其中找不到iv是因为key=iv=”1234”。
- 如果不指定模式,就是CBC
- 除非代码中显式设置了 Mode 属性,例如:
descryptoServiceProvider.Mode = CipherMode.ECB; |
需要注意的是:dnSpy里的字符串是用UTF-16编码存储的,也就是宽字节(双字节)
比如:字符串”1234”
它在内存里面其实是:
每个字符后面都跟一个00h,也就是说每个字符占两个字节。
写出脚本
本以为美美结束了,但是这个是错的flag。接下来需要使用 Cheat Engine
出题人应该隐藏了真实的密文和key,这些都会放在dll里面,Managed里面的dll是所有的代码。
所以在Managed文件夹里面按照时间排序,看最近的日期就能知道哪些是可疑的。
然后打开CE和那个游戏,在CE里选择这个游戏进程,会看到出现了Mono,点击它选择分析Mono。就会看到这些dll,找到刚才可疑的3个dll与AssemblyCSarp.dll对比。
- Mono → Dissect mono 打开 “Mono Dissector” 窗口
两者对比会发现,真正的密文和key都在UnityEngine.UmbraModule里面。我们可以换成 Mono ->.Net Info进行分析,在这里面找到UnityEngine.UmbraModule程序集,里面有个.Main类,打开看看。
知道key=iv=”test”,是静态的,可以直接读取值,但是密文是动态的
找到存放密文的变量,选中后点击Lookup 按钮,它就会自动查找所有可能的内存指针然后读取里面的内容。
第二种方法
两个地址有关分别定位内存中的位置,可以看到这两个真假密文和key
from Crypto.Cipher import DES |
第三种方法:使用uniref框架解出真的密文
uniref框架:https://github.com/in1nit1t/uniref
uniref 是一个辅助分析 Unity 应用的框架。它可以帮助我们取获取 Unity 应用中的类、方法、成员变量等的反射信息,也可以实时地查看和操作它们。
from uniref import WinUniRef |
- 前提是知道ddl和类名,可以作为备选方案
IL2CPP 打包逆向
IL2CPP 将游戏 C# 代码转换为 C++ 代码,然后编译为各平台 Native 代码。现在市面上的很多游戏基本上都是用Il2cpp的方式打包的。
global-metadata.dat 文件里面记录了所有的 C# 中的类名、方法名、属性名、字符串等地址信息。目前对于il2cpp逆向都以Android中的.so文件居多。
IL2CPP 打包的应用在逆向前会多一步操作,即使用项目 Il2CppDumper 和应用程序目录中的 global-metadata.dat 和 GameAssembly.dll 来获得 dll 的符号信息。之后再通过 IDA 加载 GameAssembly.dll 及符号信息来进行逆向分析(常规 C 语言逆向)。
[MRCTF2021]EzGame
- github上找到了MRCTF2021的总附件链接:https://pan.baidu.com/s/1AizR9m_HV_0E3zJRiOVkAQ#list/path=%2F(密码ming)
- 还有首页写了死太多次会失败
打开后在Data文件夹里看到 il2cpp_data ,这就是il2cpp打包的unity游戏。
需要的工具:Il2CppDumper 下载使用:https://github.com/Perfare/Il2CppDumper
命令
Il2CppDumper.exe GameAssembly.dll的路径(第一个参数) global-metadata.dat的路径(第二个参数) 输出的路径(第三个参数) |
- 输出目录必须手动创建好
dump.cs:这个文件会把 C# 的 dll 代码的类、方法、字段列出来
IL2cpp.h:生成的 cpp 头文件,从头文件里可以看到相关的数据结构
script.json:以 json 格式显示类的方法信息
stringliteral.json:以 json 的格式显示所有字符串信息
DummyDll:进入该目录,可以看到很多dll,其中就有 Assembly-CSharp.dll 和我们刚刚的 dump.cs 内容是一致的
接下来使用IDA来加载 GameAssembly.dll ,但是这个题加了个Themida的壳,很难脱,这个壳是一款商业级的壳,特点是保护强度高,经常被用来保护游戏、外挂、商业软件。
其实到这里这个常规的方法就行不通了,下面是拿这道题来演示一下,常规的流程是什么。
选择 File -> Script file 去应用 ida_with_struct_py3.py文件。
然后选择该文件
再导入头文件
然后就来等待它分析就好了。那这种方法行不通,换CE来看看。
C# 的所有源代码文件,默认编码为 UTF-8,注意,是源代码文件,而不是 C# 中的 string。
C# 中的所有 string,默认编码均为 Unicode (UTF-16)。
C# 产生的 ASP.NET 源代码,如 ASPX/CS,在浏览器响应回去客户端之后,编码默认为 UTF-8。可以通过 ContentType 请求头信息更改默认编码。比如:ContentType: application/json, charset=utf-8。
原文链接:https://blog.csdn.net/u011127019/article/details/99629697
https://majikoo1028.github.io/2021/04/20/MRCTF-EzGame/
官方WP的做法是通过反编译得到得信息结合il2Cpp的API写dll注入脚本来解的
- 欸后面再学注入(落荒而逃
与上一题相同的方法,看到GetFlag很容易猜想到主要逻辑就在这。
其他条件都可以改成True,但是tokenGet如果直接改成105是不行的,看到下面的EatTokenUpdateKey方法,(这里也是看过别人的wp才明白的)就应该猜到了每调用一次这个方法,上面的token会加一,这个方法里面应该还有最后解密的key也会随着调用的次数而改变。这样如果直接改上面的值,key还是原来的错误key,flag也就没有被解密出来。我们可以直接右键选择调用,但是这样要一直点105下。于是我们用uniref框架来写。
使用时要和CE一样,确保游戏还开着。
from uniref import WinUniRef |
- 其实也可以手动调用(其实就是不会编程(摆手
XYCTF2024-baby unity
- 这是真找不到附件了,纯看学姐解析了:
这个游戏一打开仅仅就是一个flag验证的程序,逻辑应该比较简单。
使用Il2CppDumper提取文件失败了 大概率是 GameAssembly.dll 有壳
使用查壳工具进行查壳,这个是UPX壳
直接使用upx -d 进行脱壳,成功脱壳!
然后再次进行尝试,脱过壳后就成功了
使用 IDA 加载 GameAssembly.dll 及符号信息,方法上一题演示了。此刻需要耐心等待
那先使用dnSpy来看看output文件夹里的Assembly-CSharp.dll,观察到CheckkkkkkkkkkFlag函数,等IDA分析好后,直接去搜索,得到加密逻辑,先base64加密,再异或
直接写脚本
Android游戏逆向
无论是Android或者是Windows都能够使用unity引擎进行开发。安卓unity游戏的核心逻辑一般位于 assets\bin\Data\Managed\Assembly-CSharp.dll
BUU-[MRCTF2020]PixelShooter
使用jeb打开这个apk文件,直接在找到assets\bin\Data\Managed\Assembly-CSharp.dll这个位置打开。
搜索{然后硬看。。
普通C#程序
使用 .NET 框架提供的编译器可以直接将源程序编译为 .exe 或 .dll 文件,但此时编译出来的程序代码并不是 CPU 能直接执行的机器代码,而是一种中间语言 IL(Intermediate Language)的代码。
这个时候再放入IDA里面进行分析,就不太行了。放入DIE里面查看,出现如下
这可以使用新的工具dnSpy,它是C#逆向的好帮手。
dnSpy是一个.NET调试器和反编译器,可以在无源码的情况下,进行代码调试和修改。遇到此类题目,我们可以把它放到dnSpy里面进行分析。
[ISC 2016]Classical CrackMe
运行一下看看
把它放到dnSpy里面进行分析,但是发现本该显示form1的字样的地方成了一串字符,这就加壳混淆了
这个时候需要使用de4dot来帮助,它是是一个很强的.Net程序脱壳,反混淆工具,支持对于以下工具混淆过的代码的清理:如Xenocode、.NET Reactor、MaxtoCode、Eazfuscator.NET、Agile.NET、Phoenix Protector、MancoObfuscator 、CodeWall、NetZ .NET Packer 、Rpx .NET Packer、Mpress .NET Packer、ExePack.NET Packer、Sixxpack .NET Packer、Rummage Obfuscator、Obfusasm Obfuscator、Confuser1.7、Agile.NET、Babel.NET、CodeFort、CodeVeil、CodeWall、CryptoObfuscator、DeepSea
Obfuscator、Dotfuscator、 Goliath.NET、ILProtector、MPRESS、Rummage、SmartAssembly、Skater.NET、Spices.Net 等。
使用方法:直接 de4dot exe所在的位置 就好,会生成一个xxx-cleaned.exe
然后把新的exe再放入dnSpy里面进行分析
[FlareOn5]Ultimate Minesweeper
先查壳,无壳32位,是net写的。我们试着运行一下程序,发现是个扫雷游戏,而且这个只要扫到雷就会弹出失败的弹窗然后退出游戏。
把它放到dnSpy里面进行分析,能找到MainForm部分,这里就是重要部分
里面有GetKey方法,这应该就是flag的生成逻辑
分析后发现,这从输入 revealedCells生成一个动态伪随机数种子,用于扰乱一个固定的字节数组 array2,最后返回扰乱后的结果字符串,想要逆向过去还是很难的。往上翻看,发现了扫雷的校验逻辑
上面红框是扫到雷后,弹出失败的弹窗并退出游戏,下面的是将所有非雷块都扫了而且没扫到雷就能通过这个游戏,会调用GetKey方法获取flag弹出胜利弹窗。
第一种方法: 我们只需要把上面扫到雷的失败弹窗部分的代码(也就是红框框住的)都删掉,然后保存,就能一直扫雷不会弹出失败弹窗,也不会退出了。然后记住正确的位置,再在原来的程序上点击就能顺利通关了。
第二种方法:
TotalUnrevealedEmptySquares是代表着剩下还没被揭开的”非雷”方块数量
点进去,然后下翻找到初始化棋盘部分,如下所示
MinesPresent → 存储“是否有雷”。 MinesVisible → 存储“是否被翻开”。 MinesFlagged → 存储“是否被插旗”。
选中这部分后,右键选择编辑方法
using System; |
true代表可视,false代表不可视。初始化全部都是false。这个是遍历所有方块都改成true。然后点击编译就🆗了。
最后保存模块,打开就透明可视了。
Python逆向-PYC
- 识别pyc文件
- 法一:
| Python 版本 | 文件头(十六进制) |
| --------- | ------------- |
| 2.7 |03 F3 0D 0A|
| 3.0 |3B 0C 0D 0A|
| 3.1 |4F 0C 0D 0A|
| 3.2 |6C 0C 0D 0A|
| 3.3 |9E 0C 0D 0A|
| 3.4 |EE 0C 0D 0A|
| 3.5 |17 0D 0D 0A|
| 3.6 |33 0D 0D 0A|
| 3.7 |42 0D 0D 0A|
| 3.8 |55 0D 0D 0A|
| 3.9 |61 0D 0D 0A|
| 3.10 |6F 0D 0D 0A|
| 3.11 |A7 0D 0D 0A|
| 3.12 |CB 0D 0D 0A| - 法二:魔数识别脚本
pyc转py文件:
第二种,先下载pyinstxtractor.py工具(工具箱里面有嘿嘿嘿),可以从网上搜索并自行下载,安装uncompyle库,使用pip命令安装。在cmd中输入命令。
命令:pip install uncompyle6
将所得的pyc文件与pyinstxtractor.py文件放在一起,打开cmd,输入uncompyle6 文件名.pyc > 文件名.py
decompyle3 -o output.py input.pyc- 但是output.py必须存在再运行
pycdc -o output.py main.pyc- 终极大法:https://pylingual.io/
exe文件转pyc文件再转py文件
die看到语言是Python,打包工具是Pylnstaller,就是考察exe转pyc,转py
将这个exe文件放在pyinstxtractor.py工具一个目录下,然后打开终端,输入命令
python pyinstxtractor.py 文件名.exe再回车一下,
会看到一个新的与exe文件同名的文件夹,点进去查看会看到struct.pyc文件,胆大心细一下,找到你觉得可疑的文件,这个文件夹里明显是1.pyc文件。struct.pyc文件是标准正确的的文件头,1.pyc和struct.pyc需要放到010里面查看,1.pyc是否有文件头,文件头是否与标准的文件头相同。改成一致的后保存。
Z3求解器
- 参考:
https://ctf-wiki.org/reverse/tools/constraint/z3/
https://microsoft.github.io/z3guide/programming/Z3 Python - Readonly/Introduction/
安装
pip install z3-solver |
变量表示
一阶命题逻辑公式由项(变量或常量)与扩展布尔结构组成,在 z3 当中我们可以通过如下方式创建变量实例:
- 整型
import z3 |
- 实数类型
y = z3.Real(name = 'y') |
- 位向量
z = z3.BitVec(name = 'z', bv = 32) |
- 布尔类型
p = z3.Bool(name = 'p') |
整型与实数类型变量之间可以互相进行转换:
z3.ToReal(x) |
常量表示
除了 Python 原有的常量数据类型外,我们也可以使用 z3 自带的常量类型参与运算:
z3.IntVal(val = 114514) # integer |
简答求解
x = Int('x') |
复杂求解
创建求解器
s = z3.Solver() |
- 或者实验性质求解器:
s = Solver() |
添加约束
s.add(x * 5 == 10) |
- 对于布尔类型的式子而言,我们可以使用 z3 内置的 And()、Or()、Not()、Implies()蕴含 、if、==(等价)等方法进行布尔逻辑运算:
- 诶呀太有意思了前面逻辑学的居然还能用上~~
s.add(z3.Implies(p, q)) |
- If(布尔条件, 条件为真时的值, 条件为假时的值)
- simplify()简化约束
x = Int('x') |
from z3 import * |
求解
- 使用
check()方法检查约束是否是可满足的
z3.sat:约束可以被满足
z3.unsat:约束无法被满足
s.check() |
若约束可以被满足,则我们可以通过 model() 方法获取到一组解:
s.model() |
函数
遍历表达式的函数
x = Int('x') |
s = Solver() |
输出格式调整函数
from z3 import * |
注意点
误差问题
- z3中最好不要使用python原生浮点会引入二进制近似误差
print (1/3) # 0.3333333333333333 |
- 通过加限制让z3代码可以运行
- 加位宽要注意可能溢出导致答案不一样,这种是没有提示的,慎用
a1 = [BitVec(f'a{i}', 16) for i in range(20)] # 通过限制位宽使代码可运行 |
- 后一种是加上字符约束,固定在可见字符范围内,相对来说可靠
upx脱壳
“壳”可以分为两类:压缩壳和加密壳。压缩壳的目的是压缩原程序的体积,很多恶意代码都采用压缩壳,以便于传输。加密壳也称保护壳,是指通过对原程序加密来防止文件被破解。一般而言,加密壳会增加文件的体积。常见的压缩壳包括UPX、ASPack、Nspack(北斗压缩壳)等,常见的加密壳有VMProtect、ASProtect等。所有的压缩壳都能被脱壳,但是加密壳脱壳的难度较大,因此比赛时出现压缩壳问题的概率较大。
UPX(the Ultimate Packer for eXecutables,官网地址为https://upx.github.io/)
- 工具箱里也有,再次感谢学长学姐整理的工具箱嘿嘿嘿
upx
upx -d (需要脱壳的文件地址) |
upx魔改壳
小写的upx,改成大写的,就将
75 70 78都改成55 50 58,再保存一下就好了。
- 加壳可使用如下指令
>upx -1 要加壳的程序名.exe #更快
>upx -9 要加壳的程序名.exe #更好的压缩率
- 魔改UPX
- 修改UPX头
- 修改入口点
例题:NCTF2023的中文编程2
当我们查壳时发现UPX的版本号被抹去,那就是文件头被魔改了。这时候手动脱壳依然是有效的,不过有时候太麻烦,所以我们也可以手动把UPX头改回来。
用010Editor打开文件修改回UPX0、UPX1、UPX(注意在左边改,在右边改会把后面的.覆盖掉)
UPX手动脱壳
参考:
- https://hanafudastore.github.io/2025/06/08/UPX/index.html
- https://hello-ctf.com/hc-reverse/从零开始的壳/#_4
分类:
- 32 位:OllyDbg 脱壳(较简单,可参考网上大量教程)
- 64 位:x64dbg 示例为 [SWPUCTF 2022 新生赛]upx 中的附件(64 位,UPX3.96 壳),可以用工具脱壳。下面尝试手动脱壳。
32位
- 使用附件:
通过网盘分享的文件:3.手工脱壳.exe |
寻找OEP
ESP 定律法是脱壳的利器, 是应用频率最高的脱壳方法之一,它的原理在于利用程序中堆栈平衡来快速找到 OEP。
由于在程序自解密或者自解压过程中,不少壳会先将当前寄存器状态压栈,如使用 pushad,在解压结束后会将之前的寄存器值出栈,如使用 popad。因此在寄存器出栈时,往往程序代码被恢复,此时硬件断点触发。然后在程序当前位置,只需要少许单步操作,就很容易到达正确的 OEP 位置。
- 程序刚载入开始 pushad/pushfd
- 将寄存器压栈后就在 ESP/RSP 寄存器所在地址处设硬件访问断点
- 运行程序,触发断点
找到 pushad 。pushad = 一次性保存所有通用寄存器到栈。有的时候不是pushad,而是一连串的 push 将通用寄存器入栈也是一样的。
- 选项只保留入口断点重新运行找到
- 步进后栈顶断电
然后在这个地址处设硬件断点,硬件断点,访问 -> 4字节
然后直接运行,看到程序跳到 popad,显而易见的程序进行弹栈操作后又进行了jmp 跳转,跳到了比较远的位置,进行大跳转的jump地址就是OEP的所在地址。在这里打上断点直接跟进去,里面就是程序的OEP。
Dump 及 IAT 修复
找到 OEP 后,通过 x64dbg 自带的 Scylla 插件即可完成 Dump 及 IAT 修复。在弹出的窗口中点击 Dump,并设置保存路径。
dump 的默认文件名为 原文件名_dump.exe,之后点击 IAT Autosearch,会弹出一个警告:
选择 是 后,会显示找到的 IAT 地址及大小:
此时再点击 Get Imports,插件会列出找到的所有导入信息,如果有红叉需要手动删除:
接着点击 Fix Dump,选择刚才 dump 的 exe,插件会在同级目录下生成一个新程序 原文件名_dump_SCY.exe。
去重定位
但此时该 exe 可能仍旧不能运行,我们需要修改其 PE 结构中的两个字段值:
File Header 的 Charateristics
Optional Header 的 DllCharateristics
我们可以使用 CFFExplorerVII (小辣椒) 等工具来进行编辑。
首先修改 File Header 的 Charateristics,勾选 Relocation info stripped from file。
然后修改 Optional Header 的 DllCharateristics,取消勾选 DLL can move。
最后保存就好了,这样UPX壳就去除成功了,使用IDA打开,也能找到main函数
64位
- 这里内容根据https://hello-ctf.com/hc-reverse/从零开始的壳/#_4进行学习和记录
XP后的系统都有ASLR(地址空间随机化),导致dump后程序运行出错,因此我们首先用CFF Explorer修改该文件的Nt Header,禁用ASLR
- 没看懂原文章这里要进行什么操作,问了ai让我取消勾选option header-dllcharacteristics-dll can move
在Nt Header下的Optional Header里修改Characteristics,勾选Relocation info srtipped from file。关闭后记得保存。
- 这里原文如上,肯定写错了,在file header里面改
用x64dbg打开文件,进入系统断点(push rbx)。
按F9到达该断点(这里要按两次,上面还有个别的什么断点)
F7走完push压栈部分
观察到RSP的变化,在其上右键“在内存窗口中转到”
在右下角该地址右键,设置硬件断点
F9运行到断点处,看到下面的jmp大跳应该是入口,设置断点,F9跳过去
F7步入程序
往下翻可以看到提示字符串
这就正式进入了原程序,可以进行dump了
Scylla插件,和32位一样修复
- “IAT Autosearch”→“Get Imports”→在“Imports”中删除掉带有红色叉叉的→“Dump”→“Fix Dump”选中之前的Dump文件→修复成功。
拖入IDA中(由于某种神秘力量?)没有main函数,只能通过字符串来找函数,可以看到大致的代码
如果想把字符串展开看,可以(退到IDA View-A中)修改edit-Segment-edit segment,取消write勾选
- 好玩嘻嘻
刷题
[SWPUCTF 2021 新生赛]re1
- 蛮好理解的
- {34sy_r3v3rs3}
- {easy_reverse}
[SWPUCTF 2021 新生赛]re2
- 逻辑与 && :and
- ||:or
- ord()/chr()
- exp1:注意加密判断是对密文判断的,所以反过来解密判断也是对处理后对象判断!!!
flag = '' |
- exp2
alpha='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890{}-_' |
[SWPUCTF 2021 新生赛]简简单单的逻辑
'''flag = 'xxxxxxxxxxxxxxxxxx' |
[LitCTF 2023]世界上最棒的程序员
[NSSCTF 2022 Spring Recruit]easy C
a = "d`vxbQd" |
- 简单题
[SWPUCTF 2022 新生赛]base64
[SWPUCTF 2022 新生赛]base64-2
[SWPUCTF 2021 新生赛]简简单单的解密
- 题目:
import base64,urllib.parse |
import base64,urllib.parse |
[WUSTCTF 2020]level2
- 纯UPX脱壳题
[SWPUCTF 2022 新生赛]贪吃蛇
来玩贪吃蛇吧( 提示 没必要死磕这道题
- jz命题jnz
- NSSCTF{YourAreTheMasterOfGame!}
[青海民族大学 2025 新生赛]你的flag被加密啦!
在一场激烈的 CTF 竞赛中,你遭遇了一个棘手的加密谜题。现已知存在一个加密函数,它使用了特定的规则对一段关键信息进行了加密。你拿到了用于加密的 Python 代码以及加密后的字符串,请揭开这个神秘 flag 的真面目把。(加密后的字符串为iqcj{qafmgh89991})
- 题目:
def custom_encrypt(plaintext): |
- 脚本
def custom_decrypt(ciphertext): |
这里藏着真正的iodq,你得自己解出来iqcj{qafmgh89991} |
[青海民族大学 2025 新生赛]简单的逆向
# 打乱顺序的flag |
- 给的附件有错但能猜出来答案
d = {} |
- partition/repatition/sorted/join用法
[Newstar ctf 2024]ezAndroidStudy
既然知道了flag被分成了5份,可以用搜索直接搜flag1
flag{Y0u |
再搜索flag2并转到
_@r4 |
- 根据提示在/res/raw下找到flag4.txt
_andr01d |
- 搜索得到flag3
_900d |
搜索flag5可以看到:有 native 标签,说明函数是 C 语言编写的,主体在 so 文件,需要逆向so文件。并且flag2处也提示了要逆向so文件
在lib里找到x86_64点开找到so文件右键导出,然后放到IDA64位里查看
_r4V4rs4r} |
- 最后合到一起
flag{Y0u_@r4_900d_andr01d_r4V4rs4r}
[Newstar ctf 2024]ezencrypt
- 也是西电复现
走一下流程,找到MainActivity并跳转,主要看onClick里面的代码,可以看到Enc enc = new Enc(tx),加密逻辑在 Enc中
|
- 跟进check函数
package work.pangbai.ezencrypt; |
- 这个类有一个native方法doEncCheck,静态块里加载了libezencrypt.so,所以这个库应该包含doEncCheck的实现
- 分析:Enc(String v),里面调用了encrypt方法,参数是v和从MainActivity.title生成的密钥。然后加密后的结果保存在enc变量里。check方法调用doEncCheck,传入这个enc字符串,返回结果。
- 使用
MainActivity.title作为密钥来源,转换为AES密钥。- 采用
AES/ECB/PKCS5Padding模式加密输入字符串,结果经Base64编码后存储。check()调用本地方法doEncCheck()验证加密结果。
- 逆向so文件放到IDA64里面运行
- ai
- 要确定一个 native 方法的具体实现位置,通常有三种方法:
命名规范推断(最常用)、动态注册搜索以及工具辅助定位。 - 静态映射:JNI 命名规范在 Android 中,最常见的关联方式是遵循 JNI(Java Native Interface)的标准命名规则。当你调用 doEncCheck 时,系统会自动在已加载的 .so 库中寻找符合以下格式的函数名:
Java_包名_类名_方法名 - 对于你的代码 work.pangbai.ezencrypt.Enc.doEncCheck:
- 包名:work.pangbai.ezencrypt (点号变成下划线 work_pangbai_ezencrypt)
- 类名:Enc
- 方法名:doEncCheck
- 最终结果:
- 在 libezencrypt.so 中,一定会有一个 C/C++ 函数名叫:Java_work_pangbai_ezencrypt_Enc_doEncCheck
- 要确定一个 native 方法的具体实现位置,通常有三种方法:
__int64 __fastcall Java_work_pangbai_ezencrypt_Enc_doEncCheck(__int64 a1, __int64 a2, __int64 a3) |
- 跟进enc()
- 先异或加密,再典型的RC4加密,密钥是xork = meow
- 将加密后的数据进行RC4解密,得出来的数据与xork进行异或。
- c现在不是很会,先当脚本记下来好了
|
- flag{0hh_U_kn0w_7h15_5ki11}
- 累死了还是java代码一点了解没有造成的后面得学
[SUCTF 2018 招新赛]dead_z3r0
- pyc-python3.6-33 0D 0D 0A
- 反而网站reverse不行~~有意思
# uncompyle6 version 3.9.3 |
import base64 |
- 提示stegosaurus剑龙
python -m stegosaurus dead_z3r0.pyc -x |
专门找z3题目刷一下
[Reisen]NSSCTF2025入门题目-Z3
printf("please input flag:"); |
from z3 import * |
[*CTF 2023]easy_z3
- 用心良苦其实这题是isctf的嘻嘻
from z3 import * |
[NSSRound#X Basic]ez_z3
- 还有其他upx魔改,暂时还没遇到:https://blog.csdn.net/qq_73505302/article/details/130580670
- 主函数:
int __fastcall main_0(int argc, const char **argv, const char **envp) |
- 后一个是数组的[0]
- 就是0x1207
- 但是好像不止对比第一个???不懂
.data:0000000140025050 dword_140025050 dd 1207h, 4CA0h, 4F21h, 39h, 1A523h, 23Ah, 926h, 4CA7h |
dword_140025050=[0x1207, 0x4CA0, 0x4F21, 0x39, 0x1A523, 0x23A, 0x926, 0x4CA7,0x6560, 0x36, 0x1A99B, 0x4CA8, 0x1BBE0, 0x3705, 0x926, 0x77D3,0x9A98, 0x657B, 0x18, 0x0B11, 0,0,0,0,0,0,0,0] |
- 前一个在这
- ok现在按主函数顺序再顺一下
- 第一步:索要flag和z3
- 第二步:
- 跟进发现:flag和某数组作为a1和a2输入此函数
- 某数组:
.data:0000000140025000 dword_140025000 dd 3 dup(7), 9, 5, 6, 3 dup(7), 9, 2 dup(7), 5, 3 dup(7) |
shuzu=[7]*3+[9,5,6]+[7]*3+[9]+[7]*2+[5]+[7]*3+[5,7,9,7] |
- ai
一个非常经典的**快速幂算法(Fast Exponentiation)**的变体,但它有一个非常特殊的“陷阱”,导致它的行为和标准的数学幂运算不一样。
我们把它当成一个数学题来做,输入是两个数:
- 底数 a1 :就是你的 Flag 字符(ASCII 码)。
- 指数 a2 :就是那个固定数组里的数字(比如 7, 5, 9…)。
- ok咱先学下快速幂吧,点击跳转笔记
__int64 __fastcall sub_140014CC0(int a1, __int64 a2, __int64 a3) |
- 跟进sub_14001103C,把z3输入的和约束条件对比
__int64 __fastcall sub_140014E90(char *a1, __int64 a2, __int64 a3) |
- 这里上z3代码得到&unk_140025588即正确z3值
from z3 import * |
- 这里z3要注意(其实也不知道具体原因反正要加限制,都试下好了以后
- 一种是位宽16
- 一种是加上字符约束,固定在可见字符范围内
- 仔细分析下这步异或
__int64 __fastcall sub_140014830(__int64 a1, __int64 a2, __int64 a3) |
- ok试着写下脚本吧
a1=[104, 97, 104, 97, 104, 97, 116, 104, 105, 115, 105, 115, 102, 97, 99, 107, 102, 108, 97, 103] |





























































































