Reverse Engineering Notes
This is just a page of notes and important things to me to remember while learning Reverse Engineering
PE Structure
- Typical windows programs are in the Portable Executable (PE) Format. It’s portable because it contains information, resources, and references to dynamic-linked libraries (DLL) that allows windows to load and execute the machine code.
Windows Architecture
-
In user-mode, an application starts a user-mode process which comes with its own private virtual address space and handle table
-
In kernel mode, applications share virtual address space
PE Header
= The PE header provides information to the operating system on how to map the file into memory. The executable code has designated regions that require a different memory protection (RWX)
PE Header Example
The Stack
- EBP is the Base Pointer register that is used to store memory addresses or pointers that point to specific locations within the stack frame
Threads, Functions and Stacks
-
Each thread executing inside a process has its own stack. // 5 Threads = 5 Stacks
-
When a function is called, it places its parameters on the stack, PUSH places data on top of the stack and POP removes that value from the top of the stack. LIFO (Last In - First Out).
-
When an item is pushed onto the stack, the ESP register, which always points to the Top of the Stack, is decremented in order to point to the new item placed on the Top. Works the same the other way around, POPing from the stack will increment in order to point to the item below. EBP just points to the bottom becauSE IT IS A BASE POINTER.
-
CALL is obviously responsible for executing functions, it loads the entry point of the function into the EIP register in order to run it. RET for exiting functions
-
The program in execution needs to “remember” the return address each time the execution flow enters a function and that function needs to have its own memory area where it can store return addresses, local variables, etc. This is done by splitting the memory assigned for the stack into stack frames, each frame holds the information just mentioned for each function.
Calling a function
Local variables on the Stack
Heaps, Handles, and Exceptions.
-
Heaps are memory areas dynamically allocated at runtime, used to store data that doesn’t have a fixed size or data that can’t fit inside the stack.
-
Handles can be considered references to various resources, and they are used by the OS in order to control the resources access (read, write, etc.)
-
Exceptions are a specific type of event that occurs during the execution of an applications. They are usually associated with specific exception handlers, which are code blocks dedicated to handle that type of event correctly. Exceptions can also be caused by programming errors, so it is important to have an exception handler that will prevent the application from crashing unexpectedly. You also have Hardware and Software exceptions
-
Hardware exceptions are usually caused by the execution of a bad sequence of instructions such as division by zero or an attempt to access an invalid memory location. On the other hand Software exceptions are generated by the application or the OS itself, and can be used in order to flag certain conditions with user defined exception codes.
-
Windows implements its own mechanism to handle exceptions which is called Structured Exception Handling, or SEH for short
-
Tricks like redirecting the execution flow to inside the handler and then back to the normal execution flow can be commonly used as an anti-reversing trick.
Basic Windows Ring3 Internal Structures.
-
THREAD_ENVIRONMENT_BLOCK (TEB)
. This structure stores information such as the addresses of the top and bottom of the current thread’s stack, the thread identifier, the identifier of the process the thread belongs to, the code of the last error that occurred during the thread execution, the address of theThread Local Storage (TLS)
, and the address of thePROCESS_ENVIRONMENT_BLOCK (PEB)
-
PROCESS_ENVIRONMENT_BLOCK (PEB)
. The PEB structure is also very important to understand. Some of the information stored in the PEB is: the image base of the process, the address of the loader data structure (PEB_LDR_DATA
- which contains pointers to lists regarding the modules loaded by the process). TheNtGlobalFlag
value, the major and minor versions of the Windows OS, the number of processors available, theBeingDebugged
flag, and much more -
Some of the information stored inside PEB can be used for debugger detection, such as the
NtGlobalFlag
orBeingDebugged
. -
Another important structure is the
CONTEXT
structure, the system uses this structure to keep track of all necessary CPU state information afor a specific thread under execution during internal operations. For example, it stored the values of the registers which would be necessary to continue in the execution of the thread from the correct virtual address in case an event (like an exception) occurs.
Windows APIs
-
Windows APIs are Ring3 operating system functions. Their use is well documented, the parameters they receive and their purpose clear so that they can be used by the programmers in order to take advantage of the various functionalities of the operating system.
-
Basically communication tunnel between running application and kernel
Types of Reversing Tools
-
Hex Editor like HxD
-
Decompiler like DnSpy for .NET, Ghidra for native
-
Disassembler like IDA Pro or Ghidra again
-
Debugger like x64dbg or gdb-gef
-
System monitoring tools like Process Hacker or Procmon
-
Windows API monitoring tools
VA/RVA/Offset
-
Applications do not directly access physical memory, only virtual memory. Memory addresses referenced by an application are virtual addresses (VAs).
-
A relative virtual address (RVA) is the difference between two VAs and refers to the highest one.
How to calculate offsets:
VA_1 = 0x00400000
VA_2 = 0x00401000
RVA of VA_2 = VA_2 - VA_1 = 0x00001000
-
On the other hand, offsets are usually refered to as either physical memory, a physical file on the disk, or in other general cases where we treat data as raw data, without worrying about any differences in the internal alignment of this data on memory against the one on the disk.
-
The offset is the difference between the locations of 2 bytes, for example inside a file, usually starting from the beginning of the file. You can find this in tools like Ghidra which show the offset of certain functions and instructions.
-
If we want to bypass a anti-reversing function or trick, we can use information like offsets of the bytes you want to modify on the disk and patching code in memory as a testing solution.
-
Opcodes are important btw, for example
JNZ
’s opcode would be 0x75.
Opcodes and Instructions
- Each Instruction represents opcodes (hex code) that tell the machine what to do next.
There are 3 categories of instructions:
- Data Movement / Access
- Arithmetic / Logic
- Control-Flow
Common Instructions
mov, lea (data movement, data access)
add, sub (arithmetic)
or, and, xor (Logic)
shr, shl (Logic)
ror, rol (Logic)
jmp, jne, jnz, jnb (Control Flow)
push, pop, call, leave, enter, ret (Control Flow)
- Example below is moving value at
0xaaaaaaaa
intoecx
.
Instruction Opcode
mov ecx,[0xaaaaaaaa]; 8B 0D AA AA AA AA
Top Tips
- Top tip btw, when clicking on the beginning of a function (
PUSH EBP
), you can view the functions that call this function under the CPU box:
- Another top tip, if you’re stuck in a loop, set a breakpoint just underneath it and continue running with F9 instead of spamming F8 to step over
- This fucking C means CPU, click it if you get lost in execution or mem addresses on the left change to like 775B29FA
- You can right click and click “New origin here” and the process will start at that instruction
Patching Lab #1
Here we have a regular program that is looking for a valid input, we can see at address 0x402E76
there is a JNZ
(jump if not zero) instruction where a decision is taken regarding an output.
Above that we can see TEST EAX,EAX
which checks the value of EAX
to see if it is zero or not, if it is NOT then the conditional jump that follows will redirect the execution to the “invalid” message. Otherwise the execution will continue with the good message.
To bypass this, we can change the JNZ
instruction to JZ
using the “Assemble” command
After we edit this and run the program, we can input any key and it becomes valid
We can also do the same thing by changing the address of the jump condition to jump to the “valid” memory address (0x402E78)
instead of the invalid one (0x402E97
Finding Byte Offset & Cracking the Program
I want to patch the byte located at VA 0x00402E77
, so that the program is now permanently “cracked” and will always give a “valid” answer no matter what key is inputted.
Use ALT+M to get to the memory map and find the first section which is at 0x00401000
Right click the PE Header (0x00400000)
and click Dump in CPU
The bottom part of the interface will now be filled with human-readable information
Scroll down a bit more and you’ll find the first section of the PE Header, the .text section.
The comments squared in red are the ones we will need to find the byte offset
- VirtualSize: The size of the section in memory.
- VirtualAddress: The RVA of the section in memory, not the VA.
- SizeOfRawData: Size of the section in the file.
- PointerToRawData: Starting offset of the section in the file.
The below is the formula to calculate the information
Byte_Offset = Byte_VA - (Image_Base + Section_RVA) + PointerToRawData
So it would be:
0x00402E77 - (0x400000 + 0x1000) + 0x600 = 0x2477
Putting the binary in HxD and scrolling down to 0x2477
brings us to the same hex values we saw in Ollydbg!
And at the bottom left we can see the offset is 0x2477
Changing the Hex values to 75 00
and saving the file means we have fully patched the binary!
And we win!
Exploring the Stack with Lab #2
Because strings this time are XOR encrypted and we can’t just search for string references, we must use the stack while the program is running to see data being passed through it when it has been decrypted.
Using F12 or the Pause icon at the top toolbar of Ollydbg, means we can pause execution of the program and analyse the stack at runtime. After triggering a “This code is invalid!!!” I searched through the stack for this string, once i found it, i ensured this was the correct string since there were many different “This code is invalid!!!” strings on the stack, i confirmed this was my target by seeing the CALL
that is made to the MessageBoxW
WinAPI function.
Right click on the function in the stack you want to go to in CPU, and click “Follow in Disassembler”. I can also use CTRL+G to go straight to the memory address location of the function in CPU.
- NOTE: Some Windows API names have an appended “A” or “W” in the end in order to indicate their ANSI or Unicode name respectively. For example, in case
MessageBoxA
call is used when we need to use ASCII character strings, otherwise, we useMessageBoxW
for Unicode character strings.
Scrolling up a bit further we can see some JMP conditional calls and more MessageBox’s. This is probably where most of the decision making is happening:
I can set a breakpoint just before the comparison is made to check if the right string has been inputted:
After running the program with F9 we should hit the breakpoint and have some information in the stack:
As you can see, since the code to decrypt the string has been ran, we can see the correct password for the program in the stack clearly!
0019F4EC 0063B26C UNICODE "Fr0m-sma11-b3g1nn1ng5-c0m3-grea7-th1ng5"
Using this code we can check and see that we get the win
I also used HxD to permanently patch this program, I used the Byte Offset calculation formula to get the location of the hex values that needed to be edited
After this, I saved it separately as a way to track my efforts and skills:
Anti-Reversing Tricks #1
For example, if an application is being debugged, then the BeingDebugged member of the PEB structure will be equal to 1, if not, it will be 0.
Most Anti-RE tricks are only effective against Ring3 (Userland) debuggers, unless stated otherwise, or they are related to code execution time-elapsed calculation.
1) Direct Debugger Detection
A). Check -> PEB.BeingDebugged
The figure below demonstrates the layout of the PEB structure:
The check is commonly used with WinAPIs, in order to “directly” detect if the application is being debugged. For example, a call to the IsDebuggerPresent
API which checks the PEB.BeingDebugged member would detect this.
However, someone can instead implement that type of call in his own code instead of calling it, which makes it more difficult to spot. This is actaully the code executed by Windows when you call that API:
mox eax, dword ptr fs:[18h] <-- Get address of TEB structure
mov eax, dword pty ds:[eax+30h] <-- Get address of PEB structure
movzx eax, byte ptr ds:[eax+2] <-- Get value of PEB.BeingDebuggec
test eax, eax <-- if not zero, a debugger has been detected
jne_debugger_detected()
B). Check -> PEB.NtGlobalFlag
This has the same concept as the previous one, but this time we check another member of the PEB structure. THis member is not documented in the figure above, but we know that it is localed at PEB_Start + 68h
C). CheckRemoteDebuggerPresent API
This Windows API can be used to detect if the calling process is being debugged through a Ring3 debugger, but also if another process is being debugged.
This API is actually a wrapper around the native API ZwQueryInformationProcess
The following lines of code demonstrate how a call to CheckRemoteDebuggerPresent API is normally performed, which then will also call the ZwQueryInformationProcess
native API
push pbDebuggerPresent_address <- push the address of a 32-bit variable
push Process_handle <- push the handle to a process (-1 for the calling process)
call CheckRemoteDebuggerPresent <- call the API
mov eax, [pbDebuggerPresent_address] <- check the returned result in the variable
test eax,eax
jne_debuggerfound <- if not zero, debugger was found
2) Indirect Debugger Detection
A). OutputDebugString API
This is used to send a string to the debugger itself
If the process is not being debugged the return value in EAX
will be 1 in Windows XP and 0 in Windows Vista and above, because of the changes made to Windows Internals.
3 ways to detect a debugger:
- Check the returned value in EAX, depending on the Windows version as described above
- GetLastError in Windows XP. If no Ring3 debugger is present, then calling this API after the OutputDebugString will reveal an error code. If EAX == 0 then a debugger has been detected.
- Through SEH -> Works in all windows versions from XP and above, not tested in Windows 8.
B). OpenProcess API
Some debuggers enable the SeDebugPrivilege
in their access token. The child process inherits the same privilege level as the parent process, and in this case, we refer to the debugee (the process that is being debugged).
Normally, unless specified by the author, an application will not have that privilege enabled, which means that an attempt to obtain a valid handle by using the OpenProcess
API to a system process such as services.exe
will fail.
On the other hand, if it succeeds, then we know that some other process escalated its privileges and we can assume that it was a debugger.
3) Windows Debugger Detection
The most commonly used API for obtaining a handle to the top-level window of a running application, through either its title or its class name, is the FindWindow
API.
Example:
push 0 <- push NULL as title, we want to detect via class name
push "OLLYDBG" <- push class name, basically pushing the string address
call FindWindow
test eax,eax
jnz_Olly_Detected <- if eax!=0 Olly is running.
Sometimes though, the EnumWindows
API is used, in order to enumerate all top-level windows on the screen and compare each one against a list of well-known application window names beloning to debuggers and or reversing tools.
Anti-Reversing Tricks #2
1) Process Debugger Detection
This trick is used to verify that no known debugger, disassembler or reversing tool is running at the same time with our application, by retrieving a list of all running processes and evaluating their names. Usually the following Windows APIs are involved:
CreateToolhelp32Snapshot
Obtains a snapshot of all running processes, by using the TH32CS_SNAPPROCESS flag.
Process32First
Obtains information about the first process in the snapshot by filling the PROCESSENTRY32
structure.
2) Parent Process Detection
A process can detect if it’s being debugged by checking the name of its parent process. Usually the check is done and expecting “explorer.exe” which is commonly the parent process of a process started by the user.
The same technique is used as in the previous case, but this time we target the parent process of the process we are interested in.
The usual method involves obtaining the PID (Process IDentifier) of our process, enumerating through the processes snapshot list, locating our process and retrieving the PPID (Parent Process Identifier), and finally going once more through the processes snapshot list to see which process the PID belongs to.
A piece of malware could, for example, only accept “explorer.exe” as a legitimate parent process and act differently if it detects a different parent process.
3) Module Debugger Detection
This has the same goal as above (identifying debuggers), but in this case it does so by retrieving a list of all running processes and then a list of all the loaded modules of every process, such as DLLs, which are commonly used as plugins to add extra functionality to many reversing tools.
Usually the following WIndows APIs are involved:
Once again..
- CreateToolhelp32Snapshot
Obtains a snapshot of all running processes, by using the TH32CS_SNAPPROCESS flag.
- Module32First
Obtains information about the first module in the snapshot by filling in the MODULEENTRY32 structure.
- Module32Next
This is used to go through the loaded modules as listed after the snapshot is taken.
4) Code Execution Time Detection
This is an efficient and easily implemented anti-reversing technique.
It’s purpose is to evaluate the time elapsed for the execution of the instructions in a specific block of code.
Other Windows APIs can be used to get time related information and to achieve the same goals. Some examples are the timeGetTime
and QueryPerformaceCounter
APIs
Anti-Reversing Tricks #3
Software vs Hardware Breakpoints
A software breakpoint is placed by substituting the byte originally located at that memory address by a software interrupt - specifically the INT 3h
interrupt of which the opcode is the 0xCC
When the execution hits a INT 3h
instruction, a software breakpoint exception is raised (80000003h
) and if the process is being debugged, the debugger will force the execution to stop there.
Software breakpoints can only be used for code under execution and not memory access monitoring.
Hardware breakpoints use the debug registers DR0-DR3
. They can be used for different types of memory access monitoring and not to just break the execution on a specific instruction. You can set a Hardware Breakpoint On Access on a specific memory area which will be triggered when the virtual address space inside the process is accessed for read/write.
Hardware breakpoints dont do any code modifications in memory, this makes them very suitable to use in cases where self-modifying code is present. (Or other tricks that detect software breakpoints by calculating checksums of code blocks.)
Software Breakpoint Detection
push 'kernel32.dll'
call LoadLibrary -> get imagebase of kernel32.dll
push 'VirtualProtect'
push eax
GetProcAddress -> get address of 'VirtualProtect' API inside exported by kernel32.dll
cmp byte ptr ds:[eax], 0xCC -> check if there is a breakpoint set there.
Hardware Breakpoint Detection
The most common way involves the use of 2 Windows APIs:
1). OpenThread -> Get a handle to the desired thread.
2). GetThreadContext -> Read the current thread context and locate the values of the Debug Registers used to store HW BPs, DR0-DR3
NOTE: DR0-DR3 will be zero if no HW BPs are set.
Ring0 Debuggers & System Monitoring Tools Detection
The most common use involves the usage of the CreateFile Windows API
Such types of tools use drivers with which to communicate using their own named devices:
\.\NTICE -> Softice (Windows NT)
\.\FILEM -> FileMon (Windows NT)
\.\REGSYS -> RegMon (Windows NT)
So if we manage to obtain a valid handle to a specifc device like these above, then we can actually reveal the presence of the driver belonging to that tool.
Structured Exception Handling (SEH)
This is debugger detection through exception generation.
This is mainly used for debugger detection but can be used for redirecting the execution flow under certain circumstances; it is a nice logic obfuscation tool.
So, what basically happens in this example is that we set our own exception handler and then we attempt to execute an INT 3h
instruction which is the equivalant of a software breakpoint.
Depending on the debugger’s settings, genrally a debugger will think that this is a software breakpoint set by the user so it will handle the generated exception itself and set the EIP
to point to the next instruction mox eax,1
The point is, if the process is not being debugged, since an exception is raised, the execution should reach the exception handler from where we can set the EIP accordingly.
Basically, 1 if debugger detected, 0 if not detected.
NOTE: Instead of INT 3h, a call to DebugBreak Windows API can be used which executes an INT 3h instruction.
Unhandled Exception Filter
This is another powerful anti-reversing technique that uses a specific exception handler (normally used when there are no appropriate handlers to handle an exception)
In this case, if a process is being debugged, after the execution of the UnhandledExceptionFilter API, the process will exit instead of continuing execution which, in the context of anti-reversing properties, is very useful.
When the UnhandledExceptionFilter API is called it will itself make a call (through a subroutine) to the ZwQueryInformationProcess API asking for ProcessDebugPort information which will return 0xFFFFFFFF if the process is being debugged by a Ring3 debugger, and 0x0 if it is not.
Basically, according to the MSDN documentation, if it returns a non zero value, the process is being debugged by a Ring3 Debugger.
VM Detection
The use of virtualization environments for application testing and malware analysis is basic math at this point.
Some malware authors do not want their malware being analysed in a VM with no juicy info to steal, so they have functions which detect if the malware process is running inside of a Virtual Machine, if it is, it terminates.
There are many ways to achieve this, but some of them are well-known and widely used.
VMware Detection:
mov eax, 'VMXh' <- magic number
mov ebx, 0
mov ecx, 0Ah <- set function number / 0Ah = get VMware version
mov edx, 5658h <- port number used to communicate with VMware
in eax, dx <- read a dword from that port
cmp eax, ebx <- if VMware is present EAX == EBX
je__VMware_detected
This piece of code uses a logical port that is used for communication between the VM and VMware itself in order to get information about the VMware version used.
The concept behind this trick is that in the in eax, dx
instruction is a privileged one. We can’t usually execute this instruction from usermode (Ring3)
However, the virtual CPU of VMware will allow the execution of this instruction to communicate with VMware itself.
This trick is always used along with an exception handler since if the application is not running inside VMware, a privileged instruction exception will be raised (C0000096h
) and will cause the application to crash without the handler in place.
VirtualPC Detection:
mov ebx, 0
mov eax, 1
db 0Fh, 3Fh, 7, 0Bh // VPC Call
cmp ebx, 0
je__VPC_detected
The virtual CPU in VirtualPC can decode this set of bytes 0Fh, 3Fh, 7, 0Bh
as special call instructions which of course dont resolve to valid instructions from a native x86 CPU.
Attempting to execute this virtual instruction in a physical system will cause an illegal instruction exception (C000001Dh
). For this reason this trick has to be used along with an associated exception handler.
VirtualBox Detection:
A very simple and easy way to detect VirtualBox is through the window class name of a tray icon that it places in the taskbar.
push 0
push 'VBoxTrayToolWndclass'
call FindWindowA
test eax, eax
jnz_VboxDetected ; if we managed to obtain a valid window handle, VBox was detected.
These aren’t the only ways to detect VM environments, do some research.
Code Obfuscation
- Deliberate act of creating obfuscated code that is difficult for humans to understand
- Plain text strings will appear as base64 or Xor
- Control-Flow Flattening
- String Encryption
- Junk Bytes (anti-disassembling technique)
- Trampolines
- Permutations
Logic Flow Obfuscation
The main goal of this type of obfuscation is to make it difficult to predict and understand when program execution should reach certain conditional branches of code. These branches are the core of the logic and determine which blocks of code are executed based on whether certain conditions meet specific requirements.
These are statements like ‘if’, ‘else’, ‘while’, ‘for, etc. In assembly this would be conditional jumps like ‘jne’, ‘je’, ‘jl’, ‘jg’, etc.
Lil Bit of Malware Analysis Tips
You can use CFF Explorer’s Resource Editor to find extra binaries which might be hidden within the file:
You can use “X” key in IDA to travel to xrefs to functions
You can use “G” key in IDA to travel to memory addresses
You can use “Shift+;” in IDA to add comments
You can use “F5” key in IDA Pro (only) to bring up the decompiled Pseudo-C code of a function
You can use “Space in IDA to change the views from graph view to text view
You can right/left click on pushed hex values and select “R” for the Readable string option, you can also just press R after clicking the value
This thing is a reference label, you can click on it and press X to go to the referenced function as mentioned before
And we are taken to where the string is referenced:
Interesting functions that should be monitored:
Opening a file from the resource section
FindResource
SizeofResource
LoadResource
LockResource
Creating a file
GetEnvironmentVariable
CreateFile
WriteFile
CloseHandle
Sleep
Starting a new process
CreateProcess
Using Ollydbg’s command line at the bottom, you can use the bp CreateProcess
command to set breakpoints at common ATT&CK functions.
Utilize wireshark &
, fakedns
, inetsim
and httpd start
commands on a separate Linux VM to intercept web requests to Domain C2’s, or use HTTP Toolkit if it wants to play fair.
When doing this, make sure to configure the IPv4, Gateway, and DNS to point to the other Linux VM on the Control Panel
When monitoring API calls like ReadFile
, always check the function Syntax on the MSDN website, for ReadFile
, it is:
BOOL ReadFile(
[in] HANDLE hFile,
[out] LPVOID lpBuffer,
[in] DWORD nNumberOfBytesToRead,
[out, optional] LPDWORD lpNumberOfBytesRead,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
Looking at the CPU registers in memory, we can see after this ReadFile breakpoint is hit, we get the value 10C
in the rcx
register
So the HANDLE = 10C
Going into the “Handles” section in x64dbg, hitting refresh and searching for 10C
in the handles section, brings us the file path it is trying to read!
Call Stack is useful, you can check where the execution path is for a specific function, the To
address ending in 2E3F
shows where the ReadFile
function will return to, the From
is where we are presently paused
Using “Run to User code” is also very useful, as if we set a breakpoint on a function like ReadFile
, we can use “Run to User code” to get to the code that calls that function, also, now we have ran to the START of the user code, AKA “where the ReadFile
function will RETURN to”, we can see the addresses match
You can use accept-all-ips start
and netcat
to intercept requests sent directly to IPv4 C2’s. 3127
is the port number to listen on.
x64dbg - Common Commands
- Enter Comment = ;
- Breakpoint = F2
- Step Into = F7
- Step Over = F8
- Run = F9
- Edit Instruction = Space
What to do when you find a malware sample?
Things to do:
- Determine the file type with tools like DetectItEasy or
file
in Linux - Verify the file header with a hex editor like HxD
- Determine what resources, DLLs, and libraries are being used. (Example: if you see Ws2_32.dll it might be setting up a network connection because it’s used for setting up sockets)
- Get the hash of the file and look it up to see if its been reported on already
- Use sites like hybrid-analysis.com or malwr.com to get information about behaviour quickly
- If you can’t do this then you will need to dynamically debug the malware
Function calls like LoadResource
& FindResourceA
from KERNEL32.dll
and CreateProcessA
could indicate that the malware is extracting something from the resources and is creating a new process with it
32-bit vs 64-bit
32-bit CPU registers are different, for example the Base Pointer is EBP for 32-bit where 64-bit is RBP.
CFF Explorer Characteristics will indicate a 32-bit binary as a “32 bit word machine” and a 64-bit binary as “App can handle >2gb address space”
32-bit in Optional Headers have BaseOfData, whereas 64-bit doesnt. 32-bits image base is also 4 bits whereas 64-bits image base is 8 bits.
Overall who fuckin cares it’s either RIP or EIP.
Packers And How They Impact Initial Assessments
Difference between a Packed 32-bit Executable file and unpacked 32-bit Executable file:
You can also see Entry points + sections are different, and DetectItEasy also detects that it is packed with UPX. WinAPI calls will also be hidden/there will be less of them (6 when packed vs 80 when unpacked):
Furthermore, strings are completely encoded:
These types of things usually indicate a packer, even if its not being detected by CFF or the header may have been removed, the binary could always be using a custom inbuilt packer.
Monitoring Malicious API Usage with API Monitor (32bit)
You can use API Monitor to monitor different WinAPI usage that programs are using
These API functions can be found in CFF Explorer
When these are used in malware, they are usually used for process injection techniques.
Hit CTRL+F to set checkmarks on common WinAPI functions and they will be monitored in the right window
You can also set breakpoints when a certain function is called
Here is an example of monitoring process hollowing
As you can see the notepad.exe
process is created using CreateProcessA
and we can see that in the screenshot, this process is going to be used for the process hollowing technique. You can also see the WriteProcessMemory
calls which write the sections into the process.
You can also see the injected strings of the process that was injected when the notepad process is running with any task manager or process hacker
64bit version
How Process Hollowing works (+ Visualizing it):
- The malware will call CreateProcess API
- It will pass the creation flag in
CREATE_SUSPENDED
mode - It will then switch to the other process which it wants to load, and there are multiple different ways of loading, but in this case, it will switch to the other executable process which it wants to load, hollow out the original image and replace it with the malicious one using calls like
WriteProcessMemory
, then useSuspendThread
andResumeThread
to pause and resume the thread while loading.
Here we can see the “victim.exe” process getting loaded by the Process Hollowing executable
And this would be the flags for the VirtualAllocEx
call that ultimately starts the process hollowing:
And here we can see the malicious process being loaded:
After loading, the new entry point address for the new loaded process is set with SetThreadContext
The process and thread is then resumed with ResumeThread
Manipulating Control Flow
In this exe i have “stage2.exe”, there is a buffer check, which is an area of memory being checked for a particular condition.
When the buffer checks are made (with the jne
instructions), we can manipulate the ZF Flag (Zero Flag) by stepping into the jne
calls.
NOTE: In x86 assembly language, the Zero Flag (ZF) is a status flag that is set if the result of an operation is zero. For example if we failed the buffer check, this flag would be 0, same if we failed to supply a valid license key or something.
Popped the flag for this one!
Analysing Different Files, Borland Delphi
Here I have an obvious malicious binary which is impersonating Malwarebytes, you can see the file size is 2.14MB which is quite small for legitimate setup files, usually smaller payloads are used when a loader is incorporated into the malware. However, the reason for the large file size can still be the following reasons:
- It has many resources
- It is compiled in Borland Delphi
(BobSoft Mini Delphi -> BoB / BobSoft)
Why does it matter how the sample was compiled? Because it will determine how the disassembly will be structured.
Imports
Looking at the Imported DLL files, like kernel32.dll
, we can see interesting imported functions like VirtualAlloc
which as we know is commonly used for code injection.
We can also see imports of possible resource manipulation techniques (i.e. FindResourceA
, SizeofResource
), and possible Anti-Analysis tricks (i.e. Sleep
, GetTickCount
). With the imports from Advapi32.dll
, we can tell it is going to access registry keys (i.e. RegOpenKeyExA
).