【问题标题】:.Net 4.6 breaks XOR cipher pattern?.Net 4.6 打破了 XOR 密码模式?
【发布时间】:2015-07-31 18:40:42
【问题描述】:

在 .NET 4.5 中,此密码在 32 位和 64 位架构上完美运行。将项目切换到 .NET 4.6 会在 64 位中完全破解此密码,而在 32 位中,有一个 odd 补丁可以解决这个问题。

在我的方法“DecodeSkill”中,SkillLevel 是唯一在 .NET 4.6 上中断的部分。 此处使用的变量是从网络流中读取并进行编码的。

DecodeSkill(总是为 SkillLevel 返回正确的解码值)

    private void DecodeSkill()
    {
        SkillId = (ushort) (ExchangeShortBits((SkillId ^ ObjectId ^ 0x915d), 13) + 0x14be);
        SkillLevel = ((ushort) ((byte)SkillLevel ^ 0x21));
        TargetObjectId = (ExchangeLongBits(TargetObjectId, 13) ^ ObjectId ^ 0x5f2d2463) + 0x8b90b51a;
        PositionX = (ushort) (ExchangeShortBits((PositionX ^ ObjectId ^ 0x2ed6), 15) + 0xdd12);
        PositionY = (ushort) (ExchangeShortBits((PositionY ^ ObjectId ^ 0xb99b), 11) + 0x76de);
    }

ExchangeShortBits

    private static uint ExchangeShortBits(uint data, int bits)
    {
        data &= 0xffff;
        return (data >> bits | data << (16 - bits)) & 65535;
    }

DecodeSkill(Patched for .NET 4.6 32-bit,注意“var patch = SkillLevel”)

    private void DecodeSkill()
    {
        SkillId = (ushort) (ExchangeShortBits((SkillId ^ ObjectId ^ 0x915d), 13) + 0x14be);
        var patch = SkillLevel = ((ushort) ((byte)SkillLevel ^ 0x21));
        TargetObjectId = (ExchangeLongBits(TargetObjectId, 13) ^ ObjectId ^ 0x5f2d2463) + 0x8b90b51a;
        PositionX = (ushort) (ExchangeShortBits((PositionX ^ ObjectId ^ 0x2ed6), 15) + 0xdd12);
        PositionY = (ushort) (ExchangeShortBits((PositionY ^ ObjectId ^ 0xb99b), 11) + 0x76de);
    }

将变量指定为 SkillLevel(仅限 32 位)将导致 SkillLevel 始终是正确的值。去掉这个补丁,值总是不正确的。在 64 位中,即使有补丁,这也总是不正确的。

我尝试在解码方法上使用 MethodImplOptions.NoOptimization 和 MethodImplOptions.NoInlining ,认为它会有所作为。

有什么想法会导致这种情况吗?

编辑: 我被要求举一个输入、好输出和坏输出的例子。 这是来自实际使用场景,值是从客户端发送的,并由服务器使用 .NET 4.6 上的“补丁”正确解码。

输入:

ObjectId = 1000001

TargetObjectId = 2778236265
PositionX = 32409
PositionY = 16267
SkillId = 28399
SkillLevel = 8481

良好的输出

TargetObjectId = 0
PositionX = 302
PositionY = 278
SkillId = 1115
SkillLevel = 0

输出错误

TargetObjectId = 0
PositionX = 302
PositionY = 278
SkillId = 1115
SkillLevel = 34545

编辑#2:

我应该包括这部分,这绝对是一个重要的部分。

EncodeSkill(时间戳是 Environment.TickCount)

     private void EncodeSkill()
    {
        SkillId = (ushort) (ExchangeShortBits(ObjectId - 0x14be, 3) ^ ObjectId ^ 0x915d);
        SkillLevel = (ushort) ((SkillLevel + 0x100*(Timestamp%0x100)) ^ 0x3721);
        Arg1 = MathUtils.BitFold32(SkillId, SkillLevel);
        TargetObjectId = ExchangeLongBits(((TargetObjectId - 0x8b90b51a) ^ ObjectId ^ 0x5f2d2463u), 19);
        PositionX = (ushort) (ExchangeShortBits((uint) PositionX - 0xdd12, 1) ^ ObjectId ^ 0x2ed6);
        PositionY = (ushort) (ExchangeShortBits((uint) PositionY - 0x76de, 5) ^ ObjectId ^ 0xb99b);
    }

BitFold32

    public static int BitFold32(int lower16, int higher16)
    {
        return (lower16) | (higher16 << 16);
    }

交换长比特

    private static uint ExchangeLongBits(uint data, int bits)
    {
        return data >> bits | data << (32 - bits);
    }

【问题讨论】:

  • 你能举一个输入数据、好的输出和坏输出的例子吗?
  • 好的,给我 2-3 分钟。
  • @Scotty 你能发一个sscce 来实际重现问题吗?有一些你可以实际编译的东西而不是分析这个伪代码会很有帮助。
  • C# 语言中的这些操作中没有未定义的行为。结果是精确定义的。不管它是什么,它应该总是一样的。这是我在 .NET 4.6 发布后听到的第三个 JIT 错误。请在 CoreCLR 项目的 GitHub 上报告。
  • 调试模式,如果你使用var x = SkillLevel,值会正确。释放模式,您无法更正该值。但 Asad 提供了解决此问题的方法。

标签: c# .net-4.5 bit-shift xor .net-4.6


【解决方案1】:

这是我想出的代码,我认为与您的实际情况类似:

using System;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        var dc = new Decoder();
        dc.DecodeSkill();
        Debug.Assert(dc.TargetObjectId == 0m && dc.PositionX == 302 && dc.PositionY == 278 && dc.SkillId == 1115 && dc.SkillLevel == 0);
    }
}

class Decoder
{
    public uint ObjectId = 1000001;
    public uint TargetObjectId = 2778236265;
    public ushort PositionX = 32409;
    public ushort PositionY = 16267;
    public ushort SkillId = 28399;
    public ushort SkillLevel = 8481;

    public void DecodeSkill()
    {
        SkillId = (ushort)(ExchangeShortBits((SkillId ^ ObjectId ^ 0x915d), 13) + 0x14be);
        SkillLevel = ((ushort)((byte)(SkillLevel) ^ 0x21));
        TargetObjectId = (ExchangeLongBits(TargetObjectId, 13) ^ ObjectId ^ 0x5f2d2463) + 0x8b90b51a;
        PositionX = (ushort)(ExchangeShortBits((PositionX ^ ObjectId ^ 0x2ed6), 15) + 0xdd12);
        PositionY = (ushort)(ExchangeShortBits((PositionY ^ ObjectId ^ 0xb99b), 11) + 0x76de);
    }

    private static uint ExchangeShortBits(uint data, int bits)
    {
        data &= 0xffff;
        return (data >> bits | data << (16 - bits)) & 65535;
    }

    public static int BitFold32(int lower16, int higher16)
    {
        return (lower16) | (higher16 << 16);
    }

    private static uint ExchangeLongBits(uint data, int bits)
    {
        return data >> bits | data << (32 - bits);
    }
}

您将 8481 与 33 进行异或运算。这是 8448,这是我在我的机器上看到的。假设,SkillLevelushort,我认为发生的事情是你期望转换为byte 以截断SkillLevel,这样剩下的就是最后 8 位,但这不是正在发生,所以当你转换回ushort 时,高阶位仍然存在。

如果您想可靠地截断低 8 位之后的所有数字,您需要像这样对其进行位掩码:

SkillLevel = ((ushort) ((SkillLevel & 255) ^ 0x21));

编辑:

我怀疑这与运营商的数字促销有关。 ^ 运算符,当应用于 byte ushortint 时,会将两个操作数提升为 int,因为隐式转换存在于两种可能的类型int 的第一个操作数。似乎正在发生的事情是从 ushortbyteexplicit 转换,这会导致截断,被跳过。现在你只有两个ints,当异或时,它们被截断回ushort,保留它们的高位。

【讨论】:

  • 所以在某些机器(包括我的 32 位 4.5 机器)上,转换为字节的工作如他所料,但不是低于 4.6?规范中有什么变化吗?还是实现行为发生了变化?
  • 这是假的。在 C# 语言中的这些操作中没有未定义的行为。结果是精确定义的。不管它是什么,过去 10 年的所有 CLR 都应该始终相同。
  • 另外,他的 34545 是从哪里来的?
  • 英雄!谢谢 :) 位掩码是答案,显然是从 .NET 4.5-4.6 开始将值重新装箱为字节更改
  • 似是而非。跳过截断强制转换的损坏优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多