在C#编译的exe文件中修改嵌入字符串

12

我有一个问题,需要一个已编译的exe文件(.net 3.5 c#),我要复制它以分发,但在发送之前需要更改密钥。

我不能每次需要新的exe时都进行编译。这是一个轻客户端,将作为注册过程的一部分使用。

是否可以在资源文件中添加一个空值条目,然后当请求到来时,另一个应用程序获取空白默认的轻客户端,对其进行复制,并填充所需数据的空白值。

如果可以,怎么做?如果不行,你有什么想法吗?我已经苦思冥想了几天,由于我必须工作在规定的边界内,所以存在限制。

我想到的另一个想法是将值注入到方法中,但我甚至不知道如何尝试。

谢谢。


这是什么目的?了解这一点可能会帮助我们更好地帮助您。 - Robert Harvey
你好,Robert,我需要将一个轻量级客户端exe分发给x个用户。然而,在发送之前,这个客户端需要能够动态注入密钥。按需编译不起作用。每个发送出去的客户端都有其自己的唯一标识符。我不能使用app.config文件,因为文件必须是自包含的(另一个要求),因此我开始查看资源文件,因为密钥必须被嵌入其中。谢谢。 - nitefrog
这个文件需要签名吗? - Lasse V. Karlsen
我觉得可能是,但如果你有其他选择,我全听着。谢谢。 - nitefrog
6个回答

6

不要将密钥嵌入程序集中,而是将其放在app.config文件(或与应用程序一起提供的另一个文件)中,并防止应用程序在密钥不存在或无效时运行。为了防止用户修改它,还应在配置文件中添加RSA签名。

以下代码可用于生成包含密钥的XML。

public static void Main()
{
   Console.WriteLine(GenerateKey());
}

public static Byte[] Transform(Byte[] bytes, ICryptoTransform xform)
{
   using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
   {
      using (CryptoStream cstream = new CryptoStream(stream, xform, CryptoStreamMode.Write))
      {
         cstream.Write(bytes, 0, bytes.Length);
         cstream.Close();
         stream.Close();
         return stream.ToArray();
      }
   }
}

public static string GenerateKey()
{
   RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
   // This is the private key and should never be shared.
   // Generate your own with RSA.Create().ToXmlString(true).
   String rsaPrivateKey = "<RSAKeyValue><Modulus>uPCow37yEzlKQXgbqO9E3enSOXY1MCQB4TMbOZyk9eXmc7kuiCMhJRbrwild0LGO8KE3zci9ETBWVVSJEqUqwtZyfUjvWOLHrf5EmzribtSU2e2hlsNoB2Mu11M0SaGd3qZfYcs2gnEnljfvkDAbCyJhUlxmHeI+35w/nqSCjCk=</Modulus><Exponent>AQAB</Exponent><P>4SMSdNcOP0qAIoT2qzODgyl5yu9RubpIU3sSqky+85ZqJHXLUDjlgqAZvT71ROexJ4tMfMOgSWezHQwKWpz3sw==</P><Q>0krr7cmorhWgwCDG8jmzLMo2jafAy6tQout+1hU0bBKAQaPTGGogPB3hTnFIr84kHcRalCksI6jk4Xx/hiw+sw==</Q><DP>DtR9mb60zIx+xkdV7E8XYaNwx2JeUsqniwA3aYpmpasJ0N8FhoJI9ALRzzp/c4uDiuRNJIbKXyt6i/ZIFFH0qw==</DP><DQ>mGCxlBwLnhkN4ind/qbQriPYY8yqZuo8A9Ggln/G/IhrZyTOUWKU+Pqtx6lOghVdFjSxbapn0W8QalNMFGz7AQ==</DQ><InverseQ>WDYfqefukDvMhPHqS8EBFJFpls/pB1gKsEmTwbJu9fBxN4fZfUFPuTnCIJsrEsnyRfeNTAUFYl3hhlRYZo5GiQ==</InverseQ><D>qB8WvAmWFMW67EM8mdlReI7L7jK4bVf+YXOtJzVwfJ2PXtoUI+wTgH0Su0IRp9sR/0v/x9HZlluj0BR2O33snQCxYI8LIo5NoWhfhkVSv0QFQiDcG5Wnbizz7w2U6pcxEC2xfcoKG4yxFkAmHCIkgs/B9T86PUPSW4ZTXcwDmqU=</D></RSAKeyValue>";

   rsa.FromXmlString(rsaPrivateKey);
   String signedData = "<SignedData><Key>Insert your key here</Key></SignedData>";
   Byte[] licenseData = System.Text.Encoding.UTF8.GetBytes(signedData);
   Byte[] sigBytes = rsa.SignData(licenseData, new SHA1CryptoServiceProvider());
   String sigText = System.Text.Encoding.UTF8.GetString(Transform(sigBytes, new ToBase64Transform()));
   System.Text.StringBuilder sb = new StringBuilder();
   using (System.Xml.XmlWriter xw = System.Xml.XmlTextWriter.Create(sb))
   {
      xw.WriteStartElement("License");
      xw.WriteRaw(signedData);
      xw.WriteElementString("Signature", sigText);
      xw.WriteEndElement();
   }
   return sb.ToString();
}

这段代码的示例输出:
<?xml version="1.0" encoding="utf-16"?>
<License>
  <SignedData>
    <Key>Insert your key here</Key>
  </SignedData>
  <Signature>cgpmyqaDlHFetCZbm/zo14NEcBFZWaQpyHXViuDa3d99AQ5Dw5Ya8C9WCHbTiGfRvaP4nVGyI+ezAAKj287dhHi7l5fQAggUmh9xTfDZ0slRtvYD/wISCcHfYkEhofXUFQKFNItkM9PnOTExZvo75pYPORkvKBF2UpOIIFvEIU=</Signature>
</License>

然后您可以使用以下代码来验证它。您永远不需要分发私钥:
public static Boolean CheckLicenseSignature(String licXml)
{
   try
   {
      System.Xml.XmlDocument xd = new System.Xml.XmlDocument();
      xd.LoadXml(licXml);
      String licSig = xd.SelectSingleNode("/License/Signature").InnerText;
      RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
      String rsaPublicKey = "<RSAKeyValue><Modulus>uPCow37yEzlKQXgbqO9E3enSOXY1MCQB4TMbOZyk9eXmc7kuiCMhJRbrwild0LGO8KE3zci9ETBWVVSJEqUqwtZyfUjvWOLHrf5EmzribtSU2e2hlsNoB2Mu11M0SaGd3qZfYcs2gnEnljfvkDAbCyJhUlxmHeI+35w/nqSCjCk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
      rsa.FromXmlString(rsaPublicKey);
      Byte[] licenseData = System.Text.Encoding.UTF8.GetBytes(xd.SelectSingleNode("/License/SignedData").OuterXml);
      return rsa.VerifyData(licenseData, new SHA1CryptoServiceProvider(), Transform(System.Text.Encoding.UTF8.GetBytes(licSig), new FromBase64Transform()));
   }
   catch (System.Xml.XmlException ex)
   {
      return false;
   }
   catch (InvalidOperationException ex)
   {
      return false;
   }
}

1
再次强调,该文件必须是自包含的。我不能使用任何依赖文件,但如果我能有另一个文件附带,那将是一个非常棒的解决方案。同时,密钥必须可以被读取,因为加密是不需要的。在安全性方面还有另一个过程来加固安全性。我只需要一个与exe相关联的唯一标识符,并且它需要嵌入到我发送出去的每个exe中。 - nitefrog
我认为你可能在解决错误的问题,因为我认为在许多情况下,应用程序的配置文件会随可执行文件自动传输。而且,如果有必要,还可以将多个文件打包成一个文件。但是,如果这些问题比你接受的解决方案更难处理,那么肯定要选择最省力的方法。 - BlueMonkMN
此外,此代码不会加密密钥。整个XML都是明文,只有在结尾添加的签名是无法理解的。 - BlueMonkMN

6
将汇编代码转换为IL代码,进行文本搜索和替换,再将IL代码重新编译为汇编代码。使用.NET SDK中的标准工具

丹和阿米,我刚刚发布了这个问题:http://stackoverflow.com/questions/2749113/compile-il-code-at-runtime-using-net-3-5-and-c-from-file我将采取ildasm和ilasm的方法,但是你们知道如何自动化此过程,而不必使用process.start命令行吗? - nitefrog

1
接受的答案是垃圾! 我已经成功地完成了这个。更容易 只需将需要密钥的基本应用程序(.net)与填充有"XXXXXXXXXXXXXXX"(超过您所需的)的字符串资源放在某个地方即可。
.Net资源通常保存在代码顶部,因此您会快速找到它们,跳过我的情况下的前100,000字节。
然后,您只需读取它并查找那些XXXXXX。当您找到它们时,将它们替换为真实的API密钥,并将其余的X替换为您在代码中修剪掉的空格。这就是答案。它有效且运行良好。
        ApiToken at = new ApiToken(UserId, SelectedCID);
        at.MakeToken();

        byte[] app = System.IO.File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.GetData("DataDirectory").ToString(), "notkeyedapp.exe"));

        for (int i = 100000; i < app.Length; i++)
        {
            if (app[i] == 0x58 && app[i + 1] == 0x58 && app[i + 2] == 0x58)
            {
                for (int j = 0; j < 128; j++)
                {
                    if (at.Token.Length >= j + 1)
                        app[i + j] = System.Text.Encoding.ASCII.GetBytes(at.Token[j].ToString())[0];
                    else
                        app[i + j] = 0x20;

                }
                break;
            }
        }
        string filename = "SoftwareProduct for - " + BaseModel.CompanyName.Replace(".", "") + ".exe";
        return File(app, System.Net.Mime.MediaTypeNames.Application.Octet, filename);

1

从.NET代码本身的能力来看,我不确定这是否可行。但是可以动态生成一个包含某些关键字的.NET DLL,该关键字可以从主应用程序中引用。也就是说,如果您不介意在分发中使用第二个文件。

或者,如果您不介意使用Ildasm来反汇编.exe,更改密钥,然后使用Ilasm重新组装,那么您可以做一些自动化操作。


如果您将其转换为il并再次转换,是否会在转换回来后对签署exe造成任何问题。该过程需要自动化。我无论如何都无法让UpdateResource正常工作。它可以修改文件,但无法覆盖名称值... - nitefrog
您可以使用ilasm /key=keyfile参数在重新组装exe时对其进行签名。 - Amry
我刚刚尝试并创建了一个IL,问题是资源文件信息没有显示在任何地方。我可以看到名称但看不到值。有人成功使用UpdateResource吗? - nitefrog

1

我认为您无法在不重新编译.exe文件并将密钥嵌入到该.exe文件中的情况下获得成功。但是,可以通过使用ildasm.exe和ilasm.exe自动化编译过程,正如Daniel Earwicker在他的回答https://dev59.com/EXE85IYBdhLWcg3wgDzd#2742902中建议的那样。

如果有人在未来遇到类似问题,我想进一步扩展这个话题。

最近,由于我的源代码版本控制习惯不佳,我也面临着类似的问题。简而言之,我有一个可执行文件,应该通过引用其ID将一些数据写入Google电子表格。在发布可执行文件后不久,另一个团队提出了另一个请求,要求使用该工具,但必须将相同的信息写入不同的电子表格,以便为两个团队保留数据。当时我没有原始源代码,因此无法更改保存原始电子表格ID的静态变量。我所做的是:

  1. 使用CMD.exe → call "C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\ildasm.exe" "myApplication.exe" /out="myApplication.il"
  2. 使用Notepad++ → 在myApplication.il文件中查找并替换原始ID为新ID。此操作也可以通过编写自己的C#应用程序来自动完成,或者使用PowerShell,或者使用vb/j-script或使用其他现成的查找和替换工具,如FART(使用CMD.exe → call fart.exe myApplication.il "OldKey" "NewKey"
  3. 使用CMD.exe → call "C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" "myApplication.il" /res="myApplication.res" /key="myApplicationKeyFile.snk"

正如您所看到的,所有这些步骤都可以放入一个 .bat 文件中,该文件以 "NewKey" 作为输入并生成嵌入有 NewKey 的新 .exe。

希望这能有所帮助。


-1

我想到的,但尚未尝试的方法是:在程序中创建一个默认字符串,例如

static public string regGuid = "yourguidhere";

然后,使用任何好的十六进制编辑器搜索编译后的EXE文件。如果找到了该字符串,将其替换为另一个测试字符串。如果您仍然可以执行程序,您可以尝试自动化这个过程,然后就完成了。

2
我不知道无符号程序集是否具有任何类型的校验和,但我认为对于已签名程序集将会失败,因为签名将不再有效。 - Jon Skeet
这就是问题所在,无法保证密钥每次都是相同的长度。因此需要使用可变长度的密钥。 - nitefrog
只要你分配的空间比键需要的空间(例如40个字符,并在插入时用空格填充键),那就不是问题。但签名会成为问题。 - Jason Williams
不起作用。我尝试了这个,但没有找到字符串:public static readonly string ops = "HAHAHA"; - Sergio Cabral

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接