NPC²CTF 2025 Reverse 方向复现
Week1
esrever
主逻辑
ida 中点击发现在 main 和 start 之间无限跳转,搜索字符串再 x 交叉引用到关键汇编,发现这部分汇编全部飘红,反编译失败
1 |
|
但是观察后不难发现这里的汇编顺序是反的,往后翻的话还能找到几处类似的地方
上面的那部分汇编的逻辑大致是:
读取用户输入到 byte_4080,
有一个字节交换
调用加密函数
字符串比较判断
密文
密文在 unk_4040 处
提取出来为 131C175213525245143E3E114550053E16511A0F0006070D
flag 格式对比
第二处飘红的地方,比较输入的字符是否符合 flag{} 的格式
1 |
|
主要加密
这部分可以看到有很多异或操作,并且是多次异或同一个值,一个 0x76 ,一个 0x72
1 |
|
解密
用密文异或 0x76 和 0x72 之后没得到可见明文字符,爆破出来第三个异或值是 0x65(后来在汇编中异或 0x76 和 异或 0x72 之间的部分看到有一个异或 0x65)
得到 r}v3r33$u__p$1d_w0{nagfl
,可以看出这个是从后往前两个字符一组交换顺序,整理出来 flag 是flag{nw0d_$1_pu_3$r3v3r}
原理
做题的时候就很好奇,为什么汇编是反着的,但是程序却能正常运行,解惑参考这篇文章
cccc
处理混淆
C# 逆向,动调之后基本能确定主要逻辑在 dll 中,被confuser 混淆了,先用 de4dot 去一下混淆
1 |
|
然后用 dnSpy 打开,发现关键类
可以看到它读取了输入之后进行了一系列操作,后面的数据应该是密文了
动调找程序逻辑,我最开始直接调试会报错,后面设置成这样就能调了(选中使用宿主可执行文件)
动调梳理
逐语句(F11) 一直调
到达用户输入的地方
这里在读取输入
找到一串字符 doyouknowcsharp
,和输入的内容传入同一个函数,推测这个字符串应该是密钥
找到加密逻辑,魔改 RC4,array2[t3] = (byte)(A_1[t3] ^ array[(array[t] + array[t2]) % 256] ^ 100);
这部分多了一个异或 100
再往后就是比较完输出 Wrong
解密
1 |
|
flag 为 flag{y0u_r34lly_kn0w_m@ny_pr0gr@mm1ng_l@ngu@g3$}
ugly_world
处理混淆
main 函数初看很简单
1 |
|
读取了用户输入,sub_55555555E656 对 input 和地址 555555561020 处的数据(0x5566778811223344,0xCCDDEEFF9900AABB)进行处理,sub_55555555E656 函数中是一个循环,其中的 sub_555555555401 是一个很大的经过混淆了的函数,byte_555555561040 的位置是密文 0x0BD3BE58, 0xBE73BBFB, 0xC8C8AF4E, 0x0B3C7D86, 0xC0257C09, 0x1D8FE0B0, 0x8837180C, 0xF5CF9D23, 0xB7A8B599,0xAE630F3D
,sub_55555555E705 输出 your are right!,else 输出 you are wrong!
1 |
|
对 sub_555555555401 中的函数逐个分析
sub_189 -> a1 + a2 -> ADD
1 |
|
sub_1BB -> a1 - a2 -> SUB
1 |
|
sub_2BF -> a1 >> a2 -> SHR
1 |
|
sub_336 -> 把内存里连续的 4 个字节,拼成一个 小端序的 32 位整数 -> byte2dword
1 |
|
sub_31A -> a1 << a2 -> SHL
1 |
|
sub_1F2 -> (a1 ^ a2) & 1 -> XOR1
1 |
|
sub_21D -> a1 ^ a2 -> XOR
1 |
|
sub_28B -> (a1 ^ a2 ^ a3) -> XOR3
1 |
|
修改之后反编译界面如下
1 |
|
加密同构
梳理第一个循环结构一下可以得到
1 |
|
可以看出这个这是一个魔改的 TEA,一共有 128 轮,每轮的 delta 都不一样,并且每轮左移右移的数也不一样
提取出每一轮的 delta 和 移位值
1 |
|
核心加密处理
1 |
|
解密
1 |
|
flag 是flag{UG1y_T3A_m@k35_[m3]_fe31_NaU5Eou5!}
babyre
之前写过了,移步[NPC²CTF 2025]babyVM WP
week2
randomXor
分析
ida 反编译的 main 函数
1 |
|
其中 srand 和 rand 组成了一个类似 MT19937 的伪随机数生成器
1 |
|
1 |
|
& cipher 处提取出密文(128 字节)
1 |
|
解密
伪随机算法已经反编译得很清楚了,稍作修改就可以了,要注意加密 input[i] ^= rand()
是对单字节操作,所以获得 rand() 值时要取低八位
1 |
|
flag 为 flag{R4ndom_rand0m_happy_Funnny_R4ndom_1s_r3ally_funNy!!_hhh233HHH_this_1s_just_a_pi3ce_of_sh1t_fffkkkkkk_f4ck_jjjjKKKKKjjjjasd}
simple
java 层
主要逻辑在 com.example.simpleandroid.CheckActivity
1 |
|
声明了一个 native 方法 CheckData,在本地库 libcheck.so 里
读取字符串输入之后,先调用 isValidInput(input) 做格式检查,再调用 native 方法 CheckData(input) 做进一步校验
native 层
先找到 CheckData
1 |
|
可以看出 ee 数组中的每一个元素都不等于 0x7F,说明 ee[i] != 0x7F 的结果恒等于 1,所以只有当传入的字符串前 32 个字符是 32 个连续的 \x01 时,这个函数才会返回 1,否则返回 0
在 JNI_Onload 里面看到函数 sub_1C430
1 |
|
sub_1C010
1 |
|
sub_11073
1 |
|
sub_11073 主要实现了 RC4 里面的 KSA
等效 python 写法如下:
1 |
|
这里面还有个 srand(0x217) 先放一边
sub_21345
1 |
|
对 v5 数组中的前 13 个数据进行异或 0x45 得到 v6 数组 ,然后 hook 了函数 sub_11073,把 v6 作为参数传入,srand(0x159357)设置了一个随机数种子
计算出 v6
1 |
|
整理出加密
1 |
|