Sunday, July 22, 2007

Hack read only properties and fields using reflection

Today I got really confusing question: "Is it possible to change readonly property?". The first question, I asked is: "Why to do it?". The next thought was: "This is dammed interesting, whether is possible". Let's see...

One thing is sure - we can not do it regular way, but we can do it for sure by using reflection. Let's create the class itself

class TestReadOnly  { 

public readonly int ReadOnlyField = 10;

readonly int m_readOnlyProperty = 20;

public int ReadOnlyProperty { get { return m_readOnlyProperty; } } }


Now, let's see what values we have

TestReadOnly tro1 = new TestReadOnly(); 

Console.WriteLine("Field value: {0}, Property value: {1}",tro1.ReadOnlyField, tro1.ReadOnlyProperty);


Well, the results are as expected: 10 and 20. The next step is to change them from external class. Is it possible? Yes, it is. The magic word is "reflection". We'll read the read only field by using reflection

Type t = typeof(TestReadOnly); 

FieldInfo fi = t.GetField("ReadOnlyField");


Now, we'll just set its value.

fi.SetValue(tro1, 50);


Well, well, well. It works. Just works. You can change field value by using reflection, even if the field is read only (actually, it is not a lot of sense to du such thing).

Now, the next step of our challenge. Change the read only property. This might be tricky, 'cos actually, there is no setter at all in IL level. Let's try

PropertyInfo pi = t.GetProperty("ReadOnlyProperty"); 

Object[] arg = new Object[0];

pi.SetValue(tro1, 60, args);


Too bad, we caught ArgumentException. It's clear, 'cos there are actually no code we can execute this way. But, if we'll look into Reflector, we can find the private read only field and set it as we did in our previous example.

PropertyInfo pi = t.GetProperty("ReadOnlyProperty"); 

Object[] arg = new Object[0];

try { pi.SetValue(tro1, 60, args); }

catch (ArgumentException e) {

fi1 = t.GetField("m_readOnlyProperty",

BindingFlags.Instance | BindingFlags.NonPublic);

fi1.SetValue(tro1, 60);



Now it works. Cool, we changed read only property of managed object. And what's about unmanaged code? Let's try to do the same thing with Outlook Appointment.

To access System.__ComObject (the real object in underlying model), we can not use regular GetMember method (due to the fact, that, actually, there are no managed methods there). But, we can invoke methods (note, that property getters and setters are actually methods ued to set values). How to do it? Simple. First of all, let's create boring outlook stuff

Microsoft.Office.Interop.Outlook.Application applicationObject = new Microsoft.Office.Interop.Outlook.ApplicationClass(); 

MAPIFolder calendarFolder = applicationObject.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderCalendar);

Items appointments = calendarFolder.Items;


Now, let's iterate appointments to get it's underlying objects. For each appointment we'll get read only LastModificationTime property

Microsoft.Office.Interop.Outlook.AppointmentItem appointment = (Microsoft.Office.Interop.Outlook.AppointmentItem)item; 

Console.WriteLine("COM Field: {0}", appointment.LastModificationTime);

//appointment.LastModificationTime is read only


Now, let's invoke it's setter (that, actually does not exists)


BindingFlags.Default | BindingFlags.SetProperty, null,

new object[]

DateTime.Now });


As expected, we got an exception. But this time, it's TargetInvokationException (we invoke it, remember). What to do? Not a lot. Look and seek OLEViewer to figure where set_LastModificationTime occurs and invoke it with new params. I have neither time, nor wish to do it, but you can. See the very beginning of this post. We should figure what actually happens in order to be able to change it. With unmanaged code it's much harder, that with managed. But it's possible.

Have a nice day.

Ah, don't you forgot to release all this stuff?

finally  { 

Console.WriteLine("COM Field: {0}", appointment.LastModificationTime);

//time to cleanup

Marshal.ReleaseComObject(appointment); }

//Just stop it from propagating

break; }

Marshal.ReleaseComObject(item); }





:) Good programming.

No comments: