本文将在 x64 的 dotNET 环境中嵌入一段汇编 shellcode,它的功能主要是“原子变量自增 / InterlockedIncrement32”的汇编实现,用于阐述如何在 x64 环境下运行你的 shellcode 代码。
动态运行 shellcode 代码,在 Win32 运行一块代码,则即指向代码的内存必须具有 PAGE_EXECUTE_READ 的权限,否则视作缓冲区,在 dotNET ,你可以通过“VirtualProtectEx、VirtualProtect”两个函数修改其内存的属性值。
同时一个重要点是,运行 x64 的 shellcode 要求一点是,你必须包含“汇编函数”的栈布局、平衡栈等过程,这与VC中直接内链汇编不同,只需要编写 body 部分(x64 的环境下VC处理内链接汇编也比较麻烦,但相对好得多)
mov dword ptr [rsp+8],ecx
push rbp
push rdi
sub rsp,0C8h
mov rbp,rsp
mov rdi,rsp
mov ecx,32h
mov eax,0CCCCCCCCh
rep stos dword ptr [rdi]
mov ecx,dword ptr [rbp+00000000000000E0h]
mov eax,1
lock xadd dword ptr[ecx],eax
inc eax
lea rsp,[rbp+00000000000000C8h]
pop rdi
pop rbp
ret
以上是一串“原子自增”的汇编代码,它可以在 x64 的环境下正确的执行,其用户代码区域(body 部分)在下面的代码中被标识出来,它其实很简单仅仅只是做了“四句话”把 rbp+00000000000000E0h 的地址拷贝到 ecx 中,然后把 1 拷贝到 eax,然后在利用 x86/x64 处理器指令集提供的 lock xadd 指令将其相加,最后为 eax 寄存器加 1。
mov ecx,dword ptr [rbp+00000000000000E0h]
mov eax,1
lock xadd dword ptr[ecx],eax
inc eax
说明一点在 x64 环境下是兼容,x86 环境的,这也是为什么 x86 应用可以运行在 x64 的处理器平台上面,但有一个比较重要的小点或许需要单独说明一点,在 x64 处理器中当机器代码操作类似如 eax (32位元)寄存器时,其与之相对应的 rax (64位元)寄存器的值,也将发生改变,唯一的区别就是可以存储的二进制位宽 是不同的,一个32,一个64。这就相当于,mov eax,1 那么 rax = 1,但 rax 寄存器设置超出 32bit 的数值时,eax 只可以反应其低 32bit 的值。
dotNET 的 GC 是一个很强大的东西,但有时候它会替你增加很多的麻烦,一个形象的例子是当我们创建一段连续的 buffer 我们把它传入到一个非托管的应用中,但是在一段时候后 GC 将这块内存移走了,哎呀麻烦了!非托管的代码因为继续访问了这块内存而造成了“内存无法访问”问题,故因此崩溃,而这个问题在dotNET中运行shellcode也是存在的,所以我们需要告诉GC不要去移动这块内存,不然这很有可能在你的代码运行过程突然爆一个类似如“外部组件内部错误”之类的问题。
“x64汇编代码”转换成“x64机器代码”是一个比较麻烦的问题,好在我们通过一些工具来解决这些问题,例如“A2BX64”,此工具你可以从“https://pan.baidu.com/s/1hyslxMuX1MULBY6OvQpEWA”这里获取。
另一个比较麻烦的事情是,”A2BX64“ 转换的”机器代码“只支持 vb 与 C 语言的格式,所以你还需要把它转换成例如 C#的数组的值,当你可以修改下面的 C 代码简单用用。
int main()
{
#define SHELLCODECOUNT 60
char shellcode[60] =
"\x89\x4C\x24\x08" //3
"\x55" //4
"\x57" //5
"\x48\x81\xEC\xC8\x00\x00\x00" //12
"\x48\x8B\xEC" //15
"\x48\x8B\xFC" //18
"\xB9\x32\x00\x00\x00" //23
"\xB8\xCC\xCC\xCC\xCC" //28
"\xF3\xAB" //30
"\x8B\x8D\xE0\x00\x00\x00" //36
"\xB8\x01\x00\x00\x00" //41
"\xF0\x67\x0F\xC1\x01" //46
"\xFF\xC0" //48
"\x48\x8D\xA5\xC8\x00\x00\x00" //55
"\x5F" //56
"\x5D" //57
"\xC3"; //58
for (int i = 0; i < SHELLCODECOUNT; i++) {
if (i <= 0) {
printf("{");
}
printf("%d", (int)(unsigned char)shellcode[i]);
if (i + 1 >= SHELLCODECOUNT) {
printf("}");
}
else {
printf(",");
}
}
return getchar();
}
以下提供 dotNET at C# 语言的实现,但要运行此代码之前你必须要指定你的 C# 项目的目标平台为”x64“才可以,否则代码是无法被正确运行的。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
class Shell64
{
[DllImport("kernel32.dll")]
private static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int __InterlockedIncrement32(ref int n);
/*
mov dword ptr [rsp+8],ecx
push rbp
push rdi
sub rsp,0C8h
mov rbp,rsp
mov rdi,rsp
mov ecx,32h
mov eax,0CCCCCCCCh
rep stos dword ptr [rdi]
mov ecx,dword ptr [rbp+00000000000000E0h]
mov eax,1
lock xadd dword ptr[ecx],eax
inc eax
lea rsp,[rbp+00000000000000C8h]
pop rdi
pop rbp
ret
*/
private static byte[] shellcode =
{
137,76,36,8,85,87,72,129,236,200,0,0,0,72,139,236,72,139,252,185,50,0,0,0,184,204,204,204,204,
243,171,139,141,224,0,0,0,184,1,0,0,0,240,103,15,193,1,255,192,72,141,165,200,0,0,0,95,93,195,0
};
private static __InterlockedIncrement32 InterlockedIncrement32;
static void Main()
{
IntPtr address = GCHandle.Alloc(shellcode, GCHandleType.Pinned).AddrOfPinnedObject();
VirtualProtectEx(Process.GetCurrentProcess().Handle, address, (uint)shellcode.Length, 0x40, out uint lpflOldProtect);
InterlockedIncrement32 = (__InterlockedIncrement32)Marshal.GetDelegateForFunctionPointer(address, typeof(__InterlockedIncrement32));
{
ISet<int> set = new HashSet<int>();
int n1 = 0;
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(() =>
{
Thread current = Thread.CurrentThread;
for (int c = 0; c < 100; c++)
{
int quantity = InterlockedIncrement32(ref n1);
set.Add(quantity);
Console.WriteLine("Thread: {0}, Output: {1}", current.Name, quantity);
}
});
t.Name = i.ToString();
t.Start();
}
Console.ReadKey(false);
if (set.Count == (10 * 100))
{
Console.WriteLine("Atomic operation results meet established expectations");
}
else
{
Console.WriteLine("No guarantee to the uniqueness of the atom, the result of the operation is not consistent with the expected");
}
}
Console.ReadKey(false);
}
}