Advertisement
BaSs_HaXoR

Steganography VI - Hiding messages in .NET Assemblies

Jan 28th, 2017
492
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scilab 21.21 KB | None | 0 0
  1.                                                                                         [Steganography VI - Hiding messages in .NET Assemblies]
  2.                                                                                                       LINK: http://adf.ly/1iqybI
  3.  
  4. [C#,Windows,.NET,.NET1.0,Visual-Studio, Dev]
  5.  
  6. ------------------------------------------------------------------------------------------------------------
  7. [62K views]
  8. [1.1K downloads]
  9. [29 bookmarked]
  10. [Posted 22 Nov 2003]
  11.  
  12. Corinna John, 23 Nov 2003 CDDL [RATING: 4.57 (18 votes)]
  13.  
  14. ------------------------------------------------------------------------------------------------------------
  15. An article about hiding instructions at the end of methods in an .NET Assembly
  16. Download source files - 11.8 Kb
  17. ------------------------------------------------------------------------------------------------------------
  18.  
  19. ------------------------------------------------------------------------------------------------------------
  20. [Introduction]
  21. An application contains lots of lines which leave the stack empty. After these lines any code can be inserted, as long as it leaves the stack empty again. You can load some values onto the stack and store them off again, without disturbing the application's flow.
  22.  
  23. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  24. [Finding silent hiding places]
  25. Let's take a look at an assembly's IL Assembler Language code. Each methods contains lines which put something onto the stack, or store something off the stack. We cannot always say what exactly is on the stack when a specific line executes, so we should not change anything between two lines. But there are some lines at which we know what is on the stack.
  26.  
  27. Every method has to contain at least one ret instruction. When the runtime environment reaches a ret, the stack must contain the return value and nothing else. That means, at a ret instruction in a method returning a Int32, the stack contains exactly one Int32 value. We could store it in a local variable, insert some code leaving the stack empty, and then put the return value back onto the stack. Nobody would notice it at runtime. There are much more lines like that, for example the closing brackets of .try { and .catch { blocks (definitly empty stack!) or method calls (only returned value of known type on the stack!). To keep the example simple, we are going to concentrate on void methods and ignore all the others. When a void method is left, the stack has to be empty, so we don't have to care about return values.
  28.  
  29. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  30. This is the IL Assembler Language code of a typical void Dispose() method:
  31.  
  32. .method family hidebysig virtual instance void
  33.             Dispose(bool disposing) cil managed
  34.     {
  35.       // Code size       39 (0x27)
  36.       .maxstack  2
  37.       IL_0000:  ldarg.1
  38.       IL_0001:  brfalse.s  IL_0016
  39.  
  40.       IL_0003:  ldarg.0
  41.       IL_0004:  ldfld      class [System]System.ComponentModel.Container
  42.           PictureKey.frmMain::components
  43.       IL_0009:  brfalse.s  IL_0016
  44.  
  45.       IL_000b:  ldarg.0
  46.       IL_000c:  ldfld      class [System]System.ComponentModel.Container
  47.           PictureKey.frmMain::components
  48.       IL_0011:  callvirt   instance void [System]System.ComponentModel
  49.           .Container::Dispose()
  50.       IL_0016:  ldarg.0
  51.       IL_0017:  ldarg.1
  52.       IL_0018:  call       instance void [System.Windows.Forms]System
  53.           .Windows.Forms.Form::Dispose(bool)
  54.       IL_0026:  ret
  55.     }
  56.  
  57. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  58. So what will happen, if we insert a new local variable and store a constant in it, just before the method returns? Yes, nothing will happen, except a little bit of performance decrease.
  59.  
  60. .method family hidebysig virtual instance void
  61.             Dispose(bool disposing) cil managed
  62.     {
  63.       // Code size       39 (0x27)
  64.       .maxstack  2
  65.       .locals init (int32 V_0) //declare a new local variable
  66.      
  67.       ...
  68.  
  69.       IL_001d:  ldc.i4     0x74007a //load an int32 constant
  70.       IL_0022:  stloc      V_0 //store the constant in the local variable
  71.       IL_0026:  ret
  72.     }
  73.  
  74. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  75. In C# the methods would look like this:
  76.  
  77. //Original
  78. protected override void Dispose( bool disposing ) {
  79.     if( disposing ) {
  80.         if (components != null) {
  81.             components.Dispose();
  82.         }
  83.     }
  84.     base.Dispose( disposing );
  85. }
  86.  
  87. //Version with hidden variable
  88. protected override void Dispose( bool disposing ) {
  89.     int myvalue = 0;
  90.     if( disposing ) {
  91.         if (components != null) {
  92.             components.Dispose();
  93.         }
  94.     }
  95.     base.Dispose( disposing );
  96.     myvalue = 0x74007a;
  97. }
  98.  
  99. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  100. We have just hidden four bytes in an application! The IL file will re-compile without errors, and if somebody de-compiles the new assembly, he can find the value 0x74007a.
  101.  
  102. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  103. [How to disguise a secret value]
  104. To make life harder for people who disassemble our application and look for useless variables, we can disguise the hidden values as forgotten debug output:
  105.  
  106. ldstr bytearray(65 00) //load an "A"
  107. stloc mystringvalue    //store it
  108. .maxstack  2           //set the stack size to exclude runtime exceptions
  109. ldstr "DEBUG - current value is: {0}"
  110. ldloc mystringvalue    //simulate forgotten debug code
  111. call void [mscorlib]System.Console::WriteLine(string, object)
  112. In order to stay invisible even in console applications, we should rather disguise it as an operation. We can insert more local/instance/static variables, to make it look like the values were needed somewhere else:
  113.  
  114. .maxstack  2  //adjust stack size
  115. ldc.i4 65     //load the "A"
  116. ldloc myintvalue //load another local variable - declaration inserted above
  117. add           //65 + myintvalue
  118. stsfld int32 NameSpace.ClassName::mystaticvalue
  119.     //remove the result from the stack
  120. This example demonstrates how to hide values at all, so only this version will be used:
  121.  
  122. ldc.i4 65;
  123. stloc myvalue
  124. There is no need to insert two lines for each byte of the message. We can combine up to four bytes to one Int32 value, inserting only half a line per hidden byte. But first we have to know where to insert it at all.
  125.  
  126. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  127. [Analysing the Disassembly]
  128. Before editing the IL file, we have to call ILDAsm.exe to create it from the compiled assembly. Afterwards we call ILAsm.exe to re-assemble it. The interesting part is between these two steps: We must walk through the lines of IL Assembler Language code, finding the void methods, their last .locals init line, and one ret line. A message can contain more 4-byte blocks than there are void methods in the file, so we have to count the methods and calculate the number of bytes to hide in each of them. The method Analyse collects namespaces, classes and void methods:
  129.  
  130. /// <summary>Lists namespaces, classes and methods
  131. ///  with return type "void"</summary>
  132. /// <param name="fileName">Name of the IL file to analyse</param>
  133. /// <param name="namespaces">Returns the names of all namespaces
  134. /// found in the file</param>
  135. /// <param name="classes">Returns the names of all classes</param>
  136. /// <param name="voidMethods">Returns the first lines of all method
  137. /// signatures</param>
  138. public void Analyse(String fileName,
  139.     out ArrayList namespaces, out ArrayList classes,
  140.     out ArrayList voidMethods){
  141.    
  142.     //initialize return lists
  143.     namespaces = new ArrayList(); classes = new ArrayList();
  144.     voidMethods = new ArrayList();
  145.     //current method's header, or null if the method doesn't return "void"
  146.     String currentMethod = String.Empty;
  147.  
  148.     //get the IL file line-by-line
  149.     String[] lines = ReadFile(fileName);
  150.    
  151.     //loop over the lines of the IL file, fill lists
  152.     for(int indexLines=0; indexLines<lines.Length; indexLines++){
  153.         if(lines[indexLines].IndexOf(".namespace ") > 0){
  154.             //found a namespace!
  155.             namespaces.Add( ProcessNamespace(lines[indexLines]) );
  156.         }
  157.         else if(lines[indexLines].IndexOf(".class ") > 0){
  158.             //found a class!
  159.             classes.Add( ProcessClass(lines, ref indexLines) );
  160.         }
  161.         else if(lines[indexLines].IndexOf(".method ") > 0){
  162.             //found a method!
  163.             currentMethod = ProcessMethod(lines, ref indexLines);
  164.             if(currentMethod != null){
  165.                 //method returns void - add to the list of usable methods
  166.                 voidMethods.Add(currentMethod);
  167.             }
  168.         }
  169.     }
  170. }
  171.  
  172. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  173. Given the number of usable methods, we can calculate the number of bytes per method:
  174.  
  175. //length of Unicode string + 1 position for this length
  176. //(it is hidden with the message)
  177. float messageLength = txtMessage.Text.Length*2 +1;
  178. //bytes to hide in each method, using only its first "ret" instruction
  179. int bytesPerMethod = (int)Math.Ceiling( (messageLength /
  180.     (float)voidMethods.Count));            
  181.  
  182. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  183. Now we are ready to begin. The method HideOrExtract uses the value of bytesPerMethod to insert the lines for one or more 4-byte blocks above each ret keyword.
  184.  
  185. /// <summary>Hides or extracts a message in/from an IL file</summary>
  186. /// <param name="fileNameIn">Name of the IL file</param>
  187. /// <param name="fileNameOut">Name for the output file -
  188. /// ignored if [hide] is false</param>
  189. /// <param name="message">Message to hide, or empty stream to
  190. /// store extracted message</param>
  191. /// <param name="hide">true: hide [message]; false: extract
  192. /// a message</param>
  193. private void HideOrExtract(String fileNameIn, String fileNameOut,
  194.     Stream message, bool hide){
  195.     if(hide){
  196.         //open the destination file
  197.         FileStream streamOut = new FileStream(fileNameOut, FileMode.Create);
  198.         writer = new StreamWriter(streamOut);
  199.     }else{
  200.         //count of bytes hidden in each method is unknown,
  201.         //it will be the first value to extract from the file
  202.         bytesPerMethod = 0;
  203.     }
  204.    
  205.     //read the source file
  206.     String[] lines = ReadFile(fileNameIn);
  207.     //no, we are not finished yet
  208.     bool isMessageComplete = false;
  209.    
  210.     //loop over the lines
  211.     for(int indexLines=0; indexLines<lines.Length; indexLines++){
  212.        
  213.         if(lines[indexLines].IndexOf(".method ") > 0){
  214.             //found a method!
  215.             if(hide){
  216.                 //hide as many bytes as needed
  217.                 isMessageComplete = ProcessMethodHide(lines,
  218.                    ref indexLines, message);
  219.             }else{
  220.                 //extract all bytes hidden in this method
  221.                 isMessageComplete = ProcessMethodExtract(lines,
  222.                     ref indexLines, message);
  223.             }
  224.         }else if(hide){
  225.             //the line does not belong to a useable method - just copy it
  226.             writer.WriteLine(lines[indexLines]);
  227.         }
  228.        
  229.         if(isMessageComplete){
  230.             break; //nothing else to do
  231.         }
  232.     }
  233.    
  234.     //close writer
  235.     if(writer != null){ writer.Close(); }
  236. }
  237.  
  238. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  239. [Hiding the message]
  240. The method ProcessMethodHide copies the method's header, and checks if the return type is void. Then it looks for the last .locals init line. If no .locals init is found, the additional variable will be inserted at the beginning of the method. The hidden variable must be the last variable initialized in the method, because the compilers emitting IL Assembler Language often use slot numbers instead of names for local variables. Just imagine a desaster like that:
  241.  
  242. //a C# compiler emitted this code - it adds 5+2
  243. //original C# code:
  244. //int x = 5; int y = 2;
  245. //mystaticval = x+y;
  246.            
  247. .locals init ([0] int32 x, [1] int32 y)
  248. IL_0000:  ldc.i4.5
  249. IL_0001:  stloc.0
  250. IL_0002:  ldc.i4.2
  251. IL_0003:  stloc.1
  252. IL_0004:  ldloc.0
  253. IL_0005:  ldloc.1
  254. IL_0006:  add
  255. IL_0007:  stsfld     int32 Demo.Form1::mystaticval
  256. IL_000c:  ret
  257.  
  258.  
  259. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  260. If we inserted an initialization at the beginning of the method, we could not re-assemble the code, because slot 0 is already in use by myvalue:
  261.  
  262. .locals init (int32 myvalue)
  263. .locals init ([0] int32 x, [1] int32 y) //Error!
  264. IL_0000:  ldc.i4.5
  265. IL_0001:  stloc.0
  266. ...
  267.  
  268. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  269. So the additional local variables has to be initialized after the last existing .locals init. ProcessMethodHide inserts a new local variable, jumps to the first ret line and inserts ldc.i4/stloc pairs. The first value being hidden is the size of the message stream - the extracting method needs this value in order to know when to stop. The last value hidden in the first method is the count of message-bytes per method. It has to be placed right above the ret line, because the extracting method has to find it without knowing how many lines to go back (because that depends on just this value).
  270.  
  271. /// <summary>Hides one or more bytes from the message stream
  272. /// in the IL file</summary>
  273. /// <param name="lines">Lines of the IL file</param>
  274. /// <param name="indexLines">Current index in [lines]</param>
  275. /// <param name="message">Stream containing the message</param>
  276. /// <returns>true: last byte has been hidden; false:
  277. /// more message-bytes waiting</returns>
  278. private bool ProcessMethodHide(String[] lines, ref int indexLines,
  279.     Stream message){
  280.     bool isMessageComplete = false;
  281.     int currentMessageValue,    //next message-byte to hide
  282.         positionInitLocals,        //index of the last ".locals init" line
  283.         positionRet,            //index of the "ret" line
  284.         positionStartOfMethodLine; //index of the method's first line
  285.    
  286.     writer.WriteLine(lines[indexLines]); //copy first line
  287.    
  288.     //ignore if not a "void"-method
  289.     if(lines[indexLines].IndexOf(" void ") > 0){
  290.         //found a method with return type "void"
  291.         //the stack will be empty at it's end,
  292.         //so we can insert whatever we like
  293.  
  294.         indexLines++; //next line
  295.         //search start of method block, copy all skipped lines
  296.         int oldIndex = indexLines;
  297.         SeekStartOfBlock(lines, ref indexLines);
  298.         CopyBlock(lines, oldIndex, indexLines);
  299.        
  300.         //now we are at the method's opening bracket
  301.         positionStartOfMethodLine = indexLines;
  302.         //go to first line of the method
  303.         indexLines++;
  304.         //get position of last ".locals init" and first "ret"
  305.         positionInitLocals = positionRet = 0;
  306.         SeekLastLocalsInit(lines, ref indexLines, ref positionInitLocals,
  307.            ref positionRet);
  308.        
  309.         if(positionInitLocals == 0){
  310.             //no .locals - insert line at beginning of method
  311.             positionInitLocals = positionStartOfMethodLine;
  312.         }
  313.  
  314.         //copy from start of method until last .locals,
  315.         //or nothing (if no .locals found)
  316.         CopyBlock(lines, positionStartOfMethodLine, positionInitLocals+1);
  317.         indexLines = positionInitLocals+1;
  318.         //insert local variable
  319.         writer.Write(writer.NewLine);
  320.         writer.WriteLine(".locals init (int32 myvalue)");
  321.         //copy rest of the method until the line before "ret"
  322.         CopyBlock(lines, indexLines, positionRet);
  323.        
  324.         //next line is "ret" - nothing left to damage on the stack
  325.         indexLines = positionRet;
  326.        
  327.         //insert ldc/stloc pairs for [bytesPerMethod] bytes
  328.         //from the message stream
  329.         //combine 4 bytes in one Int32
  330.         for(int n=0; n<bytesPerMethod; n+=4){
  331.             isMessageComplete = GetNextMessageValue(message,
  332.                 out currentMessageValue);
  333.             writer.WriteLine("ldc.i4 "+currentMessageValue.ToString());
  334.             writer.WriteLine("stloc myvalue");
  335.         }
  336.  
  337.         //bytesPerMethod must be last value in the first method
  338.         if(! isBytesPerMethodWritten){
  339.             writer.WriteLine("ldc.i4 "+bytesPerMethod.ToString());
  340.             writer.WriteLine("stloc myvalue");
  341.             isBytesPerMethodWritten = true;
  342.         }
  343.  
  344.         //copy current line
  345.         writer.WriteLine(lines[indexLines]);
  346.  
  347.         if(isMessageComplete){
  348.             //nothing read from the message stream, the message is complete
  349.             //copy rest of the source file
  350.             indexLines++;
  351.             CopyBlock(lines, indexLines, lines.Length-1);
  352.         }
  353.     }
  354.     return isMessageComplete;
  355. }
  356.  
  357. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  358. [Extracting the hidden values]
  359. The method ProcessMethodExtract looks for the first ret line. If the number of bytes hidden in each method is still unknown, it jumps two lines back and extracts the number from the ldc.i4 line, which had been inserted as the last value in the first method. Otherwise it jumps back two lines per expected ldc.i4/stloc-pair, extracts the 4-byte blocks and writes them to the message stream. If an ldc.i4 is not found where it should be, the method throws an exception. The second extracted value (after the number bytes per method) is the length of the following message. When the message stream has reached this expected length, the isMessageComplete flag is set, HideOrExtract returns, and the extracted message is displayed. Extracting works just like hiding in reverse direction.
  360.  
  361. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  362. [No key?!]
  363. Sure you'll have noticed that this application doesn't use a key file to distribute the message. An intermediate assembly contains less void methods than an intermediate sentence contains characters, so a distribution key as it is used in all preceeding articles would only mean pushing loads of additional nonsense-lines into a few methods, and that would be much too obvious.
  364. A key file for this application could specify how to disguise the values - debug output, operations, instance fields, additional methods, and so on. I'll add such a feature in future versions, if somebody is interested in it.
  365.  
  366. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  367. [Warning]
  368. This application works with the assemblies I've testet, but is might as well fail with other assemblies. If you find an assembly which causes it to crash, please tell me about it and I'll see what I've done wrong.
  369. License
  370. This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)
  371.  
  372. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  373. [Corinna John]
  374. [Software Developer]
  375. [Germany Germany]
  376. [Corinna lives in Hannover/Germany (CeBIT City) and works as a Delphi developer, though her favorite language is C#.]
  377.  
  378. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement