EZDOS

处理花指令不说了。输入flag,并判断长度0x26:

接着是循环压栈,然后弹栈,看比较是0x100,这个长度可以留心一下

中间的操作(逻辑看起来很多,还有几个函数调用),那就先不看,往下找有没有类似right,congratulation的东西。往下翻找到关键词,这里有比较,上方给了比较的地址,一个是输出的密文,一个是输入:

对应地址找到密文:

返回去看中间的操作逻辑,先看哪里调用了这个“输出正确”的函数,发现了异或和一些交换赋值:

嗯……256,交换赋值,疑似RC4。往上看验证猜想,看看有没有key,发现了设置的地方(其实在取密文的时候也注意到了密文上方特殊的字符串):

中间看了一下其他的逻辑,call 函数都是用来混淆视听的,核心逻辑只有rc4,上面256的弹栈改了Sbox,这里用dosbox直接动调到最后rc4异或的地方,取出异或值。异或就有了flag:
异或:32 7d 59 7a f3 0d b3 7b 64 8c eb 28 c4 a4 50 30 a0 ed 27 6a e3 76 69 0c da 28 f8 08 ba a6 17 3e 12 59 45 06 4e f1
密文:7C 3E 0D 3C 88 54 83 0E 3B B8 99 1B 9B E5 23 43 C5 80 45 5B 9A 29 24 38 A9 5C CB 7A E5 93 73 0E 70 6D 7C 31 2B 8C

NCTF{Y0u_4r3_Assemb1y_M4st3r_5d0b497e}

  1. 一开始看出rc4然后直接解密发现不对,改了sbox,用dosbox直接到最后一步(这东西真的难用……)

safeprogram

头尾校验,flag长度是38,格式NCTF{},tls和seh,核心逻辑只有sm4,异常处理的时候执行了一个sub_7FF6B4F81480,修改sbox和key,参量fk,ck都没变


脚本解密即可

  1. 做的时候卡在了最后一步不知道为什么key最后6个byte变成0了,赛后看WP也没解决,别的师傅都是直接读df d2 c5 d7 a3 a5 ff f2 e5 f7 df d2 c5 d7 a3 a5

x1Login

有类似base64的字符串,在so里找到逻辑,一个变表加密和一个异或字符串长度:

1
2
3
4
5
6
7
8
9
10
11
import base64
import string
Str = 'ygvUF2vHFgbPiN9J'
string2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
string1 = 'AbcdefghijklmnopqrstuvwxyzaBCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/'
data=base64.b64decode(Str.translate(str.maketrans(string1,string2)))
for i in range(len(data)):
print(chr((data[i])^len(data)),end='')
# com.nctf.simplelogin.Check
# check
# libsimple.so

可以得到java层的三个字符串


往上看可以看到一个动态加载dex,字符串“ygvUF2vHFgbPiN9J”是libsimple.so

secure里面,本地natve:

看libnative.so:



看assets里面的libsimple.so,发现dex的文件头

结合ida里面的-64,发现是跳过了前64个字节,去掉(010editor选中前64个字节然后delete)以后dump出dex丢入jadx反编译。拿到check函数:

拿到username: X1c@dM1n1$t

username的MD5值(7d53ecd36a43d3d237e7dd633dcf8497)和password传入Secure.doCheck方法,sub_1C44对应上面的loaddex,sub_1E30对应docheck

1
2
3
4
5
from hashlib import md5
username =b"X1c@dM1n1$t"
print(username)
key = md5(username).digest()
print(key.hex())


核心逻辑是sub_2644+sub_2DC4+sub_2644,密文已经给了:

这里没看出来是什么算法,看wp知道类似ABA结构的函数,有64位输入,key可以联想到3des。注意端序的问题,全部转化为大端序进行解密,然后将密文转化为小端序


SafePWDW5y$x?YM+5U05Gm6=
username和passwor连在一起得到flag
NCTF{X1c@dM1n1$t_SafePWD
5y$x?YM+5U05Gm6=}

  1. 顺一下解题逻辑:java层DecStr方法看libsimple.so,进行base字符串解密 -> 有loaddex动态加载,看Secure里面调用,跟进libnative.so -> 发现两个函数,一个loaddex一个docheck。其中loaddex相关函数结合java层的字符串解密名字知道要去看assets里面的libsimple.so -> 发现dex文件头,结合ida里面的分析是dump出dex,得到check函数 -> 分析ida里面的docheck知道3des ->注意数据类型,解密。

  2. 大概是先看java层——本地simple——java——native_loaddex——dump dex——native_docheck

  3. 感觉这道题其实不是特别复杂,虽然有检测但是也可以不用frida绕过,直接分析。主要是有几个调用,所以要看的东西相对比较多,但是顺下来发现核心的步骤其实没多少,注意两个地方就行,一个dumpdex一个分析3des的密文密钥要端序转化。

  4. 3des也可以用Unicorn,具体参考下方的wp~

参考wp:
https://mp.weixin.qq.com/s?__biz=Mzk2NDQ0Mjk0Ng==&mid=2247483856&idx=1&sn=dabe0d4219e20281ecfa1b46903456f7&chksm=c57c349fef3bb9f5d52ae979bc8db513d3d968e9c1e65f05eaa219108e1a5d4a0b5cb44d81df&mpshare=1&scene=23&srcid=0324AomiTgf3yw261Uf8fxHW&sharer_shareinfo=7792ad2a6d9e026d0347e6e7d4239fb5&sharer_shareinfo_first=7792ad2a6d9e026d0347e6e7d4239fb5#rd
https://tkazer.github.io/2025/03/24/NCTF2024/
https://o1r4dlnbgzn.feishu.cn/docx/JtOBd2LZXocQQmxz099cuUTLnLh