-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Program_with_Command_Line_Demo.cs
1922 lines (1668 loc) · 103 KB
/
Program_with_Command_Line_Demo.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.Collections.Specialized;
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 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 GetNewAssemeblySandbox(AppDomain appDomain)
{
Type assemblySandboxType = typeof(AssemblySandbox);
return (IAssemblySandbox)appDomain.CreateInstanceFromAndUnwrap(assemblySandboxType.Assembly.Location, assemblySandboxType.FullName);
}
// Prints messages with indicators and color.
public static void PrintDebug(string DebugMessage, string Level = "info", bool DebugEnabled = true, bool IndicatorEnabled = true)
{
if (!DebugEnabled)
return;
var PreviousForegroundColor = Console.ForegroundColor;
string Indicator = "[*]";
if (Level.ToLower() == "warning")
{
Console.ForegroundColor = ConsoleColor.Yellow;
Indicator = "[!]";
}
else if (Level.ToLower() == "error")
{
Console.ForegroundColor = ConsoleColor.Red;
Indicator = "[-]";
}
else if (Level.ToLower() == "success")
{
Console.ForegroundColor = ConsoleColor.Green;
Indicator = "[+]";
}
if(IndicatorEnabled)
Console.WriteLine("{0} {1}", Indicator, DebugMessage);
else
Console.WriteLine("{0}", DebugMessage);
Console.ForegroundColor = PreviousForegroundColor;
}
static void Main()
{
// Regex for parsing PowerShell-like command lines.
Regex MethodCallRegex = new Regex(@"^\s*(\$(?<StorageVariableName>.+?)(\s*=\s*))?(((?<ConstructNew>new\s+)?(\[)?((\$(?<ClassInstanceVariableName>.+?))|(?<ClassName>.+?))(\.(?<AdditionalClassTypeNames>.+))?(\.|\]::)((?<Method>.+?)\((?<Parameters>.*)\)))|(\$(?<variableAssignment>[^\s]+))|(?<nullAssignment>\$null)|(?<intAssignment>(-)?\d+)|(?<floatAssignment>(-)?\d+\.\d+)|(""(?<DoubleQuotedAssignment>.*)"")|('(?<SingleQuotedAssignment>.*)'))\s*$", RegexOptions.ExplicitCapture | RegexOptions.Compiled);
// Regex for parsing single variables, specified as explicitly and implicitly typed values (e.g. '[System.String] "asdf"' and 5) or variable names (e.g. "$variableName").
Regex ParameterRegex = new Regex(@"((\[(?<ValueType>.+)\])?\s*((""(?<DoubleQuotedValue>(\\.|[^""])*)"")|('(?<SingleQuotedValue>(\\.|[^""])*)')|(?<Int32Value>-?\d+)|(?<FloatValue>-?\d+\.\d+)|(\$(?<VariableName>[^,]+?))|(?<Stuff>[^\$][^,]+?)))(\s*,\s*|\s*$)", RegexOptions.ExplicitCapture | RegexOptions.Compiled);
// 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 CurrentSandboxId = Guid.NewGuid();
// Create the AppDomain using the above GUID as the friendly name.
AppDomain CurrentAppDomain = GetNewAppDomain(CurrentSandboxId);
// Use this new AppDomain to get an interface to the AssemblySandbox execution class.
IAssemblySandbox CurrentAssemblySandbox = GetNewAssemeblySandbox(CurrentAppDomain);
PrintDebug(String.Format("Created new Assembly Sandbox with GUID: {0}", CurrentSandboxId.ToString()), "success");
// Create an ordered mapping of GUID's to Sandboxes.
OrderedDictionary Sandboxes = new OrderedDictionary();
OrderedDictionary AppDomains = new OrderedDictionary();
Sandboxes.Add(CurrentSandboxId, CurrentAssemblySandbox);
AppDomains.Add(CurrentSandboxId, CurrentAppDomain);
PrintDebug(String.Format("Set active Assembly Sandbox to GUID: {0}", CurrentSandboxId.ToString()), "success");
string command = "";
bool DebugEnabled = true;
while (command.ToLower() != "exit" && command.ToLower() != "quit")
{
Console.Write(">");
command = Console.ReadLine();
if (string.IsNullOrEmpty(command))
continue;
string SyntacticLine = "";
List<string> ParameterTypes = new List<string>();
List<object> ParameterValues = new List<object>();
byte[] ReturnValue = null;
Match CommandMatch = MethodCallRegex.Match(command);
MatchCollection ParameterMatches = ParameterRegex.Matches(CommandMatch.Groups["Parameters"].Value);
bool wasSuccessful = CommandMatch.Groups["ConstructNew"].Success;
bool StoreResults = CommandMatch.Groups["StorageVariableName"].Success;
bool ConstructNew = CommandMatch.Groups["ConstructNew"].Success;
// Debug section that prints out what the MethodCallRegex could parse from the typed value.
if (DebugEnabled)
{
Console.WriteLine("\r\n[+] Command Regex Results:");
Console.WriteLine("\tStorageVariableName:......... {0}", CommandMatch.Groups["StorageVariableName"].Value);
Console.WriteLine("\tvariableAssignment:.......... {0}", CommandMatch.Groups["variableAssignment"].Value);
Console.WriteLine("\tnullAssignment:.............. {0}", CommandMatch.Groups["nullAssignment"].Value);
Console.WriteLine("\tintAssignment:............... {0}", CommandMatch.Groups["intAssignment"].Value);
Console.WriteLine("\tfloatAssignment:............. {0}", CommandMatch.Groups["floatAssignment"].Value);
Console.WriteLine("\tDoubleQuotedAssignment:...... {0}", CommandMatch.Groups["DoubleQuotedAssignment"].Value);
Console.WriteLine("\tSingleQuotedAssignment:...... {0}", CommandMatch.Groups["SingleQuotedAssignment"].Value);
Console.WriteLine("\tStoreResults:................ {0}", CommandMatch.Groups["StorageVariableName"].Success);
Console.WriteLine("\tConstructNew:................ {0}", CommandMatch.Groups["ConstructNew"].Success);
Console.WriteLine("\tClassInstanceVariableName:... {0}", CommandMatch.Groups["ClassInstanceVariableName"].Value);
Console.WriteLine("\tClassName:................... {0}", CommandMatch.Groups["ClassName"].Value);
Console.WriteLine("\tAdditionalClassTypeNames:.... {0}", CommandMatch.Groups["AdditionalClassTypeNames"].Value);
Console.WriteLine("\tMethod:...................... {0}", CommandMatch.Groups["Method"].Value);
Console.WriteLine("\tParameters:.................. {0}\r\n", CommandMatch.Groups["Parameters"].Value);
}
#region Parameter Parsing
if (ParameterMatches.Count > 0)
{
PrintDebug("Parameter Regex Results:", DebugEnabled: DebugEnabled);
foreach (Match ParameterMatch in ParameterMatches)
{
string ParameterType = "";
string ParameterValue = "";
if (!string.IsNullOrEmpty(ParameterMatch.Groups["ValueType"].Value))
{
PrintDebug(String.Format("\tValueType:........... {0}", ParameterMatch.Groups["ValueType"].Value), "info", DebugEnabled, false);
ParameterType = ParameterMatch.Groups["ValueType"].Value;
}
if (!string.IsNullOrEmpty(ParameterMatch.Groups["DoubleQuotedValue"].Value))
{
PrintDebug(String.Format("\tDoubleQuotedValue:... {0}", ParameterMatch.Groups["DoubleQuotedValue"].Value), "info", DebugEnabled, false);
ParameterValue = ParameterMatch.Groups["DoubleQuotedValue"].Value;
if (ParameterType == "")
ParameterType = "System.String";
}
if (!string.IsNullOrEmpty(ParameterMatch.Groups["SingleQuotedValue"].Value))
{
PrintDebug(String.Format("\tSingleQuotedValue:... {0}", ParameterMatch.Groups["SingleQuotedValue"].Value), "info", DebugEnabled, false);
ParameterValue = ParameterMatch.Groups["SingleQuotedValue"].Value;
if (ParameterType == "")
ParameterType = "System.String";
}
if (!string.IsNullOrEmpty(ParameterMatch.Groups["Int32Value"].Value))
{
PrintDebug(String.Format("\tInt32Value:.......... {0}", ParameterMatch.Groups["Int32Value"].Value), "info", DebugEnabled, false);
ParameterValue = ParameterMatch.Groups["Int32Value"].Value;
if (ParameterType == "")
ParameterType = "System.Int32";
}
if (!string.IsNullOrEmpty(ParameterMatch.Groups["FloatValue"].Value))
{
PrintDebug(String.Format("\tFloatValue:.......... {0}", ParameterMatch.Groups["FloatValue"].Value), "info", DebugEnabled, false);
ParameterValue = ParameterMatch.Groups["FloatValue"].Value;
if (ParameterType == "")
ParameterType = "System.Single";
}
if (!string.IsNullOrEmpty(ParameterMatch.Groups["VariableName"].Value))
{
PrintDebug(String.Format("\tVariableName:........ {0}", ParameterMatch.Groups["VariableName"].Value), "info", DebugEnabled, false);
ParameterValue = ParameterMatch.Groups["VariableName"].Value;
if (ParameterType == "")
ParameterType = string.Format("Local.Variable.{0}", ParameterMatch.Groups["VariableName"].Value);
}
if (!string.IsNullOrEmpty(ParameterMatch.Groups["Stuff"].Value))
{
PrintDebug(String.Format("\tStuff:............... {0}", ParameterMatch.Groups["Stuff"].Value), "info", DebugEnabled, false);
ParameterValue = ParameterMatch.Groups["Stuff"].Value;
}
if(!String.IsNullOrEmpty(ParameterType))
{
ParameterTypes.Add(ParameterType);
ParameterValues.Add(ParameterValue);
}
PrintDebug("", "info", DebugEnabled, false);
}
PrintDebug("", "info", DebugEnabled, false);
}
#endregion
#region Variable Assignments
if (CommandMatch.Groups["variableAssignment"].Success)
{
SyntacticLine += string.Format("Get local variable \"{0}\".", CommandMatch.Groups["variableAssignment"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.CopyVariable(CommandMatch.Groups["variableAssignment"].Value, CommandMatch.Groups["StorageVariableName"].Value);
}
else
{
ReturnValue = CurrentAssemblySandbox.GetVariable(CommandMatch.Groups["variableAssignment"].Value);
}
}
else if (CommandMatch.Groups["nullAssignment"].Success)
{
SyntacticLine += string.Format("Get null.");
if (StoreResults)
{
CurrentAssemblySandbox.SetVariable(CommandMatch.Groups["StorageVariableName"].Value, "None", null);
}
}
else if (CommandMatch.Groups["intAssignment"].Success)
{
SyntacticLine += string.Format("Create a new int: {0}.", CommandMatch.Groups["intAssignment"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.SetVariable(CommandMatch.Groups["StorageVariableName"].Value, "System.Int32", Serialize(Int32.Parse(CommandMatch.Groups["intAssignment"].Value)));
}
}
else if (CommandMatch.Groups["floatAssignment"].Success)
{
SyntacticLine += string.Format("Create a new float: {0}.", CommandMatch.Groups["floatAssignment"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.SetVariable(CommandMatch.Groups["StorageVariableName"].Value, "System.Single", Serialize(float.Parse(CommandMatch.Groups["floatAssignment"].Value)));
}
}
else if (CommandMatch.Groups["DoubleQuotedAssignment"].Success)
{
SyntacticLine += string.Format("Create a new string \"{0}\".", CommandMatch.Groups["DoubleQuotedAssignment"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.SetVariable(CommandMatch.Groups["StorageVariableName"].Value, "System.String", Serialize(CommandMatch.Groups["DoubleQuotedAssignment"].Value));
}
}
else if (CommandMatch.Groups["SingleQuotedAssignment"].Success)
{
SyntacticLine += string.Format("Create a new string \"{0}\".", CommandMatch.Groups["SingleQuotedAssignment"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.SetVariable(CommandMatch.Groups["StorageVariableName"].Value, "System.String", Serialize(CommandMatch.Groups["SingleQuotedAssignment"].Value));
}
}
#endregion
#region Method Calls
// Handle all calls to non-static and local methods.
else if (CommandMatch.Groups["Method"].Success)
{
if (CommandMatch.Groups["ClassInstanceVariableName"].Success)
{
// If this method is being called on an instance of a class with the variable name "Local", this is attempting to invoke a method outside of this PowerShell-like language.
// $Local is reserved for calling methods in this class and the AssemblySandbox (like AssemblySandbox.Load()).
if (CommandMatch.Groups["ClassInstanceVariableName"].Value == "Local")
{
// Wrap AssemblySandbox.Load() as $Local.Load(string "DllName", string "C:\Path\To\Dll\DllName.dll")
if (CommandMatch.Groups["Method"].Value == "Load")
{
// Parameter count checking.
if(ParameterTypes.Count != 2 || ParameterValues.Count != 2 )
{
PrintDebug(String.Format("No overload found for Local method Load with {0} parameters and {1} types.", ParameterValues.Count, ParameterTypes.Count), "warning");
continue;
}
// Parameter type checking.
else if (ParameterTypes[0] != "System.String" || ParameterTypes[1] != "System.String")
{
PrintDebug(String.Format("No overload found for Local method Load for types: {0}, {1}.", ParameterTypes[0], ParameterTypes[1]), "warning");
continue;
}
// Parameter value checking.
else if (string.IsNullOrEmpty((string)ParameterValues[0]))
{
PrintDebug(String.Format("Parameter LibraryName cannot be Null or empty."), "warning");
continue;
}
else if (string.IsNullOrEmpty((string)ParameterValues[1]))
{
PrintDebug(String.Format("Parameter LibraryPath cannot be Null or empty."), "warning");
continue;
}
else if (!File.Exists((string)ParameterValues[1]))
{
PrintDebug(String.Format("Parameter LibraryPath needs to point to a valid file."), "warning");
continue;
}
// If parameter and Type checking passed, call the Local method.
PrintDebug(String.Format("Attempting to call Local method Load on library \"{0}\" as \"{1}\"...", (string)ParameterValues[0], (string)ParameterValues[1]), "info", DebugEnabled, true);
try
{
CurrentAssemblySandbox.Load((string)ParameterValues[0], File.ReadAllBytes((string)ParameterValues[1]));
PrintDebug(String.Format("Successfully called Local method Load."), "success");
}
catch
{
PrintDebug(String.Format("Caught exception while reading library and calling Local Load method."), "error");
}
}
// Gets information on the stored variables from the active Assembly Sandbox.
else if (CommandMatch.Groups["Method"].Value == "GetVariableInfo")
{
// Parameter count checking.
if (ParameterTypes.Count > 1 || ParameterValues.Count > 1)
{
PrintDebug(String.Format("No overload found for Local method GetVariableInfo with {0} parameters and {1} types.", ParameterValues.Count, ParameterTypes.Count), "warning");
continue;
}
// Parameter type checking.
else if (ParameterTypes.Count == 1 && ParameterTypes[0] != "System.String")
{
PrintDebug(String.Format("No overload found for Local method GetVariableInfo for type: {0}.", ParameterTypes[0]), "warning");
continue;
}
// If parameter and Type checking passed, call the Local method.
PrintDebug(String.Format("Attempting to call Local method GetVariableInfo on AssemblySandbox..."), "info", DebugEnabled, true);
if (ParameterValues.Count == 1)
{
Console.WriteLine(CurrentAssemblySandbox.GetVariableInfo((string)ParameterValues[0]));
}
else
{
Console.WriteLine(CurrentAssemblySandbox.GetVariableInfo());
}
}
// Gets a list of the Assemblies loaded in the active Assembly Sandbox.
else if (CommandMatch.Groups["Method"].Value == "CheckLoadedAssemblies")
{
// Do parameter and Type checking.
if (ParameterTypes.Count != 0 || ParameterValues.Count != 0)
{
PrintDebug(String.Format("No overload found for Local method CheckLoadedAssemblies with {0} parameters and {1} types.", ParameterValues.Count, ParameterTypes.Count), "warning");
continue;
}
// If parameter and Type checking passed, call the Local method.
PrintDebug(String.Format("Attempting to call Local method CheckLoadedAssemblies on AssemblySandbox..."), "info", DebugEnabled, true);
Console.WriteLine(CurrentAssemblySandbox.CheckLoadedAssemblies());
}
// Lists all Assembly Sandboxes, their indexes, and the active one.
else if (CommandMatch.Groups["Method"].Value == "ListSandboxes")
{
Guid[] Keys = new Guid[Sandboxes.Keys.Count];
Sandboxes.Keys.CopyTo(Keys, 0);
for (int index = 0; index < Keys.Count<Guid>(); index++)
{
if(Keys[index] == CurrentSandboxId)
{
Console.WriteLine("{0}) {1} [Active Sandbox]", index, Keys[index]);
}
else
{
Console.WriteLine("{0}) {1}", index, Keys[index]);
}
}
}
// Create a new Assembly Sandbox, and set it as the active one.
else if (CommandMatch.Groups["Method"].Value == "NewSandbox")
{
// 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.
CurrentSandboxId = Guid.NewGuid();
// Create the AppDomain using the above GUID as the friendly name.
CurrentAppDomain = GetNewAppDomain(CurrentSandboxId);
// Use this new AppDomain to get an interface to the AssemblySandbox execution class.
CurrentAssemblySandbox = GetNewAssemeblySandbox(CurrentAppDomain);
PrintDebug(String.Format("Created new Assembly Sandbox with GUID: {0}", CurrentSandboxId.ToString()), "success");
Sandboxes.Add(CurrentSandboxId, CurrentAssemblySandbox);
AppDomains.Add(CurrentSandboxId, CurrentAppDomain);
PrintDebug(String.Format("Set active Assembly Sandbox to GUID: {0}", CurrentSandboxId.ToString()), "success");
}
// Set the active Assembly Sandbox to the one at the supplied index number.
else if (CommandMatch.Groups["Method"].Value == "SetSandbox")
{
// Parameter count checking.
if (ParameterTypes.Count != 1 || ParameterValues.Count != 1)
{
PrintDebug(String.Format("No overload found for Local method ChangeSandbox with {0} parameters and {1} types.", ParameterValues.Count, ParameterTypes.Count), "warning");
continue;
}
// Parameter Type checking.
else if (ParameterTypes[0] != "System.Int32")
{
PrintDebug(String.Format("No overload found for Local method ChangeSandbox for type: {0}.", ParameterTypes[0]), "warning");
continue;
}
int SandboxIndex = -1;
// Parameter value checking.
try
{
SandboxIndex = Int32.Parse((string)ParameterValues[0]);
}
catch
{
PrintDebug(String.Format("Unable to convert Supplied value to an integer index."), "warning");
continue;
}
// Check the parameter value against the available range.
if (SandboxIndex < 0 || SandboxIndex >= Sandboxes.Keys.Count)
{
PrintDebug(String.Format("Supplied value outside available range: 0-{0} (Supplied Value: {1}).", Sandboxes.Keys.Count, SandboxIndex), "warning");
continue;
}
Guid[] Keys = new Guid[Sandboxes.Keys.Count];
Sandboxes.Keys.CopyTo(Keys, 0);
// Set all current values to that of the selected Assembly Sandbox.
CurrentSandboxId = Keys[SandboxIndex];
CurrentAssemblySandbox = (IAssemblySandbox)Sandboxes[CurrentSandboxId];
CurrentAppDomain = (AppDomain)AppDomains[CurrentSandboxId];
PrintDebug(String.Format("Set active Assembly Sandbox to GUID: {0}", CurrentSandboxId.ToString()), "success");
}
// Delete an Assembly Sandbox.
else if (CommandMatch.Groups["Method"].Value == "DeleteSandbox")
{
// Parameter count checking.
if (ParameterTypes.Count != 1 || ParameterValues.Count != 1)
{
PrintDebug(String.Format("No overload found for Local method DeleteSandbox with {0} parameters and {1} types.", ParameterValues.Count, ParameterTypes.Count), "warning");
continue;
}
// Parameter Type checking.
else if (ParameterTypes[0] != "System.Int32")
{
PrintDebug(String.Format("No overload found for Local method DeleteSandbox for type: {0}.", ParameterTypes[0]), "warning");
continue;
}
int SandboxIndex = -1;
// Parameter value checking.
try
{
SandboxIndex = Int32.Parse((string)ParameterValues[0]);
}
catch
{
PrintDebug(String.Format("Unable to convert Supplied value to an integer index."), "warning");
continue;
}
Guid[] Keys = new Guid[Sandboxes.Keys.Count];
Sandboxes.Keys.CopyTo(Keys, 0);
// Check the parameter value against the available range.
if (SandboxIndex < 0 || SandboxIndex >= Sandboxes.Keys.Count)
{
PrintDebug(String.Format("Supplied value outside available range: 0-{0} (Supplied Value: {1}).", Sandboxes.Keys.Count, SandboxIndex), "warning");
continue;
}
// Prevent the current Assembly Sandbox from being unloaded. (TODO: Rearrange command line logic to prevent any non-Local commands from running if the active Assembly Sandbox is null).
else if(CurrentSandboxId == Keys[SandboxIndex])
{
PrintDebug(String.Format("Cannot delete active Assembly Sandbox. Use SetSandbox to change to a different Assembly Sandbox, first.", Sandboxes.Keys.Count, SandboxIndex), "warning");
continue;
}
// Unload the associated AppDomain, to unload all of the loaded binaries.
AppDomain.Unload((AppDomain)AppDomains[Keys[SandboxIndex]]);
// Remove the IAssemblySandbox Object from the Sandboxes dict.
Sandboxes.Remove(Keys[SandboxIndex]);
// Remove the AppDomain Object from the AppDomains dict.
AppDomains.Remove(Keys[SandboxIndex]);
PrintDebug(String.Format("Successfully unloaded and removed Assembly Sandbox with GUID: {0}", Keys[SandboxIndex].ToString()), "success");
}
// Turns off all debug messages.
else if (CommandMatch.Groups["Method"].Value == "DisableDebug")
{
DebugEnabled = false;
PrintDebug(String.Format("Disabled debug output."), "success");
}
// Turns on all debug messages.
else if (CommandMatch.Groups["Method"].Value == "EnableDebug")
{
DebugEnabled = true;
PrintDebug(String.Format("Enabled debug output."), "success");
}
}
else
{
if (CommandMatch.Groups["AdditionalClassTypeNames"].Success)
{
SyntacticLine += string.Format("Call non-static method \"{0}.{1}\" on local variable \"{2}\".", CommandMatch.Groups["AdditionalClassTypeNames"].Value, CommandMatch.Groups["Method"].Value, CommandMatch.Groups["ClassInstanceVariableName"].Value);
PrintDebug(String.Format("This doesn't work yet. TODO: Add additional class type name resolution to ExecuteMethodOnVariable."), "warning");
}
else
{
SyntacticLine += string.Format("Call non-static method \"{0}\" on local variable \"{1}\".", CommandMatch.Groups["Method"].Value, CommandMatch.Groups["ClassInstanceVariableName"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.ExecuteMethodOnVariable(CommandMatch.Groups["Method"].Value, CommandMatch.Groups["ClassInstanceVariableName"].Value, CommandMatch.Groups["StorageVariableName"].Value, ParameterTypes.ToArray(), ParameterValues.ToArray());
}
else
{
ReturnValue = CurrentAssemblySandbox.ExecuteMethodOnVariable(CommandMatch.Groups["Method"].Value, CommandMatch.Groups["ClassInstanceVariableName"].Value, ParameterTypes.ToArray(), ParameterValues.ToArray());
}
}
}
}
// Handle all calls to static methods.
else if (CommandMatch.Groups["ClassName"].Success)
{
// Handle all calls to Class constructors.
if (ConstructNew)
{
string AssemblyQualifiedTypeName = String.Join(".", new string[] { CommandMatch.Groups["ClassName"].Value, CommandMatch.Groups["Method"].Value });
if (CommandMatch.Groups["AdditionalClassTypeNames"].Success)
{
AssemblyQualifiedTypeName = String.Join(".", new string[] { CommandMatch.Groups["ClassName"].Value, CommandMatch.Groups["AdditionalClassTypeNames"].Value, CommandMatch.Groups["Method"].Value });
}
SyntacticLine += string.Format("Create a new instance of Type \"{0}\".", AssemblyQualifiedTypeName);
if (StoreResults)
{
CurrentAssemblySandbox.ConstructNewObject(AssemblyQualifiedTypeName, CommandMatch.Groups["StorageVariableName"].Value, ParameterTypes.ToArray(), ParameterValues.ToArray());
}
else
{
PrintDebug(String.Format("Newly constructed Objects must be stored in a variable (e.g. $x = new Custom.Class.Thing())."), "warning");
}
}
else
{
string AssemblyQualifiedTypeName = CommandMatch.Groups["ClassName"].Value;
if (CommandMatch.Groups["AdditionalClassTypeNames"].Success)
{
AssemblyQualifiedTypeName = String.Join(".", new string[] { CommandMatch.Groups["ClassName"].Value, CommandMatch.Groups["AdditionalClassTypeNames"].Value });
}
SyntacticLine += string.Format("Call static method \"{0}.{1}\".", AssemblyQualifiedTypeName, CommandMatch.Groups["Method"].Value);
if (StoreResults)
{
CurrentAssemblySandbox.ExecuteMethodAndStoreResults(CommandMatch.Groups["ClassName"].Value, AssemblyQualifiedTypeName, CommandMatch.Groups["Method"].Value, CommandMatch.Groups["StorageVariableName"].Value, null, null, ParameterTypes.ToArray(), ParameterValues.ToArray());
}
else
{
ReturnValue = CurrentAssemblySandbox.ExecuteMethod(CommandMatch.Groups["ClassName"].Value, AssemblyQualifiedTypeName, CommandMatch.Groups["Method"].Value, null, null, ParameterTypes.ToArray(), ParameterValues.ToArray());
}
}
}
}
#endregion
if (CommandMatch.Groups["StorageVariableName"].Success)
{
SyntacticLine += string.Format(" Store the value in local variable \"{0}\".", CommandMatch.Groups["StorageVariableName"].Value);
}
else if (!StoreResults && !string.IsNullOrEmpty(SyntacticLine))
{
SyntacticLine += string.Format(" Return the value.");
}
if (!string.IsNullOrEmpty(SyntacticLine) && DebugEnabled)
PrintDebug(SyntacticLine, "info", DebugEnabled, false);
//else
//Console.WriteLine("Failed to parse line.");
if (!StoreResults)
{
Console.WriteLine();
Console.WriteLine(Deserialize(ReturnValue));
}
}
}
public static object Deserialize(byte[] byteArray)
{
if(byteArray == null || byteArray.Length == 0)
{
return null;
}
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 (Exception ex)
{
PrintDebug(String.Format("Encountered Exception while Deserializing the return value of the last command:\r\n\r\n{0}\r\n\r\n", ex.Message), "error");
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);
byte[] ExecuteMethodOnVariable(string methodName, string targetVariableName, 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);
bool UnSetVariable(string variableName);
byte[] GetVariable(string variableName);
bool CopyVariable(string sourceVariableName, string destinationVariableName);
string GetVariableInfo(string variableName = "");
string CheckLoadedAssemblies();
}
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()];