TheWover / donut

Generates x86, x64, or AMD64+x86 position-independent shellcode that loads .NET Assemblies, PE files, and other Windows payloads from memory and runs them with parameters
BSD 3-Clause "New" or "Revised" License
3.42k stars 619 forks source link

Additions to Donut >= v0.9.3 (feedback welcome) #7

Closed odzhan closed 4 years ago

odzhan commented 4 years ago
bit-St0rm commented 4 years ago

+1 for option of base64 encoded shellcode.

aus commented 4 years ago

Option to customize the module name for staged shellcode from command line

phra commented 4 years ago

is it possible to add arguments passing also for normal EXEs?

odzhan commented 4 years ago

At the moment, only DLL functions can accept arguments, but i will look into how it might work. Not sure TBH.

odzhan commented 4 years ago

@phra I added some code to update the command line for EXE files. It's not complete -- It doesn't save and restore the original command line. Worked for Ansi and Unicode versions of a test program that accepts console input, but didn't work for mimikatz. Check out SetCommandLineW in the latest dev version and let me know what you think.

antonioCoco commented 4 years ago

@phra I added some code to update the command line for EXE files. It's not complete -- It doesn't save and restore the original command line. Worked for Ansi and Unicode versions of a test program that accepts console input, but didn't work for mimikatz. Check out SetCommandLineW in the latest dev version and let me know what you think.

Hi @odzhan , my congratulations for this great project! :)

I made some tests with the commit dafbdbd and tried out the SetCommandLineW debugging with payload.exe API.

I used this code as unmanaged EXE test:

int wmain (int argc, wchar_t **argv)
{
    wprintf(L"\nHello from WMAIN\n");
    wprintf(L"\nArgc: %d\n", argc);
    wprintf(L"\nArgv0: %s\n", argv[0]);
    wprintf(L"\nArgv1: %s\n", argv[1]);
    return 0;
}

The problem i see in changing the PEB commandline field is that it affects only calls to GetCommandLineW() and not if a programm access argv[] (like mimikatz). So, the above program crash because it can't find the appropriate indexes.

As a side note the argc,argv parameters inherits from the current process. With that in mind i found a possible workaround.

When you call your host process, i.e. cmd.exe, and you inject the mimikatz.bin into it, your mimikatz payload will inherit the same argv as cmd.exe. This means if you launch your host process with "cmd.exe coffee exit" your mimikatz payload will run the argv commands "coffee exit".

image

Hope this can help! :)

phra commented 4 years ago

atm it seems not possible to pass arguments to .NET exe either. (tested with sharphound)

odzhan commented 4 years ago

@antonioCoco Haven't solved the command line problem, yet. Thanks for the feedback! @phra Haven't tested with sharphound. What parameters does it receive?

antonioCoco commented 4 years ago

@odzhan @stefano118 and i have found a nice way to change the argv parameters and commandline without touching the PEB. We have placed some hooks on the function that manages that variables. (getmainargs, p___argv and GetCommandLine). We have tested it with mimikatz binary on x64 and x86 windows 10 and everything is working as expected. You can check changes here antonioCoco/donut@928443e2d26c9fde9c92d61c585a4d77f6a3e710

Big refactor of the code is ongoing. Once we are done we will submit a PR. :)

odzhan commented 4 years ago

SetCommandLineW does work with the code you posted. commandline

phra commented 4 years ago

SetCommandLineW does work with the code you posted.

shouldn't argc be 2 instead of 3?

odzhan commented 4 years ago

Here's the program running normally and donut building the shellcode for it. first_cmdline

This is the payload being debugged. second_cmdine

Hooking GetCommandLineA and GetCommandLineW is a good idea (was already planning to hook ExitProcess for EXE files) and perhaps that's what we should use instead of updating the data section. The PEB doesn't necessarily need to be changed, just the two variables in the data section.

antonioCoco commented 4 years ago

SetCommandLineW does work with the code you posted. commandline

this will work only if you compile the test program with modern version of cl.exe If you try to compile with mingw it won't work. This because modern version of cl.exe (if i remember correctly starting from 2017) will use the function __p_argv to parse the argv that will read from PEB. Previous version of cl.exe and current versions of mingw will use the classic function getmainargs that won't parse data from PEB

phra commented 4 years ago

Here's the program running normally and donut building the shellcode for it.

arg2 was not visible in the previous screenshot. now it makes sense.

was already planning to hook ExitProcess for EXE files

in case make it configurable since it can be useful when injecting pre-existing processes but not so much when injecting into new, dedicated, spawned processes where having it automatically exiting at the end saves you from doing manual clean up.

antonioCoco commented 4 years ago

Using this as an example code:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int main (int argc, char **argv)
{
    printf("\nHello from ANSI MAIN\n");
    printf("\nArgc: %d\n", argc);
    for(DWORD i=0; i<argc; i++)
        printf("\nArgv[%d]: %s\n", i, argv[i]);
    char *commandline = GetCommandLineA();
    printf("\nGetCommandLineA(): %s\n", commandline);
    printf("\nANSI MAIN End. BB!\n");
    return 0;
}

This is what happens if you compile it with mingw:

image

The changes to commandline doesnt affect argv.

odzhan commented 4 years ago

Well, it should affect argv :) ...but I can see now mingw is using msvcrt!_wcmdln for wmain() and msvcrt!_acmdln for main(). The good news is that these two symbols are exported by msvcrt, so changing the pointers are enough in this case. So, while changing BaseUnicodeCommandLine, the same pointer can be used roughly like this:

wchar_t **_wcmdln = (wchar_t**)inst->api.GetProcAddress(inst->api.GetModuleHandle("msvcrt"), "_wcmdln"); _wcmdln[0] = wcs->Buffer;

antonioCoco commented 4 years ago

Well, it should affect argv :) ...but I can see now mingw is using msvcrt!_wcmdln for wmain() and msvcrt!_acmdln for main(). The good news is that these two symbols are exported by msvcrt, so changing the pointers are enough in this case. So, while changing BaseUnicodeCommandLine, the same pointer can be used roughly like this:

wchar_t **_wcmdln = (wchar_t**)inst->api.GetProcAddress(inst->api.GetModuleHandle("msvcrt"), "_wcmdln"); _wcmdln[0] = wcs->Buffer;

Great! This fixed also mimikatz argv (tested). Which of the 2 approaches is ideal in your opinion? Hooking functions or patching data section ?

odzhan commented 4 years ago

Both are good. The reason I chose to modify data for _acmdln+_wcmdln is just because it was quicker to implement with the existing SetCommandLineW code. BaseDllInitialize that sets BaseUnicodeCommandLine and BaseAnsiCommandLine has been part of windows for 30+ years, so we can safely assume the variables exist in either kernel32.dll or kernelbase.dll for all past and present versions of windows. I've no idea if changing code is more difficult to detect than changing data.

On the other hand, hooking GetCommandLineA/GetCommandLineW and getmainargs/wgetmainargs might be easier to implement so long as a pointer to the instance can be accessed when required. One negative aspect about patching code is when it comes to calling conventions, architectures. Windows supports three, so maybe patching data has advantage there.

The hook I was considering for ExitProcess would just call ExitThread. Currently, the IAT is patched, but this is unreliable and needs updating, so I think an empty slot in the PEB for all versions of windows would be useful to implement hooking. Maybe even the data section of a windows DLL could be used to store pointer to an instance too?

phra commented 4 years ago

i am trying again sharphound with the latest dev branch, but this version i don't get the output. on the v0.9.2 you can see the usage because the command line is not correctly parsed.

branch dev: image

v0.9.2: image

odzhan commented 4 years ago

Try: donut -f sharphound.exe -p --CollectionMethod,All,--SearchForest,--Threads,20

I can see that storing a command line for application like this isn't handled sufficiently by current version of donut. will explore better ways to parse command line.

phra commented 4 years ago

dev branch: (it hangs) image

v0.9.2: (parameters seems ignored) image

phra commented 4 years ago

also i noticed that with .NET exe the injected process is not exiting as with regular exe.. is there anything converting the ExitProcess into ExitThread in the .NET version already in place?

odzhan commented 4 years ago

I'll update parsing arguments tomorrow. Should have just copied -p argument to be processed with CommandLineToArgvW. Nothing is converting ExitProcess into ExitThread for .NET EXE. If you look at the import table (dumpbin /imports sharphound.exe), you'll see mscoree!_CorExeMain but internally mscoree!CorExitProcess is resolved dynamically upon exiting the application. The plan is to hook both mscoree!CorExitProcess for managed EXE and ntdll!RtlExitUserProcess for unmanaged EXE so the AppDomain can be unloaded and doesn't leave behind footprint.

phra commented 4 years ago

i just suggest leaving the exit behaviour configurable since sometimes exiting the whole process can be wanted behaviour (spawned processes) versus exiting just the thread (existing processes) for all cases.

odzhan commented 4 years ago

Yes, there will be an option so the user can specify behaviour.

antonioCoco commented 4 years ago

Both are good. The reason I chose to modify data for _acmdln+_wcmdln is just because it was quicker to implement with the existing SetCommandLineW code. BaseDllInitialize that sets BaseUnicodeCommandLine and BaseAnsiCommandLine has been part of windows for 30+ years, so we can safely assume the variables exist in either kernel32.dll or kernelbase.dll for all past and present versions of windows. I've no idea if changing code is more difficult to detect than changing data.

On the other hand, hooking GetCommandLineA/GetCommandLineW and getmainargs/wgetmainargs might be easier to implement so long as a pointer to the instance can be accessed when required. One negative aspect about patching code is when it comes to calling conventions, architectures. Windows supports three, so maybe patching data has advantage there.

The hook I was considering for ExitProcess would just call ExitThread. Currently, the IAT is patched, but this is unreliable and needs updating, so I think an empty slot in the PEB for all versions of windows would be useful to implement hooking. Maybe even the data section of a windows DLL could be used to store pointer to an instance too?

The main problem we found in hooking the functions was in accesing the donut instance. From debug build it was possible using global variables but then we noticed that in the release version everything get stripped and the final PIC is only the .text section of payload.exe. Then we found a big hack for hijacking function pointers and let them point to instance pointer, so it was possible, from the hooked function, to access the instance data through a function pointer. This hack works well for x64 bit but not for x86 payloads. Sadly we couldn't find a way to fix the x86 build neither the root cause of why we couldn't use function pointer as integer like in the x64 bit hack.

In the end we choose a more low level approach by defining the asm code of the hooked function and allocating at runtime the memory to run it. This address then will be the one that will replace the address of the real function. Having the asm code manipulation is easier to pass pointers from donut instance directly in registers/stack. Luckily enough the function to be hooked for the commandline hadn't that hard logic so the asm code needed wasn't that hard to write and debug.

In that way we are able to hook every function we need with the access to donut instance. Some considerations must be taken... For every hooked function you should define the corresponding asm code and allocate at runtime. This could be tough if you need to define hooking function with complex logics, (i.e. rootkit behaviours)

It would be nice to have a reliable way to access instance data from hooked functions without defining the asm code, but just writing the c code within the inmem_pe module but ATM we couldn't find a way to do that if not using asm code. For sure if the instance pointer can be placed somewhere on PEB at a fixed offset, it could be easily reached from the hooked function and could take away the effort of writing asm code.

Considering that you cannot pass any parameters to hooked functions, i don't think that using the data section af a dll could work because, from the hooked function, you will need the instance itself to find the instance data in the data section .

hackabean commented 4 years ago

Hey @odzhan amazing project, thank you for your work. Just wanted to kindly ask for +1 for base64 if possible.

odzhan commented 4 years ago

Yes, just added it. Will update dev repo soon.

odzhan commented 4 years ago

@phra With the new method of processing command line in dev branch, how does sharphound work for you? donut -f sharphound.exe -p"--CollectionMethod All --SearchForest --Threads 20"

Unfortunately I can't properly test sharphound at the moment.

odzhan commented 4 years ago

@hackabean Just in case you didn't see dev branch updated. -e option now encodes shellcode with base64. Also copies to clipboard on windows.

hackabean commented 4 years ago

I have just noticed, that's amazing @odzhan many thanks. I am exploring all donut has to offer at the moment and it makes me wonder, can it ever run python binaries compiled with pyinstaller or py2exe ?

odzhan commented 4 years ago

@TheWover and myself were talking about supporting python some months ago. Since python isn't installed by default on windows, donut currently doesn't support it. However, we know Microsoft intend to support python in the future. We haven't tested anything generated by py2exe or pyinstaller.

hackabean commented 4 years ago

No worries, project like that just makes you wonder about the possibilities. Thanks for answering and have a great day/night.

phra commented 4 years ago

@odzhan i have tried with mimikatz and the command line is somehow corrupted:

command: donut -f /opt/mimikatz/x64/mimikatz.exe -a 2 -o /opt/mimikatz/x64/mimicmd.bin -p "log base64 coffee exit"

output:

msf5 post(windows/manage/shellcode_inject) > run

[*] Running module against TESTBOX
[*] Spawned process 6256
[*] Process found checking Architecture
[+] Process is the same architecture as the payload
[*] Injecting shellcode into process ID 6256
[*] Allocating memory in process 6256
[*] Allocated memory at address 0x1c035f00000, for 1027931 byte shellcode
[+] Successfully injected payload into process: 6256
[*] Interacting

  .#####.   mimikatz 2.2.0 (x64) #18362 Aug 14 2019 01:31:47
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(commandline) # base64
isBase64InterceptInput  is false
isBase64InterceptOutput is false
[*] Post module execution completed
odzhan commented 4 years ago

For unmanaged EXE files, a random 4 byte string is added.

mimikatz_donut

mimikatz_running