Better Disassembly Through Computable Trust About Me: Chris Parich Sophomore, University of Louisiana RITA August 2nd, 2010
Download ReportTranscript Better Disassembly Through Computable Trust About Me: Chris Parich Sophomore, University of Louisiana RITA August 2nd, 2010
Better Disassembly Through Computable Trust About Me: Chris Parich Sophomore, University of Louisiana RITA August 2nd, 2010 How Important is Correct Disassembly? Disassembly Affects: Code Obfuscation Algorithms Code De-Obfuscation Algorithms Code Watermarking (Stenography) Decompiling Techniques Or more generally Everything Correct disassembly affects all aspects of program analysis, reverse engineering, and binary modification. Why do you say “Correct”? Most program analysis techniques are created with the assumption that the entire binary is perfectly disassembled Where is this assumption safe? This works well for interpreted languages like Java, Python, and any other programmatic bytecode compiled language. Java intentionally compiles to a verbose format. All its type information and instruction information are clearly visible with static analysis This works well-enough for RISC architectures like MIPS, ARM, PPC, and SPARC RISC uses a single-size instruction format to help enforce simplicity And guess where it fails! On > 80% of the processors we run. And even more of the personal desktop market. For most of our application market. Issues in x86 Variable instruction length Instructions can range from 1 to 15 bytes The average instruction length in most programs is ~2.5 bytes long Most of the bulk of an average program is taken by 5+ byte instructions High instruction density Starting at an arbitrary offset in arbitrary data is highly likely to produce a valid x86 instruction. Starting at the very next offset is just as likely to produce another valid x86 instruction Extremely easy to confuse code with data How to correctly disassemble x86 Required knowledge The specific OS targeted The specific processor model targeted The language being used Optimizations for size and speed The list of all subroutine addresses GCC, MSVC, Borland, Intel, etc. The transformations being applied C, C++, D, Fortran, Iron Python, etc. The compiler being used to produce the binary Win32/64, Linux 32/64, OSX, BSD, Solaris, etc. Or at least all independent subroutines The entry point of the program (Where it starts). What information is available? The guaranteed-to-be-there information consists of: Targeted OS, Architecture Executable memory pages and Entry Point The might-be-there-in-well-behaved-code information appends: List of independent subroutines and Library and function import tables What to do? A collection of heuristic data are needed, both from the target application, and x86 applications in general. Instruction likelihood, or, the chances that a disassembled instruction is actually valid Instruction “except-ability”, or, the chances a disassembled instruction would cause a software or hardware exception. Instruction “overlap ability”, or, how easily can another instruction be disassembled from the same offset range. On Instruction Likelihood x86 Instruction Usage For a popular 3D Rendering Engine: Moves, Calls, Adds, Floating Operations Push, Cmp, Jmp, Pop less used For Glib 2.24.0 Mov, Call, Jmp, Test Important Arithmetic less necessary mov l call addl leal f lds subl pushl ret f stps cmpl jmp leav e je mov zbl popl movl c all leal je testl popl addl pushl jmp c mpl jne ret subl xorl On Instruction Exception Instructions can cause exceptions in several ways: A hardware exception A software exception A malformed instruction or A bug in the processor or Trying to execute when a no-execute bit is set An instruction that the OS deems illegal Accessing out of bounds of a programs allocation Out of allocatable memory An application exception Internally defined by the application for invalid input On Instruction Overlap Bytes read from one offset for a range will translate into a specific collection of instructions. Bytes read from an immediately proceeding offset and for the same range will translate into a likely different collection of instructions. x86 is known for having the ability to overlap different control flows together. Work is being done to find how possible this capability is to exploit or even just implement. A Means of Batch Disassembly There are two different kinds of disassemblers: A linear-sweep disassembler A recursive-traversal disassembler IDA Pro Linear-sweep OBJDUMP Most Debuggers Will disassemble more code, but not necessarily correctly Recursive-Traversal Will disassemble code more correctly, but only that which it can reach. Hybrid Disassembly Some disassemblers attempt to hybridize the disassembler, and make it a multi-pass analyzer. These disassemblers, in published implementations, are usually based on machinelearning techniques. Use neural networks or some other type of classifying mechanism to determine both is-a and is-not-a valid instruction. Usage is extremely slow and manual, since the machine must be trained on a crafted dataset. Batch Disassembly via Modified Recursive Traversal Acknowledge the multi-pass paradigm and embrace it. Make only provably true assumptions, for any available architecture. Disassemble from multiple offsets independently Only the entry point must be a valid instruction. Calls (or equivalent) do not have to return to the next offset. Conditional Jumps either do or do not occur. Finally, heuristically score instructions on likelihood of being valid. Trusted Disassembly Using the assumption that only the first instruction must occur, we can create a trust mechanism in produced disassembly. Trust only flows from a caller to a callee. No conditional flow can be trusted. The first executed block is trustworthy. Unconditional direct jumps are as trustworthy as the block they are part of. Where they go is as well. Calls are as trustworthy as the block they come from. Where they go is as well. Where they claim to return is not. Returns are as trustworthy as the block they come from. If they are not trusted, they might not occur. Trusted Disassembly Unconditional indirect Jumps as trustworthy as the block they come from. If the location is discoverable with the given trustworthy disassembly, then the location is as trustworthy as its caller. Conditional Jumps are NOT trustworthy. They give valid locations to disassemble, but not trustworthy locations. Trusted Disassembly If a trusted block directs to a known untrusted block in a trustworthy manner, then that untrusted block must be trustworthy. Multiple Passes Pass 1 Pass 2 The zero-offset of an executable section is often data, but is just as likely valid instructions as any other byte in the section. Any exported function from the Program's load header is a valid location for disassembly. Any discovered call of an imported function is a valid location to disassemble None of these locations are trustworthy. Malware can easily mess up these addresses to make disassembly difficult. The Trusted Pass Pass 3 Disassemble from the Entry Point. Only the entry point is a trustworthy valid block. All other trusted code must be discovered from here. Pass 4 Recurse through all discovered blocks, letting the discovered trust disseminate until no further “changes” are noted. This stage also scores discovered instructions heuristically. Purpose Trusted disassembly is applicable to Machine Learning mechanisms and Reverse Engineering / Program Analysis. The analyzer now has knowledge about what is undoubtedly going to occur in the program. This allows for faster and better analysis to build on top of this method. Questions? Questions? o/ <(Pick me!)