Last active
August 29, 2015 14:10
-
-
Save Porges/868916d1a39a6b93960b to your computer and use it in GitHub Desktop.
smuggling data in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.CodeDom; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Runtime.Remoting.Messaging; | |
using System.Runtime.Serialization; | |
using System.Threading; | |
// The situation: | |
// There is an untouchable class that wraps some instance that we can provide. | |
// In order for our instance to do its work, it needs some more data to be | |
// passed to it (but not at construction time). (To make this less easy, | |
// the data we need to pass is an object with behaviour, it is not just some value.) | |
// However, we can't modify the 'untouchable' class to allow us to pass this data. | |
// Here is the 'untouchable' class. | |
sealed class Untouchable<TArg> | |
{ | |
// Pretend TArg is fixed, it's just generic so I only have to write one instance. | |
private readonly IDoIt<TArg> _inner; | |
public Untouchable(IDoIt<TArg> inner) { _inner = inner; } | |
public void DoIt(TArg arg) => _inner.DoIt(arg); | |
} | |
// The interface we're implementing: | |
interface IDoIt<in TArg> | |
{ | |
void DoIt(TArg arg); | |
} | |
// And the data we want to be able to access : | |
class ThingIWantToSmuggle | |
{ | |
private int _counter; | |
public ThingIWantToSmuggle(int i) { _counter = i; } | |
public void Hello() => Console.WriteLine("Hello! " + _counter++); | |
} | |
public static class Smuggler | |
{ | |
// 1) | |
// If we can pass through a subclass, it's easy! | |
class Base { } | |
class DerivedSmuggler : Base | |
{ | |
public ThingIWantToSmuggle Instance; | |
} | |
class UseSubclass : IDoIt<Base> | |
{ | |
public void DoIt(Base arg) => | |
((DerivedSmuggler)arg).Instance.Hello(); | |
} | |
private static void Smuggle1(ThingIWantToSmuggle budgie) | |
{ | |
new Untouchable<Base>(new UseSubclass()) | |
.DoIt(new DerivedSmuggler { Instance = budgie }); | |
} | |
// 2) | |
// What if the data class is sealed? Let's pass it in a global (static) variable... | |
sealed class SealedBase { } | |
class UseStaticVariable : IDoIt<SealedBase> | |
{ | |
public static ThingIWantToSmuggle Instance; | |
public void DoIt(SealedBase arg) => | |
Instance.Hello(); | |
} | |
private static void Smuggle2(ThingIWantToSmuggle budgie) | |
{ | |
UseStaticVariable.Instance = budgie; | |
new Untouchable<SealedBase>(new UseStaticVariable()).DoIt(new SealedBase()); | |
} | |
// 3) | |
// But this is going to be chaos if we have multiple threads... | |
// Let's magically attach the data to what we're passing: | |
class UseConditionalWeakTable : IDoIt<SealedBase> | |
{ | |
public static readonly ConditionalWeakTable<SealedBase, ThingIWantToSmuggle> Mapper | |
= new ConditionalWeakTable<SealedBase, ThingIWantToSmuggle>(); | |
public void DoIt(SealedBase arg) | |
{ | |
ThingIWantToSmuggle value; | |
if (Mapper.TryGetValue(arg, out value)) | |
{ | |
value.Hello(); | |
} | |
} | |
} | |
static void Smuggle3(ThingIWantToSmuggle budgie) | |
{ | |
var sb = new SealedBase(); | |
UseConditionalWeakTable.Mapper.Add(sb, budgie); | |
new Untouchable<SealedBase>(new UseConditionalWeakTable()).DoIt(sb); | |
} | |
// 4) | |
// But what if the value is a struct... | |
struct Structy { } | |
// ... or the untouchable generates a new instance of SealedBase? | |
// We can pass a named argument with CallContext. | |
class UseCallContext : IDoIt<Structy> | |
{ | |
public void DoIt(Structy arg) => | |
((ThingIWantToSmuggle)CallContext.GetData("budgie")).Hello(); | |
} | |
static void Smuggle4(ThingIWantToSmuggle budgie) | |
{ | |
CallContext.SetData("budgie", budgie); | |
try | |
{ | |
new Untouchable<Structy>(new UseCallContext()).DoIt(new Structy()); | |
} | |
finally | |
{ | |
CallContext.FreeNamedDataSlot("budgie"); // clean up after ourselves | |
} | |
} | |
// 5) | |
// What if this is not perverse enough? | |
// Imagine that the data we're passing has an integer field: | |
class WithAField | |
{ | |
public long Data; | |
} | |
// we can store a GCHandle in an integer and fetch the object from that: | |
class UseGCHandle : IDoIt<WithAField> | |
{ | |
public void DoIt(WithAField arg) | |
{ | |
var handle = GCHandle.FromIntPtr(new IntPtr(arg.Data)); | |
var budgie = (ThingIWantToSmuggleWithData)handle.Target; | |
budgie.Hello(); | |
// and we can get the actual data for that field from our struct: | |
var actualData = budgie.RealData; | |
Console.WriteLine("was passed " + actualData); | |
} | |
} | |
struct ThingIWantToSmuggleWithData | |
{ | |
public ThingIWantToSmuggleWithData(long data) : this() { RealData = data; } | |
public long RealData; | |
public void Hello() { Console.WriteLine("Hello from " + GetType()); } | |
} | |
static void Smuggle5() | |
{ | |
{ | |
var handle = GCHandle.Alloc(new ThingIWantToSmuggleWithData(12345)); | |
try | |
{ | |
new Untouchable<WithAField>(new UseGCHandle()) | |
.DoIt(new WithAField { Data = GCHandle.ToIntPtr(handle).ToInt64() }); | |
// note that this is actually what you do when you need to supply | |
// a void* to a callback from native code | |
} | |
finally | |
{ | |
handle.Free(); | |
} | |
} | |
} | |
// 6) | |
// What if we want to smuggle the GCHandle inside something that has only a string field... | |
class WithAString | |
{ | |
public string Data; | |
} | |
// .. *and* the string field is validated so we can't put weird stuff into it? [NB: we don't validate it here, but print it instead] | |
class UseGCHandleHiddenInString : IDoIt<WithAString> | |
{ | |
public unsafe void DoIt(WithAString arg) | |
{ | |
fixed (char* smuggling = arg.Data) | |
{ | |
var len = arg.Data.Length; | |
var chars = new[] { smuggling[len + 1], smuggling[len + 2], smuggling[len + 3], smuggling[len + 4] }; | |
fixed (char* cs = chars) | |
{ | |
var bs = (byte*)cs; | |
var handleValue = BitConverter.ToInt64(new[] { bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7] }, 0); | |
var budgie = (ThingIWantToSmuggleWithData)GCHandle.FromIntPtr(new IntPtr(handleValue)).Target; | |
budgie.Hello(); | |
var actualData = budgie.RealData; | |
Console.WriteLine("was passed " + actualData); | |
} | |
} | |
} | |
} | |
private unsafe static void Smuggle6() | |
{ | |
var handle = GCHandle.Alloc(new ThingIWantToSmuggleWithData(54321)); | |
try | |
{ | |
var x = "<the real string>"; | |
Console.WriteLine("The string is: " + x + "."); | |
fixed (byte* chars = BitConverter.GetBytes(GCHandle.ToIntPtr(handle).ToInt64())) | |
{ | |
x += "\0" + new string((char*)chars, 0, 4); // 4 chars = 1 long | |
} | |
fixed (char* c = x) | |
{ | |
// Tell the string it's not as long as it thinks it is: | |
var length = ((int*)c) - 1; | |
*length -= 5; | |
try | |
{ | |
Console.WriteLine("The string is (still): " + x + "."); | |
new Untouchable<WithAString>(new UseGCHandleHiddenInString()) | |
.DoIt(new WithAString { Data = x }); | |
} | |
finally | |
{ | |
// better restore this, or else. | |
*length += 5; | |
} | |
} | |
} | |
finally | |
{ | |
handle.Free(); | |
} | |
} | |
// 7) | |
// Here's an alternate solution to the "sealed base class" problem, when | |
// the base class isn't sealed but has a private/internal constructor: | |
class PrivateBase | |
{ | |
private PrivateBase() | |
{ | |
} | |
} | |
class UseZombieObject : IDoIt<PrivateBase> | |
{ | |
public void DoIt(PrivateBase arg) | |
{ | |
var zombie = (EvilDerived)arg; | |
zombie.Smuggling.Hello(); | |
} | |
} | |
class EvilDerived : PrivateBase | |
{ | |
public EvilDerived() : this(Asplode()) { } | |
private EvilDerived(int i) : this() { } | |
public ThingIWantToSmuggle Smuggling; | |
private static int Asplode() | |
{ | |
throw new StackOverflowException(); | |
} | |
public static EvilDerived Instance; | |
~EvilDerived() { Instance = this; } | |
} | |
private static void Smuggle7(ThingIWantToSmuggle budgie) | |
{ | |
try | |
{ | |
new EvilDerived(); | |
} | |
catch (StackOverflowException) | |
{ | |
// ^ we know we threw this or we wouldn't be able to catch it | |
while (EvilDerived.Instance == null) | |
{ | |
// NB: don't try this with the debugger attached | |
GC.Collect(); | |
GC.WaitForPendingFinalizers(); | |
} | |
var derived = EvilDerived.Instance; | |
derived.Smuggling = budgie; | |
new Untouchable<PrivateBase>(new UseZombieObject()).DoIt(derived); | |
} | |
} | |
// 8) | |
// Or, if you have the right security permissions, you can simply: | |
class LessEvilButStillPrettyEvil : PrivateBase | |
{ | |
private LessEvilButStillPrettyEvil() : this(1) { } | |
private LessEvilButStillPrettyEvil(int i) : this() { } | |
public ThingIWantToSmuggle Instance; | |
} | |
class UseLessEvilObject : IDoIt<PrivateBase> | |
{ | |
public void DoIt(PrivateBase arg) | |
{ | |
var zombie = (LessEvilButStillPrettyEvil)arg; | |
zombie.Instance.Hello(); | |
} | |
} | |
private static void Smuggle8(ThingIWantToSmuggle budgie) | |
{ | |
var easyDerived = (LessEvilButStillPrettyEvil)FormatterServices.GetUninitializedObject(typeof(LessEvilButStillPrettyEvil)); | |
easyDerived.Instance = budgie; | |
new Untouchable<PrivateBase>(new UseLessEvilObject()).DoIt(easyDerived); | |
} | |
// 9) | |
// Another fun way to pass data is using Thread.Abort: | |
class UseThreadAbort : IDoIt<Structy> | |
{ | |
public void DoIt(Structy arg) | |
{ | |
try | |
{ | |
Thread.Sleep(Timeout.Infinite); | |
} | |
catch (ThreadAbortException ex) | |
{ | |
var budgie = (ThingIWantToSmuggle)ex.ExceptionState; | |
budgie.Hello(); | |
Thread.ResetAbort(); // NB: this must happen after you access ExceptionState | |
} | |
} | |
} | |
static void Smuggle9(ThingIWantToSmuggle budgie) | |
{ | |
var outer = new Untouchable<Structy>(new UseThreadAbort()); | |
var t = new Thread(() => outer.DoIt(new Structy())); | |
t.Start(); | |
Thread.Sleep(1); | |
t.Abort(budgie); | |
t.Join(); | |
} | |
// 10) | |
// Alternately, if we're allowed free reign with the string, | |
// and we have a struct to pass... we can fudge the runtime type: | |
class JustDoIt : IDoIt<WithAString> | |
{ | |
public static int StructIWantToSmuggleTypeHandle; | |
public unsafe void DoIt(WithAString arg) | |
{ | |
// overwrite the type handle | |
// this is just about the most dangerous thing I can think of doing in .NET | |
var budgie = (object)arg.Data; | |
var handle = GCHandle.Alloc(budgie, GCHandleType.Pinned); | |
try | |
{ | |
Console.WriteLine("Before: " + budgie.GetType()); | |
// typehandle is the 4 bytes before the start of the object | |
var typeHandle = ((int*)handle.AddrOfPinnedObject()) - 2; | |
// save the old one | |
var oldTypeHandle = *typeHandle; | |
// overwrite the typehandle | |
*typeHandle = StructIWantToSmuggleTypeHandle; | |
Console.WriteLine("After: " + budgie.GetType()); | |
// now we can unbox it to the type we want | |
var unboxed = (StructIWantToSmuggle)handle.Target; | |
unboxed.Hello(); | |
// restore old type handle | |
*typeHandle = oldTypeHandle; | |
} | |
finally | |
{ | |
handle.Free(); | |
} | |
} | |
} | |
struct StructIWantToSmuggle | |
{ | |
public int Value; | |
public void Hello() | |
{ | |
Console.WriteLine("Hello, " + Value); | |
} | |
} | |
unsafe static void Smuggle10() | |
{ | |
var s = new StructIWantToSmuggle { Value = 6789 }; | |
var size = Marshal.SizeOf(s); | |
var str = new string('x', size); | |
fixed (char *c = str) | |
{ | |
var handle = GCHandle.Alloc(s, GCHandleType.Pinned); | |
try | |
{ | |
var source = (byte*)handle.AddrOfPinnedObject(); | |
var dest = (byte*)c; | |
JustDoIt.StructIWantToSmuggleTypeHandle = *(((int*)source) - 1); | |
for (int i = 0; i < size; ++i) | |
{ | |
// string data starts 4 bytes earlier than where the pointer is | |
dest[i-4] = source[i]; | |
} | |
new Untouchable<WithAString>(new JustDoIt()) | |
.DoIt(new WithAString { Data = str }); | |
// restore length just in case | |
*(((int*)dest) - 1) = size; | |
} | |
finally | |
{ | |
handle.Free(); | |
} | |
} | |
} | |
// Run them all: | |
public static void Main() | |
{ | |
var budgie = new ThingIWantToSmuggle(0); | |
Smuggle1(budgie); | |
Smuggle2(budgie); | |
Smuggle3(budgie); | |
Smuggle4(budgie); | |
Smuggle5(); | |
Smuggle6(); | |
Smuggle7(budgie); | |
Smuggle8(budgie); | |
Smuggle9(budgie); | |
Smuggle10(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment