Sunday, April 24, 2005

 

Some notes on Fully Trusted Code and Reflection

1.
作者:Flier Lu
出处:http://flier_lu.blogone.net/?id=1601113

Keith Brown在4月份的MSDN杂志上发表了一篇讨论.NET下安全性的文章,Beware of Fully Trusted Code,其中详细讨论了让 Managed Code 允许在 Fully Trusted 模式下的危害。真是不看不知道,呵呵,现在我是深深感到了 Fully Trusted Code 的可怕。

对一个类型的私有成员变量来说,其私有性的保护实际上只是编译期的

以下为引用:

class DiskQuota {
private:
long MinBytes;
long MaxBytes;
};

以上类型的私有成员变量实际上可以简单的通过unsafe代码中的指针操作访问

以下为引用:

void EvilCode(DiskQuota* pdq) {
// use pointer arithmetic to index
// into the object wherever we like!
((long*)pdq)[1] = MAX_LONG;
}

好在CLR能够通过类型系统完整性检测,一定程度上解决这类有意或缓冲区溢出的问题,除非在程序中显式的要求CLR关闭此类检测。如

以下为引用:

// evilcode.cs
using System.Security.Permissions;

[assembly: SecurityPermission(
SecurityAction.RequestMinimum,
Flags=SecurityPermissionFlag.SkipVerification)]

// your evil code goes here

麻烦的是一旦使用/unsafe参数编译C#程序,或者使用Managed C++编写程序,这个权限就被隐式地设置为最低,以保障代码地正确执行。虽然新版本的C#编译器将提供参数选项打开此权限,但最根本的解决方法还是得通过.NET安全策略来完成。可惜在缺省状态下,本地硬盘上的Managed程序都是在 Fully Trusted 模式下运行的。
可以通过控制面板的管理工具中的"Microsft .NET Framework 1.1配置"工具,在 我的电脑\运行库安全策略\计算机\代码组\All_Code 上通过 编辑代码组属性 看到当前的安全策略设置。安全策略一般分为企业、计算机和用户三级,每级又可根据一定的代码组条件分为多个代码组,设置不同的权限集。
我们接下来看看 Fully Trusted 权限的“威力”所在吧 :P

首先是所谓的私有方法,众所周知它们是可以通过Reflection被直接调用的,呵呵,如下:

以下为引用:

using System;
using System.Reflection;

class EvilCodeWithFullTrust
{
static void CallPrivateMethod(object o, string methodName) {
Type t = o.GetType();
MethodInfo mi = t.GetMethod(methodName,
BindingFlags.NonPublic |
BindingFlags.Instance);
mi.Invoke(o, null);
}
static void Main() {
CallPrivateMethod(new NuclearReactor(), "Meltdown";
}
}

比较好的解决方法是通过检测调用者是否被信任,来保障私有函数不被滥用,如使用StrongNameIdentityPermission属性限制调用者所在assembly必须有相同的public key,可以参见我以前的一篇 BLog 文章《关于信任粒度的讨论》

以下为引用:

public class NuclearReactor {
[StrongNameIdentityPermission(
SecurityAction.LinkDemand,
PublicKey="002400000..."]
private void Meltdown() {
// calling assembly must have specified public key!
}
}

这样可以禁止直接通过Reflection访问私有成员函数或变量。但如果调用者代码具有 Fully Trusted 权限的话,它可以直接使用命令行工具 caspol -s off 或者通过代码,完全关闭 StrongNameIdentityPermission 进行的检测,呵呵

以下为引用:

using System.Security;
class EvilCodeWithFullTrust {
static void Main() {
SecurityManager.SecurityEnabled = false;
// now call Meltdown via reflection!
}
}

改变SecurityManager.SecurityEnabled的值需要SecurityPermissionFlag.ControlPolicy权限,对应于配置中安全性下的允许策略控制权限, Fully Trusted 情况下是打开的

既然系统自动的调用者验证可以被跳过,是否能通过程序手工进行验证呢?以下代码在调用不可完全信任的插件之前,通过降低权限来限定插件的能力,可以说这是一种非常好的安全编程习惯。

以下为引用:

using System.Security;
using System.Security.Permissions;

class WellMeaningCode {
public void CallPlugIn(EvilCode plugin) {
// put a CAS modifier on the stack that denies all file system access
new FileIOPermission(
PermissionState.Unrestricted).Deny();
plugin.DoWork();
CodeAccessPermission.RevertDeny();
}
}

但是在 Fully Trusted 权限下还是无能为力,直接对PermissionSet.Assert函数的调用使得权限检查再一次被跳过。

以下为引用:

class EvilCodeWithFullTrust {
void DoWork() {
new PermissionSet(
PermissionState.Unrestricted).Assert();
// happily access the file system
// regardless of the caller''s deny!
}
}

前面提到配置工具中对安全策略的配置有企业、机器和用户三层,实际上还有AppDomain这一层,可以通过AppDomain.SetAppDomainPolicy载入单独的安全策略限定其后需要执行的代码,ASP.NET就是通过这种方法限定执行代码的权限。但是具有 Fully Trusted 权限的代码可以打破 AppDomain 边界的限制,并可以通过调用其他 Unmanaged Code 实现对其他 AppDomain 侵入。好在可以通过修改 machine.config 文件让 ASP.NET 运行在较低的权限集中,如

以下为引用:

configuration>
system.web>
securityPolicy>
trustLevel name="Full" policyFile="internal" />
trustLevel name="High" policyFile="web_hightrust.config" />
trustLevel name="Medium" policyFile="web_mediumtrust.config" />
trustLevel name="Low" policyFile="web_lowtrust.config" />
trustLevel name="Minimal" policyFile="web_minimaltrust.config" />
/securityPolicy>
!-- level="[Full|High|Medium|Low|Minimal]" -->
trust level='Medium'/>
/system.web>
/configuration>

但因为很多程序员没有耐心完成最小权限集的调整工作,导致大部分情况下为了省事将缺省权限设置为 Fully Trusted,导致上述各种精心设计的安全检测都形同虚设。再好的技术如果没有相应的管理来保障,只是摆设而已。
解决这个问题,一方面需要程序员对其代码的权限做限定和调整,另一方面需要开发厂商提供静态和动态的权限分析工具辅助,最后还得配合传统的NT权限设置来把关,毕竟CLR还是运行在NT的用户的权限集下。

有兴趣的朋友可以进一步阅读这篇文章:Writing managed code for semi-trusted environment

此外 Brown 还是 Programming Windows Security 一书的作者,他在书中详细介绍了 Windows 环境下的安全子系统的运行机制和使用方法,并一定程度上涉及到了网络认证、COM+和Internet环境下的安全性问题,非常详尽值得一读。此书已经由电力出版社翻译出版 《Windows安全性编程》。

2.
Writing managed code for semi-trusted environment

By (c) Ivan Medvedev 2003
From http://www.dotnetthis.com/Articles/WritingForSEE.htm

Say what?

In .NET’s CLR Microsoft has introduced the concept of semi-trusted code – a code that can do some things and can not do other things, for example – read files but not the registry. Well, the concept was not exactly new J, but the .NET implementation, IMO, got it right and it is actually very usable. In 3 words the CLR security system works like this:

- A loader loads an assembly

o It collects information about the assembly called the “evidence”. There are 5 major types of evidence that built into the system – Url, Zone, Site, StrongName, Publisher; evidence system is extendable and there could be custom types of evidence, too.

o Once the evidence it gathered it is fed into the security policy system, which maps a set of evidence to a set of permissions.

o That set of permissions is called an “assembly grant set”, and it is assigned to each assembly when it is loaded.

- A protected resources is accessed – e.g. a file open operation is attempted

o The FileStream class constructor creates an object of FileIOPermission describing what file and how it being accessed and does .Demand() on it.

o This initiates an operation called a “stack walk”. Stack walk verifies that every method on the stack has the permission to open the file, by looking at the method’s assembly grant set.

o If any of the methods on the stack belongs to an assembly whose grant set does not include FileIOPermission, a SecurityException is thrown. This prevents something called “luring attacks” (do I remind you of Dr. Evil?), where lower trusted code orchestrates highly trusted code into doing bad things.

o There are things called “stack walk modifiers” – Assert(), Deny() and PermitOnly(). They make the security system much more flexible, but also make it easier for a trusted library writer to screw up.



If you had to write your own FileStream class you would have to use an Assert() in addition to a Demand(). The Assert() would stop a check for the permission to call into unmanaged code (which you will have to call because you need Win32 APIs to do files) – you wouldn’t want your callers to necessarily have that permission, you only want them to have the permission to work with files.



If you haven’t worked with code access security before the above is probably confusing (it took me about a year to fully and deeply understand this stuff J). If you want to know more about it, there are a couple of good books – “.NET Framework Security” by Brian LaMacchia et al, “Writing Secure Code 2nd edition” by Michael Howard (look me up in the acknowledgements section J), also “Visual Basic .NET Code Security Handbook” by Eric Lippert is great for VB.NET people.

Also, Greg Fee has recently written a good article on stack walk in his blog.

The Problem

Anyway, semi-trusted environment is when your application does not get a full set of permissions that would allow it to do everything. If you look at the default security policy (run “.NET Framework Configuration” from the control panel or “caspol –l” if you are a hardcode command line guy), you can kind of see that everything coming from zone MyComputer has FullTrust (can do everything) and stuff coming from Internet gets a permission set named “Internet” (we will look at it in detail). Assemblies coming from Intranet or network shares get LocalIntranet permission set – that is why you get a security exception when running your favorite application (whose creator didn’t read this article) off a mapped drive (read Shawn’s blog on how you can fix their mess J).



If you have looked at the default Internet permission set in caspol tool output you have seen a bunch of xml goo that represents it. I will try to translate it for you. So, as an application coming from the Internet (in v1.1 of the Framework), you are entitled to:

- Open files the user has explicitly pointed you to (FileDialogPermission – Open);

- Use up to 10240 bytes of Isolated Storage (a cool feature, I will probably write another entry just about that in the future), with isolation by application (no cross app talking) – IsolatedStorageFilePermission;

- You can actually execute code (didn’t expect it? J) – SecurityPermission – Execution;

- You can display safe top level windows - you won’t be able to fully control the window title, it is going to say “Internet” and then the site name – UIPermission - SafeTopLevelWindows;

- You can use your own application clipboard (no passing stuff between applications) – UIPermission – OwnClipboard

- You can print (only with user’s explicit approval) – PrintingPermission – SafePrinting;

- You can read web contents originating from the same location as your application - Same Site Web.



Not much, is it? Now, if we take LocalIntranet, it adds the following:

- You can read ‘USERNAME’ environment variable (EnvironmentPermission);

- You can read and save files with explicit user’s approval (FileDialogPermission);

- You can use all the IsolatedStorage space you want, and you can use isolation by assembly – applications can share data this way (IsolatedStorageFilePermission);

- You can dynamically create assemblies in memory (ReflectionPermission – ReflectionEmit);

- You can redirect assembly bindings (SecurityPermission – BindingRedirects);

- You can assert permissions (use .Assert() method) (SecurityPermission – Assertion);

- You can display any kind of windows with full control over them and you can fully use the clipboard (UIPermission – Unrestricted);

- You can use DNS services (DnsPermission);

- You can print with the default printing settings without user’s explicit approval (PrintingPermission – DefaultPrinting);

- You can access Event Log (EventLogPermission);

- You can read files from the directory of your origin and its subdirectories, and you can enumerate files/folders there (Same directory FileIO - Read, PathDiscovery).





Now, how in the world I can write code that works with these restrictions?

Well, that’s the hard part. First there are some things that make your code un-runnable in default semi-trusted environment for Internet or Intranet right away:

- Unverifiable code: C# code with unsafe blocks or compiled with /unsafe option (you will have it, for example, if you are using pointers), any C++ code, code written in IL that uses unverifiable constructs (see ECMA specs);

- Corrupted code or code changed by some obfuscators that made it unverifiable;

- Code that has declarative assembly permission requests for more trust than the security policy gives them (you can see assembly requests with permview.exe tool);

- Code that calls into unmanaged code (by using native COM or DllImport attribute);

- Code using classes/methods that require permissions outside of the security sandbox (for example Reflection, some file i/o, Registry, etc.). The documentation that comes with the Framework tries to document the security requirements for all APIs, however you may here and there stumble upon a method where the doc does not list its real requirements.



(Note that we are talking about the default security policy here. Remember that you can always adjust policy on the client machine to give more permissions to a certain assembly).



It is in fact very hard to figure out whether your code is going to work off the Internet or Intranet at the development stage without trying it out. In the next release of the .NET Framework we are working very hard to make this analysis possible. Meanwhile, trying and failing is the most reliable method. To make it easier you can use something called “assembly permission requests”. You can put a declarative attribute like the following in your assembly to ensure that no more permissions are granted to it, even if it is loaded from the local drive – this will help you catch sandbox violations easier:



[assembly: FileIOPermissionAttribute(SecurityAction.RequestOptional, Unrestricted=true)]

[assembly: SecurityPermissionAttribute(SecurityAction.RequestOptional, Execution=true)]



The attribute above will ensure that your assembly only gets FilIOPermission and the permission to execute. It is really a good practice to have those assembly requests on your assemblies in general; by using them you are limiting your liability – imagine your library has a security hole that would allow attackers to make it do bad things you didn’t intend it to do – having a self-restriction like this may help to avoid such situation.



I am sure you will get a sense of what’s allowed and what is not pretty soon.



I have done something you can call a case study of this problem by trying to implement a useful tool that would work in Internet or Intranet environment. I have created an assembly metadata parser and IL disassembler (you can think of it as a mini-version of ILDASM), along with a simple managed code obfuscator. You can find them at http://www.dotnetthis/Samples/mangler.htm, and I encourage you to check them out because they are really cool. So, as you can see the exercise was successful and I got some nice apps out of it and some knowledge to share with you.



Some problems I hit while developing these applications and their solutions.

Writing a metadata parser / IL disassembler is a problem in itself, and doing it in partially-trusted code is even harder. First, you can not use Reflection (not that it helped much anyway) or unmanaged metadata reading APIs (like IMetaDataImport), so you have to parse the PE files from scratch. Then, you can not use pointers to walk through the memory and pull bytes. Then, when you found the data you are interested in you can not just map it to a struct and be done (in fully trusted code you could use something like Marshal.PtrToStructure).



So let me give you a few specific examples.



Reading files from disk

In Internet/Intranet sandbox you can not just open a file and read its contents – you will get a security exception. However if you look closer at the set of allowed permissions you can see a FileDialogPermission there. This basically means that you can display an OpenFileDialog, and if the user selects a file, you can grab an already opened Stream object off of it. The code goes like this:



// create an OpenFileDialog instance

OpenFileDialog openFileDialog = new OpenFileDialog();

// see if the user picked a file

if (openFileDialog.ShowDialog() != DialogResult.OK)

return;

// check if the file actually exists

if (!openFileDialog.CheckFileExists)

return;

// open the file for read and grab the stream

FileStream fs = (FileStream)openFileDialog.OpenFile();

// read the contects into a byte array

byte[] array = new byte[fs.Length];

fs.Read(array, 0, (int)fs.Length);



This will get you the contents of a file the user has picked.



Saving a file to disk

Saving file to disk is accomplished approximately the same way, except you use a SaveFileDialog. Intranet zone, by default, gets the permission to use SaveFileDialog. Internet zone however, does not. That is why in my sample obfuscator, if you are running it off the Internet, you won’t be able to save the obfuscated files (you will be able to view them though) unless you adjust the policy to give my application something less restrictive, for example LocalIntranet permission set. Another thing about SaveFileDialog is that with the default policy you are not going to get permissions to control the dialog caption, so in case you have to save multiple files, you won’t be able to say “Saving file X” in the title. As you can see, in my sample I am using the main control window’s status bar for it. This restriction is done to prevent evil people from confusing you by trustworthy-looking window captions.



Calling into your other components

If your application consists of several assemblies it is important that it is able to load them and run them. Both Internet and Intranet zone in the default v1.1 policy give assemblies the permission to connect back to the server of origin to fetch components – this is needed for loading referenced libraries. If you are on v1.0, your Internet zone may get not permission at all and you will have to grant some permissions to it manually, including the permission to read things from the server if your application has multiple assemblies.

Another important thing that I mentioned is being able to run. If your assembly is strong name signed and you call into it from your partially trusted application this will not work unless you have AllowPartiallyTrustedCallersAttribute on it:



[assembly: AllowPartiallyTrustedCallersAttribute]





Working with binary data

As I already mentioned, parsing metadata is a lot of work involving walking binary data structures. In plain C I would just cast a pointer to byte to a pointer to a structure describing a certain metadata header and I have easy access to the fields. In unsafe managed code I would use Marshal class to do essentially the same. However in security sandboxed codes I don’t have that luxury and I need to invent my own way to make it easier. What I have come up with in my application is a pseudo-pointer class that makes it easier for me to emulate pointer operation C-style. It goes something like this (some error checking and other parts are omitted for better clarity):



public struct SafePointer

{

internal byte[] _array;

internal int _index;



// ...



public static implicit operator byte(SafePointer pp)

{

return pp._array[pp._index];

}



public static explicit operator UInt16(SafePointer sp)

{

return (ushort)(((byte)(sp+1)<<8) | (byte)(sp));

}



// ... other conversion skipped



public static SafePointer operator +(SafePointer pp, int n)

{

return new SafePointer(pp._array, pp._index+n);

}



// ... skipped



public static SafePointer operator ++(SafePointer sp)

{

sp._index++;

return sp;

}



// ... the rest of operators and methods skipped

}



A class like this allows a very convenient pointer operations, for example:



SafePointer spBase = new SafePointer(array, 0);



SafePointer spTemp = spBase + offset;

while(spTemp < spEnd)

{

Console.WriteLine((UInt16)spTemp);

spTemp+=2;

}



This code will read 2 bytes words from a block of memory and print them.



I then created a universal Header class that, given information about the fields, such as their names, offsets and length, would accept a SafePointer and let you read the values of the fields based either on the field name or its index. It was then very easy to subclass that class and come up with customized header classes for reading various kinds of headers living in the metadata. Same deal for metadata tables and so forth.

This gave me a very powerful and flexible design which allowed many different things. If you saw a MetaInfo sample in the Tools Developers Guide, I wrote a similar tool using the library I have created, and the whole tool was less than 2 screens of code.



Checking your permissions

When writing for semi-trust, it is important for the application to be aware of the permissions it has, so that the user will not get an ugly security exception dialog. For example, I mentioned above that the default policy gives permissions to display a SaveFileDialog to Intranet, but not to Internet. If you try to invoke this functionality while working off the Internet, a SecurityException will be thrown. A more graceful way to deal with this kind of situation is checking your permissions. To do that you should create an instance of the permission class that describes the resource you are trying to access and then you have two options:

- Call its Demand() methods and prepare to catch a SecurityException. If you caught it that means you (or your callers) do not have the permission.

- Do SecurityManager.IsGranted(…). This will tell you if the permission is granted to your assembly.

There is an important difference between the two. The first approach initiates a stack walk and will check all the callers; it imitates what happens when you actually use the resource and is more accurate. The second approach will check your assembly’s permissions, but not its callers’. If you are working within the default security policy the 2nd approach is good – it is fast and it will give you the right result. The 1st approach you will need if you adjust the security policy to give your assembly more trust, in this case you want to check all the callers to make sure the thing will actually work (and if it does not you may want to use Assert() to stop the stack walk in your assembly).

Here is the code I used to check for save file dialog permission:



FileDialogPermission fdp = new FileDialogPermission(FileDialogPermissionAccess.Save);

if (!SecurityManager.IsGranted(fdp))

{

MessageBox.Show("Saving files is not allowed"

return;

}

// do stuff

Getting the performance right

Writing fast semi-trusted code is a big problem in itself. When you need to process large amounts of data and are unable to use unsafe code your performance is going to be affected. First of all, you will likely have to use boxing/unboxing (casting to/from Object), which is expensive. Then, you won’t be able to use some processor/memory-friendly optimization. To give you an example, I have looked at two different functions comparing byte arrays. The first function works in semi-trust and goes through the arrays byte by byte. The second function uses unsafe code blocks, pointers and casts 8-byte chunks of data to UInt64, which are then compared. The second function is more than 3 times faster than the 1st. Finally, when you run FullTrust, there is a lot of performance optimizations in the security system that skip security checks when possible. In semi-trusted environment such as Internet or Intranet much less optimizations are possible and the security checks that are happening when you use some APIs will potentially take more time. Don’t try to turn the security off though, because if you do, next time you visit my obfuscator sample I will tell it to obfuscate your whole disk and nothing is going to stop it! J

3.
作者:jobs
出处:http://www.blogcn.com/user9/szujobs/index.html?dd=2004-6-6

如何调用只有私有构造函数的类

当我试用ObjectSpaces时,ObjectSpaces竟然能够调用只有私有构造函数的类。例如:
以下内容为程序代码:

Class A
{
  private A() {}
}

ObjectSpaces能够创建A的实例,我刚看到的时候,吃了一惊,呵呵…… 后来,借助Reflector分析整理学会了此技巧。

你不能通过Reflection直接创建只有私有构造函数的类,但是你可以通过一些偏门技巧绕过此限制,亦可谓之奇淫技巧。何为奇淫技巧?
其大概思路这样的:
以下内容为程序代码:

Type[] paramTypeArray = new Type[] {};
Type rtnType = ...;
String methodName = ...;
bool skipVisibility = true;
Module module = ...;
DynamicMethod method = new DynamicMethod(
    methodName,
    rtnType, //returnType
    paramTypeArray, //parameterTypes
    module,
    skipVisibility
    );

ILGenerator ilGen = method.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, ctor);

以上是思路,我们也来写一段代码,让其能够调用缺省构造函数创建对象实例,具体代码:

第一步, 定义一个Delegate:
以下内容为程序代码:

public delegate T CreateInstanceDelegate();

第二步,定义构建Delegate的方法,关键在此:
以下内容为程序代码:

private CreateInstanceDelegate BuildDelegate()
{
  Type type = typeof(T);

  AssemblyName assemblyName = new AssemblyName();
  assemblyName.Name = "System.Data.ObjectSpaces.Dynamic";

  AssemblyBuilder assemblyBuilder
    = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

  Module module
    = assemblyBuilder.DefineDynamicModule("WebData", "DynamicAssembly.dll", true);

  Type[] paramTypeArray = new Type[] {};
  Type rtnType = type;
  String methodName = "call_privateCtor";
  bool skipVisibility = true;

  DynamicMethod method = new DynamicMethod(
      methodName,
      rtnType,
      paramTypeArray,
      module,
      skipVisibility
      );

  ConstructorInfo ctor = type.GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
        null,
        new Type[0],
        null
        );

  ILGenerator ilGen = method.GetILGenerator();
  ilGen.Emit(OpCodes.Newobj, ctor);
  ilGen.Emit(OpCodes.Ret);

  return (CreateInstanceDelegate ) method.CreateDelegate(typeof(CreateInstanceDelegate ));
}

第三步,定义创建实例的方法:
以下内容为程序代码:

public T CreateInstance()
{
  CreateInstanceDelegate createInstDelegate = BuildDelegate();
  return createInstDelegate();
}

第四步,如此使用:
定义一个私有缺省构造函数的类
以下内容为程序代码:

class A
{
  private A() {}
}

创建实例的代码:
以下内容为程序代码:

A a = CreateInstance();
Console.WriteLine("create A instance" );

如此奇淫技巧,就能够绕过可见性访问限制构造对象。

4.
发信人: flier (小海 [寻找风车中]), 信区: DotNET
标 题: Re: 如何调用只有私有构造函数的类
发信站: BBS 水木清华站 (Mon Jun 7 21:27:53 2004), 站内

昨天和jobs讨论了一下,ms这么做应该是为了提高调用时的效率
如果只是要完成功能的话,用refection足以,Full Trusted 代码是无敌的 :P

using System;
using System.Reflection;

namespace PrivCon
{
public class Demo
{
string _name;

private Demo(string name)
{
_name = name;
}

private void SayHello()
{
Console.WriteLine("Hello " + _name);
}
}

class EntryPoint
{
[STAThread]
static void Main(string[] args)
{
ConstructorInfo con = typeof(Demo).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic, null,
new Type[] { typeof(string) }, null);

if(con != null)
{
Console.WriteLine("call " + con.ToString());

object obj = con.Invoke(new object[] { "Flier Lu" });

MethodInfo method = typeof(Demo).GetMethod("SayHello",
BindingFlags.Instance | BindingFlags.NonPublic,
null, new Type[0], null);

if(method != null)
{
Console.WriteLine("call " + method.ToString());

method.Invoke(obj, new object[0]);
}
}
}
}
}

5.
作者:hush
出处:http://www.cnblogs.com/hush/archive/2004/04/29/8064.aspx

今天在微软新闻组里,看到有人问对于以下这个类:

class Test
{
public Test()
{
throw new Exception("Can not use this constructor";
}
public void Hello()
{
Console.WriteLine("hello World";
}
}
如何成功调用它的Hello方法。

这个类的构造函数里会扔出异常,而类实例化的时候会自动调用构造函数,这样用普通的方法我们永远也得不到一个可以成功调用方法的类的实例。那么有什么办法,可以突破构造函数这个限制?

用反射似乎不行,因为反射要生成一个实例,也还是要调用构造函数,那么有什么其它方法吗?

呵呵,请原谅我卖了那么长时间的关子,好东西总要留到最后,不是吗? 

其实,我们可以这样做:

class main
{
static void Main(string[] args)
{
//此处获得了一个未经初始化的实例(没有调用构造函数)
Test t = (Test)System.Runtime.Serialization.FormatterServices.GetUninitializedObject( typeof(Test) ;
t.Hello();
}
}

是不是有点奇技淫巧的感觉? 嘿嘿

我第一次看到这个方法是在思归呓语上的一个帖子(在思归的blog上找了半天,也没找到原帖,还是用goolge才找到了....)

btw,思归的帖子: http://blog.joycode.com/saucer/posts/407.aspx

6.
发信人: hBifTs (Programing?Reverse:Crack...), 信区: DotNET
标 题: FormatterServices 好用的东东..
发信站: BBS 水木清华站 (Mon May 24 19:32:39 2004), 站内


原文: http://www.cnblogs.com/hbifts/archive/2004/05/23/11035.aspx

在前面一个博客园的hush的文章如何不调用构造函数而获得一个类的实例!中,我们知道了使用System.Runtime.Serialization.FormatterServices.GetUninitializedObject可以得到一个没有调用构造函数的对象..

在得到这个对象后,那么如何在不继续调用构造函数的情况下面对其Private成员/其父类的Private成员进行赋值呢??

呵呵,你可能会说,使用Reflection.对,Reflection是可以得到Private Field..
好,给你下面的一个代码.你能否通过Reflection,给Father类中的objFather赋值?
class Father{
private object objFather;
public Father( object obj){
objFather = obj;
}

public object GetFather(){
return objFather;
}
}

class Son : Father{
private object objSon ;
public Son( object objSon,object objFather) : base(objFather){
this.objSon = objSon;
}
}
object obj1 = new object();
object obj2 = new object();
Son x = new Son( obj1,obj2);
Son t = (Son)System.Runtime.Serialization.FormatterServices.GetUninitializedObject( typeof(Son) ;
......//你的代码.
t.GetFather() == obj2
即,你通过什么方式让上面的等式成立??



呵呵..
Reflection不行吧..
这里,我们如果要想完成上面的任务,就得使用FormatterServices这个类了~
通过MSDN,我们知道他还有这两个函数 一个,GetObjectData,得到一个Object里的所有的Field的值.
PopulateObjectMembers,给一个Object中的所有的Field赋值..

哈哈.好了,就用这两个函数.
代码如下:
FieldInfo[] fields = typeof( Father).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
object[] objfields = FormatterServices.GetObjectData(x,fields);
t= FormatterServices.PopulateObjectMembers(t,fields,objfields);

到这里,上面的等式就可以成立了:P


这里面其实是通过Serialize函数来完成的. 所谓的GetObjectData应该就是相当于把这个对象 序列化,这样就可以得到其中的Field的值了.然后再通过反序列化的手段 PopulateObjectMembers(...) 把所得到的数据再放到我们自己的对象中~

--

just for fun~~~~

山自高兮水自深!當塵霧消散,唯事實留傳

Welcome to My Blog We Miss you~ : http://www.cnblogs.com/hBifTs


※ 来源:·BBS 水木清华站 smth.org·[FROM: 218.197.209.*]

补充:
还是把思归老大的帖子也贴一下

在.NET中,怎么生成一个只有私有(private)构造函数的类的对象?譬如象这样的类

class PrivateClass
{
public string Name;
public int Age;
private PrivateClass()
{
Name = "not initialized";
Age = 0;
}
}

先想一想,再看一个答案


using System;
using System.Reflection;

Type t = typeof(PrivateClass);
ConstructorInfo[] cis = t.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
PrivateClass ptest = (PrivateClass)cis[0].Invoke(null);
Console.WriteLine("{0} {1}", ptest.Name, ptest.Age);

另一个答案
作者:Scott Hanselman (按:这家伙也是CP上的老人啦)
出处:http://www.hanselman.com/blog/PermaLink.aspx?guid=410

using System;
namespace Corillian.Testing
{
class PrivateClass
{
public string Name;
public int Age;
private PrivateClass()
{
Name = "not initialized";
Age = 0;
}
}

class Test
{
static void Main(string[] args)
{
/// The following statement will not work as the constructor is private
/// PrivateClass newpTest = new PrivateClass();
/// But you can create the object through Serialization
PrivateClass ptest = (PrivateClass)System.Runtime.Serialization.FormatterServices.GetUninitializedObject( typeof(PrivateClass) ;
ptest.Name = "Scott";
ptest.Age = 0x1D;

Console.WriteLine( String.Format("{0} {1}",ptest.Name,ptest.Age ;
}
}
}
6/10/2004修改

补充:
flier老大指出

btw:用Delegate.InternalAlloc也可以完成相同功能
static public object InternalAlloc(Type type)
{
foreach(MethodInfo method in
typeof(Delegate).GetMethods(BindingFlags.NonPublic | BindingFlags.Static))
{
if(method.Name == "InternalAlloc"
{
return method.Invoke(null, new object[] { type });
}
}
throw new Exception("static method InternalAlloc is not a member of class Delegate";
}

(Test)InternalAlloc(typeof(Test));
6/11/2004修改



<< Home

This page is powered by Blogger. Isn't yours?