Friday, June 23, 2017

Basics of Assembly

Introduction to Assembly

Hello everyone, it's me again bringing some basic stuff. Even being a basic stuff I hope that can help anyone. I am making this basic posts to my incoming ones, I will bring more technical analysis. Sorry for any mistakes that I maybe did on this post and if I did any, please send me a feedback.
Well, the assembly code it's the "only" way on reversing engineering. Or you interpret assembly mnemonics or you analyze opcodes (machine code). As said in the previous introductory topic (if you didn't read, I do recommend) the assembly code it's generated by the compiler for every code language that you use. So, understanding it is vital to RE. Assembly language doesn't is standart for all assemblers, the mnemonics(instructions) are different deppending of your processor architecture. Since the processor is made of several circuits and each processor has your own cricuits, the logic behind it's processing differs from each other. So the instructions that they "understand" is different from each processor architecture. In this blog I will give the approach of IA-32 architecture. I will not "teach", is more like an approach of what it is and how we work with. So knowing how the computer "works", as Memory-Data X Instruction-Operation. Logically the computer stocks all data on memory and all this data is interpreted and executed by the CPU.

 
Computer / Memory, CPU, IO Devices and BUS

The processor have some pointers to help the CPU keep track of what it need to do with what data. Then we have the instruction pointer and the data pointer. Through the posts we will see it in practice. So the instruction pointer points to the memory block that represents an instruction and the data pointer to a memory block that represents the data, then the CPU executes the instruction with the data that is pointed.

 
CPU Units X Memory

These pointers allocate the memory offsets in registers. As the processor executes these instructions the instruction pointer goes to the next instruction and the data pointer too. The instruction has between 1-3 bytes and is called opcode (operation code). So basically assembly has three "parts", opcode mnemonics, data sections and directives.

Opcode


Mnemonic code is the "english" representation of the instruction code, e. g. the '89' instruction is the 'mov' mnemonic. Different assembly types represent instructions differently.

 
OllyDbg / Right Assembly, Left OpCodes

Data


The data sections is the space used to store the data which the instrunction will use to execute, so this data can be in some memory section or it can use the stack (memory area, more later). All the data is stored in the hex representation and is referenced by it's memory address. So every data stored on the system has a memory address, it can be a immediate constant in the assembly code or it can be stored on the stack frame.

Directives


Directives are the elements used in assembly to tell the assembler (which compiles the assembly) how to interpret this type of data, data includes everything, like code and values. For example if you want to store a float value the assembler needs to know, then it can reserve memory properly. One of the important directives of assembly is the ".section" directive. This directive creates sections on memory for each type of data.

Sections


We can have any kind of section we want, but all programs have this by default:

  • .text:
    • All the code instructions are alocated in this section. No data is allowed here, except some fixed data of variables like a = 5, but that depends of the programmer on low-level programming and depends of the compiler on high-level programming.
  • .data:
    • This section is responsible for stores all data that the .text request. It will be referenced as an address in the .text section, like program.ADDRESS.
  • .bss:
    • This section generally used to unitialized data. I think the name might change from language to language, don't sure.


The IA-32 Architecture


The IA-32 architecture was designed for pentium processors by Intel. I don't know for sure if it is the most used nowdays, but it's a very known one and have a lot of documentation about it. When you learn assembly basis in one architecture, makes easy to learn in another one, because the base still the same. Some assembly let you do more others let do less, I think it's the basic difference. In my last topic I tried to wrote about computer in general form and I think that you already know how it is. I don't want to make this part too much extensive. So basically IA-32 is divided in 4 parts:

  • Control unit:
    • Control is responsible for bring all the information from memory, data and instructions. Then it decodes this instructions into micro-operations and pass to execution unit. The result of the operation is passed back for control unit that stores the result.
  • Execution unit:
    • Responsible for execute all the micro-operations.
  • Registers:
    • Registers are responsible to keep track of data that are been used. This registers are internal memory of the processor. Having this little memory inside the processor make it much more faster than going outside (from processor itself) searching data in RAM memory and retrieve it.
    • I want to keep it objective so I will not write about all of them. You can read in the book listed on the reference, I do recommend. So there are basically four types of registers (there are more like I said):
      • General Purposes: 8 32-bits registers. They are used to work with data.
      • Segment: 6 16-bit registers. Used to memory access.
      • Intruction Pointer: 1 32-bit register. Points to the next instruction to be executed.
      • Floating-point: 8 80-bit register. It used to work with floating-point numbers.
  • Flags:
    • Flags are used to keep control of the operations executed by processor. It's a way to know if some operation worked or not. There are specific type of flags to specific operations. We will see it.


Registers


General Purposes


These registers are mainly used to work with data as the code is been executed. All data used in the instructions are stored in these registers. They are 32-bits longer at the "top" level (32-bits), but it can be sliced in minor parts (16-bits, 8-bits) that stores minor data.

  • EAX (32-bits):
    • AH (16-bits):
      • AL (8-bits)
  • EBX (32-bits):
    • BH (16-bits):
      • BL (8-bits)
  • ECX (32-bits):
    • CH (16-bits):
      • CL (8-bits)
  • EDX (32-bits):
    • DH (16-bits):
      • DL (8-bits)
  • EDI (32-bits):
    • DI (16-bits)
  • ESI (32-bits):
    • SI (16-bits)
  • EBP (32-bits):
    • BP (16-bits)
  • ESP (32-bits):
    • SP (16-bits)

OllyDbg / Registers and Flags

These are the 8 32-bits registers that we will work with a lot. So it's important to say that modifing a top level register you will modify the low level register. For example if you put any value at AL and then put a new value on EAX the AL value will be overrided. Some of these registers are used in the default way. EBP and ESP for example are used to control the stack frame. Stack frame is a block of memory used to control some local variables of function/method. But you can use the stack when you need to call a method/function you push the parameter onto the stack so the call instructions can grab these parameters to call the method/function. As we analyze artifacts we will see the pattern of it's usage. Because of this it's important to know programming. The good way to learn it's to code and analyze it. On the later topics I will bring much more pratical examples with C language. It's amazing what we can do (I mean, you are managing energy, bro!! lol).


Segment


The segment registers are used to identify where data is located. Each segment register has a pointer to the section where it suposed to grab the data. The segment registers are:
  • CS: Code Segment.
  • DS: Data Segment
  • SS: Stack Segment
  • ES: Extra Segment
  • FS: Extra Segment
  • GS: Extra Segment
So each one is used in specific cases. For example, if you have a address memory on EAX (of data section) register and you have some data on EBX and want to save it on the .data section you maybe see that:

mov ds:[EAX], EBX

So you are moving the data on the EBX to where [EAX] pointing in the DS section. The [] brackets indicating that is a pointer to some memory address.

 
OllyDbg / Code acessing SS segment


We will see much more in pratical examples. The best way to learn.


Flags


The flags are maintained in a single 32-bits register called EFLAGS and each flag is represented by each bit. So as I said the flags are used to control if the operations that the processor executed worked or not. For example for conditional jumps through the code execution it checks if the Zero Flag (depends on the jump, others can be used) was set so it can know whether it will jump or not. There is an image as you already saw in registers topic. The flags is divided in three groups:

  • Status Flags
  • Control Flags
  • System Flags

I will only discuss about status. If you want to know more, there are books listed in the reference, I do recommend to read. The status flags are used to sign the result of mathematical operations executed by the processor. The flags are:
  • CF: Carry Flag.
    • Carry flag is used to manage the carry or borrow out in mathematical operations. It means that occured an overflow, i. e. there is some remaining data. Used in unsigned arithmetic.
  • PF: Parity Flag.
    • Is set when the result of the operation have sum of the 1's bit even number.
  • AF: Adjust Flag.
    • Used in Binary Coded Decimal (BCD), is set when the result of an operation is a borrow or carry. BCD it's a nice feature to work with decimals, if you want to know more at reference has what you need.
  • ZF: Zero Flag.
    • Is set when the result of an operation is 0.
  • SF: Sign Flag.
    • Is set in the most significant bit, when the operation results in a negative number.
  • OF: Overflow Flag.
    • Is used in signed integer operations and is set when the operation result is too large for positive numbers representation or too small in the negative numbers representation.

Stack


So now we are going to talk about the stack. Stack is very important in the reversing due it's common usage in the assembly world. The stack is a memory area where the program in execution uses to store short-term data. So the stack is generally used to:
  • Save register values
    • You can save the register in the stack to use it for another operation and then retrieve the old value to it.
  • Store local variables
    • Store the local variables at function scope. Like I said before, when doing it the variable is accessed directly using SS segment to access the offset in the stack.
  • Passing function parameters
    • To call a function generally you push all parameters on the stack from the right to left and call the function. For example, f(p1,p2,p3), you will pass p3,p2,p1.
  • Store the return address of the function call
    • The address of the next instruction after the call instruction, doing that the program will know where to back when it executes the retn instruction inside the function called.
Storing locally means that when you enter in a function scope all the local variables generally uses the stack to keep your data. In general everything is on memory, on the address space that the operating system gave us. The stack is just an area on that address space where the program uses to store data. So to put values on the stack you uses push instruction and to retrieve it you uses pop instruction. Everytime you enter a function a stack frame is set. Stack frame is set of addresses reserved to the function in execution, that set is limited by EBP(Base pointer) and ESP ('Top' Stack pointer). Generally this is how you see the stack frame been set:

PUSH EBP
MOV EBP,ESP
SUB ESP, SIZE

 First you save the old EBP, for when you back to the previous function will be able to reset the old stack frame, then you set the new base pointer and add the size (how many addresses) you need to that function. Stack are managed as LIFO type, last in first out. So the last element that you pushed onto the stack is the first element that will be poped out. But you can access this data directly if you need it, remember the SS segement ? So you can access using it together with the ESP (points to the top of the stack) register, like:

mov EAX, PTR SS:[ESP+4]

In the last instruction you moved the value of "ESP (plus) 4" pointing at to the general register EAX.

 Some important things about stack is that it grows up to lower addresses on IA-32 architecture. So bigger the stack is, lower addresses it access as we can see:

 
How stack works basically

In the image we can see the heap too and it's our next topic. This post I am bringing a theorical approach but in the next we will discuss it all in a pratical way. For now lets just abstract.


Heap


Heap is a memory area where the program uses for dynamic allocation. The heap is managed generally by the OS. So when the programmer need some space to store data that is bigger than the stack could manage, then the program store it in the heap memory. So the memory heap is passed to the program when the OS is loading it on memory. When you have a literal expression on your code like:

char newText[] = "Testing how code storing data.";

Regardless the data was inside some function the compiler generally uses the .data section and give to the instruction that will use this data the constant immediate address of the data, something like program.ADDRESS. But when you are loading something external for example, you don't know the size you going to need so the size is dynamic, then the program will have a routine that allocate the size you need in the heap.

 Heap it's important in RE because many program uses it to allocate data and sometimes identify which routine allocates the memory can be useful.

Now I think you are able to understand the incoming new posts that I will bring here. As I post new topics I will try to discuss a little more on technical topics. On the pratical part we will have a better view.

References


  • Google
  • Eldad Eilam - Reversing: Secrets of Reverse Engineering
  • Reverse Engineering Code With IDA Pro
  • Professional Assembly Language - Richard Blum

Sunday, June 4, 2017

Reverse Engineering - Didactic point of view ?

Hello folks!

Well, I'm starting this blog to produce content to the community and as a way to study about it. Sorry for any and all english mistakes :). Since I'm starting this blog I thought to bring an introductory text and increase difficulty as I make new ones, anyway I want to bring my parallel studies too. My goal it's generate information to everyone, trying making objective and didactic content. I'll try to translate all my posts from Portuguese to English. My focus is Portuguese, because our community is lack of Portuguese content. I hope you enjoy the content generated here.


If you have malware samples and wish to share with me before trash it, please send it to my e-mail pimptechh@gmail.com. I would be grateful. My intention is to bring the samples analyzed here. 


Reverse engineering is a complicated and polemic subject, because evolve many legal questions and for this reason is a theme that must be treated carefully. I will using the concept in software as it is a very embracing and could be referred in any engineered artifact. Basically it is a "deconstruction" of something that already is built. Many of us, human beings, use this knowledge to break software, in general known as "Crackers". Using this power to create a certain chaos on the system. Anyway this is very important process in the industry nowadays, software security, maintenance of software, software performance and so on. It's like a view on how it works and how can we make it better.

In reverse engineering is fundamental to have curiosity, since it is basically from where it arise the idea of reversing things. "Well, how would this was made ?" or "Well, why this is happening ?". A simple debugging, even with the source code on hands, would fit in this process. Trying to find some value or bug in the system. Therefore to understand a reverse process we must primarily have some basis on how this process or artifact was built in first place. If there is a need to revert a software, it's necessary to know at least how build one. Understand how a software works it's indispensable, since before just throw it in the reversing tools we must to know the basis. The fundamentals it's always important, I will try to dissertate the best way that I could to show the importance of the fundamentals by my studies.

Everything began, well I don't really know where it all began (still finding out), but we know the "first" big invent, or something like it of computer models that we have today, the ENIAC. This project already have a little collaboration of Neumann(creator of the actual computer model), helping solving some mathematical logic problem and right after ENIAC they start it's sucessor EDVAC. ENIAC basically did complex arithmetic calculations, although it didn't the concept of memory and programs. In the EDVAC version this concept started to be explored.


The modern computer "started" with John Von Neumann, a exceptional mind and memory. He could recite whole book chapters using exactly the same 
writed words without changing anything. Even reciting and translating simultaneously from German to English. In 1927 and 1928 published some papers about mathematical fundamentals of quantum theory and probability in quantum statistics, so if you don't believe in quantum mechanics or even doubt it, turn of you computer and throw it away haha. In these papers he demonstrated physicists phenomenal knowledge. The abstraction came from this extremely complex problems in which Von Neumann as was an expert. Clearly he was very ahead of his time. The history it's very interesting, worth read about it. There is a book "The computer and the brain" in which he tries to explain the guesses and concepts for the model designed by him. In this book he tries to do some analogy of our nervous system in a mathematical point-of-view using concepts of statistics and logic. Human body, biological machine.

When we thought about the magic computer makes,  it's amazing. Storage energy as the same time storage information and with that information solve problems fruits of intellect. The computer had it's necessity helping solving problems in a much faster way. Making complex calculations in unbelievable speed. Basically you insert an input, this value is stored in memory as energy/tension, this tension is differentiated between 0 and 1. All this stored within your computer and assisted by boolean algebra that generates our output, whatever it is, all depends of the problem you want to solve. When I say "problems" it's necessary an abstraction of what you consider a problem.

I don't know how used to be the conversation between Von Neumann and his mathematical friends, but would be very crazy for sure :P.

We solve a lot of problems every single day by the simple fact that we have stored energy in our computers. One of the programming concepts is here, abstraction, as higher is the layer you are more you abstracts the computation. When you need to talk to someone, all you need to do is choose your IM, send a message and solve the problem(talk to someone), just an example. Don't realizing this makes the paradigm breaking difficult. Math calculations like (2+2), you look at the operands, you realize that is a number 2, stored this information, added more one number 2 and then makes the logic addition (+) operation. You just reads the information stores it and operate it logically, just like and computer do. But how make all of this turn into information ? Rising the representation layers. We have in this case 0 and 1 stored as energy in our computer, this numbers can represent many thins like on and off, full and empty and so on. When we put blocks of 0 and 1 we have a lot of possible ways to represent information like number and letters.


All the information are disposed as 0 and 1, puting all them together forms blocks of information. All depends the way you want to interpret this information. Therefore by been 2 possible values we can assume that is base 2 representation. I'm not super math fan, but I can see the beauty. Anyway, how can we abstracts information from number base 2 like that ? Lets start by decimal base-10. Everybody, I do believe, saw on school about greatness, ways to represent this greatness like 10, 100, 1000 and so on. You implicitly understand these representation like ten, one hundred, one thousand and you can do that because the additional 0 at the end of each greatness. When we put the decimal digit together we are able to count (0,1,2,3.. 10,11,12...), each greatness break we have a new representation.


Is the same with binaries, but in binaries we change 0's and 1's places inside an information block. Here is an example of binary counting:



We can represent in this block-form or we can rise the way of representation to decimal numbers(friendly to us), converting base 2 to base 10 like follows:



Now, how is possible make operations with this primitive way of representation ? With boolean algebra. Inside the CPU have a lot of microcircuits of complex logic in which our information is treated. One of these microcircuits is the Half-Adder and I believe it is the smaller one:




In this circuit runs two inputs (A, B) of tension, "A" can be 0 or 1, the same for the "B" input. Now how this circuit really works ? Inside of microcircuit there is the logical gates like OR, Exclusive OR (XOR), AND, NOT and so on. This circuit have two outputs, sum and carry, like we saw in the previous image. With this we can add two bits like we do with numbers. Lets take this example (5+5=10), in this case sum is the value 0 and the carry is the value 1. The same with bits, e. g. like our Half-Adder we can "sum" two bits 1+1.




Doing that we realize another problem, because we know that the number 2 in binary is "10", right ? The Half-Adder did it's job, now we have to take care of this carry output, for this reason we have the Full-Adder (image below), it can receive three inputs A,B and the additional carry. Do you realize the problem abstraction ? Inside the Full-Adder we have the Half-Adder. Our CPU have a lot of extremely complex abstraction layers or microcircuits within each other, a lot of hours studying to really know all of it, however we will not design microcircuits we just want to know what our computer is made of and how it works, know the fundamentals. If is your desire to learn more, I'd recommend read about computational logic. There is many microcircuits embed in each other and the CPU is an arrangement of extremely complex circuits.




Right, now we have our 0's and 1's represented as information in blocks and been treated by the logic operators, making our machine generate our output. Even converting our information to decimal and after to hexadecimal, we will see it later, we have to understand that computer borned to solve complex mathematical operations and to do so it need to understand "complex" numbers. I'm not a mathematician, so sorry any mistakes. We have topics to see before get into hexadecimals, it's the main way to work with data in our field. Machine needs to interpret negative numbers, for this we have the Signed number system (is good topic to write about it later desmistify Signed and Unsigned in language C). With this system we use a most significant bit (msb) to represent the sign of the number (0)0000001, in this case the decimal number is "1" positive. 0 stands for positive and 1 to negative. Using the first bit or msb we are unable to represent the decimal 0 to 255, with that one bit less we can now represent only -127 to +127, because the msb was cut from our 8bit block. Have the solution and not having it, right ? Because, how do we will operate this logically ? Let's to the example 2 + (-3):




As we can see isn't the desired result, right ? To solve this we have the "Complement". The first version of the complement is the "One's complement", basically we have to flip all the bits in the block for it's opposite like this, "00000010"(+2) to "11111101"(-2). But the problem was solved partially since our result isn't the correct value yet as we can see in the next example:




Just to remember that the carry from the previous operation is carried out of the 

8bit result, in the third position from right to left then operation generates an carry that is carried to left until it's is carried out. As we can see the result is -1 of the correct result, would must to be "00000010" but instead we get "00000001". How would be possible to fix this ? If we add 1 bit to this result it would solve our problem. So the "Two's complement" came to help us doing exactly that. Let's see in the next example:



The "Two's complement" came to solve the "One's complement" problem. So let's suppose the same type of operation, but this time we want to do it with a negative number been bigger than the positive. Let's see 4 + (-5):




Well, as we can see in the previous example the value isn't the correct when the number negative is bigger. When this happen it's necessary to take this result and applies the complement on it again, flipping the 0's to 1 and the 1's to 0, then add 1 bit to it and grabbing that last carry to the new result. The last carry will sign our new number. If we do that our new result would be signed "10000001" (-1). The CPU uses flags to do this type of operation, in this case it uses the (S)ign Flag, but let's talk about it in another text.


Let's proceed to the base-10 conversion that is the number notation we use on daily basis. However we will see base-16 too and this particularly is very important since we use it a lot in reverse engineering. But before we do that it's important to know the bit-block sizes or rotulation it's very used on RE too. Each bit-block have a size or "name" to differentiate how long is the information inside of it. Let's see in the next example:



4 bits = 1 Nibble
8 bits = 1 Byte 
16 bits = WORD 
32 bits = DWORD
64 bits = QWORD


Now with hexadecimals we can encapsulate a larger quantity of bits in only one digit of hexadecimal. Let's take the block "1111" as example, in decimal the value is "15". Converting this previous value to hexadecimal we have "F", just it, one digit of hexadecimal and with two digits we can represent 8bit values. Let's see an example how count in hexadecimal:



There is many sites to convert hexadecimals, some examples:


  • http://string-functions.com/hex-string.aspx
  • http://string-functions.com/hex-decimal.aspx
To do all this we need to store all that information to work with it. In nowadays the way to do this is using Memory RAM. Memory RAM stands for Random Access Memory and are cells that stores energy/tension or information, call as you want, this type of memory are volatile, i. e. when the energy power is cut all the memory is lost. This mean that the CPU have to check this memory every cycle to maintain the energy already stored there, avoiding energy leaking.

So, now we have the energy stored, the CPU working logically with information, but how these information flows through the computer ? Well, to do this job we have the BUS of data. When we think about BUS of data it's necessary to abstracts the term to make it easy to understand. Just imagine a tunnel where all 0's and 1's passes freely. But how we manage this information passing through the channel ? I mean, how to address origin and destination of data ? For this we use the Multiplexer and Demultiplexer. These are circuits that manage from where to where, they pick the required information from the tunnel. It's like encode and decode the information. It's a complex topic, so if you interested I recommend to read about it. In the reference listed there is some good information.

So far we know how the computer interacts and how it operates all the information stored in it, however I would like to remember that all this I'm writing here is very basic, our computer is much more complex than that. Anyway the fundamentals is always the same, information is the same, bits are the same, how information is stored remains the same. I see this text as a way to think about computation. So everything is possible! We just have to visualize knowing how it works. Ok, how to interact with this incredible machine ? The answer is OS (Operating System) he manage everything for us, all the memory, all the low-level part of the machine. So everything passes through the OS, all the tasks uses the OS to do something. In reverse engineering we have to keep this in mind, you want to figure how something works ? First figures how it will be interact with the OS, starts from there. The OS encapsulates all the methods to manage your machine in different layers of abstraction. Want to know how it works ? Look for OS internals books. Worth the time reading I guarantee.

Well, now we know how the machine operates and what helps us to operate it. In the user layer of OS we already are in the tops of the abstraction layer everything is prettier and easier visually. So do the programming languages, knowing how programming it's essential. Many people may say that you don't really know how programming, but I ensure you that everything it's more easier when you know how programming. Our entire computer do what we say because of it. The only language computer know is machine language with all it's operators and operands. Any language that you use turns into machine language in the end. Nowadays we have what we call High-Level languages, they are called like that because your commands are close to our formal language in "real" life.

There many types of high-level languages, but there are two of them that is most used. Java and C# they are high used in daily basis. It let us solve problems in a much faster way, but that not implies performance because of encapsulation and abstraction. Higher you are in abstraction layer more technology you have under you, this means many things that you can't control. In general these languages runs within a virtual machine, so in this case the OS doesn't interact direct with your program, but instead with their virtual machines. Let's see an example of languages life cycle:


High-Level => Compiler =>Assembly => Machine Language

As we could see this cycle stands for languages that are directly compiled. Java and C# doesn't runs directly, instead they use a virtual machines like I already said. The cycle I've shown it's for languages like C/C++ that are compiled and runs directly with OS, there is no virtual machine between them. At low-level layer we have the assembly language that in fact is the machine code, but translated to assembly. Assembly language basically are the mnemonics from real machine language. Java have the JVM (Java Virtual Machine) and the C# have the CLR (Common Language Runtime), they both are virtual machine that interacts with the OS. These languages doesn't generates machine code, instead they generate bytecodes and only can be interpreted by their virtual machines. So to reverse it it's a little different, more easier I would say, because bytecode is a type of machine code but in high-level and more readable.

In reverse engineering we must be conscious about fundamentals, because we work directly with machine code/assembly. And this language works directly with the machine, with memory, deal directly with CPU and it's architecture like any high-level language but with a little different concepts. When you change the language you have to know how iteract with it, same with assembly and CPU architecture, but in general operations are the same. "Any" language have loops, variables, constants and so on. The same is for CPU and it's architecture. So I freeze that is important to programming and if you want to go down this path, you have to learn C/C++. If my opinion worth something, go for C :). When you understand low-level world well and how to interact well with C, then you should maybe migrate to C++ or don't hehe. Anyway OS is the key to reverse anything that is interacting with it. Let's see the below image and try to abstract your machine after read all this text:


Operating System | Third Layer
|||
Instruction Set Architecture | Second Layer
|||
Microarchitecture (Hardware) | First Layer

Conclusion

Well, this is my first text about the introductory part of reverse engineering, I hope that could help anyone. It's a magical world, in fact very wonderful world! The question is how far down the rabbit hole do you want to go ? I want to keep this blog always updated, trying to help the community to grown as I do. I will try to be most objective and didactic as possible to help everyone to understand the concepts. In the next texts I wish to bring more technical stuff like, malware analysis, solving puzzles, programming assembly/c and so on. Feel free to ask and criticize. My only goal is to learn and help the community. Let's share, let's grown together.

Regards from Pimptechh and nice studies.

References:
  • Google
  • Andrew S. Tanenbaum - Structured Computer Organization
  • Eldad Eilam - Reversing: Secrets of Reverse Engineering
  • Richard Blum - Professional Assembly Language (2005)

Engenharia Reversa - Ponto de vista didático ?

Olá para todos!

Bom, estou iniciando esse blog para gerar conteúdo à toda a comunidade e como forma de estudo também. Como estou iniciando o blog achei melhor trazer um texto introdutório e a ideia é ir elevando o nível com o passar dos textos. Não é nenhuma super revelação esse post, porém acho importante dissertar sobre. Meu único interesse é gerar informação para todos, quero continuar trazendo conteúdo didático e objetivo para aprendizado. Tentarei traduzir meus posts e postá-los em português e inglês.

Caso tenham recebido malwares por e-mail e quiserem compartilhá-los comigo. Podem me enviar, ficarei agradecido. pimptechh@gmail.com.

Engenharia reversa é um assunto bem polêmico, pois envolve muitas questões legais e por esta razão é um tema que deve ser tratado com muito cuidado. No caso estarei utilizando o conceito para software de computador, visto que é um termo bem abrangente e pode ser referido para qualquer tipo de artefato fruto de engenharia. Basicamente se trata da desconstrução de algo que já está pronto. Muitos utilizam deste conhecimento para quebrar software, muitas vezes citados pelo termo "Cracker" que remete a "Crack" (quebrar) unindo o "er" no final ficaria algo como "Quebrador" ou o usuário que irá quebrar o software. Contudo é um processo muito importante hoje em dia na indústria, nos quesitos de segurança de software, manutenção de software, performance de software e etc. É uma visão de como funciona para que então possamos melhorar ainda mais o software em si.

Em engenharia reversa é fundamental que se tenha curiosidade no geral, pois é basicamente dai que nasce a ideia de reverter algo. "Ora, mas como será que isso foi feito ?" ou "Ora, por que será que isso está acontecendo ?". Um simples debugar de um programa com o código fonte em mãos já se encaixa no processo, a fim de descobrir o motivo de determinado valor ou bug, por exemplo. Portanto para entender um processo reverso devemos primeiramente ter uma base de como o processo foi desenvolvido em primeiro lugar. Se há a necessidade de se reverter um software é necessário que saiba ao menos como desenvolver um software. Entender o funcionamento de um software é imprescindível, pois antes de jogar nas ferramentas que auxiliam o processo temos de ter no mínimo uma base. A base é sempre importante, tentarei dissertar da melhor forma possível para tentar passar a importância da base pelos meus estudos.

Tudo começou, bem não sei exatamente onde tudo começou (ainda estou descobrindo), mas sabemos do "primeiro" grande invento ou algo aproximado do modelo de computadores que temos hoje, o ENIAC. Esse projeto já possuía o dedo de Neumann(criador do modelo atual de computadores), ajudando a resolver alguns problemas lógico matemáticos. ENIAC basicamente fazia cálculos aritméticos complexos, porém não possuía o conceito de memória e programa. Depois do ENIAC nasceu o EDVAC um pré computador moderno, com conceito de memória e programa.

O computador moderno "começou" com John von Neumann, uma mente brilhante com um memória fantástica. Recitava capítulos inteiros de livros usando exatamente o que estava escrito sem trocar nem acrescentar. Capaz até de recitar livros em alemão traduzindo simultâneamente. Em 1927 e 1928 publicou vários "papers" sobre fundamentos matemáticos da teoria quântica e probabilidade na estatística quântica, então se você não acredita em mecânica quântica ou duvida, desliga seu pc e joga fora hahaha.. Nesses "papers" ele demonstra o conhecimento nos fenômenos físicos. A abstração vem desses problemas extremamente complexos que por sinal Von Neumann dominava. Se ele não era um cara a frente do tempo, então não sei. A história é bem interessante, vale a pena ler. Tem um livro chamado "The computer and the brain" em que ele tenta explicar os conceitos e palpites que teve durante os testes com o modelo projetado principalmente por ele, nesse livro ele tentar fazer uma analogia entre o nosso sistema nervoso de um ponto matemático, utilizando conceitos de lógica e estatística. Corpo humano, máquina biológica.

Quando pensamos na mágica que o computador faz, isso o deixa ainda mais bonito haha. Armazenar energia ao mesmo passo que armazena informação, e com essa informação resolver problemas frutos do intelecto. O computador teve sua necessidade para ajudar a resolver os problemas de forma muito mais rápida. Fazendo cálculos complexos em uma velocidade difícil de imaginar. Basicamente você insere um "input" esse valor é armazenado em memória na forma de energia/tensão, essa tensão é diferenciada entre 0's e 1's. Isso tudo armazenado dentro do seu computador, e com auxilio da lógica booliana temos nosso "output", seja ele qual for, depende do tipo de problema que você quer resolver. Quando digo isso, é necessária abstração do que você encara como um problema. Não sei como era a conversa de Von Neumann e seus amigos matemáticos, mas a conversa deveria ser bem maluca ;p. 

Nós resolvemos e criamos muitos problemas todos os dias pelo simples fato de se ter um monte de energia estocada dentro do seu computador. Um dos conceitos de programação está aqui, a abstração, quanto mais no topo você está, mais você abstrai a computação. Quando você precisa falar com alguém, você chama este alguém no seu IM de escolha e resolver o problema(falar com este alguém), apenas um exemplo. Não perceber isso dificulta a quebra de paradigma. Contas de matemática (2+2). O que você fez ? Você olhou o 2 entendeu que é um 2, armazenou essa informação, adicionou mais 2 e então operou de forma lógica "+", obtendo o 4. Isso pode ser abstraído de por exemplo, olha tenho 2 maças, se como uma sobra só mais 1. Então você observa, estoca informação e processa, exatamente igual o computador. Agora como fazer isso tudo virar informação ? Subindo as camadas de representação. Temos nesse caso 0 e 1 armazenados em forma de energia no nosso computador podendo representar informações como ligado e desligado, cheio e vazio, e etc. Quando formamos blocos destes bits/valores temos um acervo maior de possibilidades. Como números, letras e etc.

Todas as informações estão em 0 e 1, juntando todos os 0's e 1's em blocos temos as informações. Tudo depende da maneira como você deseja interpretar esta informação. Portando por serem 2 valores possíveis podemos então deduzir que é uma representação de base 2, ou seja 2 possíveis valores. Não sou super fã de matemática, porém sei apreciar a beleza. Enfim, como podemos abstrair essas informações de conjuntos de 0 e 1 ? Vamos começar pelos decimais base 10. Todo mundo, acredito eu, viu na escola sobre grandezas e formas de representar essas grandezas, por exemplo 10, 100, 1000 e assim sucessivamente. Você implicitamente entende essas representações como dez, cem e mil, e consegue discernir isso pois adicionamos um 0 no fim de cada grandeza, desta forma elevando-as. Adicionando os dígitos decimais nós podemos contar (0,1,2,3... 10,11,12...), a cada quebra de grandezas temos uma nova representação.

O mesmo se dá com binário, porém alternando o 1 e o 0 de lugar. Na imagem abaixo é possível ver uma contagem com binários:



Podemos representar ou subir para uma representação mais amigável(numérica) convertendo a base 2 para a base 10 (a qual estamos acostumados) como pode ser demonstrado a seguir:



Agora, como é possível fazer operações com formas tão primitivas de representação ? Aqui entra o que citei lá em cima, lógica booliana. O que tem dentro da sua CPU são microcircuitos de lógica cada vez mais complexos. Temos o que eu acredito ser o menor microcircuito Half-Adder:





Nesse circuito correm dois inputs (A e B) de tensão, "A" pode ser tanto 0 quanto 1, o mesmo se da para o "B". Agora como funciona esse microcircuito ? Dentro do microcircuito existem os chamados portões lógicos como OU (OR), OU Exclusivo (XOR), E (AND), Negação (NOT) e etc. Esse circuito tem 2 outputs, soma (sum) e o valor carregado (carry), como vimos na imagem anterior. Com isso podemos adicionar 2 bits, assim como fazemos com 2 números. Pegue o exemplo de (5+5=10), tenha como sum o valor 0 e o carry o valor 1. O mesmo se dá com a soma de bits, por exemplo com o nosso Half-Adder podemos somar dois bits. 1+1.



Com isso criamos outro problema, sabemos que a representação do número 2 é "10" em binário, certo ? O Half-Adder fez o trabalho dele, agora temos que dar conta desse carry ai, por esta razão veio o Full-Adder (imagem abaixo), que por sua vez recebe 3 inputs, A, B e o carry. Percebem a abstração dos problemas ? Dentro do Full-Adder temos um Half-Adder. Nossa querida CPU tem muitas camadas de abstração extremamente complexas são muitas e muitas horas de estudo aprofundado, contudo é bom saber a base pelo menos, certo ?. Sugiro quem tiver interesse, a pesquisar por organização lógica computacional. Existem vários microcircuitos embutidos um dentro do outro e a CPU em si é um arranjo de circuitos extremamente complexos. 



Certo, agora temos nossos 0's e 1's representados como dados em blocos e sendo "operados" pelos seus respectivos operadores lógicos, fazendo nossa máquina gerar nossos outputs. Contudo mesmo com as conversões de dados para decimal e posteriormente hexadecimal, que veremos mais abaixo, temos que entender que primeiramente o computador foi necessário para realizar cálculos matemáticos complexos isso inclui números com domínio nos REAIS, certo ? Cálculos complexos necessitam de números "complexos", não vou me aprofundar muito até porque não saberia explicar com prioridade. Temos dois importantes tópicos para serem discutidos antes de avançarmos para a conversão de hexadecimal que é a principal forma de representação de dados que iremos trabalhar. Houve a necessidade de representar os números negativos para realizar as operações, para isso utilizaram o sistema de "Signed" (Que também é um assunto bem discutido quando falamos da linguagem C junto com "Unsigned", vamos chegar lá!). Isto significou utilizar dentre os 8bits de dados o bit mais significativo ou o primeiro bit (0)0000000 para representar "+" e "-", sobrando então 7bits para representar o valor. Desta forma não podemos representar mais de 0 à 255, pois o bit mais significante (msb=most significant bit) está sendo utilizado para representar se ele é positivo ou negativo. De forma que agora podemos representar entre -127 à +127, pois o primeiro bit (msb) foi "cortado" para representar o sinal do número. Contanto com essa forma de representação os nossos cálculos com binários ficaram falhos, certo ? Vamos ao seguinte exemplo (+2) + (-3), deveria dar -1, correto ? Vejamos o que acontece:



Como podemos ver ao somar dois números, sendo um negativo e o outro positivo temos um resultado errado. Por esta razão foi implementado o "Complemento" (Complement). Em sua primeira "versão" chamado de "One's complement", que nada mais é do que a inversão do número signed, por exemplo o número "00000010" (2) ao receber o complemento fica "11111101" (2), mas mesmo utilizando esse conceito o resultado sai como -1 do valor correto. Vamos ao exemplo 4 + (-2):



O resultado da operação resultou em "00000001" que em decimal é 1. Lembrando que nesse sistema o último carry é sempre descartado, como podem ver na tarceira casa da direita para a esquerda nossa operação de adição gera um carry que é carrregado para as operações restantes e que no final é descartado por sair do bloco de 8bits. Então para mostrar corretamente o valor da operação é necessário adicionar +1 ao resultado final. Por esta razão o "Two's complement" veio para resolver este problema. Vejamos no exemplo:



Basicamente o "Two's complement" adicionar + 1 ao final da operação resolvendo o problema com a falha do "One's complement".
Contudo ainda sim temos um pequeno problema, mas fácil de ser resolvido. Imaginando que o resultado da operação seja negativo, ao realizá-la nos deparamos com o seguinte problema 4 + (-5):



Como vemos na imagem acima o valor fica "errado" quando tentamos utilizar a soma com o número negativo maior que o número positivo, então quando isso acontece é necessário refazer o complemento do número no resultado da operação, ou seja fazendo um flip de 0 para 1 ou 1 para 0 em cada bit, adicionando (+1) ao resultado deste flip e trazendo o carry restante da operação para o novo número de forma que ele possa ser representado em forma de signed "10000001" (-1). A CPU utiliza de uma das flags, mais precisamente a flag (S)ign Flag para fazer esta operação, veremos isso em outro texto

Bom, depois de toda essa complicação hehe, vamos seguir com a conversão para base 10 que é a forma numérica mais próxima do entendimento humano. Contudo não paramos por aqui, pois em engenharia reversa utilizamos base 16 para representação dos dados, então ao interagir com dados em memória eles estarão em hexadecimal, até quando eu não sei =). Mas antes vamos entender como é a rotulação desses blocos de binário, pois é importante saber. Cada bloco/conjunto de binários possui um nome ou tamanho, que é um parâmetro para saber a quantidade de informação que aquele bloco de binários possui.

4 bits = 1 Nibble
8 bits = 1 Byte 
16 bits = WORD 
32 bits = DWORD
64 bits = QWORD

Com a introdução do hexadecimal podemos encapsular uma maior quantidade de dados dentro de um único dígito. Por exemplo, o bloco "1111" em decimal é "15" em hexadecimal é "F", portanto podemos representar 4bits com apenas 1 dígito de hex e com 2 dígitos podemos representar 8bits de dados. Hexadecimais tem uma tabela bem tranquila de se entender:



Existem diversos sites que fazem a conversão de hex para decimal e string, seguem alguns exemplos:

http://string-functions.com/hex-string.aspx
http://string-functions.com/hex-decimal.aspx

Para fazer tudo isso funcionar precisamos estocar tudo isso e então trabalhar com essas informações, a forma como utilizamos hoje para estocar essas informações são as memórias RAM. Memórias RAM (Random Access Memory) são células que armazenam essa informação, assim como todos sabem ela é volátil, pois ao cortar a energia os dados que são a energia, são perdidos. A memória RAM precisa ser "revisada" por assim dizer, em ciclos, para checar quais os valores que estão estocados nas células e garantir que eles continuem lá. E quem estoca e admnistra essa informação é o cérebro, também conhecido como CPU. É necessário garantir que os valores que foram estocados não "descarreguem".

Bom, agora como essas informaçõs são trafegádas ? Através dos BUS de dados. Quando vemos "BUS de dados" é necessário uma abstração, imaginar um canal, um meio pelo qual todas as informações estão sendo transmitidas e recebidas entre os componentes do nosso computador. Todos nossos 0's e 1's transitam por esses BUS de dados, esse canal de informação. Ai temos um novo problema, como eu vou comandar minha CPU estocar e manipular essa informação estocada ? Como ele vai saber onde buscar e para onde levar essa informação ? Qual algoritmo utilizamos para resolver esse problema ? Multiplexer e Demultiplexer, são os circuitos que cuidam desse "transporte" ou rotulação de endereços de origem e destino e virse versa, recomendo ler ler um pouco melhor sobre eles. Nas referências listadas abaixo tem um bom conteúdo.

Até agora "sabemos" como o computador interage e como opera as informações que nele são estocadas, contudo gostaria de lembrar que é muito básico o que eu estou escrevendo aqui, é a base. Nosso atual computador é MUITO complexo com muito mais componentes, porém a base é a mesma, os bits são os mesmos, o estoque de informação é o mesmo, a forma de operar a informação ainda é a mesma. A base ainda reside no estoque de informação e operação lógica dessas informações. Eu vejo esse texto como uma forma de pensar sobre computação. Sabendo a base o resto fica por conta da imaginação. Agora como fazer para interagir com essa máquina de complexas operações ? Temos então o SO (Sistema Operacional), o sistema que facilita operarmos a máquina. Em engenharia reversa devemos ter isso muito focado em nossa mente, tudo passa pelo SO. Então uma operação específica tem um certo padrão, o que quero dizer é que todas as funções que um software desempenha é o SO quem garante isso, "abstraindo" a conexão direto com o hardware, com a linguagem de máquina.

O sistema operacional conta muito na hora de se reverter algo, ele encapsula/abstrai os métodos necessários para a interação com a máquina. Entender a forma como ele trabalha é um trabalho árduo que requer tempo de estudo, e acima disso, experiência e prática. É bom deixar isso frizado na mente, leva-se tempo para ir digerindo tudo, por isso é importante estar sempre pensando sobre o funcionamento. Se formos voltar no tempo e analisar o histórico dos SO's para o atual é uma mudança enorme, muitos paradigmas quebrados com muitas novas tecnologias implementadas. Conforme nossos problemas ficam mais complexos necessitamos de mais camadas de abstração, por isso temos linguagens de programação de alto nível que encapsulam funcionalidades para agilizar a solução dos problemas, subindo cada vez mais camadas para facilitar as nossas vidas de operar máquinas computacionais. 

Para se comandar uma máquina computacional é necessário uma linguagem que instruam comandos para a máquina, e como sabemos, a linguagem que de fato interage com o computador é a linguagem de máquina, com seus operadores e operandos. Tudo, no fim das contas é linguagem de máquina. É só o que o seu computador entende, você pode usar qualquer linguagem que no fim você está utilizando linguagem de máquina para operar sua máquina. O que temos hoje são linguagem de nível maior, ou seja, que podemos ter um contato mais "humano" mais real do nosso dia-a-dia. Linguagens como Java e C# são encapsulamento das linguagens de baixo nível.

Hoje temos essas linguagens que eu citei que acredito serem as "mais" utilizadas, pois são muito mais rápidas na solução de problemas, porém não necessariamente de performance. Pois quão mais alto você estiver nas camadas de abstração/encapsulamento, mais tecnologia você tem abaixo que resolvem muitos problemas para acelerar nossas soluções, porém tudo em computação é suscetível ao erro. Então quando falamos de linguagens de baixo nível queremos dizer que são as linguagens mais próximas da máquina. Esse é o modelo atual das linguagens:



É importante salientar que assembly é linguagem de máquina, o que ocorre é uma tradução da linguagem de máquina para mnemônicos. Chamamos esses mnemônicos de assembly. Esse fluxo da linguagens aplicam-se apenas para as linguagens compiladas como C/C++. Java e C# (entre outras) não rodam diretamente pelo computador, eles tem o intermédio da suas respectivas máquinas virtuais. Java possui a JVM (Java virtual machine) e o C# o CLR (Common language runtime), essas máquinas virtuais por sua vez é que interagem com o SO e o computador em si. Quando "compiladas" ambas geram o que chamamos de bytecode que é a "linguagem de máquina" mais alto nível, elas interagem com suas respectivas máquinas virtuais.

Em engenharia reversa temos de ter consciência da base, pois a linguagem mais próxima da leitura humana e ao mesmo tempo mais próxima da máquina é o assembly. Ele lida diretamente com a memória e operações, contudo como eu disse antes, com intermédio do SO, então mesmo em assembly temos a total interação com os métodos que o SO abstraiu/encapsulou para que possamos interagir com a máquina. Então no fim tudo é linguagem de máquina e "antes" disso é assembly. Podemos dizer também que antes de assembly temos as linguagens C e C++, sendo que C++ possui conceitos mais modernos, como programção OOP. Se eu fosse dar uma dica seria estudar C, é a melhor forma de você estar perto da máquina realmente. É o mais próximo para se trabalhar com a manipulação de memória, precisando entender os conceitos de memória e performance para se programar utilizando ela. Você resolve seus problemas estando mais próximo da camada de hardware. Abaixo segue uma imagem que mostra de forma abstrata as camadas entre Hardware e SO:



Conclusão e opinão

Bom, esse primeiro texto quis abordar apenas um pouco da parte bem introdutória da ER/computação que por sinal é muita mágica, pois imaginar energia se transformando em informação é realmente muito "dahora". Quero continuar atualizando este blog tanto para me ajudar quanto para ajudar a comunidade, acredito que talvez ajude alguém :). A minha ideia é continuar escrevendo textos tentando ser o mais objetivo e didático possível. Pretendo trazer conteúdo mais técnico nos próximos textos como programação assembly/c, análise de malware, resolução de softwares/puzzles e etc. Sintam-se à vontade para questionar ou apontar algum erro/falha. O intuito aqui é somente conhecimento e crescimento.

Grande abraço e bons estudos!

Referências:
  • Google
  • Andrew S. Tanenbaum - Structured Computer Organization
  • Eldad Eilam - Reversing: Secrets of Reverse Engineering
  • Richard Blum - Professional Assembly Language (2005)

Windows Objects

Objects in windows are referred as kernel objects . They provide a link or an way to use any objects functionality according with the object...