home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #20 / NN_1992_20.iso / spool / comp / os / msdos / programm / 9098 < prev    next >
Encoding:
Internet Message Format  |  1992-09-08  |  8.2 KB

  1. Xref: sparky comp.os.msdos.programmer:9098 sci.math.symbolic:2331
  2. Path: sparky!uunet!convex!darwin.sura.net!spool.mu.edu!olivea!mintaka.lcs.mit.edu!zurich.ai.mit.edu!ara
  3. From: ara@zurich.ai.mit.edu (Allan Adler)
  4. Newsgroups: comp.os.msdos.programmer,sci.math.symbolic
  5. Subject: studying executables
  6. Message-ID: <ARA.92Sep6131908@camelot.ai.mit.edu>
  7. Date: 6 Sep 92 18:19:08 GMT
  8. Sender: news@mintaka.lcs.mit.edu
  9. Distribution: comp
  10. Organization: M.I.T. Artificial Intelligence Lab.
  11. Lines: 129
  12.  
  13.  
  14. For years I have had an interest in understanding, in the absence of any
  15. documentation, what a given executable does. I understand that one can simply
  16. use a debugger, preferably a smart one, and follow it step by step in
  17. the hope of discovering what the code does. The fact that DOS is nonreentrant
  18. has always been the primary obstacle to doing this, at least for me.
  19.  
  20. It seems to me that there are more enterprising things one can do, but I don't
  21. know how successful they can be. I would appreciate feedback on them and on
  22. tools that might be available or feasible to carry them out.
  23.  
  24. First, it seems to me that every compiler and every assembler must have its
  25. own idiosyncratic way of producing the executable. Is that true? If so, then
  26. it might be possible to have a database of idiosyncrasies that lets one
  27. figure out what compiler or assembler produced the executable. From that,
  28. one would know what the source language. For each compiler, there might
  29. be a specific decompiler that takes into account the special features of
  30. that compiler. Also, if by some miracle the code was compiled but
  31. not optimized, it seems like it would retain more features of the original.
  32. Or if compiled with C and debugging information was not stripped from the
  33. executable, that would be quite helpful. Knowledge of the source language
  34. and the specific compiler seems like it would be quite helpful.
  35.  
  36. The second idea is that even if one knows nothing about the source language
  37. or the compiler, one can still imagine tools that will give an overview
  38. of the structure of the program. The trouble with debuggers and disassemblers
  39. is that they work at the lowest level, one instruction at a time. Imagine
  40. instead an attack on the entire executable. First, one determines all of
  41. the bytes in the file that could be the first byte of an instruction,
  42. even if perhaps spuriously (since data could have bytes like that, for example,
  43. or starting an instruction on the second byte could look like some other
  44. instruction). Next, one picks out those places that could conceivably be
  45. jumps, branches, returns or maybe interrupts. This has the effect of cutting
  46. the code up in a natural way into intervals. At this stage, we don't know
  47. which of these intervals is really code and which is data. Next, one
  48. takes each interval and for each byte in the interval one examines
  49. the longest consecutive chain of bytes that constitutes a valid sequence
  50. of instructions. This will give us a collection of subintervals, some of
  51. them overlapping. Now, any consecutive sequence of instructions that
  52. does not jump or branch or return will have to involve one of these chains.
  53.  
  54. So one enumerates all these chains (just storing their endpoints is sufficient)
  55. and tentatively one treats them like a library of routines one can call,
  56. except we don't know what they do. But they are much smaller than the
  57. original executable and can be examined individually in detail if the need 
  58. arises. Even without stepping through them, it seems as though certain things
  59. can be done automatically, such as determining all of the registers and
  60. (perhaps unknwon) memory locations that are the "inputs" to that chain
  61. and all the registers and memory locations (perhaps unknown) that are the
  62. outputs.
  63.  
  64. It then seems conceivable that for each such chain, one could use or write
  65. a computer algebra program that determines how the outputs depend on
  66. its inputs. After all, we have removed all the noncomputational things like
  67. jumps and branches and interrupts and returns. So what it is doing is in effect
  68. implementing some disgusting formula.
  69.  
  70. At the moment, that is as far as I can see into the chains from a general
  71. point of view. On the other hand, we still have to look more closely at
  72. the branches, jumps, returns and interrupts. Now, none of these was actually
  73. known to be one, merely that it could be one. So one wants techniques to 
  74. determine whether they are really there or not and if so, where do they
  75. branch, jump, return to and which interrupt. In the case of interrupts,
  76. a certain register will be loaded with the number of the interrupt before
  77. the interrupt is executed. In code I have looked at, that happens immediately
  78. or almost immediately before, but that could be either a programming convention
  79. or one of the idiosyncrasies I mentioned that each compiler has. It is 
  80. conceivable that the register is loaded much earlier. So one has two choices
  81. here: (1) one can assume that if the register is not loaded within a certain
  82. number of instructions before the interrupt, then the interrupt is not real. 
  83. The number of instructions back one looks can then be called the "resolution" 
  84. of the search (2) one can look arbitrarily far back. This would probably
  85. be an act of desperation. I'll assume that one takes the first approach
  86. and has a fairly small resolution. Similarly, branches and jumps have some
  87. information regarding where one is jumping or branching to and that information
  88. would like in a certain register. So one can look at when that register was
  89. loaded, again with a certain resolution. I'm not sure what to do with returns,
  90. but if a return is genuine, it has to be regarded as terminating whatever
  91. chains it lies at the end of. Similarly, any unconditional jump instruction
  92. has to be regarded as terminating whatever chains it lies on. A conditional
  93. branch instruction will not in general terminate the chain it is on, since
  94. it ought to be conceivable that the branch condition fails and that the
  95. code continues. Therefore, a valid branch condition ought to be the nexus
  96. of two chains.
  97.  
  98. One of the lacuna of what has already been said is that it is hard to know
  99. what specific values one is working with in jumps, etc. One can also
  100. try to automatie some methods of getting those values. For example, look
  101. at every byte that can be a load immediate instruction and look at every
  102. byte that can be setting the value of the code segment, data segment,etc.
  103.  
  104. The more information one get get about the numbers that are actually
  105. being loaded in turn narrows the possibilities for where the jump and
  106. branches could be going, which in turn reduces the number of possible 
  107. chains. 
  108.  
  109. There is more one can do. For example, with interrupts, some interrupts are
  110. more likely to be used than others. I would expect that an interrupt to
  111. print something on the screen would be likely to occur at least once. One
  112. can also look through the executable for all sequences of ascii characters
  113. and to enumerate them. Some of them will be messages and one can note
  114. their locations. That gives some more clues to the numbers. There might be
  115. similar tricks one can do with other interrupts or for other ways in which
  116. strings are used (e.g. environment variables, file names...).
  117.  
  118. Assuming that one has determined the locations of the set of chains, jumps, 
  119. branches, interrupts, etc, that actually do occur in the program, one can
  120. then "condense" the executable down to a much smaller object, by treating
  121. each chain as an instruction in some "higher language". It is possible that
  122. some chains are contained in others, but that is not a problem, just as it
  123. is not a problem that the effects of some instructions include the effects
  124. of other instructions. Once "condensed" in this way, it becomes possible
  125. to study the program more directly.
  126.  
  127. Most likely, there will be more than one way to condense an executable
  128. given the information one has, so one might have to examine several 
  129. condensations. However, in principle each is a possible way to use the
  130. unknown executable.
  131.  
  132. Admittedly this does not solve the problem, but I feel that automating what
  133. I have described would put one in a position to take the next step.
  134.  
  135. I've posted this on comp.os.msdos.programmer because usually it is on 
  136. MSDOS systems that I am looking at executables. I am crossposting 
  137. to sci.math.symbolic because of the possible applications here of computer 
  138. algebra.
  139.  
  140. Allan Adler
  141. ara@altdorf.ai.mit.edu
  142.