-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathProgram.cs
1345 lines (1160 loc) · 70.8 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Threading;
namespace AppDomainExample
{
class Program
{
public static byte[] DllBytes = File.ReadAllBytes(@"C:\Path\To\SharpSploit.dll");
public static Regex GenericTypeRegex = new Regex(@"^(?<name>[\w\+]+(\.[\w|\+]+)*)(\&*)(\**)(`(?<count>\d))?(\[(?<subtypes>.*?)\])(,\s*(?<assembly>[\w\+]+(\.[\w|\+]+)*).*?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
public static AppDomain GetNewAppDomain(Guid Id)
{
AppDomainSetup appDomainSetup = new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
};
PermissionSet appDomainPermissions = new PermissionSet(PermissionState.Unrestricted);
return AppDomain.CreateDomain(Id.ToString(), null, appDomainSetup, appDomainPermissions, null);
}
public static IAssemblySandbox GetAssemeblySandbox(AppDomain appDomain)
{
Type assmeblySandboxType = typeof(AssemblySandbox);
return (IAssemblySandbox)appDomain.CreateInstanceFromAndUnwrap(assmeblySandboxType.Assembly.Location, assmeblySandboxType.FullName);
}
static void Main()
{
// Creating a lil break here for those following along with a debugger.
Console.WriteLine("Press Return to start.");
Console.ReadLine();
// The next three commented lines of code can be uncommented, for debugging purposes.
// Check loaded Assemblies before loading SharpSploit into the main AppDomain.
//Assembly[] AssembliesBeforeLoad = AppDomain.CurrentDomain.GetAssemblies();
// Load the SharpSploit DLL into the main AppDomain, for funsies.
//Assembly SharpSploit = Assembly.Load(DllBytes);
// Check loaded Assemblies after loading SharpSploit into the main AppDomain.
//Assembly[] AssembliesAfterLoad = AppDomain.CurrentDomain.GetAssemblies();
// When creating a new AppDomain, a friendly name is required, for later reference.
// GUID's are used to avoid any sort of creative thought or telling name.
Guid SandboxId = Guid.NewGuid();
// Create the AppDomain using the above GUID as the friendly name.
AppDomain appDomain = GetNewAppDomain(SandboxId);
// Use this new AppDomain to get an interface to the AssemblySandbox execution class.
IAssemblySandbox assmeblySandbox = GetAssemeblySandbox(appDomain);
// Place a breakpoint in AssemblySandbox.CheckLoadedAssemblies() and uncomment below to verify the Assembly isn't loaded.
//assmeblySandbox.CheckLoadedAssemblies();
// ## Demo #1: Loading a DLL into the AssemblySandbox, and calling a method from the Assembly.
Console.WriteLine("\r\n-- Demo #1 -------------------------------------------------------------------");
// We're going to load the SharpSploit DLL and call the non-static method SharpSploit.Credentials.Tokens.WhoAmI().
// This isn't the best way to do this (see Demo #3), but it covers how both static and non-static Methods are called.
// It's also a product of me developing something for a specific, arbitrary use-case, and I need to rework how calls
// to non-static methods are made.
// Load SharpSploit into the AssemblySandbox, with name "SharpSploit".
Console.WriteLine("[*] Loading the SharpSploit DLL...");
assmeblySandbox.Load("SharpSploit", DllBytes);
// Place a breakpoint in AssemblySandbox.CheckLoadedAssemblies() and uncomment below to now verify the Assembly was loaded.
//assmeblySandbox.CheckLoadedAssemblies();
// Execute the non-static method WhoAmI from the loaded instance of SharpSploit, in the AssemblySandbox.
Console.WriteLine("[*] Executing the non-static SharpSploit.Credentials.Tokens.WhoAmI() method from the SharpSploit DLL...");
byte[] serializedReturnValueWhoAmI = assmeblySandbox.ExecuteMethod("SharpSploit", "SharpSploit.Credentials.Tokens", "WhoAmI", null, null, null, null);
// Deserialize the return value. We are assuming the return value is a Type understood in this main AppDomain (i.e. string)
var whoAmI = Deserialize(serializedReturnValueWhoAmI);
// Print deserialized value, and hit return for next demo.
Console.WriteLine("[+] WhoAmI Results: \"{0}\"\r\n", whoAmI);
Console.WriteLine("Press Return to continue...");
Console.ReadLine();
// ## Demo #2: Passing commonly-Typed variables to a method in the AssemblySandbox.
Console.WriteLine("\r\n-- Demo #2 -------------------------------------------------------------------");
// We're going to do a simple, static String.Join() on a string-array, akin to String.Join(",", ["a", "b"]) => "a,b"
// It's important to understand that this is only possible because we're passing basic Types and calling Methods that exist,
// by default, in both application domains; all Types and Methods referenced can be resolved in each domain, given the
// assemblies currently loaded.
// First, define the types of the specific Method we want to call.
List<string> MethodTypes = new List<string>() { "System.String", "System.String[]" };
// We want to join the strings "a" and "b" with a delimiter of ",".
List<object> MethodParameters = new List<object>() { ",", new string[] { "a", "b" } };
// Call the Method on the parameters, and get the serialized result.
Console.WriteLine("[*] Executing \'System.String.Join(\",\", new string[] { \"a\", \"b\" })\' within the AssemblySandbox AppDomain...");
byte[] serializedReturnValueJoin = assmeblySandbox.ExecuteMethod(null, "System.String", "Join", null, null, MethodTypes.ToArray(), MethodParameters.ToArray());
// Deserialize the result into an Object of a Type that we're assuming to be a String.
object actualReturnValueJoin = Deserialize(serializedReturnValueJoin);
Console.WriteLine("[+] Join result: \"{0}\"", actualReturnValueJoin);
Console.WriteLine("Press Return to continue...");
Console.ReadLine();
// ## Demo #3: Storing and referencing variables and return values in the AssemblySandbox.
Console.WriteLine("\r\n-- Demo #3 -------------------------------------------------------------------");
// We're going to store a Typed value in the AssemblySandbox's variable Dictionary, call a Method on it, and store
// the return value in the AssemblySandbox's variable Dictionary. This is probably the correct way to call a method
// on an instance of a potentially unknown class (a class that only exists within the AssemblySandbox).
// We're creating a string-array, of size 5, and calling SetValue("asdf", 0), setting the first element to "asdf".
// First, define the variable Types of the string-array constructor we want (new string[5]).
List<string> ConstructorTypes = new List<string>() { "System.Int32" };
// Create a new string-array, of size 5, and store it as a variable named "stringArrayVar" in the AssemblySandbox.
// If everything initializes correctly, AssmeblySandbox.ConstructNewObject returns true.
Console.WriteLine("[*] Creating a new string[5] and storing it as variable \"stringArrayVar\" inside the AssemblySandbox AppDomain...");
bool successfullyCreated = assmeblySandbox.ConstructNewObject("System.String[]", "stringArrayVar", ConstructorTypes.ToArray(), new object[] { 5 });
// Execute SetValue on our stored string-array "stringArrayVar", setting the object at index 0 to "asdf", and store
// the return value as a new variable named "returnVar" (which will be null because SetValue returns void).
// AssmeblySandbox.ExecuteMethodOnVariable returns True if the Method successfully executed on the variable.
Console.WriteLine("[*] Executing \'returnVar = stringArrayVar.SetValue(\"asdf\", 0)\' within the AssemblySandbox AppDomain...");
bool executeMethodWasSuccessful = assmeblySandbox.ExecuteMethodOnVariable("SetValue", "stringArrayVar", "returnVar", new string[] { "System.Object", "System.Int32" }, new object[] { "asdf", 0 });
// Get and deserialize variable "stringArrayVar", which should become a string-array with the Object at index 0 set to "asdf'.
string[] stringArrayReturnValue = (string[])Deserialize(assmeblySandbox.GetVariable("stringArrayVar"));
Console.WriteLine("stringArrayReturnValue type: {0}", stringArrayReturnValue.GetType());
Console.WriteLine("stringArrayReturnValue[0]: \"{0}\"\r\n", stringArrayReturnValue[0]);
// AssmeblySandbox.GetVariableInfo returns a formatted string, displaying info about the stored variables.
string variableInfo = assmeblySandbox.GetVariableInfo();
Console.WriteLine("Variables Stored in the AssemblySandbox:\r\n========================================");
Console.WriteLine(variableInfo);
Console.WriteLine();
// This is probably how Demo #1 should have been done - creating a new instance of Token, storing it as a variable in
// the AssemblySandbox, and calling the WhoAmI Method on the variable.
// We're donezo. Unload the AppDomain that the AssemblySandbox was using.
Console.WriteLine("Press Return to unload the AssemblySandbox's AppDomain...");
Console.ReadLine();
AppDomain.Unload(appDomain);
// Place a breakpoint in AssemblySandbox.CheckLoadedAssemblies() and uncomment below to verify everything is gone.
//assmeblySandbox.CheckLoadedAssemblies();
Console.WriteLine("All done. Press Return to exit...");
Console.ReadLine();
}
public static object Deserialize(byte[] byteArray)
{
try
{
BinaryFormatter binForm = new BinaryFormatter
{
Binder = new BindChanger()
};
using (var memoryStream = new MemoryStream())
{
memoryStream.Write(byteArray, 0, byteArray.Length);
memoryStream.Seek(0, SeekOrigin.Begin);
return binForm.Deserialize(memoryStream);
}
}
catch
{
return null;
}
}
public static byte[] Serialize(object objectToSerialize)
{
try
{
BinaryFormatter serializer = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
serializer.Serialize(memoryStream, objectToSerialize);
return memoryStream.ToArray();
}
}
catch
{
return null;
}
}
public class BindChanger : System.Runtime.Serialization.SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
return ReconstructType(string.Format("{0}, {1}", typeName, assemblyName), false);
}
}
public static Type ReconstructType(string typeAssemblyQualifiedName, bool throwOnError = false, params Assembly[] referencedAssemblies)
{
Type type = null;
// If no assemblies were provided, then there wasn't an attempt to reconstruct the type from a specific assembly.
// Check if the current app domain can be used to resolve the requested type (this should be 99% of calls for resolution).
if (referencedAssemblies.Count() == 0)
{
type = Type.GetType(typeAssemblyQualifiedName, throwOnError);
if (type != null)
return type;
// If it made it here, populate an array of assemblies in the current app domain.
referencedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
}
// If that failed, attempt to resolve the type from the list of supplied assemblies or those in the current app domain.
foreach (Assembly asm in referencedAssemblies)
{
type = asm.GetType(typeAssemblyQualifiedName.Replace($", {asm.FullName}", ""), throwOnError);
if (type != null)
return type;
}
// If that failed and the type looks like a generic type with assembly qualified type arguments, proceed with constructing a generic type.
// TODO: follow the below TODO in ConstructGenericType because this if statement probably isn't accurate enough.
Match match = GenericTypeRegex.Match(typeAssemblyQualifiedName);
if (match.Success && !string.IsNullOrEmpty(match.Groups["count"].Value))
{
type = ConstructGenericType(typeAssemblyQualifiedName, throwOnError);
if (type != null)
return type;
}
// At this point, just returns null;
return type;
}
private static Type ConstructGenericType(string assemblyQualifiedName, bool throwOnError = false, params Assembly[] referencedAssemblies)
{
/// Modified the functionality of the regex and type resolution logic when handling cases like:
/// 1: an assembly-qualified generic type
/// A: with only normal type arguments
/// B: with only assembly-qualified type arguments
/// C: with a mixture of both normal and assembly-qualified type arguments
/// 2: a generic type
/// A: with only normal type arguments
/// B: with only assembly-qualified type arguments
/// C: with a mixture of both normal and assembly-qualified type arguments
///
/// I think it's possible to have a type with normal and assembly-qualified arguments, but I'm not sure.
/// I'm also not skilled enough to develop test cases for each of the scenarios addressed here.
/// Reference: https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype?view=netframework-3.5
///
Match match = GenericTypeRegex.Match(assemblyQualifiedName);
if (!match.Success)
return null;
string typeName = match.Groups["name"].Value.Trim();
string typeArguments = match.Groups["subtypes"].Value.Trim();
// If greater than 0, this is a generic type with this many type arguments.
int numberOfTypeArguments = -1;
if (!string.IsNullOrEmpty(match.Groups["count"].Value.Trim()))
{
try
{
numberOfTypeArguments = int.Parse(match.Groups["count"].Value.Trim());
}
catch { };
}
// I guess this attempts to get the default type for a type of typeName for a given numberOfTypeArguments.
// Seems to work on commonly configured.
if (numberOfTypeArguments >= 0)
typeName = typeName + $"`{numberOfTypeArguments}";
Type genericType = ReconstructType(typeName, throwOnError, referencedAssemblies);
if (genericType == null)
return null;
//List<string> typeNames = new List<string>();
List<Type> TypeList = new List<Type>();
int StartOfArgument = 0;
int offset = 0;
while (offset < typeArguments.Length)
{
// All type arguments are separated by commas.
// Parsing would be easy, except square brackets introduce scoping.
// If a left square bracket is encountered, start parsing until the matching right bracket is reached.
if (typeArguments[offset] == '[')
{
int end = offset;
int level = 0;
do
{
switch (typeArguments[end++])
{
// If the next character is a left square bracket, the beginning of another bracket pair was encountered.
case '[':
level++;
break;
// Else if it's a right bracket, the end of a bracket pair was encountered.
case ']':
level--;
break;
}
} while (level > 0 && end < typeArguments.Length);
// 'offset' is still the index of the encountered left square bracket.
// 'end' is now the index of the closing right square bracket.
// 'level' should be back at zero (meaning all left brackets had closing right brackets). Else there was a formatting error.
if (level == 0)
{
// Adding 1 to the offset and subtracting two from the substring length will get a substring without the brackets.
// Check that the substring length, sans the enclosing brackets, would result in a non-empty string.
if ((end - offset - 2) > 0)
{
// If the start of the first type argument was the left square bracket, this argument is an assembly-qualified type.
// Example: MyGenericType`1[[MyType,MyAssembly]]
if (StartOfArgument == offset)
{
try
{
TypeList.Add(ReconstructType(typeArguments.Substring(offset + 1, end - offset - 2).Trim(), throwOnError, referencedAssemblies));
}
catch
{
return null;
}
}
// Else a square bracket was encountered on a generic type argument.
// Example: MyGenericType`1[AnotherGenericType`2[MyType,AnotherType]]
else
{
try
{
TypeList.Add(ReconstructType(typeArguments.Substring(StartOfArgument, end - StartOfArgument).Trim(), throwOnError, referencedAssemblies));
}
catch
{
return null;
}
}
}
}
// Set the offset and StartOfArgument to the position of the discovered right square bracket (or the end of the string).
offset = end;
StartOfArgument = offset;
// Decrement the number of type arguments
numberOfTypeArguments--;
}
// Else if a comma is encountered without hitting a left square bracket, a normal type argument was encountered.
// I don't know if this will ever happen because these types should always be resolvable, I think.
else if (typeArguments[offset] == ',')
{
if ((offset - StartOfArgument) > 0)
{
try
{
TypeList.Add(ReconstructType(typeArguments.Substring(StartOfArgument, offset - StartOfArgument).Trim(), throwOnError, referencedAssemblies));
}
catch
{
return null;
}
}
offset++;
StartOfArgument = offset;
}
// Essentially adds the character at this offset to any substring produced with the StartOfArgument offset.
else
offset++;
}
// 'offset' is out-of-bounds. 'StartOfArgument' may be out-of-bounds.
// 'offset-1' should be in-bounds, and if it's greater than 'StartOfArgument', there should be one last type argument to create.
if ((offset - 1) > StartOfArgument)
{
try
{
TypeList.Add(ReconstructType(typeArguments.Substring(StartOfArgument, offset - StartOfArgument).Trim(), throwOnError, referencedAssemblies));
}
catch
{
return null;
}
}
// "Should never happen" --original StackOverflow author
// This should only happen if the number of type arguments supplied in the type string doesn't match with the number of supplied arguments.
// If it's less than 0,
if (numberOfTypeArguments > 0)
return null;
try
{
return genericType.MakeGenericType(TypeList.ToArray());
}
catch
{
return null;
}
}
}
// https://docs.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-3.5
/// <summary>
/// Proxy interface for AssmeblyLoader.
/// </summary>
public interface IAssemblySandbox
{
void Load(string name, byte[] bytes);
byte[] ExecuteMethod(string assemblyName, string typeName, string methodName, string[] ConstructorTypes = null, object[] ConstructorParameters = null, string[] MethodTypes = null, object[] MethodParameters = null);
bool ExecuteMethodAndStoreResults(string assemblyName, string assemblyQualifiedTypeName, string methodName, string variableName, string[] ConstructorTypes = null, object[] ConstructorParameters = null, string[] MethodTypes = null, object[] MethodParameters = null);
bool ExecuteMethodOnVariable(string methodName, string targetVariableName, string returnVariableName, string[] MethodTypes = null, object[] MethodParameters = null);
bool ConstructNewObject(string assemblyQualifiedTypeName, string variableName, string[] ConstructorTypes = null, object[] ConstructorParameters = null);
bool SetVariable(string variableName, string assemblyQualifiedTypeName = "", byte[] serializedObject = null);
byte[] GetVariable(string variableName);
string GetVariableInfo(string variableName = "");
}
public class AssemblySandbox : MarshalByRefObject, IAssemblySandbox
{
/// Handles the loading and execution of in-memory Assemblies inside of a new AppDomain.
///
/// Inside this AppDomain, once the Assemblies are loaded, all Types are assumed to make sense.
/// Any types defined within the loaded Assemblies will resolve within this AppDomain's context.
/// In order to communicate between AppDomains, a proxy interface is required - the IAssemblySandbox.
/// When communication happens (i.e. when calling a proxied function w/ params and receiving a return value),
/// both parameters and return objects are serialized when passed. To prevent issues when passing
/// objects with custom, unknown types, the return objects are serialized BEFORE returning, with
/// the expectation that deserialization will occur somewhere where those types can be resolved.
/// The problem with most other solutions for dynamically loading Assemblies into different AppDomains
/// is that they all assume the loaded Assembly is a local, and therefore automatically resolvable, resource.
/// All dependencies must be loaded into this domain - nothing is automatic.
///
/// TODO: Continue doing research on this and check out:
/// https://github.com/jduv/AppDomainToolkit
///
/// Other References:
/// https://stackoverflow.com/questions/50127992/appdomain-assembly-not-found-when-loaded-from-byte-array
// TODO: Figure out exactly how/where serlialization/deserialization takes place and add a custom binder.
// Maybe use this as a reference: https://github.com/jduv/AppDomainToolkit
/// <summary>
/// Keeps track of specific Assemblies we've loaded so we can find the specific methods in the specific Assemblies loaded.
/// </summary>
private Dictionary<string, Assembly> AssemblyMap = new Dictionary<string, Assembly>();
/// <summary>
/// Mapping of variable names to objects of types that only this AppDomain can describe.
/// </summary>
private Dictionary<string, VariableTuple> Variables = new Dictionary<string, VariableTuple>();
/// <summary>
/// Regex used by ReconstructType/ConstructGenericType to recursively recognize and reconstruct generic Types.
/// </summary>
public static Regex GenericTypeRegex = new Regex(@"^(?<name>[\w\+]+(\.[\w|\+]+)*)(\&*)(\**)(`(?<count>\d))?(\[(?<subtypes>.*?)\])(,\s*(?<assembly>[\w\+]+(\.[\w|\+]+)*).*?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
/// <summary>
/// Regex used by ReconstructType/ConstructGenericType to recursively recognize and reconstruct generic Types.
/// </summary>
public static Regex LocalVariableRegex = new Regex(@"^Local\.Variable\.(?<VariableName>.+)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
/// <summary>
/// <para>Checks the supplied assemblyMap dictionary for an Assembly matching the supplied assemblyName.</para>
/// <para>If it's not there, checks all Assemblies loaded in the current AppDomain and returns the first match.</para>
/// <para>If it's not there, reconstruct a Type based off of the object/method's Assembly qualified Type name, and then return its Assembly.</para>
/// <para>Else, returns null. All checks are case-insensitive (all values are lowered; consider removing this in the future).</para>
/// </summary>
/// <param name="assemblyMap">Mapping of user-defined names to Assemblies loaded by Load method.</param>
/// <param name="assemblyName">User-defined name to check against assemblyMap and the Assemblies loaded in the current AppDomain.</param>
/// <param name="assemblyQualifiedTypeName">Assembly qualified Type name of a method</param>
/// <returns>Assembly object, if a match was found for the supplied name. Else, null.</returns>
private static Assembly GetAssembly(Dictionary<string, Assembly> assemblyMap = null, string assemblyName = "", string assemblyQualifiedTypeName = "")
{
try
{
// If a specific Assembly name was supplied and exists in the assemblyMap dict, use that specific Assembly.
if (assemblyMap != null && !string.IsNullOrEmpty(assemblyName) && assemblyMap.ContainsKey(assemblyName.ToLower()))
return assemblyMap[assemblyName.ToLower()];
// Else if the supplied Assembly name is in the AppDomain's loaded assemblies, use that one. (case-insensitive)
else if (!string.IsNullOrEmpty(assemblyName) && AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.ToLower() == assemblyName.ToLower()).Count() > 0)
return AppDomain.CurrentDomain.GetAssemblies().First(x => x.FullName.ToLower() == assemblyName.ToLower());
// Else if the Assembly can be found by resolving the specified type name, resolve the type name and get its Assembly.
else if (!string.IsNullOrEmpty(assemblyQualifiedTypeName) && ReconstructType(assemblyQualifiedTypeName) != null)
return ReconstructType(assemblyQualifiedTypeName).Assembly;
// Else we're out of ideas...
else
return null;
}
catch
{
return null;
}
}
/// <summary>
/// "Gets the Type object with the specified name in the Assembly instance". Wrapped method to ignore case and prevent throwing an error.
/// </summary>
/// <param name="assembly">Target Assembly instance to get the Type from.</param>
/// <param name="typeName">"The full name of the type".</param>
/// <returns>If successful, "An object that represents the specified class". Else, null.</returns>
private static Type GetAssemblyType(Assembly assembly, string typeName)
{
try
{
return assembly.GetType(typeName, false, true);
}
catch
{
return null;
}
}
/// <summary>
/// "Searches for the specified public method whose parameters match the specified argument types". Wrapped for our pleasure.
/// </summary>
/// <param name="assemblyType">Type from the target Assembly, where the desired method exists.</param>
/// <param name="methodName">"The string containing the name of the public method to get."</param>
/// <param name="methodParameterTypes">"An array of Type objects representing the number, order, and type of the parameters for the method to get."</param>
/// <returns>"An object representing the public method whose parameters match the specified argument types, if found; otherwise, null."</returns>
private static MethodInfo GetMethodInfo(Type assemblyType, string methodName, Type[] methodParameterTypes = null)
{
try
{
if (methodParameterTypes == null)
methodParameterTypes = new Type[] { };
return assemblyType.GetMethod(methodName, methodParameterTypes);
}
catch
{
return null;
}
}
/// <summary>
/// "Searches for a public instance constructor whose parameters match the types in the specified array". Wrapped for our pleasure.
/// </summary>
/// <param name="constructorType">Type of Constructor to get the ConstructorInfo object for.</param>
/// <param name="constructorParameterTypes">"An array of Type objects representing the number, order, and type of the parameters for the desired constructor." Can be empty.</param>
/// <returns>"An object representing the public instance constructor whose parameters match the types in the parameter type array, if found; otherwise, null."</returns>
private static ConstructorInfo GetConstructorInfo(Type constructorType, Type[] constructorParameterTypes = null)
{
try
{
return constructorType.GetConstructor(constructorParameterTypes);
}
catch
{
return null;
}
}
/// <summary>
/// "Searches for a public instance constructor whose parameters match the types in the specified array", but derived from a supplied method, instead of a class.
/// </summary>
/// <param name="methodInfoObject">MethodInfo Object for the desired non-static method.</param>
/// <param name="constructorTypes">Array of Types used to find the desired constructor.</param>
/// <returns>"An object representing the public instance constructor whose parameters match the types in the parameter type array, if found; otherwise, null." Also returns null if method is static.</returns>
private static ConstructorInfo GetConstructorInfo(MethodInfo methodInfoObject, Type[] constructorTypes = null)
{
try
{
// If the method is static, there is no constructor; return null.
if(methodInfoObject.IsStatic)
return null;
if (constructorTypes == null)
constructorTypes = new Type[] { };
// DeclaringType => "Gets the type that declares the current nested type or generic type parameter."
return methodInfoObject.DeclaringType.GetConstructor(constructorTypes);
}
catch
{
return null;
}
}
/// <summary>
/// Constructs an instance of a non-static class for the supplied methodInfoObject, given the constructor Types and parameters.
/// </summary>
/// <param name="methodInfoObject">MethodInfo Object of the desired method that needs instantiation.</param>
/// <param name="constructorTypes">Type-Array defining which constructor will be used and what all of the parameters are.</param>
/// <param name="constructorParameters">Object-Array containing parameters for the constructor.</param>
/// <returns>An instance of the class that the method should be invoked on.</returns>
private static object GetConstructedClassObject(MethodInfo methodInfoObject, Type[] constructorTypes = null, object[] constructorParameters = null)
{
try
{
if (methodInfoObject.IsStatic)
return null;
if (constructorTypes == null)
constructorTypes = new Type[] { };
if (constructorParameters == null)
constructorParameters = new object[] { };
return GetConstructorInfo(methodInfoObject, constructorTypes).Invoke(constructorParameters);
}
catch
{
return null;
}
}
/// <summary>
/// Combines ordered arrays of Assembly qualified Type names and objects into a list of VariableTuple-joined values.
/// Resolves string representations of Types into their actual Types, and converts references to locally stored variables
/// into the stored VariableTuple objects.
/// </summary>
/// <param name="localVariables">Mapping of user-defined variable names to VariableTuple objects.</param>
/// <param name="typeStrings">Array of Assembly qualified type names as strings.</param>
/// <param name="objects">Array of objects described by and matching the order of the Types in typeStrings.</param>
/// <param name="newParameters">Returned list of VariableTuple objects parsed from the supplied Types and objects.</param>
/// <returns>True if the supplied Types and objects were successfully resolved and combined into VariableTuples. Else, False.</returns>
private static bool ZipParameters(Dictionary<string, VariableTuple> localVariables, string[] typeStrings, object[] objects, out List<VariableTuple> newParameters)
{
newParameters = new List<VariableTuple>();
// If both are null, no parameters were specified - there is no error; return true.
if (typeStrings == null && objects == null)
return true;
// Else if only one is null, there was a msimatch in supplied parameter data; return false.
if (typeStrings == null || objects == null)
return false;
// Else if they're not null and their counts aren't the same, there was a msimatch in supplied parameter data; return false.
if (typeStrings.Count() != objects.Count())
return false;
try
{
for(int index = 0; index < typeStrings.Count(); index++)
{
// Check to see if this variable is stored locally, so we can retrieve the type from the Variables dict.
Match matchObject = LocalVariableRegex.Match(typeStrings[index]);
if (matchObject.Success && localVariables.ContainsKey(matchObject.Groups["VariableName"].Value))
newParameters.Add(localVariables[matchObject.Groups["VariableName"].Value]);
// Else, it's not local and we'll try to resolve the string into an actual type.
else
newParameters.Add(new VariableTuple(index.ToString(), ReconstructType(typeStrings[index]), objects[index]));
}
// Successfully made it to the end; return true.
return true;
}
catch
{
// Caught an unknowable exception; return false.
return false;
}
}
/// <summary>
/// Invokes the supplied method.
/// </summary>
/// <param name="assemblyName">Name of the assembly containing the method.
/// <para>This is either the user-defined name of an Assembly loaded by the Load method or the FullName of an Assembly in this AppDomain (e.g. "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=actual_publicKeyToken_goes_here", if attempting to do System.String.Join(...)).</para>
/// <para>Though this parameter is mandatory, it does not need to be correct or identify a real Assembly, so long as the assemblyQualifiedTypeName does lead to the resolution of an Assembly.</para>
/// </param>
/// <param name="assemblyQualifiedTypeName">Assembly qualified Type name where the method exists (e.g. "System.String", if attempting to do System.String.Join(...)).</param>
/// <param name="methodName">Name of the method to invoke (e.g. "Join", if attempting to do System.String.Join(...)).</param>
/// <param name="constructorTypes">Ordered array of Type name strings, defining which constructor is used and what the constructorParameters are (if this is a non-static method).</param>
/// <param name="constructorParameters">Ordered array of Objects, supplied as parameters when constructing an instance (if this is a non-static method).</param>
/// <param name="methodTypes">Ordered array of Type name strings, defining which method is used and what the methodParameters are.</param>
/// <param name="methodParameters">Ordered array of Objects, supplied as parameters to the method.</param>
/// <returns>The serialized return value (if anything returned after successful execution). Else, an empty byte-array.</returns>
public byte[] ExecuteMethod(string assemblyName, string assemblyQualifiedTypeName, string methodName, string[] constructorTypes = null, object[] constructorParameters = null, string[] methodTypes = null, object[] methodParameters = null)
{
List<VariableTuple> zippedConstructorParameters;
List<VariableTuple> zippedMethodParameters;
ZipParameters(Variables, constructorTypes, constructorParameters, out zippedConstructorParameters);
ZipParameters(Variables, methodTypes, methodParameters, out zippedMethodParameters);
// Both the *Types and *Parameters arrays need all types specified (even for optional parameters), using null for any non-specified *Parameters values.
// Get the exact Assembly Load'ed in the AssemblyMap, based on the supplied name.
Assembly CurrentAssembly = GetAssembly(AssemblyMap, assemblyName, assemblyQualifiedTypeName);
if (CurrentAssembly == null)
return new byte[] { };
// Get the specified Type from the supplied Assembly.
Type CurrentType = GetAssemblyType(CurrentAssembly, assemblyQualifiedTypeName);
if (CurrentType == null)
return new byte[] { };
MethodInfo MethodInfoObject = GetMethodInfo(CurrentType, methodName, zippedMethodParameters.Select(x => x.Type).ToArray());
if (MethodInfoObject == null)
return new byte[] { };
object ConstructedClassObject = GetConstructedClassObject(MethodInfoObject, zippedMethodParameters.Select(x => x.Type).ToArray(), zippedMethodParameters.Select(x => x.Instance).ToArray());
if (MethodInfoObject.IsStatic == false && ConstructedClassObject == null)
return new byte[] { };
// Serialize the return value to prevent the proxy/appdomain from attempting to serialize/deserialize types it doesn't understand.
try
{
return Serialize(MethodInfoObject.Invoke(ConstructedClassObject, zippedMethodParameters.Select(x => x.Instance).ToArray()));
}
catch
{
return new byte[] { };
}
}
/// <summary>
/// Invokes the supplied method and stores the results as a local variable.
/// </summary>
/// <param name="assemblyName">Name of the assembly containing the method.
/// <para>This is either the user-defined name of an Assembly loaded by the Load method or the FullName of an Assembly in this AppDomain (e.g. "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=actual_publicKeyToken_goes_here", if attempting to do System.String.Join(...)).</para>
/// <para>Though this parameter is mandatory, it does not need to be correct or identify a real Assembly, so long as the assemblyQualifiedTypeName does lead to the resolution of an Assembly.</para>
/// </param>
/// <param name="assemblyQualifiedTypeName">Assembly qualified Type name where the method exists (e.g. "System.String", if attempting to do System.String.Join(...)).</param>
/// <param name="methodName">Name of the method to invoke (e.g. "Join", if attempting to do System.String.Join(...)).</param>
/// <param name="variableName">Name to store any results under, in the local Variables dictionary.</param>
/// <param name="constructorTypes">Ordered array of Type name strings, defining which constructor is used and what the constructorParameters are (if this is a non-static method).</param>
/// <param name="constructorParameters">Ordered array of Objects, supplied as parameters when constructing an instance (if this is a non-static method).</param>
/// <param name="methodTypes">Ordered array of Type name strings, defining which method is used and what the methodParameters are.</param>
/// <param name="methodParameters">Ordered array of Objects, supplied as parameters to the method.</param>
/// <returns>True if the method was successfully executed and the results were successfully stored. Else, False.</returns>
public bool ExecuteMethodAndStoreResults(string assemblyName, string assemblyQualifiedTypeName, string methodName, string variableName, string[] constructorTypes = null, object[] constructorParameters = null, string[] methodTypes = null, object[] methodParameters = null)
{
List<VariableTuple> zippedConstructorParameters;
List<VariableTuple> zippedMethodParameters;
ZipParameters(Variables, constructorTypes, constructorParameters, out zippedConstructorParameters);
ZipParameters(Variables, methodTypes, methodParameters, out zippedMethodParameters);
// Both the *Types and *Parameters arrays need all types specified (even for optional parameters), using null for any non-specified *Parameters values.
// Get the exact Assembly Load'ed in the AssemblyMap, based on the supplied name.
Assembly CurrentAssembly = GetAssembly(AssemblyMap, assemblyName, assemblyQualifiedTypeName);
if (CurrentAssembly == null)
return false;
// Get the specified Type from the supplied Assembly.
Type CurrentType = GetAssemblyType(CurrentAssembly, assemblyQualifiedTypeName);
if (CurrentType == null)
return false;
MethodInfo MethodInfoObject = GetMethodInfo(CurrentType, methodName, zippedMethodParameters.Select(x => x.Type).ToArray());
if (MethodInfoObject == null)
return false;
object ConstructedClassObject = GetConstructedClassObject(MethodInfoObject, zippedMethodParameters.Select(x => x.Type).ToArray(), zippedMethodParameters.Select(x => x.Instance).ToArray());
if (MethodInfoObject.IsStatic == false && ConstructedClassObject == null)
return false;
// Serialize the return value to prevent the proxy/appdomain from attempting to serialize/deserialize types it doesn't understand.
try
{
Variables[variableName] = new VariableTuple(variableName, MethodInfoObject.ReturnType, MethodInfoObject.Invoke(ConstructedClassObject, zippedMethodParameters.Select(x => x.Instance).ToArray()));
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Creates a new instance of the Object specified by the Assembly qualified Type name, and stores it in the local Variables dictionary, using the supplied variable name.
/// </summary>
/// <param name="assemblyQualifiedTypeName">Assembly qualified Type name of the Object to create.</param>
/// <param name="variableName">User-defined name to store/lookup the newly created object in the Variables dictionary.</param>
/// <param name="constructorTypes">Ordered array of Type name strings, defining which constructor is used and what the constructorParameters are.</param>
/// <param name="constructorParameters">Ordered array of Objects, supplied as parameters when constructing an instance.</param>
/// <returns>True if a new instance was successfully created and stored in the local Variables dictionary. Else, False.</returns>
public bool ConstructNewObject(string assemblyQualifiedTypeName, string variableName, string[] constructorTypes = null, object[] constructorParameters = null)
{
List<VariableTuple> zippedConstructorParameters;
ZipParameters(Variables, constructorTypes, constructorParameters, out zippedConstructorParameters);
// Get the specified Type from the supplied Assembly.
Type ConstructorType = ReconstructType(assemblyQualifiedTypeName);
if (ConstructorType == null)
return false;
ConstructorInfo constructorInfo = GetConstructorInfo(ConstructorType, zippedConstructorParameters.Select(x => x.Type).ToArray());
if (constructorInfo == null)
return false;
try
{
Variables[variableName] = new VariableTuple(variableName, ConstructorType, constructorInfo.Invoke(zippedConstructorParameters.Select(x => x.Instance).ToArray()));
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Invokes the supplied method on the specified local variable Object, and stores the results in a local variable.
/// </summary>
/// <param name="methodName">Name of the method to invoke.</param>
/// <param name="targetVariableName">Name of the variable in the Variables dictionary to execute the method on.</param>
/// <param name="returnVariableName">Name to store any return results as in the Variables dictionary.</param>
/// <param name="methodTypes">Ordered array of Type name strings, defining which method is used and what the methodParameters are.</param>
/// <param name="methodParameters">Ordered array of Objects, supplied as parameters to the method.</param>
/// <returns>True if the method was successfully executed on the variable. Else, False.</returns>
public bool ExecuteMethodOnVariable(string methodName, string targetVariableName, string returnVariableName, string[] methodTypes = null, object[] methodParameters = null)
{
List<VariableTuple> zippedMethodParameters;
ZipParameters(Variables, methodTypes, methodParameters, out zippedMethodParameters);
if (!Variables.ContainsKey(targetVariableName))
return false;
if (Variables[targetVariableName] == null)
return false;
MethodInfo MethodInfoObject = GetMethodInfo(Variables[targetVariableName].Type, methodName, zippedMethodParameters.Select(x => x.Type).ToArray());
if (MethodInfoObject == null)
return false;
// Serialize the return value to prevent the proxy/appdomain from attempting to serialize/deserialize types it doesn't understand.
try
{
Variables[returnVariableName] = new VariableTuple(returnVariableName, MethodInfoObject.ReturnType, MethodInfoObject.Invoke(Variables[targetVariableName].Instance, zippedMethodParameters.Select(x => x.Instance).ToArray()));
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Loads an assembly into the current AppDomain, and stores its representation in the AssemblyMap dictionary.
/// </summary>
/// <param name="name">User-defined name to store/access the Assembly representation in the AssemblyMap dictionary.</param>
/// <param name="bytes">Byte-array of the .NET Assembly to load into the current AppDomain.</param>
public void Load(string name, byte[] bytes)
{
//Assembly[] AssembliesBeforeLoad = AppDomain.CurrentDomain.GetAssemblies();
AssemblyMap[name.ToLower()] = AppDomain.CurrentDomain.Load(bytes);
//Assembly[] AssembliesAfterLoad = AppDomain.CurrentDomain.GetAssemblies();
return;
}
/// <summary>
/// Stores an Object in the local Variables dictionary for further interaction.
/// <para>If a serialized Object is supplied, the Type from the deserialized object is used; discarding the assemblyQualifiedTypeName.</para>
/// </summary>
/// <remarks>
/// Allows other AppDomains to pass objects into this AppDomain without understanding the Types involved.
/// What they push in is simply understood as a byte-array.
/// </remarks>
/// <param name="variableName">User-defined name used to access the variable.</param>
/// <param name="assemblyQualifiedTypeName">Assembly qualified Type name to use if (de)serialized Object is null.</param>
/// <param name="serializedObject">Serialized Object to store in the local Variables dictionary.</param>
/// <returns>True if the Variable was successfully stored. Else, False.</returns>
public bool SetVariable(string variableName, string assemblyQualifiedTypeName = "", byte[] serializedObject = null)
{
object DeserializedObject = Deserialize(serializedObject);
if (DeserializedObject == null)
{
if (string.IsNullOrEmpty(assemblyQualifiedTypeName))
return false;
try
{
Variables[variableName] = new VariableTuple(variableName, ReconstructType(assemblyQualifiedTypeName), null);
}
catch
{
return false;
}
}
else
{
try
{
Variables[variableName] = new VariableTuple(variableName, DeserializedObject.GetType(), DeserializedObject);
}
catch
{
return false;
}
}
return true;
}
/// <summary>
/// Gets a serialized value from the local Variables dictionary, using the specified variable name.
/// </summary>
/// <remarks>
/// Allows other AppDomains to request objects from this AppDomain without understanding the Types involved.
/// What they get back is simply understood as a byte-array.
/// </remarks>
/// <param name="variableName">Name of the variable to retrieve from the Variables dictionary</param>
/// <returns>If Variables contains the supplied variableName, the serialized object specified. Else, null.</returns>
public byte[] GetVariable(string variableName)
{
try
{
return Serialize(Variables[variableName].Instance);
}
catch
{
return null;
}
}
/// <summary>
/// Generates a list of Variables with their types and whether or not their values are null. Mainly for debugging purposes; consider removing.
/// </summary>
/// <param name="variableName">The user-defined name of a specific variable in the Variables dictionary.</param>
/// <returns>A formatted string containing the variable name, Type, and whether or not the variable is null. If no variableName was supplied, information is returned for all variables.</returns>
public string GetVariableInfo(string variableName = "")
{
try
{
if (string.IsNullOrEmpty(variableName))
return string.Join("\r\n", Variables.Values.Select(x => string.Format("{0} ({1}) [IsNull:{2}]", x.Name, x.Type, x.Instance == null)).ToArray());
if (Variables.ContainsKey(variableName))
return string.Format("{0} ({1}) [IsNull:{2}]", variableName, Variables[variableName].Type, Variables[variableName].Instance == null);
}
catch { }
return "";
}
/// <summary>
/// Serializes an Object, of a Type understood by this AppDomain, into a byte-array.
/// </summary>
/// <param name="objectToSerialize">An Object of a Type that this AppDomain is aware of.</param>
/// <returns>If serialization occurs successfully, a byte array representing the Object is returned. Else, null.</returns>
private static byte[] Serialize(object objectToSerialize)
{
try
{
BinaryFormatter serializer = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
serializer.Serialize(memoryStream, objectToSerialize);
return memoryStream.ToArray();
}
}
catch
{
return null;
}
}