-
-
[原创]强网杯2023 dotdot 题解及设计思路
-
发表于: 2023-12-22 21:04 9670
-
大家好,这里是强网杯s7 dotdot出题人。因为看到大家对dotdot这题的评价,对于这题给大家带来不好的解题体验,这里非常抱歉orz
这题是一道存粹的C#题目,没有任何混淆,因此可以通过dnspy直接分析,直接阅读源码进行逆向。因此,我在加密算法和程序控制流上做了一些选择。以下是考点设计:
选择白盒AES作为题目的第一步的主要原因,是为了匹配上C#题目白盒逆向的特点进行选择的。因为在一般其他的crackme类型的题目设计上,对于加密算法的使用上,都需要对密钥进行反复的变化,对选手的逆向量有着较高的要求,但是当选手拿到题目源码的情况下,密钥就基本上直接摆在大家面前。因此,赛题首先使用白盒AES作为赛题的第一关。
但是,仅有第一关显然是不够作为强网杯非先锋类型题目要求的,所以我们需要更多的考点。于是,我想到了在前期研究C#反序列化漏洞时学习到的通过反序列化链调用函数的方式进行函数调用。那么问题就来了,我该如何尽可能合理的将反序列化融进题目中呢。思前想后,最终感觉以License的方式相对合理,选手提供一个正确的License,题目自动输出flag。
那么这里就涉及到我需要选手利用反序列化执行什么,来满足题目的完备性。最终,就有了FFF函数,需要选手解出一个参数,并将FFF参数所需的两个参数填入License中。当FFF函数检测两个参数满足验证需求时,计算License Hash,吐出flag。
关于白盒AES的考点, 这里就不在赘述了,大家可以看看lfyyy师傅发布的贴子,里面内容非常之详细。还有很多选手设计了爆破算法,实现了这部分考点的求解orz。
然后就是第二部分,一个题目提供的被RC4加密过的License。此时预期的思路是,选手首先运行后,发现程序出现异常报错。并且在对main函数进行审查后,其余的无更多内容,那么验证题目flag的控制流由什么引起呢?为进一步分析,通过RC4将License进行解密,可以发现有FFF函数的字样,同时分析发现FFF函数就是验证功能。因此,可通过查询C# BinaryFormatter 反序列化是被微软官方认定的危险函数,因此可进一步学习如何生成反序列化的链子。以下是样例代码:
这样就能生成一个满足题目要求的License。其实在这一块儿的设计上,我还考虑过两种,一是将题目布置成服务器类型,能够远程验证,但是后来由于害怕选手们的的奇思妙想,最终就没有实现orz。第二个思路是除了FFF函数,再额外的构造上千个混淆函数,使得选手必须先理解步骤2,才能进入步骤3,但是最后觉得那样有点过去刻意的增加难度了,有点偏离大家通过考点学习反序列化漏洞的初衷,最终就否决了。
然后就是最后一部分FFF函数的解密,因为认为前面的考点已经足够支撑这一题目了,因此这一部分就只是非常简单的tea加密,来使第一步白盒AES的内容能够贯穿赛题的全部过程,而不是单一的一关。
对于赛题想要选手将参数填入License中的想法,题目中有几个隐藏的提示,包括FFF的参数类型就是string,FFF需要两个参数,Main函数在反序列化执行后不再有其他内容等。
以上就是本次赛题的设计思路和预期的解题过程。整体大家的解法和预期的结果是一样的,要想解出这个题目,必须完整掌握这三个过程。也和预期的一样,大家作为逆向手,直接去找到了验证函数,在比赛过程中,也有选手像裁判提问为什么提交的FFF函数接出来的结果不正确。
原本预想的本题按照这个思路下来后能够像完成探索的过程一样,浑身舒畅。但是有的选手表示这个赛题设计有点抽象,在这里给大家道个歉orz。
static void TypeConfuseDelegate(Comparison<string> comp)
{
FieldInfo fi
=
typeof(MulticastDelegate).GetField(
"_invocationList"
,
BindingFlags.NonPublic | BindingFlags.Instance);
object
[] invoke_list
=
comp.GetInvocationList();
/
/
Modify the invocation
list
to add Process::Start(string, string)
invoke_list[
1
]
=
new Func<string, string,
int
>(FFF);
fi.SetValue(comp, invoke_list);
}
static void Main(string[] args){
/
/
Create a simple multicast delegate.
Delegate d
=
new Comparison<string>(String.Compare);
Comparison<string> c
=
(Comparison<string>)MulticastDelegate.Combine(d, d);
/
/
Create
set
with original comparer.
IComparer<string> comp
=
Comparer<string>.Create(c);
SortedSet<string> mysl
=
new SortedSet<string>(comp);
mysl.Add(
"dotN3t_Is_1nt3r3sting"
);
mysl.Add(
"WelcomeToQWB2023"
)}
TypeConfuseDelegate(c);
BinaryFormatter fmt
=
new BinaryFormatter();
BinaryFormatter fmt2
=
new BinaryFormatter();
MemoryStream stm
=
new MemoryStream();
fmt.Serialize(stm, mysl);
using (FileStream fs
=
new FileStream(
"License.dat"
, FileMode.Create, FileAccess.Write))
{
stm.Position
=
0
;
stm.CopyTo(fs);
}
}
static void TypeConfuseDelegate(Comparison<string> comp)
{
FieldInfo fi
=
typeof(MulticastDelegate).GetField(
"_invocationList"
,
BindingFlags.NonPublic | BindingFlags.Instance);
object
[] invoke_list
=
comp.GetInvocationList();
/
/
Modify the invocation
list
to add Process::Start(string, string)
invoke_list[
1
]
=
new Func<string, string,
int
>(FFF);
fi.SetValue(comp, invoke_list);
}
static void Main(string[] args){
/
/
Create a simple multicast delegate.
Delegate d
=
new Comparison<string>(String.Compare);
Comparison<string> c
=
(Comparison<string>)MulticastDelegate.Combine(d, d);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)