前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >浅析syscall

浅析syscall

作者头像
鸿鹄实验室
发布于 2022-02-17 05:54:42
发布于 2022-02-17 05:54:42
3.8K00
代码可运行
举报
文章被收录于专栏:鸿鹄实验室鸿鹄实验室
运行总次数:0
代码可运行

最近在面试一些人的免杀问题时总会谈到syscall,但对于一些检测、细节、绕过检测反而没有说的很清楚,本文简单总结一些syscall的方式,来帮你唬过面试官。

简介

目前syscall已经成为了绕过AV/EDR所使用的主流方式,可以用它绕过一些敏感函数的调用监控(R3)。主流的AV/EDR都会对敏感函数进行HOOK,而syscall则可以用来绕过该类检测。

Hook一般放置在kernel32.dll、kernelbase.dl、ntdll.dll之中,在EDR环境中,如果调用的函数被 HOOK,则跳转到EDR的dll之中,该dll一般在进程启动时被加载。这里拿Bitdefender Antivirus 进行测 试。

这是一个简单的测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <Windows.h> 
#include <iostream>
int main() {  
  STARTUPINFO            sinfo;    
  PROCESS_INFORMATION    pinfo;    
  memset(&sinfo, 0, sizeof(STARTUPINFO));    
  memset(&pinfo, 0, sizeof(PROCESS_INFORMATION));    
  sinfo.dwFlags = STARTF_USESHOWWINDOW;    s
  info.wShowWindow = SW_SHOWMAXIMIZED;   
   BOOL bSucess = CreateProcess(L"C:\\Windows\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &sinfo, &pinfo);    
   std::cout << "Hello World!\n"; 
   }

当进程启动时会加载Bitdefender的dll,即atcuf64.dll

如果进行调试则可以看到atcuf64的调用:

EDR一般会对多个函数进行HOOK,常见的Hook手段有JMP、EAT、GPA,Bitdefender的HOOK列表 可以在这里找到:https://github.com/Mr-Un1k0d3r/EDRs/blob/main/bitdefender.txt,而针对性的 Unhook可以在此处找到:https://github.com/plackyhacker/Unhook-BitDefender 除了这种手动检测 之外可以编写代码进行批量测试,主要测试逻辑如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 if (correct_bytes[0] == assemblyBytes[0] && correct_bytes[1] == assemblyBytes[1] && correct_bytes[2] == assemblyBytes[2] && correct_bytes[3] == assemblyBytes[3])            {                printf( "\t[*]%s has NOT been hooked!\n", szExportedFunctionName );                nClean++;            }            else            {                printf("\t[*] %s HAS been hooked!\n", szExportedFunctionName);                printf("\t\t");             }

效果

windows下函数的调用流程是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OpenProcess() [Kernel32] -> OpenProcess() [Kernelbase] -> NtOpenProcess() [Ntdll] -> Direct syscall to the kernel -> | Kernel Mode |

在执行流程中以Nt和Zw开头的Ntdll函数进行执行,他们后的代码都在内核中运行。

所以直接系统调用是R3执行的后一步,将函数执行转发给R0。整个过程 唯一的区别就是EAX中的数字,也就是syscall number,不同操作系统版本之间syscall number不同。可以参考https://j00ru.vexillium.org/syscalls/nt/64/

即下面这种形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0x4c 0x8b 0xd1 0xb8 0xZZ 0xZZ 0x00 0x00

所以为了绕过HOOK,我们可以使用Syscall。使用前提为:

  • 不使用GetModuleHandle找到ntdll 的基址
  • 解析DLL的导出表
  • 查找syscall number
  • 执行syscall

查找DLL地址

此类操作我们需要用到PEB_LDR_DATA中的InMemoryOrderModuleList,说白了还是PEB、TEB的使 用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct _LIST_ENTRY {   struct _LIST_ENTRY *Flink;   struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

PEB_LDR_DATA的InMemoryOrderModuleList.Flink指向第一个模块加载的InMemoryOrderLinks, InMemoryOrderLinks在LDR_DATA_TABLE_ENTRY结构体之中,而模块加载信息都在 _LDR_DATA_TABLE_ENTRY中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct _LDR_DATA_TABLE_ENTRY {    PVOID Reserved1[2];    LIST_ENTRY InMemoryOrderLinks;    PVOID Reserved2[2];    PVOID DllBase; // Base address of the module in memory     PVOID EntryPoint;    PVOID Reserved3;    UNICODE_STRING FullDllName; // Full path + name of the dll    BYTE Reserved4[8];    PVOID Reserved5[3];    union {        ULONG CheckSum;        PVOID Reserved6;    };    ULONG TimeDateStamp; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

如果我们想浏览每个模块,我们只需要跳转到每个InMemoryOrderLinks.Flink。在 _LDR_DATA_TABLE_ENTRY结构中,我们可以检索到模块的基本地址和它的名称等。更详细的信息可以 查看:https://mohamed-fakroud.gitbook.io/red-teamings-dojo/shellcoding/leveraging-from-pe-pa rsing-technique-to-write-x86-shellcode

解析导出地址表 (EAT)

一旦我们检索到 Dll 基地址,我们需要找到目标函数的地址。为此,我们必须解析 DLL 的导出部分以 找到Export Address Table(EAT)。包含 DLL的EAT所有函数地址。这个工作可以交给_IMAGE_EXPORT_DIRECTORY,其架构体如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct _IMAGE_EXPORT_DIRECTORY {                  DWORD   Characteristics;                DWORD   TimeDateStamp;                    WORD    MajorVersion;                 WORD    MinorVersion;                 DWORD   Name;                   // The name of the Dll    DWORD   Base;                   // Number to add to the values found in AddressOfNameOrdinals to retrieve the "real" Ordinal number of the function (by real I mean used to call it by ordinals).    DWORD   NumberOfFunctions;      // Number of all exported functions          DWORD   NumberOfNames;          // Number of functions exported by name          DWORD   AddressOfFunctions;     // Export Address Table. Address of the functions addresses array.       DWORD   AddressOfNames;         // Export Name table. Address of the functions names array.            DWORD   AddressOfNameOrdinals;  // Export sequence number table.  Address of the Ordinals (minus the value of Base) array.             } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

调用Syscall

有了上面的内容,下面就是进行动态查找syscall number和调用syscall ,下面是一些常见的Syscall技 术

(https://github.com/jthuraisamy/SysWhispers不在其中)。

Hell’s Gate:地狱之门

地址之门,项目地址为:https://github.com/am0nsec/HellsGate、项目简介地址:

https://vxug.fak edoma.in/papers/VXUG/Exclusive/HellsGate.pdf 利用代码动态查找0x4c 0x8b 0xd1 0xb8 0xZZ 0xZZ 0x00 0x00:

也就是函数从 RCX 寄存器移入 R10 寄存器,然后将系统调用移入 EAX,与自带的asm文件对应:

测试结果,未能绕过Bitdefender (shellcode为手写的shellcode不存在被杀的问题):

Halo’s Gate:光环之门

光环之门主要是为了防止当mov r10,rcx被HOOK时地狱之门失效的问题。项目地址为:https://blog. sektor7.net/#!res/2021/halosgate.md 成熟的项目为:https://github.com/boku7/AsmHalosGate 其 思路为syscall stub中的 syscall ID 是彼此递增的!这意味着,例如,如果你被钩住了,但下面的下一个 函数没有,你只需要检索它的系统调用并减去 1 即可获得当前函数的系统调用。

代码如下:

Tartarus Gate:塔尔塔罗斯之门

在光环之门的基础上增加了一些asm混淆。从:

变成了:

项目地址为:https://github.com/trickster0/TartarusGate

FreshyCalls

mdsec新出的一种syscall的方式,项目地址:https://github.com/crummie5/FreshyCalls 文章地 址:https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-syst em-calls-for-red-teams/ 主要思路为从函数地址中获取Syscall ID。在调试器中按照地址排序,ID号递增

所以我们可以将Nt函数的地址进行排序,便可以在不解析syscall stub 的情况下获取syscall id。实现 代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (size_t i = 0; i < export_dir->NumberOfNames; i++) {    function_name = reinterpret_cast<const char *>(ntdll_base + names_table[i]);
    // If the name of the function start with "Nt" and don't start with "Ntdll"    // we retrieve the info    if (function_name.rfind("Nt", 0) == 0 && function_name.rfind("Ntdll", 0) == std::string::npos) {      stub_ordinal = names_ordinals_table[i];      stub_address = ntdll_base + functions_table[stub_ordinal];            // We put the RVA as a key and the function name as the value.      // This is a sorted map.      // The elements are automatically sorted using the key value.      // This means that when all the Nt function will be loaded,       // the first element will be the Nt function with the lowest address       // and the last the one with the biggest address.      stub_map.insert({stub_address, function_name});    }  }
  // `stub_map` is ordered from lowest to highest using the stub address. Syscalls numbers are  // assigned using this ordering too. The lowest stub address will be the stub with the lowest  // syscall number (0 in this case). We just need to iterate `stub_map` and iterate the syscall  // number on every iteration.
  static inline void ExtractSyscallsNumbers() noexcept {    uint32_t syscall_no = 0;    // The stub_map filled previously in the code presented above    for (const auto &pair: stub_map)  {      //Creation of a map associating function name and syscall identifier.      syscall_map.insert({pair.second, syscall_no});      syscall_no++;    }  };

Syswhispers2

与FreshyCalls基本相同,项目地址如下:https://github.com/jthuraisamy/SysWhispers2 只是把Nt 换成了zw,nt与zw的区别参考:https://docs.microsoft.com/en-us/windows-hardware/drivers/kern el/using-nt-and-zw-versions-of-the-native-system-services-routines

实现代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  DWORD i = 0;
  /*
  //For info. It's in the header file.
  typedef struct _SW2_SYSCALL_ENTRY
  {
    DWORD Hash;
    DWORD Address;
  } SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;
  
  #define SW2_MAX_ENTRIES 500
  */
  
    PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
    do
    {
        // Retrieve function name from the Dll.
        PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);

        // Check if the function name starts with "Zw"
        if (*(USHORT*)FunctionName == 'wZ')
        {
      // If yes, hash the name (for AV/EDR/Malware Analyst evasion reasons) and put it in an Entries element
            Entries[i].Hash = SW2_HashSyscall(FunctionName);
      // Put also the address of the function
            Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];

            i++;
            if (i == SW2_MAX_ENTRIES) break;
        }
    } while (--NumberOfNames);

    // Save total number of system calls found.
    SW2_SyscallList.Count = i;

    // Sort the list by address in ascending order.
    for (i = 0; i < SW2_SyscallList.Count - 1; i++)
    {
        for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
        {
            if (Entries[j].Address > Entries[j + 1].Address)
            {
                // Swap entries.
                SW2_SYSCALL_ENTRY TempEntry;

                TempEntry.Hash = Entries[j].Hash;
                TempEntry.Address = Entries[j].Address;

                Entries[j].Hash = Entries[j + 1].Hash;
                Entries[j].Address = Entries[j + 1].Address;

                Entries[j + 1].Hash = TempEntry.Hash;
                Entries[j + 1].Address = TempEntry.Address;
            }
        }
    }

    return TRUE;
}

ParallelSyscalls

也是一种由mdsec提出的方式,项目地址:https://github.com/mdsecactivebreach/ParallelSyscalls 文章地址:https://www.mdsec.co.uk/2022/01/edr-parallel-asis-through-analysis/ 主要思路为利用 LdrpThunkSignature 恢复系统调用。实现代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BOOL InitSyscallsFromLdrpThunkSignature()
{
    PPEB Peb = (PPEB)__readgsqword(0x60);
    PPEB_LDR_DATA Ldr = Peb->Ldr;
    PLDR_DATA_TABLE_ENTRY NtdllLdrEntry = NULL;

    for (PLDR_DATA_TABLE_ENTRY LdrEntry = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink;
        LdrEntry->DllBase != NULL;
        LdrEntry = (PLDR_DATA_TABLE_ENTRY)LdrEntry->InLoadOrderLinks.Flink)
    {
        if (_wcsnicmp(LdrEntry->BaseDllName.Buffer, L"ntdll.dll", 9) == 0)
        {
            // got ntdll
            NtdllLdrEntry = LdrEntry;
            break;
        }
    }

    if (NtdllLdrEntry == NULL)
    {
        return FALSE;
    }

    PIMAGE_NT_HEADERS ImageNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)NtdllLdrEntry->DllBase + ((PIMAGE_DOS_HEADER)NtdllLdrEntry->DllBase)->e_lfanew);
    PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&ImageNtHeaders->OptionalHeader + ImageNtHeaders->FileHeader.SizeOfOptionalHeader);

    ULONG_PTR DataSectionAddress = NULL;
    DWORD DataSectionSize;

    for (WORD i = 0; i < ImageNtHeaders->FileHeader.NumberOfSections; i++)
    {
        if (!strcmp((char*)SectionHeader[i].Name, ".data"))
        {
            DataSectionAddress = (ULONG_PTR)NtdllLdrEntry->DllBase + SectionHeader[i].VirtualAddress;
            DataSectionSize = SectionHeader[i].Misc.VirtualSize;
            break;
        }
    }

    DWORD dwSyscallNo_NtOpenFile = 0, dwSyscallNo_NtCreateSection = 0, dwSyscallNo_NtMapViewOfSection = 0;

    if (!DataSectionAddress || DataSectionSize < 16 * 5)
    {
        return FALSE;
    }

    for (UINT uiOffset = 0; uiOffset < DataSectionSize - (16 * 5); uiOffset++)
    {
        if (*(DWORD*)(DataSectionAddress + uiOffset) == 0xb8d18b4c &&
            *(DWORD*)(DataSectionAddress + uiOffset + 16) == 0xb8d18b4c &&
            *(DWORD*)(DataSectionAddress + uiOffset + 32) == 0xb8d18b4c &&
            *(DWORD*)(DataSectionAddress + uiOffset + 48) == 0xb8d18b4c &&
            *(DWORD*)(DataSectionAddress + uiOffset + 64) == 0xb8d18b4c)
        {
            dwSyscallNo_NtOpenFile = *(DWORD*)(DataSectionAddress + uiOffset + 4);
            dwSyscallNo_NtCreateSection = *(DWORD*)(DataSectionAddress + uiOffset + 16 + 4);
            dwSyscallNo_NtMapViewOfSection = *(DWORD*)(DataSectionAddress + uiOffset + 64 + 4);
            break;
        }
    }

    if (!dwSyscallNo_NtOpenFile)
    {
        return FALSE;
    }

    ULONG_PTR SyscallRegion = (ULONG_PTR)VirtualAlloc(NULL, 3 * MAX_SYSCALL_STUB_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (!SyscallRegion)
    {
        return FALSE;
    }

    NtOpenFile = (FUNC_NTOPENFILE)BuildSyscallStub(SyscallRegion, dwSyscallNo_NtOpenFile);
    NtCreateSection = (FUNC_NTCREATESECTION)BuildSyscallStub(SyscallRegion + MAX_SYSCALL_STUB_SIZE, dwSyscallNo_NtCreateSection);
    NtMapViewOfSection = (FUNC_NTMAPVIEWOFSECTION)BuildSyscallStub(SyscallRegion + (2* MAX_SYSCALL_STUB_SIZE), dwSyscallNo_NtMapViewOfSection);

    return TRUE;
}

缺点是会在加载处显示两个ntdll。

绕过syscall检测

下面是一些针对syscall检测的绕过方法。

int2Eh法

来源:https://captmeelo.com/redteam/maldev/2021/11/18/av-evasion-syswhisper.html 就是把 syscall关键字换成了int 2eh

Egg Hunting

因为调用syscall的过程基本都是固定的,所以我们可以更改其行为逻辑。

在汇编中我们可以使用DB进行字节插入,比如“Hello”,我们便可以:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DB 77h ; 'H' DB 0h  ; 'e' DB 0h  ; 'l' DB 74h ; 'l' DB 0h  ; 'o'

使用这个技巧,我们可以放置一系列已知字节(egg)作为syscall指令的占位符,并在运行时替换 它。比如这样:

结果

增加syscall混淆后,成功绕过bitdefender

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿鹄实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
驱动开发:内核RIP劫持实现DLL注入
本章将探索内核级DLL模块注入实现原理,DLL模块注入在应用层中通常会使用CreateRemoteThread直接开启远程线程执行即可,驱动级别的注入有多种实现原理,而其中最简单的一种实现方式则是通过劫持EIP的方式实现,其实现原理可总结为,挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,并把相关的指令机器码和数据拷贝到里面去,然后直接修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关代码后,然后再次跳转回来执行原始指令集。
王 瑞
2023/10/11
1.2K0
驱动开发:内核RIP劫持实现DLL注入
驱动开发:内核RIP劫持实现DLL注入
本章将探索内核级DLL模块注入实现原理,DLL模块注入在应用层中通常会使用CreateRemoteThread直接开启远程线程执行即可,驱动级别的注入有多种实现原理,而其中最简单的一种实现方式则是通过劫持EIP的方式实现,其实现原理可总结为,挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,并把相关的指令机器码和数据拷贝到里面去,然后直接修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关代码后,然后再次跳转回来执行原始指令集。
王 瑞
2023/06/16
1K0
驱动开发:内核RIP劫持实现DLL注入
如何找SSDT中精准的
我们可以通过枚举ntdll.dll的导出函数来间接枚举SSDT所有表项所对应的函数,因为所有的内核服务函数对应于ntdll.dll的同名函数都是这样开头的:
战神伽罗
2019/07/24
1.3K0
驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章《内核RIP劫持实现DLL注入》介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过NtCreateThreadEx这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,NtCreateThreadEx函数最终会调用ZwCreateThread,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
王 瑞
2023/06/25
5350
驱动开发:内核远程线程实现DLL注入
Hell's Gate的一次尝试入门
接触安全已经一年多了,在实习工作中跟进项目的时候,以前我的弱项也逐步暴露出来,并越发明显,我不懂免杀与工具开发,钓鱼、下马的工作无法顺利进行,几乎就是面向google的渗透测试工程。
Nayon
2023/04/17
1.3K0
Hell's Gate的一次尝试入门
edr对抗技术1-api unhook output
这里借用别人的一张图,大概是这样:因为刚开始是有一个ntdll被载入内存,然后杀软对其hook,自然也就是修改了代码段。然后这个时候,我们用新的ntdll的代码段覆盖被hook的代码段,实现ntdll重载。
Gamma实验室
2024/05/14
7840
edr对抗技术1-api unhook output
完美实现GetProcAddress
我们知道kernel32.dll里有一个GetProcAddress函数,可以找到模块中的函数地址,函数原型是这样的: WINBASEAPI FARPROC WINAPI GetProcAddress( IN HMODULE hModule, IN LPCSTR lpProcName ); hModule 是模块的句柄,说白了就是内存中dll模块的首地址 loProcName 一般指函数名称的字符串地址,也可能是指序号,如何区分呢? 我们这样 if (((DWORD)lpProcName& 0xFFFF0000) == 0) { //这里是序号导出的; } { //这里是函数名称导出的 } 最终真找到匹配的函数地址,然后返回就ok了,但是前提是你需要了解PE的导出表 下面简单说一下,首先从PE里找到下面这个结构,如果不知道如何找的话,建议在坛子里搜索一下PE结构解析的文章,找到这个结构应该还是很简单的 typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 具体什么意思呢? 虽然, kanxue老大在这里写的很漂亮了都 http://www.pediy.com/tutorial/chap8/Chap8-1-7.htm 我还是打算啰嗦一下 Base 函数以序号导出的时候的序号基数,从这个数开始递增 NumberOfFunctions 本dll一共有多少个导出函数,不管是以序号还是以函数名导出 NumberOfFunctions 本dll中以能够以函数名称导出的函数个数(注意,说一下,其实函数里的每一个函数都能通过序号导出,但是为了兼容性等等,也给一些函数提供用函数名称来导出) AddressOfFunctions 指向一个DWORD数组首地址,共有NumberOfFunctions 个元素,每一个元素都是一个函数地址 AddressOfNames 指向一个DWORD数组首地址,共有NumberOfNames个元素,每一个元素都是一个字符串(函数名字符串)首地址 AddressOfNameOrdinals指向一个WORD数组首地址,共有NumberOfNames个元素,每一个元素都是一个函数序号 我们说的最后俩数组,其实是一种一一对应的关系,假如分别叫 dwNames[] 和 dwNamesOrdinals[], 假如dwNames[5]的值(这个指是一个地址,前面都说了)指向的字符串等于“GetValue”,那么dwNamesOrdinals[5]的值(这个指是一个序号,前面都说了),就是GetValue导出函数的序号啦,那么怎样找到地址呢? 这时候就需要用到第一个数组了,假如名字叫dwFuncAddress[], GetValue的导出地址就是 dwFuncAddress[dwNamesOrdinals[5]] + 模块基址 好了,啰嗦了这么多,看代码: DWORD MyGetProcAddress( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // function name ) { int i=0; PIMAGE_DOS_HEADER pImageDosHeader = NULL; PIMAGE_NT_HEADERS pImageNtHeader = NULL; PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL; pImageDosHeader=(PIMAGE_DOS_HEADER)hModule; pImageNtHeader=(PIMAGE_NT_HEADERS)((DWORD)hModule+pImageDosHeader->e_lfanew); pImageExportDirectory=(PIMAGE_EXPORT_DIREC
战神伽罗
2019/07/24
1.6K0
x64下Hash获取Kernel32基地址
代码中包含x64Asm 其中函数也是可以算hash的.自己写asm遍历导出表即可.
IBinary
2021/02/05
8440
x64下Hash获取Kernel32基地址
C2基石--syscall模块及amsi bypass
最近读了一些C2的源码,其中shad0w的syscall模块具有很高的移植性,分享给有需要的朋友。
鸿鹄实验室
2021/08/25
7190
驱动开发:内核扫描SSDT挂钩状态
在笔者上一篇文章《驱动开发:内核实现SSDT挂钩与摘钩》中介绍了如何对SSDT函数进行Hook挂钩与摘钩的,本章将继续实现一个新功能,如何检测SSDT函数是否挂钩,要实现检测挂钩状态有两种方式,第一种方式则是类似于《驱动开发:摘除InlineHook内核钩子》文章中所演示的通过读取函数的前16个字节与原始字节做对比来判断挂钩状态,另一种方式则是通过对比函数的当前地址与起源地址进行判断,为了提高检测准确性本章将采用两种方式混合检测。
王 瑞
2023/06/06
4410
驱动开发:内核扫描SSDT挂钩状态
驱动开发:取进程模块的函数地址
在笔者上一篇文章《驱动开发:内核取应用层模块基地址》中简单为大家介绍了如何通过遍历PLIST_ENTRY32链表的方式获取到32位应用程序中特定模块的基地址,由于是入门系列所以并没有封装实现太过于通用的获取函数,本章将继续延申这个话题,并依次实现通用版GetUserModuleBaseAddress()取远程进程中指定模块的基址和GetModuleExportAddress()取远程进程中特定模块中的函数地址,此类功能也是各类安全工具中常用的代码片段。
王 瑞
2023/06/28
4920
驱动开发:取进程模块的函数地址
暴力搜索x64 ssdt 以及挂钩HOOK
IDA加载ntoskrnl.exe 微软符号,进入,jump by name,随便输入一个,比如zwwritefile,层层跟踪,进入,可以发现
战神伽罗
2019/07/24
2.5K0
1.15 自实现GetProcAddress
在正常情况下,要想使用GetProcAddress函数,需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址,接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址,但在有时这个函数会被保护起来,导致我们无法直接调用该函数获取到特定函数的内存地址,此时就需要自己编写实现LoadLibrary以及GetProcAddress函数,该功能的实现需要依赖于PEB线程环境块,通过线程环境块可遍历出kernel32.dll模块的入口地址,接着就可以在该模块中寻找GetProcAddress函数入口地址,当找到该入口地址后即可直接调用实现动态定位功能。
王 瑞
2023/10/11
3610
1.15 自实现GetProcAddress
驱动开发:内核中枚举进线程与模块
内核枚举进程使用PspCidTable 这个未公开的函数,它能最大的好处是能得到进程的EPROCESS地址,由于是未公开的函数,所以我们需要变相的调用这个函数,通过PsLookupProcessByProcessId函数查到进程的EPROCESS,如果PsLookupProcessByProcessId返回失败,则证明此进程不存在,如果返回成功则把EPROCESS、PID、PPID、进程名等通过DbgPrint打印到屏幕上。
王 瑞
2022/12/28
6070
驱动开发:内核中枚举进线程与模块
Windows 内核驱动程序完整性校验的原理分析
在上一篇文章中提到了 Windows Vista 及之后版本的 Windows 操作系统在驱动程序加载完成后,驱动中调用的一些系统回调函数(如 ObRegisterCallbacks,可用来监控系统中对进线程句柄的操作,如打开进程、复制线程句柄等)等 API 中会通过 MmVerifyCallbackFunction 函数对该驱动程序进行完整性检查,检测未通过则会返回 0xC0000022 拒绝访问的返回值。在这篇文章中将会对这个函数进行简单的分析,以明确其原理。
稻草小刀
2022/12/12
1.3K0
Windows 内核驱动程序完整性校验的原理分析
完美实现GetProcAddress
我们知道kernel32.dll里有一个GetProcAddress函数,可以找到模块中的函数地址,函数原型是这样的: WINBASEAPI FARPROC WINAPI GetProcAddress( IN HMODULE hModule, IN LPCSTR lpProcName ); hModule 是模块的句柄,说白了就是内存中dll模块的首地址 loProcName 一般指函数名称的字符串地址,也可能是指序号,如何区分呢? 我们这样 if (((DWORD)lpProcName& 0xFFFF0000) == 0) { //这里是序号导出的; } { //这里是函数名称导出的 } 最终真找到匹配的函数地址,然后返回就ok了,但是前提是你需要了解PE的导出表 下面简单说一下,首先从PE里找到下面这个结构,如果不知道如何找的话,建议在坛子里搜索一下PE结构解析的文章,找到这个结构应该还是很简单的 typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 具体什么意思呢? 虽然, kanxue老大在这里写的很漂亮了都 http://www.pediy.com/tutorial/chap8/Chap8-1-7.htm 我还是打算啰嗦一下 Base 函数以序号导出的时候的序号基数,从这个数开始递增 NumberOfFunctions 本dll一共有多少个导出函数,不管是以序号还是以函数名导出 NumberOfFunctions 本dll中以能够以函数名称导出的函数个数(注意,说一下,其实函数里的每一个函数都能通过序号导出,但是为了兼容性等等,也给一些函数提供用函数名称来导出) AddressOfFunctions 指向一个DWORD数组首地址,共有NumberOfFunctions 个元素,每一个元素都是一个函数地址 AddressOfNames 指向一个DWORD数组首地址,共有NumberOfNames个元素,每一个元素都是一个字符串(函数名字符串)首地址 AddressOfNameOrdinals指向一个WORD数组首地址,共有NumberOfNames个元素,每一个元素都是一个函数序号 我们说的最后俩数组,其实是一种一一对应的关系,假如分别叫 dwNames[] 和 dwNamesOrdinals[], 假如dwNames[5]的值(这个指是一个地址,前面都说了)指向的字符串等于“GetValue”,那么dwNamesOrdinals[5]的值(这个指是一个序号,前面都说了),就是GetValue导出函数的序号啦,那么怎样找到地址呢? 这时候就需要用到第一个数组了,假如名字叫dwFuncAddress[], GetValue的导出地址就是 dwFuncAddress[dwNamesOrdinals[5]] + 模块基址 好了,啰嗦了这么多,看代码:
战神伽罗
2019/07/24
2.8K0
2.5 Windows驱动开发:DRIVER_OBJECT对象结构
在Windows内核中,每个设备驱动程序都需要一个DRIVER_OBJECT对象,该对象由系统创建并传递给驱动程序的DriverEntry函数。驱动程序使用此对象来注册与设备对象和其他系统对象的交互,并在操作系统需要与驱动程序进行交互时使用此对象。DRIVER_OBJECT对象还包含了与驱动程序所管理的设备对象相关联的设备扩展结构,以及用于处理I/O请求的函数指针等信息。它是驱动程序与操作系统内核之间的桥梁,用于协调设备的操作和管理。
王 瑞
2023/11/15
3830
2.5 Windows驱动开发:DRIVER_OBJECT对象结构
shellcode编写指南
linux的shellcode就不用说了,直接通过一个int 0x80系统调用,指定想调用的函数的系统调用号(syscall),传入调用函数的参数,即可,懂的都懂。
Gamma实验室
2021/03/10
1.7K0
shellcode编写指南
1.15 自实现GetProcAddress
在正常情况下,要想使用GetProcAddress函数,需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址,接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址,但在有时这个函数会被保护起来,导致我们无法直接调用该函数获取到特定函数的内存地址,此时就需要自己编写实现LoadLibrary以及GetProcAddress函数,该功能的实现需要依赖于PEB线程环境块,通过线程环境块可遍历出kernel32.dll模块的入口地址,接着就可以在该模块中寻找GetProcAddress函数入口地址,当找到该入口地址后即可直接调用实现动态定位功能。
王 瑞
2023/09/04
4840
1.15 自实现GetProcAddress
反射Dll注入
上一篇我们介绍了CreateRemoteThread+LoadLibrary进行注入的技巧。但是这种方法实在是太过格式化,所以几乎所有的安全软件都会监控这种方法。所以HarmanySecurity的Stephen Fewer提出了ReflectiveDLL Injection,也就是反射DLL注入。
全栈程序员站长
2022/08/29
1.1K0
相关推荐
驱动开发:内核RIP劫持实现DLL注入
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档