HDCTF2023RE_Offcial_WP
导言
本次比赛HnuSec实验室的师傅们都准备了好久,可能经验不足,有好多问题,还望师傅们包容。再次感谢各位师傅们参加。
其次呢,因为我们实验室就两Re手,我还是个菜鸟,这次比赛让我负责Re方向,感觉压力很大,出题也没有经验,挺多问题的,也算是一次挑战吧。同时感谢shangwendada供题除了签到ez_re和Basketball都是他出的,帮了大忙。同时也感谢余的帮助。
ez_re
不多说,upx脱壳后,base64
flag
NSSCTF{Y0u_h@v2_/\/\@57er3d_7he_r3v3rs3}
easy_asm
由Masm for Windows 集成实验环境 2015编译的一个汇编语言程序。直接进入ida分析汇编语言。
主要点如下
这里是在拿al寄存器于cl寄存器做异或处理
然后以$符号作为结尾符号.
cl赋值如下:
可以发现就是异或0x10
这里则是输入点与al寄存器作比较.
看到数据段.
显然不全是密文,有一段是用来判断是否相等的,之前的分析可以发现我们的字符结束符号是$,那么密文则为XTSDVkZecdOqOu#ciOqC}m
最后异或一个0x10输出即为flag
exp:
1 |
|
flag:
NSSCTF{Just_a_e3sy_aSm}
非预期
- 有的师傅把汇编代码交给ChatGPT,让他输出C代码来阅读,挺不错的思路
- 有的师傅直接所有数据扔进Cyberchef里面xor一把梭,就问你出没出吧
- 有师傅通过分析16进制猜测单字符异或爆破出解
double_code
打开ida,发现是一个非常典型的上线器,shellcode loader。
我们需要做的是找到其中的shellcode
此处加载了buf,也就是shellcode
点击查看
这就是shellcode的内容了,我们只需要将其复制下来
然后重新编辑一个文件使用010
再使用32位ida打开该文件分析
就可以发现文件的主要逻辑了。
是一个类似于虚拟机的操作
输出的数组已经给你,通过这个逆向操作将输出的数组还原回去
就可以拿到flag了
exp
1 |
|
flag
NSSCTF{Sh3llC0de_and_0pcode_al1_e3sy}
非预期
好像没看到有非预期,要是有的话,欢迎师傅联系我,或者在NSSCTF上提交单题WP
fake_game
pyinstall解包
找到game.pyc,然后uncompyle6反编译
注较新版本pyinstall好像会自动修复Magic Number
如过遇到uncomyle6无法反编译,请手动修复Magic Number
得到如下代码
1 | # uncompyle6 version 3.9.0 |
分析游戏开始函数可以发现,当分数,距离下一关分数,金钱,关卡数,成特定约束关系时,他们将会作为异或因子,对flag的进行解密,然后将解密的文件写如本地文件,一个叫做flag.txt的文件中
那么我们需要使用z3方程来求解这些异或因子如何才能满足该约束关系
脚本如下:
1 | from z3 import * |
解出值为178940,248,56890,2361
exp
1 |
|
flag:
NSSCTF{G0Od_pl2y3r_f0r_Pvz!!}
非预期&一些说明
- 出题人的脚本精度有差,将xorr最后一位+1了,好多师傅解出来是2360,导致出错,再次道歉。
- 有师傅直接猜HDCTF{}格式flag,然后用HDCT四个字符去倒推异或因子
买了些什么呢
01背包
其实所谓01背包就‘0’与’1‘拿与不拿的问题;
在解决问题之前,为描述方便,首先定义一些变量:$V_i$表示第 i 个物品的价值,$W_i$表示第 i 个物品的体积,定义$V_{ij}$:当前背包容量 j,前 i 个物品最佳组合对应的价值,同时背包问题抽象化($X_1$,$X_2$,…,$X_n$,其中 $X_i$ 取0或1,表示第 i 个物品选或不选)。
1、建立模型,即求$\max_{i=1}^{n} {X_i V_i}$
2、寻找约束条件,$\sum_{i=1}^{n} W_i X_i < \text{capacity}
$;
3、寻找递推关系式,面对当前商品有两种可能性:
包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即$V_{i,j} = V_{i-1,j}$;
还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即$V_{i,j}=\max{V_{i-1,j},V_{i-1,j-W_i}+V_i}$。
其中$V_{i-1j}$表示不装,$V_{i-1,j-W_i}+V_i$ 表示装了第i个商品,背包容量减少$W_i$,但价值增加了$V_i$;
由此可以得出递推关系式:
$$\begin{cases}
V_{i,j}=V_{i-1,j} & j<w_i \
V_{i,j}=\max{V_{i-1,j},V_{i-1,j-W_i}+V_i} & j \geq w_i
\end{cases}
$$
因此状态转移方程为: max(dp[i-1][j], val[i-1] + dp[i-1][j-wt[i-1]])
但这样只能找到理论的最大值
在01背包算法中,我们创建了一个二维的动态规划数组 dp
,其中 dp[i][j]
表示在前 i
个物品中选择总重量不超过 j
的最大价值。在填充 dp
数组的过程中,我们会根据当前物品的重量和价值,来决定是否选择该物品。
当我们求解完整个动态规划数组后,需要找到选择的物品的下标。为了实现这一步,我们可以从动态规划数组的右下角 dp[n][W]
开始,一步步往左上角移动,并判断当前位置是否被选中。我们从右下角开始往左上角移动的原因是,选中的物品的总重量一定是等于 W
的,因此我们需要从 dp[n][W]
开始反向推导,找出哪些物品被选中。
具体来说,我们定义一个变量 w
,初始值为背包的容量 W
,然后从 dp[n][W]
开始往左上角移动。每次移动的时候,我们需要判断当前位置 dp[i][w]
是否被选中。如果 dp[i][w]
的值等于 dp[i-1][w]
,说明第 i
个物品没有被选中;如果 dp[i][w]
的值等于 val[i-1] + dp[i-1][w-wt[i-1]]
,说明第 i
个物品被选中了,此时我们将 i-1
添加到一个向量 indices
中,表示选中的物品的下标。最后,我们从 indices
向量的末尾开始循环,输出选择的物品的下标即可。
总结一下,找到选择的物品的下标的过程就是从 dp[n][W]
开始,根据动态规划数组的状态,一步步往左上角移动,并判断当前位置是否被选中。如果被选中,则将该物品的下标添加到一个向量中。最后,从该向量的末尾开始循环,输出选择的物品的下标。
因此可以使用如下代码
1 | for(int i = n; i > 0 && w > 0; i--) |
解题过程
分析题目文件
可以发现题目的重量和价值是使用随机种子1生成的,因此我们可以使用同样的方法生成
然后分析
发现判断成功与否的条件是所收集到的最后价值是否等于v5然后v5是knapsack函数的返回值,我们查看knapsack函数
是一段dp算法,计算最大价值,我们只需要抄下来让他产生相同的dp数组
将其输出
这就是最大价值的选举过程
横坐标i为选取的物品,因此我们只需要向矩阵的左上角遍历果 dp[i][w]
的值等于 val[i-1] + dp[i-1][w-wt[i-1]]
,说明第 i
个物品被选中了
因为选举过程中的状态转移如下
exp
1 |
|
flag
NSSCTF{0 4 6 10 11 13 16 18 21 22 24 26 31 33 34 36 39}
非预期&一些说明
- 这题前期没有沟通清楚下标问题,导致很多师傅因为下标从1开始一直无法解出,深感抱歉
- 还有就是因为这题设置的是静态flag,出题人没考虑到有多解的情况,后面有解出别的解来问的师傅都直接告诉答案了,还是我们的问题,再次跪求原谅
- 这题比较多的非预期就是01背包是一个经典的dp问题,问ChatGPT一般都能秒切
- 有师傅是通过动调找到重量和价值之和,再去解的,不错的方法,有兴趣的师傅可以试一下
enc
32位无壳,直接分析
主函数:
1 | __int64 __fastcall main() |
需要输入一个key来启动程序,这个key是使用tea加密技术加密的,让我们看tea加密函数
1 | void __fastcall tea_encrypt(unsigned int *v, unsigned int *k) |
根据该函数以及上面的数字直接写出解密脚本:
1 |
|
输出为3
可以看到3被存入了key2然后传入了unpack函数
可以发现unpack函数实际上是调用了一个叫做smc的函数,查看其内容
1 | void __fastcall SMC(char *pBuf, int key) |
典型的smc局部代码加密技术,此处加密的部分为.hdctf程序段
加密方法在xxor函数中
1 | void __cdecl xxor(char *soure, int dLen, char key) |
这其中的key就是之前tea解密出来的值
看到Fun函数可以发现ida无法识别 且字段位.hdctf,则是被smc加密了的字段,使用idapython解密回去即可
1 | .hdctf:000000014001E000 ; Section 3. (virtual address 0001E000) |
解密完后即为:
1 | void __fastcall Fun1(char *data) |
典型的rc4
exp:
smc:
1 | for i in range(0x14001e000,0x14001f600): |
解密:
1 |
|
flag
NSSCTF{y0u_ar3_rc4_t3a_smc_m4ster!!}
Basketball
64位无壳,直接逆
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
先要读入两个100以内数字和一句话加密,加密的话是一句hint
加密函数如下text_66点进去可以发现是一个gcd,加密后为
1 | Uihx!hr!`!ibeu!|iju!H!idmq+xnt!hr+sdlhoe!xnt!un!bIdbj!uid!`ss`x!` |
1 | void __cdecl f(int k1_0, int k2_0) |
考虑暴力破解枚举k1,k2
可以随便输两个数字可以得到Thiy is a hcdt jhkt I help*you is * remind you to cHeck the array and three nugbers can vcew as a group等等类似结果
大致可以猜到hint就是告诉你数组中三个数字为一组
写个暴力循环模拟加密过程应该是可以完美解出s,我就不给出暴力了
给出一个可以猜出s的脚本,并给出s=“This is a hint what I help you is remind you to check the array and three numbers can view as a group”
1 | import math |
然后看看array.txt,里面一堆的300以内数字,结合三个一组,猜想应该是RGB值。
然后结合我后面给出的3 * 637 * 561,构造出图片
1 | from PIL import Image |
可以看到是一张三井寿说教练我想打篮球的名场面
从中提取出message,结合message长度25是一句英文
得到"I want to play basketball"
然后补足28位"I want to play basketballI w"
和code异或即可得出flag
exp
1 | from PIL import Image |
flag
NSSCTF{$1AM_DVN|<_5|-|0|-|<U}
跪求原谅说明
这题是我出题出道半夜,人快昏了,脑子不清醒,出了挺多问题的,跪求原谅。
- 首先就是没给分辨率问题,我当时想的是给了分辨率会不会太明显,就没给,后面考虑到就算知道RGB值,没分辨率也无法还原图片。所以最后就给了一个数组共有3 * 637 * 561个数据的hint,
貌似给了这个前面第一步的加密也不用解了,可能在源代码中加一个无意义的[637][561]的二维数组会好一点。 - 其次就是message长度问题,我本来是想给的,半夜写代码写昏了,给的是flag的长度,flag的长度可以由code数组直接得到,我真该死啊。所以后面又给了message长度为25的hint。
- 有师傅说我谜语人,我觉得倒也还好吧,把图片搞出来,图片中就三井寿的一句台词,我题目也提示就是一句英文,长度也给了,不难得出是
I want to play basketball
吧。比赛中有解出图片不明白的师傅我也提示了,真没想难为大家。 - 再者是flag,有师傅吐槽是乱码一样
HDCTF{$1AM_DVN|<_5|-|0|-|<U}
这应该能看出来是SLAM_DUNK_SHOHKU灌篮高手和湘北的英文名吧。
非预期
- 直接通过搜索我题目简述,可以搜到图片
- 通过猜想flag是以HDCTF开头,直接和code异或得出前几位"I want"进而继续猜想得出"I want to play basketball"
鸣谢
再次感谢shangwendada
和余
还有NSSCTF平台的两位运营
还有HnuSec的每一位师傅