这是我参加的第一个CTF比赛,参与的是逆向方向。作为一个入门
逆向工程入门指北
反编译后即可看到

moectf{C0ngr4tuLati0N_On_find1n9_your_1st_RE_f1aggggg!!!}
Assembly
一段汇编语言,看不懂,丢ai让它一步步讲解
了解到寄存器,基本指令
发现flag由两部分组成,一部分是直接转ASCII码,另一部分和0x42按位进行异或




组合起来写个小代码则成flag
1 |
|
moectf{Assemb1y_L4nguage_1S_co0o0Oool!!}
bbxor
简单的异或


利用异或的对称性得到
moectf{B4sic_x0r_cha1lenge_solved!!}
反方向的 RC4
依旧对称加密算法
手解
通过这个了解了一下rc4算法
想用cyberchef解,但是解不出来。
主要是dump的数据不知道咋搞,
这里面说是20个,但是按dumphex后只有17个数据
去网上找了个脚本

也不知道为啥,后面构造一下蒙过了。
moectf{OH~Dyn4mic!}
到时候再看吧
动调
通过这道题也学着调配了一下wsl和linux调试环境

在call那里下断点,再F8然后去RDI那里

得到flag
moectf{OH~Dyn4mic!}
超级土豆大爆炸

用die查到有一层upx壳,通过这个学了一下手脱和upx -d(好像linux的不能用xdbg)

碰到一串字符串,直接丢一把梭里,一不小心解出来了

一点点看吧,base58编码
主要是下面这层代码
1 | __int64 __fastcall sub_401216(const char *a1, __int64 a2, unsigned __int64 a3, unsigned __int64 *a4) |
搜一下特征
Base58 特征
- 字母表固定
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
共 58 个字符。 - 去掉了易混淆字符
没有0、O、I、l,也没有+、/、=。 - 前导
1表示前导零字节
解码时,开头的每个1对应一个0x00字节。 - 本质是大整数进制转换
把字节串当成大整数,按 58 进制编码/解码:num = num * 58 + index - 无填充,长度可变
不像 Base64 有=补位。 - 常见用途
比特币地址、IPFS、Ripple、CTF 逆向题中的隐藏编码。 - 代码识别特征
- 出现那串 58 字符常量
strchr查字母表- 循环里乘
58 - 大整数数组(常见 128 字节)
- 统计前导
'1'数量
- 与 Base64 区别
- Base64:有
+ / =,含0OIl - Base58:无符号,无易混淆字符,纯字母数字
- Base64:有
- CyberChef 解法
搜索 From Base58,输入字符串即可解码。
flag:
moectf {EaSy_UPX_anD_Base58}
奇怪的 APP
apk逆向,找到一个工具JADX

依旧找main函数,发现可疑字符串

继续找拼图



moectf{Apk_REv3rse_1sghT?!!!}
请你喝茶
第一次碰到比较复杂的题目
做了两次,第一次跟着填空,第二次自己写了代码

两部分tea
主要就是这些代码的理解

然后写逆向就倒过来
首先先改名改类型
根据上下代码改好使程序变得好读
比如说v6其实就是a1[0],v5是a1[1],v4这种改成sum
还有后面_DWORD那块,改a2类型

瞬间好读很多

然后补全代码
C
1 | #include <bits/stdc++.h> |
输出
moectf{Wh4t_a_n1ce_cup_0f_TEA!!}
Tell Me No Lies
这题当时直接看爆了
后面慢慢静下来认真读找到感觉,也通过这道题了解了很多知识点
然而后面还是问了AI
分析代码

从上往下:首先是v7,通过下面ifelse可以猜得v7必须是false,进入函数IsDebuggerPresent()查看

可知这是一个反调试口,需要手动改rax或者patch
问题1:这个/status是什么,文件?
答:Linux 里记录当前进程状态的文件
读取进程状态,碰到Tracerpid开始内部语句
问题2:strtoul()函数含义是什么,有何作用?
答:string to unsigned long(字符串转无符号长整型),把字符串里开头的数字抠出来,变成整数。一碰到其他字符就退出,如果没有值的话返回0
作用:判断有无数值,这里用来判断是否有调试进程,有就返回真值,用来反调试

往后走,三个函数
build_required_token(s),acquire_operator_token(),compare_operator_token(v8, s)
第一个build_required_token(s)
1 | __int64 result; // rax |
问题1:看到类型提示想改参数类型,改成int *a1报错了
答:要改成char *a1
具体不知道为啥
因为反编译里
*(_BYTE *)(a1 + i) = ...的意思是:a1是一个地址,往a1 + i这个字节位置写数据。如果你把它改成__int64 *a1,那a1 + i就变成“跳过 i 个 int64”了,语义完全变了。这里的a1应该理解为char \*a1(字符串首地址),反编译工具偷懒没给你标清楚类型而已。
第二个acquire_operator_token()
1 | if ( (getpid() & 1) != 0 ) |
问题:&按位与作用
答:
- 奇数的最低位是 1 →
getpid() & 1 == 1 - 偶数的最低位是 0 →
getpid() & 1 == 0
作用:判断奇偶
第三个就是简单比较数据,使v8和s相等
然后进行自揭密程序self_driven_decrypt(v7, v6, v4)
然后程序结束
操作
动调跳过
改寄存器rax的值为0跳到else分支
patch
选中v7 = isdebuggerpresent那一行
edit->patch program->assemble
因为我们想让它直接为0,并且原先有5个数

所以改成
1 | xor eax, eax |
也就是31 C0 90 90 90
因为eax和自身异或结果就是0,达到了我们的目的
我们继续动调,看看if(v6)那块
当在acquire处下断点后F8后,RDI中显示这一串

与其他俩都不符合,所以又得patch
同样的操作把后面v6的if改了
后面就是自解密了,不用我们上手,直接下断点到printf

出了
moectf{Wow~h0w_do_Y0u_f1nd_IT?}
成都好室友(未解)
这道题是pyc的逆向,首先先反汇编,用
python -m xdis.disasm chall15.pyc > chall15_disasm.txt
这条命令,当然要先装一个pip install xdis库
看不懂,于是丢ai,发现这是一个生成图片的程序
发现是一个缺角二维码,补角发现扫不出来。卡关了
(ai题解懒得看了,好像说是还要颜色反转)
让我们说中文

在里面乱点,观察到

这是base64改表的特征,直接上cyberchef(或者网上找个python脚本)

得出一串字符串
丢到一把梭里试试

确实让我们说中文
最后结果
1 | moectf{Spy4KA_y@$w_W07ds} |
Mewtype
一个py文件
打开来发现乱七八糟,一条蛆
慎开
1 | exit(1 - (lambda myk43: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(len(myk43) == 54))(lambda: (lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk19: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((myk19 * myk19 * myk19 - myk19) % 6 == 0))(len(myk43)))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda myk39: lambda vol40: lambda myk41: lambda vol42: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(myk41 >= len(vol40) - 1))(lambda: lambda myk3: lambda vol4: myk3)(lambda: (lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda vol18: (lambda myk15: lambda vol16: myk15((lambda vol10: lambda myk11: lambda vol12: vol10(vol12)(myk11))(vol16))(vol16))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((vol18 * vol18 + vol18) % 2 == 0))(lambda myk5: lambda vol6: vol6))(myk41))(lambda: (lambda vol22: lambda myk23: lambda vol24: lambda myk25: lambda vol26: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda vol20: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol20 == 0))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 + myk23 + vol24 * vol24 == vol26))((lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk21: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(myk21 == 1))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * myk23 + vol24 == vol26))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * vol22 + myk23 * myk23 + vol24 * vol24 * vol24 == vol26)))())(ord(vol40[myk41]))(ord(vol40[myk41 + 1]))(ord(vol40[(myk41 + 17) % len(vol40)]))(vol42)((10111, 11290, 137705, 13834, 12017, 882822, 2861, 6952, 148686, 9290, 9962, 153274, 13069, 9595, 871218, 9834, 10851, 1584472, 9149, 5765, 159511, 9289, 5093, 126222, 11887, 4921, 152566, 13114, 6109, 140607, 13366, 9447, 156018, 9712, 11045, 1795540, 15852, 4864, 1389923, 10278, 5369, 1586542, 10519, 5995, 1281695, 4298, 5674, 963368, 10232, 5085, 880083, 10095, 15119)[myk41] ^ 90))(lambda: (lambda vol22: lambda myk23: lambda vol24: lambda myk25: lambda vol26: (lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda vol20: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol20 == 0))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 + myk23 + vol24 * vol24 == vol26))((lambda myk7: lambda vol8: lambda myk9: myk7(vol8)(myk9))((lambda myk21: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(myk21 == 1))(myk25))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * myk23 + vol24 == vol26))(lambda: (lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)(vol22 * vol22 + myk23 * myk23 + vol24 * vol24 * vol24 == vol26)))())(ord(vol40[myk41 + 1]))(ord(vol40[myk41]))(ord(vol40[(myk41 + 17) % len(vol40)]))(vol42)((10111, 11290, 137705, 13834, 12017, 882822, 2861, 6952, 148686, 9290, 9962, 153274, 13069, 9595, 871218, 9834, 10851, 1584472, 9149, 5765, 159511, 9289, 5093, 126222, 11887, 4921, 152566, 13114, 6109, 140607, 13366, 9447, 156018, 9712, 11045, 1795540, 15852, 4864, 1389923, 10278, 5369, 1586542, 10519, 5995, 1281695, 4298, 5674, 963368, 10232, 5085, 880083, 10095, 15119)[myk41] ^ 90))())(myk39(vol40)(myk41 + 1)((vol42 + 1) % 3)))())(myk43)(0)(0))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda myk27: lambda vol28: lambda myk29: 0 if myk29 == len(vol28) else ord(vol28[myk29]) + myk27(vol28)(myk29 + 1))(myk43)(0) == 5138))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda vol30: lambda myk31: lambda vol32: 0 if vol32 == len(myk31) else ord(myk31[vol32]) ^ vol30(myk31)(vol32 + 1))(myk43)(0) == 26))((lambda myk13: lambda vol14: myk13(vol14)(myk13))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda myk33: lambda vol34: lambda myk35: 0 if myk35 == len(vol34) else ord(vol34[myk35]) * ord(vol34[myk35]) + myk33(vol34)(myk35 + 1))(myk43)(0) == 520600))((lambda myk17: (lambda myk3: lambda vol4: myk3) if myk17 else lambda myk5: lambda vol6: vol6)((lambda vol0: (lambda myk1: myk1(myk1))(lambda vol2: vol0(lambda *a: vol2(vol2)(*a))))(lambda vol36: lambda myk37: lambda vol38: 0 if vol38 == len(myk37) - 1 else ord(myk37[vol38]) * ord(myk37[vol38 + 1]) + vol36(myk37)(vol38 + 1))(myk43)(0) == 467644)))))))(lambda: lambda myk5: lambda vol6: vol6)())(input())(1)(0)) |
我都不知道如果没有ai怎么整理
1 | import sys |
看出来它要满足这些条件。
可以用Z3约束求解器,正好学下它的语法。。
新写了re-Z3约束求解器的文章,里面写了遇到的挫折
下面是自己复现的
1 | from z3 import * |
得出下列flag
moectf{l@mbd4_c@lcu1us_4r3_h4rd_but_z3_s0lv3r_1s_3asy}
Ultato Polosion Xptra


这一题无法直接upx-d解壳,又因为不是windowsPE文件,也不能xdbg手脱。想法是用010看看文件是不是被改动过
010patch

当时第一次接触010什么都看不出来,只能复制全部然后丢到deepseek里胡乱分析,并没有卵用

后面想到打包一个正常文件然后对比
依旧是不熟悉010,只能是一点一点比对着分析,然后也不知道哪些是壳哪些是正常的不同文件
过了几天后发现有个功能。。


这样就容易看出来了,把正常打包文件的信息丢上去,注意位数要一样。然后另存为
解包成功

进入题目

依旧,一顿操作之后比对静态数据。
反向解出input即flag
重点是encrypt()函数,点进去

纵览一下,一顿操作,函数,加减法,异或,换位
从下面看起,换位的写出来是这样

即可写出
1 | uint64_t old_v1=v4; |
异或是对称的,所以从下往上就行了,加减法不涉及多个量,换号即可
主要是rol64函数

这个程序怎么反向写解密?思考它是有两种可能性还是什么的。
却忽略了输入的数据本身就是定死的,直接可以知道n是多少。
encrypt()就看完了,现在还有main中最上面的str_to_u64()要处理

不知道怎么处理,问了deepseek,说是大端序存储了,用
1 | uint64_t vs[6] = {v1, v2, v3, v4, v5, v6}; |
处理。但是我复现不出来,想不到。对底层,32位int,64位int,还有最终uint64数据转char还不太了解,memcpy还是什么的用法不了解
写代码中遇到的问题
- 交换值时要用中间值
- 多数据return,可以直接用全局变量,用void即可
请 fanchai 喝茶
是请你喝茶的升级版
xxtea改版,需要先理解算法然后写代码
其实这道题在开学前就已经上线了,但是当时没有长代码分析的经验,一看到这么长都吓死了。看都不敢再看这道题,但是后面反复摸然后一点点理解,辅以ai帮助,写出了这道题目
难懂在哪
难懂的是这几个块

1.开头部分:好奇怪啊
他把v3复制到v4,然后向前4字节,v4也前进4字节,v5-1。从上文看,v5等于16,而fgets了66,也就是实际文本是64,分4段变
这一段其实就是把输入变成了 16 个 32 位字,承担了memcpy函数的功能
感觉不是主体部分的就不用多去想

两段搭配在一起


2.赋值部分:这一段是在赋值,要注意赋值顺序,对理解代码很有效
emm,其实我还是不太懂_mm_or_si128哪几个的作用是什么
问了ai,他说不用管,密文是静态数据,比较是运行时逻辑;解 flag 只关心密文,不关心比较。
3.主体加密部分


当时就是看这里太复杂了,一个是函数名似乎没什么规律,另一个是这加密算法不知道怎么去写解密
分析主体部分
首先就是对赋值那块,和主题解密这块变量的改名

瞬间舒服多了,然后开始找规律。
先看框架
某个字 += (一堆移位、异或、加法) ^ (另一堆)
共用这些:
sum(每轮都变,轮中不变),key[](一直不变)
再看不同

结尾这里有点奇怪,异或1,2,~,空,5,6,7,空,发现每隔4行异或归0
查询ai后发现,这里可以提取出 p & 3

“每隔几行重复一次” → 周期 → 周期长度对应 p & (周期长度-1) → 提取变量 → 写成循环。
那我们现在就可以写出通用公式了
1 | MX = ((z << 4) ^ (y >> 3))\+ ((y << 2) ^ (z >> 5))^ ((sum ^ y) + (z ^ k)); |
然而写解密时还要注意加密了谁,加密用的数据是已经加密过的还是没有加密过的。还原的时候怎么用值
加密时
v[0]先更新,然后v[1]用新的 v[0] 算 MX。
所以解密时,v[15]要先还原,并且它用的y是“还没还原的v[0]”;
还原完v[15]后,v[14]用的y正好是刚还原的v[15]。
这就是y从头传递到尾的“接力”。
写出代码
1 |
|
结尾总结
这其实是XXTEA算法的改版,只要认出它以后就好办了
XXTEA特征
最外层:轮循环(for r = 0..rounds)
中间层:字循环(for p = 0..n-1)
最内层:一个 MX 函数(用左右邻居 + sum + key 算一个值)
flag
moectf{Tre4t_f4ncha1_W1tH_xxtea_Wh4t_A_g00d_Id3a_HahaHAh4hAH4ha}
广晨风语录(未解)
apk,还是不太会分析
2B 小模型(未解)
这题ai能出来,但是我看不懂emm,没交flag
MCb4ckd00r(未解)
好像是个流量分析?没什么思路啊,网上有一题目是ftp中含压缩包的,跟这个没关系,这里只有http和json之类的,没怎么了解
九转蛇肠(未解)
反思
ai太方便了,有了ai的辅助我们能轻松拿到一些题目的flag,但是当闭卷再做的时候,还是要走那条必须走过的路,还是要花费那些必须花费的时间。
还是得沉下心去认认真真写
