5

The first time I heard about binary disassembly I thought that it is something what can be called as perfect decompilation tool to assembly code and I still don't understand why it is not. I thought that assembly opcodes can be translated directly to binary sequences and directly back from binary sequences to opcodes but leater I heard about some things like possibility of mixing code and data and possibly some other things that make my thought about that I could dissasemble any binary and back assemble it to recreate the same binary impossible. Please don't downvote me immediately. I know practically nothing about reverse engineering and I'm thinking about starting my adventure with it. Can You please axplain me on some examples why things are like they are ?

2

First of all welcome to the world of reverse engineering, if there was really such a tool as a perfect disassembler this whole stack exchange forum wouldn't even exist.

Before tackling your question right away I'd like to start talking about what reversing really means and what it is all about as I see it as a more appropriate approach to your question.

The first question you ask given a binary file is "What?" I mean in both senses, a binary dump of what seems to be an infinetely large sequence of zeroes and ones and is in fact a your favorite video game or driver.

How do you interpret the data? Honestly, this sequence could be anything a text file, program, driver, image, music, video, some trojan etc.. and suppose you know it's one of the above, you still don't know how to interpret it, if it's some sort of media, what format is it (png, mp3, avi...)? If it is a program for which platform is it (windows / Linux) or even worse what CPU architecture is it even for (x86, ARM, PowerPC, MSP430...), and what version of that CPU is it for?. Wait but what if it is encrypted? What encryption is it? I believe you get the point by now.

The last paragraph is meant to give a sense to the ridiculously large amount of possibilities that this said series of code could represent. Now your question is specifically about code disassembly. Disassembly is exactly the process of converting the different binary sequences into their original opcode, however when you get a program and supposing you know the platform / CPU / version etc..

And, supposing the opcodes couldn't be mixed up. For example, Let 0101 (instruction a), 0011 (b) be opcodes, suppose there is also longer different opcodes 01010011 (c) and 00110101 (d). Given the sequence 0101001100110101 How do you know how to interpret the code (abba, cd, cab..)? (Spoiler: usually ISAs are designed in a way that such collisions wouldn't be made possible)

Great, we now supposedly have a perfect disassembler and now we want to get a step further, and get the original code. Here comes the problem

Take for example the following code:

.loop:
    xadd eax, edx
    loop .loop

Basically what we see here is a command of addition and exchange (add edx to eax and then switch their content) now the trivial way to make out the original code would be something like:

for (int i = n; i > 0; i--)
{
    a += b;
    switch(a, b);
}

However the smart reverse engineer could translate it as such:

genrate_nth_fibonnacci(n);

when eax and edx start at 0 and 1

Similarly, a series of random commands in a malware may be translated into "makeAntivirusNotNotice" function or in an otherwise legit program be a very efficient algorithm for a special case.

Thus, programming also has intention when writing the code so when you are trying to reverse a program the same code or as mentioned earlier seemingly chaotic series of bytes could have different meanings depending on context, a lot of high level code alternatives and as of the time of writing there still isn't a tool which can also predict the original programmer's intention. The best decompilers and reversing tools such as Radare and IDA try to analyze better and imitate such functionality but right now it is the reverse engineer's task.

  • 1
    Another great decompiler, free and easy to use, is retdec.com – savram May 16 '18 at 2:39
1

disassembly produces a human readable mnemonic form hexbytes

the processor understands 0's and 1's

the binary contains an encoded version of 0's and 1's in the form of hex bytes

if one needs to instruct the processor to mov 1 to register eax

it needs to be encoded as 0b1011100000000001000000000000000000000000

which when converted to hex (base 16 instead of base 2 as above ) will become > 0xb801000000

disassembler takes this > 0xb801000000 interprets and dispalys this hex bytes
as mov eax ,1

the high level languages does not know about registers you will write code like

int a =1;

this can be translated as mov eax,1 or mov ebx,1 or mov [addr] , 1

so converting back one of several forms back to original is not possible always

1

Consider reading chapters 1, 2 and 3 of Computer Systems: A Programmer's Perspective. It explains everything. Strive to gain an understanding of the fundamental concepts of computer architecture and programming languages before diving into reverse engineering.


The first time I heard about binary disassembly I thought that it is something what can be called as perfect decompilation tool to assembly code and I still don't understand why it is not.

It is important to understand what compilation is and what assembly is before attempting to understand decompilation and disassembly.

  • compilation is language-to-language transformation in which the original meaning is preserved. Typically, a computer architecture-independent language such as C is transformed into a architecture-specific language, such as x86 assembly language, which targets Intel i386 family CPUs.

    Here is an example of such a high-level language to low-level language transformation using GCC:

    Source C "hello_world.c" file (ASCII text):

    #include <stdio.h>
    
    int main(void) {
      printf("Hello, world.\n");
      return 0;
    }
    

    Compiler output (x86 assembly ASCII text generated using the -S flag):

    $ gcc -m32 -S hello_world.c 
    $ cat hello_world.s
      .file   "hello_world.c"
      .section    .rodata
    .LC0:
      .string "Hello, world."
      .text
      .globl  main
      .type   main, @function
    main:
    .LFB0:
      .cfi_startproc
      pushl   %ebp
      .cfi_def_cfa_offset 8
      .cfi_offset 5, -8
      movl    %esp, %ebp
      .cfi_def_cfa_register 5
      andl    $-16, %esp
      subl    $16, %esp
      movl    $.LC0, (%esp)
      call    puts
      movl    $0, %eax
      leave
      .cfi_restore 5
      .cfi_def_cfa 4, 4
      ret
      .cfi_endproc
    .LFE0:
      .size   main, .-main
      .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4"
      .section    .note.GNU-stack,"",@progbits
    

    As you can see, this is all ASCII text. There is no machine code here. An additional step is required in order for the CPU to execute this code.

  • the above x86 assembly language encoded in ASCII is transformed to a sequence of binary values that conform to the instruction set specification of the target CPU via an assembler. The assembler takes assembly language as input and from it will generate machine language that the CPU can execute. Machine language is not encoded in ASCII and is not human readable.

  • The role of a disassembler is to display machine-language operation codes as human-readable mnemonics. The output below is disassembly of x86 machine language:

     0804841d <main>:
     804841d: 55                      push   %ebp
     804841e: 89 e5                   mov    %esp,%ebp
     8048420: 83 e4 f0                and    $0xfffffff0,%esp
     8048423: 83 ec 10                sub    $0x10,%esp
     8048426: c7 04 24 d0 84 04 08    movl   $0x80484d0,(%esp)
     804842d: e8 be fe ff ff          call   80482f0 <puts@plt>
     8048432: b8 00 00 00 00          mov    $0x0,%eax
     8048437: c9                      leave  
     8048438: c3                      ret    
     8048439: 66 90                   xchg   %ax,%ax
     804843b: 66 90                   xchg   %ax,%ax
     804843d: 66 90                   xchg   %ax,%ax
     804843f: 90                      nop
    

    On the left are hexadecimal values of machine-language operation codes, and on the right are mnemonics corresponding to the machine-language operation codes.

  • decompilation can be understood in the following way:

    A decompiler, or reverse compiler, is a program that attempts to perform the inverse process of the compiler: given an executable program compiled in any high-level language, the aim is to produce a high-level language program that performs the same function as the executable program. Thus, the input is machine dependent, and the output is language dependent.

    In essence, decompilation is translation of machine language consisting of binary opcodes and operands to a architecture-independent language such as C. Disassembly and decompilation are quite different concepts and shouldn't be confused.


I thought that assembly opcodes can be translated directly to binary sequences and directly back from binary sequences to opcodes but leater I heard about some things like possibility of mixing code and data and possibly some other things that make my thought about that I could dissasemble any binary and back assemble it to recreate the same binary impossible.

When you say "assembly opcodes" what you mean is assembly language mnemonics corresponding to machine-language operation codes. While there is a strong relationship between assembly language and machine language, the relationship is not necessarily 1-to-1. Take a look at Why there are not any disassemblers that can generate re-assemblable asm code?.

  • the question 'Why there are not any disassemblers that can generate re-assemblable asm code?.' is what I wanted to know. Thanks for pointing this out – Daro May 15 '18 at 20:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.