WMCTF 2025 re wp
WMCTF 2025 re wp
catfriend
校验逻辑
1 | #!/usr/bin/env python3 |
WMCTF{5a3e8f2b-1c7d-4a6f-b89e-0d3c2f1a4b5c}
appfriend
jadx打开
逆向native层
很明显的sm4
1 | #!/usr/bin/env python3 |
Want2BecomeMagicalGirl[复现]
WMCTF2025 Want2BecomeMagicalGirl - Britney
我原以为逻辑是这样的(通过blutter看到的):
check 里:
- 初始化
AesCrypt,从native_add获取一些 native 值(getSym、getKey),组装 Key/IV; - 根据文本长度补齐到 16 字节块(自定义 padding),UTF-8 编码;
- 设定 AES 的 keys & mode,执行
aesEncrypt; - 把密文
Uint8List转十六进制字符串(uint8ListToHex),再send(); - 拿到
send()的返回字符串后(通过.then(...)),再setState:如果返回值等于一段硬编码字符串("8sAFX45zT7u..."),显示成功文案“You have become a magical girl !!”,否则显示“This spell has no magic power”。
1 | 8sAFX45zT7uc0vSUyFNNly1h/d5zTt89tV3kcVr5P5n7lRKPyYtxg31zYNB2lPV0c5nf/x2/IK94XV9Ufs9XfaDG5IXxMlZy+Z2nE+ZZRFBSpMoKzQXfUq2TSjJJfQxV |
From Base64, AES Decrypt - CyberChef
发现解出来是乱码
出题人给了提示init_array,猜到是做了修改,但是安卓还是不太熟
前端有部分xxtea
密文前端,还有加密逻辑都有了,可以得知是一个aes–>xxtea(中间可能还有rc4生成key)–>base64返回与那一串密文校验
剩下的就是其他操作了,看了wp
WMCTF2025 Want2BecomeMagicalGirl - Britney
复现下
1 | __int64 __fastcall magical_girl_aes_crypt_null_safe__Aes::_mixColumns_28d8b0(__int64 a1, __int64 a2) |
发现一大串疑似sbox的东西
其中这些函数
1 | __int64 magical_girl_EditView_MyEditTextState::check_28bea8() |
最重要
包括setkey和mode
加密的数据通过 aesEncrypt_28c6a8 和其他相关函数进行处理,这表明该函数处理的是加密或解密任务。
显然这里肯定会传一个类似实例的东西,借助blutter自带的frida脚本
给了一个blutter_frida.js hook脚本
hook试试
1 | function onLibappLoaded() { |
或者hook这个
getArg(this.context, 0) 更改第二个参数控制传参打印位置
1 | function onLibappLoaded() { |
hook得到
1 | AesCrypt@6f00854529 = { |
aesEncrypt_28c6a8都第一个参数是输入的flag
16字节的那个有点像key
1 | frida -U -f work.pangbai.magic.magical_girl -l blutter_frida.js |
反正现在S盒子S盒逆key ecb都有了可以写代码解密了
sbox 我们将这些值均除以2
flutter的整数貌似都是*2存储的
发现还改了一下addroundkey和mixClou
Flutter 函数调用约定: 参数从右往左入栈,最后入栈对象的this指针
1 | typedef struct |
下一步就是java层的xxtea
标准的xxtea,不过出不来T.T,出题人提示init_proc,猜测可能是被改掉了 ,而且字符串均被加密了
在libnative_add.so里
init_proc() 在 libnative_add.so 里动态解密/打补丁(把“shellcode”写到 mmap 出来的内存,再改写目标函数的前两条指令为 trampoline 跳转)。因此字符串/代码/数据的解密过程发生在 native 层内存复制之前或期间。抓取被解密后的字符串,最可靠的方式是 在解密后 memcpy 把解密内容写入可执行内存时把那块内容 dump 出来,或者在 sub_19F78(看起来像解密)返回时直接 dump 它返回的内存。
中间参考了下出题人的解释:
“原生库”是指用 C 或 C++ 编写并编译为
.so文件(Shared Object,共享库) 的代码。它不是 Java 层代码,而是直接运行在 Linux 层 的二进制代码。
在 Android 应用中,如果你用 JNI(Java Native Interface) 调用本地函数,例如:
1 System.loadLibrary("mylib");系统库是 Android 平台本身提供的
.so文件,位于系统分区,比如:
1
2
3
4
5 /system/lib/ 或 /system/lib64/
libc.so
libm.so
liblog.so
libandroid_runtime.so所有的应用库都是原生库,但不是所有的原生库都是应用库。
名称 含义 由谁编写 存放位置 运行层级 原生库 指用 C/C++ 编写、编译成 .so(共享库)的底层二进制库。包括系统的和应用自己的。Android 系统或应用开发者 /system/lib/、/vendor/lib/、/data/app/.../lib/等Linux 层(Native 层) 应用库 指应用自己带的原生库(一个子集),随 APK 打包,用于 JNI 调用。 应用开发者 /data/app/包名/lib/应用进程的 Native 层
Android 7.0 的变化:引入“命名空间隔离”
在 Android 7.0(Nougat)之前,应用加载原生库时,它能看到系统 /system/lib/ 目录下几乎所有 .so 文件。
这带来了严重问题:
- 有些应用会“偷偷”调用 Android 内部未公开的系统库函数(非 SDK 接口);
- 这些接口未被官方支持,系统更新后容易崩溃;
- 同名库(系统库 vs 应用自带库)容易“串库”或冲突。
Android 7.0 起,每个进程(尤其是 app 进程)都有独立的 linker namespace。
这意味着:
- 系统库和应用库在“命名空间”上是隔离的;
- 应用默认只能访问经过允许的系统库(例如
libc.so,libm.so,liblog.so等); - 其他系统内部库(如
libandroid_runtime.so,libbinder.so)对应用是不可见的; - 如果应用想加载这些“私有系统库”,会报错
原生库的命名空间 | Android Open Source Project
Android 7.0 为原生库引入了命名空间,以限制内部 API 的可见性,听起来很高大上,具体解释就是安卓上的动态链接库使用被约束了,普通开发者不管是编译时链接还是使用 dlsym 都不能获取到被限制的 lib 的符号地址, native 开发能调用的 api 被严重限制,同理 Java 也有类似的约束 hiddenapi 用来限制反射的功能。
ssrlive/fake-dlfcn: dlopen/dlclose/dlsym in Android
本题目使用 fake-dlfcn 来查找调用 libart.so 的符号,同时修改了一些代码来适配高版本安卓。这个项目原理比较简单,扫描 proc/self/maps 得到原生库的基址,并通过解析原生库的 elf 符号表得到偏移,再进行计算得到符号地址。
libart.so是什么?
libart.so的全称就是 Android Runtime Library。它是 Android 系统中实现 ART(Android Runtime)虚拟机 的核心共享库。
通俗地说:
libart.so就是 Android 上运行 Java/Kotlin 应用的底层“虚拟机引擎”。
fake-dlfcn是一个“自实现的 dlopen/dlsym”小工具/库,它绕过 Android(尤其是 Nougat 以后的)对dlopen/dlsym访问的限制,允许在受限环境下加载系统.so(比如libart.so)或直接解析 ELF 符号,从而找到/取到那些符号在运行时的地址。Android 7+ 有命名空间限制,应用不能任意
dlopen("/system/lib/libart.so"),系统会把很多内部库对应用隐藏;因此普通dlsym/dlopen不一定生效。
fake-dlfcn提供了替代实现(或直接解析 ELF)来读取/解析系统库的符号表并返回函数地址,或者通过把库映射到进程地址空间再解析符号,从而找到调用libart.so的函数名/地址,便于你在运行时定位与 hook。
接下来可以看 native 了, libnative_add.so 在 Flutter 输入框回车的时候被加载。
genKey 函数是一个 rc4 用来生成静态的 Key ,通过 blutter 生成的代码可以知道这个函数被 flutter 的 getKey() 调用
getSym 函数被 flutter 的 getSym 函数调用。其伪代码如下
1 | __int64 __fastcall getSym(char *a1) |
后面卡住了hook不到….
需要较深的安卓知识,只好先放一放了
解密代码:
1 |
|
1 | WMCTF{I_R4@11y_w@n7_70 _84c0m4_@_m@gic@1_Gir1} |
Videoplayer [复现]
WMCTF2025 VideoPlayer 逆向出题笔记 | Liv’s blog
有壳VMP
首先我们需要VMProtect。VMProtect是一款虚拟机保护软件,是目前最为流行的保护壳之一。VMProtect将保护后的代码放到虚拟机中运行,这将使分析反编译后的代码和破解变得极为困难。除了代码保护,VMProtect还可以生成和验证序列号,设置过期时间,限制免费更新等等。这样即使攻击者用反汇编工具看到指令,也只是虚拟机的字节码,而不是真实的 x86/AMD64 指令,大大增加了逆向难度。虚拟机保护通常会和大量反调试 / 反分析 / 完整性检查一起使用,以进一步提升逆向难度。
脱壳需要寻找oep,程序入口点
VMProtect + 其他反调试 会在用户态用很多 API/机制检测调试器(如 IsDebuggerPresent、异常行为、时间差、调试寄存器、调试句柄等),这些检测很难单靠用户态插件完全隐蔽。
用了sharpod和Scyllahide均无法过
出题人给了两种解法
法1:TitanHide过反调试
这边反调试使用TitanHide驱动,Github可以搜索到相关项目,运行TitanHide.sys需要配置环境。
mrexodia/TitanHide: Hiding kernel-driver for x86/x64.
1 | bcdedit /set testsigning on |
如果无法设置需要进入bios去关闭相应的安全模式,电脑不同方法不同
命令成功后需要重启,左/右下交出现test成功了
关闭就是
1 | bcdedit /set testsigning off |
使用VKD工具的target64中的vminstall在虚拟机中运行安装,会多出来一个引导启动,重启电脑选择新的引导启动就可以,他会进入内核调试模式,禁止驱动强制签名以及关闭PG,也就可以让我们加载titanhide驱动。
运行titanhide.sys,将titanhide的dbg相关插件文件放入dbg的plugins文件夹中,运行dbg即可调试VMP程序。
法2:CheatEngine Veh debugger
软件运行后,使用CE附加,选用VEH Debugger即可进行断点调试。
这能调出来我觉得也是神人了T.T
还是得脱壳,调试可以用CE
VMP脱壳
TitanHide环境配置
先搞个win10 64虚拟机,开启测试模式后
把TitanHide.sys放在(root)/system32/drive/下
注意使用管理员权限
1 | bcdedit /set testsigning on |
然后把GUI打开
更详细步骤可以看:https://www.ctfiot.com/272572.html
下断点
还是卡住了,看了下wp忽略了一条消息
使用VKD工具的target64中的vminstall在虚拟机中运行安装,会多出来一个引导启动,重启电脑选择新的引导启动就可以,他会进入内核调试模式,禁止驱动强制签名以及关闭PG,也就可以让我们加载titanhide驱动。
VKD 是 VirtualKD(一个用来加速/简化在虚拟机上做 Windows 内核调试的工具链);
target64 是 VirtualKD 发布包里针对 64-bit 客机的那一组文件夹(包含 vminstall.exe、驱动和调试传输 dll 等);
vminstall.exe 是放在 target64 里、在虚拟机内运行的安装/引导助手 —— 它会在客机上安装需要的组件并添加一个新的启动项(boot entry)以便用内核调试模式启动虚拟机,从而能更方便地进行 KD(kernel debugging)。这些信息都可以在 VirtualKD 的官方文档/教程里找到。
注意使用TitanHide需要删掉其它用户态的反调试插件如ScyllaHide等,多亏了Liv师傅,不然得搞半天
可以先转到然后搜索GetSystemTimeAsFileTime
下断点
运行到EntryPoint后
第二次运行到断点后
下硬件断点
Scylla导出dump
分析
题目说存在后门账户,猜测会通过strcmp判断用户名或密码文本(这边通过断点用户名或密码的内存进行后续分析也可以)
随便输入用户名和密码后,断点strcmp,点击Login按钮断下,发现会判断一次用户名是否为WMAdmin_#6&JZZ%B,证实确实存在后门用户名判断,但是目前还是登入失败。
做到这目前是我的极限了……





































