:::success # OT Lab 1 - Assembly Patterns ::: # Part 1 - Understanding Assembly ## Task 1 - Preparation :::info 1. Choose any debugger/disassembler what supports the given task architecture (32bit & 64bit): **gdb** 2. You have to check and analyze what does a binary do before running it. You have no idea what file we are working with, even if it is initially considered as legitimate application. 3. Prepare a Linux VM for this lab, as you might need to disable ASLR ::: Okay, I'll use my favorite self-assembled ubuntu image 20.04 from the first LS lab work. ## Task 2 - Theory :::info 1. What is the difference between `mov` and `lea` commands? ::: `LEA` takes the result from address generation, and puts that result directly in a register, without accessing memory. `MOV` will move data to or from memory at the computer offset. In short, `LEA` loads a pointer to the item you're addressing whereas `MOV` loads the actual value at that address. :::info 2. What is ASLR , and why do we need it? ::: ASLR - Address Space Layout Randomization - it is a memory protection process that protects from buffer overflow attacks (randomization of offsets). It helps to ensure that the memory addresses associated with running processes on systems are not predictable, thus flaws or vulnerabilities associated with these processes will be more difficult to exploit. <center> ![](https://i.imgur.com/3AUqy69.png) Figure 1 - ASLR works in full randomization mode ![](https://i.imgur.com/fG5y40V.png) Figure 2 - Different sets of addresses </center> :::info 3. How can the debugger insert a breakpoint in the debugged binary/application? ::: He can do this with the help of a special instruction, which he places at the breakpoint I set. It can be some instruction that causes some kind of interrupt/exception that the debugger can catch on to, or some instruction that handles the control for debugging. If we are talking about a software breakpoint, then in this case the binary file will be subject to changes. If we talk about the hardware breakpoint, then manipulations with the processor will not change the file itself. ## Task 3 - Disassembly :::info 1. Disable ASLR in your Linux VM using the following command: ``` sudo sysctl -w kernel.randomize_va_space=0 ``` 2. Load the binaries ( sample 32 and sample 64 from `part1.zip`) into a disassembler/debugger. 3. Does the function prologue and epilogue differ in 32bit and 64bit? What about calling conventions? 4. Does function calls differ in 32bit and 64bit? What about argument passing? ::: <center> ![](https://i.imgur.com/sgzgl1M.png) Figure 3 - ASLR is disabled </center> ``` #download and unzip binaries scp part1.zip user@10.1.1.61:/home/user unzip part1.zip #install gdb sudo apt-get update sudo apt install gdb #run gdb gdb -q sample32(/64) set disassembly-flavor intel ``` > The AMD64 processors are able to execute 64-bit AMD64 and also 32-bit ia32 programs. Libraries conforming to the Intel386 ABI will live in the normal places like /lib, /usr/lib and /usr/bin. Libraries following the AMD64, will use lib64 subdirectories for the libraries, e.g /lib64 and /usr/lib64. Programs conforming to Intel386 ABI and to the AMD64 ABI will share directories like /usr/bin. In particular, there will be no /bin64 directory. <center> ![](https://i.imgur.com/EDB22jO.png) Figure 4 - Disassamble sample_function from x32 </center> <center> ![](https://i.imgur.com/abKU4TS.png) Figure 4 - Disassamble sample_function from x64 </center> Yes, the prologue and epilogue functions are different. If we start with the prologue, then first of all x32 and x64 differ in general-purpose registers (for example, `eax/rax`). There were not so many of them in x86 processors, unlike later x64. Therefore, the prologue ends with a `sub`, which "reserves" space on the stack for storing local variables. From this point on, the `ebp/rbp` register, throughout the operation of the function code, begins to be used to access local variables and function arguments through an offset set explicitly(for example [rbp - 0x8]). Also versions different of distinguished by the presence of a call to `<__i686.get_pc_thunk.bx>`, which loads the position of the code into the `ebx` register, which allows us to access global objects as an offset from this register. For the x86-64 architecture, this call is not required because this architecture has IP-relative addressing modes (that is, it can directly access memory cells as an offset from the location of the current instruction). The epilogue is the same, `leave` is used, which essentially combines commands such as `mov esp, ebp` and `mov ebp`. The Linux kernel uses internally the same calling conventions as user level applications. x86-64 calling conventions take advantage of the additional register space to pass more arguments in registers. The calling convention on the x86-64 platform resembles the Microsoft convention existing in x86. * In the x64-convention, the first six integer arguments are passed in 64-bit registers chosen specially for this purpose: RDI, RSI, RDX, RCX, R8, R9. * For system calls, R10 is used instead of RCX. Additional arguments are passed to the stack, and the return value is stored in RAX. If there are no registers available for any eightbyte of an argument, the whole argument is passed on the stack. * The pointer "this" is considered an first parametr. * If floating-point values are passed, the first eight of them are passed in the registers XMM0-XMM7 while all the next are passed through the stack. * 128 bytes, known as the red zone, are subtracted from the stack before anything is placed on the stack. In the 64-bit version, as in the 32-bit version, functions are called using the `call` instruction, which puts the address of the next instruction on the stack and goes to the operand. The functions return to the calling program using the `ret` instruction, which extracts the value from the stack and jumps to it. > **For 64-bit** > Functions preserve the registers rbx, rsp, rbp, r12, r13, r14, and r15; while rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 are scratch registers. The return value is stored in the rax register, or if it is a 128-bit value, then the higher 64-bits go in rdx. Optionally, functions push rbp such that the caller-return-rip is 8 bytes above it, and set rbp to the address of the saved rbp. This allows iterating through the existing stack frames. > **For 32-bit** > Functions preserve the registers ebx, esi, edi, ebp, and esp; while eax, ecx, edx are scratch registers. The return value is stored in the eax register, or if it is a 64-bit value, then the higher 32-bits go in edx. Functions push ebp such that the caller-return-eip is 4 bytes above it, and set ebp to the address of the saved ebp. :::info 5. What does the command ldd do? “ ldd BINARY-NAME ”. ::: `ldd` displays dependencies (common packages) and their destination addresses in memory. > ldd (List Dynamic Dependencies) is a *nix utility that prints the shared libraries required by each program or shared library specified on the command line. ``` #add a library for cross-compilation apt-get install gcc-multilib ``` <center> ![](https://i.imgur.com/NAp4kNR.png) Figure - ldd command </center> In this situation, you see what I described earlier for randomizing addresses, only in this case they are the same every time the command is run. # Part 2 - Reverse engineering ## Task 1 - Theory :::info 1. What kind of file did you receive (which arch? 32bit or 64bit?)? ::: The output of the `objdump` command shows that all binary files belong to the 64-bit architecture (that's 64 bit - the x86 architectures are described by objdump as `i386:x86-64` (AMD64)), and the .exe files is PE32 (32-bit) format is for x86 systems. ``` objdump -f binХ | grep ^architecture ``` <center> ![](https://i.imgur.com/YEygO2q.png) Figure - 32-bit & 64-bit </center> :::info 2. What do stripped binaries mean? ::: A stripped binary is a program that is compiled with a strip flag that tells the compiler to discard these debugging symbols and compile to program as it is. Simply put, there is no built-in debugging information in the split binaries. Stripping a binary reduces its size on the disk and makes it a little more difficult to debug and reverse engineer. :::info 3. What are GOT and PLT ? Describe on the practical example. ::: PLT and GOT are part of the Linux ELF file format, which is used for Linux executables. A Procedure Layout Table (PLT) is a read—only table in an ELF file that stores all the necessary characters that require permission from [`printf` or `puts` output functions]. When a function call is made, resolution occurs. It calls the dynamic linker to resolve the address of the requested function at runtime. The Global Offset Table (GOT) is a writable memory that is used to store pointers to allowed functions. As soon as the dynamic linker resolves the function, it will update the GOT so that this entry is read for use. The easiest way would be to show it just on the `printf` function. For example, let's take a 32-bit executable ELF file. It just outputs the string "Hi!" at execute. <center> ![](https://i.imgur.com/VXMCQGq.png) Figure - Example </center> The compiler replaced the `printf` function with the `puts` function, the call instruction goes to the address 0x8049060. ``` objdump -d lab1 -M intel ``` <center> ![](https://i.imgur.com/rdntdCO.png) Figure - disass main() </center> The command below displays the entries in the GOT table. <center> ![](https://i.imgur.com/pcvviIl.png) Figure - GOT </center> Here I encountered an error executing the ENDBR32 instruction, unfortunately, strict instructions for gcc (-fcf-protection=branch and -mmanual-endbr) solved the problem only in the main function, which you can see below. But the error is still present in the `puts` function itself, which prevents me from showing you the direction of the original flow. Therefore, I will try to describe what happens after the PLT calls the dynamic linker to resolve the address of the `puts` function (we will set a breakpoint after calling `puts`). The instruction at address 0x8049060 is a jump to the value stored at memory address 0x804a00c, which is a GOT record. Now, if we read this entry, it will lead us to the address 0xf7e461e0 (whereas if the `puts` function was called in the first time, then the value of the GOT pointer would send us back to PLT), an address that is in a different range from PLT. And if we look inside this address, we get a direct listing of the `puts` function. <center> ![](https://i.imgur.com/MdcxMbY.png) Figure - GOT and PLT </center> In this screenshot you can see my attempts to catch the primary call of the puts function to show a return to the PLT table (when the GOT pointer value sends us back to PLT). <center> ![](https://i.imgur.com/qDcgAjW.png) Figure - Alternative version but not success </center> :::info 4. What are binary symbols in reverse engineering? How does it help? ::: A symbol, also known as a label, is an association between a name and an address. A label is a name associated with an address. Labels are used to make code easier to read. For example, instead of "call 0x103f2d", the instruction might read "call printf". The name "printf" has been associated with the address "0x103f2d". In other words, the address "0x103f2d" has been labeled printf. The Symbol Table displays a tabular view of each symbol currently defined in the program. <center> ![](https://i.imgur.com/D3xSsli.png) Figure - Example of symbols ![](https://i.imgur.com/AN3DCfR.png) Figure - Example of Symbol Table </center> ## Task 2 - Reverse & Cracking :::info *Tasks distribution:* **My st number is odd (st7): I have to work with bin 1-4 + task1.exe + task2.exe** Inside the ZIP file (part2.zip), you will have multiple binaries ( bin 1-6 ), try to reverse them by recreating them using any programming language of your choice (C is more preferred). You are given two simple PE files as well: * task1.exe (it was found that this binary is dynamically linked to the debug version of Microsoft Visual C++ redistributable 2015. You can put this package together with Visual Studio installation, or add dll file that is inside the zip archive) * task2.exe Your task is to crack the program and find the correct password. ::: I started by installing Ghidra. Then, through the import of binary files, you can see what they are. **Bin1** displays information about the local time of the system. In the screenshots below you can see the Ghidra window where I found the main() function. <center> ![](https://i.imgur.com/E3Mg60B.png) ![](https://i.imgur.com/BES28cC.png) Fgiure - Disassembling and decompiling the main function of bin1 </center> Conveniently, Ghidra immediately displays the decompiled C code. Therefore, to recreate the file, I also chose the C language, all that was necessary was to add a few basic libraries and remove the stack protection implementation. I didn't want to leave the __stack_chk_fail() function because it caused a conflict in gcc, and was additional in the code. The main functionality of the program is fully restored and working, you can see it in the screenshots below (but I am not responsible for the correct local time on the virtual machine :)). ``` #include <stdio.h> #include <time.h> int main() { long in_FS_OFFSET; time_t local_20; struct tm *local_18; local_20 = time((time_t *)0x0); local_18 = localtime(&local_20); printf("%04d-%02d-%02d %02d:%02d:%02d\n",local_18->tm_year,local_18->tm_mon,local_18->tm_mday, local_18->tm_wday,local_18->tm_min,local_18->tm_yday); return 0; } ``` <center> ![](https://i.imgur.com/NUxZvpd.png) Figure - Result of bin1 </center> I applied a similar solution to the rest of the binary files. For example, **binary file number 2** outputs an array of even numbers, where 2 is added to each subsequent element of the array. <center> ![](https://i.imgur.com/TrTD5WT.png) ![](https://i.imgur.com/C8vnf7L.png) Figure - </center> ``` #include<stdio.h> int main() { long in_FS_OFFSET; int local_6c; int aiStack104 [22]; for (local_6c = 0; local_6c < 0x14; local_6c = local_6c + 1) { aiStack104[local_6c] = local_6c * 2; } for (local_6c = 0; local_6c < 0x14; local_6c = local_6c + 1) { printf("a[%d]=%d\n",local_6c,aiStack104[local_6c]); } return 0; } ``` <center> ![](https://i.imgur.com/xyQelHN.png) Figure - Result of bin2 </center> **Binary file 2 and 3 are the same**. You can see this by decompiling the main function in Ghidra. And also by checking the checksum, and also by comparing the binary code and the size of the files. <center> ![](https://i.imgur.com/nMnnW0u.png) ![](https://i.imgur.com/oMSk4jb.png) Figures - Inside bin3 ![](https://i.imgur.com/JI5rIb6.png) Figure - Check the hash-sum ![](https://i.imgur.com/rg1XkL0.png) Figure - Check the size ![](https://i.imgur.com/MglMpyM.png) Figure - Check the binary code </center> For the **fourth binary file**, all manipulations are the same as for the previous files. <center> ![](https://i.imgur.com/zNpbWFC.png) ![](https://i.imgur.com/2GxmHkC.png) Figure - </center> ``` #include<stdio.h> int main() { int local_14; printf("Enter an integer: "); scanf("%d", &local_14); if ((local_14 & 1) == 0) { printf("%d is even.",local_14); } else { printf("%d is odd.",local_14); } printf("\n"); return 0; } ``` The program contains an algorithm for determining even and odd integers. <center> ![](https://i.imgur.com/HKht7O2.png) Figure - Result of bin4 </center> To hack the task1.exe I installed Visual Studio on my personal laptop and added a couple more libraries (sruntime140d and ucrtbased) just in case. So I managed to run the exe. All the code can be seen in Ghidra, where I also found a tool for searching for keywords in the body of the code, so it was not difficult to find the phrases "Username" and others. As you can see in the screenshot below, the nickname and password for the user are written directly in the code. <center> ![](https://i.imgur.com/Sb2cOaU.png) Figure - Searching process ![](https://i.imgur.com/rHKzRVa.png) Figure - Result </center> I have done similar actions for task2.exe, the initial clues were found using Symbol tree and Program trees, but also thanks to the keywords, it was possible to find the desired function. <center> ![](https://i.imgur.com/UmKC0qM.png) Figure - Need func </center> ``` if (_DAT_0040437c == 0x247679) { pcVar2 = "Nice! ;)"; ``` Where 0x247679 = 2389625 in decimal. Let's check! <center> ![](https://i.imgur.com/kmVKv09.png) Figure - Result </center> # Bonus :::info Describe the difference between PE and ELF executable files. Show it by a practical example. ::: For this task, I used the same simple file from the task about tables GOT/PLT, only this time I compiled the "hello world" code in Visual Studio to get .exe. As you can see, the differences are already at least in the fact that with the same names, these are completely two different files (at least by the fact that one of them is a 32-bit version, and the second is 64-bit, but I hope this will not significantly affect the description of the differences between PE and ELF:)) <center> ![](https://i.imgur.com/EPC2vBu.png) Figure - Example </center> ``` readelf -e lab11.c ``` The ELF file format contains the concepts of both sections and segments. In fact, each segment contains one or more sections, i.e. the data of one section is contained in one segment. The PE format does not distinguish between binding and execution representation and contains only the concept of partitions. <center> ![](https://i.imgur.com/0hGSH5i.png) ![](https://i.imgur.com/Llcd97d.png) Figure - Executable launch segments and section headers </center> Another difference is that during the opening of the ELF executable file, the basic C functions will be used, and for PE, the WinAPI functions will be used. Another difference is the number of file headers, ELF has one, and the PE file format has two main headers: MS-DOS and PE headers. <center> ![](https://i.imgur.com/gyZ71Vj.png) Figure - ELF-header </center> And this is what the PE header looks like, which is shared in turn by another signature that identifies the file as an image file in PE format, and two separate headers: the file header and an optional header. ``` typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER OptionalHeader; } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS; ``` This is the most basic thing that I managed to show in the file differences. # References: 1. [System V AMD64 ABI](https://web.archive.org/web/20160315222117/http://www.x86-64.org/documentation_folder/abi.pdf) 2. [Wiki.osdev: System V ABI](https://wiki.osdev.org/System_V_ABI) 3. [GCC 9.4.0](https://gcc.gnu.org/onlinedocs/gcc-9.4.0/gcc.pdf) 4. [Ghidra/help/labels](https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Base/src/main/help/help/topics/LabelMgrPlugin/Labels.htm) 5. [PE_Headers_Code](https://habr.com/ru/post/266831/)