What is PE?
Windows Executable file format, aka PE (Portable Executable), is a data structure that holds information necessary for files. It is a way to organize executable file code on a disk. Windows operating system components, such as Windows and DOS loaders, can load it into memory and execute it based on the parsed file information found in the PE.
Here is a picture of the structure of a PE file.
![[Pasted image 20231205120132.png]]
As we can see.There are many types of containers of date, each of them holds different data.
- .text stores the actual code of the program
- .data holds the initialized and defined variables
- .bss holds the uninitialized data (declared variables with no assigned values)
- .rdata contains the read-only data
- .edata: contains exportable objects and related table information
- .idata imported objects and related table information
- .reloc image relocation information
- .rsrc links external resources used by the program such as images, icons, embedded binaries, and manifest file, which has all information about program versions, authors, company, and copyright!
How PE works
- Header sections: DOS, Windows, and optional headers are parsed to provide information about the EXE file. For example,
- The magic number starts with “MZ,” which tells the loader that this is an EXE file.
- File Signatures
- Whether the file is compiled for x86 or x64 CPU architecture.
- Creation timestamp.
- Parsing the section table details, such as
- Number of Sections the file contains.
- Mapping the file contents into memory based on
- The EntryPoint address and the offset of the ImageBase.
- RVA: Relative Virtual Address, Addresses related to Imagebase.
- Imports, DLLs, and other objects are loaded into the memory.
- The EntryPoint address is located and the main execution function runs.
Small trick
When you view a PE file in a hex editor and see 4D5A
, it’s actually stored in little-endian byte order. Little-endian byte order means the lower byte (in this case, 5A
) is stored at the lower address, and the higher byte (4D
) is stored at the higher address. So, when these bytes are stored consecutively in memory, they actually appear in the order 5A4D
.
- x86 and x86-64 are using little-endian
- ARM mostly uses little-endian
- Some MIPS , PowerPC and SPARC use big-endian
About Shellcode
Shellcode is mostly used to create a reverse shell and it will modifies the code run flow and update register and program to execute attacker’s code.
It is generally written in Assembly and it will be helpful to evading AV software.But it is really hard.
Things we must to know
- A deep understanding of x86 and x86 CPU architectures.
- Assembly.(Oh no)
- Familiarity with the Linux and Windows.
An example
Print THM,Rocks
The following assembly code uses two main functions:
- System Write function (sys_write) to print out a string we choose.
- System Exit function (sys_exit) to terminate the execution of the program.
In order to call those two functions we need to use syscall.
What is syscall
Syscall is the way we interact with the kernel.It is a mechanism through which a program requests a service from the operating system’s kernel. Syscall is provided by OS kernel to perform operations that they cannot do directly, such as file operations, network communications, memory management, etc.
In summary, system calls are a mechanism that enables user space programs to safely and efficiently utilize services provided by the kernel, while maintaining the stability and security of the operating system. Everything begins with syscall. Higher level programming language makes a encapsulation to provide abstraction.
Also different OS provides different interface.
Linux x64
rax | System Call | rdi | rsi | rdx |
---|---|---|---|---|
0x1 | sys_write | unsigned int fd | const char *buf | size_t count |
0x3c | sys_exit | int error_code |
For sys_write
, the first parameter sent through rdi
is the file descriptor to write to. The second parameter in rsi
is a pointer to the string we want to print, and the third in rdx
is the size of the string to print.
For sys_exit
, rdi needs to be set to the exit code for the program. We will use the code 0, which means the program exited successfully.
Example
global _start |
db
Define Byterax
syscall numberrdi
file descriptor
Let’s take a look of it.
First the program will begin wit _start
,it will jump to MESSAGE
, and push every command into stack , mov rax , 0x1
means call sys_write
,and mov rdi, 0x1
set stdout into 1. And “THM, Rocks!” is pushed after the GOBACK
,so we just pop it to rsi
which should contain the sting we want to print. And set rdx
to the length of the string. The next thing is just call.
How to compile?
root@ubuntu:/home/GS/ASM# nasm -f elf64 thm.asm |
Take a look into the bin file
After we having the file , we could use objdump -d ./thm
to extract the shellcode.
root@ubuntu:/home/GS/ASM# objdump -d thm |
Now we need to extract the hex value from the above output. To do that, we can use objcopy
to dump the .text
section into a new file called thm.text
in a binary format as follows:
root@ubuntu:/home/GS/ASM# objcopy -j .text -O binary thm thm.text |
To confirm that the extracted shellcode works as we expected, we can execute our shellcode and inject it into a C program.
|
We should compile the c program by:
root@ubuntu:/home/GS/ASM# gcc -g -Wall -z execstack thmX.c -o thmx |
The
-z execstack
option modifies the program’s stack segment to make it executable. By default, the stack segment in modern operating systems is non-executable, serving as a security measure to prevent buffer overflow attacks.But here we should set it to execstack.
Mession
To execute the following shellcode to get flag:
unsigned char message[] = { |
The c file should be:
|
Why the C works?
Let’s have a look at (*(void(*)())message)();
*(void(*)()
means converted into a pointer to a function that takes no arguments and returns no value.
After the type conversion, it will be *(void(*)() (), it will be regard as function call.
Generate Shellcode
The advantage of generating shellcode via public tools is that we don’t need to craft a custom shellcode from scratch, but it means that it will be easier to be detected.
Create a shellcode to call calc
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room] |
Shellcode injection to memory
Example below from thm show a way to inject our shellcode into memory and will execute “calc.exe”.
Example
|
To compile it should with
i686-w64-mingw32-gcc calc.c -o calc-MSF.exe
Generate Shellcode from EXE files
Shellcode can also be stored in .bin
files, which is a raw data format. In this case, we can get the shellcode of it using the xxd -i
command.
Example
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room/ShellCodeAv/Bin-Example] |
I like to use vim with
%!xxd
xxd will transfer the hex into c code.
Staged Payloads
Stageless Payloads
It will includes all codes, it just take one step to finish,but it leads to easier detection.
Staged Payloads
While there might be payloads with several stages, the usual case involves having a two-stage payload where the first stage, which we’ll call stage0, is a stub shellcode that will connect back to the attacker’s machine to download the final shellcode to be executed.
Staged vs. Stageless
In the case of stageless payloads, you will find the following advantages:
- The resulting executable packs all that is needed to get our shellcode working.
- The payload will execute without requiring additional network connections. The fewer the network interactions, the lesser your chances of being detected by an IPS.
- If you are attacking a host with very restricted network connectivity, you may want your whole payload to be in a single package.
For staged payloads, you will have:
- Small footprint on disk. Since stage0 is only in charge of downloading the final shellcode, it will most likely be small in size.
- The final shellcode isn’t embedded into the executable. If your payload is captured, the Blue Team will only have access to the stage0 stub and nothing more.
- The final shellcode is loaded in memory and never touches the disk. This makes it less prone to be detected by AV solutions.
- You can reuse the same stage0 dropper for many shellcodes, as you can simply replace the final shellcode that gets served to the victim machine.
Creating Your Own Stager
using System; |
In fact we could use http, useing https is more complex.
Using Https
openssl req -new -x509 -keyout localhost.pem -out localhost.pem -days 365 -nodes |
What happens
Serving HTTP on :: port 8000 (http://[::]:8000/) ... |
As we can see after the code executed it will try to get the shellcode on our machine.
Encode using MSFVenom
Public Tools such as Metasploit provide encoding and encryption features. However, AV vendors are aware of the way these tools build their payloads and take measures to detect them.
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room/ShellCodeAv/Bin-Example] |
We could encode our payload with the encoder we like,here is a example:
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room/ShellCodeAv/Encode] |
Encryption using MSFVenom
Also we can easily generate encrypted payloads using msfvenom.
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room/ShellCodeAv/Encode] |
Here is a example to create a XOR encrypted payload.
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room/ShellCodeAv/Encode] |
Creating a Custom Payload
But payload created by MSFVenom are always easy to be detected! So we can use our own custom encoding schemes so that the AV doesn’t know what to do to analyze our payload.
For this task, we will take a simple reverse shell generated by msfvenom and use a combination of XOR and Base64 to bypass Defender.
Gen payload
First we could create a shellcode without any encryption.
┌──(root㉿kali)-[/Users/GS/Hacking/tmp/THM/Learning-Room/ShellCodeAv/Encode] |
Encoder
using System; |
Self-decoding Payload
using System; |
Although AVs like windows defender are powerful, but they detect virus by signature or decrypting code, so we just need a bit of imagination to customize any method could prove enough for a successful bypass.
Maybe cheating people is hard, but cheating machines is easy, because machines are honesty
Packers
Packers are mostly used by software developers who would like to protect their software from being reverse engineered or cracked.They achieve some level of protection by implementing a mixture of transforms that include compressing, encrypting, adding debugging protections and many others. So packers are also commonly used to obfuscate malware without much effort.