Previously
这些鬼题都太折磨了,感觉不是我能好好做的
于是去找题解怼了一波,希望碰到类似的玩意至少能有点头绪
Reverse
GameMaster
游戏题,打开,发现是经典21点游戏,但是不知道获胜条件。
考虑到游戏随机性过大,强行写算法贪过去不太现实,于是打算逆进去看看获胜条件。ida对付微软的鬼东西基本没辙,于是用一个好用的工具——dnspy解决:
dnSpy is a debugger and .NET assembly editor. You can use it to edit and debug assemblies even if you don’t have any source code available.
——dnSpy official Github site
总而言之,面向对象,可实例化,以及各种优势,你可以找到一万个用它的理由。
我们直接把这玩意和附属dll之类的甩进dnspy,就直接逆出来一堆东西,比方说游戏内置的金手指(作弊码)goldFunc,作弊码分四段,第一段没大用,其余的每次输入正确就会触发对应AchivePoint。
但是先不急,打开Main函数,一开始读gamemassage,并存入全局变量memory。
注意到键盘操作底下塞了个verifyCode:
……
case (ConsoleKey)63:
case (ConsoleKey)64:
continue;
case ConsoleKey.Escape:
Program.verifyCode(arrayList, game);
continue;
case ConsoleKey.Spacebar:
……
点进去,这玩意是调用goldFunc对键盘操作进行判断的,
private static void verifyCode(ArrayList arrayList, Game game)
{
string str = "";
for (int i = 0; i < arrayList.Count; i++)
{
str += arrayList[i].ToString()[0].ToString();
}
Program.goldFunc(arrayList, game);
arrayList.Clear();
}
那么回到作弊码函数,注意到第二段作弊码中AchivePoint处含有如下数据和解码:
byte[] key = new byte[]{ 66, 114,
97,
105,
110,
115,
116,
111,
114,
109,
105,
110,
103,
33,
33,
33
};
ICryptoTransform cryptoTransform = new RijndaelManaged
{
Key = key,
Mode = CipherMode.ECB,
Padding = PaddingMode.Zeros
}.CreateDecryptor();
Program.m = cryptoTransform.TransformFinalBlock(Program.memory, 0, Program.memory.Length);
而在第一段和第三段当中也有对数据进行类似操作:
game.Player.Bet -= 22m;
for (int i = 0; i < Program.memory.Length; i++)
{
byte[] array = Program.memory;
int num = i;
array[num] ^= 34;
}
……
game.Player.Balance -= 27m;
Environment.SetEnvironmentVariable("AchivePoint3", game.Player.Balance.ToString());
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream serializationStream = new MemoryStream(Program.m);
binaryFormatter.Deserialize(serializationStream);
那么金手指干了些啥不言而喻:
1、对gamemassage进行一个34的异或
2、再进行ECB解码
3、反序列化
那么现在就有两种思路:
1、复现金手指的算法,然后拿到处理过的gamemassage
2、打入金手指,拿到处理过的gamemassage
考虑到做法2临场尝试屡次碰壁,这里就参考Jamie743 dalao的文章,从题目给的原程序扒代码下来对着数据的pg狠狠地铎:
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.IO;
using System;
Console.WriteLine("Hello, World!");
byte[] memory;
byte[] m;
byte[] key =
{
66,114,97,105,110,115,116,111,114,109,105,110,103,33,33,33
};
ICryptoTransform cryptoTransform = new RijndaelManaged
{
Key = key,
Mode = CipherMode.ECB,
Padding = PaddingMode.Zeros
}.CreateDecryptor();
FileStream fileStream = File.OpenRead("gamemessage");
int num = (int)fileStream.Length;
memory = new byte[num];
fileStream.Position = 0L;
fileStream.Read(memory, 0, num);
m = new byte[num];
for (int i = 0; i < memory.Length; i++)
m[i] = (byte)(memory[i] ^ 34);
m = cryptoTransform.TransformFinalBlock(m, 0, m.Length);
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream serializationStream = new MemoryStream(m);
binaryFormatter.Deserialize(serializationStream);
FileStream fileStream2 = File.Create("gamemessage2");
fileStream2.Write(m, 0, m.Length);
有一点玄学的地方,这玩意在C#项目中的默认文件输入输出位置不是在当前文件夹,而是bin下的Dbg文件夹,不知道是什么原理。
但是有一点可以肯定,解出来的gamemassage2肯定不能拿编辑器直接看。万策尽,但只是RE方面。微软弄出来的东西跟PE文件经常沾上,关键就是DOS头,也就是所谓的MZ头。我们就010editor打开,找MZ头:
文件头到手,把前面的部分删去省得逆的时候出错。再塞回dnSpy开始逆:
提flag的部分就在这了。首先将num1、num2、num3存入array之后加密,结果存入array2。结合上面的输入,理论上这些数据要通过游戏本体的金手指得到,但由于其过程太过复杂,我们还是利用z3和原gamemassage的Check1函数解方程算了……
找Jamie743 dalao和凡_tastic dalao现学了一波z3位运算操作:
import z3
first = [101, 5, 80, 213, 163, 26, 59, 38, 19, 6, 173, 189, 198, 166, 140, 183, 42, 247, 223, 24, 106, 20, 145, 37, 24, 7, 22, 191, 110, 179, 227, 5, 62, 9, 13, 17, 65, 22, 37, 5]
x = z3.BitVec("num1", 64) #这里是64位程序,保险起见用64位解
y = z3.BitVec("num2", 64)
z = z3.BitVec("num3", 64)
KeyStream = [0] * len(first)
num = -1
for i in range(320): #还是那句话,源码里有现成的就趁热
x = (((x >> 29 ^ x >> 28 ^ x >> 25 ^ x >> 23) & 1) | x << 1)
y = (((y >> 30 ^ y >> 27) & 1) | y << 1)
z = (((z >> 31 ^ z >> 30 ^ z >> 29 ^ z >> 28 ^ z >> 26 ^ z >> 24) & 1) | z << 1)
if i % 8 == 0:
num += 1
KeyStream[num] = ((KeyStream[num] << 1) | (
((z >> 32 & 1 & (x >> 30 & 1)) ^ (((z >> 32 & 1) ^ 1) & (y >> 31 & 1)))))
solver = z3.Solver()
for i in range(len(first)):
solver.add(first[i] == KeyStream[i])
if solver.check() == z3.sat:
mod = solver.model()
print(mod)
于是可以得到三个num:
[num2 = 868387187, num3 = 3131229747, num1 = 156324965]
刚才看解密的主逻辑,还有俩步骤,一个是ParseKey函数:
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
Key[i * 4 + j] = (byte)(L[i] >> j * 8 & 255UL);
}
}
一个是最后一步:
for (int i = 0; i < array5.Length; i++)
{
array5[i] ^= array4[i % array4.Length];
}
通通在py里头复现一波完事:
v = [156324965, 868387187, 3131229747]
array4 = [0]*12
for i in range(3):
for j in range(4):
array4[i*4+j] = (v[i] >> j * 8 & 255)
array5 = [60, 100, 36, 86, 51, 251, 167, 108, 116, 245, 207, 223, 40, 103, 34, 62, 22, 251, 227]
print("flag{", end='')
for i in range(len(array5)):
array5[i] ^= array4[i % len(array4)]
print(chr(array5[i]), end='')
print("}")
flag{Y0u_@re_G3meM3s7er!}