HDCTF2024
Rev
FinalEncrypt
附件给了三个文件,一个是FinalEncrypt
,一个是flag.md.enc
,一个是Encryption.exe.enc
明显两个加密文件,先看看FinalEncrypt
直接运行,回显提示了用法
用 IDA64 反编译一下,关键点在后面
跟进一下文件加密函数,可以看到是对文件进行了chacha20加密
直接解密较难操作,考虑用 FinalEncrypt 加密一个文件,然后用同样的随机数种子去生成密钥,再用 FinalEncrypt 解密
首先是我们保持文件的修改时间不变,从压缩包中提取文件
tar --atime-preserve -xvf encrypted.tar.xz
然后用 stat
命令查看文件的访问时间
得到两文件的修改时间2024-05-08 16:20:22.000000000 +0800
和2024-05-08 16:21:59.000000000 +0800
然后将这两个时间转换成Unix时间戳
Unix时间戳转换
得到1715156422
和1715156519
随便生成一个测试文件
echo "test" > test.txt
GDB 调试 FinalEncrypt
gdb --args ./FinalEncrypt -re test.txt
在 time 函数处下断,开始调试
1 | b time |
一直按 n
步过,到这条汇编语句
明显这两条语句是调用了寄存器rax
的值作为随机数种子
然后调用 srand
函数
用 set $rax=1715156422
修改 rax
的值
然后按 c
继续执行
获得了Encryption.exe.enc
加密的key
507CD82354B1A821ED46A45FAF06D53D0F941C24E085D511F244410B2666056462E126287107616B308DDB193B62036C89C7112C8E713828BC8E8C5079ED221A
同理,我们可以得到flag.md.enc
的key
CB0C24457D0BE9695EFDD94C533DE2036E0E6E706C1B06471DBE334DA3195C32F46C8078C7311D068270A10EAE446D0553FA1F628CE4336A39DA852730625324
然后我们用这两个key去解密两个文件
./FinalEncrypt -d Encryption.exe.enc 507CD82354B1A821ED46A45FAF06D53D0F941C24E085D511F244410B2666056462E126287107616B308DDB193B62036C89C7112C8E713828BC8E8C5079ED221A
./FinalEncrypt -d flag.md.enc CB0C24457D0BE9695EFDD94C533DE2036E0E6E706C1B06471DBE334DA3195C32F46C8078C7311D068270A10EAE446D0553FA1F628CE4336A39DA852730625324
flag.md
文件为密文e07816e1dba1da61536634bef2c3b6346d533cc3b6b834e3beb634c80264143c34e36400bb4daa6902ff643414e3b8344dff6634b8b66db6bbc33834143461ab147e04
看看 Encryption.exe
加密逻辑就是用随机数生成v8的首位,然后进行一系列变换生成一个加密表,最后加密flag
__ROR1__是ida的内置位移函数可以在ida/plugins/defs.h查看__ROR1__定义
有两个参数:(value, int count)
第一个参数为左移的数,第二个参数为左移的位数。
如果第二个参数值为负数,则实际上为循环右移 -count位。
该函数的实现细节为:
先得到value的位数,然后count对位数取模。
如果count值大于0,则先右移-count取模的结果,然后在左移取模的结果,得到的两个数相或,即为循环左移的结果。
如果count值小于0,先左移在右移即可。
举例来说: value = 0110, count = 6
value为4位数, 6 % 4 = 2,
0110先右移4-2=2位,得到0001,然后在左移2位,得到1000,0001 | 1000结果为1001,即循环左移结果为1001。
理解了这个函数,我们可以模拟加密表的生成方式生成加密表,爆破查找正确的加密表来解密flag
因为根据加密结果来看,加密表的值不超过0xff,所以我们可以爆破第一个字节,然后用相同的方法生成剩余的字节
ida生成的伪代码可以直接使用,最后的exp如下
exp
1 |
|
baby_rop
简单运行一下提示输入flag
用ida打开发现是一个rop题目,静态看不到什么有用的信息
用动态调试看一下
前面是一些读入和随机数的初始化,不用太关注
运行到这里会有一个flag长度的比较
如果长度为32位,寄存器RDI
和RSI
的值相等,就会继续往下走,否则会直接退出
继续往下运行,会得到key的值HDIN2024
同时运行进入 500044
函数可以发现对输入进行了异或和加
继续往下运行可以发现比较
RDI
存放输入加密后的结果,RSI
存放密文
如果RDI
和RSI
相等,就会继续往下走,否则会直接退出
所以要每次输入正确8位后,再次调试到这里进行获取下一个8位的密文进行解密
如此循环,直到解密完整个密文
exp
1 | enc = [11131674077274786132, 10336780887984666816, 4152170666298469215, 9026692794781294446] |
Realeazy
在刚进入该题目时,首先看到的应该是MainActivity
,这里是一个魔改的xtea
。这里如果分析一下的话会发现输入的前五个字节根本没有被使用到,再者如果看下控件对应的方法或者xml
文件的属性,甚至于弹窗的语句都是有一点不一样的,都可以发现这个Activity
是假的。真正的默认启动活动是ProxyActivity
。
但是假如说这些都没有发现的话,那么这里以java代码来解开这个Tea也是提示性的flag。
明文为flag??{Thisafakeflaghhh}
当我们进入ProxyActivity
时,会发现主要调用了一个本地方法。
当我进入so文件库进行查看时,这个文件所有的函数被加了混淆
这里的混淆是间接跳转加平坦化。
写脚本或者其他方法去掉混淆后
前五位作为dword_708
的索引进行一个校验,使用z3
求解即可,但是这里真的很丑陋。
这部分源码
1 | from z3 import* |
解出key
后会发现key
进行一种运算生成了七个大数。%48
是为了保证是0
到9
然后进行了大数运算最后校验。
由于我没有实现大数除法(真的很抱歉)所以都只是简单的加减,唯一的乘法也只是用来生成加减的数上。
所以只需要从网上搜索大数运算脚本或者在线网站应该都是可以实现的。
解出后24位输入为114514131452125252550000
最终输入为: rea1!114514131452125252550000
Flag{114514131452125252550000}
exp
1 |
|